package workspace import ( "encoding/json" "testing" "time" ) func TestStageManifestDefaults(t *testing.T) { if WorkspaceSchemaVersion != "notarius.workspace.v2" { t.Fatalf("current schema version = %q, want notarius.workspace.v2", WorkspaceSchemaVersion) } if WorkspaceSchemaVersionV1 != "notarius.workspace.v1" { t.Fatalf("legacy schema version = %q, want notarius.workspace.v1", WorkspaceSchemaVersionV1) } manifest := NewStageManifest(StageExtract, StatusRunning) if manifest.WorkspaceSchemaVersion != WorkspaceSchemaVersion { t.Fatalf("schema version = %q, want %q", manifest.WorkspaceSchemaVersion, WorkspaceSchemaVersion) } if manifest.Stage != StageExtract { t.Fatalf("stage = %q, want extract", manifest.Stage) } if manifest.Status != StatusRunning { t.Fatalf("status = %q, want running", manifest.Status) } } func TestManifestJSONRoundTrips(t *testing.T) { started := time.Unix(100, 0).UTC() completed := time.Unix(200, 0).UTC() t.Run("source", func(t *testing.T) { manifest := SourceManifest{ StageManifest: populatedManifest(StageSource, "", "seriatim", started, completed), SourceID: "source-1", } var got SourceManifest roundTripManifest(t, manifest, &got) if got.SourceID != manifest.SourceID || got.Stage != StageSource { t.Fatalf("round trip source manifest = %+v", got) } }) t.Run("extract", func(t *testing.T) { manifest := ExtractLaneManifest{ StageManifest: populatedManifest(StageExtract, "spells", "dnd/spells", started, completed), ChunkCount: 3, OutputCount: 2, } var got ExtractLaneManifest roundTripManifest(t, manifest, &got) if got.LaneID != "spells" || got.OutputCount != manifest.OutputCount || got.Stage != StageExtract { t.Fatalf("round trip extract manifest = %+v", got) } }) t.Run("merge", func(t *testing.T) { manifest := MergeLaneManifest{ StageManifest: populatedManifest(StageMerge, "spells", "appendorder", started, completed), InputCount: 2, } var got MergeLaneManifest roundTripManifest(t, manifest, &got) if got.InputCount != manifest.InputCount || got.Stage != StageMerge { t.Fatalf("round trip merge manifest = %+v", got) } }) t.Run("normalize", func(t *testing.T) { manifest := NormalizeLaneManifest{ StageManifest: populatedManifest(StageNormalize, "spells", "noop", started, completed), InputCount: 1, } var got NormalizeLaneManifest roundTripManifest(t, manifest, &got) if got.InputCount != manifest.InputCount || got.Stage != StageNormalize { t.Fatalf("round trip normalize manifest = %+v", got) } }) } func TestStatusValues(t *testing.T) { values := []StageStatus{ StatusPending, StatusRunning, StatusSucceeded, StatusSucceededWithRejections, StatusFailed, StatusInvalidated, } want := []string{ "pending", "running", "succeeded", "succeeded_with_rejections", "failed", "invalidated", } for i, value := range values { if string(value) != want[i] { t.Fatalf("status[%d] = %q, want %q", i, value, want[i]) } } } func populatedManifest(stage StageName, laneID string, moduleKey string, started time.Time, completed time.Time) StageManifest { manifest := NewStageManifest(stage, StatusSucceededWithRejections) manifest.LaneID = laneID manifest.ModuleKey = moduleKey manifest.DependencyFingerprints = []Fingerprint{{Name: "source", Value: "sha256:source"}} manifest.OutputDigests = []Fingerprint{{Name: "output", Value: "sha256:output"}} manifest.ValidationStatus = "approved_with_warnings" manifest.Rejections = []RejectionSummary{ { ValidatorName: "shape", ReasonCode: "invalid_shape", Message: "invalid output shape", Count: 1, }, } manifest.StartedAt = &started manifest.CompletedAt = &completed manifest.Metadata = map[string]string{"attempt": "1"} return manifest } func roundTripManifest(t *testing.T, in any, out any) { t.Helper() data, err := json.Marshal(in) if err != nil { t.Fatalf("marshal manifest: %v", err) } if err := json.Unmarshal(data, out); err != nil { t.Fatalf("unmarshal manifest: %v", err) } }