package notify import ( "b612.me/stario" "context" "errors" "math" "net" "testing" "time" ) func TestServerStopMonitorChanUsesRuntimeStopCtx(t *testing.T) { server := NewServer().(*ServerCommon) runtimeCtx, runtimeCancel := context.WithCancel(context.Background()) defer runtimeCancel() queue := stario.NewQueueCtx(runtimeCtx, 4, math.MaxUint32) server.setServerSessionRuntime(&serverSessionRuntime{ stopCtx: runtimeCtx, stopFn: runtimeCancel, queue: queue, }) ch := server.StopMonitorChan() if ch == nil { t.Fatal("StopMonitorChan should return runtime stop channel") } runtimeCancel() select { case <-ch: case <-time.After(time.Second): t.Fatal("StopMonitorChan should close after runtime context cancel") } } func TestServerSendEnvelopeUsesRuntimeUDPListener(t *testing.T) { server := NewServer().(*ServerCommon) UseLegacySecurityServer(server) receiverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") if err != nil { t.Fatalf("ResolveUDPAddr receiver failed: %v", err) } receiver, err := net.ListenUDP("udp", receiverAddr) if err != nil { t.Fatalf("ListenUDP receiver failed: %v", err) } defer receiver.Close() senderAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") if err != nil { t.Fatalf("ResolveUDPAddr sender failed: %v", err) } sender, err := net.ListenUDP("udp", senderAddr) if err != nil { t.Fatalf("ListenUDP sender failed: %v", err) } defer sender.Close() runtimeCtx, runtimeCancel := context.WithCancel(context.Background()) defer runtimeCancel() queue := stario.NewQueueCtx(runtimeCtx, 4, math.MaxUint32) server.setServerSessionRuntime(&serverSessionRuntime{ stopCtx: runtimeCtx, stopFn: runtimeCancel, queue: queue, udpListener: sender, }) server.markSessionStarted() defer server.markSessionStopped("test done", nil) // Ensure send path depends on runtime snapshot, not stale owner field reads. server.udpListener = nil client := newServerCodecClientConnForTest(server) client.ClientAddr = receiver.LocalAddr() client.setClientConnMaxWriteTimeout(100 * time.Millisecond) if err := server.sendEnvelope(client, newSignalAckEnvelope(1001)); err != nil { t.Fatalf("sendEnvelope failed: %v", err) } _ = receiver.SetReadDeadline(time.Now().Add(time.Second)) buf := make([]byte, 4096) n, _, err := receiver.ReadFromUDP(buf) if err != nil { t.Fatalf("receiver ReadFromUDP failed: %v", err) } if n == 0 { t.Fatal("receiver should get runtime udp payload") } } func TestServerSendEnvelopeUsesClientConnRuntimeTransport(t *testing.T) { server := NewServer().(*ServerCommon) UseLegacySecurityServer(server) runtimeLeft, runtimeRight := net.Pipe() defer runtimeLeft.Close() defer runtimeRight.Close() runtimeCtx, runtimeCancel := context.WithCancel(context.Background()) defer runtimeCancel() queue := stario.NewQueueCtx(runtimeCtx, 4, math.MaxUint32) server.setServerSessionRuntime(&serverSessionRuntime{ stopCtx: runtimeCtx, stopFn: runtimeCancel, queue: queue, }) server.markSessionStarted() defer server.markSessionStopped("test done", nil) client, _, _ := newStartedClientConnForTest(t, "", server, runtimeLeft, runtimeCtx, runtimeCancel) client.setClientConnMaxWriteTimeout(100 * time.Millisecond) client.applyClientConnAttachmentProfile(0, 100*time.Millisecond, server.defaultMsgEn, server.defaultMsgDe, server.handshakeRsaKey, server.SecretKey) recvCh := make(chan []byte, 1) errCh := make(chan error, 1) go func() { _ = runtimeRight.SetReadDeadline(time.Now().Add(time.Second)) reader := stario.NewFrameReader(runtimeRight, nil) payload, err := reader.Next() if err != nil { errCh <- err return } recvCh <- payload }() if err := server.sendEnvelope(client, newSignalAckEnvelope(1001)); err != nil { t.Fatalf("sendEnvelope failed: %v", err) } select { case err := <-errCh: t.Fatalf("runtime conn read failed: %v", err) case got := <-recvCh: if len(got) == 0 { t.Fatal("runtime conn should receive framed payload") } case <-time.After(time.Second): t.Fatal("runtime conn did not receive payload") } } func TestServerListenFailureCleansRuntimeAndAllowsRetry(t *testing.T) { server := NewServer().(*ServerCommon) UseLegacySecurityServer(server) err := server.Listen("tcp", "127.0.0.1:bad") if err == nil { t.Fatal("Listen should fail for invalid address") } if status := server.Status(); status.Alive { t.Fatalf("server should remain stopped after failed Listen: %+v", status) } rt := server.serverSessionRuntimeSnapshot() if rt == nil { t.Fatal("runtime snapshot should still expose stop context after cleanup") } if rt.queue != nil || rt.listener != nil || rt.udpListener != nil { t.Fatalf("runtime transport artifacts should be cleaned after failed Listen: %+v", rt) } if err := server.Listen("tcp", "127.0.0.1:0"); err != nil { t.Fatalf("Listen should succeed after failed attempt cleanup: %v", err) } if status := server.Status(); !status.Alive { t.Fatalf("server should be alive after successful retry Listen: %+v", status) } if err := server.Stop(); err != nil { t.Fatalf("Stop failed: %v", err) } } func TestServerMarkSessionStoppedUsesRuntimeStopFn(t *testing.T) { server := NewServer().(*ServerCommon) if !server.beginServerSessionStart() { t.Fatal("beginServerSessionStart should succeed") } server.markSessionStarted() runtimeCtx, runtimeCancel := context.WithCancel(context.Background()) defer runtimeCancel() server.setServerSessionRuntime(&serverSessionRuntime{ stopCtx: runtimeCtx, stopFn: runtimeCancel, }) fallbackCtx, fallbackCancel := context.WithCancel(context.Background()) defer fallbackCancel() server.stopCtx = fallbackCtx server.stopFn = fallbackCancel server.markSessionStopped("runtime stop", nil) select { case <-runtimeCtx.Done(): case <-time.After(time.Second): t.Fatal("runtime stop context should be canceled by markSessionStopped") } select { case <-fallbackCtx.Done(): t.Fatal("fallback owner stop context should not be canceled when runtime stopFn is active") default: } rt := server.serverSessionRuntimeSnapshot() if rt == nil { t.Fatal("runtime snapshot should remain available after stop") } if rt.listener != nil || rt.udpListener != nil || rt.queue != nil { t.Fatalf("runtime transport should be cleared after stop: %+v", rt) } if rt.stopCtx == nil { t.Fatalf("runtime stop context should be preserved after stop: %+v", rt) } } func TestServerClearSessionRuntimeTransportKeepsLogicalStopContext(t *testing.T) { server := NewServer().(*ServerCommon) stopCtx, stopFn := context.WithCancel(context.Background()) defer stopFn() queue := stario.NewQueueCtx(stopCtx, 4, math.MaxUint32) server.setServerSessionRuntime(&serverSessionRuntime{ stopCtx: stopCtx, stopFn: stopFn, queue: queue, listener: &stubListener{}, }) transportStopCtx := server.serverTransportStopContextSnapshot() server.clearServerSessionRuntimeTransport() rt := server.serverSessionRuntimeSnapshot() if rt == nil { t.Fatal("runtime snapshot should remain after transport clear") } if rt.listener != nil || rt.udpListener != nil { t.Fatalf("runtime transport should be cleared: %+v", rt) } if rt.queue != queue { t.Fatalf("runtime queue should be preserved across pure transport clear: got %v want %v", rt.queue, queue) } select { case <-transportStopCtx.Done(): case <-time.After(time.Second): t.Fatal("transport stop context should be canceled after clearing transport") } select { case <-server.serverStopContextSnapshot().Done(): t.Fatal("logical stop context should remain active after clearing transport") default: } if server.serverTransportAttachedSnapshot() { t.Fatal("server transport should be marked detached after clearing transport") } if got := server.serverQueueSnapshot(); got != queue { t.Fatalf("server queue snapshot should be preserved after transport clear: got %v want %v", got, queue) } } func TestServerClearSessionRuntimeTransportPreservesQueueForEncoding(t *testing.T) { server := NewServer().(*ServerCommon) UseLegacySecurityServer(server) stopCtx, stopFn := context.WithCancel(context.Background()) defer stopFn() queue := stario.NewQueueCtx(stopCtx, 4, math.MaxUint32) server.setServerSessionRuntime(&serverSessionRuntime{ stopCtx: stopCtx, stopFn: stopFn, queue: queue, listener: &stubListener{}, }) server.markSessionStarted() defer server.markSessionStopped("test done", nil) server.clearServerSessionRuntimeTransport() data, err := server.encodeEnvelope(newServerCodecClientConnForTest(server), newSignalAckEnvelope(1002)) if err != nil { t.Fatalf("encodeEnvelope failed after pure transport clear: %v", err) } if len(data) == 0 { t.Fatal("encodeEnvelope should still return framed payload after pure transport clear") } } type stubListener struct{} func (stubListener) Accept() (net.Conn, error) { return nil, errors.New("stub") } func (stubListener) Close() error { return nil } func (stubListener) Addr() net.Addr { return stubAddr("stub") } type stubAddr string func (a stubAddr) Network() string { return string(a) } func (a stubAddr) String() string { return string(a) }