package artifacts import ( "path/filepath" "testing" ) func TestSessionWorkDirForCampaign(t *testing.T) { root := "/tmp/workspace" got := SessionWorkDirForCampaign(root, "forsaken", "2026-04-19") want := filepath.Join(root, "work", "forsaken", "2026-04-19") if got != want { t.Fatalf("SessionWorkDirForCampaign() = %q, want %q", got, want) } } func TestSessionManifestPathForCampaign(t *testing.T) { root := "/tmp/workspace" got := SessionManifestPathForCampaign(root, "forsaken", "2026-04-19") want := filepath.Join(root, "work", "forsaken", "2026-04-19", "manifest.json") if got != want { t.Fatalf("SessionManifestPathForCampaign() = %q, want %q", got, want) } } func TestSessionRunRootAndStageDirForCampaign(t *testing.T) { root := "/tmp/workspace" runID := "20260515T031522Z-a1b2c3d4" runRoot := SessionRunRootForCampaign(root, "forsaken", "2026-04-19", runID) wantRoot := filepath.Join(root, "work", "forsaken", "2026-04-19", "runs", runID) if runRoot != wantRoot { t.Fatalf("SessionRunRootForCampaign() = %q, want %q", runRoot, wantRoot) } stageDir := SessionRunStageDirForCampaign(root, "forsaken", "2026-04-19", runID, "transcribe") wantStage := filepath.Join(root, "work", "forsaken", "2026-04-19", "runs", runID, "transcribe") if stageDir != wantStage { t.Fatalf("SessionRunStageDirForCampaign() = %q, want %q", stageDir, wantStage) } } func TestSessionRunManifestPathForCampaign(t *testing.T) { root := "/tmp/workspace" runID := "20260515T031522Z-a1b2c3d4" got := SessionRunManifestPathForCampaign(root, "forsaken", "2026-04-19", runID) want := filepath.Join(root, "work", "forsaken", "2026-04-19", "runs", runID, "manifest.json") if got != want { t.Fatalf("SessionRunManifestPathForCampaign() = %q, want %q", got, want) } } func TestSessionNotariusPathsForCampaign(t *testing.T) { root := "/tmp/workspace" campaign := "forsaken" sessionID := "2026-04-19" runID := "20260515T031522Z-a1b2c3d4" extractDir := filepath.Join(root, "work", campaign, sessionID, "runs", runID, "extract") tests := []struct { name string got string want string }{ {name: "extract directory", got: SessionRunExtractDirForCampaign(root, campaign, sessionID, runID), want: extractDir}, {name: "receipt", got: SessionRunNotariusReceiptPathForCampaign(root, campaign, sessionID, runID), want: filepath.Join(extractDir, "notarius.receipt.json")}, {name: "stderr", got: SessionRunNotariusLogPathForCampaign(root, campaign, sessionID, runID), want: filepath.Join(extractDir, "notarius.stderr.log")}, {name: "output root", got: SessionRunNotariusOutputRootForCampaign(root, campaign, sessionID, runID), want: filepath.Join(extractDir, "notarius-output")}, {name: "durable bundle", got: SessionNotariusBundleDirForCampaign(root, campaign, sessionID, runID), want: filepath.Join(root, "work", campaign, sessionID, "artifacts", "notarius", runID)}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.got != test.want { t.Fatalf("path = %q, want %q", test.got, test.want) } }) } } func TestSessionPreviousPathsForCampaign(t *testing.T) { root := "/tmp/workspace" previousDir := SessionPreviousDirForCampaign(root, "forsaken", "2026-04-19") wantPreviousDir := filepath.Join(root, "work", "forsaken", "2026-04-19", "previous") if previousDir != wantPreviousDir { t.Fatalf("SessionPreviousDirForCampaign() = %q, want %q", previousDir, wantPreviousDir) } manifestPath := SessionPreviousManifestPathForCampaign(root, "forsaken", "2026-04-19") wantManifestPath := filepath.Join(root, "work", "forsaken", "2026-04-19", "previous", "manifest.json") if manifestPath != wantManifestPath { t.Fatalf("SessionPreviousManifestPathForCampaign() = %q, want %q", manifestPath, wantManifestPath) } artifactsDir := SessionPreviousArtifactsDirForCampaign(root, "forsaken", "2026-04-19") wantArtifactsDir := filepath.Join(root, "work", "forsaken", "2026-04-19", "previous", "artifacts") if artifactsDir != wantArtifactsDir { t.Fatalf("SessionPreviousArtifactsDirForCampaign() = %q, want %q", artifactsDir, wantArtifactsDir) } artifactPath := mustSessionPreviousArtifactPathForCampaign(t, root, "forsaken", "2026-04-19", "session_recap.md") wantArtifactPath := filepath.Join(root, "work", "forsaken", "2026-04-19", "previous", "artifacts", "session_recap.md") if artifactPath != wantArtifactPath { t.Fatalf("mustSessionPreviousArtifactPathForCampaign(t, ) = %q, want %q", artifactPath, wantArtifactPath) } previousRelativeArtifactPath := mustSessionPreviousArtifactPathForCampaign(t, root, "forsaken", "2026-04-19", "artifacts/session_recap.md") if previousRelativeArtifactPath != wantArtifactPath { t.Fatalf("mustSessionPreviousArtifactPathForCampaign(t, previous-relative) = %q, want %q", previousRelativeArtifactPath, wantArtifactPath) } } func TestSessionPreviousPathsFromSessionPaths(t *testing.T) { paths := buildSessionPaths("/tmp/workspace", "forsaken", "2026-04-19") if got := SessionPreviousDir(paths); got != paths.PreviousDir { t.Fatalf("SessionPreviousDir() = %q, want %q", got, paths.PreviousDir) } if got := SessionPreviousManifestPath(paths); got != paths.PreviousManifestPath { t.Fatalf("SessionPreviousManifestPath() = %q, want %q", got, paths.PreviousManifestPath) } if got := SessionPreviousArtifactsDir(paths); got != paths.PreviousArtifactsDir { t.Fatalf("SessionPreviousArtifactsDir() = %q, want %q", got, paths.PreviousArtifactsDir) } got := mustSessionPreviousArtifactPath(t, paths, "quest_log.json") want := filepath.Join(paths.PreviousArtifactsDir, "quest_log.json") if got != want { t.Fatalf("SessionPreviousArtifactPath() = %q, want %q", got, want) } got = mustSessionPreviousArtifactPath(t, paths, "artifacts/session_recap.md") want = filepath.Join(paths.PreviousArtifactsDir, "session_recap.md") if got != want { t.Fatalf("SessionPreviousArtifactPath(previous-relative) = %q, want %q", got, want) } } func TestSessionSpoolAudioDir(t *testing.T) { root := "/var/spool/narratio" got := SessionSpoolAudioDir(root, "forsaken", "2026-04-19", "20260515T031522Z-a1b2c3d4") want := filepath.Join(root, "forsaken", "2026-04-19", "20260515T031522Z-a1b2c3d4", "audio") if got != want { t.Fatalf("SessionSpoolAudioDir() = %q, want %q", got, want) } } func TestSessionSpoolDir(t *testing.T) { root := "/var/spool/narratio" got := SessionSpoolDir(root, "forsaken", "2026-04-19") want := filepath.Join(root, "forsaken", "2026-04-19") if got != want { t.Fatalf("SessionSpoolDir() = %q, want %q", got, want) } } func TestSessionSpoolRestoreAudioDir(t *testing.T) { root := "/var/spool/narratio" got := SessionSpoolRestoreAudioDir(root, "forsaken", "2026-04-19") want := filepath.Join(root, "forsaken", "2026-04-19", "restore", "audio") if got != want { t.Fatalf("SessionSpoolRestoreAudioDir() = %q, want %q", got, want) } } func TestS3AudioCachePath(t *testing.T) { got, err := S3AudioCachePath("/var/cache/narratio", "my-dnd-archive", "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac") if err != nil { t.Fatalf("S3AudioCachePath() error = %v", err) } want := filepath.Join("/var/cache/narratio", "s3", "my-dnd-archive", "dnd", "campaigns", "forsaken", "sessions", "2026-04-19", "audio", "alice.flac") if got != want { t.Fatalf("S3AudioCachePath() = %q, want %q", got, want) } } func TestS3AudioCacheNamespaceDir(t *testing.T) { got, err := S3AudioCacheNamespaceDir("/var/cache/narratio", "my-dnd-archive", "dnd") if err != nil { t.Fatalf("S3AudioCacheNamespaceDir() error = %v", err) } want := filepath.Join("/var/cache/narratio", "s3", "my-dnd-archive", "dnd", "campaigns") if got != want { t.Fatalf("S3AudioCacheNamespaceDir() = %q, want %q", got, want) } } func TestS3AudioCachePathRejectsUnsafeInputs(t *testing.T) { tests := []struct { name string root string bucket string key string }{ {name: "empty root", root: "", bucket: "bucket", key: "audio/a.flac"}, {name: "empty bucket", root: "/cache", bucket: "", key: "audio/a.flac"}, {name: "bucket slash", root: "/cache", bucket: "bad/bucket", key: "audio/a.flac"}, {name: "empty key", root: "/cache", bucket: "bucket", key: ""}, {name: "escaping key", root: "/cache", bucket: "bucket", key: "../audio/a.flac"}, {name: "absolute key", root: "/cache", bucket: "bucket", key: "/audio/a.flac"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got, err := S3AudioCachePath(tt.root, tt.bucket, tt.key); err == nil { t.Fatalf("S3AudioCachePath() = %q, want error", got) } }) } }