Skip to content

Commit 96d12bd

Browse files
committed
refactor: configure mocks and support classes and add unit tests
1 parent baba0ed commit 96d12bd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+4272
-179
lines changed

cmd/agent_smith/config.go

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import (
88
"net/http"
99
"os"
1010
"runtime"
11-
"time"
1211

12+
"github.com/RewstApp/agent-smith-go/internal/adapters"
1313
"github.com/RewstApp/agent-smith-go/internal/agent"
14+
"github.com/RewstApp/agent-smith-go/internal/ports"
1415
"github.com/RewstApp/agent-smith-go/internal/service"
1516
"github.com/RewstApp/agent-smith-go/internal/utils"
1617
"github.com/RewstApp/agent-smith-go/internal/version"
@@ -20,7 +21,35 @@ type fetchConfigurationResponse struct {
2021
Configuration agent.Device `json:"configuration"`
2122
}
2223

23-
func runConfig(params *configParams) {
24+
// ConfigRunner handles configuration with injectable dependencies.
25+
type ConfigRunner struct {
26+
httpClient ports.HTTPClient
27+
fs ports.FileSystem
28+
clock ports.Clock
29+
}
30+
31+
// NewConfigRunner creates a new ConfigRunner with the provided dependencies.
32+
func NewConfigRunner(httpClient ports.HTTPClient, fs ports.FileSystem, clock ports.Clock) *ConfigRunner {
33+
return &ConfigRunner{
34+
httpClient: httpClient,
35+
fs: fs,
36+
clock: clock,
37+
}
38+
}
39+
40+
// DefaultConfigRunner returns a ConfigRunner using real implementations.
41+
func DefaultConfigRunner() *ConfigRunner {
42+
return NewConfigRunner(
43+
adapters.NewHTTPClient(),
44+
adapters.NewOSFileSystem(),
45+
adapters.NewClock(),
46+
)
47+
}
48+
49+
// Default instance for backward compatibility
50+
var defaultConfigRunner = DefaultConfigRunner()
51+
52+
func (c *ConfigRunner) run(params *configParams) {
2453
logger := utils.ConfigureLogger("agent_smith", os.Stdout, utils.Default)
2554

2655
// Show header
@@ -51,8 +80,7 @@ func runConfig(params *configParams) {
5180
req.Header.Set("x-rewst-secret", params.ConfigSecret)
5281
req.Header.Set("Content-Type", "application/json")
5382

54-
client := &http.Client{}
55-
res, err := client.Do(req)
83+
res, err := c.httpClient.Do(req)
5684
if err != nil {
5785
logger.Error("Failed to execute http request", "error", err)
5886
return
@@ -102,7 +130,7 @@ func runConfig(params *configParams) {
102130
// Got configuration
103131
logger.Info("Received configuration", "configuration", string(configBytes))
104132

105-
err = os.WriteFile(configFilePath, configBytes, utils.DefaultFileMod)
133+
err = c.fs.WriteFile(configFilePath, configBytes, utils.DefaultFileMod)
106134
if err != nil {
107135
logger.Error("Failed to save config", "error", err)
108136
return
@@ -134,7 +162,7 @@ func runConfig(params *configParams) {
134162
// Wait for some time for the service executable to clean up
135163
existingService.Close()
136164
logger.Info("Waiting for service executable to stop")
137-
time.Sleep(serviceExecutableTimeout)
165+
c.clock.Sleep(serviceExecutableTimeout)
138166
}
139167

140168
logger.Info("Configuration saved to", "path", configFilePath)
@@ -149,20 +177,20 @@ func runConfig(params *configParams) {
149177
}
150178

151179
// Copy the agent executable
152-
execFilePath, err := os.Executable()
180+
execFilePath, err := c.fs.Executable()
153181
if err != nil {
154182
logger.Error("Failed to get executable", "error", err)
155183
return
156184
}
157185

158-
execFileBytes, err := os.ReadFile(execFilePath)
186+
execFileBytes, err := c.fs.ReadFile(execFilePath)
159187
if err != nil {
160188
logger.Error("Failed to read executable file", "error", err)
161189
return
162190
}
163191

164192
agentExecutablePath := agent.GetAgentExecutablePath(params.OrgId)
165-
err = os.WriteFile(agentExecutablePath, execFileBytes, utils.DefaultExecutableFileMod)
193+
err = c.fs.WriteFile(agentExecutablePath, execFileBytes, utils.DefaultExecutableFileMod)
166194
if err != nil {
167195
logger.Error("Failed to create agent executable", "error", err)
168196
return
@@ -198,3 +226,7 @@ func runConfig(params *configParams) {
198226

199227
logger.Info("Service started")
200228
}
229+
230+
func runConfig(params *configParams) {
231+
defaultConfigRunner.run(params)
232+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/RewstApp/agent-smith-go/internal/mocks"
7+
"go.uber.org/mock/gomock"
8+
)
9+
10+
func TestNewConfigRunner(t *testing.T) {
11+
ctrl := gomock.NewController(t)
12+
defer ctrl.Finish()
13+
14+
mockHTTP := mocks.NewMockHTTPClient(ctrl)
15+
mockFS := mocks.NewMockFileSystem(ctrl)
16+
mockClock := mocks.NewMockClock(ctrl)
17+
18+
runner := NewConfigRunner(mockHTTP, mockFS, mockClock)
19+
20+
if runner == nil {
21+
t.Error("expected non-nil runner")
22+
}
23+
if runner.httpClient != mockHTTP {
24+
t.Error("expected httpClient to be set")
25+
}
26+
if runner.fs != mockFS {
27+
t.Error("expected fs to be set")
28+
}
29+
if runner.clock != mockClock {
30+
t.Error("expected clock to be set")
31+
}
32+
}
33+
34+
func TestDefaultConfigRunner(t *testing.T) {
35+
runner := DefaultConfigRunner()
36+
37+
if runner == nil {
38+
t.Error("expected non-nil runner")
39+
}
40+
if runner.httpClient == nil {
41+
t.Error("expected httpClient to be set")
42+
}
43+
if runner.fs == nil {
44+
t.Error("expected fs to be set")
45+
}
46+
if runner.clock == nil {
47+
t.Error("expected clock to be set")
48+
}
49+
}

cmd/agent_smith/main_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,132 @@ func TestParseUpdateParams(t *testing.T) {
166166
}
167167
}
168168
}
169+
170+
func TestParseUpdateParams_InvalidLoggingLevel(t *testing.T) {
171+
orgId := "test123"
172+
_, err := parseUpdateParams([]string{
173+
"--org-id", orgId,
174+
"--update",
175+
"--logging-level", "invalid-level",
176+
})
177+
178+
if err == nil {
179+
t.Error("expected error for invalid logging level")
180+
}
181+
if !strings.Contains(err.Error(), "invalid logging-level") {
182+
t.Errorf("expected 'invalid logging-level' error, got %v", err.Error())
183+
}
184+
}
185+
186+
func TestParseConfigParams_InvalidLoggingLevel(t *testing.T) {
187+
orgId := "test123"
188+
configUrl := "https://config.url/"
189+
configSecret := "secret123"
190+
191+
_, err := parseConfigParams([]string{
192+
"--org-id", orgId,
193+
"--config-url", configUrl,
194+
"--config-secret", configSecret,
195+
"--logging-level", "invalid-level",
196+
})
197+
198+
if err == nil {
199+
t.Error("expected error for invalid logging level")
200+
}
201+
if !strings.Contains(err.Error(), "invalid logging-level") {
202+
t.Errorf("expected 'invalid logging-level' error, got %v", err.Error())
203+
}
204+
}
205+
206+
func TestParseConfigParams_AllLoggingLevels(t *testing.T) {
207+
orgId := "test123"
208+
configUrl := "https://config.url/"
209+
configSecret := "secret123"
210+
211+
// Note: Default is an empty string "", not "default"
212+
validLevels := []string{"info", "warn", "error", "off", "debug"}
213+
214+
for _, level := range validLevels {
215+
result, err := parseConfigParams([]string{
216+
"--org-id", orgId,
217+
"--config-url", configUrl,
218+
"--config-secret", configSecret,
219+
"--logging-level", level,
220+
})
221+
222+
if err != nil {
223+
t.Errorf("logging level '%s' should be valid, got error: %v", level, err)
224+
}
225+
if result.LoggingLevel != level {
226+
t.Errorf("expected logging level '%s', got '%s'", level, result.LoggingLevel)
227+
}
228+
}
229+
230+
// Test that default logging level (empty string) works when not specified
231+
result, err := parseConfigParams([]string{
232+
"--org-id", orgId,
233+
"--config-url", configUrl,
234+
"--config-secret", configSecret,
235+
})
236+
if err != nil {
237+
t.Errorf("default logging level should work, got error: %v", err)
238+
}
239+
// Default is "" (empty string)
240+
if result.LoggingLevel != "" {
241+
t.Errorf("expected empty logging level for default, got '%s'", result.LoggingLevel)
242+
}
243+
}
244+
245+
func TestParseConfigParams_OptionalFlags(t *testing.T) {
246+
orgId := "test123"
247+
configUrl := "https://config.url/"
248+
configSecret := "secret123"
249+
250+
result, err := parseConfigParams([]string{
251+
"--org-id", orgId,
252+
"--config-url", configUrl,
253+
"--config-secret", configSecret,
254+
"--syslog",
255+
"--disable-agent-postback",
256+
"--no-auto-updates",
257+
})
258+
259+
if err != nil {
260+
t.Errorf("expected no error, got %v", err)
261+
}
262+
if !result.UseSyslog {
263+
t.Error("expected UseSyslog true")
264+
}
265+
if !result.DisableAgentPostback {
266+
t.Error("expected DisableAgentPostback true")
267+
}
268+
if !result.NoAutoUpdates {
269+
t.Error("expected NoAutoUpdates true")
270+
}
271+
}
272+
273+
func TestGetAllowedConfigLevelsString(t *testing.T) {
274+
result := getAllowedConfigLevelsString(", ")
275+
276+
// Should contain valid levels but not "default"
277+
if strings.Contains(result, "default") {
278+
t.Error("result should not contain 'default'")
279+
}
280+
281+
// Should contain other valid levels
282+
validLevels := []string{"info", "warn", "error", "off", "debug"}
283+
for _, level := range validLevels {
284+
if !strings.Contains(result, level) {
285+
t.Errorf("result should contain '%s'", level)
286+
}
287+
}
288+
}
289+
290+
func TestGetAllowedConfigLevelsString_CustomSeparator(t *testing.T) {
291+
result := getAllowedConfigLevelsString("|")
292+
293+
// Should use the custom separator
294+
if !strings.Contains(result, "|") {
295+
t.Error("result should use custom separator '|'")
296+
}
297+
}

0 commit comments

Comments
 (0)