返回 DeepSeek-Reasonix
compact_summary_guard_test.go
根目录 / internal / agent / compact_summary_guard_test.go
1 package agent
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "strings"
8 "testing"
9
10 "reasonix/internal/event"
11 "reasonix/internal/extension"
12 "reasonix/internal/extension/dispatch"
13 "reasonix/internal/extension/protocol"
14 )
15
16 func TestCompactionPrepareCannotExpandAutomaticSummaryPastWindow(t *testing.T) {
17 const window = 60_000
18 tests := []struct {
19 name string
20 replacement func(*testing.T, json.RawMessage) protocol.InterceptResult
21 }{
22 {
23 name: "messages",
24 replacement: func(t *testing.T, _ json.RawMessage) protocol.InterceptResult {
25 return replaceWith(t, dispatch.CompactionPreparePayload{
26 Messages: []protocol.ProviderMessage{{
27 Role: protocol.ProviderRoleUser, Content: strings.Repeat("x", window*4),
28 }},
29 })
30 },
31 },
32 {
33 name: "guidance",
34 replacement: func(t *testing.T, raw json.RawMessage) protocol.InterceptResult {
35 var payload dispatch.CompactionPreparePayload
36 if err := json.Unmarshal(raw, &payload); err != nil {
37 t.Fatalf("decode compaction.prepare payload: %v", err)
38 }
39 payload.Guidance = strings.Repeat("preserve expanded guidance ", window)
40 return replaceWith(t, payload)
41 },
42 },
43 }
44
45 for _, tc := range tests {
46 t.Run(tc.name, func(t *testing.T) {
47 client := &fakeDispatchClient{interceptFn: func(ev protocol.InterceptEvent, raw json.RawMessage) (protocol.InterceptResult, error) {
48 if ev == protocol.EventCompactionPrepare {
49 return tc.replacement(t, raw), nil
50 }
51 return protocol.InterceptResult{Decision: protocol.DecisionContinue}, nil
52 }}
53 prov := &opaqueWindowProvider{}
54 a := agentOverForceWindow(t, prov, foldableSessionOverForce(120), window)
55 a.svc.extensions = newExtDispatcher(client, true, nil, extension.PointCompactionPrepare)
56
57 var rejected *ContextMaintenanceReceipt
58 a.svc.sink = event.FuncSink(func(e event.Event) {
59 if e.Kind == event.ContextMaintenanceEvent && e.Maintenance != nil && e.Maintenance.Status == "blocked" {
60 rejected = &ContextMaintenanceReceipt{Status: e.Maintenance.Status, Reason: e.Maintenance.Reason}
61 }
62 })
63
64 // The oversized replacement is never sent; over the ceiling the
65 // truncation rescue then stands in for the rejected summary.
66 if err := prepareContext(context.Background(), a, CompactionTriggerPressure); !errors.Is(err, ErrCompactionRequired) {
67 t.Fatalf("pressure maintenance error = %v, want a recoverable context failure", err)
68 }
69 if len(prov.requests) != 0 {
70 t.Fatalf("summary requests = %d, want none for an oversized extension replacement", len(prov.requests))
71 }
72 if rejected == nil || !strings.Contains(rejected.Reason, "prepared summary request") {
73 t.Fatalf("blocked receipt = %+v, want the final summary-budget rejection", rejected)
74 }
75 assertNoFailedSummaryRewrite(t, a)
76 })
77 }
78 }
79
79 lines GO