107 lines
3.4 KiB
Go

package appuser
import (
"context"
"encoding/json"
"strings"
"testing"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/config"
"hyapp-admin-server/internal/model"
"hyapp-admin-server/internal/modules/shared"
)
type recordingAppUserExportStore struct {
job *model.AdminJob
}
func (s *recordingAppUserExportStore) CreateJob(job *model.AdminJob) error {
copy := *job
copy.ID = 81
job.ID = copy.ID
s.job = &copy
return nil
}
func TestCreateExportJobPersistsFilterSnapshot(t *testing.T) {
store := &recordingAppUserExportStore{}
service := &Service{
jobStore: store,
jobsConfig: config.JobsConfig{
Enabled: true,
MaxAttempts: 5,
},
}
ctx := appctx.WithContext(context.Background(), "lalu")
response, err := service.CreateExportJob(ctx, shared.Actor{UserID: 7, Username: "operator"}, listQuery{
Country: " ph ",
Status: "active",
Keyword: "1299324",
SortBy: "coin",
SortDirection: "asc",
Page: 9,
PageSize: 10,
})
if err != nil {
t.Fatalf("create export job: %v", err)
}
if response.JobID != 81 || response.SnapshotAtMs <= 0 || store.job == nil {
t.Fatalf("response/job mismatch: response=%+v job=%+v", response, store.job)
}
if store.job.Type != AppUserExportJobType || store.job.MaxAttempts != 5 || store.job.CreatedBy != 7 {
t.Fatalf("job metadata mismatch: %+v", store.job)
}
var payload appUserExportPayload
if err := json.Unmarshal([]byte(store.job.PayloadJSON), &payload); err != nil {
t.Fatalf("decode payload: %v", err)
}
if payload.AppCode != "lalu" || payload.Query.Country != "PH" || payload.Query.Status != "active" || payload.Query.Keyword != "1299324" {
t.Fatalf("filter snapshot mismatch: %+v", payload)
}
if payload.Query.Page != 1 || payload.Query.PageSize != 100 || payload.Query.SnapshotAtMs != response.SnapshotAtMs {
t.Fatalf("pagination snapshot mismatch: %+v", payload.Query)
}
if payload.Query.SortBy != "created_at" || payload.Query.SortDirection != "desc" {
t.Fatalf("export must use stable database pagination instead of coin projection sort: %+v", payload.Query)
}
}
func TestStableAppUserExportQueryForcesDescendingKeysetOrder(t *testing.T) {
for _, query := range []listQuery{
{SortBy: "created_at", SortDirection: "asc"},
{SortBy: "coin", SortDirection: "asc"},
} {
stable := stableAppUserExportQuery(normalizeListQuery(query))
if stable.SortBy != "created_at" || stable.SortDirection != "desc" {
t.Fatalf("export order must match descending keyset: input=%+v stable=%+v", query, stable)
}
}
}
func TestAppUserExportWhereUsesSnapshotAndKeysetCursor(t *testing.T) {
query := normalizeListQuery(listQuery{
SnapshotAtMs: 1_000,
ExportCursorCreatedAtMs: 900,
ExportCursorUserID: 88,
})
whereSQL, args := appUserListWhereSQLAt("lalu", query, query.SnapshotAtMs)
for _, fragment := range []string{
"u.created_at_ms <= ?",
"u.created_at_ms < ? OR (u.created_at_ms = ? AND u.user_id < ?)",
} {
if !strings.Contains(whereSQL, fragment) {
t.Fatalf("export where SQL missing %q: %s", fragment, whereSQL)
}
}
wantTail := []any{int64(1_000), int64(900), int64(900), int64(88)}
if len(args) < len(wantTail) {
t.Fatalf("export args too short: %+v", args)
}
for index, want := range wantTail {
if got := args[len(args)-len(wantTail)+index]; got != want {
t.Fatalf("export cursor arg[%d]=%v want=%v all=%+v", index, got, want, args)
}
}
}