Skip to main content

LOCAL_DEV_SETUP — reservation-service

Sibling: DEPLOYMENT_TOPOLOGY · TESTING_STRATEGY · DATA_MODEL

Strategic anchors: 02 §16 Local Development

This guide brings up reservation-service on a developer workstation with all dependencies running locally — Postgres, Pub/Sub emulator, mock payment provider, mock lock adapter — and seed data sufficient to exercise the full booking saga end-to-end.


1. Prerequisites

ToolVersion
Node.js20 LTS
pnpm9.x
Docker Desktop4.x (Linux containers)
Google Cloud SDKlatest (for the Pub/Sub emulator)
Java17+ (Pub/Sub emulator runtime)
GNU make4.x

The platform monorepo bundles a make bootstrap that installs Node deps, builds shared packages (@ghasi/shared, @ghasi/domain-primitives, @ghasi/telemetry), and verifies tool versions.


2. Bring up the local stack

cd services/reservation-service
cp .env.example .env
make local-up

make local-up runs docker compose up -d against docker-compose.local.yml which starts:

ServiceImagePortNotes
Postgres 15postgres:15-alpine5432melmastoon_local database; default schema reservation
Redis 7redis:7-alpine6379hot-cache substitute for Memorystore
Pub/Sub emulatorgcr.io/google.com/cloudsdktool/cloud-sdk:emulators8085PUBSUB_EMULATOR_HOST=localhost:8085
Mock payment providermelmastoon-platform/mock-payment:dev7100matches payment-gateway-service webhook contract
Mock lock adaptermelmastoon-platform/mock-lock:dev7200matches lock-integration-service event contract
Mock notificationmelmastoon-platform/mock-notification:dev7300sinks confirmation/cancel emails to a local file
OpenTelemetry collectorotel/opentelemetry-collector:latest4317optional; exports to local Jaeger if you also start it

After containers start, run migrations and seed:

pnpm drizzle-kit push # apply migrations to local Postgres
pnpm seed:local # insert seed data (see §4)
pnpm dev # start NestJS in watch mode on :8080

The hold-expiry worker can be run separately:

pnpm dev:hold-expiry-worker

3. Environment variables (.env.example)

NODE_ENV=development
PORT=8080
LOG_LEVEL=debug

# Postgres
DATABASE_URL=postgres://melmastoon:melmastoon@localhost:5432/melmastoon_local
DATABASE_SCHEMA=reservation
DATABASE_POOL_SIZE=10

# Redis (Memorystore substitute)
REDIS_URL=redis://localhost:6379

# Pub/Sub emulator
PUBSUB_EMULATOR_HOST=localhost:8085
GOOGLE_CLOUD_PROJECT=melmastoon-local
PUBSUB_TOPIC_PREFIX=melmastoon
PUBSUB_DLQ_SUFFIX=-dlq

# Mock service URLs (called via the platform port adapters)
PRICING_BASE_URL=http://localhost:7050
INVENTORY_BASE_URL=http://localhost:7060
PAYMENT_BASE_URL=http://localhost:7100
LOCK_BASE_URL=http://localhost:7200
NOTIFICATION_BASE_URL=http://localhost:7300
AI_ORCHESTRATOR_BASE_URL=http://localhost:7400 # mock returns canned anomaly + parser responses

# Tenancy
DEFAULT_TENANT_HOLD_TTL_SECONDS=600
DEFAULT_TENANT_NO_SHOW_GRACE_HOURS=2

# OTel
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
OTEL_SERVICE_NAME=reservation-service

Never commit a real .env; only .env.example is in version control.


4. Seed data

pnpm seed:local inserts a reproducible scenario:

EntityCountNotes
Tenants2tnt_local_af (Afghan tenant, AFN base, USD billing) and tnt_local_ir (Iran tenant, IRR base, IRR billing with FX snapshot)
Properties3ppt_local_kbl_grand (Kabul, 12 rooms), ppt_local_thr_lake (Tehran, 8 rooms), ppt_local_dyu_central (Dushanbe, 6 rooms)
Room types6Suite, Suite Plus, Twin, Family, Single, Apartment
Rooms26distributed across the three properties; each carries a vendor hint for the mock lock adapter (ttlock, salto, wiegand)
Rate plans4rate_local_BAR, rate_local_NREF, rate_local_CASH_FRIENDLY, rate_local_CORP_ACME
Sample guests6Mixed scripts: ps-AF, fa-IR, tg-TJ, en-US, ar-SA; one guest carries a national_id last4, others passport
Sample reservations5one per status to exercise UI: held (with active TTL), confirmed (future stay), checked_in (in-house), checked_out (last week), cancelled (last month)

The seed script also publishes the corresponding events to the Pub/Sub emulator so projections downstream can be exercised.


5. Common commands

TaskCommand
Run all testspnpm test
Run unit tests onlypnpm test:unit
Run integration tests (Testcontainers)pnpm test:integration
Run the mandatory threepnpm test:integration -- tenant-isolation outbox inbox
Generate OpenAPI from controllerspnpm openapi:gen (writes openapi.json)
Generate Drizzle migration from schemapnpm db:gen "<name>"
Apply migrations to local DBpnpm db:push
Tail mock notification sinktail -f docker/mock-notification/outbox.log
Replay an event into the local emulatorpnpm pubsub:replay <subject> <fixture-file.json>

6. Mock service behavior

  • mock-payment (localhost:7100): accepts POST /intents, immediately calls back with payment.transaction.captured.v1 500 ms later for cards. Cash-on-arrival returns pending_cash and only emits captured when the developer hits POST /sim/cash-paid/:intentId. Configurable failure modes via ?failure=timeout|declined|webhook_lost.
  • mock-lock (localhost:7200): emits lock.key.issued.v1 200 ms after request; emits lock.key.failed.v1 if the room id contains _fail. The sandbox runbook in 09 §6 describes the simulator UI.
  • mock-notification (localhost:7300): writes every outbound message to outbox.log instead of sending; surfaces a small UI at localhost:7301 for visual verification.
  • mock-ai-orchestrator (localhost:7400): returns canned anomaly verdicts (band=low), tag arrays for special requests, and identity transliteration; supports ?force=high_risk for fraud-flow testing.

7. End-to-end booking dry run

# 1. Get a quote
curl -s -X POST http://localhost:8080/api/v1/reservations/quotes \
-H "Authorization: Bearer dev-staff-token" \
-H "X-Tenant-Id: tnt_local_af" \
-H "Idempotency-Key: 01J0000000000000000000QUOTE" \
-H "Content-Type: application/json" \
-d @fixtures/quote-request.json | jq .

# 2. Place a hold
curl -s -X POST http://localhost:8080/api/v1/reservations/holds \
-H "Authorization: Bearer dev-staff-token" \
-H "X-Tenant-Id: tnt_local_af" \
-H "Idempotency-Key: 01J0000000000000000000HOLD" \
-H "Content-Type: application/json" \
-d @fixtures/hold-request.json | jq .

# 3. Mock payment fires; reservation transitions to confirmed automatically.
# 4. Inspect:
curl -s http://localhost:8080/api/v1/reservations/<rsv_id> \
-H "Authorization: Bearer dev-staff-token" \
-H "X-Tenant-Id: tnt_local_af" | jq .

fixtures/ holds canonical request bodies (quote, hold, walk-in, modify date, cancel, check-in, check-out) ready to use.


8. Troubleshooting

  • PUBSUB_EMULATOR_HOST not honored — confirm the env var is exported in the shell where pnpm dev runs; the SDK reads it at module load.
  • Migrations fail with RLS error — connect as melmastoon_admin (which has BYPASSRLS for migrations); the application role does not.
  • Hold expiry not firing — start the worker (pnpm dev:hold-expiry-worker); it does not auto-start with the API.
  • Mock lock returns failed — check the roomId you passed; remove _fail suffix to get the success path.

9. Cross-references