LOCAL_DEV_SETUP — pricing-service
Sibling: DEPLOYMENT_TOPOLOGY · TESTING_STRATEGY · DATA_MODEL
This guide gets a contributor from a fresh checkout to a running pricing-service with seed data and a passing test suite in under 15 minutes. It assumes the platform monorepo is already cloned and pnpm install has been run at the repo root.
1. Prerequisites
| Tool | Version | Purpose |
|---|---|---|
| Node.js | 20.x LTS | runtime |
| pnpm | 9.x | package manager |
| Docker Desktop | 4.30+ | local Postgres, Redis, Pub/Sub emulator |
| gcloud CLI | latest | only for hitting staging from local (optional) |
make | any | task runner |
Windows users: WSL 2 + Ubuntu 22.04 is recommended; Docker Desktop must be set to use the WSL backend.
2. Quickstart
cd services/pricing-service
cp .env.example .env.local
make dev:up # boots postgres + redis + pubsub-emulator via docker-compose
make db:migrate # applies Drizzle migrations
make db:seed # loads canonical seed data
make dev # starts NestJS in watch mode on :4070
Verify:
curl -s http://localhost:4070/v1/healthz
# {"status":"ok","version":"0.0.0-dev"}
3. Docker compose stack
services/pricing-service/docker-compose.dev.yml brings up:
| Service | Port | Notes |
|---|---|---|
postgres | 5435 | Postgres 16, password pricing, db pricing |
redis | 6385 | Redis 7, no auth (local only) |
pubsub-emulator | 8085 | gcr.io/google.com/cloudsdktool/cloud-sdk:emulators |
wiremock-fx | 8090 | stubbed FX provider with seed rates |
wiremock-ai | 8091 | stubbed ai-orchestrator-service |
make dev:up starts the stack; make dev:down tears it down preserving volumes; make dev:reset wipes volumes.
4. Configuration (.env.local)
NODE_ENV=development
PORT=4070
LOG_LEVEL=debug
DATABASE_URL=postgres://pricing:pricing@localhost:5435/pricing
REDIS_URL=redis://localhost:6385/0
PUBSUB_EMULATOR_HOST=localhost:8085
PUBSUB_PROJECT_ID=melmastoon-local
FX_PROVIDER_BASE_URL=http://localhost:8090
FX_PROVIDER_API_KEY=local-dev-key
AI_ORCHESTRATOR_BASE_URL=http://localhost:8091
AI_ORCHESTRATOR_TOKEN=local-dev-token
JWT_AUDIENCE=pricing-service
JWT_ISSUER=http://localhost:4001 # local iam-service
DEV_TENANT_ID=tnt_LOCAL_DEV
DEV_PROPERTY_ID=pty_LOCAL_DEV
A bundled dev-token script (pnpm dev:token) mints a JWT with the dev tenant and a revenue_manager role for hitting authenticated endpoints from curl/Postman.
5. Seed data
scripts/seed.ts plants the following:
- Tenants:
tnt_LOCAL_DEV(Sharia-mixed),tnt_LOCAL_DEV_2(Sharia-strict). - Properties: Kabul Hotel (
pty_LOCAL_DEV), Dushanbe Inn (pty_LOCAL_DEV_2), Mashhad Plaza (pty_LOCAL_DEV_3). - Room types (3 per property): Standard, Deluxe, Suite.
- Rate plans (4 per property):
BAR— Best Available Rate, refundable, USD, channelall.WEEKLY— 7-night weekly discount, refundable, USD, min LOS = 7.GOVERNMENT— fixed government rate, refundable, USD, channelcorporate.NONREF— Non-Refundable, USD, advance purchase, no refund.- On
pty_LOCAL_DEV_3an additionalHALAL_PKGSharia-compliant package plan with breakfast inclusion.
- Rate rules: baseline
BARrule for May–September withmultiplier=1.2on Fri/Sat; LOS discount onWEEKLY(5%). - Tax rules:
- AF: VAT 10% (room).
- TJ: VAT 18% (room), city tax flat 50 TJS/night.
- IR: VAT 9% (room), tourism tax flat 100,000 IRR/stay.
- Fee rules:
cleaning10 USD/stay (all properties),resort5 USD/night (Mashhad). - Promotions:
SUMMER10(10%, cap 1000),EID5(5%, cap 500),LOCAL_LOYALTY(flat 10 USD off, no cap). - FX snapshots: AFN/USD = 0.014, IRR/USD = 0.0000238, TJS/USD = 0.092 (captured today).
- One sample dynamic suggestion for
pty_LOCAL_DEVStandard, statusgenerated.
Re-seed any time with make db:seed:reset.
6. Common commands
make test # vitest unit + property-based (fast)
make test:int # integration suite (requires dev stack up)
make test:contract # event/openapi contract tests
make lint # eslint + prettier --check
make typecheck # tsc --noEmit
make openapi # regenerate openapi/pricing-v1.yaml from controllers
make migrate:new name=add_xxx # create new Drizzle migration
pnpm dev:token # print a dev JWT for tnt_LOCAL_DEV / revenue_manager
pnpm dev:quote -- --plan BAR --nights 3 # quick CLI quote against local server
7. Hitting the local API
TOKEN=$(pnpm -s dev:token)
curl -s http://localhost:4070/v1/pricing/quotes \
-H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: tnt_LOCAL_DEV" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"propertyId": "pty_LOCAL_DEV",
"ratePlanCode": "BAR",
"stayWindow": { "start": "2026-05-15", "end": "2026-05-18" },
"roomTypeIds": ["rmt_LOCAL_DEV_STD"],
"occupancy": { "adults": 2, "children": 0 },
"displayCurrency": "USD",
"channel": "direct"
}' | jq .
8. Running with sibling services
For end-to-end work with reservation-service + iam-service, use the platform-wide compose at the repo root:
make stack:up # boots iam, tenant, property, pricing, reservation, billing
make stack:seed # cross-service seed
The booking BFF will be available at http://localhost:5173/ and exercises the full quote → reserve flow against pricing-service.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
ECONNREFUSED 5435 | Postgres container not up | make dev:up; check docker ps |
column "sharia_tag" does not exist | Migrations not applied | make db:migrate |
| FX snapshot stale errors | Wiremock-fx not seeded today | make wiremock:reseed-fx |
MELMASTOON.AI.UNAVAILABLE | Wiremock-ai not running | docker compose -f docker-compose.dev.yml up -d wiremock-ai |
401 from /v1/pricing/quotes | Stale dev token | regenerate pnpm dev:token; ensure JWT_ISSUER matches local iam |
| RLS empty results | App didn't SET LOCAL app.tenant_id | check the Tenant context interceptor wiring; usually a custom-startup test path |