package scout import ( "context" "errors" "testing" "apps/backend/internal/middleware" "apps/backend/internal/module/scout/domain" scoutRepo "apps/backend/internal/module/scout/repository" scoutUC "apps/backend/internal/module/scout/usecase" "apps/backend/internal/svc" "apps/backend/internal/types" ) func testRun(id string, owner int64, created int64, status string) *domain.Run { r := domain.NewRun(id, "job-"+id, owner, domain.RunBrief{ Intent: "敏感肌保養", Mode: domain.ModeTheme, ThemeKey: "skin", ThemeLabel: "敏感肌保養", }, created) r.Status = status if status == domain.RunSucceeded { r.EligibleCount = 1 } return r } func testScoutContext(uid int64, repo *scoutRepo.MemoryStore) (*svc.ServiceContext, context.Context) { service := scoutUC.New(repo) return &svc.ServiceContext{Scout: service}, middleware.WithUID(context.Background(), uid) } func TestRunListLogicOwnerScopedPagination(t *testing.T) { repo := scoutRepo.NewMemory() for _, run := range []*domain.Run{ testRun("run-old", 7, 100, domain.RunSucceeded), testRun("run-new", 7, 200, domain.RunSucceeded), testRun("run-other", 8, 300, domain.RunSucceeded), } { if err := repo.CreateRun(context.Background(), run); err != nil { t.Fatal(err) } } svcCtx, ctx := testScoutContext(7, repo) data, err := NewListScoutRunsLogic(ctx, svcCtx).ListScoutRuns(&types.ScoutRunListReq{Page: 1, PageSize: 1}) if err != nil { t.Fatal(err) } if data.Pagination.Total != 2 || data.Pagination.TotalPages != 2 || len(data.List) != 1 || data.List[0].Id != "run-new" { t.Fatalf("unexpected run page: %+v", data) } if data.List[0].JobId == "" { t.Fatal("run response omitted job id") } } func TestRunPostsLogicReturnsRunAndIndependentPage(t *testing.T) { repo := scoutRepo.NewMemory() run := testRun("run-posts", 7, 100, domain.RunSucceeded) if err := repo.CreateRun(context.Background(), run); err != nil { t.Fatal(err) } for _, post := range []*domain.Post{ {ID: "post-old", RunID: run.ID, OwnerUID: 7, Text: "old", PostedAt: 100, CreatedAt: 100}, {ID: "post-new", RunID: run.ID, OwnerUID: 7, Text: "new", PostedAt: 200, CreatedAt: 200}, } { if err := repo.PublishRunPosts(context.Background(), 7, run.ID, []*domain.Post{post}); err != nil { t.Fatal(err) } } svcCtx, ctx := testScoutContext(7, repo) data, err := NewListScoutRunPostsLogic(ctx, svcCtx).ListScoutRunPosts(&types.ScoutRunPostsReq{RunId: run.ID, Page: 2, PageSize: 1}) if err != nil { t.Fatal(err) } if data.Run.Id != run.ID || data.Pagination.Total != 2 || data.Pagination.Page != 2 || len(data.List) != 1 || data.List[0].Id != "post-old" { t.Fatalf("unexpected run posts page: %+v", data) } } func TestRunRemoveLogicGuardsNonTerminalAndOwner(t *testing.T) { repo := scoutRepo.NewMemory() queued := testRun("run-queued", 7, 100, domain.RunQueued) done := testRun("run-done", 7, 200, domain.RunSucceeded) for _, run := range []*domain.Run{queued, done} { if err := repo.CreateRun(context.Background(), run); err != nil { t.Fatal(err) } } svcCtx, ctx := testScoutContext(7, repo) if _, err := NewRemoveScoutRunLogic(ctx, svcCtx).RemoveScoutRun(&types.ScoutRunPostsReq{RunId: queued.ID}); !errors.Is(err, domain.ErrIllegalRunStatus) { t.Fatalf("queued run removal error = %v, want illegal status", err) } otherCtx := middleware.WithUID(context.Background(), 8) if _, err := NewRemoveScoutRunLogic(otherCtx, svcCtx).RemoveScoutRun(&types.ScoutRunPostsReq{RunId: done.ID}); !errors.Is(err, domain.ErrNotFound) { t.Fatalf("cross-owner removal error = %v, want not found", err) } if data, err := NewRemoveScoutRunLogic(ctx, svcCtx).RemoveScoutRun(&types.ScoutRunPostsReq{RunId: done.ID}); err != nil || !data.Ok { t.Fatalf("terminal run removal = %+v, %v", data, err) } if _, err := repo.GetRun(context.Background(), 7, done.ID); !errors.Is(err, domain.ErrNotFound) { t.Fatalf("removed run still exists: %v", err) } } func TestRunLogicRequiresAuth(t *testing.T) { svcCtx := &svc.ServiceContext{Scout: scoutUC.New(scoutRepo.NewMemory())} _, err := NewListScoutRunsLogic(context.Background(), svcCtx).ListScoutRuns(&types.ScoutRunListReq{}) if err == nil || err.Error() != "missing authorization" { t.Fatalf("auth error = %v", err) } }