package artifacts import ( "context" "encoding/json" "errors" "strings" "testing" "time" "gitea.maximumdirect.net/eric/narratio/internal/adapters/storage" ) func TestLoadCurrentStateMissingRunPointer(t *testing.T) { store := &storage.FakeBackend{} _, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{}) if err == nil { t.Fatal("LoadCurrentState() error = nil, want missing run pointer error") } var missing *CurrentRunPointerMissingError if !errors.As(err, &missing) { t.Fatalf("errors.As(err, *CurrentRunPointerMissingError) = false; err=%v", err) } if !errors.Is(err, ErrCurrentRunPointerMissing) { t.Fatalf("errors.Is(err, ErrCurrentRunPointerMissing) = false; err=%v", err) } } func TestLoadCurrentStateMissingManifest(t *testing.T) { store := &storage.FakeBackend{} _, _, runIDKey := testCurrentStateKeys() store.SeedObject(storage.FakeObject{Key: runIDKey, Data: []byte("20260519T010203Z-a1b2c3d4\n")}) _, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{}) if err == nil { t.Fatal("LoadCurrentState() error = nil, want missing manifest error") } var missing *CurrentManifestMissingError if !errors.As(err, &missing) { t.Fatalf("errors.As(err, *CurrentManifestMissingError) = false; err=%v", err) } if !errors.Is(err, ErrCurrentManifestMissing) { t.Fatalf("errors.Is(err, ErrCurrentManifestMissing) = false; err=%v", err) } } func TestLoadCurrentStateEmptyRunPointerFails(t *testing.T) { store := &storage.FakeBackend{} _, manifestKey, runIDKey := testCurrentStateKeys() store.SeedObject(storage.FakeObject{Key: runIDKey, Data: []byte(" \n\t")}) store.SeedObject(storage.FakeObject{Key: manifestKey, Data: testManifestJSON(t, "2026-05-03", "sample-campaign", "20260519T010203Z-a1b2c3d4")}) _, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{}) if err == nil || !strings.Contains(err.Error(), "is empty") { t.Fatalf("error = %v, want empty run pointer failure", err) } } func TestLoadCurrentStateMalformedManifestFails(t *testing.T) { store := &storage.FakeBackend{} _, manifestKey, runIDKey := testCurrentStateKeys() store.SeedObject(storage.FakeObject{Key: runIDKey, Data: []byte("20260519T010203Z-a1b2c3d4\n")}) store.SeedObject(storage.FakeObject{Key: manifestKey, Data: []byte("{invalid json")}) _, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{}) if err == nil || !strings.Contains(err.Error(), "current manifest decode failed") { t.Fatalf("error = %v, want manifest decode failure", err) } } func TestLoadCurrentStateCampaignMismatchFails(t *testing.T) { store := &storage.FakeBackend{} seedCurrentState(t, store, "2026-05-03", "wrong-campaign", "20260519T010203Z-a1b2c3d4") _, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{ ExpectedCampaign: "sample-campaign", }) if err == nil || !strings.Contains(err.Error(), "does not match expected campaign") { t.Fatalf("error = %v, want campaign mismatch failure", err) } } func TestLoadCurrentStateSessionMismatchFails(t *testing.T) { store := &storage.FakeBackend{} seedCurrentState(t, store, "wrong-session", "sample-campaign", "20260519T010203Z-a1b2c3d4") _, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{ ExpectedSessionID: "2026-05-03", }) if err == nil || !strings.Contains(err.Error(), "does not match expected session_id") { t.Fatalf("error = %v, want session mismatch failure", err) } } func TestLoadCurrentStateRunIDMismatchFails(t *testing.T) { store := &storage.FakeBackend{} seedCurrentState(t, store, "2026-05-03", "sample-campaign", "different-run-id") _, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{ ValidateRunID: true, }) if err == nil || !strings.Contains(err.Error(), "current manifest run_id") { t.Fatalf("error = %v, want run mismatch failure", err) } } func TestLoadCurrentStateReadsCoherentLegacyPair(t *testing.T) { store := &storage.FakeBackend{} seedCurrentState(t, store, "2026-05-03", "sample-campaign", "20260519T010203Z-a1b2c3d4") state, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{ ExpectedCampaign: "sample-campaign", ExpectedSessionID: "2026-05-03", ValidateRunID: true, }) if err != nil { t.Fatalf("LoadCurrentState() error = %v", err) } if state.Commit != nil || state.Pointer != nil { t.Fatalf("legacy current state unexpectedly includes immutable commit data: %#v", state) } } func TestLoadCurrentStateRejectsTornLegacyPair(t *testing.T) { store := &storage.FakeBackend{} seedCurrentState(t, store, "2026-05-03", "sample-campaign", "different-run-id") _, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{ValidateRunID: true}) if err == nil || !strings.Contains(err.Error(), "current manifest run_id") { t.Fatalf("error = %v, want torn legacy pair rejection", err) } } func testCurrentSessionPrefix() string { return S3SessionPrefix("dnd", "sample-campaign", "2026-05-03") } func testCurrentStateKeys() (sessionPrefix, manifestKey, runIDKey string) { sessionPrefix = testCurrentSessionPrefix() manifestKey, runIDKey = ResolveCurrentStateKeys(sessionPrefix) return sessionPrefix, manifestKey, runIDKey } func seedCurrentState(t *testing.T, store *storage.FakeBackend, sessionID, campaign, manifestRunID string) { t.Helper() _, manifestKey, runIDKey := testCurrentStateKeys() store.SeedObject(storage.FakeObject{Key: runIDKey, Data: []byte("20260519T010203Z-a1b2c3d4\n")}) store.SeedObject(storage.FakeObject{Key: manifestKey, Data: testManifestJSON(t, sessionID, campaign, manifestRunID)}) } func testManifestJSON(t *testing.T, sessionID, campaign, runID string) []byte { t.Helper() now := time.Date(2026, 5, 19, 23, 0, 0, 0, time.UTC).Format(time.RFC3339Nano) payload := map[string]any{ "session_id": sessionID, "campaign": campaign, "run_id": runID, "created_at": now, "updated_at": now, "stages": map[string]any{}, } data, err := json.Marshal(payload) if err != nil { t.Fatalf("marshal manifest payload: %v", err) } return append(data, '\n') }