Developer Docs

๐Ÿ” Debugging & Testing

Manual debug workflows for verifying auth context, order queries, and data consistency across the Ngwenya federation.


๐Ÿ“– Overview

When troubleshooting data visibility, auth propagation, or order consistency issues, you need to query multiple layers of the stack independently:

  1. Auth Service (Rust, port 3008) โ€” Session validation, user identity
  2. Gateway (NestJS Apollo, port 30000) โ€” Request routing, context injection
  3. Subgraphs (NestJS, various ports) โ€” Business logic, DB queries
  4. MongoDB โ€” Raw data verification

This guide documents the exact curl commands and patterns used during debugging, with real examples from Bug #56 (Murchase History Mismatch).


๐Ÿ”‘ Prerequisites

Get Your Session Token

Grab session_token from browser DevTools โ†’ Application โ†’ Cookies โ†’ session_token.

Or extract from the auth store in the browser console:

JSON.parse(localStorage.getItem('ngwenya_auth_user'));
// โ†’ { id, email, username, session_token, ... }

Service Ports

Service Port URL
Auth (Rust) 3008 http://localhost:3008
Gateway 30000 http://localhost:30000/graphql
Murchases 3013 http://localhost:3013/graphql
Products 3003 http://localhost:3003/graphql
Malets 3011 http://localhost:3011/graphql
uCart 3014 http://localhost:3014/graphql
uChat GraphQL 3017 http://localhost:3017/graphql
uChat WS 3018 ws://localhost:3018/ws

๐Ÿงช Auth Pipeline Debugging

1. Validate Session Token

Verify what the auth service returns for a session. This is what the gateway calls on every request.

curl -s http://localhost:3008/auth/validate \
  -H "Cookie: session_token=YOUR_TOKEN" | python3 -m json.tool

Expected response (after Bug #56 fix):

{
	"user_id": "2e6f0c3a-...",
	"device_id": "117b2901-...",
	"email": "user@example.com",
	"username": "meekdenzo"
}

โš ๏ธ If email is missing, the auth service is running old code. Rebuild with cargo build in apps/auth.

2. Check `/me` Endpoint (Full Profile)

Returns the complete user profile from the auth DB:

curl -s http://localhost:3008/me \
  -H "Cookie: session_token=YOUR_TOKEN" | python3 -m json.tool

Expected response:

{
	"id": "2e6f0c3a-...",
	"username": "meekdenzo",
	"email": "user@example.com",
	"email_verified": true,
	"is_privileged": false,
	"session_token": "cYvijcn..."
}

3. Check Account Existence (Identity Status)

Verify if an account exists for a specific email or phone number. Used for fail-fast guest lookups.

# Check Email
curl -s "http://localhost:3008/auth/email/check?email=user@example.com" | python3 -m json.tool

# Check Phone (E.164 format)
curl -s "http://localhost:3008/auth/phone/check?phone=+1234567890" | python3 -m json.tool

Expected response:

{
	"exists": true,
	"userId": "2e6f0c3a-..."
}

4. Verify Gateway Context Propagation

The gateway should forward the full user object (including email) as a JSON header to subgraphs. Test by querying a guarded resolver:

curl -s http://localhost:30000/graphql \
  -H "Content-Type: application/json" \
  -H "Cookie: session_token=YOUR_TOKEN" \
  -d '{"query": "{ orders { id buyerId guestEmail } }"}' | python3 -m json.tool

If orders with guestEmail matching your email don't appear, the gateway isn't forwarding email in the user context.


๐Ÿ›’ Murchases Debugging

4. Query Authenticated Orders (via Gateway)

This is what the frontend calls on /murchases:

curl -s http://localhost:30000/graphql \
  -H "Content-Type: application/json" \
  -H "Cookie: session_token=YOUR_TOKEN" \
  -d '{"query": "{ orders { id status buyerId guestEmail totalAmount { formatted } items { name quantity } createdAt } }"}' \
  | python3 -c "
import json, sys
data = json.load(sys.stdin)
orders = data['data']['orders']
print(f'Total: {len(orders)} orders')
for o in orders:
    sid = o['id'][-8:].upper()
    print(f'  #{sid} | buyer={o[\"buyerId\"][:16]}... | guest={o[\"guestEmail\"]} | {o[\"totalAmount\"][\"formatted\"]}')
"

5. Query Subgraph Directly (Bypass Gateway)

Bypass the gateway entirely to test the subgraph resolver in isolation. Pass the user header manually to simulate what the gateway sends:

curl -s http://localhost:3013/graphql \
  -H "Content-Type: application/json" \
  -H 'user: {"id":"YOUR_USER_ID","email":"your@email.com"}' \
  -d '{"query": "{ orders { id buyerId guestEmail totalAmount { formatted } createdAt } }"}' \
  | python3 -m json.tool

๐Ÿ’ก This is the most valuable debug tool โ€” it lets you test what happens when the gateway provides different user contexts. Try:

  • With email: should return guest orders too
  • Without email: should only return buyerId-matched orders
  • With a different id: should return that user's orders

6. Check Individual Order Ownership

Query a specific order to inspect its buyerId and guestEmail:

ORDER_ID="69c99debf608483ad49f1068"

curl -s http://localhost:3013/graphql \
  -H "Content-Type: application/json" \
  -H 'user: {"id":"YOUR_USER_ID","email":"your@email.com"}' \
  -d "{\"query\": \"{ order(id: \\\"$ORDER_ID\\\") { id buyerId guestEmail status totalAmount { formatted } } }\"}" \
  | python3 -m json.tool

7. Batch-Check Order Ownership

Loop through multiple order IDs to quickly see which account each belongs to:

for oid in ORDER_ID_1 ORDER_ID_2 ORDER_ID_3; do
  result=$(curl -s http://localhost:3013/graphql \
    -H "Content-Type: application/json" \
    -H 'user: {"id":"YOUR_USER_ID","email":"your@email.com"}' \
    -d "{\"query\": \"{ order(id: \\\"$oid\\\") { id buyerId guestEmail totalAmount { formatted } } }\"}" 2>/dev/null)
  echo "$oid: $(echo $result | python3 -c "
import json,sys
d=json.load(sys.stdin)
o=d.get('data',{}).get('order')
print(f'buyer={o[\"buyerId\"]} guest={o[\"guestEmail\"]}' if o else 'NOT FOUND')
" 2>/dev/null)"
done

8. Compare Guest vs Authenticated Views

The core mismatch test โ€” these two should return the same orders for the same email:

echo "=== AUTHENTICATED VIEW ==="
curl -s http://localhost:30000/graphql \
  -H "Content-Type: application/json" \
  -H "Cookie: session_token=YOUR_TOKEN" \
  -d '{"query": "{ orders { id totalAmount { formatted } } }"}' \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'{len(d[\"data\"][\"orders\"])} orders')"

echo "=== GUEST VIEW (requires valid OTP) ==="
curl -s http://localhost:30000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ ordersByEmail(email: \"your@email.com\", code: \"123456\") { id totalAmount { formatted } } }"}' \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'{len(d[\"data\"][\"ordersByEmail\"])} orders') if d.get('data') else print(d['errors'][0]['message'])"

echo "=== PHONE VIEW (requires valid OTP) ==="
curl -s http://localhost:30000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ ordersByPhone(phone: \"+1234567890\", code: \"123456\") { id totalAmount { formatted } } }"}' \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'{len(d[\"data\"][\"ordersByPhone\"])} orders') if d.get('data') else print(d['errors'][0]['message'])"

๐Ÿ—ƒ๏ธ Database Queries

9. Query MongoDB Directly

When you need to see raw data without going through GraphQL resolvers:

# All orders for an email (regardless of buyerId)
mongosh mongodb://localhost:27017/ngwenya --quiet --eval '
  db.orders.find({ guestEmail: /your@email.com/i })
    .sort({ createdAt: -1 })
    .forEach(o => print(
      `${o._id} | buyer=${o.buyerId} | guest_email=${o.guestEmail} | guest_phone=${o.guestPhone} | $${o.totalAmount?.formatted}`
    ))
'

# Count unreassigned guest orders
mongosh mongodb://localhost:27017/ngwenya --quiet --eval '
  print("Unreassigned:", db.orders.countDocuments({
    guestEmail: /your@email.com/i,
    buyerId: "guest"
  }))
'

# Count orders by buyerId for an email
mongosh mongodb://localhost:27017/ngwenya --quiet --eval '
  db.orders.aggregate([
    { $match: { guestEmail: /your@email.com/i } },
    { $group: { _id: "$buyerId", count: { $sum: 1 } } },
    { $sort: { count: -1 } }
  ]).forEach(g => print(`  ${g._id}: ${g.count} orders`))
'

10. Check Guest OTP Tokens

Inspect pending OTP tokens in the database:

mongosh mongodb://localhost:27017/ngwenya --quiet --eval '
  db.guestordertokens.find({ $or: [{ email: "your@email.com" }, { phone: "+1234567890" }] })
    .sort({ createdAt: -1 })
    .limit(3)
    .forEach(t => print(
      `dest=${t.email || t.phone} | code=${t.code} | used=${t.used} | expires=${t.expiresAt}`
    ))
'

๐Ÿ”€ Order Reassignment

11. Trigger Manual Reassignment

Reassign guest orders to the authenticated user (requires matching email):

curl -s http://localhost:30000/graphql \
  -H "Content-Type: application/json" \
  -H "Cookie: session_token=YOUR_TOKEN" \
  -d '{"query": "mutation { reassignGuestOrders(guestEmail: \"your@email.com\") }"}' \
  | python3 -m json.tool

Returns the count of reassigned orders. Only works when the authenticated user's email matches the guestEmail argument.


๐Ÿ› Common Issues & Root Causes

Orders Missing in Authenticated View

Symptom Root Cause Debug Step
Guest view shows more orders than auth view findByBuyer query too restrictive Steps 4-5: compare with/without email
Auth view shows 0 orders user.email not in gateway context Step 1: check /auth/validate response
Orders belong to different buyerId UUIDs Multiple accounts created for same email Step 7: batch-check ownership
Guest OTP always fails Token expired or already used Step 10: check OTP tokens

Auth Pipeline Breakdowns

Symptom Root Cause Fix
/auth/validate missing email Auth service running old binary cargo build + restart in apps/auth
Gateway not forwarding email auth.context.ts not including data.email Check line 69/89 in auth.context.ts
Subgraph user.email is undefined Gateway willSendRequest not setting header Check user header in app.module.ts

๐Ÿ“‚ File Reference

Category File Path
Auth Route ngwenya-federation/apps/auth/src/routes/validate.rs
Gateway ngwenya-federation/apps/ngwenya-gateway/src/auth.context.ts
Gateway Boot ngwenya-federation/apps/ngwenya-gateway/src/app.module.ts
Order Service ngwenya-federation/apps/murchases/src/order/order.service.ts
Order Resolver ngwenya-federation/apps/murchases/src/order/order.resolver.ts
Frontend Store ngwenya-front/src/stores/orderStore.ts
Murchases Page ngwenya-front/src/routes/murchases/+page.svelte
Order Queries ngwenya-front/src/lib/queries/orders.ts

๐Ÿ’ฌ uChat Debugging

Verify uChat Service is Running

# Check if uChat ports are listening
lsof -i :3017 -i :3018 -sTCP:LISTEN

# Restart uChat (rebuilds from source)
make restart-uchat

Test WebSocket Delivery

The uChat benchmark script measures E2E latency across auth, HTTP, and WS paths:

npx tsx scripts/bench-uchat-latency.ts

Requires at least two distinct user sessions in logs/main.log. The script auto-extracts tokens and deduplicates by user ID.

Verify Redis Pub/Sub

Subscribe to a user's global WS channel and send a message to verify the fan-out:

# In terminal 1: subscribe to User B's channel
podman exec -it ngwenya-redis redis-cli SUBSCRIBE "uchat:user:<USER_B_ID>"

# In terminal 2: send a message as User A
curl -s http://localhost:3017/graphql \
  -H "Content-Type: application/json" \
  -H "x-user-id: <USER_A_ID>" \
  -d '{"query": "mutation { sendMessage(input: { conversationId: \"<CONV_ID>\", encryptedBody: \"test\" }) { id } }"}'

If the message appears in terminal 1, Redis pub/sub fan-out is working correctly.

Common uChat Issues

Symptom Root Cause Fix
request to localhost:3017 failed uChat service is down make restart-uchat
x-user-id header missing Gateway auth timed out Check auth service; gateway retries automatically
WS connects but no messages arrive Sender-filter bug (fixed) or wrong channel Verify with Redis CLI subscriber
Raw JSON error in chat UI Error not sanitized Fixed in store.svelte.ts