-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathcontroller_test.go
More file actions
179 lines (155 loc) · 5.67 KB
/
controller_test.go
File metadata and controls
179 lines (155 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package controller
import (
"context"
"fmt"
"sync"
"testing"
"time"
"github.com/github/deployment-tracker/pkg/deploymentrecord"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
amcache "k8s.io/apimachinery/pkg/util/cache"
)
// mockPoster records all PostOne calls and returns a configurable error.
type mockPoster struct {
mu sync.Mutex
calls int
lastErr error
}
func (m *mockPoster) PostOne(_ context.Context, _ *deploymentrecord.DeploymentRecord) error {
m.mu.Lock()
defer m.mu.Unlock()
m.calls++
return m.lastErr
}
func (m *mockPoster) getCalls() int {
m.mu.Lock()
defer m.mu.Unlock()
return m.calls
}
// newTestController creates a minimal Controller suitable for unit-testing
// recordContainer without a real Kubernetes cluster.
func newTestController(poster *mockPoster) *Controller {
return &Controller{
apiClient: poster,
cfg: &Config{
Template: "{{namespace}}/{{deploymentName}}/{{containerName}}",
LogicalEnvironment: "test",
PhysicalEnvironment: "test",
Cluster: "test",
},
observedDeployments: amcache.NewExpiring(),
unknownArtifacts: amcache.NewExpiring(),
}
}
// testPod returns a pod with a single container and a known digest.
func testPod(digest string) (*corev1.Pod, corev1.Container) {
container := corev1.Container{
Name: "app",
Image: "nginx:latest",
}
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
OwnerReferences: []metav1.OwnerReference{{
APIVersion: "apps/v1",
Kind: "ReplicaSet",
Name: "test-deployment-abc123",
}},
},
Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{{
Name: "app",
ImageID: fmt.Sprintf("docker-pullable://nginx@%s", digest),
}},
},
}
return pod, container
}
func TestRecordContainer_UnknownArtifactCachePopulatedOn404(t *testing.T) {
t.Parallel()
digest := "sha256:unknown404digest"
poster := &mockPoster{
lastErr: &deploymentrecord.NoArtifactError{},
}
ctrl := newTestController(poster)
pod, container := testPod(digest)
// First call should hit the API and get a 404
err := ctrl.recordContainer(context.Background(), pod, container, deploymentrecord.StatusDeployed, EventCreated, nil)
require.NoError(t, err)
assert.Equal(t, 1, poster.getCalls())
// Digest should now be in the unknown artifacts cache
_, exists := ctrl.unknownArtifacts.Get(digest)
assert.True(t, exists, "digest should be cached after 404")
}
func TestRecordContainer_UnknownArtifactCacheSkipsAPICall(t *testing.T) {
t.Parallel()
digest := "sha256:cacheddigest"
poster := &mockPoster{
lastErr: &deploymentrecord.NoArtifactError{},
}
ctrl := newTestController(poster)
pod, container := testPod(digest)
// First call — API returns 404, populates cache
err := ctrl.recordContainer(context.Background(), pod, container, deploymentrecord.StatusDeployed, EventCreated, nil)
require.NoError(t, err)
assert.Equal(t, 1, poster.getCalls())
// Second call — should be served from cache, no API call
err = ctrl.recordContainer(context.Background(), pod, container, deploymentrecord.StatusDeployed, EventCreated, nil)
require.NoError(t, err)
assert.Equal(t, 1, poster.getCalls(), "API should not be called for cached unknown artifact")
}
func TestRecordContainer_UnknownArtifactCacheAppliesToDecommission(t *testing.T) {
t.Parallel()
digest := "sha256:decommission404"
poster := &mockPoster{
lastErr: &deploymentrecord.NoArtifactError{},
}
ctrl := newTestController(poster)
pod, container := testPod(digest)
// Deploy call — 404, populates cache
err := ctrl.recordContainer(context.Background(), pod, container, deploymentrecord.StatusDeployed, EventCreated, nil)
require.NoError(t, err)
assert.Equal(t, 1, poster.getCalls())
// Decommission call for same digest — should skip API
err = ctrl.recordContainer(context.Background(), pod, container, deploymentrecord.StatusDecommissioned, EventDeleted, nil)
require.NoError(t, err)
assert.Equal(t, 1, poster.getCalls(), "decommission should also be skipped for cached unknown artifact")
}
func TestRecordContainer_UnknownArtifactCacheExpires(t *testing.T) {
t.Parallel()
digest := "sha256:expiringdigest"
poster := &mockPoster{
lastErr: &deploymentrecord.NoArtifactError{},
}
ctrl := newTestController(poster)
pod, container := testPod(digest)
// Seed the cache with a very short TTL to test expiry
ctrl.unknownArtifacts.Set(digest, true, 50*time.Millisecond)
// Immediately — should be cached
err := ctrl.recordContainer(context.Background(), pod, container, deploymentrecord.StatusDeployed, EventCreated, nil)
require.NoError(t, err)
assert.Equal(t, 0, poster.getCalls(), "should skip API while cached")
// Wait for expiry
time.Sleep(100 * time.Millisecond)
// After expiry — should call API again
err = ctrl.recordContainer(context.Background(), pod, container, deploymentrecord.StatusDeployed, EventCreated, nil)
require.NoError(t, err)
assert.Equal(t, 1, poster.getCalls(), "should call API after cache expiry")
}
func TestRecordContainer_SuccessfulPostDoesNotPopulateUnknownCache(t *testing.T) {
t.Parallel()
digest := "sha256:knowndigest"
poster := &mockPoster{lastErr: nil} // success
ctrl := newTestController(poster)
pod, container := testPod(digest)
err := ctrl.recordContainer(context.Background(), pod, container, deploymentrecord.StatusDeployed, EventCreated, nil)
require.NoError(t, err)
assert.Equal(t, 1, poster.getCalls())
// Digest should NOT be in the unknown artifacts cache
_, exists := ctrl.unknownArtifacts.Get(digest)
assert.False(t, exists, "successful post should not cache digest as unknown")
}