Skip to content

Commit 0080645

Browse files
authored
chore: speed up tests (#241)
1 parent dff983c commit 0080645

File tree

9 files changed

+329
-132
lines changed

9 files changed

+329
-132
lines changed

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ agent_bin:
4747
test:
4848
@go test -race ./...
4949

50+
.PHONY: test-timed
51+
test-timed:
52+
@echo "Running tests with timing measurement..."
53+
@echo "=========================================="
54+
@start=$$(date +%s); \
55+
go test -count=1 ./...; \
56+
exit_code=$$?; \
57+
end=$$(date +%s); \
58+
duration=$$((end - start)); \
59+
echo "=========================================="; \
60+
echo "Test execution completed in $$duration seconds"; \
61+
exit $$exit_code
62+
5063
.PHONY: test-coverage
5164
test-coverage:
5265
@mkdir -p .coverage

agent/configmgr/fleet.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var _ Manager = (*fleetConfigManager)(nil)
2121

2222
type fleetConfigManager struct {
2323
logger *slog.Logger
24-
connection *fleet.MQTTConnection
24+
connection fleet.MQTTConnector
2525
authTokenManager *fleet.AuthTokenManager
2626
resetChan chan struct{}
2727
reconnectChan chan struct{}
@@ -51,6 +51,21 @@ func newFleetConfigManager(logger *slog.Logger, pMgr policymgr.PolicyManager, ba
5151
}
5252
}
5353

54+
// newFleetConfigManagerWithConnection creates a fleetConfigManager with a custom connection (for testing)
55+
func newFleetConfigManagerWithConnection(logger *slog.Logger, pMgr policymgr.PolicyManager, backendState backend.StateRetriever, conn fleet.MQTTConnector) *fleetConfigManager {
56+
resetChan := make(chan struct{}, 1)
57+
reconnectChan := make(chan struct{}, 1)
58+
return &fleetConfigManager{
59+
logger: logger,
60+
connection: conn, // Use provided connection instead of creating new one
61+
authTokenManager: fleet.NewAuthTokenManager(logger),
62+
resetChan: resetChan,
63+
reconnectChan: reconnectChan,
64+
backendState: backendState,
65+
policyManager: pMgr,
66+
}
67+
}
68+
5469
func (fleetManager *fleetConfigManager) Start(cfg config.Config, backends map[string]backend.Backend) error {
5570
ctx := context.Background()
5671

agent/configmgr/fleet/auth_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,7 @@ func TestAuthTokenManager_IsTokenExpired_ExpiredCases(t *testing.T) {
299299
_, err := authTokenManager.GetToken(ctx, serverExpired.URL, true, 60*time.Second, "test_client_id", "test_client_secret")
300300
require.NoError(t, err)
301301

302-
// Wait a moment to ensure time has passed
303-
time.Sleep(2 * time.Second)
302+
// Token is already expired (pastExpiry is 1 hour ago), so no wait needed
304303
assert.True(t, authTokenManager.IsTokenExpired())
305304
}
306305

agent/configmgr/fleet/connection.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ type ConnectionDetails struct {
6666
Zone string
6767
}
6868

69+
// MQTTConnector defines the interface for MQTT connection operations
70+
type MQTTConnector interface {
71+
Connect(ctx context.Context, details ConnectionDetails, backends map[string]backend.Backend, labels map[string]string, configFile string) error
72+
Disconnect(ctx context.Context, heartbeatTopic string) error
73+
Reconnect(ctx context.Context, details ConnectionDetails, backends map[string]backend.Backend, labels map[string]string, configFile string, timeout time.Duration) error
74+
AddOnReadyHook(fn func(cm *autopaho.ConnectionManager, topics TokenResponseTopics))
75+
}
76+
6977
// Connect connects to the MQTT broker
7078
func (connection *MQTTConnection) Connect(ctx context.Context, details ConnectionDetails, backends map[string]backend.Backend, labels map[string]string, configFile string) error {
7179
// Parse the ORB URL

agent/configmgr/fleet/connection_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ func TestFleetConfigManager_Connect_ValidURL(t *testing.T) {
101101
// since we don't have a real MQTT server
102102
backends := make(map[string]backend.Backend)
103103
trt2 := TokenResponseTopics{Inbox: "test/topic"}
104-
// Timeout after 3 seconds
105-
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
104+
// Timeout after 100ms for faster test execution (connection will fail quickly)
105+
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
106106
defer cancel()
107107
err := connection.Connect(ctx,
108108
ConnectionDetails{MQTTURL: "mqtt://localhost:1883", Token: "test_token", AgentID: "test-agent-id", Topics: trt2, ClientID: "test-agent-id", Zone: "test-zone"},

agent/configmgr/fleet/mocks.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"log/slog"
88
"time"
99

10+
"github.com/eclipse/paho.golang/autopaho"
1011
"github.com/stretchr/testify/mock"
1112

1213
"github.com/netboxlabs/orb-agent/agent/backend"
@@ -86,3 +87,40 @@ func (m *mockBackend) RemovePolicy(data policies.PolicyData) error {
8687
args := m.Called(data)
8788
return args.Error(0)
8889
}
90+
91+
// MockMQTTConnection is a mock implementation of MQTTConnector for testing
92+
type MockMQTTConnection struct {
93+
ConnectError error
94+
DisconnectError error
95+
ReconnectError error
96+
ConnectCalled bool
97+
hooks []func(cm *autopaho.ConnectionManager, topics TokenResponseTopics)
98+
}
99+
100+
// Connect connects to the MQTT broker
101+
func (m *MockMQTTConnection) Connect(_ context.Context, _ ConnectionDetails, _ map[string]backend.Backend, _ map[string]string, _ string) error {
102+
m.ConnectCalled = true
103+
return m.ConnectError
104+
}
105+
106+
// Disconnect disconnects from the MQTT broker
107+
func (m *MockMQTTConnection) Disconnect(_ context.Context, _ string) error {
108+
return m.DisconnectError
109+
}
110+
111+
// Reconnect reconnects to the MQTT broker
112+
func (m *MockMQTTConnection) Reconnect(_ context.Context, _ ConnectionDetails, _ map[string]backend.Backend, _ map[string]string, _ string, _ time.Duration) error {
113+
return m.ReconnectError
114+
}
115+
116+
// AddOnReadyHook registers a hook function to be called when the MQTT connection is ready.
117+
func (m *MockMQTTConnection) AddOnReadyHook(fn func(cm *autopaho.ConnectionManager, topics TokenResponseTopics)) {
118+
m.hooks = append(m.hooks, fn)
119+
}
120+
121+
// TriggerOnReadyHook triggers all registered onReady hooks (for testing)
122+
func (m *MockMQTTConnection) TriggerOnReadyHook(cm *autopaho.ConnectionManager, topics TokenResponseTopics) {
123+
for _, hook := range m.hooks {
124+
hook(cm, topics)
125+
}
126+
}

agent/configmgr/fleet_test.go

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package configmgr
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"log/slog"
78
"net/http"
89
"net/http/httptest"
@@ -107,15 +108,32 @@ func TestFleetConfigManager_Start_ConnectError(t *testing.T) {
107108
// Arrange
108109
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
109110
mockPMgr := &mockPolicyManagerForFleet{}
110-
fleetManager := newFleetConfigManager(logger, mockPMgr, &mockBackendState{})
111+
112+
// Create mock MQTT connection that returns a connection error immediately
113+
mockConn := &fleet.MockMQTTConnection{
114+
ConnectError: fmt.Errorf("mqtt connection failed: invalid URL"),
115+
}
116+
fleetManager := newFleetConfigManagerWithConnection(logger, mockPMgr, &mockBackendState{}, mockConn)
111117

112118
// Create mock HTTP server for token endpoint
119+
// Use a valid JWT token so parsing succeeds and we reach the connection step
120+
validJWT := fleet.RawJWTWithClaims(map[string]any{
121+
"orb:org_id": "test-org",
122+
"orb:zone": "default",
123+
"orb:agent_id": "test-agent",
124+
"client_id": "test-client",
125+
"iat": 1516239022,
126+
"ext": map[string]any{
127+
"orb:mqtt_url": "mqtt://test.example.com:1883",
128+
},
129+
})
113130
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
114131
response := fleet.TokenResponse{
115-
AccessToken: "test_access_token",
116-
MQTTURL: "://invalid-mqtt-url", // Invalid MQTT URL
132+
AccessToken: validJWT,
133+
MQTTURL: "mqtt://test.example.com:1883",
117134
ExpiresIn: 3600,
118135
}
136+
w.Header().Set("Content-Type", "application/json")
119137
_ = json.NewEncoder(w).Encode(response)
120138
}))
121139
defer server.Close()
@@ -126,6 +144,7 @@ func TestFleetConfigManager_Start_ConnectError(t *testing.T) {
126144
Sources: config.Sources{
127145
Fleet: config.FleetManager{
128146
TokenURL: server.URL,
147+
SkipTLS: true, // Skip TLS verification for test server
129148
ClientID: "test_client",
130149
ClientSecret: "test_secret",
131150
},
@@ -141,6 +160,8 @@ func TestFleetConfigManager_Start_ConnectError(t *testing.T) {
141160

142161
// Assert
143162
assert.Error(t, err)
163+
// Verify the error is from the mock connection (no 30s wait)
164+
assert.Contains(t, err.Error(), "mqtt connection failed")
144165
}
145166

146167
func TestFleetConfigManager_GetContext(t *testing.T) {
@@ -182,7 +203,12 @@ func TestFleetConfigManager_Start_WithJWTTopicGeneration(t *testing.T) {
182203

183204
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))
184205
mockPMgr := &mockPolicyManagerForFleet{}
185-
fleetManager := newFleetConfigManager(logger, mockPMgr, &mockBackendState{})
206+
207+
// Create mock MQTT connection that returns a connection error
208+
mockConn := &fleet.MockMQTTConnection{
209+
ConnectError: fmt.Errorf("mqtt connection failed: connection refused"),
210+
}
211+
fleetManager := newFleetConfigManagerWithConnection(logger, mockPMgr, &mockBackendState{}, mockConn)
186212

187213
// Create mock HTTP server that returns a JWT token
188214
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -216,22 +242,19 @@ func TestFleetConfigManager_Start_WithJWTTopicGeneration(t *testing.T) {
216242
// Mock backends
217243
backends := make(map[string]backend.Backend)
218244

219-
// Since we can't easily mock the MQTT connection in this test,
220-
// we expect the Start method to fail at the MQTT connection step,
221-
// but succeed in generating topics from JWT claims
245+
// The Start method should succeed in generating topics from JWT claims,
246+
// but fail at the MQTT connection step (which is mocked to return an error)
222247
err := fleetManager.Start(cfg, backends)
223248

224-
// We expect an error because we can't actually connect to MQTT,
249+
// We expect an error because the mock connection returns an error,
225250
// but we want to verify that topic generation succeeded
226251
// (The error should be related to MQTT connection, not JWT parsing)
227252
require.Error(t, err)
228-
// The error could be connection-related (mqtt, tcp, timeout, etc.)
253+
// The error should be the mocked connection error
229254
errorMsg := strings.ToLower(err.Error())
230255
assert.True(t,
231256
strings.Contains(errorMsg, "mqtt") ||
232-
strings.Contains(errorMsg, "connection") ||
233-
strings.Contains(errorMsg, "timeout") ||
234-
strings.Contains(errorMsg, "deadline"),
257+
strings.Contains(errorMsg, "connection"),
235258
"Expected connection-related error, got: %s", err.Error())
236259
}
237260

@@ -386,7 +409,7 @@ func TestFleetConfigManager_MonitorTokenExpiry_Configuration(t *testing.T) {
386409
select {
387410
case <-done:
388411
// Monitor stopped successfully
389-
case <-time.After(1 * time.Second):
412+
case <-time.After(100 * time.Millisecond):
390413
t.Fatal("monitor did not stop within timeout")
391414
}
392415
}
@@ -434,7 +457,7 @@ func TestFleetConfigManager_MonitorTokenExpiry_CustomConfiguration(t *testing.T)
434457
select {
435458
case <-done:
436459
// Monitor stopped successfully
437-
case <-time.After(1 * time.Second):
460+
case <-time.After(100 * time.Millisecond):
438461
t.Fatal("monitor did not stop within timeout")
439462
}
440463
}
@@ -467,7 +490,7 @@ func TestFleetConfigManager_Stop_CancelsMonitor(t *testing.T) {
467490
select {
468491
case <-fleetManager.monitorCtx.Done():
469492
// Good, context is cancelled
470-
case <-time.After(100 * time.Millisecond):
493+
case <-time.After(50 * time.Millisecond):
471494
t.Fatal("monitor context should be cancelled after Stop()")
472495
}
473496
}
@@ -572,10 +595,11 @@ func TestFleetConfigManager_MonitorTokenExpiry_DetectsExpiredToken(t *testing.T)
572595
}()
573596

574597
// Wait for monitor to detect expired token
598+
// Allow at least one ticker interval (100ms) plus buffer
575599
select {
576600
case <-done:
577601
// Monitor detected expired token
578-
case <-time.After(500 * time.Millisecond):
602+
case <-time.After(200 * time.Millisecond):
579603
t.Fatal("monitor did not detect expired token within timeout")
580604
}
581605

@@ -670,10 +694,11 @@ func TestFleetConfigManager_MonitorTokenExpiry_DetectsExpiringSoonToken(t *testi
670694
}()
671695

672696
// Wait for monitor to detect expiring token
697+
// Allow at least one ticker interval (100ms) plus buffer
673698
select {
674699
case <-done:
675700
// Monitor detected expiring token
676-
case <-time.After(500 * time.Millisecond):
701+
case <-time.After(200 * time.Millisecond):
677702
t.Fatal("monitor did not detect expiring token within timeout")
678703
}
679704

agent/secretsmgr/vault_auth_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@ func TestNewAuthentication(t *testing.T) {
5151
}
5252

5353
func TestTokenAuth_Authenticate(t *testing.T) {
54-
// Create test vault server
55-
cluster, client := createTestVault(t)
56-
defer cluster.Cleanup()
54+
// Use shared test vault server
55+
_, client := createTestVault(t)
5756

5857
ctx := context.Background()
5958

0 commit comments

Comments
 (0)