- Update agent-ops project rules and roadmap files - Add proto-socket infrastructure communication rail milestone - Update Flutter pubspec.lock and contracts notes - Enhance core service: config, HTTP middleware, router - Add notification module improvements - Add protosocket internal package
138 lines
3.5 KiB
Go
138 lines
3.5 KiB
Go
package protosocket
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
toki "git.toki-labs.com/toki/proto-socket/go"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
"nhooyr.io/websocket"
|
|
)
|
|
|
|
func TestServerAcceptsWebSocketAndClosesClients(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
srv := NewServer(Config{
|
|
HeartbeatIntervalSec: 0,
|
|
HeartbeatWaitSec: 0,
|
|
}, logger)
|
|
|
|
testServer := httptest.NewServer(srv)
|
|
defer testServer.Close()
|
|
|
|
wsURL := strings.Replace(testServer.URL, "http", "ws", 1)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
conn, _, err := websocket.Dial(ctx, wsURL, nil)
|
|
if err != nil {
|
|
t.Fatalf("failed to dial websocket: %v", err)
|
|
}
|
|
defer conn.Close(websocket.StatusNormalClosure, "")
|
|
|
|
client := toki.NewWsClient(conn, 0, 0, ParserMap())
|
|
defer client.Close()
|
|
|
|
if !client.IsAlive() {
|
|
t.Error("client should be alive after connection")
|
|
}
|
|
|
|
disconnected := make(chan struct{}, 1)
|
|
client.AddDisconnectListener(func(c *toki.WsClient) {
|
|
disconnected <- struct{}{}
|
|
})
|
|
|
|
if err := srv.Close(); err != nil {
|
|
t.Errorf("failed to close server: %v", err)
|
|
}
|
|
|
|
select {
|
|
case <-disconnected:
|
|
// success
|
|
case <-time.After(1 * time.Second):
|
|
t.Error("timed out waiting for client to disconnect after server close")
|
|
}
|
|
}
|
|
|
|
func TestServerRespondsWithErrorEnvelopeForUnsupportedAction(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
srv := NewServer(Config{
|
|
HeartbeatIntervalSec: 0,
|
|
HeartbeatWaitSec: 0,
|
|
}, logger)
|
|
defer srv.Close()
|
|
|
|
testServer := httptest.NewServer(srv)
|
|
defer testServer.Close()
|
|
|
|
wsURL := strings.Replace(testServer.URL, "http", "ws", 1)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
conn, _, err := websocket.Dial(ctx, wsURL, nil)
|
|
if err != nil {
|
|
t.Fatalf("failed to dial websocket: %v", err)
|
|
}
|
|
defer conn.Close(websocket.StatusNormalClosure, "")
|
|
|
|
client := toki.NewWsClient(conn, 0, 0, ParserMap())
|
|
defer client.Close()
|
|
|
|
requestEnv := Envelope{
|
|
ProtocolVersion: ProtocolVersion,
|
|
ID: "req-999",
|
|
Type: "request",
|
|
Channel: "tasks",
|
|
Action: "invalid-unsupported-action",
|
|
}
|
|
reqStruct, err := requestEnv.ToStruct()
|
|
if err != nil {
|
|
t.Fatalf("failed to create request struct: %v", err)
|
|
}
|
|
|
|
resStruct, err := toki.SendRequestTyped[*structpb.Struct, *structpb.Struct](&client.Communicator, reqStruct, 1*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("failed to send request: %v", err)
|
|
}
|
|
|
|
responseEnv, err := EnvelopeFromStruct(resStruct)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse response struct: %v", err)
|
|
}
|
|
|
|
if responseEnv.Type != "error" {
|
|
t.Errorf("expected response type 'error', got %q", responseEnv.Type)
|
|
}
|
|
if responseEnv.CorrelationID != "req-999" {
|
|
t.Errorf("expected CorrelationID 'req-999', got %q", responseEnv.CorrelationID)
|
|
}
|
|
if responseEnv.Error == nil {
|
|
t.Fatal("expected error in response env")
|
|
}
|
|
if responseEnv.Error.Code != "UNSUPPORTED_ACTION" {
|
|
t.Errorf("expected error code 'UNSUPPORTED_ACTION', got %q", responseEnv.Error.Code)
|
|
}
|
|
if responseEnv.Error.Retryable {
|
|
t.Error("expected Retryable to be false for UNSUPPORTED_ACTION")
|
|
}
|
|
}
|
|
|
|
func TestServerHeartbeatConfigSupport(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
|
|
srvZero := NewServer(Config{
|
|
HeartbeatIntervalSec: 0,
|
|
HeartbeatWaitSec: 0,
|
|
}, logger)
|
|
srvZero.Close()
|
|
|
|
srvNonZero := NewServer(Config{
|
|
HeartbeatIntervalSec: 10,
|
|
HeartbeatWaitSec: 2,
|
|
}, logger)
|
|
srvNonZero.Close()
|
|
}
|