Skip to content

Commit a367a27

Browse files
PLM-186. Add unit tests for CLI
1 parent af52cdb commit a367a27

File tree

1 file changed

+285
-0
lines changed

1 file changed

+285
-0
lines changed

main_test.go

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"net/http/httptest"
8+
"net/url"
9+
"os"
10+
"reflect"
11+
"strconv"
12+
"testing"
13+
14+
"github.com/spf13/cobra"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
type testHelper struct {
19+
t *testing.T
20+
server *httptest.Server
21+
oldStdout *os.File
22+
stdoutW *os.File
23+
}
24+
25+
func extractPort(serverURL string) int {
26+
u, err := url.Parse(serverURL)
27+
if err != nil {
28+
return 0
29+
}
30+
port, _ := strconv.Atoi(u.Port())
31+
return port
32+
}
33+
34+
// newTestHelper creates a new test helper with stdout redirection
35+
func newTestHelper(t *testing.T) *testHelper {
36+
oldStdout := os.Stdout
37+
_, w, _ := os.Pipe()
38+
os.Stdout = w
39+
40+
return &testHelper{
41+
t: t,
42+
oldStdout: oldStdout,
43+
stdoutW: w,
44+
}
45+
}
46+
47+
// cleanup restores stdout and closes the test server
48+
func (h *testHelper) cleanup() {
49+
os.Stdout = h.oldStdout
50+
if h.server != nil {
51+
h.server.Close()
52+
}
53+
}
54+
55+
// setupCommand prepares a command for testing with the test server port
56+
func (h *testHelper) setupCommand(cmd *cobra.Command) func() {
57+
originalRunE := cmd.RunE
58+
cmd.RunE = func(c *cobra.Command, args []string) error {
59+
port := extractPort(h.server.URL)
60+
c.Flags().Set("port", fmt.Sprintf("%d", port))
61+
return originalRunE(c, args)
62+
}
63+
return func() { cmd.RunE = originalRunE }
64+
}
65+
66+
func TestStartCmd(t *testing.T) {
67+
testCmd := &cobra.Command{
68+
Use: "start",
69+
RunE: startCmd.RunE,
70+
}
71+
72+
testCmd.Flags().Int("port", 3000, "Port number")
73+
testCmd.Flags().StringSlice("include-namespaces", nil,
74+
"Namespaces to include in the replication (e.g. db1.collection1,db2.collection2)")
75+
testCmd.Flags().StringSlice("exclude-namespaces", nil,
76+
"Namespaces to exclude from the replication (e.g. db3.collection3,db4.*)")
77+
78+
h := newTestHelper(t)
79+
defer h.cleanup()
80+
81+
h.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
82+
if r.URL.Path == "/start" {
83+
var startReq startRequest
84+
if r.ContentLength > 0 {
85+
data := make([]byte, r.ContentLength)
86+
r.Body.Read(data)
87+
json.Unmarshal(data, &startReq)
88+
}
89+
expectedInclude := []string{"db1.collection1"}
90+
expectedExclude := []string{"db3.collection3", "db4.*"}
91+
92+
if !reflect.DeepEqual(startReq.IncludeNamespaces, expectedInclude) {
93+
t.Errorf("Expected include namespaces %v, got %v", expectedInclude, startReq.IncludeNamespaces)
94+
}
95+
if !reflect.DeepEqual(startReq.ExcludeNamespaces, expectedExclude) {
96+
t.Errorf("Expected exclude namespaces %v, got %v", expectedExclude, startReq.ExcludeNamespaces)
97+
}
98+
response := startResponse{Ok: true}
99+
json.NewEncoder(w).Encode(response)
100+
}
101+
}))
102+
103+
cleanup := h.setupCommand(testCmd)
104+
defer cleanup()
105+
106+
testCmd.SetArgs([]string{"--exclude-namespaces=db3.collection3,db4.*", "--include-namespaces=db1.collection1"})
107+
err := testCmd.Execute()
108+
require.NoError(t, err)
109+
}
110+
111+
func TestStartCmd_NamespaceValidation(t *testing.T) {
112+
testCmd := &cobra.Command{
113+
Use: "start",
114+
RunE: startCmd.RunE,
115+
}
116+
117+
testCmd.Flags().Int("port", 3000, "Port number")
118+
testCmd.Flags().StringSlice("include-namespaces", nil,
119+
"Namespaces to include in the replication (e.g. db1.collection1,db2.collection2)")
120+
testCmd.Flags().StringSlice("exclude-namespaces", nil,
121+
"Namespaces to exclude from the replication (e.g. db3.collection3,db4.*)")
122+
123+
h := newTestHelper(t)
124+
defer h.cleanup()
125+
126+
h.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
127+
if r.URL.Path == "/start" {
128+
var startReq startRequest
129+
if r.ContentLength > 0 {
130+
data := make([]byte, r.ContentLength)
131+
r.Body.Read(data)
132+
json.Unmarshal(data, &startReq)
133+
}
134+
135+
t.Logf("Received exclude namespaces: %v", startReq.ExcludeNamespaces)
136+
t.Logf("Received include namespaces: %v", startReq.IncludeNamespaces)
137+
138+
response := startResponse{Ok: true}
139+
json.NewEncoder(w).Encode(response)
140+
}
141+
}))
142+
143+
cleanup := h.setupCommand(testCmd)
144+
defer cleanup()
145+
146+
testCases := []struct {
147+
name string
148+
excludeArgs string
149+
expectError bool
150+
}{
151+
{"valid_db.collection", "db.collection", false},
152+
{"valid_db.*", "db.*", false},
153+
{"valid_multiple", "db1.collection1,db2.*", false},
154+
{"malformed_extra_chars", "$#@!!#invalid", false}, // validation should be added?
155+
{"malformed_double_dot", "dfa..collection", false}, // validation should be added?
156+
{"malformed_trailing_dot", "db.collection.", false}, // validation should be added?
157+
}
158+
159+
for _, tc := range testCases {
160+
t.Run(tc.name, func(t *testing.T) {
161+
testCmd.SetArgs([]string{"--exclude-namespaces=" + tc.excludeArgs})
162+
err := testCmd.Execute()
163+
164+
if tc.expectError {
165+
require.Error(t, err, "Expected error for: %s", tc.excludeArgs)
166+
} else {
167+
require.NoError(t, err, "Expected success for: %s", tc.excludeArgs)
168+
}
169+
})
170+
}
171+
}
172+
173+
func TestStatusCmd(t *testing.T) {
174+
testCmd := &cobra.Command{
175+
Use: "status",
176+
RunE: statusCmd.RunE,
177+
}
178+
testCmd.Flags().Int("port", 3000, "Port number")
179+
180+
h := newTestHelper(t)
181+
defer h.cleanup()
182+
183+
h.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
184+
if r.URL.Path == "/status" {
185+
response := statusResponse{Ok: true, State: "idle"}
186+
json.NewEncoder(w).Encode(response)
187+
}
188+
}))
189+
190+
cleanup := h.setupCommand(testCmd)
191+
defer cleanup()
192+
193+
testCmd.SetArgs([]string{})
194+
err := testCmd.Execute()
195+
require.NoError(t, err)
196+
}
197+
198+
func TestFinalizeCmd(t *testing.T) {
199+
testCmd := &cobra.Command{
200+
Use: "finalize",
201+
RunE: finalizeCmd.RunE,
202+
}
203+
testCmd.Flags().Int("port", 3000, "Port number")
204+
testCmd.Flags().Bool("ignore-history-lost", false, "Ignore history lost error")
205+
206+
h := newTestHelper(t)
207+
defer h.cleanup()
208+
209+
h.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
210+
if r.URL.Path == "/finalize" {
211+
var finalizeReq finalizeRequest
212+
if r.ContentLength > 0 {
213+
data := make([]byte, r.ContentLength)
214+
r.Body.Read(data)
215+
json.Unmarshal(data, &finalizeReq)
216+
}
217+
require.Equal(t, true, finalizeReq.IgnoreHistoryLost)
218+
response := finalizeResponse{Ok: true}
219+
json.NewEncoder(w).Encode(response)
220+
}
221+
}))
222+
223+
cleanup := h.setupCommand(testCmd)
224+
defer cleanup()
225+
226+
testCmd.SetArgs([]string{"--ignore-history-lost"})
227+
err := testCmd.Execute()
228+
require.NoError(t, err)
229+
}
230+
231+
func TestPauseCmd(t *testing.T) {
232+
testCmd := &cobra.Command{
233+
Use: "pause",
234+
RunE: pauseCmd.RunE,
235+
}
236+
testCmd.Flags().Int("port", 3000, "Port number")
237+
h := newTestHelper(t)
238+
defer h.cleanup()
239+
h.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
240+
if r.URL.Path == "/pause" {
241+
response := pauseResponse{Ok: true}
242+
json.NewEncoder(w).Encode(response)
243+
}
244+
}))
245+
246+
cleanup := h.setupCommand(testCmd)
247+
defer cleanup()
248+
249+
testCmd.SetArgs([]string{})
250+
err := testCmd.Execute()
251+
require.NoError(t, err)
252+
}
253+
254+
func TestResumeCmd(t *testing.T) {
255+
testCmd := &cobra.Command{
256+
Use: "resume",
257+
RunE: resumeCmd.RunE,
258+
}
259+
testCmd.Flags().Int("port", 3000, "Port number")
260+
testCmd.Flags().Bool("from-failure", false, "Resume from failure")
261+
262+
h := newTestHelper(t)
263+
defer h.cleanup()
264+
265+
h.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
266+
if r.URL.Path == "/resume" {
267+
var resumeReq resumeRequest
268+
if r.ContentLength > 0 {
269+
data := make([]byte, r.ContentLength)
270+
r.Body.Read(data)
271+
json.Unmarshal(data, &resumeReq)
272+
}
273+
require.Equal(t, true, resumeReq.FromFailure)
274+
response := resumeResponse{Ok: true}
275+
json.NewEncoder(w).Encode(response)
276+
}
277+
}))
278+
279+
cleanup := h.setupCommand(testCmd)
280+
defer cleanup()
281+
282+
testCmd.SetArgs([]string{"--from-failure"})
283+
err := testCmd.Execute()
284+
require.NoError(t, err)
285+
}

0 commit comments

Comments
 (0)