proto-socket/go/communicator_nonce_test.go
toki 9cc1f1d58f sync: update communicator implementation across all languages
- Align protocol documentation (PROTOCOL.md, README.md, VERSIONING.md)
- Go: add nonce test, update communicator
- Kotlin: update Communicator, TcpClient, TcpServer, add TLS test
- Python: update all modules, add certificate test resources
- TypeScript: update communicator, tcp/ws clients and servers, add tests
- Dart: update communicator, heartbeat mixin, and tests
2026-04-26 05:31:56 +09:00

131 lines
3.1 KiB
Go

package toki_socket
import (
"sync"
"testing"
"time"
"google.golang.org/protobuf/proto"
"toki-labs.com/toki_socket/go/packets"
)
type nonceTestTransport struct {
mu sync.Mutex
packets []*packets.PacketBase
}
func (t *nonceTestTransport) WritePacket(base *packets.PacketBase) error {
t.mu.Lock()
defer t.mu.Unlock()
t.packets = append(t.packets, base)
return nil
}
func (t *nonceTestTransport) Close() error {
return nil
}
func (t *nonceTestTransport) sent() []*packets.PacketBase {
t.mu.Lock()
defer t.mu.Unlock()
return append([]*packets.PacketBase{}, t.packets...)
}
func nonceTestParserMap() ParserMap {
return ParserMap{
TypeNameOf(&packets.TestData{}): func(b []byte) (proto.Message, error) {
m := &packets.TestData{}
return m, proto.Unmarshal(b, m)
},
}
}
func TestNonceWrapsAfterInt32MaxWithoutEmittingZero(t *testing.T) {
transport := &nonceTestTransport{}
communicator := NewCommunicator(transport, nonceTestParserMap())
defer communicator.Close()
communicator.nonce.Store(MaxNonce - 1)
if err := communicator.Send(&packets.TestData{Index: 1}); err != nil {
t.Fatal(err)
}
if err := communicator.Send(&packets.TestData{Index: 2}); err != nil {
t.Fatal(err)
}
sent := transport.sent()
if len(sent) != 2 {
t.Fatalf("expected 2 packets, got %d", len(sent))
}
if got := sent[0].GetNonce(); got != MaxNonce {
t.Fatalf("first nonce = %d, want %d", got, MaxNonce)
}
if got := sent[1].GetNonce(); got != 1 {
t.Fatalf("second nonce = %d, want 1", got)
}
for _, packet := range sent {
if packet.GetNonce() == 0 {
t.Fatal("emitted reserved nonce 0")
}
}
}
func TestSendRequestNonceWrapsAtInt32Max(t *testing.T) {
transport := &nonceTestTransport{}
communicator := NewCommunicator(transport, nonceTestParserMap())
defer communicator.Close()
communicator.nonce.Store(MaxNonce - 1)
resultCh := make(chan *packets.TestData, 1)
errCh := make(chan error, 1)
go func() {
res, err := SendRequestTyped[*packets.TestData, *packets.TestData](
communicator,
&packets.TestData{Index: 1, Message: "max"},
time.Second,
)
if err != nil {
errCh <- err
return
}
resultCh <- res
}()
var request *packets.PacketBase
for deadline := time.Now().Add(time.Second); time.Now().Before(deadline); {
sent := transport.sent()
if len(sent) == 1 {
request = sent[0]
break
}
time.Sleep(time.Millisecond)
}
if request == nil {
t.Fatal("request packet was not sent")
}
if request.GetNonce() != MaxNonce {
t.Fatalf("request nonce = %d, want %d", request.GetNonce(), MaxNonce)
}
if request.GetNonce() == 0 {
t.Fatal("emitted reserved nonce 0")
}
response := &packets.TestData{Index: 2, Message: "max response"}
data, err := proto.Marshal(response)
if err != nil {
t.Fatal(err)
}
communicator.OnReceivedData(TypeNameOf(response), data, 0, request.GetNonce())
select {
case err := <-errCh:
t.Fatal(err)
case got := <-resultCh:
if got.GetMessage() != "max response" {
t.Fatalf("response message = %q, want %q", got.GetMessage(), "max response")
}
case <-time.After(time.Second):
t.Fatal("timed out waiting for response")
}
}