181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
package message
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.asio.asia/nixevol/NixMsg/internal/app/port"
|
|
"git.asio.asia/nixevol/NixMsg/internal/protocol"
|
|
)
|
|
|
|
// 定时到点且接收方离线成终态时,在线发送方经 WakePush 拿到回执。
|
|
func TestR365DispatchDueWakesSenderForReceipt(t *testing.T) {
|
|
t.Parallel()
|
|
e := openDeliveryEnv(t, nil)
|
|
insertEndpoint(t, e.db, "alice", "", 1, 0)
|
|
insertEndpoint(t, e.db, "bob", "", 1, 0)
|
|
_ = e.db.Queue.Do(context.Background(), func(tx *sql.Tx) error {
|
|
_, err := tx.Exec(`UPDATE endpoints SET offline_since = ? WHERE id=?`, e.nowMs-120_000, "bob")
|
|
return err
|
|
})
|
|
ctx := context.Background()
|
|
aliceConn := LiveConn{ConnID: "c-alice", Ready: true}
|
|
e.conns.Set("alice", aliceConn)
|
|
if err := e.app.OnHandshakeComplete(ctx, "alice", aliceConn); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { e.app.stopPushWorker("alice", "") })
|
|
|
|
delay := int64(5000)
|
|
req := baseSend("due-rcpt", "bob")
|
|
req.DelayMs = &delay
|
|
if _, err := e.app.Submit(ctx, "alice", port.ConnInfo{}, req); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
st, _ := e.msgState("alice", "due-rcpt")
|
|
if st != StateScheduled {
|
|
t.Fatalf("want scheduled got %s", st)
|
|
}
|
|
if e.down.FilterType(protocol.TypeReceipt) != 0 {
|
|
t.Fatal("receipt before due")
|
|
}
|
|
|
|
e.setNow(e.nowMs + 5000)
|
|
if _, err := e.app.DispatchDue(ctx, e.nowMs, 10); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
if e.down.FilterType(protocol.TypeReceipt) > 0 {
|
|
return
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
t.Fatalf("sender got no receipt after scheduled dispatch, receipts=%d", e.down.FilterType(protocol.TypeReceipt))
|
|
}
|
|
|
|
// 窗口内首条超限拒收后,同连接后续小消息仍被 worker 推送。
|
|
func TestR365TooLargeThenSmallContinuesPush(t *testing.T) {
|
|
t.Parallel()
|
|
e := openDeliveryEnv(t, func(l *Limits) { l.DeliveryWindow = 1 })
|
|
insertEndpoint(t, e.db, "alice", "", 1, 0)
|
|
insertEndpoint(t, e.db, "bob", "", 1, 0)
|
|
ctx := context.Background()
|
|
// 整帧上限:小消息能过,大正文整帧超限被拒。
|
|
bobConn := LiveConn{ConnID: "c-bob", Ready: true, MaxReceiveBytes: 200}
|
|
e.conns.Set("bob", bobConn)
|
|
if err := e.app.OnHandshakeComplete(ctx, "bob", bobConn); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { e.app.stopPushWorker("bob", "") })
|
|
|
|
big := baseSend("big-skip", "bob")
|
|
b := make([]byte, 400)
|
|
for i := range b {
|
|
b[i] = 'A'
|
|
}
|
|
big.Body.Data = string(b)
|
|
if _, err := e.app.Submit(ctx, "alice", port.ConnInfo{}, big); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
small := baseSend("small-ok", "bob")
|
|
small.Body.Data = "hi"
|
|
if _, err := e.app.Submit(ctx, "alice", port.ConnInfo{}, small); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
foundSmall := false
|
|
for _, p := range e.down.Snapshots() {
|
|
if payloadType(p.Payload) != protocol.TypeMsg {
|
|
continue
|
|
}
|
|
var m struct {
|
|
ID string `json:"id"`
|
|
}
|
|
_ = json.Unmarshal(p.Payload, &m)
|
|
if m.ID == "small-ok" {
|
|
foundSmall = true
|
|
}
|
|
}
|
|
if foundSmall {
|
|
seqBig := e.seqOf("alice", "big-skip")
|
|
st, reason := e.deliveryState(seqBig, "bob")
|
|
if st != DeliveryRejected || reason != ReasonTooLarge {
|
|
t.Fatalf("big: %s/%s", st, reason)
|
|
}
|
|
return
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
t.Fatalf("small msg not pushed after too_large; msgs=%d snapshots=%d",
|
|
e.down.FilterType(protocol.TypeMsg), len(e.down.Snapshots()))
|
|
}
|
|
|
|
// 撤回已推在途消息后,同连接更晚的 pending 继续推。
|
|
func TestR365RecallFreesWindowForLaterPending(t *testing.T) {
|
|
t.Parallel()
|
|
e := openDeliveryEnv(t, func(l *Limits) { l.DeliveryWindow = 1 })
|
|
insertEndpoint(t, e.db, "alice", "", 1, 0)
|
|
insertEndpoint(t, e.db, "bob", "", 1, 0)
|
|
ctx := context.Background()
|
|
bobConn := LiveConn{ConnID: "c-bob", Ready: true}
|
|
e.conns.Set("bob", bobConn)
|
|
if err := e.app.OnHandshakeComplete(ctx, "bob", bobConn); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { e.app.stopPushWorker("bob", "") })
|
|
|
|
if _, err := e.app.Submit(ctx, "alice", port.ConnInfo{}, baseSend("first-in-flight", "bob")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := e.app.Submit(ctx, "alice", port.ConnInfo{}, baseSend("second-wait", "bob")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
if e.down.FilterType(protocol.TypeMsg) >= 1 {
|
|
break
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if e.down.FilterType(protocol.TypeMsg) < 1 {
|
|
t.Fatal("first msg not pushed")
|
|
}
|
|
seq1 := e.seqOf("alice", "first-in-flight")
|
|
var pushed sql.NullString
|
|
_ = e.db.Read.QueryRow(`SELECT pushed_conn FROM deliveries WHERE seq=? AND endpoint_id='bob'`, seq1).Scan(&pushed)
|
|
if !pushed.Valid {
|
|
t.Fatal("first not in-flight")
|
|
}
|
|
|
|
if _, err := e.app.Recall(ctx, "alice", &protocol.Recall{
|
|
V: protocol.Version, Type: protocol.TypeRecall, RID: "r1", ID: "first-in-flight",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
deadline = time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
for _, p := range e.down.Snapshots() {
|
|
if payloadType(p.Payload) != protocol.TypeMsg {
|
|
continue
|
|
}
|
|
var m struct {
|
|
ID string `json:"id"`
|
|
}
|
|
_ = json.Unmarshal(p.Payload, &m)
|
|
if m.ID == "second-wait" {
|
|
return
|
|
}
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
t.Fatalf("second pending not pushed after recall; msgs=%d", e.down.FilterType(protocol.TypeMsg))
|
|
}
|