package message import ( "context" "database/sql" "testing" "git.asio.asia/nixevol/NixMsg/internal/app/port" ) func TestRejectPendingAndFinalizeRetentionZero(t *testing.T) { t.Parallel() lim := defaultTestLimits() lim.RecordRetentionDays = 0 app, db := openTestApp(t, lim) insertEndpoint(t, db, "alice", "", 1, 0) insertEndpoint(t, db, "bob", "", 1, 0) ctx := context.Background() req := baseSend("z1", "bob") req.Offline = keepTrue() if _, err := app.Submit(ctx, "alice", port.ConnInfo{}, req); err != nil { t.Fatal(err) } err := db.Queue.Do(ctx, func(tx *sql.Tx) error { var seq int64 if e := tx.QueryRow(`SELECT seq FROM messages WHERE id='z1'`).Scan(&seq); e != nil { return e } if _, e := RejectPendingTx(tx, seq, "bob", ReasonEndpointDisabled, 1_700_000_000_000); e != nil { return e } return TryFinalizeTx(tx, seq, 1_700_000_000_000, 0) }) if err != nil { t.Fatal(err) } var n int if err := db.Read.QueryRow(`SELECT COUNT(*) FROM messages WHERE id='z1'`).Scan(&n); err != nil { t.Fatal(err) } if n != 0 { t.Fatalf("message row should be deleted when retention=0, n=%d", n) } var receipts int if err := db.Read.QueryRow(`SELECT COUNT(*) FROM receipts WHERE msg_id='z1' AND state='rejected'`).Scan(&receipts); err != nil { t.Fatal(err) } if receipts != 1 { t.Fatalf("receipts=%d", receipts) } } func TestCleanupStuckDispatched(t *testing.T) { t.Parallel() lim := defaultTestLimits() app, db := openTestApp(t, lim) insertEndpoint(t, db, "alice", "", 1, 0) insertEndpoint(t, db, "bob", "", 1, 0) ctx := context.Background() nowMs := int64(1_700_000_000_000) err := db.Queue.Do(ctx, func(tx *sql.Tx) error { res, e := tx.Exec(` INSERT INTO messages(id, sender_id, dest_kind, dest_id, meta, content_type, body_enc, send_at, keep, ttl_seconds, receipt, state, reason, created_at) VALUES('stuck','alice','endpoint','bob','{}','text/plain; charset=utf-8','utf8', ?,0,0,1,'dispatched','',?)`, nowMs, nowMs) if e != nil { return e } seq, e := res.LastInsertId() if e != nil { return e } if _, e = tx.Exec(`INSERT INTO message_bodies(seq, body) VALUES(?, ?)`, seq, []byte("x")); e != nil { return e } _, e = tx.Exec(` INSERT INTO deliveries(seq, endpoint_id, send_at, keep, state, reason, updated_at) VALUES(?,?,?,0,'rejected','left_group',?)`, seq, "bob", nowMs, nowMs) return e }) if err != nil { t.Fatal(err) } if err := app.CleanupOnce(ctx, nowMs); err != nil { t.Fatal(err) } var state string if err := db.Read.QueryRow(`SELECT state FROM messages WHERE id='stuck'`).Scan(&state); err != nil { t.Fatal(err) } if state != StateCompleted { t.Fatalf("state=%s want completed", state) } var bodies int if err := db.Read.QueryRow(`SELECT COUNT(*) FROM message_bodies b JOIN messages m ON m.seq=b.seq WHERE m.id='stuck'`).Scan(&bodies); err != nil { t.Fatal(err) } if bodies != 0 { t.Fatalf("body still present: %d", bodies) } } func TestCleanupKeepsRecentlyCompletedOldCreated(t *testing.T) { t.Parallel() lim := defaultTestLimits() lim.RecordRetentionDays = 7 app, db := openTestApp(t, lim) insertEndpoint(t, db, "alice", "", 1, 0) insertEndpoint(t, db, "bob", "", 1, 0) ctx := context.Background() nowMs := int64(1_700_000_000_000) created := nowMs - int64(30)*24*3600*1000 err := db.Queue.Do(ctx, func(tx *sql.Tx) error { res, e := tx.Exec(` INSERT INTO messages(id, sender_id, dest_kind, dest_id, meta, content_type, body_enc, send_at, keep, ttl_seconds, receipt, state, reason, created_at) VALUES('old-created','alice','endpoint','bob','{}','text/plain; charset=utf-8','utf8', ?,0,0,1,'completed','',?)`, nowMs, created) if e != nil { return e } seq, e := res.LastInsertId() if e != nil { return e } _, e = tx.Exec(` INSERT INTO deliveries(seq, endpoint_id, send_at, keep, state, reason, updated_at) VALUES(?,?,?,0,'accepted','',?)`, seq, "bob", nowMs, nowMs) return e }) if err != nil { t.Fatal(err) } if err := app.CleanupOnce(ctx, nowMs); err != nil { t.Fatal(err) } var n int if err := db.Read.QueryRow(`SELECT COUNT(*) FROM messages WHERE id='old-created'`).Scan(&n); err != nil { t.Fatal(err) } if n != 1 { t.Fatalf("recently completed message should remain, n=%d", n) } }