From 61cad0fdfcef88d13d214db54ada6439709ae649 Mon Sep 17 00:00:00 2001 From: grendervill Date: Sat, 26 Sep 2026 11:06:05 +0300 Subject: [PATCH] =?UTF-8?q?feat(server):=20=D1=81=D1=82=D0=B0=D1=82=D1=83?= =?UTF-8?q?=D1=81=20=D0=BA=D0=BE=D0=BC=D0=BD=D0=B0=D1=82=D1=8B=20=D0=B2=20?= =?UTF-8?q?=D1=81=D0=BD=D0=B0=D0=BF=D1=88=D0=BE=D1=82=D0=B5=20READY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Шапка комнаты показывает статус (описание) рядом с названием, но в снапшоте этого поля не было: клиент получал описание только REST-запросом списка комнат, поэтому статус появлялся с задержкой и не приходил в событиях комнат (AGENT.md 7.5, 11.6). - `ReadyChannel.Description` + заполнение в `channelPayload` — статус приходит и в READY, и в `CHANNEL_CREATE`/`CHANNEL_UPDATE` (те же payload'ы, что у REST); - Go-тест `TestReadyCarriesChannelStatus`: описание и медленный режим доезжают до снапшота. Клиентская часть (парсер и шапка) идёт следующим коммитом. --- internal/gateway/gateway.go | 3 ++ internal/gateway/ready.go | 17 +++---- internal/server/ready_status_test.go | 69 ++++++++++++++++++++++++++++ internal/server/realtime_test.go | 3 ++ 4 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 internal/server/ready_status_test.go diff --git a/internal/gateway/gateway.go b/internal/gateway/gateway.go index 4ff7253..ebf9af5 100644 --- a/internal/gateway/gateway.go +++ b/internal/gateway/gateway.go @@ -132,6 +132,9 @@ type ReadyChannel struct { Slowmode int `json:"slowmode_seconds,omitempty"` CanSend bool `json:"can_send"` CanConnect bool `json:"can_connect"` + // Description — статус комнаты: шапка показывает его рядом с названием + // (AGENT.md 7.5, 11.6), поэтому он приходит и в READY, и в событиях комнат. + Description string `json:"description,omitempty"` // BackgroundFileID — фон комнаты: приходит и в READY, и в CHANNEL_UPDATE // (AGENT.md 7.5), иначе после перезагрузки фон терялся бы до REST-запроса. BackgroundFileID string `json:"background_file_id,omitempty"` diff --git a/internal/gateway/ready.go b/internal/gateway/ready.go index 54bdfe7..45c4bee 100644 --- a/internal/gateway/ready.go +++ b/internal/gateway/ready.go @@ -271,14 +271,15 @@ func readyPermissions(resolved permissions.Resolved) []string { func channelPayload(channel store.Channel, canSend, canConnect bool) ReadyChannel { payload := ReadyChannel{ - ID: formatID(channel.ID), - Type: string(channel.Type), - Name: channel.Name, - Position: channel.Position, - UserLimit: channel.UserLimit, - Slowmode: channel.SlowmodeSeconds, - CanSend: canSend, - CanConnect: canConnect, + ID: formatID(channel.ID), + Type: string(channel.Type), + Name: channel.Name, + Description: channel.Description, + Position: channel.Position, + UserLimit: channel.UserLimit, + Slowmode: channel.SlowmodeSeconds, + CanSend: canSend, + CanConnect: canConnect, } if channel.GuildID != nil { payload.GuildID = formatID(*channel.GuildID) diff --git a/internal/server/ready_status_test.go b/internal/server/ready_status_test.go new file mode 100644 index 0000000..9a6cac2 --- /dev/null +++ b/internal/server/ready_status_test.go @@ -0,0 +1,69 @@ +package server + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" +) + +// TestReadyCarriesChannelStatus проверяет, что снапшот несёт статус комнаты — +// описание и медленный режим: шапка показывает их рядом с названием, включая +// чужой сервер у инстанс-админа (AGENT.md 7.5, 11.6). +func TestReadyCarriesChannelStatus(t *testing.T) { + srv, _ := newTestServer(t) + httpServer := httptest.NewServer(srv.Handler()) + t.Cleanup(httpServer.Close) + + ownerCookie := registerAndLogin(t, srv, "status_owner", "status-owner@example.com") + created := doJSON(t, srv, http.MethodPost, "/api/v1/guilds", `{"name":"Статусы"}`, ownerCookie) + guild := decodeResponse[struct { + Guild struct { + ID string `json:"id"` + } `json:"guild"` + }](t, created) + + channels := doJSON(t, srv, http.MethodGet, "/api/v1/guilds/"+guild.Guild.ID+"/channels", "", ownerCookie) + channel := decodeResponse[struct { + Channels []struct { + ID string `json:"id"` + } `json:"channels"` + }](t, channels) + if len(channel.Channels) == 0 { + t.Fatal("у нового сервера нет комнат") + } + patched := doJSON(t, srv, http.MethodPatch, + "/api/v1/guilds/"+guild.Guild.ID+"/channels/"+channel.Channels[0].ID, + `{"description":"Статус комнаты","slowmode_seconds":5}`, ownerCookie) + if patched.Code != http.StatusOK { + t.Fatalf("описание комнаты = %d, body = %s", patched.Code, patched.Body.String()) + } + + client := dialGateway(t, httpServer, ownerCookie) + var snapshot struct { + Guilds []struct { + ID string `json:"id"` + Channels []struct { + Description string `json:"description"` + Slowmode int `json:"slowmode_seconds"` + } `json:"channels"` + } `json:"guilds"` + } + if err := json.Unmarshal(client.ready.D, &snapshot); err != nil { + t.Fatalf("decode READY: %v", err) + } + described := false + for _, item := range snapshot.Guilds { + if item.ID != guild.Guild.ID { + continue + } + for _, ch := range item.Channels { + if ch.Description == "Статус комнаты" && ch.Slowmode == 5 { + described = true + } + } + } + if !described { + t.Fatalf("READY не несёт статус комнаты: %+v", snapshot.Guilds) + } +} diff --git a/internal/server/realtime_test.go b/internal/server/realtime_test.go index 00cd524..1420e5d 100644 --- a/internal/server/realtime_test.go +++ b/internal/server/realtime_test.go @@ -17,6 +17,8 @@ import ( type realtimeClient struct { t *testing.T conn *websocket.Conn + // ready — тело кадра READY: тесты снапшота читают из него списки серверов. + ready realtimeFrame } // dialGateway подключается к /gateway и проходит IDENTIFY пустым токеном: @@ -47,6 +49,7 @@ func dialGateway(t *testing.T, server *httptest.Server, cookie *http.Cookie) *re if ready.T != "READY" { t.Fatalf("frame = %q, want READY", ready.T) } + client.ready = ready return client }