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