# Отправка СМС ## На ВМ выполните: cd /opt/han-chat/backend umask 077 REQUEST_FILE=$(mktemp) read -r -p "Тестовый номер в E.164 (+79...): " TEST_PHONE python3 - "$REQUEST_FILE" "$TEST_PHONE" <<'PY' import json import secrets import sys import uuid challenge_id = str(uuid.uuid4()) payload = { "idempotency_key": f"ops:smoke:{challenge_id}", "template_code": "auth_otp", "locale": "ru", "phone_e164": sys.argv[2], "substitutions": { "code": f"{secrets.randbelow(1000000):06d}", "ttl_min": "1", }, "customer_ref": challenge_id, "message_ttl_sec": 60, } with open(sys.argv[1], "w", encoding="utf-8") as file: json.dump(payload, file, ensure_ascii=False) PY ## Создайте функцию отправки: send_sms_smoke() { deployment/secrets/han-secrets run --config .env -- \ docker compose --env-file .env --profile ops run --rm --no-deps \ --user 0:0 \ --entrypoint sh \ -v /run/han-chat/secrets/SMS_SERVICE_TOKEN:/run/secrets/sms_service_token:ro \ -v "$REQUEST_FILE:/tmp/sms-request.json:ro" \ toolbox -ec ' umask 077 printf "Authorization: Bearer %s\n" \ "$(cat /run/secrets/sms_service_token)" > /tmp/auth-header curl -sS \ -w "\nHTTP %{http_code}\n" \ -X POST \ -H @/tmp/auth-header \ -H "Content-Type: application/json" \ -H "X-Request-ID: ops-sms-smoke" \ --data-binary @/tmp/sms-request.json \ http://sms-service:8080/internal/sms/v1/send ' } ## Отправка: send_sms_smoke Ожидается: HTTP 202 и JSON с sms_message_id. ## Проверьте журнал: SELECT id, phone_masked, send_status, delivery_status, provider_message_id, provider_error_code, attempt_count, created_at FROM sms.sms_outbound_message ORDER BY created_at DESC LIMIT 5; ## После проверки удалите секретные данные: shred -u "$REQUEST_FILE" 2>/dev/null || rm -f "$REQUEST_FILE" unset TEST_PHONE REQUEST_FILE # Тесты Выполняйте на ВМ из `/opt/han-chat/backend`. ### 1. Проверить запрет публичного internal API ```bash PUBLIC_WEB_URL=$(python3 - <<'PY' from pathlib import Path for line in Path(".env").read_text().splitlines(): if line.startswith("PUBLIC_WEB_URL="): print(line.split("=", 1)[1].strip().strip("\"'")) break PY ) curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \ "$PUBLIC_WEB_URL/internal/sms/v1/messages/00000000-0000-0000-0000-000000000000" ``` Ожидается: ```text HTTP 404 ``` ### 2. Проверить callback с неправильного IP ```bash curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \ -X POST \ -H 'Content-Type: application/json' \ --data '[]' \ "$PUBLIC_WEB_URL/callbacks/idgtl/sms" ``` Ожидается: ```text HTTP 403 ``` Заголовок `X-Forwarded-For` не должен позволять обойти ограничение. ### 3. Проверить Basic auth внутри Docker-сети Credentials получает только дочерний процесс через secret launcher. Неверные credentials: ```bash deployment/secrets/han-secrets run --config .env -- \ docker compose --env-file .env --profile ops run --rm --no-deps \ --entrypoint sh toolbox -ec ' curl -sS -o /dev/null -w "HTTP %{http_code}\n" \ -u invalid:invalid \ -H "Content-Type: application/json" \ --data "[]" \ http://sms-service:8080/callbacks/idgtl/sms ' ``` Ожидается `HTTP 401`. Правильные credentials: ```bash deployment/secrets/han-secrets run --config .env -- \ docker compose --env-file .env --profile ops run --rm --no-deps \ --entrypoint sh \ -v /run/han-chat/secrets/IDGTL_SMS_CALLBACK_USERNAME:/run/secrets/callback_user:ro \ -v /run/han-chat/secrets/IDGTL_SMS_CALLBACK_PASSWORD:/run/secrets/callback_password:ro \ toolbox -ec ' umask 077 { printf "user = \"%s:%s\"\n" \ "$(cat /run/secrets/callback_user)" \ "$(cat /run/secrets/callback_password)" } > /tmp/curl-auth.conf curl -sS -o /dev/null -w "HTTP %{http_code}\n" \ --config /tmp/curl-auth.conf \ -H "Content-Type: application/json" \ --data "[]" \ http://sms-service:8080/callbacks/idgtl/sms ' ``` Ожидается `HTTP 422`: авторизация прошла, но пустой callback-массив невалиден. ### 4. Проверить реальный callback Direct После тестовой SMS: ```sql SELECT id, send_status, delivery_status, provider_message_id, callback_last_at, sent_at, delivered_at FROM sms.sms_outbound_message ORDER BY created_at DESC LIMIT 5; ``` Успешный реальный callback подтверждается: - `callback_last_at IS NOT NULL`; - `delivery_status = sent` или `delivered`; - заполняются `sent_at`/`delivered_at`. Дополнительно: ```bash docker compose --env-file .env logs --since=30m nginx sms-service ``` Для callback должен быть ответ `204`. Только реальный запрос Direct может полноценно подтвердить IP allowlist.