- handler/devices_test.go: validateIdentifier (charset/лимиты, запрет _ + # /). - iotpg/dsn_test.go: replaceDSNDatabase (вкл. sslrootcert со слэшем, ошибка для key=value) + safeNamespaceRe. - gofmt для затронутых файлов. go test ./... зелёный.
92 lines
2.4 KiB
Go
92 lines
2.4 KiB
Go
// dsn_test.go — тесты replaceDSNDatabase и safeNamespaceRe.
|
|
//
|
|
// Критичны для изоляции тенантов: неверная замена БД = телеметрия всех тенантов
|
|
// уходит в одну БД; небезопасный namespace = коллизия имени БД/username.
|
|
package iotpg
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestReplaceDSNDatabase(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
dsn string
|
|
newDB string
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "обычный URL-DSN",
|
|
dsn: "postgresql://u:p@h:5432/iot_platform?sslmode=require",
|
|
newDB: "tenant_abc",
|
|
want: "postgresql://u:p@h:5432/tenant_abc?sslmode=require",
|
|
},
|
|
{
|
|
name: "sslrootcert со слэшем в query не ломает замену",
|
|
dsn: "postgresql://u:p@h:5432/iot_platform?sslmode=require&sslrootcert=/etc/ssl/ca.pem",
|
|
newDB: "tenant_abc",
|
|
want: "postgresql://u:p@h:5432/tenant_abc?sslmode=require&sslrootcert=/etc/ssl/ca.pem",
|
|
},
|
|
{
|
|
name: "DSN без query",
|
|
dsn: "postgresql://u:p@h:5432/iot_platform",
|
|
newDB: "tenant_x",
|
|
want: "postgresql://u:p@h:5432/tenant_x",
|
|
},
|
|
{
|
|
name: "key=value DSN — ошибка (иначе смешение тенантов)",
|
|
dsn: "host=h user=u dbname=x sslmode=disable",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "URL без имени БД — ошибка",
|
|
dsn: "postgresql://u:p@h:5432",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
got, err := replaceDSNDatabase(c.dsn, c.newDB)
|
|
if c.wantErr {
|
|
if err == nil {
|
|
t.Fatalf("ожидалась ошибка, получено %q", got)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("неожиданная ошибка: %v", err)
|
|
}
|
|
if got != c.want {
|
|
t.Errorf("got %q, want %q", got, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSafeNamespaceRe(t *testing.T) {
|
|
cases := []struct {
|
|
ns string
|
|
want bool
|
|
}{
|
|
{"myhome", true},
|
|
{"sless-abc123", true},
|
|
{"a", true},
|
|
{"tenant", true},
|
|
{"a_b", false}, // _
|
|
{"a+b", false}, // +
|
|
{"a#b", false}, // #
|
|
{"a/b", false}, // /
|
|
{"", false}, // пусто
|
|
{"-x", false}, // ведущий дефис
|
|
{strings.Repeat("a", 56), true},
|
|
{strings.Repeat("a", 57), false},
|
|
}
|
|
for _, c := range cases {
|
|
if got := safeNamespaceRe.MatchString(c.ns); got != c.want {
|
|
t.Errorf("safeNamespaceRe(%q) = %v, want %v", c.ns, got, c.want)
|
|
}
|
|
}
|
|
}
|