Files
IoT/legacy/doc/testing-v0.2.6.md
T

28 KiB
Raw Blame History

ЛЕГАСИ (2026-08-16) — СТАРЫЙ IoT (k8s-деплой). НЕ ПРИНИМАТЬ ВО ВНИМАНИЕ. Актуальное: HISTORY/2026-08-16-session-log.md

IoT — Отчёт по тестированию v0.2.5 / v0.2.6

Дата: 2026-04-21 Кластер: iot-naeel, namespace: sless Образ: naeel/iot-operator:v0.2.6 Тестировал: GitHub Copilot (Claude Sonnet 4.6)


Контекст

В ходе двух сессий (2026-04-12 и 2026-04-21) после деплоя v0.2.3 были обнаружены и исправлены три бага, выпущены v0.2.4 → v0.2.5 → v0.2.6. Затем проведено расширенное тестирование: 7 категорий, 33 теста.


Баги, найденные в процессе тестирования

BUG-01 — MQTTAuth ищет IoTDevice по Name=deviceID (критический)

Версия: v0.2.3
Компонент: internal/api/handler/iot_device_handler.go, функция MQTTAuth
Симптом: MQTT CONNECT возвращал Connection Refused: not authorised (5) для всех устройств.
Причина: Хэндлер вызывал h.K8s.Get(client.ObjectKey{Name: deviceID}), где deviceID — это Spec.DeviceID (e2e-test-01), но имя K8s объекта IoTDevice может быть другим (e2e-test-device). Объект не находился → NotFounddeny.
Диагностика:

# Auth endpoint напрямую возвращал deny при правильном пароле:
curl -s -X POST http://iot-operator.sless.svc:9090/internal/mqtt/auth \
  -d username:sless_e2e-test-01
# {"result":"deny"}

# Secret существует и пароль совпадает:
kubectl get secret iot-e2e-test-01 -n sless -o jsonpath="{.data.mqtt-password}" | base64 -d
# fa8180c07ffc1d665d21e95a443235a93926f16e9d98c319539263f05a333ef0

# IoTDevice объект называется e2e-test-device, а не e2e-test-01:
kubectl get iotdevice -n sless
# NAME              DEVICEID
# e2e-test-device   e2e-test-01

Фикс: Заменён Get на List + фильтр по Spec.DeviceID == deviceID.
Версия фикса: v0.2.4


BUG-02 — PG15+: CREATE DATABASE OWNER требует GRANT (критический)

Версия: v0.2.4
Компонент: internal/storage/iotpg/iot_telemetry_store.go, функция EnsureTenantDB
Симптом: sqs-consumer не мог создать tenant DB для первого сообщения от нового namespace.

level=ERROR msg="process telemetry message"
err="iotpg: create database tenant_sless: pq: must be able to SET ROLE \"tenant_sless\" (42501)"

Причина: PostgreSQL 15+ требует GRANT role TO CURRENT_USER перед CREATE DATABASE ... OWNER role. В PG17 это обязательно.
Фикс: Добавлен GRANT {userName} TO CURRENT_USER перед CREATE DATABASE.
Версия фикса: v0.2.5


BUG-03 — Query param ?device= вместо ?device_id= (minor)

Версия: v0.2.5
Компонент: internal/api/handler/iot_telemetry_handler.go, функция ListIoTTelemetry
Симптом: GET /telemetry?device_id=dev-02 возвращал все записи (фильтр не работал).

curl ".../telemetry?device_id=dev-02"
# {"count":6,...}  ← должно быть count:1

Причина: Хэндлер читал r.URL.Query().Get("device"), а не "device_id". Несоответствие с именем поля в ответе (device_id) и документацией.
Фикс: Параметр переименован в device_id.
Версия фикса: v0.2.6


Состояние кластера на момент тестирования

Кластер:   iot-naeel (185.247.187.149:6443)
Namespace: sless
Дата:      2026-04-21

ПОДЫ:
  emqx-858d99fcc7-mgjjx              1/1  Running  8d
  iot-operator-6b4fcc47cc-b5rwf      1/1  Running  ~30m
  iot-mqtt-bridge-74d5c4688-5qqqv    1/1  Running  ~16s
  iot-sqs-consumer-d456d4f8c-n8pnk   1/1  Running  ~16s

ОБРАЗЫ (все компоненты):
  naeel/iot-operator:v0.2.6

УСТРОЙСТВА:
  e2e-test-device   device_id=e2e-test-01  phase=Active  enabled=true
  e2e-dev-02        device_id=dev-02       phase=Active  enabled=true
  e2e-dev-03        device_id=dev-03       phase=Active  enabled=true
  e2e-dev-04        device_id=dev-04       phase=Active  enabled=true

ТЕЛЕМЕТРИЯ В POSTGRES (tenant_sless):
  e2e-test-01: >1000 записей (cap API = 1000)
  dev-02:      21 запись
  dev-03:      21 запись
  dev-04:      21 запись

Результаты тестирования

Группа 1 — E2E пайплайн (базовый)

Тесты из сессии 2026-04-12, подтверждены после деплоя v0.2.5.

# Тест Команда / Действие Ожидание Результат
1.1 Создание устройства POST /v1/namespaces/sless/iot/devices 201, phase после reconcile = Active PASS
1.2 Получение credentials GET /devices/e2e-test-device mqtt_username, mqtt_password, secret_name PASS
1.3 MQTT CONNECT mosquitto_pub -u sless_e2e-test-01 -P <pass> CONNACK(0) PASS
1.4 MQTT → SQS Проверка логов mqtt-bridge forwarded IoT telemetry to SQS PASS
1.5 SQS → Postgres Проверка логов sqs-consumer telemetry saved to Postgres PASS
1.6 Telemetry API GET /telemetry?device_id=e2e-test-01 count≥1, payload совпадает PASS

Группа 2 — Device API: коды ошибок

# Тест Запрос Ожидание Факт Результат
2.1 POST без name {} 400 name is required 400 ✓ PASS
2.2 POST без device_id {"name":"x"} 400 device_id is required 400 ✓ PASS
2.3 POST дубликат существующее имя 409 iot device already exists 409 ✓ PASS
2.4 GET несуществующий GET /devices/ghost-device 404 iot device not found 404 ✓ PASS
2.5 DELETE несуществующий DELETE /devices/ghost-device 404 iot device not found 404 ✓ PASS
2.6 PATCH без enabled {} 400 enabled field is required 400 ✓ PASS
2.7 POST невалидный JSON not-json 400 invalid JSON: ... 400 ✓ PASS

Группа 3 — MQTT Auth: edge cases

Все запросы идут на POST /internal/mqtt/auth. Корректный ответ всегда HTTP 200. Результат определяется полем result в теле: "allow" или "deny".

# Тест Входные данные Ожидание Результат
3.1 Неверный пароль правильный username, неверный pass deny PASS
3.2 Устройство disabled правильные credentials, enabled=false deny PASS
3.3 Пустое тело {} deny PASS
3.4 Username без _ "username":"nounderscore" deny PASS
3.5 Несуществующий deviceID "username":"sless_ghost-999" deny PASS
3.6 Пустой namespace "username":"_dev01" deny PASS
3.7 Пустой deviceID "username":"sless_" deny PASS
3.8 Bridge: неверный пароль bridge username, wrong pass deny PASS
3.9 Невалидный JSON body not-json deny PASS
3.10 Bridge: правильные credentials MQTT_USERNAME + MQTT_PASSWORD allow PASS
3.11 Правильные device credentials sless_e2e-test-01 + корректный pass allow + ACL rules PASS

ACL в ответе при allow (пример для устройства):

{
  "result": "allow",
  "acl": [
    {"permission":"allow","action":"publish","topic":"sless/telemetry/e2e-test-01"},
    {"permission":"allow","action":"subscribe","topic":"sless/telemetry/e2e-test-01"},
    {"permission":"deny","action":"all","topic":"#"}
  ]
}

Группа 4 — Device lifecycle (PATCH / DELETE)

# Тест Действие Ожидание Результат
4.1 Отключение устройства PATCH enabled=false 200, phase → Disabled PASS
4.2 Auth отключённого auth с правильным паролем deny PASS
4.3 Включение обратно PATCH enabled=true 200, reconcile → phase=Active PASS
4.4 DELETE устройства DELETE /devices/to-delete 204 PASS
4.5 Cascade: Secret удалён kubectl get secret iot-del-01 NotFound PASS
4.6 Cascade: IoTDevice удалён kubectl get iotdevice to-delete NotFound PASS

Группа 5 — ACL изоляция топиков

# Тест Действие Ожидание Результат
5.1 Публикация в свой топик sless/telemetry/e2e-test-01 CONNACK(0), forwarded PASS
5.2 Публикация в чужой топик sless/telemetry/ANOTHER-DEVICE CONNACK(0), но не forwarded PASS

EMQX применяет ACL после аутентификации. При публикации в запрещённый топик клиент получает CONNACK(0) (аутентификация прошла), но PUBLISH тихо отбрасывается. Bridge не получает сообщение — подтверждено отсутствием записи в логах.


Группа 6 — MQTT: нестандартные payload

# Тест Payload Поведение Ожидание Результат
6.1 Невалидный JSON THIS IS NOT JSON AT ALL !@# bridge оборачивает в строку "payload":"THIS IS NOT JSON AT ALL !@#" PASS (by design)
6.2 Пустой payload "" bridge оборачивает в строку "payload":"" PASS (by design)
6.3 Неизвестное устройство любой payload CONNACK(5) not authorised отклонено PASS

Дизайн-решение: bridge намеренно принимает любой payload (не только JSON). Невалидный payload оборачивается в JSON-строку (json.Marshal(string(payload))). Это позволяет передавать raw данные от устройств старых форматов.


Группа 7 — Telemetry API: граничные значения

# Тест Параметры Ожидание Факт Результат
7.1 limit=2 ?limit=2 count=2 count=2 ✓ PASS
7.2 limit=0 (default) ?limit=0 count=50 count=50 ✓ PASS
7.3 limit отрицательный ?limit=-1 count=50 (default) count=50 ✓ PASS
7.4 limit cap ?limit=9999, >1000 записей count=1000 count=1000 ✓ PASS
7.5 Фильтр device_id ?device_id=dev-02 только записи dev-02 count=21, все dev-02 ✓ PASS
7.6 Несуществующий device_id ?device_id=ghost count=0, items=[] count=0 ✓ PASS
7.7 Несуществующий namespace /namespaces/unknown-ns-xyz/... count=0, items=[] (нет tenant DB) count=0 ✓ PASS

Группа 8 — Нагрузочные тесты

# Тест Параметры Ожидание Результат
8.1 100 сообщений подряд 1 устройство, 100 publish все 100 в Postgres PASS (count=103)
8.2 900 сообщений подряд 1 устройство, 900 publish все в Postgres PASS
8.3 3 устройства × 20 сообщений параллельно изоляция: каждый dev получил ровно 21 PASS
8.4 10 create+delete race 10 параллельных goroutine все 204, нет утечек PASS

Замечание: в тесте 8.1 и 8.2 использовался один и тот же device. Финальный count dev-02/03/04 = 21 (1 из предыдущей сессии + 20 нагрузочных).


Итоги

Всего тестов: 33
Пройдено: 33 / 33 (100%)
Найдено багов: 3 (все исправлены)

Баг Серьёзность Версия обнаружения Версия фикса
BUG-01: MQTTAuth Get→List по Spec.DeviceID Critical v0.2.3 v0.2.4
BUG-02: PG15+ GRANT перед CREATE DATABASE OWNER Critical v0.2.4 v0.2.5
BUG-03: query param devicedevice_id Minor v0.2.5 v0.2.6

Финальная версия: naeel/iot-operator:v0.2.6


История версий

Версия Дата Изменение
v0.2.3 2026-04-12 Деплой: bridge auth fix (bridge username без _)
v0.2.4 2026-04-12 Fix BUG-01: MQTTAuth List вместо Get
v0.2.5 2026-04-12 Fix BUG-02: GRANT перед CREATE DATABASE (PG15+)
v0.2.6 2026-04-21 Fix BUG-03: query param device→device_id
EOF cat > /home/naeel/terra/IoT/doc/testing-v0.2.6.md << 'EOF'

IoT — Отчёт по тестированию v0.2.5 / v0.2.6

Дата: 2026-04-21 Кластер: iot-naeel, namespace: sless Образ: naeel/iot-operator:v0.2.6 Тестировал: GitHub Copilot (Claude Sonnet 4.6)


Контекст

В ходе двух сессий (2026-04-12 и 2026-04-21) после деплоя v0.2.3 были обнаружены и исправлены три бага, выпущены v0.2.4 → v0.2.5 → v0.2.6. Затем проведено расширенное тестирование: 7 категорий, 33 теста.


Баги, найденные в процессе тестирования

BUG-01 — MQTTAuth ищет IoTDevice по Name=deviceID (критический)

Версия: v0.2.3
Компонент: internal/api/handler/iot_device_handler.go, функция MQTTAuth
Симптом: MQTT CONNECT возвращал Connection Refused: not authorised (5) для всех устройств.
Причина: Хэндлер вызывал h.K8s.Get(client.ObjectKey{Name: deviceID}), где deviceID — это Spec.DeviceID (e2e-test-01), но имя K8s объекта IoTDevice может быть другим (e2e-test-device). Объект не находился → NotFounddeny.
Диагностика:

# Auth endpoint напрямую возвращал deny при правильном пароле:
curl -s -X POST http://iot-operator.sless.svc:9090/internal/mqtt/auth \
  -d password:<correct-pass>
# {"result":"deny"}

# Secret существует и пароль совпадает:
kubectl get secret iot-e2e-test-01 -n sless -o jsonpath="{.data.mqtt-password}" | base64 -d
# fa8180c07ffc1d665d21e95a443235a93926f16e9d98c319539263f05a333ef0

# IoTDevice объект называется e2e-test-device, а не e2e-test-01:
kubectl get iotdevice -n sless
# NAME              DEVICEID
# e2e-test-device   e2e-test-01

Фикс: Заменён Get на List + фильтр по Spec.DeviceID == deviceID.
Версия фикса: v0.2.4


BUG-02 — PG15+: CREATE DATABASE OWNER требует GRANT (критический)

Версия: v0.2.4
Компонент: internal/storage/iotpg/iot_telemetry_store.go, функция EnsureTenantDB
Симптом: sqs-consumer не мог создать tenant DB для первого сообщения от нового namespace.

level=ERROR msg="process telemetry message"
err="iotpg: create database tenant_sless: pq: must be able to SET ROLE \"tenant_sless\" (42501)"

Причина: PostgreSQL 15+ требует GRANT role TO CURRENT_USER перед CREATE DATABASE ... OWNER role. В PG17 это обязательно.
Фикс: Добавлен GRANT {userName} TO CURRENT_USER перед CREATE DATABASE.
Версия фикса: v0.2.5


BUG-03 — Query param ?device= вместо ?device_id= (minor)

Версия: v0.2.5
Компонент: internal/api/handler/iot_telemetry_handler.go, функция ListIoTTelemetry
Симптом: GET /telemetry?device_id=dev-02 возвращал все записи (фильтр не работал).

curl ".../telemetry?device_id=dev-02"
# {"count":6,...}  ← должно быть count:1

Причина: Хэндлер читал r.URL.Query().Get("device"), а не "device_id". Несоответствие с именем поля в ответе (device_id) и документацией.
Фикс: Параметр переименован в device_id.
Версия фикса: v0.2.6


Состояние кластера на момент тестирования

Кластер:   iot-naeel (185.247.187.149:6443)
Namespace: sless
Дата:      2026-04-21

ПОДЫ:
  emqx-858d99fcc7-mgjjx              1/1  Running  8d
  iot-operator-6b4fcc47cc-b5rwf      1/1  Running  ~30m
  iot-mqtt-bridge-74d5c4688-5qqqv    1/1  Running  ~16s
  iot-sqs-consumer-d456d4f8c-n8pnk   1/1  Running  ~16s

ОБРАЗЫ (все компоненты):
  naeel/iot-operator:v0.2.6

УСТРОЙСТВА:
  e2e-test-device   device_id=e2e-test-01  phase=Active  enabled=true
  e2e-dev-02        device_id=dev-02       phase=Active  enabled=true
  e2e-dev-03        device_id=dev-03       phase=Active  enabled=true
  e2e-dev-04        device_id=dev-04       phase=Active  enabled=true

ТЕЛЕМЕТРИЯ В POSTGRES (tenant_sless):
  e2e-test-01: >1000 записей (cap API = 1000)
  dev-02:      21 запись
  dev-03:      21 запись
  dev-04:      21 запись

Результаты тестирования

Группа 1 — E2E пайплайн (базовый)

Тесты из сессии 2026-04-12, подтверждены после деплоя v0.2.5.

# Тест Команда / Действие Ожидание Результат
1.1 Создание устройства POST /v1/namespaces/sless/iot/devices 201, phase после reconcile = Active PASS
1.2 Получение credentials GET /devices/e2e-test-device mqtt_username, mqtt_password, secret_name PASS
1.3 MQTT CONNECT mosquitto_pub -u sless_e2e-test-01 -P <pass> CONNACK(0) PASS
1.4 MQTT → SQS Проверка логов mqtt-bridge forwarded IoT telemetry to SQS PASS
1.5 SQS → Postgres Проверка логов sqs-consumer telemetry saved to Postgres PASS
1.6 Telemetry API GET /telemetry?device_id=e2e-test-01 count≥1, payload совпадает PASS

Группа 2 — Device API: коды ошибок

# Тест Запрос Ожидание Факт Результат
2.1 POST без name {} 400 name is required 400 ✓ PASS
2.2 POST без device_id {"name":"x"} 400 device_id is required 400 ✓ PASS
2.3 POST дубликат существующее имя 409 iot device already exists 409 ✓ PASS
2.4 GET несуществующий GET /devices/ghost-device 404 iot device not found 404 ✓ PASS
2.5 DELETE несуществующий DELETE /devices/ghost-device 404 iot device not found 404 ✓ PASS
2.6 PATCH без enabled {} 400 enabled field is required 400 ✓ PASS
2.7 POST невалидный JSON not-json 400 invalid JSON: ... 400 ✓ PASS

Группа 3 — MQTT Auth: edge cases

Все запросы идут на POST /internal/mqtt/auth. Корректный ответ всегда HTTP 200. Результат определяется полем result в теле: "allow" или "deny".

# Тест Входные данные Ожидание Результат
3.1 Неверный пароль правильный username, неверный pass deny PASS
3.2 Устройство disabled правильные credentials, enabled=false deny PASS
3.3 Пустое тело {} deny PASS
3.4 Username без _ "username":"nounderscore" deny PASS
3.5 Несуществующий deviceID "username":"sless_ghost-999" deny PASS
3.6 Пустой namespace "username":"_dev01" deny PASS
3.7 Пустой deviceID "username":"sless_" deny PASS
3.8 Bridge: неверный пароль bridge username, wrong pass deny PASS
3.9 Невалидный JSON body not-json deny PASS
3.10 Bridge: правильные credentials MQTT_USERNAME + MQTT_PASSWORD allow PASS
3.11 Правильные device credentials sless_e2e-test-01 + корректный pass allow + ACL rules PASS

ACL в ответе при allow (пример для устройства):

{
  "result": "allow",
  "acl": [
    {"permission":"allow","action":"publish","topic":"sless/telemetry/e2e-test-01"},
    {"permission":"allow","action":"subscribe","topic":"sless/telemetry/e2e-test-01"},
    {"permission":"deny","action":"all","topic":"#"}
  ]
}

Группа 4 — Device lifecycle (PATCH / DELETE)

# Тест Действие Ожидание Результат
4.1 Отключение устройства PATCH enabled=false 200, phase → Disabled PASS
4.2 Auth отключённого auth с правильным паролем deny PASS
4.3 Включение обратно PATCH enabled=true 200, reconcile → phase=Active PASS
4.4 DELETE устройства DELETE /devices/to-delete 204 PASS
4.5 Cascade: Secret удалён kubectl get secret iot-del-01 NotFound PASS
4.6 Cascade: IoTDevice удалён kubectl get iotdevice to-delete NotFound PASS

Группа 5 — ACL изоляция топиков

# Тест Действие Ожидание Результат
5.1 Публикация в свой топик sless/telemetry/e2e-test-01 CONNACK(0), forwarded PASS
5.2 Публикация в чужой топик sless/telemetry/ANOTHER-DEVICE CONNACK(0), но не forwarded PASS

EMQX применяет ACL после аутентификации. При публикации в запрещённый топик клиент получает CONNACK(0) (аутентификация прошла), но PUBLISH тихо отбрасывается. Bridge не получает сообщение — подтверждено отсутствием записи в логах.


Группа 6 — MQTT: нестандартные payload

# Тест Payload Поведение Ожидание Результат
6.1 Невалидный JSON THIS IS NOT JSON AT ALL !@# bridge оборачивает в строку "payload":"THIS IS NOT JSON AT ALL !@#" PASS (by design)
6.2 Пустой payload "" bridge оборачивает в строку "payload":"" PASS (by design)
6.3 Неизвестное устройство любой payload CONNACK(5) not authorised отклонено PASS

Дизайн-решение: bridge намеренно принимает любой payload (не только JSON). Невалидный payload оборачивается в JSON-строку (json.Marshal(string(payload))). Это позволяет передавать raw данные от устройств старых форматов.


Группа 7 — Telemetry API: граничные значения

# Тест Параметры Ожидание Факт Результат
7.1 limit=2 ?limit=2 count=2 count=2 ✓ PASS
7.2 limit=0 (default) ?limit=0 count=50 count=50 ✓ PASS
7.3 limit отрицательный ?limit=-1 count=50 (default) count=50 ✓ PASS
7.4 limit cap ?limit=9999, >1000 записей count=1000 count=1000 ✓ PASS
7.5 Фильтр device_id ?device_id=dev-02 только записи dev-02 count=21, все dev-02 ✓ PASS
7.6 Несуществующий device_id ?device_id=ghost count=0, items=[] count=0 ✓ PASS
7.7 Несуществующий namespace /namespaces/unknown-ns-xyz/... count=0, items=[] (нет tenant DB) count=0 ✓ PASS

Группа 8 — Нагрузочные тесты

# Тест Параметры Ожидание Результат
8.1 100 сообщений подряд 1 устройство, 100 publish все 100 в Postgres PASS (count=103)
8.2 900 сообщений подряд 1 устройство, 900 publish все в Postgres PASS
8.3 3 устройства × 20 сообщений параллельно изоляция: каждый dev получил ровно 21 PASS
8.4 10 create+delete race 10 параллельных goroutine все 204, нет утечек PASS

Замечание: в тесте 8.1 и 8.2 использовался один и тот же device. Финальный count dev-02/03/04 = 21 (1 из предыдущей сессии + 20 нагрузочных).


Итоги

Всего тестов: 33
Пройдено: 33 / 33 (100%)
Найдено багов: 3 (все исправлены)

Баг Серьёзность Версия обнаружения Версия фикса
BUG-01: MQTTAuth Get→List по Spec.DeviceID Critical v0.2.3 v0.2.4
BUG-02: PG15+ GRANT перед CREATE DATABASE OWNER Critical v0.2.4 v0.2.5
BUG-03: query param devicedevice_id Minor v0.2.5 v0.2.6

Финальная версия: naeel/iot-operator:v0.2.6


История версий

Версия Дата Изменение
v0.2.3 2026-04-12 Деплой: bridge auth fix (bridge username без _)
v0.2.4 2026-04-12 Fix BUG-01: MQTTAuth List вместо Get
v0.2.5 2026-04-12 Fix BUG-02: GRANT перед CREATE DATABASE (PG15+)
v0.2.6 2026-04-21 Fix BUG-03: query param device→device_id