package usecase_test import ( "context" "testing" "apps/backend/internal/module/scout/domain" "apps/backend/internal/module/scout/repository" "apps/backend/internal/module/scout/usecase" studioPublish "apps/backend/internal/module/studio/publish" "github.com/stretchr/testify/require" ) type staticDevMode struct { on bool } func (s *staticDevMode) DevModeEnabled(_ context.Context, _ int64) (bool, error) { return s.on, nil } func newScout() (*usecase.Service, *studioPublish.FakeTransport, *staticDevMode) { tp := studioPublish.NewFake() dev := &staticDevMode{} svc := usecase.New(repository.NewMemory()) svc.Transport = tp svc.Settings = dev return svc, tp, dev } func TestSC_01_CreateBrandProduct(t *testing.T) { svc, _, _ := newScout() uid := int64(5_002_001) b, err := svc.CreateBrand(context.Background(), uid, "海港", "敏感肌") require.NoError(t, err) p, err := svc.SaveProduct(context.Background(), uid, &domain.Product{ BrandID: b.ID, Label: "無香洗髮", PainPoints: []string{"刺鼻"}, MatchTags: []string{"洗髮"}, }) require.NoError(t, err) list, err := svc.ListProducts(context.Background(), uid, b.ID) require.NoError(t, err) require.Len(t, list, 1) require.Equal(t, p.ID, list[0].ID) } func TestSC_02_DeleteBrandWithProductsRefused(t *testing.T) { svc, _, _ := newScout() uid := int64(5_002_002) b, _ := svc.CreateBrand(context.Background(), uid, "B", "") _, _ = svc.SaveProduct(context.Background(), uid, &domain.Product{BrandID: b.ID, Label: "P"}) err := svc.RemoveBrand(context.Background(), uid, b.ID) require.ErrorIs(t, err, domain.ErrHasProducts) } func TestSC_03_PrepareBrief(t *testing.T) { svc, _, _ := newScout() uid := int64(5_002_003) brief, err := svc.PrepareBrief(context.Background(), uid, "敏感肌保養", "", "", "value", false) require.NoError(t, err) require.NotEmpty(t, brief.ScanTerms) require.Equal(t, "敏感肌保養", brief.Intent) } func TestSC_04_PrepareBriefEmptyIntent(t *testing.T) { svc, _, _ := newScout() _, err := svc.PrepareBrief(context.Background(), 1, " ", "", "", "", false) require.Error(t, err) } func TestSC_05_ApiPath(t *testing.T) { svc, _, dev := newScout() uid := int64(5_002_005) dev.on = false brief, _ := svc.PrepareBrief(context.Background(), uid, "遠端辦公", "", "", "value", false) posts, err := svc.RunScanFromBrief(context.Background(), uid, brief) require.NoError(t, err) require.NotEmpty(t, posts) require.Equal(t, domain.PathAPI, posts[0].ScanPath) } func TestSC_06_CrawlerPath(t *testing.T) { svc, _, dev := newScout() uid := int64(5_002_006) dev.on = true require.NoError(t, svc.SetCrawlerSession(context.Background(), uid, "ext-session-1")) brief, _ := svc.PrepareBrief(context.Background(), uid, "成分黨", "", "", "value", false) posts, err := svc.RunScanFromBrief(context.Background(), uid, brief) require.NoError(t, err) require.Equal(t, domain.PathCrawler, posts[0].ScanPath) } func TestSC_07_NoSession(t *testing.T) { svc, _, dev := newScout() uid := int64(5_002_007) dev.on = true brief, _ := svc.PrepareBrief(context.Background(), uid, "無 session", "", "", "value", false) _, err := svc.RunScanFromBrief(context.Background(), uid, brief) require.ErrorIs(t, err, domain.ErrNoCrawlerSession) } func TestSC_08_ListPostsAfterScan(t *testing.T) { svc, _, _ := newScout() uid := int64(5_002_008) brief, _ := svc.PrepareBrief(context.Background(), uid, "list test", "", "", "value", false) _, _ = svc.RunScanFromBrief(context.Background(), uid, brief) list, err := svc.ListPosts(context.Background(), uid, "") require.NoError(t, err) require.NotEmpty(t, list) require.True(t, list[0].CreatedAt > 0) } func TestSC_09_DraftOutreach(t *testing.T) { svc, _, _ := newScout() uid := int64(5_002_009) brief, _ := svc.PrepareBrief(context.Background(), uid, "draft", "", "", "value", false) posts, _ := svc.RunScanFromBrief(context.Background(), uid, brief) p, err := svc.DraftOutreach(context.Background(), uid, posts[0].ID, "") require.NoError(t, err) require.Equal(t, domain.OutreachDrafted, p.OutreachStatus) require.NotEmpty(t, p.DraftText) } func TestSC_10_SkipAndMark(t *testing.T) { svc, _, _ := newScout() uid := int64(5_002_010) brief, _ := svc.PrepareBrief(context.Background(), uid, "skip", "", "", "value", false) posts, _ := svc.RunScanFromBrief(context.Background(), uid, brief) p, err := svc.SkipOutreach(context.Background(), uid, posts[0].ID) require.NoError(t, err) require.Equal(t, domain.OutreachSkipped, p.OutreachStatus) if len(posts) > 1 { p2, err := svc.MarkPublished(context.Background(), uid, posts[1].ID) require.NoError(t, err) require.Equal(t, domain.OutreachPublished, p2.OutreachStatus) } } func TestSC_11_SendOutreach(t *testing.T) { svc, tp, _ := newScout() uid := int64(5_002_011) brief, _ := svc.PrepareBrief(context.Background(), uid, "send", "", "", "value", false) posts, _ := svc.RunScanFromBrief(context.Background(), uid, brief) p, err := svc.SendOutreach(context.Background(), uid, posts[0].ID, "你好呀", "acc1") require.NoError(t, err) require.Equal(t, domain.OutreachPublished, p.OutreachStatus) require.Equal(t, 1, tp.CallCount()) // fail path tp.Fail = true brief2, _ := svc.PrepareBrief(context.Background(), uid, "sendfail", "", "", "value", false) posts2, _ := svc.RunScanFromBrief(context.Background(), uid, brief2) _, err = svc.SendOutreach(context.Background(), uid, posts2[0].ID, "nope", "") require.Error(t, err) got, _ := svc.ListPosts(context.Background(), uid, "") for _, x := range got { if x.ID == posts2[0].ID { require.NotEqual(t, domain.OutreachPublished, x.OutreachStatus) } } } func TestSC_12_RemovePostTheme(t *testing.T) { svc, _, _ := newScout() uid := int64(5_002_012) brief, _ := svc.PrepareBrief(context.Background(), uid, "rm", "", "", "value", false) posts, _ := svc.RunScanFromBrief(context.Background(), uid, brief) require.NoError(t, svc.RemovePost(context.Background(), uid, posts[0].ID)) require.NoError(t, svc.RemoveTheme(context.Background(), uid, brief.ThemeKey)) list, _ := svc.ListPosts(context.Background(), uid, "") require.Empty(t, list) } func TestSC_13_Homework(t *testing.T) { svc, _, _ := newScout() uid := int64(5_002_013) brief, _ := svc.PrepareBrief(context.Background(), uid, "hw", "", "", "value", false) h, err := svc.SaveHomework(context.Background(), uid, &domain.Homework{ ThemeKey: brief.ThemeKey, ThemeLabel: brief.ThemeLabel, Purpose: "value", Brief: *brief, }) require.NoError(t, err) list, err := svc.ListHomework(context.Background(), uid) require.NoError(t, err) require.Len(t, list, 1) got, err := svc.GetHomework(context.Background(), uid, h.ThemeKey) require.NoError(t, err) require.Equal(t, brief.Intent, got.Brief.Intent) require.NoError(t, svc.RemoveHomework(context.Background(), uid, h.ThemeKey)) } func TestSC_14_ImportProductFromUrl(t *testing.T) { svc, _, _ := newScout() d, err := svc.ImportProductFromURL(context.Background(), "https://shop.example.com/item/1") require.NoError(t, err) require.NotEmpty(t, d.Label) require.Equal(t, "https://shop.example.com/item/1", d.PlacementURL) } func TestSC_TopicRemoved(t *testing.T) { svc, _, _ := newScout() require.ErrorIs(t, svc.TopicRemoved(), domain.ErrTopicRemoved) }