package artifacts import ( "context" "encoding/json" "errors" "fmt" "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) } if len(store.Downloads) != 0 { t.Fatalf("legacy current-state downloads = %#v, want direct bounded reads", store.Downloads) } wantReads := []string{state.CurrentRunIDKey, state.CurrentManifestKey} if len(store.Reads) != len(wantReads) { t.Fatalf("legacy current-state reads = %#v, want %q", store.Reads, wantReads) } for index, want := range wantReads { if store.Reads[index].Key != want { t.Fatalf("legacy current-state read %d = %q, want %q", index, store.Reads[index].Key, want) } } } func TestCurrentStateControlObjectLimitsAcceptExactAndRejectLimitPlusOne(t *testing.T) { tests := []struct { name string category string key string limit int64 }{ {name: "commit pointer", category: "current commit pointer", key: "current/commit-pointer.json", limit: MaxCurrentCommitPointerBytes}, {name: "commit manifest", category: "remote commit manifest", key: "runs/run/commit.json", limit: MaxRemoteCommitManifestBytes}, {name: "session manifest", category: "committed session manifest", key: "runs/run/session-manifest.json", limit: MaxRemoteSessionManifestBytes}, {name: "legacy run pointer", category: "legacy current run pointer", key: "current/run_id.txt", limit: MaxLegacyCurrentRunPointerBytes}, {name: "legacy manifest", category: "legacy current manifest", key: "current/manifest.json", limit: MaxLegacyCurrentManifestBytes}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { store := &storage.FakeBackend{} exact := strings.Repeat("x", int(test.limit)) store.SeedObject(storage.FakeObject{Key: test.key, Data: []byte(exact)}) _, data, err := readCurrentStateControlObject(context.Background(), store, test.key, test.category, test.limit) if err != nil || string(data) != exact { t.Fatalf("exact-limit read data length=%d error=%v", len(data), err) } store.SeedObject(storage.FakeObject{Key: test.key, Data: []byte(exact + "x")}) _, _, err = readCurrentStateControlObject(context.Background(), store, test.key, test.category, test.limit) if err == nil || !strings.Contains(err.Error(), test.category) || !strings.Contains(err.Error(), test.key) || !strings.Contains(err.Error(), fmt.Sprint(test.limit)) { t.Fatalf("limit-plus-one error = %v, want category, key, and limit", err) } }) } } 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') }