Skip to content

Commit a02c8fa

Browse files
committed
address PR comment
added a paragraph about the server/client architecture to the agents.md addresed the PR comment
1 parent d1dce61 commit a02c8fa

File tree

2 files changed

+81
-105
lines changed

2 files changed

+81
-105
lines changed

AGENTS.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,27 @@ These checks must be the final tasks before considering work complete.
107107
| `log/` | Logging utilities |
108108
| `util/` | Context helpers |
109109
| `tests/` | Python E2E tests |
110+
111+
## CLI Architecture (Server + Client)
112+
113+
PCSM uses a single binary that operates in two modes:
114+
115+
### Server Mode (Root Command)
116+
117+
Running `pcsm --source <uri> --target <uri>` starts a **long-running server process**:
118+
119+
1. Connects to source and target MongoDB clusters
120+
2. Creates `pcsm.PCSM` instance with real MongoDB clients
121+
3. Starts HTTP server on `localhost:<port>` (default 27018)
122+
4. Exposes REST endpoints: `/status`, `/start`, `/pause`, `/resume`, `/finalize`
123+
124+
The server holds MongoDB connections and manages the replication state machine.
125+
126+
### Client Mode (Subcommands)
127+
128+
Running `pcsm start`, `pcsm pause`, etc. operates as an **HTTP client**:
129+
130+
1. Creates `PCSMClient` with just the port number
131+
2. Constructs HTTP request with JSON body
132+
3. Sends request to the already-running server
133+
4. Prints JSON response to stdout

pcsm/state_test.go

Lines changed: 57 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,32 @@ import (
99
"github.com/stretchr/testify/require"
1010
)
1111

12-
func TestStartCommand_StateValidation(t *testing.T) {
12+
func TestStart_StateValidation(t *testing.T) {
1313
t.Parallel()
1414

1515
tests := []struct {
1616
name string
1717
initialState State
18-
expectError bool
1918
errorContains string
2019
}{
2120
{
2221
name: "fails from running state",
2322
initialState: StateRunning,
24-
expectError: true,
2523
errorContains: "already running",
2624
},
2725
{
2826
name: "fails from paused state",
2927
initialState: StatePaused,
30-
expectError: true,
3128
errorContains: "paused",
3229
},
3330
{
3431
name: "fails from failed state",
3532
initialState: StateFailed,
36-
expectError: true,
3733
errorContains: "already running",
3834
},
3935
{
4036
name: "fails from finalizing state",
4137
initialState: StateFinalizing,
42-
expectError: true,
4338
errorContains: "already running",
4439
},
4540
}
@@ -55,62 +50,51 @@ func TestStartCommand_StateValidation(t *testing.T) {
5550

5651
err := p.Start(context.Background(), nil)
5752

58-
if tt.expectError {
59-
require.Error(t, err)
60-
assert.Contains(t, err.Error(), tt.errorContains)
61-
assert.Equal(t, tt.initialState, p.state)
62-
}
53+
require.Error(t, err)
54+
assert.Contains(t, err.Error(), tt.errorContains)
55+
assert.Equal(t, tt.initialState, p.state)
6356
})
6457
}
6558
}
6659

67-
func TestPauseCommand_StateValidation(t *testing.T) {
60+
func TestPause_StateValidation(t *testing.T) {
6861
t.Parallel()
6962

7063
tests := []struct {
7164
name string
7265
initialState State
7366
setupRepl bool
74-
replRunning bool
75-
expectError bool
7667
errorContains string
7768
}{
7869
{
7970
name: "fails from idle state",
8071
initialState: StateIdle,
81-
expectError: true,
8272
errorContains: "not running",
8373
},
8474
{
8575
name: "fails from paused state",
8676
initialState: StatePaused,
87-
expectError: true,
8877
errorContains: "not running",
8978
},
9079
{
9180
name: "fails from failed state",
9281
initialState: StateFailed,
93-
expectError: true,
9482
errorContains: "not running",
9583
},
9684
{
9785
name: "fails from finalizing state",
9886
initialState: StateFinalizing,
99-
expectError: true,
10087
errorContains: "not running",
10188
},
10289
{
10390
name: "fails from finalized state",
10491
initialState: StateFinalized,
105-
expectError: true,
10692
errorContains: "not running",
10793
},
10894
{
10995
name: "fails from running when repl not actually running",
11096
initialState: StateRunning,
11197
setupRepl: true,
112-
replRunning: false,
113-
expectError: true,
11498
errorContains: "Change Replication is not running",
11599
},
116100
}
@@ -129,78 +113,60 @@ func TestPauseCommand_StateValidation(t *testing.T) {
129113
pauseC: make(chan struct{}),
130114
doneSig: make(chan struct{}),
131115
}
132-
if tt.replRunning {
133-
p.repl.startTime = time.Now()
134-
}
135116
}
136117

137118
err := p.Pause(context.Background())
138119

139-
if tt.expectError {
140-
require.Error(t, err)
141-
assert.Contains(t, err.Error(), tt.errorContains)
142-
}
120+
require.Error(t, err)
121+
assert.Contains(t, err.Error(), tt.errorContains)
143122
})
144123
}
145124
}
146125

147-
func TestResumeCommand_StateValidation(t *testing.T) {
126+
func TestResume_StateValidation(t *testing.T) {
148127
t.Parallel()
149128

150129
tests := []struct {
151130
name string
152131
initialState State
153132
fromFailure bool
154-
expectError bool
155133
errorContains string
156134
}{
157135
{
158136
name: "fails from idle state",
159137
initialState: StateIdle,
160-
fromFailure: false,
161-
expectError: true,
162138
errorContains: "cannot resume",
163139
},
164140
{
165141
name: "fails from running state",
166142
initialState: StateRunning,
167-
fromFailure: false,
168-
expectError: true,
169143
errorContains: "cannot resume",
170144
},
171145
{
172146
name: "fails from failed state without fromFailure flag",
173147
initialState: StateFailed,
174-
fromFailure: false,
175-
expectError: true,
176148
errorContains: "cannot resume",
177149
},
178150
{
179151
name: "fails from finalizing state",
180152
initialState: StateFinalizing,
181-
fromFailure: false,
182-
expectError: true,
183153
errorContains: "cannot resume",
184154
},
185155
{
186156
name: "fails from finalized state",
187157
initialState: StateFinalized,
188-
fromFailure: false,
189-
expectError: true,
190158
errorContains: "cannot resume",
191159
},
192160
{
193161
name: "fails from idle even with fromFailure flag",
194162
initialState: StateIdle,
195163
fromFailure: true,
196-
expectError: true,
197164
errorContains: "cannot resume",
198165
},
199166
{
200167
name: "fails from running even with fromFailure flag",
201168
initialState: StateRunning,
202169
fromFailure: true,
203-
expectError: true,
204170
errorContains: "cannot resume",
205171
},
206172
}
@@ -225,87 +191,73 @@ func TestResumeCommand_StateValidation(t *testing.T) {
225191
ResumeFromFailure: tt.fromFailure,
226192
})
227193

228-
if tt.expectError {
229-
require.Error(t, err)
230-
assert.Contains(t, err.Error(), tt.errorContains)
231-
}
194+
require.Error(t, err)
195+
assert.Contains(t, err.Error(), tt.errorContains)
232196
})
233197
}
234198
}
235199

236-
func TestFinalizeCommand_FailedStateValidation(t *testing.T) {
200+
func TestFinalize_FailsFromFailedStateWithoutIgnoreHistoryLost(t *testing.T) {
237201
t.Parallel()
238202

239-
t.Run("fails from failed state without ignoreHistoryLost", func(t *testing.T) {
240-
t.Parallel()
241-
242-
p := &PCSM{
243-
state: StateFailed,
244-
onStateChanged: func(State) {},
245-
err: ErrOplogHistoryLost,
246-
clone: &Clone{
247-
doneSig: make(chan struct{}),
248-
},
249-
repl: &Repl{
250-
pauseC: make(chan struct{}),
251-
doneSig: make(chan struct{}),
252-
},
253-
}
254-
255-
err := p.Finalize(context.Background(), FinalizeOptions{
256-
IgnoreHistoryLost: false,
257-
})
203+
p := &PCSM{
204+
state: StateFailed,
205+
onStateChanged: func(State) {},
206+
err: ErrOplogHistoryLost,
207+
clone: &Clone{
208+
doneSig: make(chan struct{}),
209+
},
210+
repl: &Repl{
211+
pauseC: make(chan struct{}),
212+
doneSig: make(chan struct{}),
213+
},
214+
}
258215

259-
require.Error(t, err)
260-
assert.Contains(t, err.Error(), "failed state")
216+
err := p.Finalize(context.Background(), FinalizeOptions{
217+
IgnoreHistoryLost: false,
261218
})
219+
220+
require.Error(t, err)
221+
assert.Contains(t, err.Error(), "failed state")
262222
}
263223

264-
func TestResumeFromPaused_ValidatesReplState(t *testing.T) {
224+
func TestResumeFromPaused_FailsWhenReplNotStarted(t *testing.T) {
265225
t.Parallel()
266226

267-
t.Run("fails when repl not started and not resuming from failure", func(t *testing.T) {
268-
t.Parallel()
269-
270-
p := &PCSM{
271-
state: StatePaused,
272-
onStateChanged: func(State) {},
273-
repl: &Repl{
274-
pauseC: make(chan struct{}),
275-
doneSig: make(chan struct{}),
276-
},
277-
}
278-
279-
err := p.Resume(context.Background(), ResumeOptions{
280-
ResumeFromFailure: false,
281-
})
227+
p := &PCSM{
228+
state: StatePaused,
229+
onStateChanged: func(State) {},
230+
repl: &Repl{
231+
pauseC: make(chan struct{}),
232+
doneSig: make(chan struct{}),
233+
},
234+
}
282235

283-
require.Error(t, err)
284-
assert.Contains(t, err.Error(), "replication is not started")
236+
err := p.Resume(context.Background(), ResumeOptions{
237+
ResumeFromFailure: false,
285238
})
239+
240+
require.Error(t, err)
241+
assert.Contains(t, err.Error(), "replication is not started")
286242
}
287243

288-
func TestResumeFromFailed_ValidatesReplState(t *testing.T) {
244+
func TestResumeFromFailed_FailsWhenReplNotPaused(t *testing.T) {
289245
t.Parallel()
290246

291-
t.Run("fails when repl not paused but resuming from failure", func(t *testing.T) {
292-
t.Parallel()
293-
294-
p := &PCSM{
295-
state: StateFailed,
296-
onStateChanged: func(State) {},
297-
repl: &Repl{
298-
pauseC: make(chan struct{}),
299-
doneSig: make(chan struct{}),
300-
startTime: time.Now(),
301-
},
302-
}
303-
304-
err := p.Resume(context.Background(), ResumeOptions{
305-
ResumeFromFailure: true,
306-
})
247+
p := &PCSM{
248+
state: StateFailed,
249+
onStateChanged: func(State) {},
250+
repl: &Repl{
251+
pauseC: make(chan struct{}),
252+
doneSig: make(chan struct{}),
253+
startTime: time.Now(),
254+
},
255+
}
307256

308-
require.Error(t, err)
309-
assert.Contains(t, err.Error(), "replication is not paused")
257+
err := p.Resume(context.Background(), ResumeOptions{
258+
ResumeFromFailure: true,
310259
})
260+
261+
require.Error(t, err)
262+
assert.Contains(t, err.Error(), "replication is not paused")
311263
}

0 commit comments

Comments
 (0)