113 lines
4.3 KiB
Go

package main
import (
"context"
"database/sql"
"regexp"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
)
func TestDeliverySelectorFlagKeepsColonInsideEventID(t *testing.T) {
var values deliverySelectorFlag
if err := values.Set("wallet_outbox: FAMI :wallet:balance:evt-1"); err != nil {
t.Fatalf("parse exact delivery selector: %v", err)
}
if len(values) != 1 || values[0].SourceName != walletSource || values[0].AppCode != "fami" || values[0].EventID != "wallet:balance:evt-1" {
t.Fatalf("parsed selector mismatch: %+v", values)
}
if err := values.Set("unknown:lalu:event-2"); err == nil {
t.Fatal("unsupported source must be rejected before any database access")
}
if err := values.Set("room_outbox::event-3"); err == nil {
t.Fatal("empty app_code must not default to lalu")
}
}
func TestLoadExactDueDeliveriesUsesFullPrimaryKey(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("create sqlmock: %v", err)
}
defer db.Close()
nowMS := int64(1_700_000_000_000)
query := regexp.QuoteMeta(`
SELECT source_name, app_code, source_event_id, status, retry_count, updated_at_ms
FROM notice_delivery_events FORCE INDEX (PRIMARY)
WHERE source_name = ? AND app_code = ? AND source_event_id = ? AND channel = ?
AND ((status = 'retryable' AND next_retry_at_ms <= ?)
OR (status = 'delivering' AND lock_until_ms <= ?))`)
mock.ExpectQuery(query).
WithArgs(walletSource, "fami", "wallet:evt-1", c2cChannel, nowMS, nowMS).
WillReturnRows(sqlmock.NewRows([]string{"source_name", "app_code", "source_event_id", "status", "retry_count", "updated_at_ms"}).
AddRow(walletSource, "fami", "wallet:evt-1", "retryable", 2, nowMS-1000))
candidates, err := loadExactDueDeliveries(context.Background(), db, []deliverySelector{{SourceName: walletSource, AppCode: " FAMI ", EventID: "wallet:evt-1"}}, nowMS)
if err != nil {
t.Fatalf("load exact due delivery: %v", err)
}
if len(candidates) != 1 || candidates[0].Selector != "wallet_outbox:fami:wallet:evt-1" || candidates[0].RetryCount != 2 {
t.Fatalf("exact candidate mismatch: %+v", candidates)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("exact query did not use the expected primary key: %v", err)
}
}
func TestLoadExactDueDeliveriesRejectsDuplicateBeforeQuery(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("create sqlmock: %v", err)
}
defer db.Close()
selectors := []deliverySelector{
{SourceName: roomSource, AppCode: "lalu", EventID: "evt-1"},
{SourceName: roomSource, AppCode: " LALU ", EventID: "evt-1"},
}
if _, err := loadExactDueDeliveries(context.Background(), db, selectors, 1000); err == nil || !strings.Contains(err.Error(), "duplicate") {
t.Fatalf("duplicate selector error = %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("duplicate validation unexpectedly queried MySQL: %v", err)
}
}
func TestLoadExactDueDeliveriesRejectsNotDueRow(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("create sqlmock: %v", err)
}
defer db.Close()
mock.ExpectQuery("FROM notice_delivery_events FORCE INDEX \\(PRIMARY\\)").
WillReturnError(sql.ErrNoRows)
_, err = loadExactDueDeliveries(context.Background(), db, []deliverySelector{{SourceName: roomSource, AppCode: "lalu", EventID: "evt-1"}}, 1000)
if err == nil || !strings.Contains(err.Error(), "no longer due") {
t.Fatalf("not-due selector error = %v", err)
}
}
func TestDueDeliveryBranchUsesCompleteRetryIndexPrefix(t *testing.T) {
query, args := dueDeliveryBranch(walletSource, "huwaa", "idx_notice_delivery_retry", "retryable", "next_retry_at_ms", []string{"evt-1", "evt-2"}, 20, 1234)
for _, fragment := range []string{
"FORCE INDEX (idx_notice_delivery_retry)",
"source_name = ? AND app_code = ? AND channel = ? AND status = ?",
"next_retry_at_ms <= ?",
"source_event_id IN (?,?)",
} {
if !strings.Contains(query, fragment) {
t.Fatalf("indexed preview query is missing %q:\n%s", fragment, query)
}
}
wantArgs := []any{walletSource, "huwaa", c2cChannel, "retryable", int64(1234), "evt-1", "evt-2", 20}
if len(args) != len(wantArgs) {
t.Fatalf("preview args = %v, want %v", args, wantArgs)
}
for index := range args {
if args[index] != wantArgs[index] {
t.Fatalf("preview arg[%d] = %#v, want %#v", index, args[index], wantArgs[index])
}
}
}