package main import ( "encoding/json" "reflect" "testing" "time" ) func TestDecodePayloadRejectsMissingNullUnknownAndTrailing(t *testing.T) { want := workflowPayload{RunID: "run", Sequence: 3} for _, raw := range []string{ `{"run_id":"run","sequence":3}`, `{"run_id":"run","sequence":3} `, } { got, ok := decodePayload(json.RawMessage(raw)) if !ok || !reflect.DeepEqual(got, want) { t.Fatalf("decode %s: %#v %t", raw, got, ok) } } for _, raw := range []string{ `{"run_id":"run"}`, `{"run_id":null,"sequence":3}`, `{"run_id":"run","sequence":null}`, `{"run_id":"run","sequence":3,"extra":true}`, `{"run_id":"run","sequence":3}{"again":true}`, } { if _, ok := decodePayload(json.RawMessage(raw)); ok { t.Fatalf("accepted invalid payload %s", raw) } } } func TestWorkflowSpecHasFiniteParallelBranchesAndFixedRetry(t *testing.T) { spec := workflowSpec("0123456789abcdef0123456789abcdef", "load.workflow.run", 5*time.Second) if len(spec.Body.Steps) != 1 || spec.Body.Steps[0].Kind != "PARALLEL" || spec.Body.Steps[0].Parallel == nil || len(spec.Body.Steps[0].Parallel.Branches) != 2 { t.Fatal("parallel shape changed") } for _, branch := range spec.Body.Steps[0].Parallel.Branches { if len(branch.Body.Steps) != 1 || branch.Body.Steps[0].Task == nil || branch.Body.Steps[0].Task.Retry.MaxAttempts != 1 || branch.Body.Steps[0].Task.RecoverySafety != "RECONCILE_ONLY" { t.Fatalf("unsafe branch %#v", branch) } } } func TestOutputVerificationRequiresBothBranches(t *testing.T) { runID := "run" good := `{"left":{"run_id":"run","sequence":3},"right":{"run_id":"run","sequence":3}}` if !matchesOutput(json.RawMessage(good), runID, 3) { t.Fatal("valid output rejected") } for _, raw := range []string{ `{"left":{"run_id":"run","sequence":3}}`, `{"left":{"run_id":"run","sequence":3},"right":{"run_id":"other","sequence":3}}`, `{"left":{"run_id":"run","sequence":3},"right":{"run_id":"run","sequence":3},"extra":true}`, } { if matchesOutput(json.RawMessage(raw), runID, 3) { t.Fatalf("accepted invalid output %s", raw) } } }