11 Commits

Author SHA1 Message Date
savinmax
18063fb3ef test(hub): add edge case tests for paths, query strings, and room lifecycle
Add comprehensive integration tests covering:
- TestIntegration_PathWithSlashes: nested paths work as distinct rooms
- TestIntegration_QueryStringIgnored: query strings stripped, same room
- TestIntegration_DefaultRoom: bare root path broadcast works
- TestIntegration_ClientDisconnectFromRoom: remaining clients communicate
- TestIntegration_ConcurrentRoomOperations: no races with rapid connect/disconnect

All tests pass with -race flag.

🤖 Assisted by the code-assist SOP
2026-06-13 13:32:55 +02:00
savinmax
516f8c5008 test(hub): add integration tests for room isolation
Add dedicated integration tests verifying message isolation between rooms:

- TestIntegration_SameRoomBroadcast: two clients in /chat both receive
  broadcast messages
- TestIntegration_CrossRoomIsolation: client in /room-a does not leak
  messages to client in /room-b (verified via read deadline timeout)
- TestIntegration_MultipleRoomsSimultaneous: 3 rooms with 2 clients
  each, messages stay within their room
- TestIntegration_RoomCleanup: verifies RoomCount() increases on
  connect and decreases (room removed) on last client disconnect
- TestIntegration_RoomCleanup_MultipleClients: room persists while
  any client remains connected
- TestIntegration_RoomCleanup_ConcurrentDisconnects: 5 rooms cleaned
  up concurrently without races

Also introduces dialWSPath(t, server, path) helper for tests that
specify WebSocket paths directly with a leading slash.

All tests pass with -race flag.

🤖 Assisted by the code-assist SOP
2026-06-13 13:30:43 +02:00
savinmax
c226bdab7f feat(hub): update graceful shutdown to iterate rooms for multi-room cleanup
Refactor the stop case in Hub.Run() to iterate h.rooms directly
instead of h.connRoom. For each room, iterate all connections and
send CloseGoingAway frame before closing. After the loop, reset both
maps (h.rooms, h.connRoom) in one shot rather than deleting entries
incrementally. This is cleaner and avoids modifying a map during
iteration.

Add TestIntegration_GracefulShutdownMultiRoom to verify clients in
separate rooms all receive close frames during shutdown.

🤖 Assisted by the code-assist SOP
2026-06-13 13:26:03 +02:00
savinmax
5bd08409dc feat(hub): extract room from URL path instead of query parameter
Change HandleWebSocket to use r.URL.Path as the room identifier instead
of r.URL.Query().Get("room"). This enables clean URL-based room routing
(e.g., ws://host/room-a) without query strings.

Update test helpers (dialTestHub, dialWSWithRoom) to connect via path
segments and fix direct broadcast channel tests to use path-style room
names (with leading slash).

All existing tests pass — clients connecting to / get the default room.

🤖 Assisted by the code-assist SOP
2026-06-13 13:22:33 +02:00
savinmax
48d47dfc92 test(hub): add room-scoped broadcast isolation tests (P03)
Add tests verifying that the broadcast case in Hub.Run() correctly
sends messages only to clients in the same room as the sender:

- TestIntegration_RoomIsolation_MessagesOnlyGoToSameRoom: verifies
  messages from room-a are received by room-a clients and NOT by
  room-b clients
- TestIntegration_RoomIsolation_MultipleRoomsIndependent: verifies
  two rooms operate independently with no message leakage
- TestIntegration_BroadcastToEmptyRoom: verifies graceful handling
  when broadcasting to a non-existent room (no panic, hub remains
  functional)
- TestBroadcastRoomIsolation: unit-level room isolation test using
  the broadcast channel directly

Also adds dialWSWithRoom helper for room-aware WebSocket connections
in integration tests.

🤖 Assisted by the code-assist SOP
2026-06-13 13:20:05 +02:00
savinmax
8eaba398dc feat(hub): update register/unregister to use Inc/Dec metrics and add room-aware tests
- Change ConnectedClients metrics from Set() to Inc()/Dec() pattern
  for cleaner, atomic metric updates in register/unregister/broadcast
- Add room info to unregister and broadcast-cleanup log messages
- Handle unregistered connections gracefully (close without panic)
- Capture count inside lock for accurate log output

Tests added:
- TestRegisterClient: verifies ClientCount/RoomCount after connect
- TestUnregisterClient: verifies cleanup after disconnect
- TestRegisterMultipleRooms: verifies multi-room state tracking
- TestUnregisterCleansUpEmptyRoom: verifies empty room deletion
- TestUnregisterUnknownConnNoPanic: verifies no panic on unknown conn

All tests pass including race detector.

🤖 Assisted by the code-assist SOP
2026-06-13 13:14:57 +02:00
savinmax
03f379c73c refactor(hub): introduce room types and update Hub struct
- Add client struct with conn and room fields
- Add broadcastMsg struct with room and data fields
- Change Hub.clients to Hub.rooms map[string]map[*websocket.Conn]bool
- Add Hub.connRoom map[*websocket.Conn]string for reverse lookup
- Change broadcast channel type to chan broadcastMsg
- Change register channel type to chan client
- Update New() to initialize rooms and connRoom maps
- Update ClientCount() to use len(h.connRoom)
- Add RoomCount() method
- Update Run() loop for room-segmented register/unregister/broadcast
- Update HandleWebSocket to extract room from query param
- Backward compatible: clients without ?room use default empty room
- Update TestNew to verify rooms and connRoom maps initialized
- Add TestRoomCount to verify initial room count is 0
- Fix TestBroadcastChannel to use broadcastMsg type

All existing unit and integration tests pass (16 hub tests + 21 other).

🤖 Assisted by the code-assist SOP
2026-06-13 13:09:25 +02:00
savinmax
3d14b7fcb8 feat(logging): add configurable log output and log level support
Add a 'logging' section to config.yaml supporting:
- output: stderr (default), stdout, or a file path
- level: debug, info, warn, error (default: info)

Implementation:
- New internal/logging package with Setup() for output destination
  and Logger struct with level-aware Debug/Info/Warn/Error methods
- Config struct extended with Logging section (output + level fields)
- Hub refactored to accept *logging.Logger via constructor injection
- main.go initializes logging early after config load

The leveled logger suppresses messages below the configured threshold
while maintaining the stdlib log format. File output uses append mode
with 0644 permissions for safe log rotation.

🤖 Assisted by the code-assist SOP
2026-06-11 19:21:20 +02:00
savinmax
905c241daa Improve reliability, testing, and documentation
Some checks failed
CI / test (push) Successful in 54s
CI / lint (push) Failing after 3m16s
- Fix metrics: change MessagesTotal, ConnectionsTotal, DisconnectionsTotal
  from Gauge to Counter with proper _total naming convention
- Fix broadcast write-error handling: failed clients now get properly
  removed with accurate metrics updates
- Add graceful shutdown: SIGINT/SIGTERM handling with 10s timeout,
  CloseGoingAway frame sent to clients before disconnect
- Add integration tests: 11 tests using real WebSocket connections
  covering connect, broadcast, disconnect, concurrency, and shutdown
- Fix example client port: changed from 8000 to 8443 to match config
- Rewrite README.md to reflect current features and usage
- Add AGENTS.md and .agents/summary/ documentation for AI assistants
2026-06-11 19:14:19 +02:00
savinmax
f69355d69d small fix
All checks were successful
CI / test (push) Successful in 10s
CI / lint (push) Successful in 11s
2025-08-04 11:11:10 +02:00
savinmax
e4523df602 Init
Some checks failed
CI / test (push) Successful in 1m10s
CI / lint (push) Successful in 30s
Release / release (push) Failing after 31s
2025-08-02 18:33:50 +02:00