package pocketbase import ( "strings" "testing" "github.com/pocketbase/pocketbase/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "git.vakhrushev.me/av/transcriber/internal/adapter/repo/pocketbase/migrations" ) // newTestStorage поднимает хранилище на пустом каталоге и накатывает схему — // тем же путём, каким это делает сервис при старте. func newTestStorage(t *testing.T) core.App { t.Helper() app, err := New(t.TempDir()) require.NoError(t, err) t.Cleanup(func() { if err := app.ResetBootstrapState(); err != nil { t.Logf("не удалось закрыть хранилище: %v", err) } }) return app } // Критерий приёмки 10. Содержимое записи закрыто во всех коллекциях, куда оно // переехало. // // Прежде содержимое лежало одной колонкой задачи, и закрывала его одна норма про // файл записи. Теперь оно живёт в шести коллекциях, и реализация, следующая // только прежней норме, завела бы поле вложения с умолчанием библиотеки: ссылка // на сырой ответ провайдера — а это полный текст речи — отдавала бы его любому, // кто её знает, без сессии. func TestRecordContentIsClosedEverywhere(t *testing.T) { app := newTestStorage(t) // Правило просмотра остаётся незаданным, то есть «только владелец панели». // Содержимое отдаёт собственный адрес сервиса, а не поверхность хранилища; // непустое правило открыло бы перечисление коллекции впрок. for _, name := range []string{ migrations.RecordsCollection, migrations.TextsCollection, migrations.StructuresCollection, migrations.RecognitionsCollection, migrations.RecordEventsCollection, migrations.TopicsCollection, } { collection, err := app.FindCollectionByNameOrId(name) require.NoError(t, err, "коллекция %s заведена шагом схемы", name) assert.Nil(t, collection.ListRule, "перечисление %s закрыто", name) assert.Nil(t, collection.ViewRule, "чтение %s закрыто", name) assert.Nil(t, collection.CreateRule, "заведение записи в %s закрыто", name) assert.Nil(t, collection.UpdateRule, "правка %s закрыта", name) assert.Nil(t, collection.DeleteRule, "удаление из %s закрыто", name) } // А поле вложения помечено защищённым: без пометки ссылка открывает // содержимое любому, кто её знает, и знание ссылки становится правом. recognitions, err := app.FindCollectionByNameOrId(migrations.RecognitionsCollection) require.NoError(t, err) field := recognitions.Fields.GetByName("payload") require.NotNil(t, field, "поле сохранённого ответа заведено") file, ok := field.(*core.FileField) require.True(t, ok, "сохранённый ответ лежит вложением, а не колонкой") assert.True(t, file.Protected, "поле вложения защищено") } // Прежняя коллекция задач уходит вместе с моделью: данных под ней не было, а // пустая копия висела бы в панели вторым домом для понятия, которого больше нет. // Поверхность коллекции пользователей закрыта наглухо — все пять правил. // // Проверка стоит отдельно от соседней намеренно: та сторожит коллекции, которые // заводит наш шаг схемы, а эту заводит системный шаг библиотеки, и её умолчания // открывают владельцу записи чтение, правку и удаление собственной строки. Пока // узнавание жило под корнем приложения, до этой поверхности браузер не // дотягивался вовсе; с узнаванием по заголовку она достижима, а ключ учётной // записи лежит здесь обычной колонкой — правка своей записи и есть захват чужого // имени. func TestUsersCollectionSurfaceIsClosed(t *testing.T) { app := newTestStorage(t) users, err := app.FindCollectionByNameOrId(migrations.UsersCollection) require.NoError(t, err) assert.Nil(t, users.ListRule, "перечисление учётных записей закрыто") assert.Nil(t, users.ViewRule, "чтение учётной записи закрыто") assert.Nil(t, users.CreateRule, "заведение учётной записи снаружи закрыто") assert.Nil(t, users.UpdateRule, "правка учётной записи снаружи закрыта") assert.Nil(t, users.DeleteRule, "удаление учётной записи снаружи закрыто") // Собственные способы войти выключены там же: без этого узнавание по // заголовку обходится двумя запросами — завести себе запись и войти паролем. assert.False(t, users.PasswordAuth.Enabled, "вход по паролю выключен") assert.False(t, users.OTP.Enabled, "вход по одноразовому коду выключен") assert.False(t, users.OAuth2.Enabled, "обмен кода у внешнего провайдера выключен") assert.Empty(t, users.OAuth2.Providers, "настроенных провайдеров не осталось") } func TestFormerJobsCollectionIsGone(t *testing.T) { app := newTestStorage(t) _, err := app.FindCollectionByNameOrId(migrations.JobsCollection) assert.Error(t, err, "прежней коллекции задач не осталось") } // Пара «запись и вид» уникальна: повтор прерванного шага не заводит второго // комплекта строк, и вопрос «какой текст отдавать человеку» не становится // вопросом порядка записи. func TestAppendicesAreUniquePerRecord(t *testing.T) { app := newTestStorage(t) indexes := map[string][]string{ migrations.TextsCollection: {"idx_texts_record_kind"}, migrations.StructuresCollection: {"idx_structures_record_version"}, migrations.TopicsCollection: {"idx_topics_owner_name"}, } for name, expected := range indexes { collection, err := app.FindCollectionByNameOrId(name) require.NoError(t, err) for _, index := range expected { var found bool for _, declared := range collection.Indexes { if strings.Contains(declared, index) && strings.Contains(declared, "UNIQUE") { found = true } } assert.Truef(t, found, "у %s есть уникальный индекс %s", name, index) } } }