Skip to content

Commit ec2e892

Browse files
committed
test: add integration tests for MCP round-trips
13 tests exercising McpContext through the real kanban CLI binary: - Board CRUD + get nonexistent returns None - Column create, list, update, reorder - Card create, get, move, archive, restore - Card create_full with all optional fields - Sprint create, list, activate, complete, cancel - Sprint update_full with name and dates - Card-sprint assign/unassign - Bulk archive, bulk move - Export/import round-trip Uses locally-built binary via with_kanban_path to test against current code rather than the installed system binary.
1 parent 642abf5 commit ec2e892

File tree

1 file changed

+277
-0
lines changed

1 file changed

+277
-0
lines changed
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
use kanban_domain::KanbanOperations;
2+
use kanban_mcp::context::{CreateCardFullParams, McpContext, SprintUpdateFullParams};
3+
use tempfile::TempDir;
4+
5+
fn kanban_bin() -> String {
6+
let manifest_dir = env!("CARGO_MANIFEST_DIR");
7+
let workspace_root = std::path::Path::new(manifest_dir)
8+
.parent()
9+
.unwrap()
10+
.parent()
11+
.unwrap();
12+
let bin = workspace_root.join("target/debug/kanban");
13+
assert!(
14+
bin.exists(),
15+
"kanban binary not found at {:?}. Run `cargo build --bin kanban` first.",
16+
bin
17+
);
18+
bin.to_string_lossy().to_string()
19+
}
20+
21+
fn setup() -> (McpContext, TempDir) {
22+
let dir = TempDir::new().expect("failed to create temp dir");
23+
let path = dir.path().join("test.kanban");
24+
let path_str = path.to_string_lossy().to_string();
25+
let ctx = McpContext::new(&path_str).with_kanban_path(&kanban_bin());
26+
(ctx, dir)
27+
}
28+
29+
// Board round-trips
30+
31+
#[test]
32+
fn board_create_list_get() {
33+
let (mut ctx, _tmp) = setup();
34+
let board = ctx
35+
.create_board("Test Board".into(), Some("TB".into()))
36+
.unwrap();
37+
assert_eq!(board.name, "Test Board");
38+
39+
let boards = ctx.list_boards().unwrap();
40+
assert_eq!(boards.len(), 1);
41+
assert_eq!(boards[0].id, board.id);
42+
43+
let fetched = ctx.get_board(board.id).unwrap().unwrap();
44+
assert_eq!(fetched.name, "Test Board");
45+
}
46+
47+
#[test]
48+
fn board_get_nonexistent() {
49+
let (ctx, _tmp) = setup();
50+
let id = uuid::Uuid::new_v4();
51+
let result = ctx.get_board(id).unwrap();
52+
assert!(result.is_none());
53+
}
54+
55+
// Column round-trips
56+
57+
#[test]
58+
fn column_create_list_update() {
59+
let (mut ctx, _tmp) = setup();
60+
let board = ctx.create_board("Board".into(), None).unwrap();
61+
let col = ctx
62+
.create_column(board.id, "To Do".into(), None)
63+
.unwrap();
64+
assert_eq!(col.name, "To Do");
65+
66+
let cols = ctx.list_columns(board.id).unwrap();
67+
assert!(cols.iter().any(|c| c.id == col.id));
68+
69+
let updated = ctx
70+
.update_column(
71+
col.id,
72+
kanban_domain::ColumnUpdate {
73+
name: Some("Done".into()),
74+
position: None,
75+
wip_limit: kanban_domain::FieldUpdate::NoChange,
76+
},
77+
)
78+
.unwrap();
79+
assert_eq!(updated.name, "Done");
80+
}
81+
82+
#[test]
83+
fn column_reorder() {
84+
let (mut ctx, _tmp) = setup();
85+
let board = ctx.create_board("Board".into(), None).unwrap();
86+
let _c1 = ctx
87+
.create_column(board.id, "Col A".into(), Some(0))
88+
.unwrap();
89+
let c2 = ctx
90+
.create_column(board.id, "Col B".into(), Some(1))
91+
.unwrap();
92+
let reordered = ctx.reorder_column(c2.id, 0).unwrap();
93+
assert_eq!(reordered.position, 0);
94+
}
95+
96+
// Card round-trips
97+
98+
#[test]
99+
fn card_create_get_move_archive_restore() {
100+
let (mut ctx, _tmp) = setup();
101+
let board = ctx.create_board("Board".into(), None).unwrap();
102+
let col1 = ctx
103+
.create_column(board.id, "To Do".into(), None)
104+
.unwrap();
105+
let col2 = ctx
106+
.create_column(board.id, "Done".into(), None)
107+
.unwrap();
108+
109+
let card = ctx
110+
.create_card(board.id, col1.id, "My Card".into())
111+
.unwrap();
112+
assert_eq!(card.title, "My Card");
113+
114+
let fetched = ctx.get_card(card.id).unwrap().unwrap();
115+
assert_eq!(fetched.id, card.id);
116+
117+
let moved = ctx.move_card(card.id, col2.id, None).unwrap();
118+
assert_eq!(moved.column_id, col2.id);
119+
120+
ctx.archive_card(card.id).unwrap();
121+
let archived = ctx.list_archived_cards().unwrap();
122+
assert!(archived.iter().any(|c| c.card.id == card.id));
123+
124+
let restored = ctx.restore_card(card.id, None).unwrap();
125+
assert_eq!(restored.id, card.id);
126+
}
127+
128+
#[test]
129+
fn create_card_full_with_all_fields() {
130+
let (mut ctx, _tmp) = setup();
131+
let board = ctx.create_board("Board".into(), None).unwrap();
132+
let col = ctx
133+
.create_column(board.id, "To Do".into(), None)
134+
.unwrap();
135+
136+
let card = ctx
137+
.create_card_full(CreateCardFullParams {
138+
board_id: board.id,
139+
column_id: col.id,
140+
title: "Full Card".into(),
141+
description: Some("A description".into()),
142+
priority: Some("high".into()),
143+
points: Some(5),
144+
due_date: Some("2025-12-31".into()),
145+
})
146+
.unwrap();
147+
assert_eq!(card.title, "Full Card");
148+
assert_eq!(card.description.as_deref(), Some("A description"));
149+
}
150+
151+
// Sprint round-trips
152+
153+
#[test]
154+
fn sprint_create_list_activate_complete() {
155+
let (mut ctx, _tmp) = setup();
156+
let board = ctx.create_board("Board".into(), None).unwrap();
157+
158+
let sprint = ctx.create_sprint(board.id, None, None).unwrap();
159+
let sprints = ctx.list_sprints(board.id).unwrap();
160+
assert_eq!(sprints.len(), 1);
161+
assert_eq!(sprints[0].id, sprint.id);
162+
163+
let activated = ctx.activate_sprint(sprint.id, Some(14)).unwrap();
164+
assert_eq!(activated.id, sprint.id);
165+
166+
let completed = ctx.complete_sprint(sprint.id).unwrap();
167+
assert_eq!(completed.id, sprint.id);
168+
}
169+
170+
#[test]
171+
fn sprint_update_full() {
172+
let (mut ctx, _tmp) = setup();
173+
let board = ctx.create_board("Board".into(), None).unwrap();
174+
let sprint = ctx.create_sprint(board.id, None, None).unwrap();
175+
176+
let updated = ctx
177+
.update_sprint_full(SprintUpdateFullParams {
178+
id: sprint.id,
179+
name: Some("Sprint Alpha".into()),
180+
prefix: Some("SA".into()),
181+
card_prefix: None,
182+
start_date: Some("2025-01-01".into()),
183+
end_date: Some("2025-01-15".into()),
184+
clear_start_date: false,
185+
clear_end_date: false,
186+
})
187+
.unwrap();
188+
assert_eq!(updated.id, sprint.id);
189+
}
190+
191+
#[test]
192+
fn sprint_cancel() {
193+
let (mut ctx, _tmp) = setup();
194+
let board = ctx.create_board("Board".into(), None).unwrap();
195+
let sprint = ctx.create_sprint(board.id, None, None).unwrap();
196+
let _ = ctx.activate_sprint(sprint.id, None).unwrap();
197+
let cancelled = ctx.cancel_sprint(sprint.id).unwrap();
198+
assert_eq!(cancelled.id, sprint.id);
199+
}
200+
201+
// Card-sprint assignment
202+
203+
#[test]
204+
fn card_assign_unassign_sprint() {
205+
let (mut ctx, _tmp) = setup();
206+
let board = ctx.create_board("Board".into(), None).unwrap();
207+
let col = ctx
208+
.create_column(board.id, "To Do".into(), None)
209+
.unwrap();
210+
let card = ctx
211+
.create_card(board.id, col.id, "Card".into())
212+
.unwrap();
213+
let sprint = ctx.create_sprint(board.id, None, None).unwrap();
214+
215+
let assigned = ctx.assign_card_to_sprint(card.id, sprint.id).unwrap();
216+
assert_eq!(assigned.sprint_id, Some(sprint.id));
217+
218+
let unassigned = ctx.unassign_card_from_sprint(card.id).unwrap();
219+
assert_eq!(unassigned.sprint_id, None);
220+
}
221+
222+
// Bulk operations
223+
224+
#[test]
225+
fn bulk_archive() {
226+
let (mut ctx, _tmp) = setup();
227+
let board = ctx.create_board("Board".into(), None).unwrap();
228+
let col = ctx
229+
.create_column(board.id, "Col".into(), None)
230+
.unwrap();
231+
let c1 = ctx
232+
.create_card(board.id, col.id, "Card 1".into())
233+
.unwrap();
234+
let c2 = ctx
235+
.create_card(board.id, col.id, "Card 2".into())
236+
.unwrap();
237+
238+
let count = ctx.bulk_archive_cards(vec![c1.id, c2.id]).unwrap();
239+
assert_eq!(count, 2);
240+
}
241+
242+
#[test]
243+
fn bulk_move() {
244+
let (mut ctx, _tmp) = setup();
245+
let board = ctx.create_board("Board".into(), None).unwrap();
246+
let col1 = ctx
247+
.create_column(board.id, "From".into(), None)
248+
.unwrap();
249+
let col2 = ctx
250+
.create_column(board.id, "To".into(), None)
251+
.unwrap();
252+
let c1 = ctx
253+
.create_card(board.id, col1.id, "Card 1".into())
254+
.unwrap();
255+
let c2 = ctx
256+
.create_card(board.id, col1.id, "Card 2".into())
257+
.unwrap();
258+
259+
let count = ctx.bulk_move_cards(vec![c1.id, c2.id], col2.id).unwrap();
260+
assert_eq!(count, 2);
261+
}
262+
263+
// Export/Import round-trip
264+
265+
#[test]
266+
fn export_import_roundtrip() {
267+
let (mut ctx, _tmp) = setup();
268+
let board = ctx.create_board("Export Board".into(), None).unwrap();
269+
let _col = ctx
270+
.create_column(board.id, "Col".into(), None)
271+
.unwrap();
272+
273+
let json = ctx.export_board(Some(board.id)).unwrap();
274+
assert!(json.contains("Export Board"));
275+
276+
ctx.import_board(&json).unwrap();
277+
}

0 commit comments

Comments
 (0)