feat: Seeded acceptance-ready job data through the live API with determ…
- "scripts/s06-acceptance-data.sh" - "scripts/s06-acceptance-data.test.sh" - "README.md" - ".gsd/KNOWLEDGE.md" - ".gsd/DECISIONS.md" - ".gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md" GSD-Task: S06/T02
This commit is contained in:
+16
-1
@@ -1 +1,16 @@
|
||||
[]
|
||||
[
|
||||
{
|
||||
"id": "73760608",
|
||||
"label": "jobtracker-api",
|
||||
"command": "cd /home/pi/development/JobTracker/.gsd/worktrees/M001 && dotnet run --project JobTrackerApi/JobTrackerApi.csproj",
|
||||
"cwd": "/home/pi/.gsd/projects/a40e97ae9e8f/worktrees/M001",
|
||||
"ownerSessionFile": "/home/pi/.gsd/sessions/--home-pi-development-JobTracker--/2026-03-27T08-00-27-415Z_c787326a-c2a4-40d3-9769-bdd46db73e3f.jsonl",
|
||||
"persistAcrossSessions": false,
|
||||
"startedAt": 1774598519400,
|
||||
"processType": "server",
|
||||
"group": null,
|
||||
"readyPattern": null,
|
||||
"readyPort": 5202,
|
||||
"pid": 1191158
|
||||
}
|
||||
]
|
||||
@@ -18,3 +18,4 @@
|
||||
| D010 | M001/S03 closeout | followup-drafting | How follow-up grounding should be exposed to the workspace | Return explicit follow-up grounding fields (`contextSummary`, `contextSignals`, `threadSubject`, and last-correspondence metadata) from the backend DTO instead of making the React workspace infer them client-side. | The slice needed draft trust, not just draft text. Putting grounding signals in the API contract gives the UI a durable explanation surface, keeps thread/package inference in one place with generation logic, and makes backend/frontend tests assert the same source of truth. | Yes | agent |
|
||||
| D011 | M001/S05 planning | workflow-trust | How S05 should represent daily-loop readiness and next-action state across overview surfaces | Introduce explicit workflow trust/action signals from the backend/UI contract and reuse them across the table, dashboard, reminders, and shared workspace instead of continuing to infer behavior from free-form `followUpReason` strings or raw `notes` presence in each component. | S05 is an end-to-end polish slice where the remaining risk is fragmented trust, not missing subsystems. Centralizing workflow signals avoids heuristic drift between overview surfaces, respects the saved application-answer notes-block constraint, and gives the final integrated regression one source of truth for R010 while preserving the explicit manual-send boundary required by R008. | Yes | agent |
|
||||
| D012 | M001/S05 | workspace-observability | Where linked Gmail thread continuity status should be exposed in the final trust loop | Show linked-thread refresh state directly in the correspondence workspace, including linked thread count and last refresh outcome, instead of hiding continuity feedback inside the Gmail import modal only. | The end-to-end trust loop depends on users being able to verify that already-linked Gmail threads stay current without re-importing. Surfacing continuity state in the main workspace keeps the job timeline trustworthy, supports final UAT, and avoids making thread refresh feel like a hidden one-off import behavior. | Yes | agent |
|
||||
| D013 | M001/S06/T02 | seeding | How acceptance-ready job data is created for S06 live reruns | Seed the acceptance fixture through the live companies/jobapplications/correspondence API plus the dedicated tailored-cv, application-drafts, and followup endpoints, using deterministic company/title/thread/message identifiers for idempotent reruns. | The slice goal is a repeatable live environment check, so seeding through the same HTTP contract the UI uses proves the real backend surface, keeps package/readiness behavior aligned with production code paths, and avoids brittle direct DB mutations or duplicate correspondence on reruns. | Yes | agent |
|
||||
|
||||
@@ -7,3 +7,4 @@
|
||||
- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` is now trustworthy again in this worktree after restoring the missing ASP.NET Core / Identity / xUnit test-project references in `JobTrackerApi.Tests/JobTrackerApi.Tests.csproj`; older task notes that require an isolated Docker harness are stale.
|
||||
- Running `npm --prefix job-tracker-ui start` alone is not enough for browser UAT in this worktree: the frontend calls `http://localhost:5202/api/...`, so without the backend (or a matching CORS/proxy setup) the UI loads but shows empty-state surfaces with `net::ERR_FAILED`/CORS errors instead of real job data.
|
||||
- In this CRA frontend, `react-scripts` resolves the app directory from the current working directory. Run UI tests/builds from `job-tracker-ui/` (for example `cd job-tracker-ui && CI=true ./node_modules/.bin/react-scripts ...`) instead of invoking `npm --prefix job-tracker-ui ...` from the repo root, or `react-scripts` may fail looking for a root-level `package.json`.
|
||||
- The S06 acceptance seed must backdate both `JobApplication.FollowUpAt` and the latest correspondence timestamp past the user’s `AppliedFollowUpDays` threshold; `RulesEngine` computes `Waiting` follow-up from the most recent activity (`DateApplied`, `ResponseDate`, `FollowUpAt`, `FeedbackRequestedAt`, or last correspondence), so a recent reminder date can suppress the intended `workflowSignal.actionKey = "follow-up"` fixture.
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
{"cmd":"plan-slice","params":{"milestoneId":"M001","sliceId":"S06"},"ts":"2026-03-27T07:47:00.102Z","actor":"agent","hash":"ad7ae36d97e9c851","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"}
|
||||
{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S06","taskId":"T01"},"ts":"2026-03-27T07:57:14.999Z","actor":"agent","hash":"7206faf86461a4cd","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"}
|
||||
{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S06","taskId":"T02"},"ts":"2026-03-27T08:09:46.080Z","actor":"agent","hash":"08f3c9c34195dd48","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
- Estimate: 45m
|
||||
- Files: scripts/s06-preflight.sh, README.md, job-tracker-ui/src/api.ts, JobTrackerApi/appsettings.Development.json
|
||||
- Verify: bash scripts/s06-preflight.sh
|
||||
- [ ] **T02: Seed acceptance-ready job data** — Create a seed script that prepares a richer acceptance fixture (job, correspondence, saved package, follow-up readiness) using live API calls.
|
||||
- [x] **T02: Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.** — Create a seed script that prepares a richer acceptance fixture (job, correspondence, saved package, follow-up readiness) using live API calls.
|
||||
- Why: current DB has only 1 low-signal job; acceptance rerun needs actionable overview + workspace state.
|
||||
- Steps:
|
||||
1) Write `scripts/s06-acceptance-data.sh` (bash, executable) that requires `AUTH_TOKEN` env; uses `scripts/s06-preflight.sh` first, then POSTs to `/api/jobapplications` (or PUT existing ID) to create a job with saved package fields, correspondence entry, reminder/follow-up signals, and notes.
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
id: T02
|
||||
parent: S06
|
||||
milestone: M001
|
||||
provides: []
|
||||
requires: []
|
||||
affects: []
|
||||
key_files: ["scripts/s06-acceptance-data.sh", "scripts/s06-acceptance-data.test.sh", "README.md", ".gsd/KNOWLEDGE.md", ".gsd/DECISIONS.md", ".gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md"]
|
||||
key_decisions: ["Seed acceptance data only through the live companies/jobapplications/correspondence API plus the dedicated tailored-cv, application-drafts, and followup endpoints, keyed by deterministic company/title/thread/message identifiers so reruns stay idempotent.", "Backdate both the seeded follow-up date and the latest correspondence timestamp past the active follow-up threshold so the acceptance fixture lands in workflowSignal.actionKey=follow-up instead of review-readiness."]
|
||||
patterns_established: []
|
||||
drill_down_paths: []
|
||||
observability_surfaces: []
|
||||
duration: ""
|
||||
verification_result: "Ran bash scripts/s06-acceptance-data.test.sh against the live API with a valid dev-signed bearer token to confirm missing-token, bad-token, and double-rerun behavior. Ran bash scripts/s06-acceptance-data.sh against the real backend and confirmed seed.result=success, a stable company/job fixture, one correspondence entry, seed.workflow.action=follow-up, seed.readiness.level=Ready, and seed.reminders=Waiting 14d. Verified README.md contains the acceptance-data runbook markers and token guidance."
|
||||
completed_at: 2026-03-27T08:09:46.052Z
|
||||
blocker_discovered: false
|
||||
---
|
||||
|
||||
# T02: Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.
|
||||
|
||||
> Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.
|
||||
|
||||
## What Happened
|
||||
---
|
||||
id: T02
|
||||
parent: S06
|
||||
milestone: M001
|
||||
key_files:
|
||||
- scripts/s06-acceptance-data.sh
|
||||
- scripts/s06-acceptance-data.test.sh
|
||||
- README.md
|
||||
- .gsd/KNOWLEDGE.md
|
||||
- .gsd/DECISIONS.md
|
||||
- .gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md
|
||||
key_decisions:
|
||||
- Seed acceptance data only through the live companies/jobapplications/correspondence API plus the dedicated tailored-cv, application-drafts, and followup endpoints, keyed by deterministic company/title/thread/message identifiers so reruns stay idempotent.
|
||||
- Backdate both the seeded follow-up date and the latest correspondence timestamp past the active follow-up threshold so the acceptance fixture lands in workflowSignal.actionKey=follow-up instead of review-readiness.
|
||||
duration: ""
|
||||
verification_result: passed
|
||||
completed_at: 2026-03-27T08:09:46.053Z
|
||||
blocker_discovered: false
|
||||
---
|
||||
|
||||
# T02: Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.
|
||||
|
||||
**Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.**
|
||||
|
||||
## What Happened
|
||||
|
||||
Implemented scripts/s06-acceptance-data.sh as an executable live seed flow that requires AUTH_TOKEN, runs the existing S06 preflight first, then creates or reuses a deterministic acceptance company and job via the real API, updates recruiter metadata, saves tailored CV and application package material through the dedicated endpoints, schedules an overdue follow-up, and maintains exactly one deterministic correspondence entry for the linked recruiter thread. Added scripts/s06-acceptance-data.test.sh to verify the missing-token and bad-token failure modes plus authenticated idempotent reruns, updated README.md with the seed command and token guidance, recorded the waiting-threshold gotcha in .gsd/KNOWLEDGE.md, and saved D013 in .gsd/DECISIONS.md for the live-API seeding pattern. After correcting the seeded activity dates to cross the real 14-day RulesEngine threshold, the live fixture now reports workflowSignal.actionKey=follow-up and Waiting 14d reminder output.
|
||||
|
||||
## Verification
|
||||
|
||||
Ran bash scripts/s06-acceptance-data.test.sh against the live API with a valid dev-signed bearer token to confirm missing-token, bad-token, and double-rerun behavior. Ran bash scripts/s06-acceptance-data.sh against the real backend and confirmed seed.result=success, a stable company/job fixture, one correspondence entry, seed.workflow.action=follow-up, seed.readiness.level=Ready, and seed.reminders=Waiting 14d. Verified README.md contains the acceptance-data runbook markers and token guidance.
|
||||
|
||||
## Verification Evidence
|
||||
|
||||
| # | Command | Exit Code | Verdict | Duration |
|
||||
|---|---------|-----------|---------|----------|
|
||||
| 1 | `bash scripts/s06-acceptance-data.test.sh` | 0 | ✅ pass | 3885ms |
|
||||
| 2 | `bash scripts/s06-acceptance-data.sh` | 0 | ✅ pass | 1831ms |
|
||||
| 3 | `python3 README acceptance-data guidance check` | 0 | ✅ pass | 24ms |
|
||||
|
||||
|
||||
## Deviations
|
||||
|
||||
Added scripts/s06-acceptance-data.test.sh and recorded one knowledge/decision entry beyond the plan’s expected output files so the negative-path and idempotence contract is executable and the follow-up-threshold gotcha is preserved for downstream work.
|
||||
|
||||
## Known Issues
|
||||
|
||||
The local seeded admin password from JobTrackerApi/appsettings.Development.json does not authenticate against this current DB snapshot, so the README and script comments document token retrieval in terms of the real local account or an already-authenticated browser session. Verification used a valid dev-signed JWT against the live API contract, which is sufficient for these user-scoped endpoints but does not prove the placeholder login credentials themselves.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `scripts/s06-acceptance-data.sh`
|
||||
- `scripts/s06-acceptance-data.test.sh`
|
||||
- `README.md`
|
||||
- `.gsd/KNOWLEDGE.md`
|
||||
- `.gsd/DECISIONS.md`
|
||||
- `.gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md`
|
||||
|
||||
|
||||
## Deviations
|
||||
Added scripts/s06-acceptance-data.test.sh and recorded one knowledge/decision entry beyond the plan’s expected output files so the negative-path and idempotence contract is executable and the follow-up-threshold gotcha is preserved for downstream work.
|
||||
|
||||
## Known Issues
|
||||
The local seeded admin password from JobTrackerApi/appsettings.Development.json does not authenticate against this current DB snapshot, so the README and script comments document token retrieval in terms of the real local account or an already-authenticated browser session. Verification used a valid dev-signed JWT against the live API contract, which is sufficient for these user-scoped endpoints but does not prove the placeholder login credentials themselves.
|
||||
+67
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"exported_at": "2026-03-27T07:57:14.998Z",
|
||||
"exported_at": "2026-03-27T08:09:46.079Z",
|
||||
"milestones": [
|
||||
{
|
||||
"id": "M001",
|
||||
@@ -613,19 +613,29 @@
|
||||
"milestone_id": "M001",
|
||||
"slice_id": "S06",
|
||||
"id": "T02",
|
||||
"title": "Seed acceptance-ready job data",
|
||||
"status": "pending",
|
||||
"one_liner": "",
|
||||
"narrative": "",
|
||||
"verification_result": "",
|
||||
"title": "Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.",
|
||||
"status": "complete",
|
||||
"one_liner": "Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.",
|
||||
"narrative": "Implemented scripts/s06-acceptance-data.sh as an executable live seed flow that requires AUTH_TOKEN, runs the existing S06 preflight first, then creates or reuses a deterministic acceptance company and job via the real API, updates recruiter metadata, saves tailored CV and application package material through the dedicated endpoints, schedules an overdue follow-up, and maintains exactly one deterministic correspondence entry for the linked recruiter thread. Added scripts/s06-acceptance-data.test.sh to verify the missing-token and bad-token failure modes plus authenticated idempotent reruns, updated README.md with the seed command and token guidance, recorded the waiting-threshold gotcha in .gsd/KNOWLEDGE.md, and saved D013 in .gsd/DECISIONS.md for the live-API seeding pattern. After correcting the seeded activity dates to cross the real 14-day RulesEngine threshold, the live fixture now reports workflowSignal.actionKey=follow-up and Waiting 14d reminder output.",
|
||||
"verification_result": "Ran bash scripts/s06-acceptance-data.test.sh against the live API with a valid dev-signed bearer token to confirm missing-token, bad-token, and double-rerun behavior. Ran bash scripts/s06-acceptance-data.sh against the real backend and confirmed seed.result=success, a stable company/job fixture, one correspondence entry, seed.workflow.action=follow-up, seed.readiness.level=Ready, and seed.reminders=Waiting 14d. Verified README.md contains the acceptance-data runbook markers and token guidance.",
|
||||
"duration": "",
|
||||
"completed_at": null,
|
||||
"completed_at": "2026-03-27T08:09:46.052Z",
|
||||
"blocker_discovered": false,
|
||||
"deviations": "",
|
||||
"known_issues": "",
|
||||
"key_files": [],
|
||||
"key_decisions": [],
|
||||
"full_summary_md": "",
|
||||
"deviations": "Added scripts/s06-acceptance-data.test.sh and recorded one knowledge/decision entry beyond the plan’s expected output files so the negative-path and idempotence contract is executable and the follow-up-threshold gotcha is preserved for downstream work.",
|
||||
"known_issues": "The local seeded admin password from JobTrackerApi/appsettings.Development.json does not authenticate against this current DB snapshot, so the README and script comments document token retrieval in terms of the real local account or an already-authenticated browser session. Verification used a valid dev-signed JWT against the live API contract, which is sufficient for these user-scoped endpoints but does not prove the placeholder login credentials themselves.",
|
||||
"key_files": [
|
||||
"scripts/s06-acceptance-data.sh",
|
||||
"scripts/s06-acceptance-data.test.sh",
|
||||
"README.md",
|
||||
".gsd/KNOWLEDGE.md",
|
||||
".gsd/DECISIONS.md",
|
||||
".gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md"
|
||||
],
|
||||
"key_decisions": [
|
||||
"Seed acceptance data only through the live companies/jobapplications/correspondence API plus the dedicated tailored-cv, application-drafts, and followup endpoints, keyed by deterministic company/title/thread/message identifiers so reruns stay idempotent.",
|
||||
"Backdate both the seeded follow-up date and the latest correspondence timestamp past the active follow-up threshold so the acceptance fixture lands in workflowSignal.actionKey=follow-up instead of review-readiness."
|
||||
],
|
||||
"full_summary_md": "---\nid: T02\nparent: S06\nmilestone: M001\nkey_files:\n - scripts/s06-acceptance-data.sh\n - scripts/s06-acceptance-data.test.sh\n - README.md\n - .gsd/KNOWLEDGE.md\n - .gsd/DECISIONS.md\n - .gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md\nkey_decisions:\n - Seed acceptance data only through the live companies/jobapplications/correspondence API plus the dedicated tailored-cv, application-drafts, and followup endpoints, keyed by deterministic company/title/thread/message identifiers so reruns stay idempotent.\n - Backdate both the seeded follow-up date and the latest correspondence timestamp past the active follow-up threshold so the acceptance fixture lands in workflowSignal.actionKey=follow-up instead of review-readiness.\nduration: \"\"\nverification_result: passed\ncompleted_at: 2026-03-27T08:09:46.053Z\nblocker_discovered: false\n---\n\n# T02: Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.\n\n**Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.**\n\n## What Happened\n\nImplemented scripts/s06-acceptance-data.sh as an executable live seed flow that requires AUTH_TOKEN, runs the existing S06 preflight first, then creates or reuses a deterministic acceptance company and job via the real API, updates recruiter metadata, saves tailored CV and application package material through the dedicated endpoints, schedules an overdue follow-up, and maintains exactly one deterministic correspondence entry for the linked recruiter thread. Added scripts/s06-acceptance-data.test.sh to verify the missing-token and bad-token failure modes plus authenticated idempotent reruns, updated README.md with the seed command and token guidance, recorded the waiting-threshold gotcha in .gsd/KNOWLEDGE.md, and saved D013 in .gsd/DECISIONS.md for the live-API seeding pattern. After correcting the seeded activity dates to cross the real 14-day RulesEngine threshold, the live fixture now reports workflowSignal.actionKey=follow-up and Waiting 14d reminder output.\n\n## Verification\n\nRan bash scripts/s06-acceptance-data.test.sh against the live API with a valid dev-signed bearer token to confirm missing-token, bad-token, and double-rerun behavior. Ran bash scripts/s06-acceptance-data.sh against the real backend and confirmed seed.result=success, a stable company/job fixture, one correspondence entry, seed.workflow.action=follow-up, seed.readiness.level=Ready, and seed.reminders=Waiting 14d. Verified README.md contains the acceptance-data runbook markers and token guidance.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `bash scripts/s06-acceptance-data.test.sh` | 0 | ✅ pass | 3885ms |\n| 2 | `bash scripts/s06-acceptance-data.sh` | 0 | ✅ pass | 1831ms |\n| 3 | `python3 README acceptance-data guidance check` | 0 | ✅ pass | 24ms |\n\n\n## Deviations\n\nAdded scripts/s06-acceptance-data.test.sh and recorded one knowledge/decision entry beyond the plan’s expected output files so the negative-path and idempotence contract is executable and the follow-up-threshold gotcha is preserved for downstream work.\n\n## Known Issues\n\nThe local seeded admin password from JobTrackerApi/appsettings.Development.json does not authenticate against this current DB snapshot, so the README and script comments document token retrieval in terms of the real local account or an already-authenticated browser session. Verification used a valid dev-signed JWT against the live API contract, which is sufficient for these user-scoped endpoints but does not prove the placeholder login credentials themselves.\n\n## Files Created/Modified\n\n- `scripts/s06-acceptance-data.sh`\n- `scripts/s06-acceptance-data.test.sh`\n- `README.md`\n- `.gsd/KNOWLEDGE.md`\n- `.gsd/DECISIONS.md`\n- `.gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md`\n",
|
||||
"description": "Create a seed script that prepares a richer acceptance fixture (job, correspondence, saved package, follow-up readiness) using live API calls.\n- Why: current DB has only 1 low-signal job; acceptance rerun needs actionable overview + workspace state.\n- Steps:\n 1) Write `scripts/s06-acceptance-data.sh` (bash, executable) that requires `AUTH_TOKEN` env; uses `scripts/s06-preflight.sh` first, then POSTs to `/api/jobapplications` (or PUT existing ID) to create a job with saved package fields, correspondence entry, reminder/follow-up signals, and notes.\n 2) Add curl helpers for adding correspondence (`/api/correspondence/{jobId}` or equivalent), saving package material, and setting workflow/readiness if needed; use deterministic titles so rerun is idempotent (update if exists).\n 3) Emit a short summary of created/updated IDs so the acceptance run can target them; avoid logging token.\n 4) Document any manual token retrieval step in script comments.\n- Failure Modes (Q5): missing AUTH_TOKEN → fail with guidance; 401/403 → explain token issue; 5xx → print response and fail; malformed response → show body and fail.\n- Load Profile (Q6): few API calls; minimal DB impact.\n- Negative Tests (Q7): run without AUTH_TOKEN (expect failure); rerun twice (should succeed idempotently); simulate 401 by bad token (expect clear message).\n- Must-haves: script seeds at least one job with saved package + correspondence + follow-up readiness; outputs job id for UAT; uses preflight.\n- Verification: `bash scripts/s06-acceptance-data.sh`",
|
||||
"estimate": "1h",
|
||||
"files": [
|
||||
@@ -835,6 +845,18 @@
|
||||
"revisable": "Yes",
|
||||
"made_by": "agent",
|
||||
"superseded_by": null
|
||||
},
|
||||
{
|
||||
"seq": 13,
|
||||
"id": "D013",
|
||||
"when_context": "M001/S06/T02",
|
||||
"scope": "seeding",
|
||||
"decision": "How acceptance-ready job data is created for S06 live reruns",
|
||||
"choice": "Seed the acceptance fixture through the live companies/jobapplications/correspondence API plus the dedicated tailored-cv, application-drafts, and followup endpoints, using deterministic company/title/thread/message identifiers for idempotent reruns.",
|
||||
"rationale": "The slice goal is a repeatable live environment check, so seeding through the same HTTP contract the UI uses proves the real backend surface, keeps package/readiness behavior aligned with production code paths, and avoids brittle direct DB mutations or duplicate correspondence on reruns.",
|
||||
"revisable": "Yes",
|
||||
"made_by": "agent",
|
||||
"superseded_by": null
|
||||
}
|
||||
],
|
||||
"verification_evidence": [
|
||||
@@ -870,6 +892,39 @@
|
||||
"verdict": "✅ pass",
|
||||
"duration_ms": 0,
|
||||
"created_at": "2026-03-27T07:57:14.982Z"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"task_id": "T02",
|
||||
"slice_id": "S06",
|
||||
"milestone_id": "M001",
|
||||
"command": "bash scripts/s06-acceptance-data.test.sh",
|
||||
"exit_code": 0,
|
||||
"verdict": "✅ pass",
|
||||
"duration_ms": 3885,
|
||||
"created_at": "2026-03-27T08:09:46.052Z"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"task_id": "T02",
|
||||
"slice_id": "S06",
|
||||
"milestone_id": "M001",
|
||||
"command": "bash scripts/s06-acceptance-data.sh",
|
||||
"exit_code": 0,
|
||||
"verdict": "✅ pass",
|
||||
"duration_ms": 1831,
|
||||
"created_at": "2026-03-27T08:09:46.052Z"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"task_id": "T02",
|
||||
"slice_id": "S06",
|
||||
"milestone_id": "M001",
|
||||
"command": "python3 README acceptance-data guidance check",
|
||||
"exit_code": 0,
|
||||
"verdict": "✅ pass",
|
||||
"duration_ms": 24,
|
||||
"created_at": "2026-03-27T08:09:46.052Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -78,6 +78,19 @@ bash scripts/s06-preflight.sh
|
||||
|
||||
To obtain a local admin token in dev, log in against the API with the seeded admin email/password from `JobTrackerApi/appsettings.Development.json` (or your environment override) via `POST /api/auth/login`, then export only the returned access token. The script never prints token values. Use `API_BASE` if your API is not on the default dev port.
|
||||
|
||||
### Seed acceptance-ready data
|
||||
|
||||
After preflight passes and you have a bearer token, seed one deterministic acceptance fixture for the `/jobs` → workspace → follow-up → dashboard/reminders rerun:
|
||||
|
||||
```bash
|
||||
export AUTH_TOKEN="<bearer token>"
|
||||
bash scripts/s06-acceptance-data.sh
|
||||
```
|
||||
|
||||
The script reuses `scripts/s06-preflight.sh`, creates or reuses the acceptance company/job, saves tailored package material, ensures one deterministic recruiter-thread correspondence entry, schedules follow-up readiness, and prints the seeded ids/readiness summary without echoing the token.
|
||||
|
||||
If the placeholder development password no longer matches the local DB, use the real account for this environment or a bearer token from an already-authenticated local browser session.
|
||||
|
||||
### 2) Run the UI
|
||||
|
||||
```bash
|
||||
|
||||
Executable
+294
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Seed one deterministic acceptance-ready job into the live API.
|
||||
#
|
||||
# This script requires AUTH_TOKEN because local dev auth is enabled in this milestone.
|
||||
# Retrieve a bearer token manually before running, for example by logging into the local
|
||||
# API via POST /api/auth/login with the active local account for this environment, then:
|
||||
# export AUTH_TOKEN="<bearer token>"
|
||||
# bash scripts/s06-acceptance-data.sh
|
||||
#
|
||||
# If the placeholder dev credentials in appsettings.Development.json do not match the
|
||||
# current database, use the real seeded account for this environment or a bearer token
|
||||
# captured from an already-authenticated local browser session. The script never prints
|
||||
# token values.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
API_BASE="${API_BASE:-http://localhost:5202/api}"
|
||||
AUTH_TOKEN="${AUTH_TOKEN:-}"
|
||||
CURL_TIMEOUT="${CURL_TIMEOUT:-20}"
|
||||
CONNECT_TIMEOUT="${CONNECT_TIMEOUT:-5}"
|
||||
COMPANY_NAME="S06 Acceptance Labs"
|
||||
JOB_TITLE="S06 Acceptance Backend Engineer"
|
||||
JOB_URL="https://example.invalid/jobs/s06-acceptance-backend-engineer"
|
||||
THREAD_ID="s06-acceptance-thread"
|
||||
MESSAGE_ID="s06-acceptance-message-1"
|
||||
FOLLOW_UP_AT="2026-03-10T09:00:00Z"
|
||||
DATE_APPLIED="2026-02-28T10:00:00Z"
|
||||
CORRESPONDENCE_AT="2026-03-09T11:30:00Z"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
note() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf 'ERROR: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ -z "$AUTH_TOKEN" ]]; then
|
||||
fail "AUTH_TOKEN is required. Export a bearer token first, then rerun."
|
||||
fi
|
||||
|
||||
validate_json() {
|
||||
local file="$1"
|
||||
if ! python3 - "$file" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
with open(sys.argv[1], 'r', encoding='utf-8') as fh:
|
||||
json.load(fh)
|
||||
PY
|
||||
then
|
||||
printf 'Raw response body:\n' >&2
|
||||
cat "$file" >&2
|
||||
printf '\n' >&2
|
||||
fail "Malformed JSON returned by API."
|
||||
fi
|
||||
}
|
||||
|
||||
request() {
|
||||
local method="$1"
|
||||
local name="$2"
|
||||
local url="$3"
|
||||
local body_file="$4"
|
||||
local expect_json="$5"
|
||||
local data_file="${6:-}"
|
||||
local err_file="$TMP_DIR/${name// /-}.curl.err"
|
||||
local status_file="$TMP_DIR/${name// /-}.status"
|
||||
: >"$err_file"
|
||||
|
||||
local -a curl_args=(
|
||||
-sS
|
||||
--connect-timeout "$CONNECT_TIMEOUT"
|
||||
--max-time "$CURL_TIMEOUT"
|
||||
-X "$method"
|
||||
-H 'Accept: application/json'
|
||||
-H "Authorization: Bearer ${AUTH_TOKEN}"
|
||||
-o "$body_file"
|
||||
-w '%{http_code}'
|
||||
)
|
||||
|
||||
if [[ -n "$data_file" ]]; then
|
||||
curl_args+=(-H 'Content-Type: application/json' --data-binary "@$data_file")
|
||||
fi
|
||||
|
||||
set +e
|
||||
curl "${curl_args[@]}" "$url" >"$status_file" 2>"$err_file"
|
||||
local curl_exit=$?
|
||||
set -e
|
||||
|
||||
local status='000'
|
||||
if [[ -s "$status_file" ]]; then
|
||||
status="$(tr -d '\r\n' <"$status_file")"
|
||||
fi
|
||||
|
||||
if [[ $curl_exit -ne 0 ]]; then
|
||||
case "$curl_exit:$status" in
|
||||
7:*|28:*|52:*|56:*|6:*)
|
||||
printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2
|
||||
fail "Cannot reach ${url}. Start the API and rerun."
|
||||
;;
|
||||
22:401|22:403)
|
||||
printf 'Response body:\n' >&2
|
||||
cat "$body_file" >&2 || true
|
||||
printf '\n' >&2
|
||||
fail "AUTH_TOKEN appears invalid or lacks access for ${name}."
|
||||
;;
|
||||
22:5*)
|
||||
printf 'Response body:\n' >&2
|
||||
cat "$body_file" >&2 || true
|
||||
printf '\n' >&2
|
||||
fail "${name} failed with server error HTTP ${status}."
|
||||
;;
|
||||
22:*)
|
||||
printf 'Response body:\n' >&2
|
||||
cat "$body_file" >&2 || true
|
||||
printf '\n' >&2
|
||||
fail "${name} failed with HTTP ${status}."
|
||||
;;
|
||||
*)
|
||||
printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2
|
||||
fail "${name} failed with curl exit ${curl_exit}."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ "$status" == 401 || "$status" == 403 ]]; then
|
||||
printf 'Response body:\n' >&2
|
||||
cat "$body_file" >&2 || true
|
||||
printf '\n' >&2
|
||||
fail "AUTH_TOKEN appears invalid or lacks access for ${name}."
|
||||
fi
|
||||
|
||||
if [[ "$status" =~ ^5 ]]; then
|
||||
printf 'Response body:\n' >&2
|
||||
cat "$body_file" >&2 || true
|
||||
printf '\n' >&2
|
||||
fail "${name} failed with server error HTTP ${status}."
|
||||
fi
|
||||
|
||||
if [[ "$expect_json" == "json" ]]; then
|
||||
validate_json "$body_file"
|
||||
fi
|
||||
}
|
||||
|
||||
json_extract() {
|
||||
local file="$1"
|
||||
local expression="$2"
|
||||
python3 - "$file" "$expression" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
file_path, expression = sys.argv[1], sys.argv[2]
|
||||
with open(file_path, 'r', encoding='utf-8') as fh:
|
||||
data = json.load(fh)
|
||||
value = eval(expression, {'__builtins__': {'next': next, 'len': len}}, {'data': data})
|
||||
if value is None:
|
||||
print('')
|
||||
elif isinstance(value, bool):
|
||||
print('true' if value else 'false')
|
||||
elif isinstance(value, (dict, list)):
|
||||
print(json.dumps(value, separators=(',', ':')))
|
||||
else:
|
||||
print(value)
|
||||
PY
|
||||
}
|
||||
|
||||
json_string() {
|
||||
python3 - "$1" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
print(json.dumps(sys.argv[1]))
|
||||
PY
|
||||
}
|
||||
|
||||
write_json() {
|
||||
local path="$1"
|
||||
shift
|
||||
printf '%s\n' "$*" >"$path"
|
||||
}
|
||||
|
||||
urlencode() {
|
||||
python3 - "$1" <<'PY'
|
||||
import sys
|
||||
import urllib.parse
|
||||
print(urllib.parse.quote(sys.argv[1]))
|
||||
PY
|
||||
}
|
||||
|
||||
note "Running preflight before seeding acceptance data."
|
||||
bash "$SCRIPT_DIR/s06-preflight.sh" >/dev/null
|
||||
|
||||
company_body="$TMP_DIR/company.json"
|
||||
write_json "$company_body" "{\"name\":$(json_string "$COMPANY_NAME"),\"location\":$(json_string "Oslo, Norway"),\"source\":$(json_string "LinkedIn")}"
|
||||
company_response="$TMP_DIR/company-response.json"
|
||||
request POST "company create" "$API_BASE/companies" "$company_response" json "$company_body"
|
||||
company_id="$(json_extract "$company_response" 'data["id"]')"
|
||||
[[ -n "$company_id" ]] || fail "Company create response did not include an id."
|
||||
|
||||
company_update_body="$TMP_DIR/company-update.json"
|
||||
write_json "$company_update_body" "{\"name\":$(json_string "$COMPANY_NAME"),\"location\":$(json_string "Oslo, Norway"),\"source\":$(json_string "LinkedIn"),\"recruiterName\":$(json_string "Maria Recruiter"),\"recruiterEmail\":$(json_string "maria.recruiter@example.invalid"),\"recruiterLinkedIn\":$(json_string "https://www.linkedin.com/in/maria-recruiter"),\"lastContactedAt\":$(json_string "$CORRESPONDENCE_AT"),\"nextContactAt\":$(json_string "$FOLLOW_UP_AT"),\"pipelineStage\":$(json_string "Follow-up ready")}"
|
||||
company_update_response="$TMP_DIR/company-update-response.json"
|
||||
request PUT "company update" "$API_BASE/companies/$company_id" "$company_update_response" json "$company_update_body"
|
||||
|
||||
jobs_lookup="$TMP_DIR/jobs-lookup.json"
|
||||
encoded_title="$(urlencode "$JOB_TITLE")"
|
||||
request GET "job lookup" "$API_BASE/jobapplications?page=1&pageSize=15&q=$encoded_title" "$jobs_lookup" json
|
||||
job_id="$(json_extract "$jobs_lookup" 'next((item["id"] for item in data.get("items", []) if item.get("jobTitle") == "S06 Acceptance Backend Engineer" and ((item.get("company") or {}).get("name") == "S06 Acceptance Labs")), "")')"
|
||||
job_action="updated"
|
||||
|
||||
create_job_body="$TMP_DIR/create-job.json"
|
||||
write_json "$create_job_body" "{\"jobTitle\":$(json_string "$JOB_TITLE"),\"companyId\":$company_id,\"status\":$(json_string "Waiting"),\"location\":$(json_string "Oslo / Hybrid"),\"salary\":$(json_string "NOK 900000"),\"nextAction\":$(json_string "Review saved package and send a manual recruiter follow-up from the linked thread."),\"followUpAt\":$(json_string "$FOLLOW_UP_AT"),\"notes\":$(json_string $'Acceptance seed notes for S06.\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nSaved acceptance application answer with concrete API and workflow evidence.\n<<<END_APPLICATION_ANSWER_DRAFT>>>'),\"description\":$(json_string "Lead the backend of an individual-first job tracking platform, maintain recruiter-thread continuity, and keep daily dashboard reminders trustworthy."),\"translatedDescription\":$(json_string "Backend role focused on workflow trust signals, saved application package reuse, and manual-send follow-up discipline."),\"descriptionLanguage\":$(json_string "en"),\"tags\":$(json_string "ASP.NET Core, React, SQLite, Workflow, Follow-up"),\"deadline\":$(json_string "2026-04-10T12:00:00Z"),\"coverLetterText\":$(json_string "Saved acceptance cover letter that references workflow trust signals, recruiter continuity, and measured backend delivery."),\"jobUrl\":$(json_string "$JOB_URL"),\"dateApplied\":$(json_string "$DATE_APPLIED"),\"feedbackRequestedAt\":null,\"hasResume\":true,\"hasCoverLetter\":true,\"hasPortfolio\":true,\"hasOtherAttachment\":false}"
|
||||
|
||||
if [[ -z "$job_id" ]]; then
|
||||
create_job_response="$TMP_DIR/create-job-response.json"
|
||||
request POST "job create" "$API_BASE/jobapplications" "$create_job_response" json "$create_job_body"
|
||||
job_id="$(json_extract "$create_job_response" 'data["id"]')"
|
||||
job_action="created"
|
||||
fi
|
||||
[[ -n "$job_id" ]] || fail "Job create/update flow did not yield a job id."
|
||||
|
||||
update_job_body="$TMP_DIR/update-job.json"
|
||||
write_json "$update_job_body" "{\"jobTitle\":$(json_string "$JOB_TITLE"),\"companyId\":$company_id,\"status\":$(json_string "Waiting"),\"responseReceived\":false,\"responseDate\":null,\"location\":$(json_string "Oslo / Hybrid"),\"salary\":$(json_string "NOK 900000"),\"nextAction\":$(json_string "Review saved package and send a manual recruiter follow-up from the linked thread."),\"followUpAt\":$(json_string "$FOLLOW_UP_AT"),\"hasResume\":true,\"hasCoverLetter\":true,\"hasPortfolio\":true,\"hasOtherAttachment\":false,\"notes\":$(json_string $'Acceptance seed notes for S06.\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nSaved acceptance application answer with concrete API and workflow evidence.\n<<<END_APPLICATION_ANSWER_DRAFT>>>'),\"description\":$(json_string "Lead the backend of an individual-first job tracking platform, maintain recruiter-thread continuity, and keep daily dashboard reminders trustworthy."),\"translatedDescription\":$(json_string "Backend role focused on workflow trust signals, saved application package reuse, and manual-send follow-up discipline."),\"descriptionLanguage\":$(json_string "en"),\"tags\":$(json_string "ASP.NET Core, React, SQLite, Workflow, Follow-up"),\"deadline\":$(json_string "2026-04-10T12:00:00Z"),\"coverLetterText\":$(json_string "Saved acceptance cover letter that references workflow trust signals, recruiter continuity, and measured backend delivery."),\"jobUrl\":$(json_string "$JOB_URL"),\"dateApplied\":$(json_string "$DATE_APPLIED"),\"feedbackRequestedAt\":null,\"statusChangedAt\":null}"
|
||||
update_job_response="$TMP_DIR/update-job-response.json"
|
||||
request PUT "job update" "$API_BASE/jobapplications/$job_id" "$update_job_response" empty "$update_job_body"
|
||||
|
||||
save_cv_body="$TMP_DIR/save-cv.json"
|
||||
write_json "$save_cv_body" "{\"tailoredCvText\":$(json_string "Saved acceptance tailored CV highlighting ASP.NET Core delivery, workflow trust signals, recruiter-thread continuity, and dashboard reminder ownership.")}"
|
||||
save_cv_response="$TMP_DIR/save-cv-response.json"
|
||||
request PUT "tailored CV save" "$API_BASE/jobapplications/$job_id/tailored-cv" "$save_cv_response" empty "$save_cv_body"
|
||||
|
||||
save_drafts_body="$TMP_DIR/save-drafts.json"
|
||||
write_json "$save_drafts_body" "{\"coverLetterText\":$(json_string "Saved acceptance cover letter that references workflow trust signals, recruiter continuity, and measured backend delivery."),\"notes\":$(json_string $'Acceptance seed notes for S06.\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nSaved acceptance application answer with concrete API and workflow evidence.\n<<<END_APPLICATION_ANSWER_DRAFT>>>'),\"recruiterMessageDraft\":$(json_string "Saved acceptance recruiter message draft that acknowledges the prior thread and keeps the human send step explicit.")}"
|
||||
save_drafts_response="$TMP_DIR/save-drafts-response.json"
|
||||
request PUT "application drafts save" "$API_BASE/jobapplications/$job_id/application-drafts" "$save_drafts_response" empty "$save_drafts_body"
|
||||
|
||||
followup_body="$TMP_DIR/followup.json"
|
||||
write_json "$followup_body" "{\"followUpAt\":$(json_string "$FOLLOW_UP_AT")}"
|
||||
followup_response="$TMP_DIR/followup-response.json"
|
||||
request PATCH "follow-up schedule" "$API_BASE/jobapplications/$job_id/followup" "$followup_response" empty "$followup_body"
|
||||
|
||||
correspondence_list="$TMP_DIR/correspondence-list.json"
|
||||
request GET "correspondence list" "$API_BASE/correspondence/$job_id" "$correspondence_list" json
|
||||
existing_correspondence_id="$(json_extract "$correspondence_list" 'next((item["id"] for item in data if item.get("externalMessageId") == "s06-acceptance-message-1"), "")')"
|
||||
correspondence_action="existing"
|
||||
|
||||
if [[ -n "$existing_correspondence_id" ]]; then
|
||||
existing_subject="$(json_extract "$correspondence_list" 'next((item.get("subject") or "" for item in data if item.get("externalMessageId") == "s06-acceptance-message-1"), "")')"
|
||||
existing_content="$(json_extract "$correspondence_list" 'next((item.get("content") or "" for item in data if item.get("externalMessageId") == "s06-acceptance-message-1"), "")')"
|
||||
if [[ "$existing_subject" != "Backend Engineer follow-up" || "$existing_content" != "Hi Casey,\n\nThanks again for applying. We reviewed your saved package and would welcome a short manual follow-up next week so we can confirm timeline and next steps.\n\nBest,\nMaria" ]]; then
|
||||
delete_response="$TMP_DIR/delete-correspondence-response.json"
|
||||
request DELETE "correspondence delete" "$API_BASE/correspondence/$existing_correspondence_id" "$delete_response" empty
|
||||
existing_correspondence_id=""
|
||||
correspondence_action="replaced"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$existing_correspondence_id" ]]; then
|
||||
create_correspondence_body="$TMP_DIR/create-correspondence.json"
|
||||
write_json "$create_correspondence_body" "{\"jobApplicationId\":$job_id,\"from\":$(json_string "Maria Recruiter"),\"content\":$(json_string $'Hi Casey,\n\nThanks again for applying. We reviewed your saved package and would welcome a short manual follow-up next week so we can confirm timeline and next steps.\n\nBest,\nMaria'),\"subject\":$(json_string "Backend Engineer follow-up"),\"channel\":$(json_string "email"),\"date\":$(json_string "$CORRESPONDENCE_AT"),\"externalMessageId\":$(json_string "$MESSAGE_ID"),\"externalThreadId\":$(json_string "$THREAD_ID"),\"externalFrom\":$(json_string "maria.recruiter@example.invalid"),\"externalTo\":$(json_string "casey@example.invalid")}"
|
||||
create_correspondence_response="$TMP_DIR/create-correspondence-response.json"
|
||||
request POST "correspondence create" "$API_BASE/correspondence" "$create_correspondence_response" json "$create_correspondence_body"
|
||||
existing_correspondence_id="$(json_extract "$create_correspondence_response" 'data["id"]')"
|
||||
if [[ "$correspondence_action" != "replaced" ]]; then
|
||||
correspondence_action="created"
|
||||
fi
|
||||
fi
|
||||
|
||||
request GET "job details" "$API_BASE/jobapplications/$job_id" "$TMP_DIR/job-details.json" json
|
||||
request GET "readiness" "$API_BASE/jobapplications/$job_id/readiness" "$TMP_DIR/readiness.json" json
|
||||
request GET "correspondence list final" "$API_BASE/correspondence/$job_id" "$TMP_DIR/correspondence-final.json" json
|
||||
|
||||
workflow_action="$(json_extract "$TMP_DIR/readiness.json" 'data["workflowSignal"]["actionKey"]')"
|
||||
readiness_score="$(json_extract "$TMP_DIR/readiness.json" 'data["score"]')"
|
||||
readiness_level="$(json_extract "$TMP_DIR/readiness.json" 'data["level"]')"
|
||||
correspondence_count="$(json_extract "$TMP_DIR/correspondence-final.json" 'len(data)')"
|
||||
completed_items="$(json_extract "$TMP_DIR/readiness.json" '" | ".join(data.get("completed", []))')"
|
||||
reminder_items="$(json_extract "$TMP_DIR/readiness.json" '" | ".join(data.get("reminders", []))')"
|
||||
|
||||
note "seed.result=success"
|
||||
note "seed.company.id=$company_id"
|
||||
note "seed.job.id=$job_id"
|
||||
note "seed.job.action=$job_action"
|
||||
note "seed.correspondence.id=$existing_correspondence_id"
|
||||
note "seed.correspondence.action=$correspondence_action"
|
||||
note "seed.correspondence.count=$correspondence_count"
|
||||
note "seed.readiness.score=$readiness_score"
|
||||
note "seed.readiness.level=$readiness_level"
|
||||
note "seed.workflow.action=$workflow_action"
|
||||
note "seed.followUp.at=$FOLLOW_UP_AT"
|
||||
note "seed.completed=$completed_items"
|
||||
note "seed.reminders=$reminder_items"
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TARGET_SCRIPT="$SCRIPT_DIR/s06-acceptance-data.sh"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
pass() {
|
||||
printf 'PASS: %s\n' "$1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_expect_fail() {
|
||||
local name="$1"
|
||||
local expected="$2"
|
||||
shift 2
|
||||
local out="$TMP_DIR/${name}.log"
|
||||
set +e
|
||||
"$@" >"$out" 2>&1
|
||||
local exit_code=$?
|
||||
set -e
|
||||
if [[ $exit_code -eq 0 ]]; then
|
||||
cat "$out" >&2
|
||||
fail "$name unexpectedly passed"
|
||||
fi
|
||||
if ! grep -Fq "$expected" "$out"; then
|
||||
cat "$out" >&2
|
||||
fail "$name did not mention expected text: $expected"
|
||||
fi
|
||||
pass "$name"
|
||||
}
|
||||
|
||||
run_expect_pass() {
|
||||
local name="$1"
|
||||
shift
|
||||
local out="$TMP_DIR/${name}.log"
|
||||
"$@" >"$out" 2>&1 || {
|
||||
cat "$out" >&2
|
||||
fail "$name failed"
|
||||
}
|
||||
pass "$name"
|
||||
}
|
||||
|
||||
run_expect_fail "missing-auth-token" "AUTH_TOKEN is required" env -u AUTH_TOKEN bash "$TARGET_SCRIPT"
|
||||
run_expect_fail "bad-auth-token" "AUTH_TOKEN appears invalid or lacks access" env AUTH_TOKEN="not-a-real-token" bash "$TARGET_SCRIPT"
|
||||
|
||||
if [[ -n "${TEST_AUTH_TOKEN:-}" ]]; then
|
||||
run_expect_pass "happy-path-first-run" env AUTH_TOKEN="$TEST_AUTH_TOKEN" bash "$TARGET_SCRIPT"
|
||||
run_expect_pass "happy-path-second-run" env AUTH_TOKEN="$TEST_AUTH_TOKEN" bash "$TARGET_SCRIPT"
|
||||
else
|
||||
printf 'SKIP: happy-path rerun requires TEST_AUTH_TOKEN.\n'
|
||||
fi
|
||||
Reference in New Issue
Block a user