package gateway import ( "context" "encoding/hex" "io" "log/slog" "net" "testing" "time" "lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope" "lingniu-vehicle-ingest/go/vehicle-gateway/internal/protocol/gb32960" "lingniu-vehicle-ingest/go/vehicle-gateway/internal/protocol/jt808" ) func TestTCPServerPublishesGoodFrameToRawAndUnified(t *testing.T) { frame, err := hex.DecodeString("7E020000320133077954250001000000000048000301D2C4C707376139000A00E6004F26063016235701040001900C2504000000000202000030011F31010F867E") if err != nil { t.Fatal(err) } sink := &recordingSink{} server := newTestServer(t, TCPProtocol{ Protocol: envelope.ProtocolJT808, Addr: ":0", Extract: jt808.ExtractFrames, Parse: jt808.ParseFrame, }, sink) client, done := runPipe(t, server) if _, err := client.Write(frame); err != nil { t.Fatalf("client.Write() error = %v", err) } _ = client.Close() <-done if len(sink.raw) != 1 || len(sink.unified) != 1 { t.Fatalf("raw=%d unified=%d", len(sink.raw), len(sink.unified)) } if sink.raw[0].Phone != "13307795425" { t.Fatalf("phone = %q", sink.raw[0].Phone) } if sink.raw[0].Fields[envelope.FieldTotalMileageKM] != 10241.2 { t.Fatalf("total mileage = %#v", sink.raw[0].Fields[envelope.FieldTotalMileageKM]) } } func TestTCPServerPublishesBadFrameOnlyToRaw(t *testing.T) { good := buildGBFrame(0x02, 0xfe, "LNBSCB3D4R1234567", nil) good[len(good)-1] ^= 0xff sink := &recordingSink{} server := newTestServer(t, TCPProtocol{ Protocol: envelope.ProtocolGB32960, Addr: ":0", Extract: gb32960.ExtractFrames, Parse: gb32960.ParseFrame, }, sink) client, done := runPipe(t, server) if _, err := client.Write(good); err != nil { t.Fatalf("client.Write() error = %v", err) } _ = client.Close() <-done if len(sink.raw) != 1 || len(sink.unified) != 0 { t.Fatalf("raw=%d unified=%d", len(sink.raw), len(sink.unified)) } if sink.raw[0].ParseStatus != envelope.ParseBadFrame { t.Fatalf("parse status = %q", sink.raw[0].ParseStatus) } if sink.raw[0].ParseError == "" { t.Fatal("parse error should be recorded") } } func TestTCPServerWritesProtocolResponseAfterPublish(t *testing.T) { frame := buildGBFrame(0x07, 0xfe, "LNBSCB3D4R1234567", nil) sink := &recordingSink{} server := newTestServer(t, TCPProtocol{ Protocol: envelope.ProtocolGB32960, Addr: ":0", Extract: gb32960.ExtractFrames, Parse: gb32960.ParseFrame, Respond: func(_ []byte, env envelope.FrameEnvelope) ([]byte, bool, error) { if len(sink.unified) != 1 || sink.unified[0].EventID != env.EventID { t.Fatalf("response built before publish: raw=%d unified=%d", len(sink.raw), len(sink.unified)) } return []byte("ACK"), true, nil }, }, sink) client, done := runPipe(t, server) if _, err := client.Write(frame); err != nil { t.Fatalf("client.Write() error = %v", err) } buf := make([]byte, 3) if _, err := io.ReadFull(client, buf); err != nil { t.Fatalf("read response error = %v", err) } if string(buf) != "ACK" { t.Fatalf("response = %q", string(buf)) } _ = client.Close() <-done } func newTestServer(t *testing.T, protocol TCPProtocol, sink *recordingSink) *TCPServer { t.Helper() server, err := NewTCPServer(TCPServerConfig{ Protocol: protocol, Sink: sink, Logger: slog.New(slog.NewTextHandler(testWriter{t: t}, nil)), ReadBufferSize: 1024, IdleTimeout: time.Second, MaxConnections: 1, }) if err != nil { t.Fatalf("NewTCPServer() error = %v", err) } return server } func runPipe(t *testing.T, server *TCPServer) (net.Conn, <-chan struct{}) { t.Helper() client, srv := net.Pipe() done := make(chan struct{}) go func() { defer close(done) server.handleConnection(context.Background(), srv) }() return client, done } type recordingSink struct { raw []envelope.FrameEnvelope unified []envelope.FrameEnvelope } func (s *recordingSink) PublishRaw(_ context.Context, env envelope.FrameEnvelope) error { s.raw = append(s.raw, env) return nil } func (s *recordingSink) PublishUnified(_ context.Context, env envelope.FrameEnvelope) error { s.unified = append(s.unified, env) return nil } func (s *recordingSink) Close() error { return nil } type testWriter struct { t *testing.T } func (w testWriter) Write(p []byte) (int, error) { w.t.Log(string(p)) return len(p), nil } func buildGBFrame(command byte, response byte, vin string, body []byte) []byte { frame := []byte{'#', '#', command, response} vinBytes := []byte(vin) if len(vinBytes) < 17 { vinBytes = append(vinBytes, make([]byte, 17-len(vinBytes))...) } frame = append(frame, vinBytes[:17]...) frame = append(frame, 0x01, byte(len(body)>>8), byte(len(body))) frame = append(frame, body...) var bcc byte for _, value := range frame[2:] { bcc ^= value } return append(frame, bcc) }