Skip to main content

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​

ToolVersionPurpose
Node.js20.x LTSruntime
pnpm9.xpackage manager
Docker Desktop4.30+local Postgres, Redis, Pub/Sub emulator
gcloud CLIlatestonly for hitting staging from local (optional)
makeanytask 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:

ServicePortNotes
postgres5435Postgres 16, password pricing, db pricing
redis6385Redis 7, no auth (local only)
pubsub-emulator8085gcr.io/google.com/cloudsdktool/cloud-sdk:emulators
wiremock-fx8090stubbed FX provider with seed rates
wiremock-ai8091stubbed 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, channel all.
    • WEEKLY — 7-night weekly discount, refundable, USD, min LOS = 7.
    • GOVERNMENT — fixed government rate, refundable, USD, channel corporate.
    • NONREF — Non-Refundable, USD, advance purchase, no refund.
    • On pty_LOCAL_DEV_3 an additional HALAL_PKG Sharia-compliant package plan with breakfast inclusion.
  • Rate rules: baseline BAR rule for May–September with multiplier=1.2 on Fri/Sat; LOS discount on WEEKLY (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: cleaning 10 USD/stay (all properties), resort 5 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_DEV Standard, status generated.

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​

SymptomLikely causeFix
ECONNREFUSED 5435Postgres container not upmake dev:up; check docker ps
column "sharia_tag" does not existMigrations not appliedmake db:migrate
FX snapshot stale errorsWiremock-fx not seeded todaymake wiremock:reseed-fx
MELMASTOON.AI.UNAVAILABLEWiremock-ai not runningdocker compose -f docker-compose.dev.yml up -d wiremock-ai
401 from /v1/pricing/quotesStale dev tokenregenerate pnpm dev:token; ensure JWT_ISSUER matches local iam
RLS empty resultsApp didn't SET LOCAL app.tenant_idcheck the Tenant context interceptor wiring; usually a custom-startup test path