package artifacts import ( "bytes" "context" "errors" "fmt" "strings" "testing" "gitea.maximumdirect.net/eric/narratio/internal/adapters/storage" ) func TestRemoteCommitManifestRejectsUnsupportedVersion(t *testing.T) { commit := testRemoteCommit(t, "20260519T010203Z-a1b2c3d4") pointer := testCommitPointer(t, testCurrentSessionPrefix(), commit) commit.FormatVersion++ if _, err := EncodeRemoteCommitManifest(commit); err == nil || !strings.Contains(err.Error(), "unsupported remote commit format version") { t.Fatalf("EncodeRemoteCommitManifest() error = %v, want unsupported version", err) } pointer.FormatVersion++ if _, err := EncodeCurrentCommitPointer(pointer); err == nil || !strings.Contains(err.Error(), "unsupported current commit pointer format version") { t.Fatalf("EncodeCurrentCommitPointer() error = %v, want unsupported version", err) } } func TestRemoteCommitDecodersRejectUnknownFields(t *testing.T) { commit := testRemoteCommit(t, "20260519T010203Z-a1b2c3d4") commitData, err := EncodeRemoteCommitManifest(commit) if err != nil { t.Fatalf("EncodeRemoteCommitManifest() error = %v", err) } commitData = appendUnknownJSONField(t, commitData) if _, err := DecodeRemoteCommitManifest(commitData); err == nil || !strings.Contains(err.Error(), "unknown field") { t.Fatalf("DecodeRemoteCommitManifest() error = %v, want unknown-field rejection", err) } pointer := testCommitPointer(t, testCurrentSessionPrefix(), commit) pointerData, err := EncodeCurrentCommitPointer(pointer) if err != nil { t.Fatalf("EncodeCurrentCommitPointer() error = %v", err) } pointerData = appendUnknownJSONField(t, pointerData) if _, err := DecodeCurrentCommitPointer(pointerData); err == nil || !strings.Contains(err.Error(), "unknown field") { t.Fatalf("DecodeCurrentCommitPointer() error = %v, want unknown-field rejection", err) } } func TestParseImmutableRunObjectKeyAcceptsSameSessionRuns(t *testing.T) { prefix := testCurrentSessionPrefix() for _, runID := range []string{"20260519T010203Z-a1b2c3d4", "20260401T010203Z-11223344"} { key := S3RunRelativeDestinationKey(S3RunPrefix(prefix, runID), "published/artifacts/recap.md") identity, err := ParseImmutableRunObjectKey(prefix, key) if err != nil { t.Fatalf("ParseImmutableRunObjectKey(%q) error = %v", runID, err) } if identity.RunID != runID || identity.RelativePath != "published/artifacts/recap.md" { t.Fatalf("ParseImmutableRunObjectKey(%q) = %#v", runID, identity) } } } func TestParseImmutableRunObjectKeyRejectsUnsafeOrMutableKeys(t *testing.T) { prefix := testCurrentSessionPrefix() for _, key := range []string{ prefix + "current/commit-pointer.json", prefix + "locks/source.lock", prefix + "runs//published/recap.md", prefix + "runs/20260519T010203Z-a1b2c3d4", prefix + "runs/20260519T010203Z-a1b2c3d4/published/../recap.md", "dnd/campaigns/other/sessions/2026-05-03/runs/20260519T010203Z-a1b2c3d4/published/recap.md", } { if _, err := ParseImmutableRunObjectKey(prefix, key); err == nil { t.Fatalf("ParseImmutableRunObjectKey(%q) error = nil, want rejection", key) } } } func TestRemoteCommitAllowsPublishedOutputFromEarlierSameSessionRunOnly(t *testing.T) { commit := testRemoteCommit(t, "20260519T010203Z-a1b2c3d4") prefix := testCurrentSessionPrefix() published := RemoteArtifact{ Type: RemoteArtifactTypePublishedOutput, Source: "narratio.artifact.recap", DestinationKey: S3RunRelativeDestinationKey(S3RunPrefix(prefix, "20260401T010203Z-11223344"), "published/artifacts/recap.md"), SHA256: strings.Repeat("a", 64), Size: 1, Generation: "older-generation", } commit.Artifacts = append(commit.Artifacts, published) if err := commit.ValidateForSessionPrefix(prefix); err != nil { t.Fatalf("ValidateForSessionPrefix() error = %v", err) } commit.Artifacts[1].DestinationKey = "dnd/campaigns/other/sessions/2026-05-03/runs/20260401T010203Z-11223344/published/artifacts/recap.md" if err := commit.ValidateForSessionPrefix(prefix); err == nil { t.Fatal("ValidateForSessionPrefix() cross-session error = nil") } commit.Artifacts[1].DestinationKey = published.DestinationKey commit.Artifacts[1].Type = RemoteArtifactTypeRunFile if err := commit.ValidateForSessionPrefix(prefix); err == nil { t.Fatal("ValidateForSessionPrefix() earlier non-published object error = nil") } } func TestRemoteCommitRejectsEffectivePublishedDestinationCollisionAcrossRuns(t *testing.T) { commit := testRemoteCommit(t, "20260519T010203Z-a1b2c3d4") prefix := testCurrentSessionPrefix() for index, runID := range []string{commit.RunID, "20260401T010203Z-11223344"} { commit.Artifacts = append(commit.Artifacts, RemoteArtifact{ Type: RemoteArtifactTypePublishedOutput, Source: fmt.Sprintf("narratio.artifact.recap_%d", index), DestinationKey: S3RunRelativeDestinationKey(S3RunPrefix(prefix, runID), "published/artifacts/recap.md"), SHA256: strings.Repeat("a", 64), Size: 1, Generation: fmt.Sprintf("generation-%d", index), }) } if err := commit.ValidateForSessionPrefix(prefix); err == nil || !strings.Contains(err.Error(), "duplicate published output destination") { t.Fatalf("ValidateForSessionPrefix() error = %v, want effective destination collision", err) } } func TestRemoteCommitDecoderAcceptsHistoricalRunManifestArtifact(t *testing.T) { data := []byte(`{"format_version":1,"campaign":"sample-campaign","session_id":"2026-05-03","run_id":"20260519T010203Z-a1b2c3d4","artifacts":[{"type":"run_manifest","source":"run:manifest.json","destination_key":"sample-campaign/2026-05-03/runs/20260519T010203Z-a1b2c3d4/manifest.json","sha256":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","size":1,"generation":"historical"}]}`) commit, err := DecodeRemoteCommitManifest(data) if err != nil { t.Fatalf("DecodeRemoteCommitManifest() error = %v", err) } if got := commit.Artifacts[0].Type; got != RemoteArtifactTypeRunManifest { t.Fatalf("artifact type = %q, want %q", got, RemoteArtifactTypeRunManifest) } } func TestLoadCurrentStateReadsVerifiedRemoteCommit(t *testing.T) { store := &storage.FakeBackend{} commit := testRemoteCommit(t, "20260519T010203Z-a1b2c3d4") seedRemoteCommit(t, store, testCurrentSessionPrefix(), commit, "commit-generation", "manifest-generation") state, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{ ExpectedCampaign: commit.Campaign, ExpectedSessionID: commit.SessionID, ValidateRunID: true, }) if err != nil { t.Fatalf("LoadCurrentState() error = %v", err) } if state.Commit == nil || state.Pointer == nil { t.Fatalf("state does not expose selected immutable commit: %#v", state) } if state.CurrentPointerKey != S3CurrentCommitPointerKey(testCurrentSessionPrefix()) { t.Fatalf("CurrentPointerKey = %q", state.CurrentPointerKey) } if state.Manifest.RunID != commit.RunID { t.Fatalf("manifest run ID = %q, want %q", state.Manifest.RunID, commit.RunID) } if len(store.Downloads) != 0 { t.Fatalf("committed current-state downloads = %#v, want direct bounded reads", store.Downloads) } wantReads := []string{state.CurrentPointerKey, state.Pointer.CommitKey, state.CurrentManifestKey} if len(store.Reads) != len(wantReads) { t.Fatalf("committed current-state reads = %#v, want %q", store.Reads, wantReads) } for index, want := range wantReads { if store.Reads[index].Key != want { t.Fatalf("committed current-state read %d = %q, want %q", index, store.Reads[index].Key, want) } } } func TestLoadCurrentStateUsesMetadataFromOpenedObjectVersion(t *testing.T) { store := &storage.FakeBackend{ListErr: errors.New("list must not be used for object verification")} commit := testRemoteCommit(t, "20260519T010203Z-a1b2c3d4") seedRemoteCommit(t, store, testCurrentSessionPrefix(), commit, "commit-generation", "manifest-generation") if _, err := LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{}); err != nil { t.Fatalf("LoadCurrentState() error = %v, want verification from Read metadata", err) } } func TestDecodeCommittedManifestGuidesRemoteRecovery(t *testing.T) { _, err := decodeCommittedManifest(context.Background(), []byte(`{"session_id":"2026-05-03"}`)) if err == nil { t.Fatal("decodeCommittedManifest() error = nil, want unsupported schema failure") } if !strings.Contains(err.Error(), "schema_version is required") || !strings.Contains(err.Error(), "republish this session") { t.Fatalf("decodeCommittedManifest() error = %v, want schema and republish guidance", err) } if strings.Contains(err.Error(), "local state") { t.Fatalf("decodeCommittedManifest() error = %v, must not describe remote state as local", err) } } func TestLoadCurrentStateRejectsPointerCommitIdentityMismatch(t *testing.T) { store := &storage.FakeBackend{} prefix := testCurrentSessionPrefix() pointerRunID := "20260519T010203Z-a1b2c3d4" commit := testRemoteCommit(t, "20260520T010203Z-a1b2c3d4") commitData, err := EncodeRemoteCommitManifest(commit) if err != nil { t.Fatalf("EncodeRemoteCommitManifest() error = %v", err) } store.SeedObject(storage.FakeObject{Key: S3RunCommitKey(prefix, pointerRunID), Data: commitData, ETag: "commit-generation"}) pointer := CurrentCommitPointer{ FormatVersion: RemoteCommitFormatVersion, Campaign: commit.Campaign, SessionID: commit.SessionID, RunID: pointerRunID, CommitKey: S3RunCommitKey(prefix, pointerRunID), CommitSHA256: remoteObjectSHA256(commitData), CommitSize: int64(len(commitData)), CommitGeneration: "commit-generation", } pointerData, err := EncodeCurrentCommitPointer(pointer) if err != nil { t.Fatalf("EncodeCurrentCommitPointer() error = %v", err) } store.SeedObject(storage.FakeObject{Key: S3CurrentCommitPointerKey(prefix), Data: pointerData}) _, err = LoadCurrentState(context.Background(), store, prefix, CurrentStateValidation{}) if err == nil || !strings.Contains(err.Error(), "does not match selected remote commit") { t.Fatalf("error = %v, want pointer/commit identity mismatch", err) } } func TestLoadCurrentStateRejectsCommitManifestIdentityMismatch(t *testing.T) { store := &storage.FakeBackend{} commit := testRemoteCommit(t, "20260519T010203Z-a1b2c3d4") pointer := seedRemoteCommit(t, store, testCurrentSessionPrefix(), commit, "commit-generation", "manifest-generation") wrongManifest := testManifestJSON(t, "2026-05-03", "wrong-campaign", commit.RunID) store.SeedObject(storage.FakeObject{ Key: commit.Artifacts[0].DestinationKey, Data: wrongManifest, ETag: "manifest-generation", }) commit.Artifacts[0].SHA256 = remoteObjectSHA256(wrongManifest) commit.Artifacts[0].Size = int64(len(wrongManifest)) commitData, err := EncodeRemoteCommitManifest(commit) if err != nil { t.Fatalf("EncodeRemoteCommitManifest() error = %v", err) } store.SeedObject(storage.FakeObject{Key: pointer.CommitKey, Data: commitData, ETag: "commit-generation"}) pointer.CommitSHA256 = remoteObjectSHA256(commitData) pointer.CommitSize = int64(len(commitData)) pointerData, err := EncodeCurrentCommitPointer(*pointer) if err != nil { t.Fatalf("EncodeCurrentCommitPointer() error = %v", err) } store.SeedObject(storage.FakeObject{Key: S3CurrentCommitPointerKey(testCurrentSessionPrefix()), Data: pointerData}) _, err = LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{}) if err == nil || !strings.Contains(err.Error(), "does not match committed session manifest") { t.Fatalf("error = %v, want commit/manifest identity mismatch", err) } } func TestLoadCurrentStateRejectsCommitChecksumOrGenerationMismatch(t *testing.T) { tests := []struct { name string mutate func(*CurrentCommitPointer) want string }{ { name: "checksum", mutate: func(pointer *CurrentCommitPointer) { pointer.CommitSHA256 = strings.Repeat("0", 64) }, want: "checksum mismatch", }, { name: "generation", mutate: func(pointer *CurrentCommitPointer) { pointer.CommitGeneration = "stale-generation" }, want: "generation mismatch", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { store := &storage.FakeBackend{} commit := testRemoteCommit(t, "20260519T010203Z-a1b2c3d4") pointer := seedRemoteCommit(t, store, testCurrentSessionPrefix(), commit, "commit-generation", "manifest-generation") test.mutate(pointer) pointerData, err := EncodeCurrentCommitPointer(*pointer) if err != nil { t.Fatalf("EncodeCurrentCommitPointer() error = %v", err) } store.SeedObject(storage.FakeObject{Key: S3CurrentCommitPointerKey(testCurrentSessionPrefix()), Data: pointerData}) _, err = LoadCurrentState(context.Background(), store, testCurrentSessionPrefix(), CurrentStateValidation{}) if err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("error = %v, want %q", err, test.want) } }) } } func testRemoteCommit(t *testing.T, runID string) RemoteCommitManifest { t.Helper() prefix := testCurrentSessionPrefix() manifestData := testManifestJSON(t, "2026-05-03", "sample-campaign", runID) return RemoteCommitManifest{ FormatVersion: RemoteCommitFormatVersion, Campaign: "sample-campaign", SessionID: "2026-05-03", RunID: runID, Artifacts: []RemoteArtifact{{ Type: RemoteArtifactTypeSessionManifest, Source: "session.manifest", DestinationKey: S3RunRelativeDestinationKey(S3RunPrefix(prefix, runID), "session-manifest.json"), SHA256: remoteObjectSHA256(manifestData), Size: int64(len(manifestData)), Generation: "manifest-generation", }}, } } func testCommitPointer(t *testing.T, prefix string, commit RemoteCommitManifest) CurrentCommitPointer { t.Helper() commitData, err := EncodeRemoteCommitManifest(commit) if err != nil { t.Fatalf("EncodeRemoteCommitManifest() error = %v", err) } return CurrentCommitPointer{ FormatVersion: RemoteCommitFormatVersion, Campaign: commit.Campaign, SessionID: commit.SessionID, RunID: commit.RunID, CommitKey: S3RunCommitKey(prefix, commit.RunID), CommitSHA256: remoteObjectSHA256(commitData), CommitSize: int64(len(commitData)), CommitGeneration: "commit-generation", } } func seedRemoteCommit(t *testing.T, store *storage.FakeBackend, prefix string, commit RemoteCommitManifest, commitGeneration, manifestGeneration string) *CurrentCommitPointer { t.Helper() manifestData := testManifestJSON(t, commit.SessionID, commit.Campaign, commit.RunID) commit.Artifacts[0].SHA256 = remoteObjectSHA256(manifestData) commit.Artifacts[0].Size = int64(len(manifestData)) commit.Artifacts[0].Generation = manifestGeneration commitData, err := EncodeRemoteCommitManifest(commit) if err != nil { t.Fatalf("EncodeRemoteCommitManifest() error = %v", err) } store.SeedObject(storage.FakeObject{ Key: S3RunCommitKey(prefix, commit.RunID), Data: commitData, ETag: commitGeneration, }) store.SeedObject(storage.FakeObject{ Key: commit.Artifacts[0].DestinationKey, Data: manifestData, ETag: manifestGeneration, }) pointer := testCommitPointer(t, prefix, commit) pointer.CommitGeneration = commitGeneration pointerData, err := EncodeCurrentCommitPointer(pointer) if err != nil { t.Fatalf("EncodeCurrentCommitPointer() error = %v", err) } store.SeedObject(storage.FakeObject{Key: S3CurrentCommitPointerKey(prefix), Data: pointerData}) return &pointer } func appendUnknownJSONField(t *testing.T, data []byte) []byte { t.Helper() trimmed := bytes.TrimSpace(data) if len(trimmed) < 2 || trimmed[len(trimmed)-1] != '}' { t.Fatalf("invalid JSON object %q", trimmed) } return append(append([]byte(nil), trimmed[:len(trimmed)-1]...), []byte(`,"unexpected":true}`)...) }