// namespace_test.go — проверяет, что ВСЕ эндпоинты с {namespace} валидируют его // одинаково (400), а не пропускают «мусор» до хранилища. // // Найдено e2e-прогоном 2026-10-11: валидация была только в CreateIoTDevice, // поэтому GET-список с `bad_ns` отдавал 200, а чтение телеметрии падало уже в // хранилище (safeNamespaceRe) с 500. Здесь — регрессия на все обработчики. package handler import ( "net/http" "net/http/httptest" "strings" "testing" "github.com/gorilla/mux" "gitea.services.ngcloud.ru/Nail/IoT/internal/storage/iotpg" ) // badNamespaces — значения, которые ломают MQTT-топик/username или имя БД // tenant_: подчёркивание (коллизия с дефисом), wildcard, слэш, пробел, // ведущий дефис, юникод, пустой. var badNamespaces = []string{"bad_ns", "a+b", "a#b", "a/b", "-abc", "abc def", "имя", ""} func newReq(t *testing.T, ns string) *http.Request { t.Helper() req := httptest.NewRequest(http.MethodGet, "/v1/namespaces/x/iot/devices", nil) return mux.SetURLVars(req, map[string]string{"namespace": ns, "name": "dev1"}) } func TestHandlersRejectInvalidNamespace(t *testing.T) { // Пустой IoTPG-стаб: до хранилища дело дойти не должно (валидация раньше), // поэтому nil-зависимости Devices/IoTPG не используются. h := &Handler{IoTPG: &iotpg.IoTPostgresStore{}} handlers := map[string]func(http.ResponseWriter, *http.Request){ "ListIoTDevices": h.ListIoTDevices, "GetIoTDevice": h.GetIoTDevice, "DeleteIoTDevice": h.DeleteIoTDevice, "UpdateIoTDevice": h.UpdateIoTDevice, "ListIoTTelemetry": h.ListIoTTelemetry, } for name, fn := range handlers { for _, ns := range badNamespaces { if ns == "" { continue // пустой namespace = "default" (валидный) — проверяется отдельно } t.Run(name+"/"+ns, func(t *testing.T) { rec := httptest.NewRecorder() fn(rec, newReq(t, ns)) if rec.Code != http.StatusBadRequest { t.Fatalf("код %d, ожидался 400 (body=%s)", rec.Code, rec.Body.String()) } if !strings.Contains(rec.Body.String(), "namespace") { t.Errorf("в теле нет упоминания namespace: %s", rec.Body.String()) } }) } } } func TestEmptyNamespaceMeansDefault(t *testing.T) { // Пустой {namespace} в пути → "default" (валидное значение), поэтому // проверка должна пройти дальше валидации (здесь упадёт на nil Devices — // это ожидаемо, ловим панику и убеждаемся, что это НЕ 400). defer func() { if r := recover(); r == nil { t.Log("обработчик дошёл до зависимостей без паники") } }() rec := httptest.NewRecorder() h := &Handler{} h.ListIoTDevices(rec, newReq(t, "")) if rec.Code == http.StatusBadRequest { t.Fatalf("пустой namespace не должен давать 400 (body=%s)", rec.Body.String()) } }