§ unswayed-backend
Postman complete-flow collection
Postman complete-flow collection
What it is. A Postman collection (schema v2.1) that drives the entire
implemented API surface as one continuous story against a live local server:
health → master data → auth (register/login/OTP/reset) → applicant profile,
education, filter prefs → the resume pipeline → employer profile & viewers →
employer jobs CRUD + the network feed → discovery (job detail / save /
saved-jobs merge / external / career-jobs / job alerts) → four job
applications → the hiring pipeline (interview → accept → offer → accept →
position_filled, plus the reject, interview-decline and counter-offer-decline
branches) → Google Calendar's unconfigured contract → employer reviews →
notifications → session lifecycle (refresh rotation, reuse detection, logout).
The numbers: 15 folders · 281 requests · 1008 assertions, all green
(newman, ~16s end to end). Every request asserts the status code, the
{status,message,data} envelope, the e2e-pinned exact messages, the right
pagination shape for that endpoint (pagination vs meta vs deliberately
none), and the frozen legacy quirks (capitalized Ethnicities, the job-store
country:"", the GET-that-mutates notification read, …).
How to run it
npm run postman # cold → verdict, one command
That executes postman/run.sh: brings up the dockerised Postgres, creates the
dedicated ralli_postman database (dev/test DBs untouched), applies
migrations, builds, seeds master data from the production CSVs, runs
postman/seed.cjs, boots the API on :3010, and runs newman with
postman/local.postman_environment.json. Extra args pass through to newman
(npm run postman -- --folder "10 - Applications (§32/§34/§22)").
How it's structured (and why)
- Source =
postman/folders/NN-*.json— one Postman folder per domain, filename-ordered.postman/assemble.cjsmerges them intopostman/unswayed-backend.postman_collection.json. Edit a folder file and reassemble; never hand-edit the collection artifact. - Run order is the contract. Folders chain state through collection
variables: folder 02 captures master-data ids → folder 03 logs in the seeded
users and captures tokens → folder 08's
job_id/job3_idfeed folders 09–11 → folder 10's fourapplication*_ids feed the pipeline → etc. The collection must run top-to-bottom (Collection Runner or newman), not as individual requests. - Seeded vs created.
seed.cjsprovides only what the API cannot create: two verified applicants (full profiles, fixed UCNs, one resume each withfilteredResumeset — the §32 apply snapshots it), two employers, the employer2→employer1 follow row (the §41 network feed shows only followed employers' jobs and no follow endpoint exists yet), and five disability rows (no disabilities CSV ships inseeders/). Everything else — jobs, applications, interviews, offers, reviews, notifications — is created by the requests themselves. The seed is idempotent by reset (truncates the user graph; master data survives), so every run starts identical.
The two gotchas worth remembering
@prisma/clientauto-loads the repo.envat import — regardless ofNODE_ENVor cwd. A developer's real OPENAI/AFFINDA/CLOUDINARY/RAPIDAPI keys would silently un-stub the server (Affinda starts persisting resumes; external jobs serve real 200s instead of the 503 contract).run.shtherefore shelves.envwhile the server boots and restores it once up — the same trick the Jest e2e uses (test/env-shelf.ts). If you boot the server by hand for the Postman app, shelve.envyourself.datais a predeclared global in newman's sandbox (the legacy data-file variable).const data = pm.response.json().dataworks in the Postman app but throwsIdentifier 'data' has already been declaredunder newman. Name itbodyord.
Capture strategies for non-obvious ids
- Apply and employer-action endpoints return
data:null— application ids are captured fromGET my-applications(matched by job title); interview/offer ids fromGET application-details/{id}→histories[].history_data.interview_id/offer_id. - §11 reviews take the employers-table id, not the user id: the popular
payload carries it as
employer_id(itsidfield is the user id). requisition_numbernever appears on the employer jobs wire — it's captured viaGET job-detail/{id}as the owner.
Deliberately out of scope
OTP completion (delivered out-of-band, stored hashed) and the 30-day interview/offer expiry 403s (need a fake clock) — both fully covered by the Jest e2e suite; the collection tests their failure contracts. Real provider paths (JSearch/Affinda/OpenAI/Google/LinkedIn/Cloudinary/FCM) run unconfigured by design and are asserted at their documented 503/501/422/403 contracts.
Repo memory: docs/ai/memory/0015-postman-complete-flow-collection.md. Files:
postman/ (README inside).