79 lines
3.3 KiB
Go
79 lines
3.3 KiB
Go
// 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_<ns>: подчёркивание (коллизия с дефисом), 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())
|
||
}
|
||
}
|