返回 DeepSeek-Reasonix
proxy_test.go
根目录 / internal / extension / proxy_test.go
1 package extension
2
3 import (
4 "context"
5 "sync/atomic"
6 "testing"
7 "time"
8 )
9
10 type fakeBackend struct {
11 id string
12 closed atomic.Bool
13 }
14
15 func (b *fakeBackend) ID() string { return b.id }
16 func (b *fakeBackend) Close(context.Context) error {
17 b.closed.Store(true)
18 return nil
19 }
20
21 func TestStableProxyReplaceDrainsPrevious(t *testing.T) {
22 p := NewStableProxy()
23 a := &fakeBackend{id: "a"}
24 b := &fakeBackend{id: "b"}
25 if err := p.Replace(context.Background(), a, 1); err != nil {
26 t.Fatal(err)
27 }
28 if err := p.Replace(context.Background(), b, 2); err != nil {
29 t.Fatal(err)
30 }
31 if !a.closed.Load() {
32 t.Fatal("previous backend not drained")
33 }
34 if p.Active().ID() != "b" || p.Generation() != 2 {
35 t.Fatalf("active = %v gen=%d", p.Active(), p.Generation())
36 }
37 }
38
39 func TestStableProxyCallWithoutBackend(t *testing.T) {
40 p := NewStableProxy()
41 if err := p.Call(func(Backend) error { return nil }); err == nil {
42 t.Fatal("expected no-backend error")
43 }
44 }
45
46 func TestStableProxyReplaceCancelsInFlight(t *testing.T) {
47 p := NewStableProxy()
48 a := &fakeBackend{id: "a"}
49 if err := p.Replace(context.Background(), a, 1); err != nil {
50 t.Fatal(err)
51 }
52 started := make(chan struct{})
53 done := make(chan error, 1)
54 go func() {
55 done <- p.CallCtx(context.Background(), func(ctx context.Context, _ Backend) error {
56 close(started)
57 select {
58 case <-ctx.Done():
59 return ctx.Err()
60 case <-time.After(5 * time.Second):
61 return nil
62 }
63 })
64 }()
65 <-started
66 b := &fakeBackend{id: "b"}
67 if err := p.Replace(context.Background(), b, 2); err != nil {
68 t.Fatal(err)
69 }
70 select {
71 case err := <-done:
72 if err == nil {
73 t.Fatal("expected in-flight call cancelled")
74 }
75 case <-time.After(2 * time.Second):
76 t.Fatal("in-flight call did not complete after replace")
77 }
78 }
79
79 lines GO