A Postman test suite for the DMS /wo/* REST API, with thin JavaScript helpers to run it and gate
CI. This is a public showcase copy — please read the security note below.
A factory-QA test suite for the DMS document-management REST API (the /wo/* surface), built
without access to the backend source code. The API was reverse-engineered from captured live HTTP
traffic: during exploratory testing of the running application, requests were captured with
Postman Interceptor, then replayed and shaped into a structured Postman collection, and each endpoint's
contract was inferred from its observed responses (the running API treated as the contract of record).
The suite spans ~246 requests across ~92 unique endpoints.
Tests are written from the end-user perspective and organized by discipline:
- Smoke — reachability / basic health per endpoint.
- E2E & E2E Workflows — per-module user journeys (CRUD, validation, business logic) and multi-step cross-module flows.
- Contract — response-shape / error-envelope contracts (fail on contract drift, not business outcome).
- Security — defensive controls (authentication, authorization, input validation, headers).
- Security Fuzz — data-driven injection & robustness sweeps over a curated payload corpus.
A core principle drives the design:
- Test Objectivity — a test exists to reveal the system's real state, not to pass. Where the API misbehaves, the assertion is a RED-by-design marker that encodes a logged defect and stays red until it is fixed — never relaxed to make the run green.
ci-gate.mjs understands this: it fails the build only on an unexpected failure, so the defect markers
don't break CI while a genuine regression does.
Across the disciplines, the suite logged real issues spanning common security and robustness categories — e.g.:
- Password-storage / hashing weaknesses.
- Authentication & token-handling weaknesses.
- Broken access control (object-level and role-based).
- Missing brute-force / account-lockout protection.
- Inconsistent error handling & contracts (e.g. 500-instead-of-4xx).
(Specifics — endpoints, reproduction, severities — are kept in the internal defect register, not in this public copy.)
Offensive proof-of-concept exploits were also written to confirm that several findings are genuinely exploitable end-to-end — not just theoretical — using an inverted-polarity model (the test passes when the exploit reproduces, so it doubles as a remediation-regression check). Those PoCs, and the exact exploit points they target, are intentionally withheld from this public repository for responsible-disclosure reasons. The defensive Security / Security Fuzz / Contract tests included here demonstrate the testing approach without shipping runnable exploits.
This repo ships no real credentials or secrets. The Postman environment is provided only as a
scrubbed template — postman/dms.postman_environment.example.json — with every secret value
emptied. The real environment file (postman/dms.postman_environment.json, which would hold the
login password and the JWT signing secret) is git-ignored and never committed.
Why this matters here in particular: that environment holds the JWT signing secret, and anyone who has it can forge authentication tokens for any user — which is, in fact, one of the very weaknesses this suite is built to demonstrate. A leaked password is straight account compromise. A public repository must never carry either. So the only environment in version control is the empty template; to actually run the suite you supply your own credentials locally (see "Providing credentials").
. # repo root
├── postman/
│ ├── dms.postman_collection.json # the test suite (v2.1, _Parking stripped) — SOURCE OF TRUTH
│ └── dms.postman_environment.example.json # env TEMPLATE — secret values EMPTY (copy + fill)
├── tests/ # human-readable view of every request's test code (generated, read-only)
├── import-collection.mjs # pull collection + env from Postman cloud (needs your own POSTMAN_API_KEY)
├── export-tests.mjs # (re)generate tests/ from the collection
├── run-tests.mjs # run discipline folders via Postman CLI -> reports/*.xml
├── ci-gate.mjs # classify JUnit; fail ONLY on unexpected failures
├── package.json # npm script shortcuts
└── reports/ # run artifacts (gitignored)
The tests live inside the Postman collection (postman/dms.postman_collection.json) — that file is the
single source of truth and the only thing that executes (locally and in CI). Because a collection is a
large JSON file, the tests/ folder mirrors it as one readable .js per request
(tests/<discipline>/<request>.js), showing the method/URL, the pre-request script, and the assertion
(test) script. It is a generated, read-only view for browsing here on GitHub — nothing runs from it.
Regenerate it after changing the collection with npm run export:tests.
The real postman/dms.postman_environment.json is git-ignored, so it is not present in a clone —
you create it from the template.
-
Node 18+.
-
Postman CLI (
postman). Install it for your OS:npm run postman:install:linux # Linux / CI npm run postman:install:mac # macOS npm run postman:install:win # Windows (PowerShell)
-
Postman login with your own key:
postman login --with-api-key <YOUR_POSTMAN_API_KEY>(persists).
The repo contains no credentials, so first create your own local environment from the template:
cp postman/dms.postman_environment.example.json postman/dms.postman_environment.jsonThen either fill the empty secret values in that copied file (edoc_authPassword, edoc_jwtSecret)
and set edoc_baseUrl to your target server, or leave them empty and pass them as environment variables
(EDOC_AUTH_PASSWORD, EDOC_JWT_SECRET, EDOC_BASE_URL) — run-tests.mjs overrides the file with those.
The copied file is git-ignored, so your credentials stay on your machine and never get committed.
All commands are npm scripts — run them from the repo root as npm run <name>. Each run writes JUnit
to reports/; npm run gate then classifies it.
Run the whole gated set, then check the result:
npm run ci # = run core (Smoke + Contract + Security + Security Fuzz) then gate; this is what CI does
npm run test # just run the core (no gate)
npm run gate # classify whatever is in reports/ and exit non-zero only on an UNEXPECTED failureRun one Postman folder at a time (results accumulate in reports/, so run npm run gate after):
npm run test:setup # Setup (token generation)
npm run test:smoke # Smoke Tests
npm run test:e2e # E2E Tests
npm run test:workflows # E2E Workflows
npm run test:contract # Contract Tests
npm run test:security # Security Tests
npm run test:fuzz # Security Fuzz Tests
npm run test:integration # Integration Tests (empty placeholder — for future Flowable tests)
npm run test:performance # Performance Tests (empty placeholder — for future k6/perf tests)Other:
npm run test:all # core + E2E + E2E Workflows (mutating, self-cleaning)
npm run import:postman:latest # pull the latest collection + env from Postman cloudAd-hoc — run any folder(s) without a named script via the underlying runner:
node run-tests.mjs --only "Contract Tests" --only "Security Tests" # one or more folders
node ci-gate.mjs # gate the current reports/The target server is set via edoc_baseUrl in your environment. A fresh run overwrites the matching
reports/<folder>.xml; delete reports/ to start clean.
The suite contains many RED-by-design markers — assertions that encode a real, logged defect
(FND-xx / DEF-xx) and intentionally fail until the defect is fixed (Test Objectivity Policy: never
relaxed to pass). A raw run therefore always reports failures. So:
run-tests.mjsruns with-x(don't abort on assertion failures) and writes JUnit per discipline.ci-gate.mjsclassifies every failure via a marker map (FND/DEF tag in the assertion name + per-request inheritance) and exits non-zero ONLY on an UNEXPECTED failure — a real regression. Known markers do not fail the build; a marker that stops failing is logged (defect likely fixed → update the register).
_Parking (Flowable BPM probes) is excluded entirely.
The runner + gate are CI-agnostic: install the Postman CLI, postman login, then npm run ci. The gate
exit code is the build result (non-zero only on an UNEXPECTED failure). Note that this public copy ships no
secrets, so any CI here would need credentials supplied out of band — by design, none are included.
npm run import:postman:latest pulls the latest collection (drops _Parking) and environment from the
cloud. It uses the Postman REST API (the CLI can run a collection but can't download one to a file), so it
needs your own POSTMAN_API_KEY in the environment: POSTMAN_API_KEY=<key> npm run import:postman:latest.
It writes the git-ignored real env file and keeps any secret values you already set there; the committed
.example stays scrubbed.