package ca_test import ( "context" "math" "testing" "time" "github.com/uopi/goca/proto" "github.com/uopi/goca/testca" ) // TestGetDisconnectMidRequest covers the "circuit disconnected during GET" path // in conn.go: the server drops the connection while a READ_NOTIFY is in flight, // so the in-flight reply channel is closed by the reconnect loop and the waiter // unblocks with ok=false. func TestGetDisconnectMidRequest(t *testing.T) { srv := newTestServer(t) cli := newTestClient(t, srv) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // First GET establishes the channel/circuit successfully. if _, err := cli.Get(ctx, "TEST:DOUBLE"); err != nil { t.Fatalf("initial Get: %v", err) } // Arm a mid-request disconnect for the next READ_NOTIFY. srv.SetGetFault(testca.GetFaultDisconnect) if _, err := cli.Get(ctx, "TEST:DOUBLE"); err == nil { t.Fatal("Get with mid-request disconnect: want error") } } // TestGetCorruptReply covers the "failed to decode GET reply" path: the server // answers READ_NOTIFY with a payload too short for DecodeTimeValue. func TestGetCorruptReply(t *testing.T) { srv := newTestServer(t) cli := newTestClient(t, srv) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Establish the channel first so resolution succeeds. if _, err := cli.Get(ctx, "TEST:DOUBLE"); err != nil { t.Fatalf("initial Get: %v", err) } srv.SetGetFault(testca.GetFaultCorrupt) if _, err := cli.Get(ctx, "TEST:DOUBLE"); err == nil { t.Fatal("Get with corrupt reply: want error") } } // TestServerDisconnectReconnects covers the CmdServerDisc dispatch branch and the // reconnect loop: after an orderly server disconnect the client transparently // reconnects, re-creates its channel, re-subscribes the monitor, and continues to // receive updates. func TestServerDisconnectReconnects(t *testing.T) { srv := newTestServer(t) cli := newTestClient(t, srv) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() ch := make(chan proto.TimeValue, 16) unsub, err := cli.Subscribe(ctx, "TEST:DOUBLE", ch) if err != nil { t.Fatalf("Subscribe: %v", err) } defer unsub() // Drain the initial value. select { case <-ch: case <-ctx.Done(): t.Fatal("timeout waiting for initial value") } // Force an orderly disconnect; the client must reconnect on its own. srv.Disconnect() // After the reconnect settles, push a fresh value and expect to see it. // The reconnect back-off is ~1s, so poll generously and re-arm the value // until it is observed (the re-subscribe also delivers an initial value). deadline := time.After(8 * time.Second) tick := time.NewTicker(300 * time.Millisecond) defer tick.Stop() for { select { case tv := <-ch: if math.Abs(tv.Double-88.8) < 1e-6 { return // reconnect succeeded and the update flowed through } case <-tick.C: _ = srv.SetValue("TEST:DOUBLE", 88.8) case <-deadline: t.Fatal("timeout waiting for post-reconnect update") } } }