254 lines
9.7 KiB
Go
254 lines
9.7 KiB
Go
package artifacts
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"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)
|
|
}
|
|
}
|
|
|
|
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}`)...)
|
|
}
|