package server import ( "net/http" "testing" ) // Тесты шаблонов серверов (AGENT.md 7.3): встроенный шаблон создаёт структуру // комнат и ролей без сообщений, неизвестный шаблон отклоняется. func TestGuildTemplatesCatalog(t *testing.T) { srv, _ := newTestServer(t) user := registerAndLogin(t, srv, "tpl_user", "tpl@example.com") rec := doJSON(t, srv, http.MethodGet, "/api/v1/guild-templates", "", user) if rec.Code != http.StatusOK { t.Fatalf("каталог шаблонов = %d (%s)", rec.Code, rec.Body.String()) } payload := decodeResponse[struct { Templates []struct { ID string `json:"id"` Name string `json:"name"` Roles []string `json:"roles"` TextCount int `json:"text_channels"` VoiceCount int `json:"voice_channels"` Categories []string `json:"categories"` } `json:"templates"` }](t, rec) if len(payload.Templates) != 3 { t.Fatalf("шаблонов %d, ожидалось 3", len(payload.Templates)) } found := map[string]bool{} for _, template := range payload.Templates { found[template.ID] = true if len(template.Roles) < 2 { t.Fatalf("шаблон %s без ролей: %+v", template.ID, template.Roles) } } for _, id := range []string{"empty", "community", "gaming"} { if !found[id] { t.Fatalf("шаблон %s отсутствует в каталоге", id) } } } func TestGuildFromTemplateCreatesStructure(t *testing.T) { cases := []struct { template string wantRoles []string wantText []string wantVoice []string wantCategory []string }{ { template: "empty", wantRoles: []string{"Администратор", "Пользователь"}, wantText: []string{"общий"}, }, { template: "community", wantRoles: []string{"Администратор", "Пользователь", "Модератор"}, wantText: []string{"правила", "объявления", "общий", "флудилка"}, wantVoice: []string{"Голосовой"}, wantCategory: []string{"Информация", "Общение"}, }, { template: "gaming", wantRoles: []string{"Администратор", "Пользователь", "Игрок"}, wantText: []string{"общий", "поиск-игр"}, wantVoice: []string{"Игровая 1", "Игровая 2"}, wantCategory: []string{"Текстовые", "Голосовые"}, }, } for _, item := range cases { t.Run(item.template, func(t *testing.T) { srv, _ := newTestServer(t) owner := registerAndLogin(t, srv, "tpl_"+item.template, "tpl-"+item.template+"@example.com") created := doJSON(t, srv, http.MethodPost, "/api/v1/guilds", `{"name":"Шаблон","template_id":"`+item.template+`"}`, owner) if created.Code != http.StatusOK { t.Fatalf("создание = %d (%s)", created.Code, created.Body.String()) } guildID := decodeResponse[struct { Guild struct { ID string `json:"id"` } `json:"guild"` }](t, created).Guild.ID roles := doJSON(t, srv, http.MethodGet, "/api/v1/guilds/"+guildID+"/roles", "", owner) roleNames := make([]string, 0, 4) for _, role := range decodeResponse[struct { Roles []struct { Name string `json:"name"` } `json:"roles"` }](t, roles).Roles { roleNames = append(roleNames, role.Name) } for _, want := range item.wantRoles { if !contains(roleNames, want) { t.Fatalf("роль %q не создана: %v", want, roleNames) } } channels := doJSON(t, srv, http.MethodGet, "/api/v1/guilds/"+guildID+"/channels", "", owner) list := decodeResponse[struct { Channels []struct { Name string `json:"name"` Type string `json:"type"` ParentID string `json:"parent_id"` } `json:"channels"` }](t, channels) byType := map[string][]string{} for _, channel := range list.Channels { byType[channel.Type] = append(byType[channel.Type], channel.Name) // Комнаты шаблона лежат внутри своих категорий. if len(item.wantCategory) > 0 && channel.Type != "category" && channel.ParentID == "" { t.Fatalf("комната %q не привязана к категории", channel.Name) } } for _, want := range item.wantText { if !contains(byType["text"], want) { t.Fatalf("текстовая комната %q не создана: %v", want, byType["text"]) } } for _, want := range item.wantVoice { if !contains(byType["voice"], want) { t.Fatalf("голосовая комната %q не создана: %v", want, byType["voice"]) } } for _, want := range item.wantCategory { if !contains(byType["category"], want) { t.Fatalf("категория %q не создана: %v", want, byType["category"]) } } }) } } func TestGuildWithUnknownTemplateRejected(t *testing.T) { srv, _ := newTestServer(t) owner := registerAndLogin(t, srv, "tpl_bad", "tpl-bad@example.com") rec := doJSON(t, srv, http.MethodPost, "/api/v1/guilds", `{"name":"Странный","template_id":"нет-такого"}`, owner) if rec.Code != http.StatusUnprocessableEntity { t.Fatalf("неизвестный шаблон = %d (%s)", rec.Code, rec.Body.String()) } } // contains проверяет наличие строки в списке. func contains(list []string, value string) bool { for _, item := range list { if item == value { return true } } return false } func TestGuildFromTemplateCreatesNoDuplicates(t *testing.T) { srv, _ := newTestServer(t) owner := registerAndLogin(t, srv, "tpl_dup", "tpl-dup@example.com") created := doJSON(t, srv, http.MethodPost, "/api/v1/guilds", `{"name":"Дубли","template_id":"community"}`, owner) guildID := decodeResponse[struct { Guild struct { ID string `json:"id"` } `json:"guild"` }](t, created).Guild.ID channels := doJSON(t, srv, http.MethodGet, "/api/v1/guilds/"+guildID+"/channels", "", owner) list := decodeResponse[struct { Channels []struct { Name string `json:"name"` Type string `json:"type"` } `json:"channels"` }](t, channels) seen := map[string]int{} for _, channel := range list.Channels { seen[channel.Type+":"+channel.Name]++ } for key, count := range seen { if count > 1 { t.Fatalf("комната %s создана %d раза", key, count) } } // «Сообщество»: 2 категории, 4 текстовые и 1 голосовая. if len(list.Channels) != 7 { t.Fatalf("комнат %d, ожидалось 7", len(list.Channels)) } }