113 lines
4.3 KiB
Go
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])
|
|
}
|
|
}
|
|
}
|