detached d64d62f

refactor(container): move AttachmentConfigProvider to boundaries/out with mockery mock

commit
d64d62f90cd5e0cf0d7d3fce2056bb72a5f0387a
author
brice · · 2026-03-15 16:31 UTC
parent
6a0be67
signature
unsigned
5 files · +191 -87
M .mockery.yaml +1 -0
@@ -19,6 +19,7 @@ packages:
1919 SecretProvider:
2020 EnvLoader:
2121 ContainerLogWriter:
22+ AttachmentConfigProvider:
2223 TokenStore:
2324 DomainSecretStore:
2425 RateLimiter:
@@ -19,6 +19,7 @@ packages:
19 SecretProvider:19 SecretProvider:
20 EnvLoader:20 EnvLoader:
21 ContainerLogWriter:21 ContainerLogWriter:
22+ AttachmentConfigProvider:
22 TokenStore:23 TokenStore:
23 DomainSecretStore:24 DomainSecretStore:
24 RateLimiter:25 RateLimiter:
A internal/boundaries/out/attachment_config.go +17 -0
@@ -0,0 +1,17 @@
1+package out
2+
3+// AttachmentConfigProvider is a read-only view into the live config service
4+// for attachment and network group resolution. It decouples the container
5+// service from the full ConfigService interface while ensuring fresh reads
6+// at decision time (deploy, restart).
7+//
8+// Implementations must return deep copies of the underlying data so that
9+// callers cannot mutate provider state. Each call must produce an independent
10+// map with independently allocated slices.
11+type AttachmentConfigProvider interface {
12+ // GetAttachments returns a deep copy of the current attachment config: domain/group -> []image.
13+ GetAttachments() map[string][]string
14+
15+ // GetNetworkGroups returns a deep copy of the current network group config: group -> []domain.
16+ GetNetworkGroups() map[string][]string
17+}
@@ -0,0 +1,17 @@
1+package out
2+
3+// AttachmentConfigProvider is a read-only view into the live config service
4+// for attachment and network group resolution. It decouples the container
5+// service from the full ConfigService interface while ensuring fresh reads
6+// at decision time (deploy, restart).
7+//
8+// Implementations must return deep copies of the underlying data so that
9+// callers cannot mutate provider state. Each call must produce an independent
10+// map with independently allocated slices.
11+type AttachmentConfigProvider interface {
12+ // GetAttachments returns a deep copy of the current attachment config: domain/group -> []image.
13+ GetAttachments() map[string][]string
14+
15+ // GetNetworkGroups returns a deep copy of the current network group config: group -> []domain.
16+ GetNetworkGroups() map[string][]string
17+}
A internal/boundaries/out/mocks/mock_attachment_config_provider.go +128 -0
@@ -0,0 +1,128 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockAttachmentConfigProvider creates a new instance of MockAttachmentConfigProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockAttachmentConfigProvider(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockAttachmentConfigProvider {
17+ mock := &MockAttachmentConfigProvider{}
18+ mock.Mock.Test(t)
19+
20+ t.Cleanup(func() { mock.AssertExpectations(t) })
21+
22+ return mock
23+}
24+
25+// MockAttachmentConfigProvider is an autogenerated mock type for the AttachmentConfigProvider type
26+type MockAttachmentConfigProvider struct {
27+ mock.Mock
28+}
29+
30+type MockAttachmentConfigProvider_Expecter struct {
31+ mock *mock.Mock
32+}
33+
34+func (_m *MockAttachmentConfigProvider) EXPECT() *MockAttachmentConfigProvider_Expecter {
35+ return &MockAttachmentConfigProvider_Expecter{mock: &_m.Mock}
36+}
37+
38+// GetAttachments provides a mock function for the type MockAttachmentConfigProvider
39+func (_mock *MockAttachmentConfigProvider) GetAttachments() map[string][]string {
40+ ret := _mock.Called()
41+
42+ if len(ret) == 0 {
43+ panic("no return value specified for GetAttachments")
44+ }
45+
46+ var r0 map[string][]string
47+ if returnFunc, ok := ret.Get(0).(func() map[string][]string); ok {
48+ r0 = returnFunc()
49+ } else {
50+ if ret.Get(0) != nil {
51+ r0 = ret.Get(0).(map[string][]string)
52+ }
53+ }
54+ return r0
55+}
56+
57+// MockAttachmentConfigProvider_GetAttachments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAttachments'
58+type MockAttachmentConfigProvider_GetAttachments_Call struct {
59+ *mock.Call
60+}
61+
62+// GetAttachments is a helper method to define mock.On call
63+func (_e *MockAttachmentConfigProvider_Expecter) GetAttachments() *MockAttachmentConfigProvider_GetAttachments_Call {
64+ return &MockAttachmentConfigProvider_GetAttachments_Call{Call: _e.mock.On("GetAttachments")}
65+}
66+
67+func (_c *MockAttachmentConfigProvider_GetAttachments_Call) Run(run func()) *MockAttachmentConfigProvider_GetAttachments_Call {
68+ _c.Call.Run(func(args mock.Arguments) {
69+ run()
70+ })
71+ return _c
72+}
73+
74+func (_c *MockAttachmentConfigProvider_GetAttachments_Call) Return(stringToStrings map[string][]string) *MockAttachmentConfigProvider_GetAttachments_Call {
75+ _c.Call.Return(stringToStrings)
76+ return _c
77+}
78+
79+func (_c *MockAttachmentConfigProvider_GetAttachments_Call) RunAndReturn(run func() map[string][]string) *MockAttachmentConfigProvider_GetAttachments_Call {
80+ _c.Call.Return(run)
81+ return _c
82+}
83+
84+// GetNetworkGroups provides a mock function for the type MockAttachmentConfigProvider
85+func (_mock *MockAttachmentConfigProvider) GetNetworkGroups() map[string][]string {
86+ ret := _mock.Called()
87+
88+ if len(ret) == 0 {
89+ panic("no return value specified for GetNetworkGroups")
90+ }
91+
92+ var r0 map[string][]string
93+ if returnFunc, ok := ret.Get(0).(func() map[string][]string); ok {
94+ r0 = returnFunc()
95+ } else {
96+ if ret.Get(0) != nil {
97+ r0 = ret.Get(0).(map[string][]string)
98+ }
99+ }
100+ return r0
101+}
102+
103+// MockAttachmentConfigProvider_GetNetworkGroups_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNetworkGroups'
104+type MockAttachmentConfigProvider_GetNetworkGroups_Call struct {
105+ *mock.Call
106+}
107+
108+// GetNetworkGroups is a helper method to define mock.On call
109+func (_e *MockAttachmentConfigProvider_Expecter) GetNetworkGroups() *MockAttachmentConfigProvider_GetNetworkGroups_Call {
110+ return &MockAttachmentConfigProvider_GetNetworkGroups_Call{Call: _e.mock.On("GetNetworkGroups")}
111+}
112+
113+func (_c *MockAttachmentConfigProvider_GetNetworkGroups_Call) Run(run func()) *MockAttachmentConfigProvider_GetNetworkGroups_Call {
114+ _c.Call.Run(func(args mock.Arguments) {
115+ run()
116+ })
117+ return _c
118+}
119+
120+func (_c *MockAttachmentConfigProvider_GetNetworkGroups_Call) Return(stringToStrings map[string][]string) *MockAttachmentConfigProvider_GetNetworkGroups_Call {
121+ _c.Call.Return(stringToStrings)
122+ return _c
123+}
124+
125+func (_c *MockAttachmentConfigProvider_GetNetworkGroups_Call) RunAndReturn(run func() map[string][]string) *MockAttachmentConfigProvider_GetNetworkGroups_Call {
126+ _c.Call.Return(run)
127+ return _c
128+}
@@ -0,0 +1,128 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockAttachmentConfigProvider creates a new instance of MockAttachmentConfigProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockAttachmentConfigProvider(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockAttachmentConfigProvider {
17+ mock := &MockAttachmentConfigProvider{}
18+ mock.Mock.Test(t)
19+
20+ t.Cleanup(func() { mock.AssertExpectations(t) })
21+
22+ return mock
23+}
24+
25+// MockAttachmentConfigProvider is an autogenerated mock type for the AttachmentConfigProvider type
26+type MockAttachmentConfigProvider struct {
27+ mock.Mock
28+}
29+
30+type MockAttachmentConfigProvider_Expecter struct {
31+ mock *mock.Mock
32+}
33+
34+func (_m *MockAttachmentConfigProvider) EXPECT() *MockAttachmentConfigProvider_Expecter {
35+ return &MockAttachmentConfigProvider_Expecter{mock: &_m.Mock}
36+}
37+
38+// GetAttachments provides a mock function for the type MockAttachmentConfigProvider
39+func (_mock *MockAttachmentConfigProvider) GetAttachments() map[string][]string {
40+ ret := _mock.Called()
41+
42+ if len(ret) == 0 {
43+ panic("no return value specified for GetAttachments")
44+ }
45+
46+ var r0 map[string][]string
47+ if returnFunc, ok := ret.Get(0).(func() map[string][]string); ok {
48+ r0 = returnFunc()
49+ } else {
50+ if ret.Get(0) != nil {
51+ r0 = ret.Get(0).(map[string][]string)
52+ }
53+ }
54+ return r0
55+}
56+
57+// MockAttachmentConfigProvider_GetAttachments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAttachments'
58+type MockAttachmentConfigProvider_GetAttachments_Call struct {
59+ *mock.Call
60+}
61+
62+// GetAttachments is a helper method to define mock.On call
63+func (_e *MockAttachmentConfigProvider_Expecter) GetAttachments() *MockAttachmentConfigProvider_GetAttachments_Call {
64+ return &MockAttachmentConfigProvider_GetAttachments_Call{Call: _e.mock.On("GetAttachments")}
65+}
66+
67+func (_c *MockAttachmentConfigProvider_GetAttachments_Call) Run(run func()) *MockAttachmentConfigProvider_GetAttachments_Call {
68+ _c.Call.Run(func(args mock.Arguments) {
69+ run()
70+ })
71+ return _c
72+}
73+
74+func (_c *MockAttachmentConfigProvider_GetAttachments_Call) Return(stringToStrings map[string][]string) *MockAttachmentConfigProvider_GetAttachments_Call {
75+ _c.Call.Return(stringToStrings)
76+ return _c
77+}
78+
79+func (_c *MockAttachmentConfigProvider_GetAttachments_Call) RunAndReturn(run func() map[string][]string) *MockAttachmentConfigProvider_GetAttachments_Call {
80+ _c.Call.Return(run)
81+ return _c
82+}
83+
84+// GetNetworkGroups provides a mock function for the type MockAttachmentConfigProvider
85+func (_mock *MockAttachmentConfigProvider) GetNetworkGroups() map[string][]string {
86+ ret := _mock.Called()
87+
88+ if len(ret) == 0 {
89+ panic("no return value specified for GetNetworkGroups")
90+ }
91+
92+ var r0 map[string][]string
93+ if returnFunc, ok := ret.Get(0).(func() map[string][]string); ok {
94+ r0 = returnFunc()
95+ } else {
96+ if ret.Get(0) != nil {
97+ r0 = ret.Get(0).(map[string][]string)
98+ }
99+ }
100+ return r0
101+}
102+
103+// MockAttachmentConfigProvider_GetNetworkGroups_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNetworkGroups'
104+type MockAttachmentConfigProvider_GetNetworkGroups_Call struct {
105+ *mock.Call
106+}
107+
108+// GetNetworkGroups is a helper method to define mock.On call
109+func (_e *MockAttachmentConfigProvider_Expecter) GetNetworkGroups() *MockAttachmentConfigProvider_GetNetworkGroups_Call {
110+ return &MockAttachmentConfigProvider_GetNetworkGroups_Call{Call: _e.mock.On("GetNetworkGroups")}
111+}
112+
113+func (_c *MockAttachmentConfigProvider_GetNetworkGroups_Call) Run(run func()) *MockAttachmentConfigProvider_GetNetworkGroups_Call {
114+ _c.Call.Run(func(args mock.Arguments) {
115+ run()
116+ })
117+ return _c
118+}
119+
120+func (_c *MockAttachmentConfigProvider_GetNetworkGroups_Call) Return(stringToStrings map[string][]string) *MockAttachmentConfigProvider_GetNetworkGroups_Call {
121+ _c.Call.Return(stringToStrings)
122+ return _c
123+}
124+
125+func (_c *MockAttachmentConfigProvider_GetNetworkGroups_Call) RunAndReturn(run func() map[string][]string) *MockAttachmentConfigProvider_GetNetworkGroups_Call {
126+ _c.Call.Return(run)
127+ return _c
128+}
M internal/usecase/container/config_provider.go +4 -26
@@ -1,29 +1,7 @@
11 package container
22
3-// AttachmentConfigProvider is a read-only view into the live config service
4-// for attachment and network group resolution. It decouples the container
5-// service from the full ConfigService interface while ensuring fresh reads
6-// at decision time (deploy, restart).
7-//
8-// Implementations must return deep copies of the underlying data so that
9-// callers cannot mutate provider state. Each call must produce an independent
10-// map with independently allocated slices.
11-type AttachmentConfigProvider interface {
12- // GetAttachments returns a deep copy of the current attachment config: domain/group → []image.
13- GetAttachments() map[string][]string
3+import "github.com/bnema/gordon/internal/boundaries/out"
144
15- // GetNetworkGroups returns a deep copy of the current network group config: group → []domain.
16- GetNetworkGroups() map[string][]string
17-}
18-
19-// deepCopyStringMap returns a deep copy of a map[string][]string.
20-func deepCopyStringMap(m map[string][]string) map[string][]string {
21- if m == nil {
22- return nil
23- }
24- result := make(map[string][]string, len(m))
25- for k, v := range m {
26- result[k] = append([]string{}, v...)
27- }
28- return result
29-}
5+// AttachmentConfigProvider is an alias for the boundary interface, kept for
6+// internal readability.
7+type AttachmentConfigProvider = out.AttachmentConfigProvider
@@ -1,29 +1,7 @@
1 package container1 package container
2 2
3-// AttachmentConfigProvider is a read-only view into the live config service3+import "github.com/bnema/gordon/internal/boundaries/out"
4-// for attachment and network group resolution. It decouples the container
5-// service from the full ConfigService interface while ensuring fresh reads
6-// at decision time (deploy, restart).
7-//
8-// Implementations must return deep copies of the underlying data so that
9-// callers cannot mutate provider state. Each call must produce an independent
10-// map with independently allocated slices.
11-type AttachmentConfigProvider interface {
12- // GetAttachments returns a deep copy of the current attachment config: domain/group → []image.
13- GetAttachments() map[string][]string
14 4
15- // GetNetworkGroups returns a deep copy of the current network group config: group → []domain.5+// AttachmentConfigProvider is an alias for the boundary interface, kept for
16- GetNetworkGroups() map[string][]string6+// internal readability.
17-}7+type AttachmentConfigProvider = out.AttachmentConfigProvider
18-
19-// deepCopyStringMap returns a deep copy of a map[string][]string.
20-func deepCopyStringMap(m map[string][]string) map[string][]string {
21- if m == nil {
22- return nil
23- }
24- result := make(map[string][]string, len(m))
25- for k, v := range m {
26- result[k] = append([]string{}, v...)
27- }
28- return result
29-}
M internal/usecase/container/service_test.go +41 -61
@@ -17,24 +17,10 @@ import (
1717 "go.opentelemetry.io/otel/sdk/metric/metricdata"
1818
1919 "github.com/bnema/gordon/internal/adapters/out/telemetry"
20- "github.com/bnema/gordon/internal/boundaries/out/mocks"
20+ mocks "github.com/bnema/gordon/internal/boundaries/out/mocks"
2121 "github.com/bnema/gordon/internal/domain"
2222 )
2323
24-// fakeConfigProvider implements AttachmentConfigProvider for tests.
25-type fakeConfigProvider struct {
26- attachments map[string][]string
27- networkGroups map[string][]string
28-}
29-
30-func (f *fakeConfigProvider) GetAttachments() map[string][]string {
31- return deepCopyStringMap(f.attachments)
32-}
33-
34-func (f *fakeConfigProvider) GetNetworkGroups() map[string][]string {
35- return deepCopyStringMap(f.networkGroups)
36-}
37-
3824 func testContext() context.Context {
3925 return zerowrap.WithCtx(context.Background(), zerowrap.Default())
4026 }
@@ -99,10 +85,9 @@ func TestService_NewService_WithConfigProvider(t *testing.T) {
9985 envLoader := mocks.NewMockEnvLoader(t)
10086 eventBus := mocks.NewMockEventPublisher(t)
10187
102- provider := &fakeConfigProvider{
103- attachments: map[string][]string{"test.com": {"postgres:16"}},
104- networkGroups: map[string][]string{"group1": {"test.com"}},
105- }
88+ provider := mocks.NewMockAttachmentConfigProvider(t)
89+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.com": {"postgres:16"}}).Maybe()
90+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{"group1": {"test.com"}}).Maybe()
10691
10792 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
10893 assert.NotNil(t, svc)
@@ -113,10 +98,10 @@ func TestService_ResolveAttachments_UsesLiveConfigProvider(t *testing.T) {
11398 envLoader := mocks.NewMockEnvLoader(t)
11499 eventBus := mocks.NewMockEventPublisher(t)
115100
116- provider := &fakeConfigProvider{
117- attachments: map[string][]string{"app.example.com": {"postgres:16"}},
118- networkGroups: map[string][]string{},
119- }
101+ provider := mocks.NewMockAttachmentConfigProvider(t)
102+ provider.EXPECT().GetAttachments().Return(map[string][]string{"app.example.com": {"postgres:16"}}).Once()
103+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
104+ provider.EXPECT().GetAttachments().Return(map[string][]string{"app.example.com": {"postgres:16", "redis:7"}}).Once()
120105
121106 // Create service with EMPTY static config but a live provider
122107 svc := NewService(runtime, envLoader, eventBus, nil, Config{
@@ -128,21 +113,18 @@ func TestService_ResolveAttachments_UsesLiveConfigProvider(t *testing.T) {
128113 result := svc.resolveAttachmentsForDomain("app.example.com")
129114 assert.Equal(t, []string{"postgres:16"}, result)
130115
131- // Simulate adding a new attachment after construction
132- provider.attachments["app.example.com"] = append(provider.attachments["app.example.com"], "redis:7")
133116 result = svc.resolveAttachmentsForDomain("app.example.com")
134117 assert.Equal(t, []string{"postgres:16", "redis:7"}, result)
135118 }
136119
137120 func TestService_ResolveAttachments_GroupBasedViaProvider(t *testing.T) {
138- provider := &fakeConfigProvider{
139- attachments: map[string][]string{
140- "shared-group": {"postgres:16"},
141- },
142- networkGroups: map[string][]string{
143- "shared-group": {"app1.example.com", "app2.example.com"},
144- },
145- }
121+ provider := mocks.NewMockAttachmentConfigProvider(t)
122+ provider.EXPECT().GetAttachments().Return(map[string][]string{
123+ "shared-group": {"postgres:16"},
124+ }).Maybe()
125+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{
126+ "shared-group": {"app1.example.com", "app2.example.com"},
127+ }).Maybe()
146128
147129 svc := NewService(
148130 mocks.NewMockContainerRuntime(t),
@@ -161,12 +143,17 @@ func TestService_ResolveAttachments_GroupBasedViaProvider(t *testing.T) {
161143 }
162144
163145 func TestService_GetNetworkForApp_UsesLiveConfigProvider(t *testing.T) {
164- provider := &fakeConfigProvider{
165- attachments: map[string][]string{},
166- networkGroups: map[string][]string{
167- "shared-group": {"app1.example.com", "app2.example.com"},
168- },
169- }
146+ provider := mocks.NewMockAttachmentConfigProvider(t)
147+ provider.EXPECT().GetAttachments().Return(map[string][]string{}).Maybe()
148+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{
149+ "shared-group": {"app1.example.com", "app2.example.com"},
150+ }).Once()
151+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{
152+ "shared-group": {"app1.example.com", "app2.example.com", "app3.example.com"},
153+ }).Once()
154+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{
155+ "shared-group": {"app1.example.com", "app2.example.com", "app3.example.com"},
156+ }).Once()
170157
171158 svc := NewService(
172159 mocks.NewMockContainerRuntime(t),
@@ -179,8 +166,6 @@ func TestService_GetNetworkForApp_UsesLiveConfigProvider(t *testing.T) {
179166 network := svc.getNetworkForApp("app1.example.com")
180167 assert.Equal(t, "gordon-shared-group", network)
181168
182- // Simulate adding a domain to a group after construction
183- provider.networkGroups["shared-group"] = append(provider.networkGroups["shared-group"], "app3.example.com")
184169 network = svc.getNetworkForApp("app3.example.com")
185170 assert.Equal(t, "gordon-shared-group", network)
186171
@@ -1246,10 +1231,9 @@ func TestService_Restart_WithAttachments_ErrorsWhenConfiguredButNotDeployed(t *t
12461231 envLoader := mocks.NewMockEnvLoader(t)
12471232 eventBus := mocks.NewMockEventPublisher(t)
12481233
1249- provider := &fakeConfigProvider{
1250- attachments: map[string][]string{"test.example.com": {"postgres:16"}},
1251- networkGroups: map[string][]string{},
1252- }
1234+ provider := mocks.NewMockAttachmentConfigProvider(t)
1235+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.example.com": {"postgres:16"}}).Maybe()
1236+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
12531237
12541238 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
12551239
@@ -1273,10 +1257,9 @@ func TestService_Restart_WithAttachments_ErrorsWhenPartiallyDeployed(t *testing.
12731257 envLoader := mocks.NewMockEnvLoader(t)
12741258 eventBus := mocks.NewMockEventPublisher(t)
12751259
1276- provider := &fakeConfigProvider{
1277- attachments: map[string][]string{"test.example.com": {"postgres:16", "redis:7"}},
1278- networkGroups: map[string][]string{},
1279- }
1260+ provider := mocks.NewMockAttachmentConfigProvider(t)
1261+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.example.com": {"postgres:16", "redis:7"}}).Maybe()
1262+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
12801263
12811264 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
12821265
@@ -1302,10 +1285,9 @@ func TestService_Restart_WithAttachments_SucceedsWhenAllDeployed(t *testing.T) {
13021285 envLoader := mocks.NewMockEnvLoader(t)
13031286 eventBus := mocks.NewMockEventPublisher(t)
13041287
1305- provider := &fakeConfigProvider{
1306- attachments: map[string][]string{"test.example.com": {"postgres:16"}},
1307- networkGroups: map[string][]string{},
1308- }
1288+ provider := mocks.NewMockAttachmentConfigProvider(t)
1289+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.example.com": {"postgres:16"}}).Maybe()
1290+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
13091291
13101292 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
13111293
@@ -1328,10 +1310,9 @@ func TestService_Restart_WithoutAttachmentFlag_IgnoresMissingAttachments(t *test
13281310 envLoader := mocks.NewMockEnvLoader(t)
13291311 eventBus := mocks.NewMockEventPublisher(t)
13301312
1331- provider := &fakeConfigProvider{
1332- attachments: map[string][]string{"test.example.com": {"postgres:16"}},
1333- networkGroups: map[string][]string{},
1334- }
1313+ provider := mocks.NewMockAttachmentConfigProvider(t)
1314+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.example.com": {"postgres:16"}}).Maybe()
1315+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
13351316
13361317 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
13371318
@@ -1352,10 +1333,9 @@ func TestService_Restart_WithAttachments_NoConfiguredNoDeployed_Succeeds(t *test
13521333 envLoader := mocks.NewMockEnvLoader(t)
13531334 eventBus := mocks.NewMockEventPublisher(t)
13541335
1355- provider := &fakeConfigProvider{
1356- attachments: map[string][]string{},
1357- networkGroups: map[string][]string{},
1358- }
1336+ provider := mocks.NewMockAttachmentConfigProvider(t)
1337+ provider.EXPECT().GetAttachments().Return(map[string][]string{}).Maybe()
1338+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
13591339
13601340 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
13611341
@@ -17,24 +17,10 @@ import (
17 "go.opentelemetry.io/otel/sdk/metric/metricdata"17 "go.opentelemetry.io/otel/sdk/metric/metricdata"
18 18
19 "github.com/bnema/gordon/internal/adapters/out/telemetry"19 "github.com/bnema/gordon/internal/adapters/out/telemetry"
20- "github.com/bnema/gordon/internal/boundaries/out/mocks"20+ mocks "github.com/bnema/gordon/internal/boundaries/out/mocks"
21 "github.com/bnema/gordon/internal/domain"21 "github.com/bnema/gordon/internal/domain"
22 )22 )
23 23
24-// fakeConfigProvider implements AttachmentConfigProvider for tests.
25-type fakeConfigProvider struct {
26- attachments map[string][]string
27- networkGroups map[string][]string
28-}
29-
30-func (f *fakeConfigProvider) GetAttachments() map[string][]string {
31- return deepCopyStringMap(f.attachments)
32-}
33-
34-func (f *fakeConfigProvider) GetNetworkGroups() map[string][]string {
35- return deepCopyStringMap(f.networkGroups)
36-}
37-
38 func testContext() context.Context {24 func testContext() context.Context {
39 return zerowrap.WithCtx(context.Background(), zerowrap.Default())25 return zerowrap.WithCtx(context.Background(), zerowrap.Default())
40 }26 }
@@ -99,10 +85,9 @@ func TestService_NewService_WithConfigProvider(t *testing.T) {
99 envLoader := mocks.NewMockEnvLoader(t)85 envLoader := mocks.NewMockEnvLoader(t)
100 eventBus := mocks.NewMockEventPublisher(t)86 eventBus := mocks.NewMockEventPublisher(t)
101 87
102- provider := &fakeConfigProvider{88+ provider := mocks.NewMockAttachmentConfigProvider(t)
103- attachments: map[string][]string{"test.com": {"postgres:16"}},89+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.com": {"postgres:16"}}).Maybe()
104- networkGroups: map[string][]string{"group1": {"test.com"}},90+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{"group1": {"test.com"}}).Maybe()
105- }
106 91
107 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)92 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
108 assert.NotNil(t, svc)93 assert.NotNil(t, svc)
@@ -113,10 +98,10 @@ func TestService_ResolveAttachments_UsesLiveConfigProvider(t *testing.T) {
113 envLoader := mocks.NewMockEnvLoader(t)98 envLoader := mocks.NewMockEnvLoader(t)
114 eventBus := mocks.NewMockEventPublisher(t)99 eventBus := mocks.NewMockEventPublisher(t)
115 100
116- provider := &fakeConfigProvider{101+ provider := mocks.NewMockAttachmentConfigProvider(t)
117- attachments: map[string][]string{"app.example.com": {"postgres:16"}},102+ provider.EXPECT().GetAttachments().Return(map[string][]string{"app.example.com": {"postgres:16"}}).Once()
118- networkGroups: map[string][]string{},103+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
119- }104+ provider.EXPECT().GetAttachments().Return(map[string][]string{"app.example.com": {"postgres:16", "redis:7"}}).Once()
120 105
121 // Create service with EMPTY static config but a live provider106 // Create service with EMPTY static config but a live provider
122 svc := NewService(runtime, envLoader, eventBus, nil, Config{107 svc := NewService(runtime, envLoader, eventBus, nil, Config{
@@ -128,21 +113,18 @@ func TestService_ResolveAttachments_UsesLiveConfigProvider(t *testing.T) {
128 result := svc.resolveAttachmentsForDomain("app.example.com")113 result := svc.resolveAttachmentsForDomain("app.example.com")
129 assert.Equal(t, []string{"postgres:16"}, result)114 assert.Equal(t, []string{"postgres:16"}, result)
130 115
131- // Simulate adding a new attachment after construction
132- provider.attachments["app.example.com"] = append(provider.attachments["app.example.com"], "redis:7")
133 result = svc.resolveAttachmentsForDomain("app.example.com")116 result = svc.resolveAttachmentsForDomain("app.example.com")
134 assert.Equal(t, []string{"postgres:16", "redis:7"}, result)117 assert.Equal(t, []string{"postgres:16", "redis:7"}, result)
135 }118 }
136 119
137 func TestService_ResolveAttachments_GroupBasedViaProvider(t *testing.T) {120 func TestService_ResolveAttachments_GroupBasedViaProvider(t *testing.T) {
138- provider := &fakeConfigProvider{121+ provider := mocks.NewMockAttachmentConfigProvider(t)
139- attachments: map[string][]string{122+ provider.EXPECT().GetAttachments().Return(map[string][]string{
140- "shared-group": {"postgres:16"},123+ "shared-group": {"postgres:16"},
141- },124+ }).Maybe()
142- networkGroups: map[string][]string{125+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{
143- "shared-group": {"app1.example.com", "app2.example.com"},126+ "shared-group": {"app1.example.com", "app2.example.com"},
144- },127+ }).Maybe()
145- }
146 128
147 svc := NewService(129 svc := NewService(
148 mocks.NewMockContainerRuntime(t),130 mocks.NewMockContainerRuntime(t),
@@ -161,12 +143,17 @@ func TestService_ResolveAttachments_GroupBasedViaProvider(t *testing.T) {
161 }143 }
162 144
163 func TestService_GetNetworkForApp_UsesLiveConfigProvider(t *testing.T) {145 func TestService_GetNetworkForApp_UsesLiveConfigProvider(t *testing.T) {
164- provider := &fakeConfigProvider{146+ provider := mocks.NewMockAttachmentConfigProvider(t)
165- attachments: map[string][]string{},147+ provider.EXPECT().GetAttachments().Return(map[string][]string{}).Maybe()
166- networkGroups: map[string][]string{148+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{
167- "shared-group": {"app1.example.com", "app2.example.com"},149+ "shared-group": {"app1.example.com", "app2.example.com"},
168- },150+ }).Once()
169- }151+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{
152+ "shared-group": {"app1.example.com", "app2.example.com", "app3.example.com"},
153+ }).Once()
154+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{
155+ "shared-group": {"app1.example.com", "app2.example.com", "app3.example.com"},
156+ }).Once()
170 157
171 svc := NewService(158 svc := NewService(
172 mocks.NewMockContainerRuntime(t),159 mocks.NewMockContainerRuntime(t),
@@ -179,8 +166,6 @@ func TestService_GetNetworkForApp_UsesLiveConfigProvider(t *testing.T) {
179 network := svc.getNetworkForApp("app1.example.com")166 network := svc.getNetworkForApp("app1.example.com")
180 assert.Equal(t, "gordon-shared-group", network)167 assert.Equal(t, "gordon-shared-group", network)
181 168
182- // Simulate adding a domain to a group after construction
183- provider.networkGroups["shared-group"] = append(provider.networkGroups["shared-group"], "app3.example.com")
184 network = svc.getNetworkForApp("app3.example.com")169 network = svc.getNetworkForApp("app3.example.com")
185 assert.Equal(t, "gordon-shared-group", network)170 assert.Equal(t, "gordon-shared-group", network)
186 171
@@ -1246,10 +1231,9 @@ func TestService_Restart_WithAttachments_ErrorsWhenConfiguredButNotDeployed(t *t
1246 envLoader := mocks.NewMockEnvLoader(t)1231 envLoader := mocks.NewMockEnvLoader(t)
1247 eventBus := mocks.NewMockEventPublisher(t)1232 eventBus := mocks.NewMockEventPublisher(t)
1248 1233
1249- provider := &fakeConfigProvider{1234+ provider := mocks.NewMockAttachmentConfigProvider(t)
1250- attachments: map[string][]string{"test.example.com": {"postgres:16"}},1235+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.example.com": {"postgres:16"}}).Maybe()
1251- networkGroups: map[string][]string{},1236+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
1252- }
1253 1237
1254 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)1238 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
1255 1239
@@ -1273,10 +1257,9 @@ func TestService_Restart_WithAttachments_ErrorsWhenPartiallyDeployed(t *testing.
1273 envLoader := mocks.NewMockEnvLoader(t)1257 envLoader := mocks.NewMockEnvLoader(t)
1274 eventBus := mocks.NewMockEventPublisher(t)1258 eventBus := mocks.NewMockEventPublisher(t)
1275 1259
1276- provider := &fakeConfigProvider{1260+ provider := mocks.NewMockAttachmentConfigProvider(t)
1277- attachments: map[string][]string{"test.example.com": {"postgres:16", "redis:7"}},1261+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.example.com": {"postgres:16", "redis:7"}}).Maybe()
1278- networkGroups: map[string][]string{},1262+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
1279- }
1280 1263
1281 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)1264 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
1282 1265
@@ -1302,10 +1285,9 @@ func TestService_Restart_WithAttachments_SucceedsWhenAllDeployed(t *testing.T) {
1302 envLoader := mocks.NewMockEnvLoader(t)1285 envLoader := mocks.NewMockEnvLoader(t)
1303 eventBus := mocks.NewMockEventPublisher(t)1286 eventBus := mocks.NewMockEventPublisher(t)
1304 1287
1305- provider := &fakeConfigProvider{1288+ provider := mocks.NewMockAttachmentConfigProvider(t)
1306- attachments: map[string][]string{"test.example.com": {"postgres:16"}},1289+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.example.com": {"postgres:16"}}).Maybe()
1307- networkGroups: map[string][]string{},1290+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
1308- }
1309 1291
1310 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)1292 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
1311 1293
@@ -1328,10 +1310,9 @@ func TestService_Restart_WithoutAttachmentFlag_IgnoresMissingAttachments(t *test
1328 envLoader := mocks.NewMockEnvLoader(t)1310 envLoader := mocks.NewMockEnvLoader(t)
1329 eventBus := mocks.NewMockEventPublisher(t)1311 eventBus := mocks.NewMockEventPublisher(t)
1330 1312
1331- provider := &fakeConfigProvider{1313+ provider := mocks.NewMockAttachmentConfigProvider(t)
1332- attachments: map[string][]string{"test.example.com": {"postgres:16"}},1314+ provider.EXPECT().GetAttachments().Return(map[string][]string{"test.example.com": {"postgres:16"}}).Maybe()
1333- networkGroups: map[string][]string{},1315+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
1334- }
1335 1316
1336 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)1317 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
1337 1318
@@ -1352,10 +1333,9 @@ func TestService_Restart_WithAttachments_NoConfiguredNoDeployed_Succeeds(t *test
1352 envLoader := mocks.NewMockEnvLoader(t)1333 envLoader := mocks.NewMockEnvLoader(t)
1353 eventBus := mocks.NewMockEventPublisher(t)1334 eventBus := mocks.NewMockEventPublisher(t)
1354 1335
1355- provider := &fakeConfigProvider{1336+ provider := mocks.NewMockAttachmentConfigProvider(t)
1356- attachments: map[string][]string{},1337+ provider.EXPECT().GetAttachments().Return(map[string][]string{}).Maybe()
1357- networkGroups: map[string][]string{},1338+ provider.EXPECT().GetNetworkGroups().Return(map[string][]string{}).Maybe()
1358- }
1359 1339
1360 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)1340 svc := NewService(runtime, envLoader, eventBus, nil, Config{}, provider)
1361 1341

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close