1package cli
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "testing"
8
9 climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks"
10 "github.com/bnema/gordon/internal/domain"
11 "github.com/stretchr/testify/assert"
12)
13
14type failingWriter struct{}
15
16func (failingWriter) Write(_ []byte) (int, error) {
17 return 0, errors.New("write failed")
18}
19
20func TestSortSemverTags(t *testing.T) {
21 tests := []struct {
22 name string
23 tags []string
24 wantTags []string
25 }{
26 {
27 name: "v-prefixed tags sorted descending",
28 tags: []string{"v1.0.0", "v2.0.0", "v1.5.0"},
29 wantTags: []string{"v2.0.0", "v1.5.0", "v1.0.0"},
30 },
31 {
32 name: "non-v-prefixed tags sorted as semver",
33 tags: []string{"1.0.0", "2.0.0", "1.5.0"},
34 wantTags: []string{"2.0.0", "1.5.0", "1.0.0"},
35 },
36 {
37 name: "mixed v and non-v prefixed",
38 tags: []string{"v1.0.0", "2.0.0", "1.5.0"},
39 wantTags: []string{"2.0.0", "1.5.0", "v1.0.0"}, // all recognized as semver, sorted desc
40 },
41 {
42 name: "pre-release tags",
43 tags: []string{"v1.0.0-rc1", "v1.0.0", "1.0.0-beta"},
44 wantTags: []string{"v1.0.0", "v1.0.0-rc1", "1.0.0-beta"}, // release > pre-release
45 },
46 {
47 name: "non-semver tags appended after",
48 tags: []string{"latest", "v1.0.0", "dev"},
49 wantTags: []string{"v1.0.0", "latest", "dev"},
50 },
51 }
52
53 for _, tt := range tests {
54 t.Run(tt.name, func(t *testing.T) {
55 gotTags := sortSemverTags(tt.tags)
56 assert.Equal(t, tt.wantTags, gotTags)
57 })
58 }
59}
60
61func TestValidateTagExists(t *testing.T) {
62 assert.True(t, validateTagExists("v1.0.0", []string{"v1.0.0", "v2.0.0"}))
63 assert.False(t, validateTagExists("v3.0.0", []string{"v1.0.0", "v2.0.0"}))
64}
65
66func TestPrintPinTags_WritesToProvidedWriter(t *testing.T) {
67 var buf bytes.Buffer
68
69 err := printPinTags(&buf, "myapp.example.com", "v1.2.0", []string{"v1.2.0", "v1.1.0"}, false)
70 assert.NoError(t, err)
71
72 output := buf.String()
73 assert.Contains(t, output, "Available tags for")
74 assert.Contains(t, output, "myapp.example.com")
75 assert.Contains(t, output, "- v1.2.0 (current)")
76 assert.Contains(t, output, "- v1.1.0")
77}
78
79func TestPrintPinTags_ReturnsWriteError(t *testing.T) {
80 err := printPinTags(failingWriter{}, "myapp.example.com", "v1.2.0", []string{"v1.2.0"}, false)
81 assert.Error(t, err)
82 assert.Contains(t, err.Error(), "write failed")
83}
84
85func TestDeploySelectedTag_FormatsDeployFailureAfterRevert(t *testing.T) {
86 cp := climocks.NewMockControlPlane(t)
87 route := &domain.Route{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}
88 deployErr := &domain.DeployFailureError{
89 Summary: "failed to deploy",
90 Cause: "health check failed",
91 Hint: "check DATABASE_URL",
92 }
93
94 cp.EXPECT().UpdateRoute(context.Background(), domain.Route{Domain: "app.example.com", Image: "registry.example.com/myapp:v2"}).Return(nil).Once()
95 cp.EXPECT().Deploy(context.Background(), "app.example.com").Return(nil, deployErr).Once()
96 cp.EXPECT().UpdateRoute(context.Background(), domain.Route{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}).Return(nil).Once()
97
98 err := deploySelectedTag(context.Background(), cp, route, &bytes.Buffer{}, &bytes.Buffer{}, "app.example.com", "myapp", "v2")
99 assert.Error(t, err)
100 assert.Contains(t, err.Error(), "failed to deploy")
101 assert.Contains(t, err.Error(), "Cause: health check failed")
102 assert.Contains(t, err.Error(), "Hint: check DATABASE_URL")
103 assert.Contains(t, err.Error(), "route reverted to previous image")
104}