Skip to content

Commit 1889d72

Browse files
committed
test: config flow tests
1 parent d43586e commit 1889d72

File tree

2 files changed

+380
-0
lines changed

2 files changed

+380
-0
lines changed

cmd/agent_smith/config_test.go

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"net/http"
7+
"net/http/httptest"
8+
"os"
9+
"strings"
10+
"testing"
11+
12+
"github.com/RewstApp/agent-smith-go/internal/agent"
13+
"github.com/RewstApp/agent-smith-go/internal/utils"
14+
)
15+
16+
// ── helpers ──────────────────────────────────────────────────────────────────
17+
18+
func newConfigTestSys() *mockSystemInfoProvider {
19+
return &mockSystemInfoProvider{
20+
hostname: "test-host",
21+
hostPlatform: "windows",
22+
cpuModelName: "Intel Core i9",
23+
totalMemoryBytes: 16 << 30,
24+
}
25+
}
26+
27+
func newConfigTestFS() *mockFileSystem {
28+
return &mockFileSystem{
29+
executableFunc: func() (string, error) { return "/fake/agent", nil },
30+
readFileFunc: func(string) ([]byte, error) { return []byte("binary"), nil },
31+
writeFileFunc: func(string, []byte, os.FileMode) error { return nil },
32+
mkdirAllFunc: func(string) error { return nil },
33+
}
34+
}
35+
36+
func newConfigTestServiceManager() *mockServiceManager {
37+
return &mockServiceManager{
38+
openErr: errors.New("no existing service"),
39+
createService: &mockService{},
40+
}
41+
}
42+
43+
func validConfigResponseBody(orgId string) string {
44+
resp := fetchConfigurationResponse{
45+
Configuration: agent.Device{
46+
DeviceId: "device-123",
47+
RewstOrgId: orgId,
48+
RewstEngineHost: "engine.example.com",
49+
SharedAccessKey: "key123",
50+
AzureIotHubHost: "hub.example.com",
51+
},
52+
}
53+
b, _ := json.Marshal(resp)
54+
return string(b)
55+
}
56+
57+
func newConfigServer(t *testing.T, status int, body string) *httptest.Server {
58+
t.Helper()
59+
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
60+
w.WriteHeader(status)
61+
w.Write([]byte(body)) //nolint:errcheck
62+
}))
63+
}
64+
65+
func newBaseConfigParams(configURL string) *configContext {
66+
return &configContext{
67+
OrgId: "test-org",
68+
ConfigUrl: configURL,
69+
ConfigSecret: "secret",
70+
LoggingLevel: string(utils.Default),
71+
Sys: newConfigTestSys(),
72+
Domain: &mockDomainInfoProvider{},
73+
FS: newConfigTestFS(),
74+
ServiceManager: newConfigTestServiceManager(),
75+
}
76+
}
77+
78+
// ── tests ─────────────────────────────────────────────────────────────────────
79+
80+
func TestRunConfig_Success(t *testing.T) {
81+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
82+
defer srv.Close()
83+
84+
err := runConfig(newBaseConfigParams(srv.URL))
85+
86+
if err != nil {
87+
t.Errorf("expected no error, got %v", err)
88+
}
89+
}
90+
91+
func TestRunConfig_PathsDataError(t *testing.T) {
92+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
93+
defer srv.Close()
94+
95+
params := newBaseConfigParams(srv.URL)
96+
params.Sys = &mockSystemInfoProvider{hostPlatformErr: errors.New("platform error")}
97+
98+
err := runConfig(params)
99+
100+
if err == nil || !strings.Contains(err.Error(), "failed to read paths") {
101+
t.Errorf("expected 'failed to read paths' error, got %v", err)
102+
}
103+
}
104+
105+
func TestRunConfig_HTTPRequestFails(t *testing.T) {
106+
// Close the server before the request so the connection is refused.
107+
srv := newConfigServer(t, http.StatusOK, "")
108+
url := srv.URL
109+
srv.Close()
110+
111+
err := runConfig(newBaseConfigParams(url))
112+
113+
if err == nil || !strings.Contains(err.Error(), "failed to execute http request") {
114+
t.Errorf("expected 'failed to execute http request' error, got %v", err)
115+
}
116+
}
117+
118+
func TestRunConfig_HTTPNon200(t *testing.T) {
119+
srv := newConfigServer(t, http.StatusServiceUnavailable, "")
120+
defer srv.Close()
121+
122+
err := runConfig(newBaseConfigParams(srv.URL))
123+
124+
if err == nil || !strings.Contains(err.Error(), "failed to fetch configuration") {
125+
t.Errorf("expected 'failed to fetch configuration' error, got %v", err)
126+
}
127+
}
128+
129+
func TestRunConfig_InvalidJSONResponse(t *testing.T) {
130+
srv := newConfigServer(t, http.StatusOK, "not-json")
131+
defer srv.Close()
132+
133+
err := runConfig(newBaseConfigParams(srv.URL))
134+
135+
if err == nil || !strings.Contains(err.Error(), "failed to parse response") {
136+
t.Errorf("expected 'failed to parse response' error, got %v", err)
137+
}
138+
}
139+
140+
func TestRunConfig_MkdirAllDataDirFails(t *testing.T) {
141+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
142+
defer srv.Close()
143+
144+
params := newBaseConfigParams(srv.URL)
145+
params.FS = &mockFileSystem{
146+
mkdirAllFunc: func(string) error { return errors.New("mkdir failed") },
147+
}
148+
149+
err := runConfig(params)
150+
151+
if err == nil || !strings.Contains(err.Error(), "failed to create data directory") {
152+
t.Errorf("expected 'failed to create data directory' error, got %v", err)
153+
}
154+
}
155+
156+
func TestRunConfig_WriteConfigFileFails(t *testing.T) {
157+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
158+
defer srv.Close()
159+
160+
params := newBaseConfigParams(srv.URL)
161+
params.FS = &mockFileSystem{
162+
mkdirAllFunc: func(string) error { return nil },
163+
writeFileFunc: func(string, []byte, os.FileMode) error { return errors.New("write failed") },
164+
}
165+
166+
err := runConfig(params)
167+
168+
if err == nil || !strings.Contains(err.Error(), "failed to save config") {
169+
t.Errorf("expected 'failed to save config' error, got %v", err)
170+
}
171+
}
172+
173+
func TestRunConfig_ExistingService_StopFails(t *testing.T) {
174+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
175+
defer srv.Close()
176+
177+
params := newBaseConfigParams(srv.URL)
178+
params.ServiceManager = &mockServiceManager{
179+
openService: &mockService{isActive: true, stopErr: errors.New("stop failed")},
180+
createService: &mockService{},
181+
}
182+
183+
err := runConfig(params)
184+
185+
if err == nil || !strings.Contains(err.Error(), "failed to stop service") {
186+
t.Errorf("expected 'failed to stop service' error, got %v", err)
187+
}
188+
}
189+
190+
func TestRunConfig_ExistingService_DeleteFails(t *testing.T) {
191+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
192+
defer srv.Close()
193+
194+
params := newBaseConfigParams(srv.URL)
195+
params.ServiceManager = &mockServiceManager{
196+
openService: &mockService{isActive: false, deleteErr: errors.New("delete failed")},
197+
createService: &mockService{},
198+
}
199+
200+
err := runConfig(params)
201+
202+
if err == nil || !strings.Contains(err.Error(), "failed to delete service") {
203+
t.Errorf("expected 'failed to delete service' error, got %v", err)
204+
}
205+
}
206+
207+
func TestRunConfig_MkdirAllProgramDirFails(t *testing.T) {
208+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
209+
defer srv.Close()
210+
211+
params := newBaseConfigParams(srv.URL)
212+
mkdirCount := 0
213+
params.FS = &mockFileSystem{
214+
mkdirAllFunc: func(string) error {
215+
mkdirCount++
216+
if mkdirCount == 2 {
217+
return errors.New("mkdir failed")
218+
}
219+
return nil
220+
},
221+
writeFileFunc: func(string, []byte, os.FileMode) error { return nil },
222+
}
223+
224+
err := runConfig(params)
225+
226+
if err == nil || !strings.Contains(err.Error(), "failed to create program directory") {
227+
t.Errorf("expected 'failed to create program directory' error, got %v", err)
228+
}
229+
}
230+
231+
func TestRunConfig_ExecutableFails(t *testing.T) {
232+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
233+
defer srv.Close()
234+
235+
params := newBaseConfigParams(srv.URL)
236+
params.FS = &mockFileSystem{
237+
mkdirAllFunc: func(string) error { return nil },
238+
writeFileFunc: func(string, []byte, os.FileMode) error { return nil },
239+
executableFunc: func() (string, error) { return "", errors.New("executable error") },
240+
}
241+
242+
err := runConfig(params)
243+
244+
if err == nil || !strings.Contains(err.Error(), "failed to get executable") {
245+
t.Errorf("expected 'failed to get executable' error, got %v", err)
246+
}
247+
}
248+
249+
func TestRunConfig_ReadFileFails(t *testing.T) {
250+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
251+
defer srv.Close()
252+
253+
params := newBaseConfigParams(srv.URL)
254+
params.FS = &mockFileSystem{
255+
mkdirAllFunc: func(string) error { return nil },
256+
writeFileFunc: func(string, []byte, os.FileMode) error { return nil },
257+
executableFunc: func() (string, error) { return "/fake/agent", nil },
258+
readFileFunc: func(string) ([]byte, error) { return nil, errors.New("read failed") },
259+
}
260+
261+
err := runConfig(params)
262+
263+
if err == nil || !strings.Contains(err.Error(), "failed to read executable file") {
264+
t.Errorf("expected 'failed to read executable file' error, got %v", err)
265+
}
266+
}
267+
268+
func TestRunConfig_WriteAgentExecutableFails(t *testing.T) {
269+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
270+
defer srv.Close()
271+
272+
params := newBaseConfigParams(srv.URL)
273+
writeCount := 0
274+
params.FS = &mockFileSystem{
275+
mkdirAllFunc: func(string) error { return nil },
276+
writeFileFunc: func(string, []byte, os.FileMode) error {
277+
writeCount++
278+
if writeCount == 2 {
279+
return errors.New("write failed")
280+
}
281+
return nil
282+
},
283+
executableFunc: func() (string, error) { return "/fake/agent", nil },
284+
readFileFunc: func(string) ([]byte, error) { return []byte("binary"), nil },
285+
}
286+
287+
err := runConfig(params)
288+
289+
if err == nil || !strings.Contains(err.Error(), "failed to create agent executable") {
290+
t.Errorf("expected 'failed to create agent executable' error, got %v", err)
291+
}
292+
}
293+
294+
func TestRunConfig_ServiceCreateFails(t *testing.T) {
295+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
296+
defer srv.Close()
297+
298+
params := newBaseConfigParams(srv.URL)
299+
params.ServiceManager = &mockServiceManager{
300+
openErr: errors.New("no existing service"),
301+
createErr: errors.New("create failed"),
302+
}
303+
304+
err := runConfig(params)
305+
306+
if err == nil || !strings.Contains(err.Error(), "failed to create service") {
307+
t.Errorf("expected 'failed to create service' error, got %v", err)
308+
}
309+
}
310+
311+
func TestRunConfig_ServiceStartFails(t *testing.T) {
312+
srv := newConfigServer(t, http.StatusOK, validConfigResponseBody("test-org"))
313+
defer srv.Close()
314+
315+
params := newBaseConfigParams(srv.URL)
316+
params.ServiceManager = &mockServiceManager{
317+
openErr: errors.New("no existing service"),
318+
createService: &mockService{startErr: errors.New("start failed")},
319+
}
320+
321+
err := runConfig(params)
322+
323+
if err == nil || !strings.Contains(err.Error(), "failed to start service") {
324+
t.Errorf("expected 'failed to start service' error, got %v", err)
325+
}
326+
}

cmd/agent_smith/main_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package main
22

33
import (
44
"context"
5+
"os"
6+
7+
"github.com/RewstApp/agent-smith-go/internal/service"
58
)
69

710
type mockSystemInfoProvider struct {
@@ -63,3 +66,54 @@ func (mock *mockDomainInfoProvider) IsEntraConnectServer() (bool, error) {
6366
func (mock *mockDomainInfoProvider) EntraDomain(context.Context) (*string, error) {
6467
return mock.entraDomain, mock.entraDomainErr
6568
}
69+
70+
type mockFileSystem struct {
71+
executableFunc func() (string, error)
72+
readFileFunc func(name string) ([]byte, error)
73+
writeFileFunc func(name string, data []byte, perm os.FileMode) error
74+
mkdirAllFunc func(path string) error
75+
}
76+
77+
func (m *mockFileSystem) Executable() (string, error) {
78+
return m.executableFunc()
79+
}
80+
81+
func (m *mockFileSystem) ReadFile(name string) ([]byte, error) {
82+
return m.readFileFunc(name)
83+
}
84+
85+
func (m *mockFileSystem) WriteFile(name string, data []byte, perm os.FileMode) error {
86+
return m.writeFileFunc(name, data, perm)
87+
}
88+
89+
func (m *mockFileSystem) MkdirAll(path string) error {
90+
return m.mkdirAllFunc(path)
91+
}
92+
93+
type mockService struct {
94+
isActive bool
95+
stopErr error
96+
deleteErr error
97+
startErr error
98+
}
99+
100+
func (m *mockService) IsActive() bool { return m.isActive }
101+
func (m *mockService) Stop() error { return m.stopErr }
102+
func (m *mockService) Delete() error { return m.deleteErr }
103+
func (m *mockService) Start() error { return m.startErr }
104+
func (m *mockService) Close() error { return nil }
105+
106+
type mockServiceManager struct {
107+
openErr error
108+
openService service.Service
109+
createErr error
110+
createService service.Service
111+
}
112+
113+
func (m *mockServiceManager) Open(name string) (service.Service, error) {
114+
return m.openService, m.openErr
115+
}
116+
117+
func (m *mockServiceManager) Create(params service.AgentParams) (service.Service, error) {
118+
return m.createService, m.createErr
119+
}

0 commit comments

Comments
 (0)