168 lines
6.3 KiB
Go
168 lines
6.3 KiB
Go
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')
|
|
}
|