spark-server/internal/transport/tcp/read_test.go
DESKTOP-L8OUL9F\gfl0034 c0ed0d83de fix: 命令 8 接受 last_msg_id 作为已读水位
客户端已读请求常只有 conversation_id 和 last_msg_id。任一消息 id 大于 0 即写入 last_read_msg_id 并清 Redis 未读,不再因此返回 40001。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 19:26:48 +08:00

119 lines
4.6 KiB
Go

package tcp
import (
"encoding/json"
"errors"
"strconv"
"testing"
"spark-server/internal/infrastructure/id"
convlogic "spark-server/internal/module/conversation/logic"
convmodel "spark-server/internal/module/conversation/model"
delivermodel "spark-server/internal/module/deliver/model"
friendlogic "spark-server/internal/module/friend/logic"
friendmodel "spark-server/internal/module/friend/model"
inboxmodel "spark-server/internal/module/inbox/model"
msglogic "spark-server/internal/module/message/logic"
msgmodel "spark-server/internal/module/message/model"
userentity "spark-server/internal/module/user/entity"
"spark-server/internal/module/user/logictest"
"spark-server/internal/pkg/errs"
"spark-server/internal/pkg/reqctx"
"spark-server/internal/transport/tcp/protocol"
"spark-server/internal/transport/tcp/session"
)
func TestReadMessageRegistered(t *testing.T) {
svc, b, convID, msgID := readFixture(t)
srv := &Server{messages: svc, router: NewRouter()}
srv.registerDefaultHandlers()
conn := session.NewConn("c", "127.0.0.1", 4)
conn.Authenticate(b, 9)
conn.SetRequestID("req-read")
missing, err := srv.router.Dispatch(t.Context(), conn, protocol.Frame{
Version: protocol.CurrentVersion, Command: protocol.CmdReadMessage, Body: []byte(`{}`),
})
if missing != nil || !errors.Is(err, errs.ErrInvalidArgument) {
t.Fatalf("missing fields frame=%v err=%v", missing, err)
}
onlyLast, err := srv.router.Dispatch(t.Context(), conn, protocol.Frame{
Version: protocol.CurrentVersion, Command: protocol.CmdReadMessage,
Body: []byte(`{"conversation_id":` + strconv.FormatInt(convID, 10) + `,"peer_id":1,"last_msg_id":0}`),
})
if onlyLast != nil || !errors.Is(err, errs.ErrInvalidArgument) {
t.Fatalf("zero cursor frame=%v err=%v", onlyLast, err)
}
resp, err := srv.router.Dispatch(t.Context(), conn, protocol.Frame{
Version: protocol.CurrentVersion, Command: protocol.CmdReadMessage, SequenceID: 4,
Body: []byte(`{"conversation_id":` + strconv.FormatInt(convID, 10) + `,"msg_id":` + strconv.FormatInt(msgID, 10) + `}`),
})
if err != nil || resp == nil || resp.Command != protocol.CmdReadMessage {
t.Fatalf("read %+v %v", resp, err)
}
env, err := protocol.DecodeEnvelope(resp.Body)
if err != nil || env.Code != protocol.CodeOK {
t.Fatalf("envelope %+v %v", env, err)
}
var data msglogic.ReadResult
if err := json.Unmarshal(env.Data, &data); err != nil || data.MsgID != msgID || data.ConversationID != convID {
t.Fatalf("data %+v %v", data, err)
}
alias, err := srv.router.Dispatch(t.Context(), conn, protocol.Frame{
Version: protocol.CurrentVersion, Command: protocol.CmdReadMessage, SequenceID: 5,
Body: []byte(`{"conversation_id":` + strconv.FormatInt(convID, 10) + `,"peer_id":1,"last_msg_id":` + strconv.FormatInt(msgID, 10) + `}`),
})
if err != nil || alias == nil {
t.Fatalf("last_msg_id %+v %v", alias, err)
}
aliasEnv, err := protocol.DecodeEnvelope(alias.Body)
if err != nil || aliasEnv.Code != protocol.CodeOK {
t.Fatalf("last_msg_id envelope %+v %v", aliasEnv, err)
}
var aliasData msglogic.ReadResult
if err := json.Unmarshal(aliasEnv.Data, &aliasData); err != nil || aliasData.MsgID != msgID || aliasData.ConversationID != convID {
t.Fatalf("last_msg_id data %+v %v", aliasData, err)
}
}
func readFixture(t *testing.T) (*msglogic.Service, int64, int64, int64) {
t.Helper()
ids, err := id.New(1)
if err != nil {
t.Fatal(err)
}
users := logictest.Users(ids, nil)
chats := convmodel.NewMemoryP2P()
p2p := convlogic.NewP2PService(chats, ids)
q := delivermodel.NewMemoryQueue()
msgs := msgmodel.NewMemoryRepo()
friends := friendlogic.NewService(friendmodel.NewMemoryRepo(), users, ids, friendlogic.NewGreeter(p2p, msgs, ids, q))
svc := msglogic.NewService(msgs, chats, p2p, friends, nil, ids, q)
svc.BindInbox(inboxmodel.NewMemoryStore())
ctx := reqctx.With(t.Context(), &reqctx.RequestContext{Language: "zh-CN"})
a, err := users.AuthenticateEmail(ctx, "test01@spark.com", "1", &userentity.LoginDevice{DeviceID: 1, DeviceType: userentity.DeviceIOS})
if err != nil {
t.Fatal(err)
}
b, err := users.AuthenticateEmail(ctx, "test02@spark.com", "1", &userentity.LoginDevice{DeviceID: 2, DeviceType: userentity.DeviceWeb})
if err != nil {
t.Fatal(err)
}
applied, err := friends.Apply(ctx, a.ID, &friendlogic.ApplyInput{UID: b.ID})
if err != nil {
t.Fatal(err)
}
if _, err := friends.Accept(ctx, b.ID, friendlogic.AcceptInput{RequestID: applied.RequestID}); err != nil {
t.Fatal(err)
}
sent, err := svc.SendMessage(ctx, a.ID, 1, &msglogic.SendInput{
PeerID: b.ID, ClientMsgID: "read-1", Content: json.RawMessage(`{"t":1,"text":{"body":"hi"}}`),
})
if err != nil {
t.Fatal(err)
}
return svc, b.ID, sent.ConversationID, sent.MsgID
}