Files

243 lines
8.1 KiB
Go
Raw Permalink Normal View History

package store
import (
"context"
"encoding/json"
"errors"
"path/filepath"
"strings"
"testing"
"glchat/internal/database"
)
// newUserGuildTemplateTestStore открывает временную БД со всеми миграциями и
// заводит двух пользователей: шаблоны ссылаются на владельца внешним ключом,
// а приватность проверяется на втором аккаунте.
func newUserGuildTemplateTestStore(t *testing.T) (*Store, uint64, uint64) {
t.Helper()
ctx := context.Background()
db, err := database.Open(ctx, database.Options{
Path: filepath.Join(t.TempDir(), "glchat.db"),
ReadPool: 2,
Migrate: true,
})
if err != nil {
t.Fatalf("открыть тестовую БД: %v", err)
}
t.Cleanup(func() {
if err := db.Close(); err != nil {
t.Errorf("закрыть тестовую БД: %v", err)
}
})
st := New(db)
owner, err := st.CreateUser(ctx, CreateUserParams{
Username: "tpl_owner",
EmailEnc: "enc-owner",
EmailIndex: "idx-owner",
PasswordHash: "hash",
})
if err != nil {
t.Fatalf("создать владельца: %v", err)
}
other, err := st.CreateUser(ctx, CreateUserParams{
Username: "tpl_other",
EmailEnc: "enc-other",
EmailIndex: "idx-other",
PasswordHash: "hash",
})
if err != nil {
t.Fatalf("создать второго пользователя: %v", err)
}
return st, owner.ID, other.ID
}
func TestUserGuildTemplateLifecycle(t *testing.T) {
ctx := context.Background()
st, ownerID, _ := newUserGuildTemplateTestStore(t)
guild, err := st.CreateGuild(ctx, CreateGuildParams{Name: "Источник", OwnerID: ownerID})
if err != nil {
t.Fatalf("CreateGuild: %v", err)
}
structure := json.RawMessage(`{"roles":[{"name":"Модератор","color":16711680,"permissions":"KICK_MEMBERS","position":50,"hoist":true,"mentionable":true}],"channels":[{"name":"Информация","type":"category","position":0}]}`)
created, err := st.CreateUserGuildTemplate(ctx, CreateUserGuildTemplateParams{
OwnerID: ownerID,
Name: "Мой шаблон",
Description: "Структура",
Structure: structure,
SourceGuildID: &guild.ID,
})
if err != nil {
t.Fatalf("CreateUserGuildTemplate: %v", err)
}
if created.ID == 0 || created.OwnerID != ownerID || created.Name != "Мой шаблон" {
t.Fatalf("неожиданный шаблон: %+v", created)
}
if created.SourceGuildID == nil || *created.SourceGuildID != guild.ID {
t.Fatalf("source_guild_id = %v, ожидался %d", created.SourceGuildID, guild.ID)
}
if string(created.Structure) != string(structure) {
t.Fatalf("structure_json = %s", created.Structure)
}
if created.CreatedAt.IsZero() {
t.Fatal("created_at не заполнен")
}
fetched, err := st.GetUserGuildTemplate(ctx, created.ID)
if err != nil {
t.Fatalf("GetUserGuildTemplate: %v", err)
}
if fetched.Name != created.Name || fetched.Description != created.Description {
t.Fatalf("прочитан другой шаблон: %+v", fetched)
}
second, err := st.CreateUserGuildTemplate(ctx, CreateUserGuildTemplateParams{
OwnerID: ownerID,
Name: "Второй",
Structure: json.RawMessage(`{"roles":[],"channels":[]}`),
})
if err != nil {
t.Fatalf("CreateUserGuildTemplate (второй): %v", err)
}
list, err := st.ListUserGuildTemplates(ctx, ownerID)
if err != nil {
t.Fatalf("ListUserGuildTemplates: %v", err)
}
if len(list) != 2 {
t.Fatalf("шаблонов %d, ожидалось 2", len(list))
}
// Свежие — первыми: список сортируется по id DESC.
if list[0].ID != second.ID || list[1].ID != created.ID {
t.Fatalf("порядок шаблонов неверен: %d, %d", list[0].ID, list[1].ID)
}
count, err := st.CountUserGuildTemplates(ctx, ownerID)
if err != nil {
t.Fatalf("CountUserGuildTemplates: %v", err)
}
if count != 2 {
t.Fatalf("CountUserGuildTemplates = %d, ожидалось 2", count)
}
deleted, err := st.DeleteUserGuildTemplate(ctx, created.ID, ownerID)
if err != nil {
t.Fatalf("DeleteUserGuildTemplate: %v", err)
}
if !deleted {
t.Fatal("свой шаблон не удалён")
}
if _, err := st.GetUserGuildTemplate(ctx, created.ID); !errors.Is(err, ErrNotFound) {
t.Fatalf("удалённый шаблон читается: %v", err)
}
deleted, err = st.DeleteUserGuildTemplate(ctx, created.ID, ownerID)
if err != nil {
t.Fatalf("повторное удаление: %v", err)
}
if deleted {
t.Fatal("повторное удаление вернуло true")
}
}
// Приватность: чужой шаблон не виден в списке и не удаляется, его владелец
// продолжает им пользоваться.
func TestUserGuildTemplatePrivacy(t *testing.T) {
ctx := context.Background()
st, ownerID, otherID := newUserGuildTemplateTestStore(t)
created, err := st.CreateUserGuildTemplate(ctx, CreateUserGuildTemplateParams{
OwnerID: ownerID,
Name: "Секретный",
Structure: json.RawMessage(`{"roles":[],"channels":[]}`),
})
if err != nil {
t.Fatalf("CreateUserGuildTemplate: %v", err)
}
list, err := st.ListUserGuildTemplates(ctx, otherID)
if err != nil {
t.Fatalf("ListUserGuildTemplates: %v", err)
}
if len(list) != 0 {
t.Fatalf("чужой шаблон виден в списке: %+v", list)
}
count, err := st.CountUserGuildTemplates(ctx, otherID)
if err != nil {
t.Fatalf("CountUserGuildTemplates: %v", err)
}
if count != 0 {
t.Fatalf("чужой шаблон посчитан: %d", count)
}
deleted, err := st.DeleteUserGuildTemplate(ctx, created.ID, otherID)
if err != nil {
t.Fatalf("DeleteUserGuildTemplate: %v", err)
}
if deleted {
t.Fatal("чужой шаблон удалён")
}
if _, err := st.GetUserGuildTemplate(ctx, created.ID); err != nil {
t.Fatalf("шаблон владельца пропал: %v", err)
}
}
func TestUserGuildTemplateLimit(t *testing.T) {
ctx := context.Background()
st, ownerID, otherID := newUserGuildTemplateTestStore(t)
for i := 0; i < MaxUserGuildTemplates; i++ {
if _, err := st.CreateUserGuildTemplate(ctx, CreateUserGuildTemplateParams{
OwnerID: ownerID,
Name: "Шаблон",
Structure: json.RawMessage(`{"roles":[],"channels":[]}`),
}); err != nil {
t.Fatalf("шаблон %d: %v", i, err)
}
}
_, err := st.CreateUserGuildTemplate(ctx, CreateUserGuildTemplateParams{
OwnerID: ownerID,
Name: "Лишний",
Structure: json.RawMessage(`{"roles":[],"channels":[]}`),
})
if !errors.Is(err, ErrTemplateLimitReached) {
t.Fatalf("превышение лимита = %v, ожидалось ErrTemplateLimitReached", err)
}
count, err := st.CountUserGuildTemplates(ctx, ownerID)
if err != nil {
t.Fatalf("CountUserGuildTemplates: %v", err)
}
if count != MaxUserGuildTemplates {
t.Fatalf("после отказа шаблонов %d, ожидалось %d", count, MaxUserGuildTemplates)
}
// Лимит считается по владельцу: у второго пользователя место свободно.
if _, err := st.CreateUserGuildTemplate(ctx, CreateUserGuildTemplateParams{
OwnerID: otherID,
Name: "Свой",
Structure: json.RawMessage(`{"roles":[],"channels":[]}`),
}); err != nil {
t.Fatalf("шаблон второго пользователя: %v", err)
}
}
func TestUserGuildTemplateTooLarge(t *testing.T) {
ctx := context.Background()
st, ownerID, _ := newUserGuildTemplateTestStore(t)
oversized := json.RawMessage(`{"note":"` + strings.Repeat("x", MaxUserGuildTemplateBytes) + `"}`)
if _, err := st.CreateUserGuildTemplate(ctx, CreateUserGuildTemplateParams{
OwnerID: ownerID,
Name: "Большой",
Structure: oversized,
}); !errors.Is(err, ErrTemplateTooLarge) {
t.Fatalf("слишком большая структура = %v, ожидалось ErrTemplateTooLarge", err)
}
count, err := st.CountUserGuildTemplates(ctx, ownerID)
if err != nil {
t.Fatalf("CountUserGuildTemplates: %v", err)
}
if count != 0 {
t.Fatalf("отклонённый шаблон сохранён: %d", count)
}
}