a5205cc054
- миграция 00002: users, sessions, totp_secrets, webauthn_credentials, security_events, guilds, guild_members, roles, member_roles, channels, channel_overrides, audit_log + дефолтные instance_settings (AGENT.md 6.1) - internal/store: Snowflake-идентификаторы, CRUD пользователей и сессий (ротация, step-up, logout-all), TOTP и события безопасности, серверы, участники, роли, комнаты и оверрайды, настройки инстанса и аудит - internal/permissions: 37 прав битмаской, вычисление по правилам §6.2 (баз role @user → оверрайды ролей → оверрайд пользователя → ADMINISTRATOR), иерархия ролей и участников, тайм-ауты, обход для инстанс-админа, LRU-кэш с инвалидацией - internal/source: адаптер permissions.Source поверх store - тесты: 18 unit-тестов прав + интеграционный набор на реальной SQLite (приватная комната, модератор, владелец, инстанс-админ, тайм-аут) - golangci: обоснованное исключение gosec для пакета store (конверсии Snowflake и сборка SQL из константных шаблонов)
108 lines
2.9 KiB
Go
108 lines
2.9 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestOpenAppliesPragmasAndMigrates(t *testing.T) {
|
|
ctx := context.Background()
|
|
path := filepath.Join(t.TempDir(), "db", "glchat.db")
|
|
|
|
db, err := Open(ctx, Options{Path: path, ReadPool: 2, Migrate: true})
|
|
if err != nil {
|
|
t.Fatalf("Open() returned error: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
journal, err := db.JournalMode(ctx)
|
|
if err != nil {
|
|
t.Fatalf("JournalMode() returned error: %v", err)
|
|
}
|
|
if journal != "wal" {
|
|
t.Errorf("journal mode = %q, want wal", journal)
|
|
}
|
|
|
|
version, err := db.SchemaVersion(ctx)
|
|
if err != nil {
|
|
t.Fatalf("SchemaVersion() returned error: %v", err)
|
|
}
|
|
if version < 1 {
|
|
t.Errorf("schema version = %d, want >= 1", version)
|
|
}
|
|
|
|
var foreignKeys int
|
|
if err := db.Writer.QueryRowContext(ctx, "PRAGMA foreign_keys").Scan(&foreignKeys); err != nil {
|
|
t.Fatalf("read foreign_keys pragma: %v", err)
|
|
}
|
|
if foreignKeys != 1 {
|
|
t.Errorf("foreign_keys = %d, want 1", foreignKeys)
|
|
}
|
|
|
|
var baseline string
|
|
if err := db.Reader.QueryRowContext(ctx,
|
|
`SELECT value FROM instance_settings WHERE key = 'registration_enabled'`,
|
|
).Scan(&baseline); err != nil {
|
|
t.Fatalf("read instance setting: %v", err)
|
|
}
|
|
if baseline != "true" {
|
|
t.Errorf("registration_enabled = %q, want true", baseline)
|
|
}
|
|
}
|
|
|
|
func TestMigrateIsIdempotent(t *testing.T) {
|
|
ctx := context.Background()
|
|
path := filepath.Join(t.TempDir(), "glchat.db")
|
|
|
|
db, err := Open(ctx, Options{Path: path, ReadPool: 2, Migrate: true})
|
|
if err != nil {
|
|
t.Fatalf("first Open() returned error: %v", err)
|
|
}
|
|
if err := db.Migrate(ctx); err != nil {
|
|
t.Fatalf("second Migrate() returned error: %v", err)
|
|
}
|
|
|
|
var applied int
|
|
if err := db.Reader.QueryRowContext(ctx,
|
|
`SELECT COUNT(*) FROM goose_db_version WHERE version_id = 1 AND is_applied = 1`,
|
|
).Scan(&applied); err != nil {
|
|
t.Fatalf("count applied migrations: %v", err)
|
|
}
|
|
if applied != 1 {
|
|
t.Errorf("migration 1 applied %d times, want 1", applied)
|
|
}
|
|
|
|
// Baseline-миграция должна применяться ровно один раз, независимо от их числа.
|
|
version, err := db.SchemaVersion(ctx)
|
|
if err != nil {
|
|
t.Fatalf("SchemaVersion: %v", err)
|
|
}
|
|
if version < 2 {
|
|
t.Errorf("schema version = %d, want >= 2 after phase 1 migration", version)
|
|
}
|
|
if err := db.Close(); err != nil {
|
|
t.Fatalf("Close() returned error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOpenRejectsEmptyPath(t *testing.T) {
|
|
if _, err := Open(context.Background(), Options{}); err == nil {
|
|
t.Fatal("Open() accepted an empty path")
|
|
}
|
|
}
|
|
|
|
func TestReadyFailsOnClosedDatabase(t *testing.T) {
|
|
ctx := context.Background()
|
|
db, err := Open(ctx, Options{Path: filepath.Join(t.TempDir(), "glchat.db"), Migrate: true})
|
|
if err != nil {
|
|
t.Fatalf("Open() returned error: %v", err)
|
|
}
|
|
if err := db.Close(); err != nil {
|
|
t.Fatalf("Close() returned error: %v", err)
|
|
}
|
|
if err := db.Ready(ctx); err == nil {
|
|
t.Fatal("Ready() returned nil for a closed database")
|
|
}
|
|
}
|