Skip to content

Commit 57fe6ea

Browse files
authored
Merge pull request #8 from ncode/juliano/moar_tests
tests
2 parents 7a46e23 + d922a37 commit 57fe6ea

File tree

4 files changed

+262
-34
lines changed

4 files changed

+262
-34
lines changed

cmd/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ example: tagit run -s my-super-service -x '/tmp/tag-role.sh'
7070
os.Exit(1)
7171
}
7272

73-
t := tagit.New(consulClient, &tagit.CmdExecutor{}, serviceID, script, validInterval, tagPrefix)
73+
t := tagit.New(tagit.NewConsulAPIWrapper(consulClient), &tagit.CmdExecutor{}, serviceID, script, validInterval, tagPrefix)
7474
t.Run()
7575
},
7676
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ require (
88
github.com/sirupsen/logrus v1.9.3
99
github.com/spf13/cobra v1.8.0
1010
github.com/spf13/viper v1.18.2
11+
github.com/stretchr/testify v1.8.4
1112
)
1213

1314
require (
1415
github.com/armon/go-metrics v0.4.1 // indirect
16+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1517
github.com/fatih/color v1.16.0 // indirect
1618
github.com/fsnotify/fsnotify v1.7.0 // indirect
1719
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -30,6 +32,7 @@ require (
3032
github.com/mitchellh/go-homedir v1.1.0 // indirect
3133
github.com/mitchellh/mapstructure v1.5.0 // indirect
3234
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
35+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3336
github.com/sagikazarmark/locafero v0.4.0 // indirect
3437
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
3538
github.com/sourcegraph/conc v0.3.0 // indirect

pkg/tagit/tagit.go

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,28 @@ type TagIt struct {
2525

2626
// ConsulClient is an interface for the Consul client.
2727
type ConsulClient interface {
28-
Agent() *api.Agent
28+
Agent() ConsulAgent
29+
}
30+
31+
// ConsulClientAgent is an interface for the Consul agent.
32+
type ConsulAgent interface {
33+
Services() (map[string]*api.AgentService, error)
34+
ServiceRegister(*api.AgentServiceRegistration) error
35+
}
36+
37+
// ConsulAPIWrapper wraps the Consul API client to conform to the ConsulClient interface.
38+
type ConsulAPIWrapper struct {
39+
client *api.Client
40+
}
41+
42+
// NewConsulAPIWrapper creates a new instance of ConsulAPIWrapper.
43+
func NewConsulAPIWrapper(client *api.Client) *ConsulAPIWrapper {
44+
return &ConsulAPIWrapper{client: client}
45+
}
46+
47+
// Agent returns an object that conforms to the ConsulAgent interface.
48+
func (w *ConsulAPIWrapper) Agent() ConsulAgent {
49+
return w.client.Agent()
2950
}
3051

3152
// CommandExecutor is an interface for running commands.
@@ -82,42 +103,48 @@ func (t *TagIt) runScript() ([]byte, error) {
82103
func (t *TagIt) updateServiceTags() error {
83104
service, err := t.getService()
84105
if err != nil {
85-
return err
106+
return fmt.Errorf("error getting service: %w", err)
86107
}
87-
registration := t.copyServiceToRegistration(service)
88-
log.WithFields(log.Fields{
89-
"service": t.ServiceID,
90-
"tags": registration.Tags,
91-
}).Debug("current service tags")
92-
out, err := t.runScript()
108+
109+
newTags, err := t.generateNewTags()
93110
if err != nil {
94-
return err
111+
return fmt.Errorf("error generating new tags: %w", err)
95112
}
96113

97-
var tags []string
98-
for _, tag := range strings.Fields(string(out)) {
99-
tags = append(tags, fmt.Sprintf("%s-%s", t.TagPrefix, tag))
114+
if err := t.updateConsulService(service, newTags); err != nil {
115+
return fmt.Errorf("error updating service in Consul: %w", err)
100116
}
101117

102-
updatedTags, shouldTag := t.needsTag(registration.Tags, tags)
118+
return nil
119+
}
120+
121+
// generateNewTags runs the script and generates new tags.
122+
func (t *TagIt) generateNewTags() ([]string, error) {
123+
out, err := t.runScript()
124+
if err != nil {
125+
return nil, err
126+
}
127+
return t.parseScriptOutput(out), nil
128+
}
129+
130+
// updateConsulService updates the service in Consul with the new tags.
131+
func (t *TagIt) updateConsulService(service *api.AgentService, newTags []string) error {
132+
registration := t.copyServiceToRegistration(service)
133+
updatedTags, shouldTag := t.needsTag(registration.Tags, newTags)
103134
if shouldTag {
104135
registration.Tags = updatedTags
105-
log.WithFields(log.Fields{
106-
"service": t.ServiceID,
107-
"tags": registration.Tags,
108-
}).Info("updating service tags")
109-
err = t.client.Agent().ServiceRegister(registration)
110-
if err != nil {
111-
return err
112-
}
113-
} else {
114-
log.WithFields(log.Fields{
115-
"service": t.ServiceID,
116-
"tags": registration.Tags,
117-
}).Debug("no changes to service tags")
136+
return t.client.Agent().ServiceRegister(registration)
118137
}
138+
return nil
139+
}
119140

120-
return err
141+
// parseScriptOutput parses the script output and generates tags.
142+
func (t *TagIt) parseScriptOutput(output []byte) []string {
143+
var tags []string
144+
for _, tag := range strings.Fields(string(output)) {
145+
tags = append(tags, fmt.Sprintf("%s-%s", t.TagPrefix, tag))
146+
}
147+
return tags
121148
}
122149

123150
// CleanupServiceTags cleans up the service tags added by tagit.

pkg/tagit/tagit_test.go

Lines changed: 204 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/hashicorp/consul/api"
11+
"github.com/stretchr/testify/assert"
1112
)
1213

1314
func TestDiffTags(t *testing.T) {
@@ -290,13 +291,27 @@ func TestRunScript(t *testing.T) {
290291
}
291292
}
292293

293-
// MockConsulClient is a mock implementation of the ConsulClient interface
294-
type MockConsulClient struct{}
294+
// MockConsulClient implements the ConsulClient interface for testing.
295+
type MockConsulClient struct {
296+
MockAgent *MockAgent
297+
}
298+
299+
func (m *MockConsulClient) Agent() ConsulAgent {
300+
return m.MockAgent
301+
}
302+
303+
// MockAgent simulates the Agent part of the Consul client.
304+
type MockAgent struct {
305+
ServicesFunc func() (map[string]*api.AgentService, error)
306+
ServiceRegisterFunc func(reg *api.AgentServiceRegistration) error
307+
}
308+
309+
func (m *MockAgent) Services() (map[string]*api.AgentService, error) {
310+
return m.ServicesFunc()
311+
}
295312

296-
// TODO: Implement the Agent method properly so that we can test the rest of the methods
297-
func (m *MockConsulClient) Agent() *api.Agent {
298-
// Return a mock *api.Agent if needed
299-
return &api.Agent{}
313+
func (m *MockAgent) ServiceRegister(reg *api.AgentServiceRegistration) error {
314+
return m.ServiceRegisterFunc(reg)
300315
}
301316

302317
func TestNew(t *testing.T) {
@@ -321,3 +336,186 @@ func TestNew(t *testing.T) {
321336
t.Errorf("Expected ServiceID to be 'test-service', got '%s'", tagit.ServiceID)
322337
}
323338
}
339+
340+
func TestGetService(t *testing.T) {
341+
tests := []struct {
342+
name string
343+
serviceID string
344+
mockServicesData map[string]*api.AgentService
345+
mockServicesErr error
346+
expectErr bool
347+
expectService *api.AgentService
348+
}{
349+
{
350+
name: "Service Found",
351+
serviceID: "test-service",
352+
mockServicesData: map[string]*api.AgentService{
353+
"test-service": {
354+
ID: "test-service",
355+
Service: "test",
356+
Tags: []string{"tag1", "tag2"},
357+
},
358+
},
359+
expectErr: false,
360+
expectService: &api.AgentService{ID: "test-service", Service: "test", Tags: []string{"tag1", "tag2"}},
361+
},
362+
{
363+
name: "Service Not Found",
364+
serviceID: "nonexistent-service",
365+
mockServicesData: map[string]*api.AgentService{},
366+
expectErr: true,
367+
expectService: nil,
368+
},
369+
{
370+
name: "Consul Client Error",
371+
serviceID: "test-service",
372+
mockServicesErr: fmt.Errorf("consul client error"),
373+
expectErr: true,
374+
expectService: nil,
375+
},
376+
}
377+
378+
for _, tt := range tests {
379+
t.Run(tt.name, func(t *testing.T) {
380+
mockConsulClient := &MockConsulClient{
381+
MockAgent: &MockAgent{
382+
ServicesFunc: func() (map[string]*api.AgentService, error) {
383+
return tt.mockServicesData, tt.mockServicesErr
384+
},
385+
},
386+
}
387+
tagit := New(mockConsulClient, nil, tt.serviceID, "", 0, "")
388+
389+
service, err := tagit.getService()
390+
391+
if tt.expectErr && err == nil {
392+
t.Errorf("Expected an error but got none")
393+
} else if !tt.expectErr && err != nil {
394+
t.Errorf("Did not expect an error but got: %v", err)
395+
}
396+
397+
if !reflect.DeepEqual(service, tt.expectService) {
398+
t.Errorf("Expected service: %v, got: %v", tt.expectService, service)
399+
}
400+
})
401+
}
402+
}
403+
404+
func TestGenerateNewTags(t *testing.T) {
405+
tests := []struct {
406+
name string
407+
script string
408+
mockOutput string
409+
mockError error
410+
want []string
411+
wantErr bool
412+
}{
413+
{
414+
name: "Valid Script Output",
415+
script: "echo tag1 tag2",
416+
mockOutput: "tag1 tag2",
417+
want: []string{"tag-tag1", "tag-tag2"},
418+
wantErr: false,
419+
},
420+
{
421+
name: "Script Execution Error",
422+
script: "someinvalidcommand",
423+
mockError: fmt.Errorf("command failed"),
424+
want: nil,
425+
wantErr: true,
426+
},
427+
}
428+
429+
for _, tt := range tests {
430+
t.Run(tt.name, func(t *testing.T) {
431+
mockExecutor := &MockCommandExecutor{
432+
MockOutput: []byte(tt.mockOutput),
433+
MockError: tt.mockError,
434+
}
435+
tagit := TagIt{Script: tt.script, commandExecutor: mockExecutor, TagPrefix: "tag"}
436+
437+
got, err := tagit.generateNewTags()
438+
if (err != nil) != tt.wantErr {
439+
t.Fatalf("generateNewTags() error = %v, wantErr %v", err, tt.wantErr)
440+
}
441+
if !reflect.DeepEqual(got, tt.want) {
442+
t.Errorf("generateNewTags() got = %v, want %v", got, tt.want)
443+
}
444+
})
445+
}
446+
}
447+
448+
func TestUpdateConsulService(t *testing.T) {
449+
tests := []struct {
450+
name string
451+
existingTags []string
452+
newTags []string
453+
mockRegisterErr error
454+
expectUpdate bool
455+
expectErr bool
456+
}{
457+
{
458+
name: "Update Needed",
459+
existingTags: []string{"tag1", "tag2"},
460+
newTags: []string{"tag1", "tag3"},
461+
expectUpdate: true,
462+
expectErr: false,
463+
},
464+
{
465+
name: "No Update Needed",
466+
existingTags: []string{"tag1", "tag2"},
467+
newTags: []string{"tag1", "tag2"},
468+
expectUpdate: false,
469+
expectErr: false,
470+
},
471+
{
472+
name: "Consul Register Error",
473+
existingTags: []string{"tag1", "tag2"},
474+
newTags: []string{"tag1", "tag3"},
475+
mockRegisterErr: fmt.Errorf("consul error"),
476+
expectUpdate: true,
477+
expectErr: true,
478+
},
479+
}
480+
481+
for _, tt := range tests {
482+
t.Run(tt.name, func(t *testing.T) {
483+
service := &api.AgentService{Tags: tt.existingTags}
484+
mockConsulClient := &MockConsulClient{
485+
MockAgent: &MockAgent{
486+
ServiceRegisterFunc: func(reg *api.AgentServiceRegistration) error {
487+
return tt.mockRegisterErr
488+
},
489+
},
490+
}
491+
tagit := TagIt{client: mockConsulClient}
492+
493+
err := tagit.updateConsulService(service, tt.newTags)
494+
if (err != nil) != tt.expectErr {
495+
t.Fatalf("updateConsulService() error = %v, wantErr %v", err, tt.expectErr)
496+
}
497+
})
498+
}
499+
}
500+
501+
func TestNewConsulAPIWrapper(t *testing.T) {
502+
// Mock Consul API client
503+
consulClient, err := api.NewClient(api.DefaultConfig())
504+
if err != nil {
505+
t.Fatalf("Failed to create Consul client: %v", err)
506+
}
507+
508+
// Test NewConsulAPIWrapper
509+
wrapper := NewConsulAPIWrapper(consulClient)
510+
511+
// Assert that wrapper is not nil
512+
assert.NotNil(t, wrapper, "NewConsulAPIWrapper returned nil")
513+
514+
// Assert that wrapper implements ConsulClient interface
515+
_, isConsulClient := interface{}(wrapper).(ConsulClient)
516+
assert.True(t, isConsulClient, "NewConsulAPIWrapper does not implement ConsulClient interface")
517+
518+
// Optionally, assert that wrapper's Agent method returns a ConsulAgent
519+
_, isConsulAgent := wrapper.Agent().(ConsulAgent)
520+
assert.True(t, isConsulAgent, "Wrapper's Agent method does not return a ConsulAgent")
521+
}

0 commit comments

Comments
 (0)