/* * ============================================================================ * Projekt.....: rs2322tcp * Datei.......: session_test.go * Copyright (C) 2026 Dieter Lang * * SPDX-License-Identifier: GPL-3.0-or-later * * Beschreibung: * Tests für den Lebenszyklus einer Client-Session, die Verwaltung * der zugehörigen Ressourcen und der dynamischen Data-Listener. * ============================================================================ */ package server_test import ( "errors" "net" "testing" "git.lang-dieter.de/rs2322tcp/internal/server" ) /////////////////////////////////////////////////////////////////////////////// // Test resource /////////////////////////////////////////////////////////////////////////////// type testResource struct { closed bool err error } func (r *testResource) Close() error { r.closed = true return r.err } /////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////// func TestNewSession(t *testing.T) { serverConn, clientConn := net.Pipe() defer serverConn.Close() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } if session.ID() != 1 { t.Errorf("ID() = %d, want 1", session.ID()) } if session.Conn() == nil { t.Fatal("Conn() returned nil") } if session.IsClosed() { t.Fatal("new session is already closed") } if session.ResourceCount() != 0 { t.Errorf("ResourceCount() = %d, want 0", session.ResourceCount()) } if session.DataListenerCount() != 0 { t.Errorf("DataListenerCount() = %d, want 0", session.DataListenerCount()) } } func TestNewSessionRejectsNilConnection(t *testing.T) { session, err := server.NewSession(1, nil) if err == nil { t.Fatal("NewSession() succeeded, want error") } if session != nil { t.Fatal("NewSession() returned a session despite error") } } func TestNewSessionRejectsZeroID(t *testing.T) { serverConn, clientConn := net.Pipe() defer serverConn.Close() defer clientConn.Close() session, err := server.NewSession(0, serverConn) if err == nil { t.Fatal("NewSession() succeeded, want error") } if session != nil { t.Fatal("NewSession() returned a session despite error") } } /////////////////////////////////////////////////////////////////////////////// // Resources /////////////////////////////////////////////////////////////////////////////// func TestSessionAddResource(t *testing.T) { serverConn, clientConn := net.Pipe() defer serverConn.Close() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } resource1 := &testResource{} resource2 := &testResource{} if err := session.AddResource(resource1); err != nil { t.Fatalf("AddResource(resource1) failed: %v", err) } if err := session.AddResource(resource2); err != nil { t.Fatalf("AddResource(resource2) failed: %v", err) } if session.ResourceCount() != 2 { t.Errorf("ResourceCount() = %d, want 2", session.ResourceCount()) } if resource1.closed { t.Fatal("resource1 was closed before session.Close()") } if resource2.closed { t.Fatal("resource2 was closed before session.Close()") } } func TestAddNilResource(t *testing.T) { serverConn, clientConn := net.Pipe() defer serverConn.Close() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } if err := session.AddResource(nil); err == nil { t.Fatal("AddResource(nil) succeeded, want error") } } func TestAddResourceAfterClose(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } if err := session.Close(); err != nil { t.Fatalf("Close() failed: %v", err) } resource := &testResource{} if err := session.AddResource(resource); err == nil { t.Fatal("AddResource() succeeded, want error") } if !resource.closed { t.Fatal("resource was not closed after rejected AddResource()") } if session.ResourceCount() != 0 { t.Errorf("ResourceCount() = %d, want 0", session.ResourceCount()) } } /////////////////////////////////////////////////////////////////////////////// // Data listeners /////////////////////////////////////////////////////////////////////////////// func TestSessionAddDataListener(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } listener, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() failed: %v", err) } port := listener.Port() if port == 0 { t.Fatal("DataListener.Port() returned 0") } if err := session.AddDataListener("radio", listener); err != nil { t.Fatalf("AddDataListener() failed: %v", err) } if session.DataListenerCount() != 1 { t.Errorf("DataListenerCount() = %d, want 1", session.DataListenerCount()) } if session.ResourceCount() != 1 { t.Errorf("ResourceCount() = %d, want 1", session.ResourceCount()) } storedListener, ok := session.DataListener("radio") if !ok { t.Fatal("DataListener(radio) not found") } if storedListener != listener { t.Fatal("stored listener is not the same listener") } if session.DataPort("radio") != port { t.Errorf("DataPort(radio) = %d, want %d", session.DataPort("radio"), port) } if session.DataPort("unknown") != 0 { t.Errorf("DataPort(unknown) = %d, want 0", session.DataPort("unknown")) } } func TestSessionRejectsDuplicateDataListener(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } listener1, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() #1 failed: %v", err) } listener2, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() #2 failed: %v", err) } if err := session.AddDataListener("radio", listener1); err != nil { t.Fatalf("AddDataListener() #1 failed: %v", err) } if err := session.AddDataListener("radio", listener2); err == nil { t.Fatal("AddDataListener() #2 succeeded, want error") } if listener2.Port() != 0 { t.Errorf("duplicate listener port = %d, want 0", listener2.Port()) } if session.DataListenerCount() != 1 { t.Errorf("DataListenerCount() = %d, want 1", session.DataListenerCount()) } } func TestSessionRejectsEmptyDeviceID(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } listener, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() failed: %v", err) } if err := session.AddDataListener("", listener); err == nil { t.Fatal("AddDataListener() succeeded, want error") } if listener.Port() != 0 { t.Errorf("listener port = %d, want 0", listener.Port()) } } func TestSessionClosesDataListener(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } dataListener, err := server.NewDataListener() if err != nil { t.Fatalf("NewDataListener() failed: %v", err) } if err := session.AddDataListener("radio", dataListener); err != nil { t.Fatalf("AddDataListener() failed: %v", err) } if session.DataListenerCount() != 1 { t.Fatalf("DataListenerCount() = %d, want 1", session.DataListenerCount()) } if err := session.Close(); err != nil { t.Fatalf("Session.Close() failed: %v", err) } if dataListener.Port() != 0 { t.Errorf("DataListener.Port() after Session.Close() = %d, want 0", dataListener.Port()) } if session.DataListenerCount() != 0 { t.Errorf("DataListenerCount() after Session.Close() = %d, want 0", session.DataListenerCount()) } if session.DataPort("radio") != 0 { t.Errorf("DataPort(radio) after Close() = %d, want 0", session.DataPort("radio")) } } /////////////////////////////////////////////////////////////////////////////// // Close /////////////////////////////////////////////////////////////////////////////// func TestSessionClose(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } resource1 := &testResource{} resource2 := &testResource{} if err := session.AddResource(resource1); err != nil { t.Fatalf("AddResource(resource1) failed: %v", err) } if err := session.AddResource(resource2); err != nil { t.Fatalf("AddResource(resource2) failed: %v", err) } if err := session.Close(); err != nil { t.Fatalf("Close() failed: %v", err) } if !session.IsClosed() { t.Fatal("session is not closed") } if session.Conn() != nil { t.Fatal("Conn() returned a connection after Close()") } if session.ResourceCount() != 0 { t.Errorf("ResourceCount() after Close() = %d, want 0", session.ResourceCount()) } if !resource1.closed { t.Fatal("resource1 was not closed") } if !resource2.closed { t.Fatal("resource2 was not closed") } if err := session.Close(); err != nil { t.Fatalf("second Close() failed: %v", err) } } func TestSessionCloseResourceError(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() session, err := server.NewSession(1, serverConn) if err != nil { t.Fatalf("NewSession() failed: %v", err) } expectedErr := errors.New("test resource error") resource := &testResource{ err: expectedErr, } if err := session.AddResource(resource); err != nil { t.Fatalf("AddResource() failed: %v", err) } err = session.Close() if !errors.Is(err, expectedErr) { t.Fatalf("Close() error = %v, want %v", err, expectedErr) } if !resource.closed { t.Fatal("resource was not closed") } } func TestSessionCloseNil(t *testing.T) { var session *server.Session if err := session.Close(); err != nil { t.Fatalf("Close() on nil session failed: %v", err) } }