fix(gateway): READY отдаёт права участника вместо пустого списка
Снапшот READY инициализировал my_permissions пустым списком и нигде его не заполнял, поэтому у всех — включая владельца сервера — клиент выключал композер («В эту комнату нельзя писать»), прятал шестерёнку, приглашения и кнопку саундборда: состав прав в снапшоте не совпадал с REST (GET /users/@me/guilds отдавал полный список). Права считает тот же движок и тот же кэш, что REST (permissions.Calculator, переданный в NewSnapshot), а инстанс-админ получает максимальный набор даже на чужом сервере, где он не участник (AGENT.md 7.19, 11.6). Тесты: TestReadyCarriesMyPermissions (непустые права роли по умолчанию, VIEW_CHANNEL/SEND_MESSAGES и согласие с can_send комнаты) и TestReadyGivesInstanceAdminAllPermissions.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"log/slog"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -214,6 +215,71 @@ func TestHelloAndReady(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadyCarriesMyPermissions проверяет, что снапшот несёт права участника:
|
||||
// пустой `my_permissions` выключал композер и управление сервером у всех,
|
||||
// включая владельца (AGENT.md 11.6).
|
||||
func TestReadyCarriesMyPermissions(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
token := registerUser(t, f, "perm_user", "perm@example.com")
|
||||
joinMainGuildForTest(t, f, token)
|
||||
|
||||
conn, _ := f.dial(t)
|
||||
send(t, conn, gateway.OpIdentify, map[string]any{"token": token})
|
||||
|
||||
var snapshot gateway.Ready
|
||||
if err := json.Unmarshal(readEnvelope(t, conn).D, &snapshot); err != nil {
|
||||
t.Fatalf("decode READY: %v", err)
|
||||
}
|
||||
perms := snapshot.Guilds[0].MyPerms
|
||||
if len(perms) == 0 {
|
||||
t.Fatal("READY must carry my_permissions of the default role")
|
||||
}
|
||||
// Роль @user даёт эти права каждому участнику (AGENT.md 6.3).
|
||||
for _, want := range []string{"VIEW_CHANNEL", "SEND_MESSAGES"} {
|
||||
if !slices.Contains(perms, want) {
|
||||
t.Fatalf("my_permissions = %v, want %s", perms, want)
|
||||
}
|
||||
}
|
||||
if !snapshot.Guilds[0].Channels[0].CanSend {
|
||||
t.Fatal("my_permissions must agree with channel can_send")
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadyGivesInstanceAdminAllPermissions: у инстанс-админа права
|
||||
// максимальные даже на чужом сервере, где он не участник (AGENT.md 7.19).
|
||||
func TestReadyGivesInstanceAdminAllPermissions(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
ctx := context.Background()
|
||||
token := registerUser(t, f, "admin_user", "admin@example.com")
|
||||
// Инстанс-админ — участник главного сервера: список серверов берётся из
|
||||
// членства, а права на них максимальные.
|
||||
joinMainGuildForTest(t, f, token)
|
||||
user, _, err := f.auth.ResolveSession(ctx, token)
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveSession: %v", err)
|
||||
}
|
||||
if err := f.store.SetInstanceAdmin(ctx, user.ID, true); err != nil {
|
||||
t.Fatalf("SetInstanceAdmin: %v", err)
|
||||
}
|
||||
|
||||
conn, _ := f.dial(t)
|
||||
send(t, conn, gateway.OpIdentify, map[string]any{"token": token})
|
||||
|
||||
var snapshot gateway.Ready
|
||||
if err := json.Unmarshal(readEnvelope(t, conn).D, &snapshot); err != nil {
|
||||
t.Fatalf("decode READY: %v", err)
|
||||
}
|
||||
if len(snapshot.Guilds) != 1 {
|
||||
t.Fatalf("instance admin must see the guild, got %d", len(snapshot.Guilds))
|
||||
}
|
||||
perms := snapshot.Guilds[0].MyPerms
|
||||
for _, want := range []string{"ADMINISTRATOR", "SEND_MESSAGES", "MANAGE_ROLES", "BAN_MEMBERS"} {
|
||||
if !slices.Contains(perms, want) {
|
||||
t.Fatalf("my_permissions инстанс-админа = %v, want %s", perms, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadyHidesPrivateChannels(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user