180 lines
6.0 KiB
Go
180 lines
6.0 KiB
Go
package manifest
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestStageMarkHelpers(t *testing.T) {
|
|
m := New("2026-05-03", time.Date(2026, 5, 3, 12, 0, 0, 0, time.UTC))
|
|
|
|
runningAt := time.Date(2026, 5, 3, 12, 1, 0, 0, time.UTC)
|
|
m.MarkStageRunning("transcribe", runningAt)
|
|
|
|
stage := m.Stages["transcribe"]
|
|
if stage == nil {
|
|
t.Fatal("stage not created")
|
|
}
|
|
if stage.Status != StatusRunning {
|
|
t.Fatalf("status = %q, want %q", stage.Status, StatusRunning)
|
|
}
|
|
if stage.StartedAt == nil || !stage.StartedAt.Equal(runningAt) {
|
|
t.Fatalf("started_at = %v, want %v", stage.StartedAt, runningAt)
|
|
}
|
|
|
|
succeededAt := runningAt.Add(2 * time.Minute)
|
|
outputs := []ArtifactRecord{{Kind: "transcript_polished", LocalPath: "transcripts/polished.json"}}
|
|
m.MarkStageSucceeded("transcribe", succeededAt, outputs)
|
|
if stage.Status != StatusSucceeded {
|
|
t.Fatalf("status = %q, want %q", stage.Status, StatusSucceeded)
|
|
}
|
|
if stage.CompletedAt == nil || !stage.CompletedAt.Equal(succeededAt) {
|
|
t.Fatalf("completed_at = %v, want %v", stage.CompletedAt, succeededAt)
|
|
}
|
|
if len(stage.Outputs) != 1 {
|
|
t.Fatalf("outputs len = %d, want 1", len(stage.Outputs))
|
|
}
|
|
outputs[0].LocalPath = "mutated.json"
|
|
if stage.Outputs[0].LocalPath != "transcripts/polished.json" {
|
|
t.Fatalf("stage outputs should be copied, got %#v", stage.Outputs)
|
|
}
|
|
|
|
failedAt := succeededAt.Add(1 * time.Minute)
|
|
m.MarkStageFailed("analyze", failedAt, "analyzer crashed")
|
|
failed := m.Stages["analyze"]
|
|
if failed == nil {
|
|
t.Fatal("failed stage missing")
|
|
}
|
|
if failed.Status != StatusFailed {
|
|
t.Fatalf("status = %q, want %q", failed.Status, StatusFailed)
|
|
}
|
|
if failed.Error == nil || failed.Error.Message != "analyzer crashed" {
|
|
t.Fatalf("error = %#v, want message", failed.Error)
|
|
}
|
|
|
|
skippedAt := failedAt.Add(1 * time.Minute)
|
|
m.MarkStageSkipped("notify", skippedAt, "notifications disabled")
|
|
skipped := m.Stages["notify"]
|
|
if skipped == nil {
|
|
t.Fatal("skipped stage missing")
|
|
}
|
|
if skipped.Status != StatusSkipped {
|
|
t.Fatalf("status = %q, want %q", skipped.Status, StatusSkipped)
|
|
}
|
|
if skipped.Error == nil || skipped.Error.Message != "notifications disabled" {
|
|
t.Fatalf("error = %#v, want skip reason", skipped.Error)
|
|
}
|
|
if skipped.Error.Code != "skipped" {
|
|
t.Fatalf("error code = %q, want %q", skipped.Error.Code, "skipped")
|
|
}
|
|
}
|
|
|
|
func TestMarkStageRunningClearsPriorCompletionErrorAndResultDetails(t *testing.T) {
|
|
m := New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
|
|
|
|
failedAt := time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC)
|
|
m.MarkStageFailed("merge", failedAt, "boom")
|
|
setStageResultDetails(m.Stages["merge"])
|
|
|
|
runAt := failedAt.Add(1 * time.Minute)
|
|
m.MarkStageRunning("merge", runAt)
|
|
|
|
stage := m.Stages["merge"]
|
|
if stage == nil {
|
|
t.Fatal("missing stage record")
|
|
}
|
|
if stage.Status != StatusRunning {
|
|
t.Fatalf("status = %q, want %q", stage.Status, StatusRunning)
|
|
}
|
|
if stage.CompletedAt != nil {
|
|
t.Fatalf("completed_at = %v, want nil while running", stage.CompletedAt)
|
|
}
|
|
if stage.Error != nil {
|
|
t.Fatalf("error = %#v, want nil while running", stage.Error)
|
|
}
|
|
requireStageResultDetailsCleared(t, stage)
|
|
}
|
|
|
|
func TestMarkStageSucceededClearsError(t *testing.T) {
|
|
m := New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
|
|
|
|
m.MarkStageFailed("analyze", time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), "failed once")
|
|
m.MarkStageSucceeded("analyze", time.Date(2026, 5, 3, 10, 2, 0, 0, time.UTC), nil)
|
|
|
|
stage := m.Stages["analyze"]
|
|
if stage == nil {
|
|
t.Fatal("missing stage record")
|
|
}
|
|
if stage.Status != StatusSucceeded {
|
|
t.Fatalf("status = %q, want %q", stage.Status, StatusSucceeded)
|
|
}
|
|
if stage.Error != nil {
|
|
t.Fatalf("error = %#v, want nil on success", stage.Error)
|
|
}
|
|
}
|
|
|
|
func TestMarkStageFailedClearsEarlierResultDetails(t *testing.T) {
|
|
m := New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
|
|
m.MarkStageSucceeded("extract", time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil)
|
|
setStageResultDetails(m.Stages["extract"])
|
|
|
|
m.MarkStageFailed("extract", time.Date(2026, 5, 3, 10, 2, 0, 0, time.UTC), "replacement failed")
|
|
|
|
stage := m.Stages["extract"]
|
|
if stage == nil || stage.Status != StatusFailed {
|
|
t.Fatalf("stage = %#v, want failed", stage)
|
|
}
|
|
requireStageResultDetailsCleared(t, stage)
|
|
}
|
|
|
|
func TestMarkStageSkippedClearsEarlierResultDetails(t *testing.T) {
|
|
m := New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
|
|
m.MarkStageSucceeded("extract", time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil)
|
|
setStageResultDetails(m.Stages["extract"])
|
|
|
|
m.MarkStageSkipped("extract", time.Date(2026, 5, 3, 10, 2, 0, 0, time.UTC), "integration_disabled")
|
|
|
|
stage := m.Stages["extract"]
|
|
if stage == nil {
|
|
t.Fatal("missing stage record")
|
|
}
|
|
if stage.Status != StatusSkipped {
|
|
t.Fatalf("status = %q, want %q", stage.Status, StatusSkipped)
|
|
}
|
|
requireStageResultDetailsCleared(t, stage)
|
|
}
|
|
|
|
func TestMarkStageStalePreservesResultDetails(t *testing.T) {
|
|
m := New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
|
|
m.MarkStageSucceeded("extract", time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil)
|
|
stage := m.Stages["extract"]
|
|
setStageResultDetails(stage)
|
|
|
|
m.MarkStageStale("extract", time.Date(2026, 5, 3, 10, 2, 0, 0, time.UTC), "result is not resumable")
|
|
|
|
if stage.Status != StatusStale {
|
|
t.Fatalf("status = %q, want %q", stage.Status, StatusStale)
|
|
}
|
|
if len(stage.Outputs) != 1 || len(stage.Logs) != 1 || len(stage.GeneratedConfigs) != 1 || len(stage.Metadata) != 1 {
|
|
t.Fatalf("result details were not preserved: %#v", stage)
|
|
}
|
|
}
|
|
|
|
func setStageResultDetails(stage *StageRecord) {
|
|
stage.Outputs = []ArtifactRecord{{
|
|
Kind: "structured_data",
|
|
SourceID: "narratio.example.characters",
|
|
LocalPath: "artifacts/characters.json",
|
|
}}
|
|
stage.Logs = []string{"logs/extract.log"}
|
|
stage.GeneratedConfigs = []string{"generated/extract.yaml"}
|
|
stage.Metadata = map[string]any{"bundle_path": "extract/results/run-1"}
|
|
}
|
|
|
|
func requireStageResultDetailsCleared(t *testing.T, stage *StageRecord) {
|
|
t.Helper()
|
|
if len(stage.Outputs) != 0 || len(stage.Logs) != 0 || len(stage.GeneratedConfigs) != 0 || len(stage.Metadata) != 0 {
|
|
t.Fatalf("result details were not cleared: %#v", stage)
|
|
}
|
|
}
|