Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions internal/plan/view/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (

"deployah.dev/deployah/internal/plan/semantic"
"deployah.dev/deployah/internal/plan/view"

jsonschema "github.com/santhosh-tekuri/jsonschema/v6"
)

var update = flag.Bool("update", false, "update golden files")
Expand Down Expand Up @@ -367,17 +369,35 @@ func assertJSONPaths(tb testing.TB, raw []byte, wants []jsonPathWant) {
}

// assertGolden is the primary golden contract: got must equal
// testdata/golden/<name>.golden in full. Contains checks in callers are
// secondary invariants only. A normal run never skips the comparison.
// -update rewrites the file from got, then the same equality still runs.
// testdata/golden/<name>.golden in full. A mismatch stops the test.
// A normal run never skips the comparison. -update rewrites the file
// from got, then the same equality still runs.
func assertGolden(t *testing.T, name, got string) {
t.Helper()
assert.Equal(t, string(readGolden(t, name, got)), got)
require.Equal(t, string(readGolden(t, name, got)), got)
}

func assertJSONGolden(t *testing.T, name, got string) {
t.Helper()
assert.JSONEq(t, string(readGolden(t, name, got)), got)
require.JSONEq(t, string(readGolden(t, name, got)), got)
}

func compilePlanSchema(t *testing.T) *jsonschema.Schema {
t.Helper()
compiler := jsonschema.NewCompiler()
doc, err := jsonschema.UnmarshalJSON(bytes.NewReader(view.SchemaV1()))
require.NoError(t, err)
require.NoError(t, compiler.AddResource(view.SchemaV1ID, doc))
sch, err := compiler.Compile(view.SchemaV1ID)
require.NoError(t, err)
return sch
}

func validatePlanSchema(t *testing.T, raw []byte) {
t.Helper()
var v any
require.NoError(t, json.Unmarshal(raw, &v))
require.NoError(t, compilePlanSchema(t).Validate(v))
}

func readGolden(t *testing.T, name, got string) []byte {
Expand Down
7 changes: 0 additions & 7 deletions internal/plan/view/human_chart_crd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,27 @@ func TestWriteHuman_ChartCRDLifecycle(t *testing.T) {
action semantic.HelmAction
lc semantic.ChartCRDLifecycle
golden string
note string
}{
{
name: "process",
header: fresh,
action: semantic.HelmInstall,
lc: semantic.ChartCRDProcess,
golden: "human_chart_crds_process",
note: "lifecycle: process (Helm install will process this chart CRD)",
},
{
name: "skip",
header: fresh,
action: semantic.HelmInstall,
lc: semantic.ChartCRDSkip,
golden: "human_chart_crds_skip",
note: "lifecycle: skip (Helm install will skip this chart CRD)",
},
{
name: "upgrade",
header: upgrade,
action: semantic.HelmUpgrade,
lc: semantic.ChartCRDUpgrade,
golden: "human_chart_crds_upgrade",
note: "lifecycle: upgrade (Helm upgrade does not process chart CRDs)",
},
}
for _, tt := range tests {
Expand All @@ -80,9 +76,6 @@ func TestWriteHuman_ChartCRDLifecycle(t *testing.T) {
})
text := writeHuman(t, p)
assertGolden(t, tt.golden, text)
assertHumanLayout(t, text)
assert.Contains(t, text, " Resources: 0 create, 0 update, 0 delete")
assert.Contains(t, text, tt.note)
assertChartCRDSectionHasNoDiff(t, chartCRDSection(t, text))
})
}
Expand Down
24 changes: 9 additions & 15 deletions internal/plan/view/human_norm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ import (
func TestWriteHuman_SecretDiffs(t *testing.T) {
t.Parallel()
addAfter := secretObj("s", "same-pass", "same-tok")
secretData(t, addAfter)["extra"] = "added-secret"
addData, ok := addAfter["data"].(map[string]any)
require.True(t, ok)
addData["extra"] = "added-secret"
removeBefore := secretObj("s", "same-pass", "same-tok")
secretData(t, removeBefore)["extra"] = "removed-secret"
removeData, ok := removeBefore["data"].(map[string]any)
require.True(t, ok)
removeData["extra"] = "removed-secret"
emptySecret := map[string]any{
"apiVersion": "v1",
"kind": "Secret",
Expand Down Expand Up @@ -166,10 +170,9 @@ func TestWriteHuman_OmitsBookkeepingFields(t *testing.T) {
After: snap(after),
}})

var human, jsonBuf bytes.Buffer
require.NoError(t, view.WriteHuman(&human, p, view.Options{}))
text := writeHuman(t, p)
var jsonBuf bytes.Buffer
require.NoError(t, view.WriteJSON(&jsonBuf, p, view.Options{}))
text := human.String()
assertNoBookkeeping(t, text)
assert.Contains(t, text, "- key: v1")
assert.Contains(t, text, "+ key: v2")
Expand Down Expand Up @@ -210,9 +213,7 @@ func TestWriteHuman_OmitsBookkeepingOnCreateDelete(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
p := mustPlan(t, semantic.HelmUpgrade, tt.changes)
var buf bytes.Buffer
require.NoError(t, view.WriteHuman(&buf, p, view.Options{}))
text := buf.String()
text := writeHuman(t, p)
assertNoBookkeeping(t, text)
assertKeepsUserMeta(t, text)
for _, line := range tt.want {
Expand All @@ -221,10 +222,3 @@ func TestWriteHuman_OmitsBookkeepingOnCreateDelete(t *testing.T) {
})
}
}

func secretData(t *testing.T, obj map[string]any) map[string]any {
t.Helper()
m, ok := obj["data"].(map[string]any)
require.True(t, ok)
return m
}
61 changes: 13 additions & 48 deletions internal/plan/view/human_semantic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,31 +61,11 @@ func TestWriteHuman_SemanticPlan(t *testing.T) {
t.Parallel()
p := semanticUpgradePlan(t, previousProductSpec(), currentProductSpec())
text := writeHuman(t, p)
assertGolden(t, "human_semantic_plan", text) // full-output contract; Contains below are invariants only
assertHumanLayout(t, text)
assertSemanticHeader(t, text, 2)
assertGolden(t, "human_semantic_plan", text)
assert.Equal(t, "web-prod", p.Header.Release)
assert.Equal(t, productNamespace, p.Header.Namespace)
assert.False(t, p.Header.FreshInstall)
assert.Contains(t, text, `~ update apps/v1/Deployment "web-prod-api"`)
assert.Contains(t, text, `+ create apps/v1/Deployment "web-prod-worker"`)
assert.Contains(t, text, `- delete apps/v1/Deployment "web-prod-legacy"`)
assert.Contains(t, text, "deployah.dev/project: web")
assert.Contains(t, text, "deployah.dev/environment: prod")
assert.Contains(t, text, "deployah.dev/instance: web-prod")
assert.Contains(t, text, "app.kubernetes.io/managed-by: Helm")
assert.Contains(t, text, "helm.sh/chart:")
assert.Contains(t, text, "helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded")
assert.Contains(t, text, " preDeploy")
assert.Contains(t, text, "~ migrate changed, will run")
assert.Contains(t, text, " postDeploy")
assert.Contains(t, text, "+ smoke new, will run")
assert.Contains(t, text, "- old-check removed")
assert.Contains(t, text, " schedule")
assert.Contains(t, text, "~ cleanup changed")
assert.NotContains(t, text, "~ cleanup changed, will run")
assert.Contains(t, text, `- schedule: 0 2 * * *`)
assert.Contains(t, text, `+ schedule: 0 3 * * *`)
assertCronJobOnlyUnderTasks(t, text, "web-prod-cleanup")
assert.NotContains(t, text, `create v1/Namespace`)
assert.NotContains(t, text, "-/+")
Expand Down Expand Up @@ -145,20 +125,6 @@ func TestWriteHuman_SemanticScheduleDelete(t *testing.T) {
assert.NotContains(t, text, "-/+")
}

func assertSemanticHeader(t *testing.T, text string, revision int) {
t.Helper()
block := fmt.Sprintf("Context: %s\nNamespace: %s\nRelease: web-prod\nRevision: %d\n", productClusterContext, productNamespace, revision)
assert.Contains(t, text, block)
contextIdx := strings.Index(text, "Context:")
nsIdx := strings.Index(text, "Namespace:")
relIdx := strings.Index(text, "Release:")
revIdx := strings.Index(text, "Revision:")
require.Greater(t, contextIdx, -1)
assert.Greater(t, nsIdx, contextIdx)
assert.Greater(t, relIdx, nsIdx)
assert.Greater(t, revIdx, relIdx)
}

func assertCronJobOnlyUnderTasks(t *testing.T, text, name string) {
t.Helper()
needle := fmt.Sprintf(`batch/v1/CronJob %q`, name)
Expand Down Expand Up @@ -348,7 +314,18 @@ func previousReleaseFromRender(t *testing.T, result *render.RenderResult) *v1.Re
if values == nil {
values = map[string]any{}
}
tasks, ok := nestedMap(values, "deployah", "resolved", "tasks")
cur := values
var tasks map[string]any
ok := true
for _, key := range []string{"deployah", "resolved", "tasks"} {
next, isMap := cur[key].(map[string]any)
if !isMap || next == nil {
ok = false
break
}
cur = next
tasks = next
}
require.True(t, ok && len(tasks) > 0, "previous chart values must include deployah.resolved.tasks")
return &v1.Release{
Name: result.ReleaseName,
Expand All @@ -360,18 +337,6 @@ func previousReleaseFromRender(t *testing.T, result *render.RenderResult) *v1.Re
}
}

func nestedMap(root map[string]any, keys ...string) (map[string]any, bool) {
cur := root
for _, key := range keys {
next, ok := cur[key].(map[string]any)
if !ok || next == nil {
return nil, false
}
cur = next
}
return cur, true
}

func buildSemanticPlan(t *testing.T, client plan.SemanticBuildClient, resolved *spec.ResolvedSpec, live plan.LiveReader) semantic.Plan {
t.Helper()
p, _, cleanup, err := plan.BuildSemanticPlan(t.Context(), client, productMapper{}, live, plan.SemanticBuildInput{
Expand Down
19 changes: 9 additions & 10 deletions internal/plan/view/human_style_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ func TestWriteHuman_StylerPreservesLayout(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
plain := assertRoleTrace(t, tt.golden+".roles", tt.plan)
assertGolden(t, tt.golden, plain)
assertRoleTrace(t, tt.golden+".roles", tt.plan)
})
}
}
Expand Down Expand Up @@ -155,7 +154,7 @@ func TestWriteHuman_StyleRoles(t *testing.T) {
}{
{
name: "create",
golden: "style_create",
golden: "human_create.roles",
plan: mustPlanWithHeader(t, header, semantic.HelmUpgrade, []semantic.ResourceChange{{
Resource: ref("ConfigMap", "app"),
Action: semantic.Create,
Expand All @@ -164,7 +163,7 @@ func TestWriteHuman_StyleRoles(t *testing.T) {
},
{
name: "delete",
golden: "style_delete",
golden: "human_delete.roles",
plan: mustPlanWithHeader(t, header, semantic.HelmUpgrade, []semantic.ResourceChange{{
Resource: ref("ConfigMap", "old"),
Action: semantic.Delete,
Expand All @@ -173,19 +172,19 @@ func TestWriteHuman_StyleRoles(t *testing.T) {
},
{
name: "update",
golden: "style_update",
golden: "human_update.roles",
plan: mustPlanWithHeader(t, header, semantic.HelmUpgrade, []semantic.ResourceChange{{
Resource: ref("ConfigMap", "web"),
Action: semantic.Update,
Before: snap(cm("web", "v1")),
After: snap(cm("web", "v2")),
}}, nil),
},
{name: "drift unexpected", golden: "style_drift_unexpected", plan: unexpected},
{name: "drift missing", golden: "style_drift_missing", plan: missing},
{name: "drift modified", golden: "style_drift_modified", plan: modified},
{name: "tasks", golden: "style_tasks", plan: tasks},
{name: "chart crds", golden: "style_chart_crds", plan: chartCRDs},
{name: "drift unexpected", golden: "human_drift_unexpected.roles", plan: unexpected},
{name: "drift missing", golden: "human_drift_missing.roles", plan: missing},
{name: "drift modified", golden: "human_drift_modified.roles", plan: modified},
{name: "tasks", golden: "human_tasks.roles", plan: tasks},
{name: "chart crds", golden: "human_chart_crds.roles", plan: chartCRDs},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading
Loading