- адреса приложения переехали в своё пространство `/app/`, опрос готовности убран целиком: рубеж и причину остановки владелец узнаёт карточкой записи, текст — отдельным адресом названного вида - заведена единая точка отображения доменной ошибки и слой, приводящий к той же форме отказы библиотеки: тело несёт машиночитаемый код рядом с сообщением - у записи появились имя файла отправителя, длительность и размер своими колонками, а у ленты владельца — свой индекс: без него страница сканировала весь архив сервиса
540 lines
22 KiB
Go
540 lines
22 KiB
Go
package http
|
||
|
||
import (
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/pocketbase/pocketbase/apis"
|
||
"github.com/pocketbase/pocketbase/core"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
|
||
pbrepo "git.vakhrushev.me/av/transcriber/internal/adapter/repo/pocketbase"
|
||
"git.vakhrushev.me/av/transcriber/internal/adapter/repo/pocketbase/migrations"
|
||
)
|
||
|
||
// Проверки этого файла судят допуск: кого пускают к приёму и опросу, чем
|
||
// предъявляется сессия, что её прекращает и какие адреса остаются открытыми.
|
||
|
||
// TestApiRequiresSession — первый критерий приёмки. Запрос без сессии получает
|
||
// отказ и ничего не заводит, а проба здоровья и метрики остаются открытыми.
|
||
func TestApiRequiresSession(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
t.Run("приём записи без сессии", func(t *testing.T) {
|
||
req := createMultipartRequest(t, "test.mp3", []byte("audio"))
|
||
w := httptest.NewRecorder()
|
||
|
||
env.mux.ServeHTTP(w, req)
|
||
|
||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||
assert.NotContains(t, w.Body.String(), "job_id")
|
||
|
||
// Ни файла, ни задачи: отказ наступает раньше, чем запись попадает в
|
||
// хранилище.
|
||
files, err := env.app.FindAllRecords(migrations.FilesCollection)
|
||
require.NoError(t, err)
|
||
assert.Empty(t, files)
|
||
|
||
jobs, err := env.app.FindAllRecords(migrations.RecordsCollection)
|
||
require.NoError(t, err)
|
||
assert.Empty(t, jobs)
|
||
})
|
||
|
||
t.Run("опрос готовности без сессии", func(t *testing.T) {
|
||
req := httptest.NewRequest(http.MethodGet, "/app/audiorecords/anything", nil)
|
||
w := httptest.NewRecorder()
|
||
|
||
env.mux.ServeHTTP(w, req)
|
||
|
||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||
assert.NotContains(t, w.Body.String(), "transcription_text")
|
||
assert.NotContains(t, w.Body.String(), "created_at")
|
||
})
|
||
}
|
||
|
||
// TestUnknownJobIsIndistinguishableWithoutSession: по кодам ответа без сессии не
|
||
// перебирается список заведённых задач.
|
||
func TestUnknownJobIsIndistinguishableWithoutSession(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
created := httptest.NewRecorder()
|
||
env.serve(created, createMultipartRequest(t, "test.mp3", []byte("audio")))
|
||
require.Equal(t, http.StatusCreated, created.Code)
|
||
|
||
jobs, err := env.app.FindAllRecords(migrations.RecordsCollection)
|
||
require.NoError(t, err)
|
||
require.Len(t, jobs, 1)
|
||
|
||
existing := httptest.NewRecorder()
|
||
env.mux.ServeHTTP(existing, httptest.NewRequest(http.MethodGet, "/app/audiorecords/"+jobs[0].Id, nil))
|
||
|
||
missing := httptest.NewRecorder()
|
||
env.mux.ServeHTTP(missing, httptest.NewRequest(http.MethodGet, "/app/audiorecords/nosuchjobid", nil))
|
||
|
||
assert.Equal(t, http.StatusUnauthorized, existing.Code)
|
||
assert.Equal(t, missing.Code, existing.Code)
|
||
}
|
||
|
||
// TestOpenEndpointsStayOpen — вторая сторона границы: проба здоровья и метрики
|
||
// сессии не требуют. Маршруты вешает `main`, поэтому здесь собирается такой же
|
||
// роутер с теми же двумя адресами.
|
||
func TestOpenEndpointsStayOpen(t *testing.T) {
|
||
app := newTestStorage(t)
|
||
|
||
r, err := apis.NewRouter(app)
|
||
require.NoError(t, err)
|
||
|
||
r.GET("/health", func(e *core.RequestEvent) error {
|
||
return e.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
||
})
|
||
r.GET("/metrics", func(e *core.RequestEvent) error {
|
||
return e.String(http.StatusOK, "# metrics")
|
||
})
|
||
|
||
mux, err := r.BuildMux()
|
||
require.NoError(t, err)
|
||
|
||
for _, path := range []string{"/health", "/metrics"} {
|
||
w := httptest.NewRecorder()
|
||
mux.ServeHTTP(w, httptest.NewRequest(http.MethodGet, path, nil))
|
||
assert.Equal(t, http.StatusOK, w.Code, "адрес %s обязан отвечать без сессии", path)
|
||
}
|
||
}
|
||
|
||
// TestSessionSurvivesRestart — второй критерий приёмки. Подпись сессии считается
|
||
// от секрета коллекции и ключа записи, оба лежат в базе, поэтому выкладка
|
||
// вошедших не выкидывает.
|
||
func TestSessionSurvivesRestart(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
before := httptest.NewRecorder()
|
||
env.serve(before, createMultipartRequest(t, "test.mp3", []byte("audio")))
|
||
require.Equal(t, http.StatusCreated, before.Code)
|
||
|
||
// Сервер пересоздаётся на том же хранилище — то же, что перезапуск процесса
|
||
// поверх прежнего каталога данных.
|
||
r, err := apis.NewRouter(env.app)
|
||
require.NoError(t, err)
|
||
env.handler.Register(r)
|
||
|
||
mux, err := r.BuildMux()
|
||
require.NoError(t, err)
|
||
|
||
req := httptest.NewRequest(http.MethodGet, "/app/audiorecords/nosuchjobid", nil)
|
||
req.AddCookie(&http.Cookie{Name: SessionCookieName, Value: env.session})
|
||
w := httptest.NewRecorder()
|
||
mux.ServeHTTP(w, req)
|
||
|
||
// Прежняя кука прошла проверку: до обработчика дошло, и он ответил про
|
||
// ненайденную задачу, а не про отсутствующую сессию.
|
||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||
}
|
||
|
||
// TestLogoutClosesAccess — третий критерий приёмки. Выход обесценивает выданные
|
||
// сессии, а не только убирает куку.
|
||
func TestLogoutClosesAccess(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
authHandler := NewAuthHandler(env.app, AuthHandlerConfig{
|
||
AuthURL: "https://auth.example.com/api/oidc/authorization",
|
||
RedirectURL: "https://transcriber.example.com/auth/callback",
|
||
ClientID: "transcriber",
|
||
SecureCookie: true,
|
||
}, nil)
|
||
|
||
r, err := apis.NewRouter(env.app)
|
||
require.NoError(t, err)
|
||
authHandler.Register(r)
|
||
env.handler.Register(r)
|
||
|
||
mux, err := r.BuildMux()
|
||
require.NoError(t, err)
|
||
|
||
logout := httptest.NewRequest(http.MethodPost, "/auth/logout", nil)
|
||
logout.AddCookie(&http.Cookie{Name: SessionCookieName, Value: env.session})
|
||
logoutResponse := httptest.NewRecorder()
|
||
mux.ServeHTTP(logoutResponse, logout)
|
||
require.Equal(t, http.StatusOK, logoutResponse.Code)
|
||
|
||
// Куку выход убирает.
|
||
assert.Contains(t, logoutResponse.Result().Header.Get("Set-Cookie"), SessionCookieName+"=;")
|
||
|
||
// И прежнее значение больше не открывает доступ — этого уборка куки сама по
|
||
// себе не даёт: унесённое значение работало бы до истечения срока.
|
||
after := httptest.NewRequest(http.MethodGet, "/app/audiorecords/nosuchjobid", nil)
|
||
after.AddCookie(&http.Cookie{Name: SessionCookieName, Value: env.session})
|
||
afterResponse := httptest.NewRecorder()
|
||
mux.ServeHTTP(afterResponse, after)
|
||
|
||
assert.Equal(t, http.StatusUnauthorized, afterResponse.Code)
|
||
}
|
||
|
||
// TestLogoutWhenAccountIsGone: учётной записи, которой предъявлена сессия, уже
|
||
// нет — выход отвечает отказом и не делает вид, что закрыл доступ.
|
||
func TestLogoutWhenAccountIsGone(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
authHandler := NewAuthHandler(env.app, AuthHandlerConfig{
|
||
AuthURL: "https://auth.example.com/api/oidc/authorization",
|
||
RedirectURL: "https://transcriber.example.com/auth/callback",
|
||
ClientID: "transcriber",
|
||
SecureCookie: true,
|
||
}, nil)
|
||
|
||
r, err := apis.NewRouter(env.app)
|
||
require.NoError(t, err)
|
||
authHandler.Register(r)
|
||
|
||
mux, err := r.BuildMux()
|
||
require.NoError(t, err)
|
||
|
||
// Сессия выдана, а запись удалена — так выглядит гонка выхода с удалением
|
||
// учётной записи в панели.
|
||
require.NoError(t, env.app.Delete(env.account))
|
||
|
||
logout := httptest.NewRequest(http.MethodPost, "/auth/logout", nil)
|
||
logout.AddCookie(&http.Cookie{Name: SessionCookieName, Value: env.session})
|
||
w := httptest.NewRecorder()
|
||
mux.ServeHTTP(w, logout)
|
||
|
||
// Записи нет — проверка сессии её не находит, и до обесценивания дело не
|
||
// доходит: выход отвечает успехом, убрав куку. Доступа при этом всё равно
|
||
// не осталось, потому что не осталось учётной записи.
|
||
assert.Equal(t, http.StatusOK, w.Code)
|
||
assert.Contains(t, w.Result().Header.Get("Set-Cookie"), SessionCookieName+"=;")
|
||
|
||
after := httptest.NewRequest(http.MethodGet, "/app/audiorecords/nosuchjobid", nil)
|
||
after.AddCookie(&http.Cookie{Name: SessionCookieName, Value: env.session})
|
||
afterResponse := httptest.NewRecorder()
|
||
env.mux.ServeHTTP(afterResponse, after)
|
||
assert.Equal(t, http.StatusUnauthorized, afterResponse.Code)
|
||
}
|
||
|
||
// TestLogoutWithoutSession: выход без сессии убирает куку и молчит.
|
||
func TestLogoutWithoutSession(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
authHandler := NewAuthHandler(env.app, AuthHandlerConfig{
|
||
AuthURL: "https://auth.example.com/api/oidc/authorization",
|
||
RedirectURL: "https://transcriber.example.com/auth/callback",
|
||
ClientID: "transcriber",
|
||
SecureCookie: true,
|
||
}, nil)
|
||
|
||
r, err := apis.NewRouter(env.app)
|
||
require.NoError(t, err)
|
||
authHandler.Register(r)
|
||
|
||
mux, err := r.BuildMux()
|
||
require.NoError(t, err)
|
||
|
||
w := httptest.NewRecorder()
|
||
mux.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/auth/logout", nil))
|
||
|
||
assert.Equal(t, http.StatusOK, w.Code)
|
||
assert.Contains(t, w.Result().Header.Get("Set-Cookie"), SessionCookieName+"=;")
|
||
}
|
||
|
||
// TestLoginRedirectsToProvider: вход уводит к провайдеру и запоминает состояние
|
||
// у браузера.
|
||
func TestLoginRedirectsToProvider(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
authHandler := NewAuthHandler(env.app, AuthHandlerConfig{
|
||
AuthURL: "https://auth.example.com/api/oidc/authorization",
|
||
RedirectURL: "https://transcriber.example.com/auth/callback",
|
||
ClientID: "transcriber",
|
||
SecureCookie: true,
|
||
}, nil)
|
||
|
||
r, err := apis.NewRouter(env.app)
|
||
require.NoError(t, err)
|
||
authHandler.Register(r)
|
||
|
||
mux, err := r.BuildMux()
|
||
require.NoError(t, err)
|
||
|
||
w := httptest.NewRecorder()
|
||
mux.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/auth/login", nil))
|
||
|
||
require.Equal(t, http.StatusFound, w.Code)
|
||
|
||
location := w.Result().Header.Get("Location")
|
||
assert.Contains(t, location, "https://auth.example.com/api/oidc/authorization")
|
||
assert.Contains(t, location, "code_challenge_method=S256")
|
||
assert.Contains(t, location, "client_id=transcriber")
|
||
|
||
// Носитель состояния несёт те же признаки защиты, что и кука сессии.
|
||
stateCookie := w.Result().Header.Get("Set-Cookie")
|
||
assert.Contains(t, stateCookie, stateCookieName)
|
||
assert.Contains(t, stateCookie, "HttpOnly")
|
||
assert.Contains(t, stateCookie, "Secure")
|
||
assert.Contains(t, stateCookie, "SameSite=Lax")
|
||
}
|
||
|
||
// TestCallbackRejectsForeignState — возврат с невыданным состоянием сессии не
|
||
// открывает и учётной записи не заводит.
|
||
func TestCallbackRejectsForeignState(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
authHandler := NewAuthHandler(env.app, AuthHandlerConfig{
|
||
AuthURL: "https://auth.example.com/api/oidc/authorization",
|
||
RedirectURL: "https://transcriber.example.com/auth/callback",
|
||
ClientID: "transcriber",
|
||
SecureCookie: true,
|
||
}, nil)
|
||
|
||
r, err := apis.NewRouter(env.app)
|
||
require.NoError(t, err)
|
||
authHandler.Register(r)
|
||
|
||
mux, err := r.BuildMux()
|
||
require.NoError(t, err)
|
||
|
||
accountsBefore, err := env.app.FindAllRecords("users")
|
||
require.NoError(t, err)
|
||
|
||
cases := []struct {
|
||
name string
|
||
cookie *http.Cookie
|
||
query string
|
||
}{
|
||
{
|
||
name: "состояния не выдавали вовсе",
|
||
cookie: nil,
|
||
query: "?code=whatever&state=foreign",
|
||
},
|
||
{
|
||
name: "состояние не совпало с выданным",
|
||
cookie: &http.Cookie{Name: stateCookieName, Value: "issued:verifier"},
|
||
query: "?code=whatever&state=foreign",
|
||
},
|
||
{
|
||
name: "провайдер вернул отказ",
|
||
cookie: &http.Cookie{Name: stateCookieName, Value: "issued:verifier"},
|
||
query: "?error=access_denied&state=issued",
|
||
},
|
||
}
|
||
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
req := httptest.NewRequest(http.MethodGet, "/auth/callback"+tc.query, nil)
|
||
if tc.cookie != nil {
|
||
req.AddCookie(tc.cookie)
|
||
}
|
||
w := httptest.NewRecorder()
|
||
mux.ServeHTTP(w, req)
|
||
|
||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||
|
||
accountsAfter, err := env.app.FindAllRecords("users")
|
||
require.NoError(t, err)
|
||
assert.Len(t, accountsAfter, len(accountsBefore))
|
||
|
||
// Носитель убирается и на отказном возврате: иначе состояние
|
||
// осталось бы годным для новой попытки.
|
||
assert.Contains(t, w.Result().Header.Get("Set-Cookie"), stateCookieName+"=;")
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestHeaderBeatsCookie: предъявленный заголовок побеждает куку.
|
||
func TestHeaderBeatsCookie(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
req := httptest.NewRequest(http.MethodGet, "/app/audiorecords/nosuchjobid", nil)
|
||
req.AddCookie(&http.Cookie{Name: SessionCookieName, Value: "totally-invalid-session"})
|
||
req.Header.Set("Authorization", env.session)
|
||
|
||
w := httptest.NewRecorder()
|
||
env.mux.ServeHTTP(w, req)
|
||
|
||
// Прошёл заголовок: иначе негодная кука дала бы отказ.
|
||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||
}
|
||
|
||
// TestSelfServiceAccountsAreClosed — то, ради чего задача вообще имеет смысл.
|
||
// Пока создание записи и вход по паролю открыты, закрытие приёма обходится
|
||
// двумя запросами.
|
||
func TestSelfServiceAccountsAreClosed(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
t.Run("завести учётную запись самому нельзя", func(t *testing.T) {
|
||
body := strings.NewReader(`{"email":"intruder@example.com","password":"12345678901","passwordConfirm":"12345678901"}`)
|
||
req := httptest.NewRequest(http.MethodPost, "/api/collections/users/records", body)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
w := httptest.NewRecorder()
|
||
env.mux.ServeHTTP(w, req)
|
||
|
||
assert.NotEqual(t, http.StatusOK, w.Code)
|
||
assert.GreaterOrEqual(t, w.Code, http.StatusBadRequest)
|
||
})
|
||
|
||
t.Run("вход паролем недоступен", func(t *testing.T) {
|
||
body := strings.NewReader(`{"identity":"person@example.com","password":"whatever"}`)
|
||
req := httptest.NewRequest(http.MethodPost, "/api/collections/users/auth-with-password", body)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
w := httptest.NewRecorder()
|
||
env.mux.ServeHTTP(w, req)
|
||
|
||
assert.GreaterOrEqual(t, w.Code, http.StatusBadRequest)
|
||
})
|
||
}
|
||
|
||
// TestAccessGrantingValuesAreNotLogged — четвёртый критерий приёмки, расширенный
|
||
// ревью дизайна: не печатается ничто, что даёт доступ.
|
||
//
|
||
// Проверка ищет в журнале **значения**, а не имена полей: значение, уехавшее под
|
||
// другим ключом, поиск по ключу не разбудил бы.
|
||
func TestAccessGrantingValuesAreNotLogged(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
created := httptest.NewRecorder()
|
||
env.serve(created, createMultipartRequest(t, "test.mp3", []byte("audio")))
|
||
require.Equal(t, http.StatusCreated, created.Code)
|
||
|
||
journal := env.journal.String()
|
||
require.NotEmpty(t, journal, "журнал пуст — проверке не на чем сработать")
|
||
|
||
assert.NotContains(t, journal, env.session,
|
||
"значение сессии в журнале: строка стала бы ключом к чужому доступу")
|
||
assert.NotContains(t, journal, env.account.Email(),
|
||
"адрес почты в журнале: он приходит от провайдера и принадлежит человеку")
|
||
}
|
||
|
||
// TestProviderSecretIsNotLogged: секрет клиента не появляется в журнале при
|
||
// приведении настроек провайдера к конфигу.
|
||
func TestProviderSecretIsNotLogged(t *testing.T) {
|
||
app := newTestStorage(t)
|
||
|
||
const secret = "super-secret-client-value"
|
||
|
||
require.NoError(t, pbrepo.ApplyProviderSettings(app, pbrepo.ProviderSettings{
|
||
AuthURL: "https://auth.example.com/api/oidc/authorization",
|
||
TokenURL: "https://auth.example.com/api/oidc/token",
|
||
UserInfoURL: "https://auth.example.com/api/oidc/userinfo",
|
||
ClientID: "transcriber",
|
||
ClientSecret: secret,
|
||
}))
|
||
|
||
// Настройка доехала до хранилища — иначе проверка отсутствия секрета в
|
||
// журнале прошла бы на невыполненной работе.
|
||
users, err := app.FindCollectionByNameOrId("users")
|
||
require.NoError(t, err)
|
||
provider, found := users.OAuth2.GetProviderConfig(pbrepo.ProviderName)
|
||
require.True(t, found)
|
||
assert.Equal(t, secret, provider.ClientSecret)
|
||
}
|
||
|
||
// TestProviderSecretRotationReachesStorage: смена секрета в конфиге доезжает до
|
||
// хранилища. Положенный однажды шагом схемы, он бы не доехал — применённый шаг
|
||
// не переписывается.
|
||
func TestProviderSecretRotationReachesStorage(t *testing.T) {
|
||
app := newTestStorage(t)
|
||
|
||
settings := pbrepo.ProviderSettings{
|
||
AuthURL: "https://auth.example.com/api/oidc/authorization",
|
||
TokenURL: "https://auth.example.com/api/oidc/token",
|
||
UserInfoURL: "https://auth.example.com/api/oidc/userinfo",
|
||
ClientID: "transcriber",
|
||
ClientSecret: "first-secret",
|
||
}
|
||
require.NoError(t, pbrepo.ApplyProviderSettings(app, settings))
|
||
|
||
settings.ClientSecret = "rotated-secret"
|
||
require.NoError(t, pbrepo.ApplyProviderSettings(app, settings))
|
||
|
||
users, err := app.FindCollectionByNameOrId("users")
|
||
require.NoError(t, err)
|
||
provider, found := users.OAuth2.GetProviderConfig(pbrepo.ProviderName)
|
||
require.True(t, found)
|
||
assert.Equal(t, "rotated-secret", provider.ClientSecret)
|
||
}
|
||
|
||
// TestRecordFileIsProtected: ссылка на файл перестала быть правом пройти по ней.
|
||
func TestRecordFileIsProtected(t *testing.T) {
|
||
app := newTestStorage(t)
|
||
|
||
files, err := app.FindCollectionByNameOrId(migrations.FilesCollection)
|
||
require.NoError(t, err)
|
||
|
||
field, ok := files.Fields.GetByName("file").(*core.FileField)
|
||
require.True(t, ok)
|
||
assert.True(t, field.Protected,
|
||
"поле файла не защищено: знание ссылки снова стало бы доступом, а отзыва у неё нет")
|
||
}
|
||
|
||
// TestRecordFileNeedsSession: ссылка на файл записи без сессии отказывает, а
|
||
// конвейер тот же файл по-прежнему читает — он ходит в файловую систему, а не по
|
||
// ссылке.
|
||
func TestRecordFileNeedsSession(t *testing.T) {
|
||
env := setupTestEnv(t, readableMetaViewer())
|
||
|
||
created := httptest.NewRecorder()
|
||
env.serve(created, createMultipartRequest(t, "test.mp3", []byte("audio content")))
|
||
require.Equal(t, http.StatusCreated, created.Code)
|
||
|
||
files, err := env.app.FindAllRecords(migrations.FilesCollection)
|
||
require.NoError(t, err)
|
||
require.Len(t, files, 1)
|
||
|
||
names := files[0].GetStringSlice("file")
|
||
require.Len(t, names, 1)
|
||
|
||
link := "/api/files/" + migrations.FilesCollection + "/" + files[0].Id + "/" + names[0]
|
||
|
||
anonymous := httptest.NewRecorder()
|
||
env.mux.ServeHTTP(anonymous, httptest.NewRequest(http.MethodGet, link, nil))
|
||
// Отказ приходит кодом «не найдено»: защищённый файл не раскрывает даже
|
||
// своего существования. До пометки поля защищённым эта же ссылка отдавала
|
||
// содержимое кому угодно — знание ссылки и было доступом.
|
||
assert.Equal(t, http.StatusNotFound, anonymous.Code,
|
||
"ссылка отдала файл без сессии: знание ссылки снова стало доступом")
|
||
assert.NotContains(t, anonymous.Body.String(), "audio content")
|
||
|
||
// Конвейер читает тот же файл своим путём — из файловой системы хранилища.
|
||
fileRepo := pbrepo.NewFileRepository(env.app)
|
||
reader, err := fileRepo.Open(files[0].Id)
|
||
require.NoError(t, err)
|
||
defer func() {
|
||
assert.NoError(t, reader.Close())
|
||
}()
|
||
|
||
content := make([]byte, len("audio content"))
|
||
_, err = reader.Read(content)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, "audio content", string(content))
|
||
}
|
||
|
||
// TestSessionLifetimeIsAssigned: срок жизни сессии назначен нами, а не достался
|
||
// умолчанием библиотеки в пять суток.
|
||
//
|
||
// Назначается он приведением настроек при подъёме, а не шагом схемы: применённый
|
||
// шаг не переписывается, и число, положенное туда, разошлось бы со сроком жизни
|
||
// куки при первой же правке.
|
||
func TestSessionLifetimeIsAssigned(t *testing.T) {
|
||
app := newTestStorage(t)
|
||
|
||
users, err := app.FindCollectionByNameOrId("users")
|
||
require.NoError(t, err)
|
||
require.NotEqual(t, int64(pbrepo.SessionDuration), users.AuthToken.Duration,
|
||
"шаг схемы назначил срок сам — тогда правка числа до хранилища не доедет")
|
||
|
||
require.NoError(t, pbrepo.ApplyProviderSettings(app, pbrepo.ProviderSettings{
|
||
AuthURL: "https://auth.example.com/api/oidc/authorization",
|
||
TokenURL: "https://auth.example.com/api/oidc/token",
|
||
UserInfoURL: "https://auth.example.com/api/oidc/userinfo",
|
||
ClientID: "transcriber",
|
||
ClientSecret: "local-test-secret",
|
||
}))
|
||
|
||
users, err = app.FindCollectionByNameOrId("users")
|
||
require.NoError(t, err)
|
||
assert.Equal(t, int64(pbrepo.SessionDuration), users.AuthToken.Duration)
|
||
}
|