Skip to main content

numbering-service — Local Development Setup

Version: 1.0 Status: Draft Owner: Commerce Engineering Last Updated: 2026-04-21 Companion: DEPLOYMENT_TOPOLOGY · TESTING_STRATEGY · API_CONTRACTS


1. Prerequisites

ToolVersionPurpose
Node.js20 LTS+Runtime
pnpm9+Package manager
Docker Desktop24+Postgres, Redis, NATS, MinIO containers
grpcurllatestgRPC testing
mkcertlatestLocal mTLS certs
Prisma CLIbundledMigrations
direnvoptional.envrc loading

2. Clone and Install

git clone git@github.com:ghasi-technologies/ghasi-sms-gateway.git
cd ghasi-sms-gateway/services/numbering-service
pnpm install

3. Start Infrastructure

The monorepo includes a shared docker-compose.yml at repo root. From the repo root:

docker compose up -d postgres redis nats minio

This starts:

ServicePortCredentials
PostgreSQL 15localhost:5432ghasi / ghasi_dev (db ghasi_dev)
Redis 7localhost:6379none
NATS JetStream 2.10localhost:4222none (dev mode)
MinIO (S3-compat)localhost:9000 (UI 9001)minio / minio_dev

Enable Redis keyspace notifications (one-time):

docker exec -it ghasi-redis redis-cli CONFIG SET notify-keyspace-events Ex

4. Database Setup

cd services/numbering-service
pnpm prisma migrate dev --name init_numbering_schema
pnpm prisma db seed

Seed populates:

  • 5 mobile_operators matching real Afghan MNOs:
    • Roshan (MCC 412, MNC 40, prefix +9370, +9371, +9372)
    • Etisalat-AF (MCC 412, MNC 50, prefix +9378, +9379)
    • MTN-AF (MCC 412, MNC 01 + 20, prefix +9376, +9377)
    • AWCC (MCC 412, MNC 03, prefix +9370, +9371)
    • Salaam (MCC 412, MNC 88, prefix +9374)
  • 3 lease contracts, one per primary MNO with sample 1000-MSISDN block each.
  • 3 tenant pools: dev-tenant-a (50/1/3 quotas), dev-tenant-b (10/0/1, vanity disabled), dev-tenant-vanity (100/5/10, vanity enabled).
  • 5 vanity-eligible short codes: 4000, 5000, 6000, 7000, 8000.
  • 1 MNO public signing key (matching the dev signing key in test/fixtures/mno-keys/).
  • Sample inventory: 100 AVAILABLE MSISDNs per operator, 20 short codes, 5 alpha placeholders.

5. Environment Variables

Create .env.local:

NODE_ENV=development
LOG_LEVEL=debug
REGION_ID=kbl
GRPC_PORT=50061
HTTP_PORT=3021

DATABASE_URL=postgresql://ghasi:ghasi_dev@localhost:5432/ghasi_dev?schema=numbering
REDIS_URL=redis://localhost:6379/7
NATS_URL=nats://localhost:4222

# Disable mTLS for local dev (gated by NODE_ENV check)
GRPC_TLS_ENABLED=false

# Override quarantine for fast tests
QUARANTINE_MSISDN_DAYS=1
QUARANTINE_SHORT_CODE_DAYS=1
QUARANTINE_VANITY_DAYS=2
QUARANTINE_ALPHA_DAYS=0

# Reservation TTLs (override for fast tests)
RESERVATION_RESERVE_TTL_SECS=30
RESERVATION_HOLD_TTL_SECS=120

# S3 (MinIO local)
S3_ENDPOINT=http://localhost:9000
S3_REGULATOR_BUCKET=ghasi-regulator-exports-kbl
S3_ACCESS_KEY=minio
S3_SECRET_KEY=minio_dev

For mTLS testing locally:

# Generate dev certs
mkcert -install
pnpm dev:certs:generate # outputs ./certs/{ca,server,client-orchestrator}.{crt,key}

# In .env.local:
GRPC_TLS_ENABLED=true
TLS_CERT_PATH=./certs/server.crt
TLS_KEY_PATH=./certs/server.key
TLS_CA_PATH=./certs/ca.crt

6. Run the Service

# Hot reload
pnpm start:dev

# Debug mode (Node inspector)
pnpm start:debug

# Production mode (closer-to-real)
pnpm build && pnpm start:prod

Health endpoints:

  • http://localhost:3021/health/live
  • http://localhost:3021/health/ready
  • http://localhost:3021/metrics

7. Test the gRPC Endpoints

ValidateLease

grpcurl -plaintext \
-proto src/proto/numbering.proto \
-d '{
"identifier": "+93701234567",
"type": "MSISDN",
"tenant_id": "00000000-0000-4000-8000-00000000000A"
}' \
localhost:50061 \
ghasi.sms.numbering.v1.NumberingService/ValidateLease

Expected for the seeded tenant lease: { "valid": true, "leaseId": "lease_...", "effectiveUntil": "..." }.

Reserve

grpcurl -plaintext \
-proto src/proto/numbering.proto \
-d '{
"identifier": "+93701234999",
"type": "MSISDN",
"tenant_id": "00000000-0000-4000-8000-00000000000A",
"kind": "RESERVE",
"idempotency_key": "dev-test-001"
}' \
localhost:50061 \
ghasi.sms.numbering.v1.NumberingService/Reserve

Assign

grpcurl -plaintext \
-proto src/proto/numbering.proto \
-d '{
"identifier": "+93701234999",
"type": "MSISDN",
"tenant_id": "00000000-0000-4000-8000-00000000000A",
"term": "P30D",
"auto_renew": true,
"idempotency_key": "dev-test-002"
}' \
localhost:50061 \
ghasi.sms.numbering.v1.NumberingService/Assign

Lookup

grpcurl -plaintext -proto src/proto/numbering.proto \
-d '{ "identifier": "+93701234999", "type": "MSISDN" }' \
localhost:50061 \
ghasi.sms.numbering.v1.NumberingService/Lookup

8. Test the REST API

A dev JWT can be generated with pnpm token:dev (uses pre-signed dev JWKS from auth-service).

DEV_JWT=$(pnpm token:dev --role platform.numbering.admin)

# Browse available pool (admin)
curl -H "Authorization: Bearer $DEV_JWT" \
"http://localhost:3021/v1/admin/numbering/numbers?type=MSISDN&state=AVAILABLE&limit=20"

# List MNO contracts
curl -H "Authorization: Bearer $DEV_JWT" \
http://localhost:3021/v1/admin/numbering/contracts

# Trigger an MNO CSV import (uses fixture)
curl -H "Authorization: Bearer $DEV_JWT" \
-F "operatorId=$ROSHAN_ID" \
-F "contractId=$CONTRACT_ID" \
-F "signature=@test/fixtures/lease-batch-1.sig" \
-F "csvFile=@test/fixtures/lease-batch-1.csv" \
http://localhost:3021/v1/admin/numbering/blocks/import

# Tenant portal: reserve
TENANT_JWT=$(pnpm token:dev --tenant-id 00000000-0000-4000-8000-00000000000A --role sms:numbering:write)
curl -X POST \
-H "Authorization: Bearer $TENANT_JWT" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "type": "MSISDN" }' \
http://localhost:3021/v1/portal/numbering/+93701234500/reserve

9. Running Tests

# Unit tests (fast, no Docker)
pnpm test

# Unit tests with coverage report
pnpm test:cov

# Integration tests (uses testcontainers)
pnpm test:integration

# Watch mode
pnpm test:watch

# E2E (requires full Docker stack)
pnpm test:e2e

# Load test (ghz; requires service running)
pnpm test:load

10. Helpful Dev Commands

# Trigger a manual reservation cleanup
pnpm worker:reservation-cleanup

# Trigger a manual quarantine sweep
pnpm worker:quarantine-sweep

# Trigger a manual lease renewal cycle
pnpm worker:lease-renewal

# Trigger a one-off regulator export
pnpm worker:regulator-export --period=2026-04

# Verify audit hash chain integrity
pnpm worker:audit-chain-verify

# Clear Redis caches (forces PG reads)
pnpm cache:clear

# Time-travel: advance "now" for quarantine tests
pnpm dev:time-skip --hours=24

# Generate gRPC TypeScript types from .proto
pnpm proto:generate

# Tail structured logs prettily
pnpm logs:tail | jq

# Seed an extra test tenant
pnpm seed:tenant --tenant-id=$(uuidgen) --plan=enterprise

11. Common Issues

IssueFix
Could not connect to PostgresVerify docker compose ps postgres; check DATABASE_URL
gRPC call hangsVerify GRPC_TLS_ENABLED=false in .env.local for plaintext grpcurl
Reservation does not auto-expireRun redis-cli CONFIG GET notify-keyspace-events — must include Ex
IsVerified unreachable on alpha-AssignRun pnpm mock:sender-id-registry to start the local mock
MNO CSV signature rejectedUse test/fixtures/mno-keys/dev-private.pem to sign your CSV with pnpm dev:sign-csv
Tests fail "relation does not exist"Run pnpm prisma migrate dev
RLS returns zero rows for tenantEnsure app.current_tenant_id is set by your handler middleware
Audit hash-chain test failsRe-seed: pnpm prisma migrate reset

12. Mock Dependencies

For isolated dev without external services:

# Mock sender-id-registry (returns IsVerified=true for any alpha)
pnpm mock:sender-id-registry # listens on localhost:50071

# Mock auth-service (issues dev JWTs without Keycloak)
pnpm mock:auth # listens on localhost:8080

# Mock billing-service (always returns PreviewCharge=ok)
pnpm mock:billing # listens on localhost:50081

Pointer env vars:

SENDER_ID_REGISTRY_GRPC_URL=localhost:50071
AUTH_SERVICE_URL=http://localhost:8080
BILLING_SERVICE_GRPC_URL=localhost:50081

End of LOCAL_DEV_SETUP.md