Manual Test Runbook — I5: Defender → Ticket (via E7)
Owner: Sagar | Time: ~5 min (Part A offline) · +10 min (Part C live) | Cloud: none for Parts A/B · Defender read for Part C
Promotes I5 (
apps/defender-ticketer/) from 🟦 Code Complete → 🟩 Shipped. Parts A/B are offline ($0). Part C collects real Defender alerts and files through the E7 CLI. I5 is the first E7 consumer — Part C proves the adapter end-to-end.
Prerequisites
- Local tooling:
node >= 20,npm - (Part C only)
azauthenticated against a subscription with Defender for Cloud enabled; E7 (apps/ticket-platform/) built; tracker creds in env (e.g.GITHUB_TOKENfor--platform github) - Working directory:
apps/defender-ticketer
Steps
Part A — build, typecheck, unit tests (offline, ~3 min)
bash cd apps/defender-ticketer npm install npm run typecheck npm test
Expected: typecheck clean; 21 tests pass across alerts.test.ts
(normalize both REST + az shapes, severity/status filter, dedupe, summarize),
ticket.test.ts (draft content, dry-run, E7 argv construction + output
parse), collector.test.ts (az invocation + JSON handling).
Part B — offline dry-run on the sample (~2 min, $0)
-
```bash npm run build node dist/index.js --input examples/alerts.sample.json --out-dir ./out
Expected: `dry-run — 2 of 4 alert(s) would be filed (min Medium)`. The 4 sample alerts are High/Active, Medium/InProgress, Low/Active, High/Dismissed → only the first two pass the default filter. Inspect: ```bash cat out/defender-tickets.json # selected=2, breakdown High:1 Medium:1 ls out/issue-*.md # one rendered body per selected alert -
(Negative path) Raise the floor and confirm fewer are selected:
Expected: 1 of 4 (only the High/Active alert).
Part C — live collect + file via E7 (~10 min)
- Build E7 and confirm the CLI is reachable:
( cd ../ticket-platform && npm install && npm run build )
node ../ticket-platform/dist/index.js --platform dry-run --title t \
--body "x" --dedupe-key k --output /tmp/e7.txt && cat /tmp/e7.txt
Expected: /tmp/e7.txt contains ticket_id=…, ticket_url=…,
ticket_updated=… — the contract I5 parses.
- Collect real alerts and file to a test tracker (GitHub Issues shown):
export GITHUB_TOKEN=… # repo-scoped
node dist/index.js \
--platform github --repo <org>/<test-repo> \
--ticket-cmd "node ../ticket-platform/dist/index.js" \
--min-severity High --subscription "$SUB_ID" --out-dir ./out
Expected: one issue per High active alert, labelled defender +
severity:high. Re-run the same command and confirm each outcome is
updated: true in out/defender-tickets.json (idempotent — no duplicates).
- (Optional) Repeat with
--platform jira|linear|adoand the matching passthrough flags (--project-key,--team-id,--project) to exercise the other E7 adapters.
Pass criteria
- Part A — typecheck clean; 21 tests pass
- Part B — sample dry-run selects 2/4 (Medium floor), 1/4 (High floor)
- Part C — a real alert files a ticket via E7; a second run updates it (no dup)
- Generated
defender-tickets.json+issue-*.mdlook correct
Failure mode
A noisy alert source spamming tickets — mitigated by the severity floor + status filter + the per-alert dedupe key (re-runs update one ticket). Documented in the README.
Cost impact
$0 — pure orchestration; read-only az security alert list. No resources created.
Removal path
Delete apps/defender-ticketer/. Nothing external is provisioned.
Sign-Off
| Field | Value |
|---|---|
| Part A (unit tests) | ☐ PASS |
| Part B (offline dry-run) | ☐ PASS |
| Part C (live via E7) | ☐ PASS / ☐ skipped |
| Tester | |
| Date | |
| Result | ☐ PASS |