SMOKE_TEST.md 7.7 KB

Smoke test runbook — EKS Support Relay

Run top to bottom after every deployment. Steps 1–5 are non-destructive. Steps 6–7 write to Chatwoot and must use a test conversation.

Set up once per shell:

BASE=https://eks-relay.easyklima.com
SECRET=...   # RELAY_SHARED_SECRET, read from .env — do not echo it
ADMIN=...    # ADMIN_TOKEN

1. Liveness

curl -s -o /dev/null -w '%{http_code}\n' $BASE/health   # expect 200
curl -s $BASE/health | jq

Expect ok: true, a service name, and an uptime. No secrets in the body.

2. Readiness

curl -s $BASE/ready | jq

Expect ok: true and a dependencies block. Each dependency reports {ok, status, target} where target is host+path only. configured is a set of booleans — it says whether a secret is set, never what it is.

database and chatwoot must be ok. woocommerce / wpStore may report non-200 if the shop's Cloudflare challenges the call; that is a routing issue, not a relay fault.

3. Tools auth

curl -s -o /dev/null -w '%{http_code}\n' -X POST $BASE/tools/get_payment_methods \
  -H 'Content-Type: application/json' -d '{}'                      # expect 401

curl -s -o /dev/null -w '%{http_code}\n' -X POST $BASE/tools/get_payment_methods \
  -H 'Content-Type: application/json' -H 'Authorization: Bearer wrong' -d '{}'  # expect 401

4. Read-only tool calls

curl -s -X POST $BASE/tools/get_payment_methods \
  -H 'Content-Type: application/json' -H "Authorization: Bearer $SECRET" \
  -d '{"language":"pl"}' | jq

curl -s -X POST $BASE/tools/get_shipping_data \
  -H 'Content-Type: application/json' -H "Authorization: Bearer $SECRET" \
  -d '{"language":"pl"}' | jq '.data[:3]'

curl -s -X POST $BASE/tools/get_shipping_data \
  -H 'Content-Type: application/json' -H "Authorization: Bearer $SECRET" \
  -d '{"country":"DE","language":"de"}' | jq

get_payment_methods needs admin-level Woo consumer keys; if the key is read-only it answers 200 with ok:false, code:"NOT_IMPLEMENTED" — that is the designed degradation, and the Flowise tool surfaces it as an explanation.

5. Webhook fixture and idempotency

Use a conversation id that exists in Chatwoot but has no live customer.

MSG=$RANDOM
PAYLOAD=$(cat <<JSON
{
  "event": "message_created",
  "id": $MSG,
  "message_type": "incoming",
  "content_type": "incoming_email",
  "content": "Test techniczny relaya — prosze zignorowac.",
  "conversation": {
    "id": 1311, "inbox_id": 1, "channel": "Channel::Email",
    "labels": [], "custom_attributes": {},
    "additional_attributes": {"mail_subject": "Relay smoke test"},
    "meta": {"sender": {"id": 1, "name": "Relay Test", "email": "relay-test@example.com"}}
  },
  "sender": {"id": 1, "name": "Relay Test", "email": "relay-test@example.com"}
}
JSON
)

# First delivery → 202 with a jobId
curl -s -X POST $BASE/webhooks/chatwoot -H 'Content-Type: application/json' -d "$PAYLOAD" | jq

# Same message id again → 200 with duplicate:true and NO new job
curl -s -X POST $BASE/webhooks/chatwoot -H 'Content-Type: application/json' -d "$PAYLOAD" | jq

Confirm in the DB:

curl -s "$BASE/admin/messages?conversationId=1311" -H "Authorization: Bearer $ADMIN" | jq
curl -s "$BASE/admin/events?conversationId=1311"   -H "Authorization: Bearer $ADMIN" | jq
curl -s "$BASE/admin/jobs"                          -H "Authorization: Bearer $ADMIN" | jq

Expect exactly one ProcessedMessage row for that message id and exactly one job. On the server the same data is available offline:

sudo docker compose exec relay npm run events -- --conversation 1311
sudo docker compose exec relay npm run events -- --jobs

Non-actionable events must be skipped with 200:

for t in '"outgoing"' ; do
  curl -s -X POST $BASE/webhooks/chatwoot -H 'Content-Type: application/json' \
    -d "{\"event\":\"message_created\",\"id\":$RANDOM,\"message_type\":$t,\"content\":\"x\",\"conversation\":{\"id\":1311}}" | jq -c
done
# expect {"ok":true,"skipped":true,"reason":"not_incoming:outgoing"}

6. Ticket creation (writes to Chatwoot — test conversation only)

curl -s -X POST $BASE/tools/new_ticket \
  -H 'Content-Type: application/json' -H "Authorization: Bearer $SECRET" \
  -d '{"conversationId": <TEST_CONV_ID>, "summary": "Smoke test handoff"}' | jq

Expect {"ok":true,"ticketNumber":"EKS-YYYYMMDD-<id>","status":"created"}.

Call it a second time — expect the same number with "status":"existing".

In Chatwoot verify the conversation now has the ticket label and a ticket_number custom attribute. Afterwards, remove the label and clear the attribute to return the test conversation to its previous state.

7. End-to-end e-mail test

  1. Send an e-mail to the test inbox aiac-aws@easyklima.com.
  2. Wait for Chatwoot's IMAP poll and confirm a conversation appears (filter: status = all, inbox Skrzynka EasyKlima support).
  3. GET /admin/events?conversationId=<id>webhook_accepted, then flowise_response, then reply_sent.
  4. Confirm the reply is visible in Chatwoot and in Flowise → View Messages under session chatwoot:<id>.
  5. Force a handoff (ask something that requires a human). Confirm the ticket label, the ticket_number, and that further messages log skipped_ticket_mode instead of calling Flowise.

8. Rollback

Point the Chatwoot webhook back at the PHP relay URL. The PHP deployment on the shop's web server is untouched and remains functional.


Recorded run — 2026-08-20 (initial deployment)

Commit f36fc3d, host acmycar / 18.168.156.244, domain https://eks-relay.easyklima.com.

Step Result
docker compose config OK, one service
docker compose ps eks-relay Up (healthy)
GET /health 200, mode: prod
GET /ready 200, ok: true — database, chatwoot, flowise, woocommerce, wpStore all ok
/tools/* without a bearer 401 UNAUTHORIZED on all seven endpoints
/tools/get_payment_methods 200, gateways returned (Przelewy24, PayPal, Stripe, Bancontact, bank transfer, …)
/tools/get_shipping_data (zones) 200, zones with ISO country codes
/tools/get_shipping_data (zoneId=22, language=cs) 200, DHL/DHL-COD priced in CZK — multicurrency path confirmed
/tools/get_product_data (search=gaz) 200, published products with prices
/tools/get_car_data (VW, 2016) 200, model disambiguation list
/tools/get_order_data (bogus order) 403 UNAUTHORIZED, no third-party data leaked
Webhook, delivery 1 202 + jobId
Webhook, delivery 2 (same message id) 200 duplicate:true, no second job
Webhook, outgoing message 200 skipped: not_incoming:outgoing
Worker outcome job done; ProcessedMessage = skipped/ticket_mode; audit webhook_acceptedskipped_ticket_mode
/tools/new_ticket (test conversation 1600) EKS-20260820-1600, status: created
/tools/new_ticket again same number, status: existing
Chatwoot after ticket label ticket + ticket_number + handoff + handoff_reason set
Follow-up message on that conversation conversation fetched from Chatwoot, skipped_ticket_mode, Flowise not called
git pull + docker compose restart new code live in ~0.4 s, no rebuild, no dependency reinstall
RELAY_MODE=dev + file touch process reloaded in place (uptime 47 s → 12 s), no restart, no rebuild

Test conversation 1600 was restored afterwards (labels and custom attributes cleared, local Ticket row deleted). Mode was set back to prod.

Not yet run: step 7, the end-to-end e-mail test. It needs the Chatwoot webhook repointed at this relay, which is a deliberate, separately approved change.