From d85aa507a2f75c26f33cb0cbdbd47a88407f25fb Mon Sep 17 00:00:00 2001 From: Apple Date: Mon, 2 Mar 2026 09:11:45 -0800 Subject: [PATCH] docs(dev): add redis docker-compose smoke snippet for sofiia-console Made-with: Cursor --- docker-compose.redis-smoke.yml | 13 +++++ docs/runbook/sofiia-redis-smoke.md | 90 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 docker-compose.redis-smoke.yml create mode 100644 docs/runbook/sofiia-redis-smoke.md diff --git a/docker-compose.redis-smoke.yml b/docker-compose.redis-smoke.yml new file mode 100644 index 00000000..507ef1c1 --- /dev/null +++ b/docker-compose.redis-smoke.yml @@ -0,0 +1,13 @@ +services: + redis: + image: redis:7-alpine + container_name: sofiia-redis-smoke + restart: unless-stopped + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 20 + start_period: 5s diff --git a/docs/runbook/sofiia-redis-smoke.md b/docs/runbook/sofiia-redis-smoke.md new file mode 100644 index 00000000..f662dcec --- /dev/null +++ b/docs/runbook/sofiia-redis-smoke.md @@ -0,0 +1,90 @@ +# Sofiia Console Redis Smoke + +Мета: швидко перевірити distributed idempotency для `sofiia-console` з Redis, включно з replay між двома інстансами BFF. + +## 1) Підняти Redis + +```bash +docker compose -f docker-compose.redis-smoke.yml up -d +``` + +## 2) Запустити sofiia-console з env + +```bash +cd services/sofiia-console +export SOFIIA_IDEMPOTENCY_BACKEND=redis +export SOFIIA_REDIS_URL=redis://127.0.0.1:6379/0 +export SOFIIA_REDIS_PREFIX=sofiia:idem: +export SOFIIA_IDEMPOTENCY_TTL_S=900 +export ENV=dev +python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8002 +``` + +## 3) Smoke curl: 1 send + replay + +```bash +CHAT_ID=$(curl -s -X POST "http://127.0.0.1:8002/api/chats" \ + -H "Content-Type: application/json" \ + -d '{"agent_id":"sofiia","node_id":"NODA2","source":"web","external_chat_ref":"redis-smoke"}' | jq -r '.chat.chat_id') + +curl -s -X POST "http://127.0.0.1:8002/api/chats/${CHAT_ID}/send" \ + -H "Content-Type: application/json" \ + -H "Idempotency-Key: redis-smoke-key-1" \ + -d '{"text":"ping"}' + +curl -s -X POST "http://127.0.0.1:8002/api/chats/${CHAT_ID}/send" \ + -H "Content-Type: application/json" \ + -H "Idempotency-Key: redis-smoke-key-1" \ + -d '{"text":"ping"}' +``` + +Очікування: другий виклик повертає `idempotency.replayed=true` і той самий `message.message_id`. + +## Перевірка між двома інстансами BFF (A/B) + +1. Інстанс A: +```bash +cd services/sofiia-console +export SOFIIA_IDEMPOTENCY_BACKEND=redis +export SOFIIA_REDIS_URL=redis://127.0.0.1:6379/0 +export SOFIIA_REDIS_PREFIX=sofiia:idem: +export SOFIIA_IDEMPOTENCY_TTL_S=900 +export ENV=dev +python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8002 +``` + +2. Інстанс B (в іншому терміналі, той самий env, інший порт): +```bash +cd services/sofiia-console +export SOFIIA_IDEMPOTENCY_BACKEND=redis +export SOFIIA_REDIS_URL=redis://127.0.0.1:6379/0 +export SOFIIA_REDIS_PREFIX=sofiia:idem: +export SOFIIA_IDEMPOTENCY_TTL_S=900 +export ENV=dev +python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8003 +``` + +3. Записати перший send через A, повторити через B з тим самим `Idempotency-Key`: +```bash +CHAT_ID=$(curl -s -X POST "http://127.0.0.1:8002/api/chats" \ + -H "Content-Type: application/json" \ + -d '{"agent_id":"sofiia","node_id":"NODA2","source":"web","external_chat_ref":"redis-smoke-ab"}' | jq -r '.chat.chat_id') + +A=$(curl -s -X POST "http://127.0.0.1:8002/api/chats/${CHAT_ID}/send" \ + -H "Content-Type: application/json" \ + -H "Idempotency-Key: redis-shared-key-1" \ + -d '{"text":"ping-from-a"}') + +B=$(curl -s -X POST "http://127.0.0.1:8003/api/chats/${CHAT_ID}/send" \ + -H "Content-Type: application/json" \ + -H "Idempotency-Key: redis-shared-key-1" \ + -d '{"text":"ping-from-b"}') + +echo "$A" | jq '{message_id: .message.message_id, replayed: .idempotency.replayed, node_id}' +echo "$B" | jq '{message_id: .message.message_id, replayed: .idempotency.replayed, node_id}' +``` + +Очікування: +- `A.idempotency.replayed == false` +- `B.idempotency.replayed == true` +- `A.message.message_id == B.message.message_id`