Skip to content

Commit f37fdcb

Browse files
feat(w21-24kl): batch 11 — integration test for snapshot+migrate pipeline
End-to-end test covering: exit 0, snapshot with ticket_count=3, CREATE events per ticket, STATUS event for in_progress, COMMENT event with special chars, LINK event for deps, idempotent second run. Also fixes missing LINK event emission in _phase_migrate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 993ff60 commit f37fdcb

File tree

6 files changed

+300
-4
lines changed

6 files changed

+300
-4
lines changed

.tickets/.sync-state.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,8 @@
449449
"last_synced": "2026-03-19T18:38:35Z",
450450
"local_hash": "14c516947a151a3db8bdec4010e2fd6e"
451451
},
452-
"last_pull_timestamp": "2026-03-23T06:00:24Z",
453-
"last_sync_commit": "5c9884c9ded790b7df457c4a7ea6d1054d0915f9",
452+
"last_pull_timestamp": "2026-03-23T06:29:49Z",
453+
"last_sync_commit": "993ff600a18027dc19bd0f6f40e6281954886c4d",
454454
"w21-5cqr": {
455455
"jira_hash": "bce29d76f01c58613ee99cb1dd03920d",
456456
"jira_key": "DIG-61",

.tickets/dso-6ye6.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: dso-6ye6
3-
status: in_progress
3+
status: closed
44
deps: [dso-62hs, dso-9trm]
55
links: []
66
created: 2026-03-23T03:58:36Z

.tickets/dso-hjwc.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: dso-hjwc
3-
status: open
3+
status: in_progress
44
deps: [dso-9trm, dso-6ye6]
55
links: []
66
created: 2026-03-23T03:58:57Z
@@ -54,3 +54,29 @@ File to edit: tests/scripts/test-cutover-tickets-migration.sh
5454
- [ ] Integration test passes (exit 0 from test file)
5555
Verify: cd $(git rev-parse --show-toplevel) && bash tests/scripts/test-cutover-tickets-migration.sh 2>&1 | grep -q 'PASSED:' && ! bash tests/scripts/test-cutover-tickets-migration.sh 2>&1 | grep -q 'FAIL.*pipeline'
5656

57+
58+
## Notes
59+
60+
**2026-03-23T06:20:03Z**
61+
62+
CHECKPOINT 1/6: Task context loaded ✓
63+
64+
**2026-03-23T06:20:55Z**
65+
66+
CHECKPOINT 2/6: Code patterns understood ✓
67+
68+
**2026-03-23T06:21:50Z**
69+
70+
CHECKPOINT 3/6: Tests written ✓
71+
72+
**2026-03-23T06:21:50Z**
73+
74+
CHECKPOINT 4/6: Implementation complete ✓
75+
76+
**2026-03-23T06:24:23Z**
77+
78+
CHECKPOINT 5/6: Validation passed ✓
79+
80+
**2026-03-23T06:25:21Z**
81+
82+
CHECKPOINT 6/6: Done ✓ — All 7 integration test assertions pass (AC4+AC5 verified). Added LINK event writing to _phase_migrate to satisfy assertion 6 (deps→LINK events); this was a gap in the dso-6ye6 implementation. Pre-existing RED tests (rollback+error-path, Tests 5+6) remain at 2 failures as before my changes (PASS 64 FAIL 2 vs PASS 50 FAIL 2 before).

.tickets/dso-tmqu.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
id: dso-tmqu
3+
status: open
4+
deps: []
5+
links: []
6+
created: 2026-03-23T06:25:30Z
7+
type: task
8+
priority: 3
9+
assignee: Joe Oakhart
10+
---
11+
# LINK event writing was missing from _phase_migrate — added in dso-hjwc integration test task
12+

plugins/dso/scripts/cutover-tickets-migration.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,26 @@ for i, note_body in enumerate(notes):
539539
comment_filename = f"{ts3}-{comment_uuid}-COMMENT.json"
540540
with open(f"{ticket_dir}/{comment_filename}", "w", encoding="utf-8") as fh:
541541
json.dump(comment_event, fh, ensure_ascii=False)
542+
543+
# Write LINK events for dependencies (deps frontmatter field)
544+
deps = parsed.get("deps", [])
545+
if not isinstance(deps, list):
546+
deps = []
547+
for j, dep_id in enumerate(deps):
548+
ts4 = ts + 2 + len(notes) + j
549+
link_uuid = str(uuid.uuid4())
550+
link_event = {
551+
"timestamp": ts4,
552+
"uuid": link_uuid,
553+
"event_type": "LINK",
554+
"data": {
555+
"relation": "depends_on",
556+
"target": dep_id,
557+
}
558+
}
559+
link_filename = f"{ts4}-{link_uuid}-LINK.json"
560+
with open(f"{ticket_dir}/{link_filename}", "w", encoding="utf-8") as fh:
561+
json.dump(link_event, fh, ensure_ascii=False)
542562
PYEOF
543563

544564
echo "Migrated: $_ticket_id ($_ticket_type)"

tests/scripts/test-cutover-tickets-migration.sh

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,244 @@ fi
10991099
assert_eq "test_phase_migrate_disables_compaction" "true" "$_HAS_COMPACT_DISABLED"
11001100
assert_pass_if_clean "test_phase_migrate_disables_compaction"
11011101

1102+
# =============================================================================
1103+
# Test 19: test_cutover_snapshot_and_migrate_pipeline_end_to_end
1104+
#
1105+
# Integration test: verifies the full snapshot + migrate pipeline runs
1106+
# end-to-end on a populated fixture.
1107+
#
1108+
# Setup:
1109+
# - Temp git repo with initial commit
1110+
# - 3 .tickets/*.md files with different types, statuses, and one with a note
1111+
# containing special characters (dollar sign, ampersand, angle brackets)
1112+
# - One ticket has a dependency (deps frontmatter field) on another
1113+
# - CUTOVER_SNAPSHOT_FILE, CUTOVER_TICKETS_DIR, CUTOVER_TRACKER_DIR all
1114+
# pointed at fixture paths
1115+
#
1116+
# Run: bash cutover-tickets-migration.sh --repo-root=FIXTURE (all phases)
1117+
#
1118+
# Assertions:
1119+
# 1. Exit 0
1120+
# 2. Snapshot file exists and contains ticket_count=3
1121+
# 3. CREATE event exists in tracker for each of the 3 tickets (by old ticket ID)
1122+
# 4. Ticket with non-open status: STATUS event exists
1123+
# 5. Ticket with note: COMMENT event exists and body matches note content
1124+
# 6. Ticket with dep: LINK event exists (or CREATE data contains deps)
1125+
# 7. Idempotency: run again, exit 0, still exactly 1 CREATE event per ticket
1126+
# =============================================================================
1127+
_setup_fixture
1128+
1129+
# Ticket 1: type=epic, status=open (no STATUS event expected), has a dep on ticket 2
1130+
cat > "$_FIXTURE_DIR/.tickets/dso-e2e-t1.md" <<'TICKET_EOF'
1131+
---
1132+
id: dso-e2e-t1
1133+
title: E2E Epic Ticket One
1134+
status: open
1135+
type: epic
1136+
priority: 1
1137+
deps: [dso-e2e-t2]
1138+
---
1139+
# E2E Epic Ticket One
1140+
1141+
This is the epic ticket for the e2e integration test.
1142+
TICKET_EOF
1143+
1144+
# Ticket 2: type=story, status=in_progress (STATUS event expected)
1145+
cat > "$_FIXTURE_DIR/.tickets/dso-e2e-t2.md" <<'TICKET_EOF'
1146+
---
1147+
id: dso-e2e-t2
1148+
title: E2E Story Ticket Two
1149+
status: in_progress
1150+
type: story
1151+
priority: 2
1152+
---
1153+
# E2E Story Ticket Two
1154+
1155+
This story is in progress during the e2e test.
1156+
TICKET_EOF
1157+
1158+
# Ticket 3: type=task, status=open, has a note with special characters
1159+
cat > "$_FIXTURE_DIR/.tickets/dso-e2e-t3.md" <<'TICKET_EOF'
1160+
---
1161+
id: dso-e2e-t3
1162+
title: E2E Task Ticket Three
1163+
status: open
1164+
type: task
1165+
priority: 3
1166+
---
1167+
# E2E Task Ticket Three
1168+
1169+
Body content.
1170+
1171+
## Notes
1172+
1173+
[2026-01-15T10:00:00Z] Fixed issue with $VAR & <template> processing.
1174+
TICKET_EOF
1175+
1176+
git -C "$_FIXTURE_DIR" add .tickets/
1177+
git -C "$_FIXTURE_DIR" commit -q -m "initial commit with 3 e2e tickets"
1178+
1179+
_E2E_SNAPSHOT_FILE="$_FIXTURE_DIR/e2e-snapshot.json"
1180+
_E2E_TRACKER_DIR="$_FIXTURE_DIR/.tickets-tracker"
1181+
_E2E_STATE_FILE="$_FIXTURE_DIR/.cutover-state-e2e.json"
1182+
mkdir -p "$_E2E_TRACKER_DIR"
1183+
_E2E_RC=0
1184+
1185+
CUTOVER_LOG_DIR="$_FIXTURE_LOG_DIR" \
1186+
CUTOVER_STATE_FILE="$_E2E_STATE_FILE" \
1187+
CUTOVER_SNAPSHOT_FILE="$_E2E_SNAPSHOT_FILE" \
1188+
CUTOVER_TICKETS_DIR="$_FIXTURE_DIR/.tickets" \
1189+
CUTOVER_TRACKER_DIR="$_E2E_TRACKER_DIR" \
1190+
bash "$CUTOVER_SCRIPT" --repo-root="$_FIXTURE_DIR" 2>&1 >/dev/null || _E2E_RC=$?
1191+
1192+
_snapshot_fail
1193+
1194+
# Assertion 1: exit 0
1195+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_exit_0" "0" "$_E2E_RC"
1196+
1197+
# Assertion 2a: snapshot file exists
1198+
if [[ -f "$_E2E_SNAPSHOT_FILE" ]]; then
1199+
_E2E_SNAP_EXISTS="true"
1200+
else
1201+
_E2E_SNAP_EXISTS="false"
1202+
fi
1203+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_snapshot_exists" "true" "$_E2E_SNAP_EXISTS"
1204+
1205+
# Assertion 2b: snapshot file contains ticket_count=3
1206+
_E2E_TICKET_COUNT="not_found"
1207+
if [[ -f "$_E2E_SNAPSHOT_FILE" ]]; then
1208+
_E2E_TICKET_COUNT=$(python3 -c "
1209+
import json, sys
1210+
try:
1211+
with open('$_E2E_SNAPSHOT_FILE') as fh:
1212+
data = json.load(fh)
1213+
print(data.get('ticket_count', 'missing'))
1214+
except Exception as e:
1215+
print('error:' + str(e))
1216+
" 2>/dev/null || echo "error")
1217+
fi
1218+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_ticket_count" "3" "$_E2E_TICKET_COUNT"
1219+
1220+
# Assertion 3: CREATE event exists for each of the 3 tickets
1221+
for _e2e_id in dso-e2e-t1 dso-e2e-t2 dso-e2e-t3; do
1222+
_E2E_CREATE_FILE=$(find "$_E2E_TRACKER_DIR" -path "*/${_e2e_id}/*" -name "*-CREATE.json" 2>/dev/null | head -1)
1223+
if [[ -n "$_E2E_CREATE_FILE" ]]; then
1224+
_E2E_HAS_CREATE="true"
1225+
else
1226+
_E2E_HAS_CREATE="false"
1227+
fi
1228+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_create_${_e2e_id}" "true" "$_E2E_HAS_CREATE"
1229+
done
1230+
1231+
# Assertion 4: STATUS event exists for dso-e2e-t2 (status=in_progress, not open)
1232+
_E2E_STATUS_FILE=$(find "$_E2E_TRACKER_DIR" -path "*/dso-e2e-t2/*" -name "*-STATUS.json" 2>/dev/null | head -1)
1233+
if [[ -n "$_E2E_STATUS_FILE" ]]; then
1234+
_E2E_HAS_STATUS="true"
1235+
else
1236+
_E2E_HAS_STATUS="false"
1237+
fi
1238+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_status_event" "true" "$_E2E_HAS_STATUS"
1239+
1240+
# Assertion 5: COMMENT event exists for dso-e2e-t3 (has a note) and body contains the note text
1241+
_E2E_COMMENT_FILE=$(find "$_E2E_TRACKER_DIR" -path "*/dso-e2e-t3/*" -name "*-COMMENT.json" 2>/dev/null | head -1)
1242+
if [[ -n "$_E2E_COMMENT_FILE" ]]; then
1243+
_E2E_HAS_COMMENT="true"
1244+
else
1245+
_E2E_HAS_COMMENT="false"
1246+
fi
1247+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_comment_event" "true" "$_E2E_HAS_COMMENT"
1248+
1249+
_E2E_COMMENT_BODY_OK="false"
1250+
if [[ -n "$_E2E_COMMENT_FILE" ]]; then
1251+
if python3 -c "
1252+
import json, sys
1253+
try:
1254+
with open('$_E2E_COMMENT_FILE') as fh:
1255+
d = json.load(fh)
1256+
body = d.get('body', d.get('data', {}).get('body', ''))
1257+
if '\$VAR' in body and '&' in body and '<template>' in body:
1258+
sys.exit(0)
1259+
except Exception:
1260+
pass
1261+
sys.exit(1)
1262+
" 2>/dev/null; then
1263+
_E2E_COMMENT_BODY_OK="true"
1264+
fi
1265+
fi
1266+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_comment_body" "true" "$_E2E_COMMENT_BODY_OK"
1267+
1268+
# Assertion 6: LINK event exists for dso-e2e-t1 (has deps on dso-e2e-t2)
1269+
# The migrate phase writes a LINK event with relation=depends_on when deps are present.
1270+
# If no separate LINK event is found, also accept the dep stored in the CREATE data.
1271+
_E2E_LINK_FILE=$(find "$_E2E_TRACKER_DIR" -path "*/dso-e2e-t1/*" -name "*-LINK.json" 2>/dev/null | head -1)
1272+
_E2E_HAS_DEP="false"
1273+
if [[ -n "$_E2E_LINK_FILE" ]]; then
1274+
# LINK event found: verify it references dso-e2e-t2
1275+
if python3 -c "
1276+
import json, sys
1277+
try:
1278+
with open('$_E2E_LINK_FILE') as fh:
1279+
d = json.load(fh)
1280+
data = d.get('data', {})
1281+
target = data.get('target', data.get('target_id', ''))
1282+
relation = data.get('relation', data.get('link_type', ''))
1283+
if 'dso-e2e-t2' in target and 'depends' in relation.lower():
1284+
sys.exit(0)
1285+
except Exception:
1286+
pass
1287+
sys.exit(1)
1288+
" 2>/dev/null; then
1289+
_E2E_HAS_DEP="true"
1290+
fi
1291+
fi
1292+
# Fallback: check CREATE event data.deps for the dependency
1293+
if [[ "$_E2E_HAS_DEP" == "false" ]]; then
1294+
_E2E_CREATE_T1=$(find "$_E2E_TRACKER_DIR" -path "*/dso-e2e-t1/*" -name "*-CREATE.json" 2>/dev/null | head -1)
1295+
if [[ -n "$_E2E_CREATE_T1" ]]; then
1296+
if python3 -c "
1297+
import json, sys
1298+
try:
1299+
with open('$_E2E_CREATE_T1') as fh:
1300+
d = json.load(fh)
1301+
deps = d.get('data', {}).get('deps', [])
1302+
if isinstance(deps, list) and 'dso-e2e-t2' in deps:
1303+
sys.exit(0)
1304+
# Also check top-level deps field
1305+
deps2 = d.get('deps', [])
1306+
if isinstance(deps2, list) and 'dso-e2e-t2' in deps2:
1307+
sys.exit(0)
1308+
except Exception:
1309+
pass
1310+
sys.exit(1)
1311+
" 2>/dev/null; then
1312+
_E2E_HAS_DEP="true"
1313+
fi
1314+
fi
1315+
fi
1316+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_link_event" "true" "$_E2E_HAS_DEP"
1317+
1318+
# Assertion 7: Idempotency — run again, exit 0, still exactly 1 CREATE event per ticket
1319+
rm -f "$_E2E_STATE_FILE"
1320+
_E2E_RC2=0
1321+
CUTOVER_LOG_DIR="$_FIXTURE_LOG_DIR" \
1322+
CUTOVER_STATE_FILE="$_E2E_STATE_FILE" \
1323+
CUTOVER_SNAPSHOT_FILE="$_E2E_SNAPSHOT_FILE" \
1324+
CUTOVER_TICKETS_DIR="$_FIXTURE_DIR/.tickets" \
1325+
CUTOVER_TRACKER_DIR="$_E2E_TRACKER_DIR" \
1326+
bash "$CUTOVER_SCRIPT" --repo-root="$_FIXTURE_DIR" 2>&1 >/dev/null || _E2E_RC2=$?
1327+
1328+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_idempotent_exit_0" "0" "$_E2E_RC2"
1329+
1330+
for _e2e_id in dso-e2e-t1 dso-e2e-t2 dso-e2e-t3; do
1331+
_E2E_CREATE_COUNT=$(find "$_E2E_TRACKER_DIR" -path "*/${_e2e_id}/*" -name "*-CREATE.json" 2>/dev/null | wc -l | tr -d ' ')
1332+
assert_eq "test_cutover_snapshot_and_migrate_pipeline_end_to_end_idempotent_create_count_${_e2e_id}" "1" "$_E2E_CREATE_COUNT"
1333+
done
1334+
1335+
assert_pass_if_clean "test_cutover_snapshot_and_migrate_pipeline_end_to_end"
1336+
1337+
rm -rf "$_FIXTURE_DIR"
1338+
unset _FIXTURE_DIR _FIXTURE_LOG_DIR _E2E_SNAPSHOT_FILE _E2E_TRACKER_DIR _E2E_STATE_FILE _E2E_COMMENT_FILE _E2E_LINK_FILE _E2E_CREATE_T1 _e2e_id
1339+
11021340
# =============================================================================
11031341
# =============================================================================
11041342
# Test 5: test_cutover_rollback_committed_uses_revert

0 commit comments

Comments
 (0)