Files
narratio/internal/artifacts/remote_commit_test.go

277 lines
11 KiB
Go

package artifacts
import (
"bytes"
"context"
"errors"
"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 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 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}`)...)
}