package manifest import ( "context" "os" "path/filepath" "strings" "testing" "time" ) const testSemanticDigest = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" func TestSemanticConfigFingerprintRoundTripsAndLegacyRecordsRemainReadable(t *testing.T) { ctx := context.Background() store := &LocalStore{} dir := t.TempDir() fingerprint := &SemanticConfigFingerprint{Version: 3, Digest: testSemanticDigest} session := New("session", time.Now().UTC()) session.MarkStageSucceeded("prepare", time.Now().UTC(), nil) session.Stages["prepare"].SemanticConfig = fingerprint sessionPath := filepath.Join(dir, "session.json") if err := store.Save(ctx, sessionPath, session); err != nil { t.Fatal(err) } loadedSession, err := store.Load(ctx, sessionPath) if err != nil { t.Fatal(err) } if loadedSession.Stages["prepare"].SemanticConfig == nil || !loadedSession.Stages["prepare"].SemanticConfig.Equal(*fingerprint) { t.Fatalf("session fingerprint = %#v", loadedSession.Stages["prepare"].SemanticConfig) } run := NewRun("session", "campaign", "run", false, []string{"prepare"}, time.Now().UTC()) run.MarkStageSucceeded("prepare", time.Now().UTC(), nil) run.Stages["prepare"].SemanticConfig = fingerprint runPath := filepath.Join(dir, "run.json") if err := store.SaveRun(ctx, runPath, run); err != nil { t.Fatal(err) } loadedRun, err := store.LoadRun(ctx, runPath) if err != nil { t.Fatal(err) } if loadedRun.Stages["prepare"].SemanticConfig == nil || !loadedRun.Stages["prepare"].SemanticConfig.Equal(*fingerprint) { t.Fatalf("run fingerprint = %#v", loadedRun.Stages["prepare"].SemanticConfig) } legacyPath := filepath.Join(dir, "legacy.json") legacy := `{"session_id":"session","created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z","stages":{"prepare":{"name":"prepare","status":"succeeded","created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z"}}}` if err := os.WriteFile(legacyPath, []byte(legacy), 0o644); err != nil { t.Fatal(err) } legacyManifest, err := store.Load(ctx, legacyPath) if err != nil { t.Fatal(err) } if legacyManifest.Stages["prepare"].SemanticConfig != nil { t.Fatalf("legacy fingerprint = %#v, want nil", legacyManifest.Stages["prepare"].SemanticConfig) } } func TestSemanticConfigFingerprintValidation(t *testing.T) { for _, test := range []struct { name string value SemanticConfigFingerprint want string }{ {name: "zero version", value: SemanticConfigFingerprint{Digest: testSemanticDigest}, want: "positive"}, {name: "uppercase digest", value: SemanticConfigFingerprint{Version: 1, Digest: strings.ToUpper(testSemanticDigest)}, want: "lowercase"}, {name: "short digest", value: SemanticConfigFingerprint{Version: 1, Digest: "abcd"}, want: "lowercase"}, } { t.Run(test.name, func(t *testing.T) { if err := test.value.Validate(); err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("Validate() error = %v, want %q", err, test.want) } }) } }