Testing and CI guidelines
This document describes how to write fast, deterministic tests for the PrintMaster agent and server.
Test Types
PrintMaster has three levels of testing:
- Unit Tests - Test individual functions/packages in isolation (
agent/,server/) - Integration Tests - Test component interactions (e.g., database rotation)
- End-to-End Tests - Test full agent-server communication (see
tests/directory)
For E2E test details, see ../tests/E2E_TESTING.md.
Why mocking is necessary
Network operations (ICMP, TCP connect, SNMP Get/Walk) are slow and flaky in CI. Unit tests must avoid touching the real network so they remain fast and reliable. The project uses small package-level factories and interfaces to make mocking easy.
Key patterns
- SNMPClient interface: production code uses
NewSNMPClient(returns aSNMPClient) which wrapsgosnmp. Tests replaceNewSNMPClientwith a mock or fake implementation. - DoPing: package-level variable that points to the real
pingWithExecby default. Tests overrideDoPingto return deterministic ping results. - Use short context timeouts in tests (1–5s) to guard against runaway scans.
- Avoid calling
DiscoverPrinters()in unit tests; preferScanRangesWithWorkersorDiscoverPrintersInRangeswith injected mocks.
Example test pattern
oldNew := agent.NewSNMPClient
agent.NewSNMPClient = func(cfg *agent.SNMPConfig, target string, timeout int) (agent.SNMPClient, error) {
return &agenttest.MockSNMP{ /* preset varbinds */ }, nil
}
defer func(){ agent.NewSNMPClient = oldNew }()
oldPing := agent.DoPing
agent.DoPing = func(ip string, logFn func(string)) bool { return true }
defer func(){ agent.DoPing = oldPing }()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
printers, err := agent.ScanRangesWithWorkers(ctx, nil, []string{"127.0.0.1"}, 1, nil, 1)
// assert on printers and behavior
Mocking tips
- Keep mocks simple and stateless where possible.
- To simulate timeouts or retries, have the mock delay the response or return an error on the first N calls.
- For concurrent tests, ensure the mock implementations are goroutine-safe (use channels or mutexes if necessary).
Integration testing
For full-network tests that exercise the real SNMP stack, maintain separate manual integration scripts (not executed by default in CI). Put those in scripts/integration/ and document the required environment and credentials.
End-to-End Testing
E2E tests that verify agent-server communication are located in the tests/ directory at the project root. These tests:
- Start actual server and agent instances
- Test full workflows (registration, heartbeat, WebSocket, proxy)
- Run in CI/CD after unit tests pass
- Use ephemeral ports and temp databases
See ../tests/E2E_TESTING.md for complete documentation.
Running E2E tests:
cd tests
go test -v ./...
Skipping E2E tests (fast unit tests only):
go test -short ./...