thread-master/apps/backend/internal/module/scout/usecase/activity_terms_test.go

150 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package usecase
import (
"context"
"strings"
"testing"
radarDomain "apps/backend/internal/module/radar/domain"
"apps/backend/internal/module/scout/domain"
)
func TestPrepareActivityBriefUsesGeneratedTerms(t *testing.T) {
svc := &Service{}
brief, err := svc.PrepareBrief(context.Background(), 1, "市集", "", "", "activity", true)
if err != nil {
t.Fatalf("PrepareBrief: %v", err)
}
if brief.Mode != domain.ModeActivity {
t.Fatalf("mode=%q want %q", brief.Mode, domain.ModeActivity)
}
if len(brief.ScanTerms) < 2 {
t.Fatalf("ScanTerms=%v want generated variants", brief.ScanTerms)
}
}
func TestPlanActivityTermsGeneratesVariantsNotJustSplit(t *testing.T) {
// 單一字主題:應產出「核 + 口語錨點」,不是只有原字
got := planActivityTerms("市集")
if len(got) < 3 {
t.Fatalf("市集 variants=%v want >=3 generated terms", got)
}
hasPair := false
for _, term := range got {
if !radarDomain.IsThreadsSearchable(term) {
t.Fatalf("term %q not Threads-searchable", term)
}
if strings.Contains(term, " ") {
hasPair = true
}
}
if !hasPair {
t.Fatalf("expected multi-token variants, got %v", got)
}
// 長句:不應只回整句,要抽出核並產變體
got2 := planActivityTerms("想找台北週末市集可以拍什麼")
if len(got2) < 2 {
t.Fatalf("long intent variants=%v want >=2", got2)
}
for _, term := range got2 {
if !radarDomain.IsThreadsSearchable(term) {
t.Fatalf("long-intent term %q not searchable", term)
}
// 不應留下整句長字串
if utf8Count(term) > 12 {
t.Fatalf("term too long: %q", term)
}
}
// 應含地區或市集相關
joined := strings.Join(got2, "|")
if !strings.Contains(joined, "市集") && !strings.Contains(joined, "台北") {
t.Fatalf("expected 市集/台北 in variants, got %v", got2)
}
}
func TestPlanActivityTermsNotJustTokenizeIntent(t *testing.T) {
// 舊行為tokenizeIntent("週末市集") → ["週末市集"] 或空拆
// 新行為:至少有錨點變體
got := planActivityTerms("週末市集")
if len(got) < 2 {
t.Fatalf("got %v, want multiple generated variants", got)
}
// 若只有把四字當一詞,也要再有「週末 市集」類或「市集 推薦」
onlyExact := len(got) == 1 && got[0] == "週末市集"
if onlyExact {
t.Fatal("still only returning exact intent — not generating")
}
}
func TestPlanActivityTermsInfersBackendFreelanceIntent(t *testing.T) {
got := planActivityTerms("外包 工程師 後端")
if len(got) == 0 || got[0] != "後端 外包" {
t.Fatalf("terms=%v want 後端 外包 as primary intent query", got)
}
joined := "|" + strings.Join(got, "|") + "|"
for _, want := range []string{"|後端 外包|", "|後端 接案|", "|後端 工程師|"} {
if !strings.Contains(joined, want) {
t.Fatalf("terms=%v missing inferred query %s", got, want)
}
}
for _, term := range got {
if !radarDomain.IsThreadsSearchable(term) {
t.Fatalf("term %q not Threads-searchable", term)
}
}
}
func TestPlanActivityTermsPreservesExactTitleIntent(t *testing.T) {
got := planActivityTerms("鬼滅之刃")
if len(got) == 0 || got[0] != "鬼滅之刃" {
t.Fatalf("terms=%v want exact title as primary query", got)
}
joined := "|" + strings.Join(got, "|") + "|"
for _, want := range []string{"|鬼滅之刃 最新|", "|鬼滅之刃 熱門|"} {
if !strings.Contains(joined, want) {
t.Fatalf("terms=%v missing discovery query %s", got, want)
}
}
for _, bad := range []string{"鬼滅之", "滅之刃"} {
if strings.Contains(joined, "|"+bad+"|") {
t.Fatalf("broken title fragment %q leaked: %v", bad, got)
}
}
}
func TestMergeActivityTermsKeepsDeterministicIntentFirst(t *testing.T) {
ai := []string{"市集 分享", "文青 市集", "太長了吧這整句不行"}
rules := []string{"市集 推薦", "市集"}
got := mergeActivityTerms(ai, rules, 6)
if len(got) == 0 {
t.Fatal("empty merge")
}
if got[0] != "市集 推薦" {
t.Fatalf("deterministic intent term should come first, got %v", got)
}
// 長詞應被濾掉
for _, t2 := range got {
if strings.Contains(t2, "太長") {
t.Fatalf("unssearchable term leaked: %v", got)
}
}
}
func TestParseActivityAITerms(t *testing.T) {
raw := "這裡是開場\n[\"市集 分享\", \"週末 市集\"]\n結尾"
got := parseActivityAITerms(raw)
if len(got) != 2 {
t.Fatalf("parse strings: %v", got)
}
raw2 := `[{"term":"市集 心得","reason":"x"},{"term":"活動 市集"}]`
got2 := parseActivityAITerms(raw2)
if len(got2) != 2 {
t.Fatalf("parse objects: %v", got2)
}
}
func utf8Count(s string) int {
return len([]rune(strings.ReplaceAll(s, " ", "")))
}