/* * ============================================================================ * Projekt.....: rs2322tcp * Datei.......: data_handler_test.go * Copyright (C) 2026 Dieter Lang * * SPDX-License-Identifier: GPL-3.0-or-later * * Beschreibung: * Tests für die Verbindung von DataListener, TCP-Datenverbindung und * serieller Geräteverbindung. * ============================================================================ */ package server_test import ( "bytes" "io" "net" "sync" "testing" "time" "git.lang-dieter.de/rs2322tcp/internal/server" ) /////////////////////////////////////////////////////////////////////////////// // Test constants /////////////////////////////////////////////////////////////////////////////// const testHardwareErrorResponse = "TEST ERROR RESPONSE" /////////////////////////////////////////////////////////////////////////////// // Test serial connection /////////////////////////////////////////////////////////////////////////////// type handlerTestSerial struct { reader *bytes.Reader mu sync.Mutex writer bytes.Buffer closed bool closeCh chan struct{} closeOnce sync.Once } func newHandlerTestSerial(data []byte) *handlerTestSerial { return &handlerTestSerial{ reader: bytes.NewReader(data), closeCh: make(chan struct{}), } } func (s *handlerTestSerial) Read(p []byte) (int, error) { s.mu.Lock() if s.closed { s.mu.Unlock() return 0, io.ErrClosedPipe } n, err := s.reader.Read(p) s.mu.Unlock() if err != io.EOF { return n, err } <-s.closeCh return 0, io.ErrClosedPipe } func (s *handlerTestSerial) Write(p []byte) (int, error) { s.mu.Lock() defer s.mu.Unlock() if s.closed { return 0, io.ErrClosedPipe } return s.writer.Write(p) } func (s *handlerTestSerial) Close() error { s.closeOnce.Do(func() { s.mu.Lock() s.closed = true s.mu.Unlock() close(s.closeCh) }) return nil } func (s *handlerTestSerial) WrittenData() []byte { s.mu.Lock() defer s.mu.Unlock() data := make([]byte, s.writer.Len()) copy(data, s.writer.Bytes()) return data } /////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////// func TestNewDataHandler(t *testing.T) { listener, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() failed: %v", err) } defer listener.Close() serial := newHandlerTestSerial(nil) handler, err := server.NewDataHandler( listener, func() (io.ReadWriteCloser, error) { return serial, nil }, testHardwareErrorResponse, ) if err != nil { t.Fatalf("NewDataHandler() failed: %v", err) } if handler == nil { t.Fatal("NewDataHandler() returned nil") } } func TestNewDataHandlerRejectsNilListener(t *testing.T) { handler, err := server.NewDataHandler( nil, func() (io.ReadWriteCloser, error) { return newHandlerTestSerial(nil), nil }, testHardwareErrorResponse, ) if err == nil { t.Fatal("NewDataHandler() succeeded, want error") } if handler != nil { t.Fatal("NewDataHandler() returned handler despite error") } } func TestNewDataHandlerRejectsNilFactory(t *testing.T) { listener, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() failed: %v", err) } defer listener.Close() handler, err := server.NewDataHandler( listener, nil, testHardwareErrorResponse, ) if err == nil { t.Fatal("NewDataHandler() succeeded, want error") } if handler != nil { t.Fatal("NewDataHandler() returned handler despite error") } } /////////////////////////////////////////////////////////////////////////////// // TCP -> Serial /////////////////////////////////////////////////////////////////////////////// func TestDataHandlerTCPToSerial(t *testing.T) { listener, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() failed: %v", err) } defer listener.Close() serial := newHandlerTestSerial(nil) handler, err := server.NewDataHandler( listener, func() (io.ReadWriteCloser, error) { return serial, nil }, testHardwareErrorResponse, ) if err != nil { t.Fatalf("NewDataHandler() failed: %v", err) } serveDone := make(chan error, 1) go func() { serveDone <- handler.Serve() }() tcpConn, err := net.Dial("tcp", listener.Addr().String()) if err != nil { t.Fatalf("net.Dial() failed: %v", err) } testData := []byte("handler tcp to serial") if _, err := tcpConn.Write(testData); err != nil { t.Fatalf("TCP Write() failed: %v", err) } deadline := time.Now().Add(time.Second) for { if bytes.Equal(serial.WrittenData(), testData) { break } if time.Now().After(deadline) { t.Fatalf( "serial data = %q, want %q", serial.WrittenData(), testData, ) } time.Sleep(time.Millisecond) } _ = tcpConn.Close() _ = listener.Close() select { case <-serveDone: case <-time.After(time.Second): t.Fatal("DataHandler.Serve() did not terminate") } } /////////////////////////////////////////////////////////////////////////////// // Serial -> TCP /////////////////////////////////////////////////////////////////////////////// func TestDataHandlerSerialToTCP(t *testing.T) { listener, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() failed: %v", err) } defer listener.Close() testData := []byte("handler serial to tcp") serial := newHandlerTestSerial(testData) handler, err := server.NewDataHandler( listener, func() (io.ReadWriteCloser, error) { return serial, nil }, testHardwareErrorResponse, ) if err != nil { t.Fatalf("NewDataHandler() failed: %v", err) } serveDone := make(chan error, 1) go func() { serveDone <- handler.Serve() }() tcpConn, err := net.Dial("tcp", listener.Addr().String()) if err != nil { t.Fatalf("net.Dial() failed: %v", err) } buffer := make([]byte, len(testData)) if _, err := io.ReadFull(tcpConn, buffer); err != nil { t.Fatalf("TCP Read() failed: %v", err) } if !bytes.Equal(buffer, testData) { t.Fatalf( "TCP data = %q, want %q", buffer, testData, ) } _ = tcpConn.Close() _ = listener.Close() select { case <-serveDone: case <-time.After(time.Second): t.Fatal("DataHandler.Serve() did not terminate") } } /////////////////////////////////////////////////////////////////////////////// // Serial factory error /////////////////////////////////////////////////////////////////////////////// func TestDataHandlerSerialFactoryError(t *testing.T) { listener, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() failed: %v", err) } defer listener.Close() handler, err := server.NewDataHandler( listener, func() (io.ReadWriteCloser, error) { return nil, io.ErrClosedPipe }, testHardwareErrorResponse, ) if err != nil { t.Fatalf("NewDataHandler() failed: %v", err) } serveDone := make(chan error, 1) go func() { serveDone <- handler.Serve() }() tcpConn, err := net.Dial("tcp", listener.Addr().String()) if err != nil { t.Fatalf("net.Dial() failed: %v", err) } // The handler must close the TCP connection when the serial factory // fails. buffer := make([]byte, 1) _ = tcpConn.SetReadDeadline(time.Now().Add(time.Second)) _, readErr := tcpConn.Read(buffer) if readErr == nil { t.Fatal("TCP connection remained open after serial factory error") } _ = tcpConn.Close() _ = listener.Close() select { case <-serveDone: case <-time.After(time.Second): t.Fatal("DataHandler.Serve() did not terminate") } }