feat(server): статус комнаты в снапшоте READY
Шапка комнаты показывает статус (описание) рядом с названием, но в снапшоте этого поля не было: клиент получал описание только REST-запросом списка комнат, поэтому статус появлялся с задержкой и не приходил в событиях комнат (AGENT.md 7.5, 11.6). - `ReadyChannel.Description` + заполнение в `channelPayload` — статус приходит и в READY, и в `CHANNEL_CREATE`/`CHANNEL_UPDATE` (те же payload'ы, что у REST); - Go-тест `TestReadyCarriesChannelStatus`: описание и медленный режим доезжают до снапшота. Клиентская часть (парсер и шапка) идёт следующим коммитом.
This commit is contained in:
@@ -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"`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user