rs2322tcp/internal/server/session_test.go
2026-08-10 10:38:14 +02:00

440 lines
10 KiB
Go

/*
* ============================================================================
* 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)
}
}