Skip to content

Commit 7f0bbe9

Browse files
Merge pull request #20 from clouddrove/fix/13-unit-tests
test: add unit tests
2 parents e48a7ae + 12eb4c7 commit 7f0bbe9

3 files changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package registry
2+
3+
import "testing"
4+
5+
func TestNormalizeDockerHubImage(t *testing.T) {
6+
cases := []struct {
7+
in string
8+
want string
9+
}{
10+
{"nginx", "library/nginx"},
11+
{"library/nginx", "library/nginx"},
12+
{"clouddrove/syncerd", "clouddrove/syncerd"},
13+
{"docker.io/nginx", "library/nginx"},
14+
{"docker.io/clouddrove/syncerd", "clouddrove/syncerd"},
15+
{"index.docker.io/nginx", "library/nginx"},
16+
{"index.docker.io/clouddrove/syncerd", "clouddrove/syncerd"},
17+
}
18+
for _, c := range cases {
19+
if got := NormalizeDockerHubImage(c.in); got != c.want {
20+
t.Errorf("NormalizeDockerHubImage(%q) = %q, want %q", c.in, got, c.want)
21+
}
22+
}
23+
}

internal/state/state_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package state
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
)
7+
8+
func TestStateMarkAndIsSynced(t *testing.T) {
9+
s := New()
10+
if s.IsSynced("ecr", "library/nginx", "1.25") {
11+
t.Fatal("fresh state should not report synced")
12+
}
13+
s.MarkSynced("ecr", "library/nginx", "1.25")
14+
if !s.IsSynced("ecr", "library/nginx", "1.25") {
15+
t.Fatal("expected marked tag to be synced")
16+
}
17+
// Unrelated dest/image/tag must stay unsynced.
18+
if s.IsSynced("acr", "library/nginx", "1.25") {
19+
t.Fatal("other destination must not be synced")
20+
}
21+
if s.IsSynced("ecr", "library/redis", "1.25") {
22+
t.Fatal("other image must not be synced")
23+
}
24+
if s.IsSynced("ecr", "library/nginx", "1.26") {
25+
t.Fatal("other tag must not be synced")
26+
}
27+
}
28+
29+
func TestStateNilSafe(t *testing.T) {
30+
var s *State
31+
if s.IsSynced("ecr", "library/nginx", "1.25") {
32+
t.Fatal("nil state IsSynced must return false")
33+
}
34+
}
35+
36+
func TestStateSaveLoadRoundTrip(t *testing.T) {
37+
path := filepath.Join(t.TempDir(), "nested", "state.json")
38+
39+
s := New()
40+
s.MarkSynced("ecr", "library/nginx", "1.25")
41+
s.MarkSynced("acr", "clouddrove/syncerd", "latest")
42+
if err := s.Save(path); err != nil {
43+
t.Fatalf("save: %v", err)
44+
}
45+
46+
loaded, err := Load(path)
47+
if err != nil {
48+
t.Fatalf("load: %v", err)
49+
}
50+
if !loaded.IsSynced("ecr", "library/nginx", "1.25") {
51+
t.Error("round-trip lost ecr/library/nginx:1.25")
52+
}
53+
if !loaded.IsSynced("acr", "clouddrove/syncerd", "latest") {
54+
t.Error("round-trip lost acr/clouddrove/syncerd:latest")
55+
}
56+
}
57+
58+
func TestLoadMissingFileReturnsEmptyState(t *testing.T) {
59+
path := filepath.Join(t.TempDir(), "does-not-exist.json")
60+
s, err := Load(path)
61+
if err != nil {
62+
t.Fatalf("load missing: %v", err)
63+
}
64+
if s == nil || s.Synced == nil {
65+
t.Fatal("expected initialized empty state")
66+
}
67+
}

internal/sync/sync_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package sync
2+
3+
import (
4+
"context"
5+
"errors"
6+
"strings"
7+
"testing"
8+
"time"
9+
)
10+
11+
func TestRetrySucceedsAfterFailures(t *testing.T) {
12+
calls := 0
13+
err := retry(context.Background(), 3, time.Millisecond, func() error {
14+
calls++
15+
if calls < 3 {
16+
return errors.New("transient")
17+
}
18+
return nil
19+
})
20+
if err != nil {
21+
t.Fatalf("expected success, got %v", err)
22+
}
23+
if calls != 3 {
24+
t.Fatalf("expected 3 calls, got %d", calls)
25+
}
26+
}
27+
28+
func TestRetryReturnsLastError(t *testing.T) {
29+
want := errors.New("boom")
30+
calls := 0
31+
err := retry(context.Background(), 2, time.Millisecond, func() error {
32+
calls++
33+
return want
34+
})
35+
if !errors.Is(err, want) {
36+
t.Fatalf("expected last error %v, got %v", want, err)
37+
}
38+
if calls != 2 {
39+
t.Fatalf("expected 2 calls, got %d", calls)
40+
}
41+
}
42+
43+
func TestRetryStopsOnCancelledContext(t *testing.T) {
44+
ctx, cancel := context.WithCancel(context.Background())
45+
cancel()
46+
calls := 0
47+
err := retry(ctx, 3, time.Millisecond, func() error {
48+
calls++
49+
return errors.New("transient")
50+
})
51+
if err == nil {
52+
t.Fatal("expected error from cancelled context")
53+
}
54+
if calls != 0 {
55+
t.Fatalf("expected fn not to run on cancelled context, got %d calls", calls)
56+
}
57+
}
58+
59+
func TestSlackCompactNewSyncs(t *testing.T) {
60+
events := []SyncEvent{
61+
{Destination: "ecr", Ref: "ecr.example.com/library/nginx:1.25"},
62+
{Destination: "acr", Ref: "acr.example.com/library/nginx:1.25"},
63+
}
64+
msg := slackCompactNewSyncs(events)
65+
if !strings.Contains(msg, "ecr.example.com/library/nginx:1.25") {
66+
t.Errorf("compact message missing ref: %q", msg)
67+
}
68+
if !strings.Contains(msg, "new images/tags synced") {
69+
t.Errorf("compact message missing header: %q", msg)
70+
}
71+
}
72+
73+
func TestSlackCompactNewSyncsTruncates(t *testing.T) {
74+
var events []SyncEvent
75+
for i := 0; i < 40; i++ {
76+
events = append(events, SyncEvent{Ref: "ref"})
77+
}
78+
msg := slackCompactNewSyncs(events)
79+
if !strings.Contains(msg, "and 15 more") {
80+
t.Errorf("expected truncation note, got: %q", msg)
81+
}
82+
}
83+
84+
func TestSlackCompactFailures(t *testing.T) {
85+
events := []FailureEvent{
86+
{Destination: "ecr", Ref: "ecr.example.com/library/nginx:1.25", Error: "denied"},
87+
}
88+
msg := slackCompactFailures(events)
89+
if !strings.Contains(msg, "denied") || !strings.Contains(msg, "sync failures") {
90+
t.Errorf("failure message malformed: %q", msg)
91+
}
92+
}
93+
94+
func TestSlackDetailedGroupsByDestination(t *testing.T) {
95+
events := []SyncEvent{
96+
{Destination: "ecr", Ref: "a"},
97+
{Destination: "ecr", Ref: "b"},
98+
{Destination: "acr", Ref: "c"},
99+
}
100+
msg := slackDetailedNewSyncs(events)
101+
if !strings.Contains(msg, "*ecr* (2)") {
102+
t.Errorf("expected ecr group header, got: %q", msg)
103+
}
104+
if !strings.Contains(msg, "*acr* (1)") {
105+
t.Errorf("expected acr group header, got: %q", msg)
106+
}
107+
}

0 commit comments

Comments
 (0)