Skip to content

Commit 716a677

Browse files
committed
test(helm): add helm-unittest suites + CI workflow + ci values matrix
- 7 helm-unittest suites covering smoke, validators, secret modes, envDefaults secret-mode-aware inlining (round-9 regression net), chart-computed env keys (round-8 regression net), NetworkPolicy shape, and PDB/HPA conditional rendering (38 tests, ~265ms). - ci/*.yaml render fixtures for default, production, existingSecret, ESO, and external-db install modes. - GitHub Actions workflow runs helm lint --strict, helm unittest, helm template across the ci matrix, and kubeconform validation against Kubernetes 1.30 schemas. - CONTRIBUTING.md documents how to run the same gates locally.
1 parent 33a45a0 commit 716a677

14 files changed

Lines changed: 810 additions & 0 deletions

.github/workflows/helm-chart.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Helm Chart
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'helm/**'
7+
- '.github/workflows/helm-chart.yml'
8+
push:
9+
branches:
10+
- main
11+
paths:
12+
- 'helm/**'
13+
- '.github/workflows/helm-chart.yml'
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
lint-test:
20+
name: Lint, unit-test, render, validate
21+
runs-on: ubuntu-latest
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@v4
25+
26+
- name: Set up Helm
27+
uses: azure/setup-helm@v4
28+
with:
29+
version: v3.16.2
30+
31+
- name: Install helm-unittest plugin
32+
run: helm plugin install https://github.com/helm-unittest/helm-unittest --version v0.7.2
33+
34+
- name: Helm dependency build
35+
run: helm dependency build helm/sim
36+
37+
- name: Helm lint
38+
run: helm lint helm/sim --strict
39+
40+
- name: Helm unit tests
41+
run: helm unittest helm/sim
42+
43+
- name: Render every ci/*.yaml
44+
run: |
45+
set -euo pipefail
46+
for f in helm/sim/ci/*.yaml; do
47+
echo "::group::Render $f"
48+
helm template release helm/sim -f "$f" > /tmp/render.yaml
49+
echo "::endgroup::"
50+
done
51+
52+
- name: Install kubeconform
53+
run: |
54+
curl -sSL -o /tmp/kubeconform.tar.gz \
55+
https://github.com/yannh/kubeconform/releases/download/v0.6.7/kubeconform-linux-amd64.tar.gz
56+
tar -xzf /tmp/kubeconform.tar.gz -C /tmp
57+
sudo mv /tmp/kubeconform /usr/local/bin/kubeconform
58+
kubeconform -v
59+
60+
- name: kubeconform validate every ci/*.yaml
61+
run: |
62+
set -euo pipefail
63+
for f in helm/sim/ci/*.yaml; do
64+
echo "::group::kubeconform $f"
65+
helm template release helm/sim -f "$f" \
66+
| kubeconform \
67+
-strict \
68+
-ignore-missing-schemas \
69+
-kubernetes-version 1.30.0 \
70+
-summary
71+
echo "::endgroup::"
72+
done

helm/sim/CONTRIBUTING.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Contributing to the Sim Helm chart
2+
3+
Thanks for improving the chart. This page covers how to run the checks that CI
4+
runs on every PR, so you can catch regressions locally before pushing.
5+
6+
## Prerequisites
7+
8+
- [Helm](https://helm.sh/docs/intro/install/) v3.16+
9+
- The [`helm-unittest`](https://github.com/helm-unittest/helm-unittest) plugin:
10+
11+
```bash
12+
helm plugin install https://github.com/helm-unittest/helm-unittest --version v0.7.2
13+
```
14+
15+
- (Optional, for schema validation) [kubeconform](https://github.com/yannh/kubeconform)
16+
17+
## What CI runs
18+
19+
The `Helm Chart` workflow (`.github/workflows/helm-chart.yml`) runs four gates:
20+
21+
1. **`helm lint --strict`** — catches template syntax errors and chart-metadata problems.
22+
2. **`helm unittest`** — runs every YAML suite in `helm/sim/tests/`.
23+
3. **`helm template`** against every file in `helm/sim/ci/` — proves the chart
24+
actually renders under each supported install mode.
25+
4. **`kubeconform`** on the rendered output — validates every manifest against
26+
Kubernetes API schemas (`-kubernetes-version 1.30.0`, `-strict`,
27+
`-ignore-missing-schemas` so CRDs from optional dependencies don't fail).
28+
29+
## Running the same checks locally
30+
31+
```bash
32+
cd helm/sim
33+
helm dependency build
34+
helm lint . --strict
35+
helm unittest .
36+
for f in ci/*.yaml; do
37+
helm template t . -f "$f" > /dev/null
38+
done
39+
```
40+
41+
If you have kubeconform installed:
42+
43+
```bash
44+
for f in ci/*.yaml; do
45+
helm template t . -f "$f" | kubeconform -strict -ignore-missing-schemas \
46+
-kubernetes-version 1.30.0 -summary
47+
done
48+
```
49+
50+
## Adding a unit test
51+
52+
Tests live in `helm/sim/tests/` and use the
53+
[helm-unittest DSL](https://github.com/helm-unittest/helm-unittest/blob/main/DOCUMENT.md).
54+
Each file is one suite. A test sets values, renders a template, and asserts on
55+
the rendered manifest:
56+
57+
```yaml
58+
suite: my feature
59+
release:
60+
name: t
61+
namespace: sim
62+
tests:
63+
- it: renders the thing
64+
template: deployment-app.yaml
65+
set:
66+
app.env.BETTER_AUTH_SECRET: x
67+
app.env.ENCRYPTION_KEY: x
68+
app.env.INTERNAL_API_SECRET: x
69+
app.env.CRON_SECRET: x
70+
postgresql.auth.password: x
71+
myFeature.enabled: true
72+
asserts:
73+
- contains:
74+
path: spec.template.spec.containers[0].env
75+
content: { name: MY_FEATURE_FLAG, value: "true" }
76+
```
77+
78+
Use the existing suites as references:
79+
80+
- `tests/smoke_test.yaml` — minimal render checks
81+
- `tests/validators_test.yaml` — `failedTemplate` assertions for required-value gates
82+
- `tests/secret-modes_test.yaml` — inline / existingSecret / ESO routing
83+
- `tests/env-defaults_test.yaml` — `envDefaults` secret-mode-aware inlining
84+
- `tests/chart-computed-env_test.yaml` — chart-computed env keys cannot be overridden
85+
- `tests/networkpolicy_test.yaml` — NetworkPolicy ingress/egress shape
86+
- `tests/pdb-hpa_test.yaml` — PDB tri-state + HPA conditional rendering
87+
88+
When you fix a template bug, please add a regression test for it.
89+
90+
## Adding a ci/*.yaml render fixture
91+
92+
`helm/sim/ci/*.yaml` files are minimal values overlays that CI renders end-to-end
93+
plus validates with kubeconform. Add one when you introduce a new install mode
94+
(new secret backend, new database backend, new deployment topology). Keep them
95+
small — they should test that the mode *renders*, not exercise every option;
96+
detailed behavior belongs in unit tests.
97+
98+
## Updating `Chart.yaml`
99+
100+
- Bump `version` (the chart version) on every user-visible change.
101+
- Bump `appVersion` when targeting a new Sim release.
102+
- Follow SemVer: breaking values changes → major bump, additive → minor, fix → patch.
103+
104+
## Touching `values.schema.json`
105+
106+
The chart ships a JSON Schema that validates user-supplied values. If you add a
107+
new top-level value or change a type, update the schema in the same PR.

helm/sim/ci/default-values.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
app:
2+
env:
3+
BETTER_AUTH_SECRET: ci-test-better-auth-secret-32chars
4+
ENCRYPTION_KEY: ci-test-encryption-key-32-chars-ok
5+
INTERNAL_API_SECRET: ci-test-internal-api-secret-32chr
6+
CRON_SECRET: ci-test-cron-secret-32-characters
7+
8+
postgresql:
9+
auth:
10+
password: ci-test-postgres-password
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
app:
2+
secrets:
3+
existingSecret:
4+
enabled: true
5+
name: my-existing-secret
6+
7+
postgresql:
8+
auth:
9+
password: ci-test-postgres-password
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
app:
2+
env:
3+
BETTER_AUTH_SECRET: ci-test-better-auth-secret-32chars
4+
ENCRYPTION_KEY: ci-test-encryption-key-32-chars-ok
5+
INTERNAL_API_SECRET: ci-test-internal-api-secret-32chr
6+
CRON_SECRET: ci-test-cron-secret-32-characters
7+
8+
postgresql:
9+
enabled: false
10+
11+
externalDatabase:
12+
enabled: true
13+
host: db.example.com
14+
port: 5432
15+
database: simstudio
16+
username: simstudio
17+
password: ci-test-external-db-password
18+
sslMode: require
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
externalSecrets:
2+
enabled: true
3+
secretStoreRef:
4+
name: sim-store
5+
kind: ClusterSecretStore
6+
remoteRefs:
7+
app:
8+
BETTER_AUTH_SECRET: secrets/sim/auth
9+
ENCRYPTION_KEY: secrets/sim/enc
10+
INTERNAL_API_SECRET: secrets/sim/iapi
11+
CRON_SECRET: secrets/sim/cron
12+
postgresql:
13+
password: secrets/sim/pgpw
14+
15+
postgresql:
16+
auth:
17+
password: ci-test-postgres-password

helm/sim/ci/production-values.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
app:
2+
replicaCount: 2
3+
env:
4+
BETTER_AUTH_SECRET: ci-test-better-auth-secret-32chars
5+
ENCRYPTION_KEY: ci-test-encryption-key-32-chars-ok
6+
INTERNAL_API_SECRET: ci-test-internal-api-secret-32chr
7+
CRON_SECRET: ci-test-cron-secret-32-characters
8+
9+
realtime:
10+
replicaCount: 2
11+
12+
postgresql:
13+
auth:
14+
password: ci-test-postgres-password
15+
16+
autoscaling:
17+
enabled: true
18+
minReplicas: 2
19+
maxReplicas: 6
20+
21+
networkPolicy:
22+
enabled: true
23+
24+
ingress:
25+
enabled: true
26+
app:
27+
host: sim.example.com
28+
realtime:
29+
host: realtime.example.com
30+
31+
podDisruptionBudget:
32+
enabled: true
33+
minAvailable: 1
34+
35+
telemetry:
36+
enabled: true
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
suite: chart-computed env keys can't be overridden (round-8 regression net)
2+
release:
3+
name: t
4+
namespace: sim
5+
6+
tests:
7+
- it: app pod uses chart-computed DATABASE_URL even when user tries to override
8+
template: deployment-app.yaml
9+
set:
10+
app.env.BETTER_AUTH_SECRET: x
11+
app.env.ENCRYPTION_KEY: x
12+
app.env.INTERNAL_API_SECRET: x
13+
app.env.CRON_SECRET: x
14+
app.env.DATABASE_URL: "should-be-ignored"
15+
postgresql.auth.password: x
16+
asserts:
17+
- notContains:
18+
path: spec.template.spec.containers[0].env
19+
content:
20+
name: DATABASE_URL
21+
value: "should-be-ignored"
22+
23+
- it: existingSecret inline path skips DATABASE_URL/SOCKET_SERVER_URL
24+
template: deployment-app.yaml
25+
set:
26+
app.secrets.existingSecret.enabled: true
27+
app.secrets.existingSecret.name: my-secret
28+
app.env.DATABASE_URL: "postgres://evil-1:5432/x"
29+
app.env.SOCKET_SERVER_URL: "https://evil-2.example.com"
30+
postgresql.auth.password: x
31+
asserts:
32+
- notContains:
33+
path: spec.template.spec.containers[0].env
34+
content: { name: DATABASE_URL, value: "postgres://evil-1:5432/x" }
35+
- notContains:
36+
path: spec.template.spec.containers[0].env
37+
content: { name: SOCKET_SERVER_URL, value: "https://evil-2.example.com" }
38+
39+
- it: realtime pod existingSecret inline path skips chart-computed keys (round-8)
40+
template: deployment-realtime.yaml
41+
set:
42+
app.secrets.existingSecret.enabled: true
43+
app.secrets.existingSecret.name: my-secret
44+
app.env.DATABASE_URL: "postgres://evil-1:5432/x"
45+
app.env.SOCKET_SERVER_URL: "https://evil-2.example.com"
46+
postgresql.auth.password: x
47+
asserts:
48+
- notContains:
49+
path: spec.template.spec.containers[0].env
50+
content: { name: SOCKET_SERVER_URL, value: "https://evil-2.example.com" }

0 commit comments

Comments
 (0)