// Copyright 2026 Alibaba Group // Licensed under the Apache License, Version 2.0 (the "License"); package interfacesnapshot import ( "encoding/json" "reflect" "strings" "testing" ) func TestCrossPlatformCoverageFlagMigrationStrictJSONSchemaErrors(t *testing.T) { tests := []struct { name string input string wantErr string }{ { name: "schema validator rejects a trailing value", input: coverageManifestJSON() + ` {}`, wantErr: "trailing JSON value", }, { name: "schema validator reports malformed trailing data", input: coverageManifestJSON() + ` ?`, wantErr: "read trailing flag migration manifest data", }, { name: "schema validator reports an empty document", input: "", wantErr: "read flag migration manifest value at $", }, { name: "schema validator rejects an unknown field", input: `{"version":1,"migrations":[],"unexpected":true}`, wantErr: `unknown field "unexpected"`, }, { name: "schema validator reports a malformed field token", input: `{"`, wantErr: "flag migration manifest", }, { name: "schema validator reports an unclosed object", input: `{"version":1`, wantErr: "close flag migration manifest object", }, { name: "schema validator reports an unclosed array", input: `{"version":1,"migrations":[`, wantErr: "close flag migration manifest array", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { err := validateFlagMigrationJSONSchema([]byte(test.input)) if err == nil || !strings.Contains(err.Error(), test.wantErr) { t.Fatalf("validateFlagMigrationJSONSchema() error = %v, want %q", err, test.wantErr) } }) } } func TestCrossPlatformCoverageFlagMigrationStrictJSONTypeDiagnostics(t *testing.T) { tests := []struct { name string input string schema reflect.Type wantErr string }{ { name: "manifest must be an object", input: `[]`, schema: reflect.TypeOf(FlagMigrationManifest{}), wantErr: "must be an object, got array", }, { name: "migrations must be an array", input: `{}`, schema: reflect.TypeOf([]FlagMigration{}), wantErr: "must be an array, got object", }, { name: "integer rejects string", input: `"one"`, schema: reflect.TypeOf(int(0)), wantErr: "must be a number, got string", }, { name: "string rejects number", input: `1`, schema: reflect.TypeOf(""), wantErr: "must be a string, got number", }, { name: "string rejects boolean", input: `false`, schema: reflect.TypeOf(""), wantErr: "must be a string, got boolean", }, { name: "unsupported schema fails closed", input: `1`, schema: reflect.TypeOf(float64(0)), wantErr: "unsupported Go schema type float64", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { decoder := json.NewDecoder(strings.NewReader(test.input)) decoder.UseNumber() err := validateMigrationJSONValue(decoder, "$", test.schema) if err == nil || !strings.Contains(err.Error(), test.wantErr) { t.Fatalf("validateMigrationJSONValue() error = %v, want %q", err, test.wantErr) } }) } } func TestCrossPlatformCoverageFlagMigrationStrictJSONSchemaHelpers(t *testing.T) { type fieldFixture struct { DefaultName string Ignored string `json:"-"` Tagged string `json:"tagged,omitempty"` } fields := migrationJSONFields(reflect.TypeOf(fieldFixture{})) if _, exists := fields["Ignored"]; exists { t.Fatal("migrationJSONFields() retained a json:- field") } if fields["DefaultName"] != reflect.TypeOf("") || fields["tagged"] != reflect.TypeOf("") { t.Fatalf("migrationJSONFields() = %#v, want default and tagged string fields", fields) } if got := migrationJSONKindDescription(reflect.TypeOf(float64(0))); got != "the declared JSON type" { t.Fatalf("migrationJSONKindDescription(float64) = %q", got) } for _, test := range []struct { name string token json.Token want string }{ {name: "closing delimiter", token: json.Delim('}'), want: `delimiter "}"`}, {name: "unsupported token", token: struct{}{}, want: "struct {}"}, } { t.Run(test.name, func(t *testing.T) { if got := migrationJSONTokenDescription(test.token); got != test.want { t.Fatalf("migrationJSONTokenDescription() = %q, want %q", got, test.want) } }) } } func TestCrossPlatformCoverageFlagMigrationManifestParserEdges(t *testing.T) { tests := []struct { name string input string wantErr string }{ { name: "initial malformed JSON", input: `{`, wantErr: "unexpected EOF", }, { name: "multiple JSON values", input: coverageManifestJSON() + ` {}`, wantErr: "trailing multiple JSON values", }, { name: "malformed trailing JSON", input: coverageManifestJSON() + ` {`, wantErr: "read trailing flag migration manifest data", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { _, err := ReadFlagMigrationManifest(strings.NewReader(test.input)) if err == nil || !strings.Contains(err.Error(), test.wantErr) { t.Fatalf("ReadFlagMigrationManifest() error = %v, want %q", err, test.wantErr) } }) } } func TestCrossPlatformCoverageFlagMigrationManifestRejectsEveryContractDrift(t *testing.T) { optionalCanonical := func() FlagMigrationManifest { manifest := coverageOptionalManifest(FlagMigrationPending) manifest.Migrations[0].Canonical.Before = FlagMigrationState{ Present: true, Type: "string", Scope: "local", } return manifest } tests := []struct { name string make func() FlagMigrationManifest mutate func(*FlagMigrationManifest) wantErr string }{ { name: "canonical flag name is exact", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.Name = "--message-id" }, wantErr: "canonical name must be an exact canonical flag", }, { name: "legacy and canonical names differ", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.Name = m.Migrations[0].Legacy.Name }, wantErr: "legacy and canonical flags must differ", }, { name: "legacy before state validates", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.Before.Scope = "global" }, wantErr: "legacy before present state has invalid scope", }, { name: "legacy after state validates", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.After.Type = "" }, wantErr: "legacy after present state requires type", }, { name: "canonical before state validates", make: optionalCanonical, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.Before.Type = "" }, wantErr: "canonical before present state requires type", }, { name: "canonical after state validates", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.After.Scope = "" }, wantErr: "canonical after present state has invalid scope", }, { name: "legacy before remains present", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.Before = FlagMigrationState{} }, wantErr: "legacy flag must remain present before and after migration", }, { name: "legacy after remains present", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.After = FlagMigrationState{} }, wantErr: "legacy flag must remain present before and after migration", }, { name: "legacy starts visible", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.Before.Hidden = true }, wantErr: "legacy flag must migrate exactly from visible to hidden", }, { name: "legacy ends hidden", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.After.Hidden = false }, wantErr: "legacy flag must migrate exactly from visible to hidden", }, { name: "canonical after remains present", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.After = FlagMigrationState{} }, wantErr: "canonical flag must be present after migration", }, { name: "required legacy cannot become optional canonical", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.After.Required = false }, wantErr: "requiredness must be preserved from legacy before to canonical after", }, { name: "optional legacy cannot become required canonical", make: func() FlagMigrationManifest { return coverageOptionalManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.After.Required = true }, wantErr: "requiredness must be preserved from legacy before to canonical after", }, { name: "existing canonical cannot change requiredness", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(manifest *FlagMigrationManifest) { manifest.Migrations[0].Canonical.Before = FlagMigrationState{ Present: true, Type: "string", Scope: "local", } }, wantErr: "canonical flag requiredness must remain unchanged when already present", }, { name: "hidden legacy alias is not independently required", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.After.Required = true }, wantErr: "legacy compatibility alias must not remain independently required", }, { name: "legacy after declares alias target", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.After.AliasOf = "other-id" }, wantErr: `legacy flag after state must declare alias_of "message-id"`, }, { name: "legacy before cannot declare alias", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.Before.AliasOf = "message-id" }, wantErr: "alias_of is only valid on the legacy after state", }, { name: "canonical before cannot declare alias", make: optionalCanonical, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.Before.AliasOf = "message-id" }, wantErr: "alias_of is only valid on the legacy after state", }, { name: "canonical after cannot declare alias", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.After.AliasOf = "message-id" }, wantErr: "alias_of is only valid on the legacy after state", }, { name: "canonical type remains stable", make: optionalCanonical, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.Before.Type = "stringSlice" }, wantErr: "canonical flag type must remain unchanged", }, { name: "canonical shorthand remains stable", make: optionalCanonical, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.Before.Shorthand = "m" }, wantErr: "canonical flag shorthand must remain unchanged", }, { name: "canonical no-opt remains stable", make: optionalCanonical, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.Before.NoOpt = "auto" }, wantErr: "canonical flag no_opt must remain unchanged", }, { name: "canonical scope remains stable", make: optionalCanonical, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.Before.Scope = "inherited" }, wantErr: "canonical flag scope must remain unchanged", }, { name: "legacy before type matches legacy after", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.Before.Type = "stringSlice" }, wantErr: "legacy and canonical flag types must match exactly", }, { name: "legacy after type matches canonical after", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Canonical.After.Type = "stringSlice" }, wantErr: "legacy and canonical flag types must match exactly", }, { name: "legacy shorthand remains stable", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.After.Shorthand = "x" }, wantErr: "legacy flag shorthand must remain unchanged", }, { name: "legacy no-opt remains stable", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.After.NoOpt = "" }, wantErr: "legacy flag no_opt must remain unchanged", }, { name: "legacy scope remains stable", make: func() FlagMigrationManifest { return coverageManifest(FlagMigrationPending) }, mutate: func(m *FlagMigrationManifest) { m.Migrations[0].Legacy.After.Scope = "inherited" }, wantErr: "legacy flag scope must remain unchanged", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { manifest := test.make() test.mutate(&manifest) if err := manifest.Validate(); err == nil || !strings.Contains(err.Error(), test.wantErr) { t.Fatalf("Validate() error = %v, want %q", err, test.wantErr) } }) } } func TestCrossPlatformCoverageFlagMigrationManifestRejectsAbsentAttributesAndTargetConflicts(t *testing.T) { stateMutations := []struct { name string mutate func(*FlagMigrationState) }{ {name: "type", mutate: func(s *FlagMigrationState) { s.Type = "string" }}, {name: "required", mutate: func(s *FlagMigrationState) { s.Required = true }}, {name: "hidden", mutate: func(s *FlagMigrationState) { s.Hidden = true }}, {name: "shorthand", mutate: func(s *FlagMigrationState) { s.Shorthand = "m" }}, {name: "no-opt", mutate: func(s *FlagMigrationState) { s.NoOpt = "auto" }}, {name: "scope", mutate: func(s *FlagMigrationState) { s.Scope = "local" }}, {name: "alias", mutate: func(s *FlagMigrationState) { s.AliasOf = "message-id" }}, } for _, test := range stateMutations { t.Run("absent state rejects "+test.name, func(t *testing.T) { manifest := coverageManifest(FlagMigrationPending) test.mutate(&manifest.Migrations[0].Canonical.Before) if err := manifest.Validate(); err == nil || !strings.Contains(err.Error(), "absent state must not declare flag attributes") { t.Fatalf("Validate() error = %v, want absent-state rejection", err) } }) } manifest := coverageManifest(FlagMigrationPending) second := coverageManifest(FlagMigrationPending).Migrations[0] second.Canonical.Name = "canonical-two" second.Legacy.After.AliasOf = second.Canonical.Name manifest.Migrations = append(manifest.Migrations, second) if err := manifest.Validate(); err == nil || !strings.Contains(err.Error(), "to both --message-id and --canonical-two") { t.Fatalf("Validate() error = %v, want one legacy to two canonical targets rejected", err) } } func TestCrossPlatformCoverageCompareAllWithFlagMigrationsValidatesEveryInput(t *testing.T) { before := coverageMigrationSnapshot(coverageManifest(FlagMigrationPending).Migrations[0], false, false) empty := coverageEmptyManifest() tests := []struct { name string current Snapshot references map[string]Snapshot authority FlagMigrationManifest candidate FlagMigrationManifest wantErr string }{ { name: "current snapshot", current: Snapshot{}, references: map[string]Snapshot{"merge-base": before}, authority: empty, candidate: empty, wantErr: "validate current interface snapshot", }, { name: "reference snapshot", current: before, references: map[string]Snapshot{"merge-base": {}}, authority: empty, candidate: empty, wantErr: "validate merge-base interface snapshot", }, { name: "authority manifest", current: before, references: map[string]Snapshot{"merge-base": before}, authority: FlagMigrationManifest{}, candidate: empty, wantErr: "validate approved flag migrations", }, { name: "candidate manifest", current: before, references: map[string]Snapshot{"merge-base": before}, authority: empty, candidate: FlagMigrationManifest{}, wantErr: "validate candidate flag migrations", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { _, err := CompareAllWithFlagMigrations(test.current, test.references, test.authority, test.candidate) if err == nil || !strings.Contains(err.Error(), test.wantErr) { t.Fatalf("CompareAllWithFlagMigrations() error = %v, want %q", err, test.wantErr) } }) } report, err := CompareAllWithFlagMigrations(before, map[string]Snapshot{"merge-base": before}, empty, empty) if err != nil || !report.Compatible { t.Fatalf("empty migration lifecycle = (%#v, %v), want compatible", report, err) } } func TestCrossPlatformCoverageCompareAllWithFlagMigrationsLifecycleErrors(t *testing.T) { pending := coverageManifest(FlagMigrationPending) consumed := coverageManifest(FlagMigrationConsumed) empty := coverageEmptyManifest() migration := pending.Migrations[0] before := coverageMigrationSnapshot(migration, false, false) after := coverageMigrationSnapshot(migration, true, false) partial := coverageMigrationSnapshot(migration, false, false) partial.Commands[len(partial.Commands)-1].LocalFlags[0].Hidden = true modified := coverageManifest(FlagMigrationPending) modified.Migrations[0].Reason = "A different reviewed reason." tests := []struct { name string current Snapshot references map[string]Snapshot authority FlagMigrationManifest candidate FlagMigrationManifest wantErr string }{ { name: "requires merge-base or main", current: before, references: map[string]Snapshot{"stable": before}, authority: pending, candidate: pending, wantErr: "requires a main or merge-base reference", }, { name: "pending authority requires before base", current: after, references: map[string]Snapshot{"merge-base": after, "stable": after}, authority: pending, candidate: pending, wantErr: "want exact before state for pending", }, { name: "consumed authority requires after base", current: before, references: map[string]Snapshot{"merge-base": before, "stable": before}, authority: consumed, candidate: consumed, wantErr: "want exact after state for consumed", }, { name: "missing base command is partial", current: before, references: map[string]Snapshot{"merge-base": coverageRootOnlySnapshot(), "stable": before}, authority: pending, candidate: pending, wantErr: "is partial in merge-base", }, { name: "candidate cannot modify base receipt", current: before, references: map[string]Snapshot{"merge-base": before, "stable": before}, authority: pending, candidate: modified, wantErr: "candidate modified base-owned flag migration", }, { name: "candidate cannot remove pending receipt", current: before, references: map[string]Snapshot{"merge-base": before, "stable": before}, authority: pending, candidate: empty, wantErr: "candidate removed pending flag migration", }, { name: "candidate cannot falsely consume pending receipt", current: before, references: map[string]Snapshot{"merge-base": before, "stable": before}, authority: pending, candidate: consumed, wantErr: "candidate falsely consumed unchanged flag migration", }, { name: "candidate must consume completed receipt", current: after, references: map[string]Snapshot{"merge-base": before, "stable": before}, authority: pending, candidate: pending, wantErr: "without marking it consumed", }, { name: "candidate rejects partial application", current: partial, references: map[string]Snapshot{"merge-base": before, "stable": before}, authority: pending, candidate: consumed, wantErr: "candidate partially applied flag migration", }, { name: "consumed receipt rejects current revert", current: before, references: map[string]Snapshot{"merge-base": after, "stable": before}, authority: consumed, candidate: consumed, wantErr: "candidate drifted from consumed flag migration", }, { name: "consumed receipt cannot be removed early", current: after, references: map[string]Snapshot{"merge-base": after, "stable": before}, authority: consumed, candidate: empty, wantErr: "before every reference reached the after state", }, { name: "consumed receipt cannot revert to pending", current: after, references: map[string]Snapshot{"merge-base": after, "stable": before}, authority: consumed, candidate: pending, wantErr: "back to pending", }, { name: "consumed receipt becomes stale", current: after, references: map[string]Snapshot{"merge-base": after, "stable": after}, authority: consumed, candidate: consumed, wantErr: "is stale after all references reached the after state", }, { name: "candidate-added receipt starts pending", current: before, references: map[string]Snapshot{"merge-base": before, "stable": before}, authority: empty, candidate: consumed, wantErr: "must start pending", }, { name: "candidate-added receipt matches base before", current: before, references: map[string]Snapshot{"merge-base": after, "stable": after}, authority: empty, candidate: pending, wantErr: "does not match the merge-base before state", }, { name: "candidate-added receipt cannot self-authorize", current: after, references: map[string]Snapshot{"merge-base": before, "stable": before}, authority: empty, candidate: pending, wantErr: "cannot authorize its own interface change", }, { name: "missing current command is partial", current: coverageRootOnlySnapshot(), references: map[string]Snapshot{"merge-base": before, "stable": before}, authority: pending, candidate: pending, wantErr: "candidate partially applied flag migration", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { _, err := CompareAllWithFlagMigrations(test.current, test.references, test.authority, test.candidate) if err == nil || !strings.Contains(err.Error(), test.wantErr) { t.Fatalf("CompareAllWithFlagMigrations() error = %v, want %q", err, test.wantErr) } }) } } func TestCrossPlatformCoverageCompareAllWithFlagMigrationsMainAuthorityAndCleanup(t *testing.T) { pending := coverageManifest(FlagMigrationPending) consumed := coverageManifest(FlagMigrationConsumed) empty := coverageEmptyManifest() migration := pending.Migrations[0] before := coverageMigrationSnapshot(migration, false, false) after := coverageMigrationSnapshot(migration, true, false) report, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"main": before, "stable": before}, pending, consumed, ) if err != nil || !report.Compatible { t.Fatalf("main-owned migration = (%#v, %v), want compatible", report, err) } report, err = CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": before, "main": after, "stable": before}, pending, consumed, ) if err != nil || !report.Compatible { t.Fatalf("merge-base precedence = (%#v, %v), want compatible", report, err) } report, err = CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": after, "stable": after}, consumed, empty, ) if err != nil || !report.Compatible { t.Fatalf("consumed receipt cleanup = (%#v, %v), want compatible", report, err) } report, err = CompareAllWithFlagMigrations( before, map[string]Snapshot{"merge-base": before, "stable": before}, empty, pending, ) if err != nil || !report.Compatible { t.Fatalf("pending receipt creation = (%#v, %v), want compatible", report, err) } } func TestCrossPlatformCoverageCompareAllWithFlagMigrationsRequiresStableForReceiptCleanup(t *testing.T) { consumed := coverageManifest(FlagMigrationConsumed) empty := coverageEmptyManifest() after := coverageMigrationSnapshot(consumed.Migrations[0], true, false) report, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": after}, consumed, empty, ) if err == nil || !strings.Contains(err.Error(), "requires a stable reference") { t.Fatalf("receipt cleanup without stable = (%#v, %v), want stable-reference error", report, err) } } func TestCrossPlatformCoverageCompareAllWithFlagMigrationsInheritedAndOptionalCanonical(t *testing.T) { t.Run("inherited exact state", func(t *testing.T) { pending := coverageManifest(FlagMigrationPending) pending.Migrations[0].Legacy.Before.Scope = "inherited" pending.Migrations[0].Legacy.After.Scope = "inherited" pending.Migrations[0].Canonical.After.Scope = "inherited" consumed := pending consumed.Migrations = append([]FlagMigration(nil), pending.Migrations...) consumed.Migrations[0].State = FlagMigrationConsumed before := coverageMigrationSnapshot(pending.Migrations[0], false, false) after := coverageMigrationSnapshot(pending.Migrations[0], true, false) report, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": before, "stable": before}, pending, consumed, ) if err != nil || !report.Compatible { t.Fatalf("inherited migration = (%#v, %v), want compatible", report, err) } }) t.Run("optional canonical is introduced without becoming required", func(t *testing.T) { pending := coverageOptionalManifest(FlagMigrationPending) consumed := coverageOptionalManifest(FlagMigrationConsumed) before := coverageMigrationSnapshot(pending.Migrations[0], false, false) after := coverageMigrationSnapshot(pending.Migrations[0], true, false) ordinary := Compare(after, before, "merge-base") if !hasFlagChange(ordinary.Blocking, "flag_became_hidden", pending.Migrations[0].Command, pending.Migrations[0].Legacy.Name) { t.Fatalf("fixture did not create flag_became_hidden: %#v", ordinary.Blocking) } if hasFlagChange(ordinary.Blocking, "required_flag_added", pending.Migrations[0].Command, pending.Migrations[0].Canonical.Name) { t.Fatalf("optional canonical was treated as required: %#v", ordinary.Blocking) } report, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": before, "stable": before}, pending, consumed, ) if err != nil || !report.Compatible { t.Fatalf("optional rename = (%#v, %v), want compatible", report, err) } }) t.Run("existing optional canonical remains optional", func(t *testing.T) { pending := coverageOptionalManifest(FlagMigrationPending) pending.Migrations[0].Canonical.Before = FlagMigrationState{ Present: true, Type: "string", Scope: "local", } consumed := pending consumed.Migrations = append([]FlagMigration(nil), pending.Migrations...) consumed.Migrations[0].State = FlagMigrationConsumed before := coverageMigrationSnapshot(pending.Migrations[0], false, false) after := coverageMigrationSnapshot(pending.Migrations[0], true, false) ordinary := Compare(after, before, "merge-base") if hasFlagChange(ordinary.Blocking, "flag_became_required", pending.Migrations[0].Command, pending.Migrations[0].Canonical.Name) { t.Fatalf("fixture changed canonical requiredness: %#v", ordinary.Blocking) } report, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": before, "stable": before}, pending, consumed, ) if err != nil || !report.Compatible { t.Fatalf("existing optional canonical migration = (%#v, %v), want compatible", report, err) } }) t.Run("hidden canonical inherits requiredness when promoted", func(t *testing.T) { pending := coverageManifest(FlagMigrationPending) pending.Migrations[0].Canonical.Before = FlagMigrationState{ Present: true, Type: "string", Hidden: true, Scope: "local", } consumed := pending consumed.Migrations = append([]FlagMigration(nil), pending.Migrations...) consumed.Migrations[0].State = FlagMigrationConsumed before := coverageMigrationSnapshot(pending.Migrations[0], false, false) after := coverageMigrationSnapshot(pending.Migrations[0], true, false) ordinary := Compare(after, before, "merge-base") if !hasFlagChange(ordinary.Blocking, "flag_became_required", pending.Migrations[0].Command, pending.Migrations[0].Canonical.Name) { t.Fatalf("fixture did not change canonical requiredness: %#v", ordinary.Blocking) } report, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": before, "stable": before}, pending, consumed, ) if err != nil || !report.Compatible { t.Fatalf("hidden canonical promotion = (%#v, %v), want compatible", report, err) } }) } func TestCrossPlatformCoverageOptionalFlagMigrationLifecycleRemainsHostile(t *testing.T) { pending := coverageOptionalManifest(FlagMigrationPending) consumed := coverageOptionalManifest(FlagMigrationConsumed) empty := coverageEmptyManifest() migration := pending.Migrations[0] before := coverageMigrationSnapshot(migration, false, false) after := coverageMigrationSnapshot(migration, true, false) t.Run("candidate cannot self authorize", func(t *testing.T) { _, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": before, "stable": before}, empty, pending, ) if err == nil || !strings.Contains(err.Error(), "cannot authorize its own interface change") { t.Fatalf("candidate self-authorization error = %v", err) } }) t.Run("partial application remains rejected", func(t *testing.T) { partial := coverageMigrationSnapshot(migration, false, false) partial.Commands[len(partial.Commands)-1].LocalFlags[0].Hidden = true _, err := CompareAllWithFlagMigrations( partial, map[string]Snapshot{"merge-base": before, "stable": before}, pending, consumed, ) if err == nil || !strings.Contains(err.Error(), "partially applied flag migration") { t.Fatalf("partial optional migration error = %v", err) } }) t.Run("consumed receipt remains stale after every reference converges", func(t *testing.T) { _, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": after, "stable": after}, consumed, consumed, ) if err == nil || !strings.Contains(err.Error(), "stale after all references reached the after state") { t.Fatalf("stale optional migration error = %v", err) } }) } func TestCrossPlatformCoverageCompareAllWithFlagMigrationsNormalizesAliasesAndKeepsOtherBreakage(t *testing.T) { pending := coverageManifest(FlagMigrationPending) consumed := coverageManifest(FlagMigrationConsumed) migration := pending.Migrations[0] before := coverageMigrationSnapshot(migration, false, true) after := coverageMigrationSnapshot(migration, true, true) ordinary := Compare(after, before, "merge-base") if !hasFlagChange(ordinary.Blocking, "flag_became_hidden", "dws chat deliver", migration.Legacy.Name) { t.Fatalf("alias fixture did not create alias-path finding: %#v", ordinary.Blocking) } report, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": before, "stable": before}, pending, consumed, ) if err != nil || !report.Compatible { t.Fatalf("alias-path migration = (%#v, %v), want compatible", report, err) } beforeWithOther := before afterWithOther := after beforeWithOther.Commands = append(beforeWithOther.Commands, coverageCommand("dws files", nil, []Flag{{Name: "format", Type: "string"}}, nil)) afterWithOther.Commands = append(afterWithOther.Commands, coverageCommand("dws files", nil, nil, nil)) report, err = CompareAllWithFlagMigrations( afterWithOther, map[string]Snapshot{"merge-base": beforeWithOther, "stable": beforeWithOther}, pending, consumed, ) if err != nil { t.Fatalf("unrelated breakage returned lifecycle error: %v", err) } if report.Compatible || !hasFlagChange(report.Comparisons[0].Blocking, "flag_removed", "dws files", "format") { t.Fatalf("unrelated breakage was hidden: %#v", report) } } func TestCrossPlatformCoverageCompareAllWithFlagMigrationsDoesNotAuthorizePartialReferenceOrRuleDrift(t *testing.T) { pending := coverageManifest(FlagMigrationPending) consumed := coverageManifest(FlagMigrationConsumed) migration := pending.Migrations[0] before := coverageMigrationSnapshot(migration, false, false) after := coverageMigrationSnapshot(migration, true, false) partialReference := coverageMigrationSnapshot(migration, false, false) partialReference.Commands[len(partialReference.Commands)-1].LocalFlags[0].Hidden = true report, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": before, "stable": partialReference}, pending, consumed, ) if err != nil { t.Fatalf("partial non-authority reference returned lifecycle error: %v", err) } if report.Compatible { t.Fatalf("partial reference was incorrectly authorized: %#v", report) } beforeWithoutRoot := coverageMigrationSnapshot(migration, false, false) afterWithoutRoot := coverageMigrationSnapshot(migration, true, false) beforeWithoutRoot.Commands = beforeWithoutRoot.Commands[len(beforeWithoutRoot.Commands)-1:] afterWithoutRoot.Commands = afterWithoutRoot.Commands[len(afterWithoutRoot.Commands)-1:] afterWithoutRoot.Rules.ExcludedFlags = []string{"help", "version"} report, err = CompareAllWithFlagMigrations( afterWithoutRoot, map[string]Snapshot{"merge-base": beforeWithoutRoot, "stable": beforeWithoutRoot}, pending, consumed, ) if err != nil { t.Fatalf("rule drift returned lifecycle error: %v", err) } if report.Compatible || !hasChangeKind(report.Comparisons[0].Blocking, "snapshot_rules_changed") { t.Fatalf("snapshot rule drift was incorrectly authorized: %#v", report) } } func coverageManifestJSON() string { return `{ "version": 1, "migrations": [ { "command": "dws chat send", "legacy": { "name": "legacy-id", "before": {"present": true, "type": "string", "required": true, "shorthand": "l", "no_opt": "auto", "scope": "local"}, "after": {"present": true, "type": "string", "hidden": true, "shorthand": "l", "no_opt": "auto", "scope": "local", "alias_of": "message-id"} }, "canonical": { "name": "message-id", "before": {"present": false}, "after": {"present": true, "type": "string", "required": true, "scope": "local"} }, "state": "pending", "reason": "Reviewed exact flag migration." } ] }` } func coverageManifest(state string) FlagMigrationManifest { return FlagMigrationManifest{ Version: FlagMigrationManifestVersion, Migrations: []FlagMigration{ { Command: "dws chat send", Legacy: FlagMigrationSide{ Name: "legacy-id", Before: FlagMigrationState{ Present: true, Type: "string", Required: true, Shorthand: "l", NoOpt: "auto", Scope: "local", }, After: FlagMigrationState{ Present: true, Type: "string", Hidden: true, Shorthand: "l", NoOpt: "auto", Scope: "local", AliasOf: "message-id", }, }, Canonical: FlagMigrationSide{ Name: "message-id", Before: FlagMigrationState{}, After: FlagMigrationState{ Present: true, Type: "string", Required: true, Scope: "local", }, }, State: state, Reason: "Reviewed exact flag migration.", }, }, } } func coverageOptionalManifest(state string) FlagMigrationManifest { manifest := coverageManifest(state) manifest.Migrations[0].Legacy.Before.Required = false manifest.Migrations[0].Canonical.After.Required = false return manifest } func coverageEmptyManifest() FlagMigrationManifest { return FlagMigrationManifest{Version: FlagMigrationManifestVersion, Migrations: []FlagMigration{}} } func coverageMigrationSnapshot(migration FlagMigration, after, commandAlias bool) Snapshot { legacyState := migration.Legacy.Before canonicalState := migration.Canonical.Before if after { legacyState = migration.Legacy.After canonicalState = migration.Canonical.After } local := []Flag{} inherited := []Flag{} add := func(name string, state FlagMigrationState) { if !state.Present { return } flag := Flag{ Name: name, Shorthand: state.Shorthand, Type: state.Type, NoOpt: state.NoOpt, Required: state.Required, Hidden: state.Hidden, AliasOf: state.AliasOf, } if state.Scope == "inherited" { inherited = append(inherited, flag) return } local = append(local, flag) } add(migration.Legacy.Name, legacyState) add(migration.Canonical.Name, canonicalState) aliases := []string{} if commandAlias { aliases = []string{"deliver"} } return coverageSnapshot( coverageCommand("dws", nil, nil, nil), coverageCommand("dws chat", nil, nil, nil), coverageCommand(migration.Command, aliases, local, inherited), ) } func coverageRootOnlySnapshot() Snapshot { return coverageSnapshot(coverageCommand("dws", nil, nil, nil)) } func coverageSnapshot(commands ...Command) Snapshot { return Snapshot{ SchemaVersion: SchemaVersion, Rules: Rules{ ExcludedCommandSubtrees: append([]string(nil), excludedCommandSubtrees...), ExcludedFlags: []string{"help"}, }, Commands: commands, } } func coverageCommand(path string, aliases []string, local, inherited []Flag) Command { if aliases == nil { aliases = []string{} } if local == nil { local = []Flag{} } if inherited == nil { inherited = []Flag{} } return Command{ Path: path, Runnable: true, Aliases: aliases, LocalFlags: local, InheritedFlags: inherited, } }