TestProtectionEvents_CreateAndList covers the gate vs scan row shapes: a gate event carries subject_addr and leaves the scan-only columns null; a scan event carries detector/score/categories. Both round-trip through ListProtectionEventsByMessage.
(t *testing.T)
| 31 | // carries subject_addr and leaves the scan-only columns null; a scan event carries |
| 32 | // detector/score/categories. Both round-trip through ListProtectionEventsByMessage. |
| 33 | func TestProtectionEvents_CreateAndList(t *testing.T) { |
| 34 | pool := testutil.TestDB(t) |
| 35 | store := identity.NewStore(pool) |
| 36 | ctx := context.Background() |
| 37 | |
| 38 | const msgID = "msg_screentest1" |
| 39 | agentID := seedProtAgent(t, store, ctx, "agent@screen.example.com", "screen.example.com") |
| 40 | score := 0.87 |
| 41 | |
| 42 | gate := identity.ProtectionEvent{ |
| 43 | ID: identity.DeterministicProtectionEventID(msgID, identity.ScreeningSourceGate, identity.ReviewReasonSenderGate, ""), |
| 44 | MessageID: msgID, |
| 45 | AgentID: agentID, |
| 46 | Direction: "inbound", |
| 47 | Source: identity.ScreeningSourceGate, |
| 48 | Reason: identity.ReviewReasonSenderGate, |
| 49 | Action: "review", |
| 50 | SubjectAddr: "attacker@evil.com", |
| 51 | } |
| 52 | scan := identity.ProtectionEvent{ |
| 53 | ID: identity.DeterministicProtectionEventID(msgID, identity.ScreeningSourceScan, identity.ReviewReasonInboundScan, "heuristics"), |
| 54 | MessageID: msgID, |
| 55 | AgentID: agentID, |
| 56 | Direction: "inbound", |
| 57 | Source: identity.ScreeningSourceScan, |
| 58 | Reason: identity.ReviewReasonInboundScan, |
| 59 | Action: "block", |
| 60 | Detector: "heuristics", |
| 61 | Score: &score, |
| 62 | Categories: json.RawMessage(`[{"name":"prompt_injection_direct","score":0.87}]`), |
| 63 | } |
| 64 | for _, ev := range []identity.ProtectionEvent{gate, scan} { |
| 65 | if err := store.CreateProtectionEvent(ctx, ev); err != nil { |
| 66 | t.Fatalf("CreateProtectionEvent(%s): %v", ev.Source, err) |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | got, err := store.ListProtectionEventsByMessage(ctx, msgID) |
| 71 | if err != nil { |
| 72 | t.Fatalf("ListProtectionEventsByMessage: %v", err) |
| 73 | } |
| 74 | if len(got) != 2 { |
| 75 | t.Fatalf("want 2 events, got %d", len(got)) |
| 76 | } |
| 77 | |
| 78 | var sawScan, sawGate bool |
| 79 | for _, ev := range got { |
| 80 | switch ev.Source { |
| 81 | case identity.ScreeningSourceScan: |
| 82 | sawScan = true |
| 83 | if ev.Score == nil || *ev.Score < 0.86 || *ev.Score > 0.88 { |
| 84 | t.Errorf("scan score not round-tripped: %v", ev.Score) |
| 85 | } |
| 86 | if ev.Detector != "heuristics" { |
| 87 | t.Errorf("detector = %q, want heuristics", ev.Detector) |
| 88 | } |
| 89 | if len(ev.Categories) == 0 { |
| 90 | t.Errorf("categories not round-tripped") |
nothing calls this directly
no test coverage detected