хранилище переехало с PocketBase на SQLite со своим каталогом файлов

- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose
  под файловым замком, одна миграция начальной схемы вместо семи прежних
- транспорт переписан на net/http: свои слои, свой ограничитель частоты,
  отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли
- по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета
  читается справа налево, узнавание известного идёт читающим пулом
This commit is contained in:
av
2026-08-23 08:06:04 +03:00
parent 1edf8cb225
commit c9b7765646
118 changed files with 11668 additions and 6679 deletions
+39
View File
@@ -216,3 +216,42 @@ func TestPipelineValidateSeparatesModeFromTypo(t *testing.T) {
}
}
}
// Настройки хранилища проверяются на старте, и каждая ветвь проверки закрывает
// свою поломку. Ноль и отрицательное — опечатка, а не режим: нулевое ожидание
// отдаёт «база занята» первому же воркеру, нулевой пул чтения означает пул без
// предела, а пустой каталог данных оставляет сервис без места под базу и файлы.
// Без проверки такая опечатка проявилась бы отказом под нагрузкой, а не на
// подъёме.
func TestStorageConfigValidate(t *testing.T) {
valid := StorageConfig{DataDir: "data", BusyTimeoutMs: 5000, ReadConnections: 4}
if err := valid.Validate(); err != nil {
t.Fatalf("заполненные настройки отвергнуты: %v", err)
}
cases := []struct {
name string
config StorageConfig
mention string
}{
{"каталог данных не заполнен", StorageConfig{DataDir: "", BusyTimeoutMs: 5000, ReadConnections: 4}, "data_dir"},
{"каталог данных из одних пробелов", StorageConfig{DataDir: " ", BusyTimeoutMs: 5000, ReadConnections: 4}, "data_dir"},
{"ожидание нулевое", StorageConfig{DataDir: "data", BusyTimeoutMs: 0, ReadConnections: 4}, "busy_timeout_ms"},
{"ожидание отрицательное", StorageConfig{DataDir: "data", BusyTimeoutMs: -1, ReadConnections: 4}, "busy_timeout_ms"},
{"пул чтения нулевой", StorageConfig{DataDir: "data", BusyTimeoutMs: 5000, ReadConnections: 0}, "read_connections"},
{"пул чтения отрицательный", StorageConfig{DataDir: "data", BusyTimeoutMs: 5000, ReadConnections: -3}, "read_connections"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := tc.config.Validate()
if err == nil {
t.Fatal("негодная настройка принята: поломка дошла бы до боя")
}
if !strings.Contains(err.Error(), tc.mention) {
t.Fatalf("имя ключа %q не названо: %v", tc.mention, err)
}
})
}
}