| 1 | package transcript |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "log/slog" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | |
| 11 | "reasonix/internal/provider" |
| 12 | ) |
| 13 | |
| 14 | func TestBaselineFailureDiagnosticsPreserveCauseWithoutContent(t *testing.T) { |
| 15 | const secret = "PRIVATE-CHAT-PATH-TOKEN" |
| 16 | for _, test := range []struct { |
| 17 | name string |
| 18 | rows []Message |
| 19 | code string |
| 20 | }{ |
| 21 | {"duplicate", []Message{ |
| 22 | {RecordID: secret, MessageID: secret + "-first", Role: "tool", Content: secret}, |
| 23 | {RecordID: secret, MessageID: secret + "-second", ToolCallID: secret, Role: "tool", Content: strings.Repeat(secret, 10000), |
| 24 | Reasoning: secret, ToolCalls: []ToolCall{{Arguments: secret}}}, |
| 25 | }, "duplicate_record_identity"}, |
| 26 | {"missing", []Message{{Role: secret, Content: secret}}, "missing_record_identity"}, |
| 27 | {"encode", []Message{{RecordID: "one", ServerSearch: []provider.ServerSearchCall{{Raw: json.RawMessage(secret)}}}}, "baseline_encode_failed"}, |
| 28 | } { |
| 29 | t.Run(test.name, func(t *testing.T) { |
| 30 | projection, err := NewProjection(testIdentity, test.rows, 0) |
| 31 | var diagnostic *BaselineError |
| 32 | if projection != nil || !errors.As(err, &diagnostic) { |
| 33 | t.Fatalf("projection = %v, error = %v", projection, err) |
| 34 | } |
| 35 | if diagnostic.Code() != test.code || !errors.Is(err, diagnostic.cause) { |
| 36 | t.Fatalf("lost error classification or cause: %v", err) |
| 37 | } |
| 38 | if test.name == "encode" { |
| 39 | var marshalError *json.MarshalerError |
| 40 | if !errors.As(err, &marshalError) { |
| 41 | t.Fatal("original JSON error type is no longer available") |
| 42 | } |
| 43 | } |
| 44 | var output bytes.Buffer |
| 45 | slog.New(slog.NewJSONHandler(&output, nil)).Error("failed", "diagnostic", diagnostic) |
| 46 | if strings.Contains(output.String(), secret) || output.Len() > 1500 { |
| 47 | t.Fatal("diagnostic leaked imported data or exceeded its bounded size") |
| 48 | } |
| 49 | var textOutput bytes.Buffer |
| 50 | slog.New(slog.NewTextHandler(&textOutput, nil)).Error("failed", "diagnostic", diagnostic) |
| 51 | if strings.Contains(textOutput.String(), secret) || textOutput.Len() > 1500 || |
| 52 | !strings.Contains(textOutput.String(), "diagnostic.code="+test.code) { |
| 53 | t.Fatal("text log leaked imported data or lost structured diagnostics") |
| 54 | } |
| 55 | var entry struct { |
| 56 | Diagnostic map[string]any `json:"diagnostic"` |
| 57 | } |
| 58 | if err := json.Unmarshal(output.Bytes(), &entry); err != nil { |
| 59 | t.Fatal(err) |
| 60 | } |
| 61 | fields := entry.Diagnostic |
| 62 | if fields["code"] != test.code || fields["record_count"] != float64(len(test.rows)) { |
| 63 | t.Fatalf("wrong persisted diagnostic: %+v", fields) |
| 64 | } |
| 65 | if test.name == "duplicate" && (fields["record_index"] != float64(1) || fields["previous_record_index"] != float64(0) || fields["record_key"] != diagnosticKey(secret)) { |
| 66 | t.Fatalf("missing conflict positions or stable identity: %+v", fields) |
| 67 | } |
| 68 | if test.name == "missing" && (fields["record_index"] != float64(0) || fields["role"] != "other") { |
| 69 | t.Fatalf("invalid role was not classified safely: %+v", fields) |
| 70 | } |
| 71 | }) |
| 72 | } |
| 73 | } |
| 74 |