Files
tunnel-please/internal/transport/https_test.go
T
bagas fabfd96600
SonarQube Scan / SonarQube Trigger (push) Successful in 4m22s
Tests / Run Tests (pull_request) Successful in 2m35s
Fix race conditions and improve lifecycle safety (#150) (#152)
## Summary
Comprehensive fixes for race conditions, concurrency bugs, and code quality improvements in the lifecycle module and related test files.

## Changes

### Race Condition Fixes
- Fixed data race in `SetChannel()` and `Channel()` by adding mutex protection
- Fixed data race in `StartedAt()` by adding mutex protection
- Fixed test race conditions in `transport` and `interaction` packages where variables were shared between goroutines

### Concurrency Safety Improvements
- Refactored `Close()` to release mutex before calling external code, preventing potential deadlocks
- Made `SetChannel()` reject calls after `Close()` to prevent setting channel on torn-down lifecycle
- Made `SetStatus()` ignore calls after `Close()` to ensure CLOSED is a terminal state
- Added nil check to `SetChannel()` to prevent masking upstream bugs

### Code Quality Improvements
- Refactored `Close()` to extract helper methods: `closeChannel()`, `closeConnection()`, `cleanupRegistry()`, `cleanupForwarder()`
- Fixed `isClosedError()` to remove redundant string comparison
- Added `PortRegistry` interface for better interface segregation (removed dependency on full `port.Port`)
- Made `cleanupRegistry()` conditional on non-empty slug to avoid unnecessary registry calls
- Fixed `startedAt` to be set when session starts running instead of at object creation

Reviewed-on: #150
Co-authored-by: Bagas <bagas@fossy.my.id>
Co-committed-by: Bagas <bagas@fossy.my.id>

Reviewed-on: #152
2026-07-17 13:21:31 +07:00

120 lines
2.9 KiB
Go

package transport
import (
"crypto/tls"
"errors"
"fmt"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestNewHTTPSServer(t *testing.T) {
msr := new(MockSessionRegistry)
mockConfig := &MockConfig{}
port := "0"
tlsConfig := &tls.Config{}
mockConfig.On("Domain").Return(mockConfig)
mockConfig.On("HTTPSPort").Return(port)
srv := NewHTTPSServer(mockConfig, msr, tlsConfig)
assert.NotNil(t, srv)
httpsSrv, ok := srv.(*https)
assert.True(t, ok)
assert.Equal(t, tlsConfig, httpsSrv.tlsConfig)
assert.Equal(t, msr, httpsSrv.httpHandler.sessionRegistry)
}
func TestHTTPSServer_Listen(t *testing.T) {
msr := new(MockSessionRegistry)
mockConfig := &MockConfig{}
port := "0"
mockConfig.On("Domain").Return(mockConfig)
mockConfig.On("HTTPSPort").Return(port)
tlsConfig := &tls.Config{
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
return nil, nil
},
}
srv := NewHTTPSServer(mockConfig, msr, tlsConfig)
listener, err := srv.Listen()
if err != nil {
t.Skip("Skipping tls.Listen test as it requires valid certificates/setup:", err)
return
}
assert.NotNil(t, listener)
err = listener.Close()
assert.NoError(t, err)
}
func TestHTTPSServer_Serve(t *testing.T) {
msr := new(MockSessionRegistry)
mockConfig := &MockConfig{}
port := "0"
mockConfig.On("Domain").Return(mockConfig)
mockConfig.On("HTTPSPort").Return(port)
srv := NewHTTPSServer(mockConfig, msr, &tls.Config{})
listener, err := net.Listen("tcp", "127.0.0.1:0")
assert.NoError(t, err)
go func() {
time.Sleep(100 * time.Millisecond)
_ = listener.Close()
}()
err = srv.Serve(listener)
assert.True(t, errors.Is(err, net.ErrClosed))
}
func TestHTTPSServer_Serve_AcceptError(t *testing.T) {
msr := new(MockSessionRegistry)
mockConfig := &MockConfig{}
port := "0"
mockConfig.On("Domain").Return(mockConfig)
mockConfig.On("HTTPSPort").Return(port)
srv := NewHTTPSServer(mockConfig, msr, &tls.Config{})
ml := new(mockListener)
ml.On("Accept").Return(nil, errors.New("accept error")).Once()
ml.On("Accept").Return(nil, net.ErrClosed).Once()
err := srv.Serve(ml)
assert.True(t, errors.Is(err, net.ErrClosed))
ml.AssertExpectations(t)
}
func TestHTTPSServer_Serve_Success(t *testing.T) {
msr := new(MockSessionRegistry)
mockConfig := &MockConfig{}
port := "0"
mockConfig.On("Domain").Return(mockConfig)
mockConfig.On("HTTPSPort").Return(port)
mockConfig.On("HeaderSize").Return(4096)
srv := NewHTTPSServer(mockConfig, msr, &tls.Config{})
listener, err := net.Listen("tcp", "127.0.0.1:0")
assert.NoError(t, err)
listenerport := listener.Addr().(*net.TCPAddr).Port
go func() {
_ = srv.Serve(listener)
}()
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", listenerport))
assert.NoError(t, err)
_, _ = conn.Write([]byte("GET / HTTP/1.1\r\nHost: ping.example.com\r\n\r\n"))
time.Sleep(100 * time.Millisecond)
err = conn.Close()
assert.NoError(t, err)
err = listener.Close()
assert.NoError(t, err)
}