Skip to content

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) az authenticated against a subscription with Defender for Cloud enabled; E7 (apps/ticket-platform/) built; tracker creds in env (e.g. GITHUB_TOKEN for --platform github)
  • Working directory: apps/defender-ticketer

Steps

Part A — build, typecheck, unit tests (offline, ~3 min)

  1. 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)

  1. ```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
    

  2. (Negative path) Raise the floor and confirm fewer are selected:

node dist/index.js --input examples/alerts.sample.json --min-severity High --out-dir ./out

Expected: 1 of 4 (only the High/Active alert).

Part C — live collect + file via E7 (~10 min)

  1. 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.

  1. 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).

  1. (Optional) Repeat with --platform jira|linear|ado and 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-*.md look 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