328 lines
9.7 KiB
Go
328 lines
9.7 KiB
Go
package usecase_test
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"testing"
|
||
|
||
"apps/backend/internal/module/ai"
|
||
"apps/backend/internal/module/inspire/domain"
|
||
"apps/backend/internal/module/inspire/repository"
|
||
"apps/backend/internal/module/inspire/usecase"
|
||
"apps/backend/internal/module/search"
|
||
usageDomain "apps/backend/internal/module/usage/domain"
|
||
usageRepo "apps/backend/internal/module/usage/repository"
|
||
usageUC "apps/backend/internal/module/usage/usecase"
|
||
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
func newInspire() *usecase.Service {
|
||
svc := usecase.New(repository.NewMemory())
|
||
ures := usageRepo.NewMemory()
|
||
sr := &usageUC.StaticResolver{Map: map[string]string{}}
|
||
us := usageUC.New(ures, sr)
|
||
svc.Usage = us
|
||
svc.AI = &ai.FakeClient{}
|
||
svc.Search = &search.FakeClient{}
|
||
return svc
|
||
}
|
||
|
||
func setupInspireUID(svc *usecase.Service, uid int64) {
|
||
_ = svc.Usage.Repo.SavePrefs(context.Background(), &usageDomain.MemberPrefs{
|
||
UID: uid, PlanID: usageDomain.PlanPro, Unlimited: true, UpdatedAt: domain.NowNano(),
|
||
})
|
||
if sr, ok := svc.Usage.Resolver.(*usageUC.StaticResolver); ok {
|
||
if sr.Map == nil {
|
||
sr.Map = map[string]string{}
|
||
}
|
||
for _, m := range []string{usageDomain.MeterAICopy, usageDomain.MeterWebSearch, usageDomain.MeterAIImage} {
|
||
sr.Map[itoa(uid)+":"+m] = usageDomain.KeyModePlatform
|
||
}
|
||
}
|
||
}
|
||
|
||
func itoa(n int64) string {
|
||
if n == 0 {
|
||
return "0"
|
||
}
|
||
neg := n < 0
|
||
if neg {
|
||
n = -n
|
||
}
|
||
var b [32]byte
|
||
i := len(b)
|
||
for n > 0 {
|
||
i--
|
||
b[i] = byte('0' + n%10)
|
||
n /= 10
|
||
}
|
||
if neg {
|
||
i--
|
||
b[i] = '-'
|
||
}
|
||
return string(b[i:])
|
||
}
|
||
|
||
// Chat(ctx, uid, message, pinned, mode, persona, sessionID, material)
|
||
func chat(svc *usecase.Service, uid int64, msg, mode, sessionID, material string) (*usecase.ChatOutcome, error) {
|
||
return svc.Chat(context.Background(), uid, msg, nil, mode, "", sessionID, material)
|
||
}
|
||
|
||
func TestIN_01_ListTrends(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_001)
|
||
setupInspireUID(svc, uid)
|
||
list, err := svc.ListTrends(context.Background(), uid)
|
||
require.NoError(t, err)
|
||
require.NotEmpty(t, list)
|
||
require.NotEmpty(t, list[0].Label)
|
||
}
|
||
|
||
func TestIN_02_RefreshTrends(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_002)
|
||
setupInspireUID(svc, uid)
|
||
_, _ = svc.ListTrends(context.Background(), uid)
|
||
list, err := svc.RefreshTrends(context.Background(), uid)
|
||
require.NoError(t, err)
|
||
require.NotEmpty(t, list)
|
||
}
|
||
|
||
func TestIN_03_ElementCRUD(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_003)
|
||
setupInspireUID(svc, uid)
|
||
el, err := svc.SaveElement(context.Background(), uid, &domain.Element{
|
||
Kind: "snippet", Title: "鉤子", Body: "先講痛點", Reusable: true,
|
||
})
|
||
require.NoError(t, err)
|
||
list, err := svc.ListElements(context.Background(), uid)
|
||
require.NoError(t, err)
|
||
require.Len(t, list, 1)
|
||
require.NoError(t, svc.RemoveElement(context.Background(), uid, el.ID))
|
||
list, _ = svc.ListElements(context.Background(), uid)
|
||
require.Empty(t, list)
|
||
}
|
||
|
||
func TestIN_04_ChatMode(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_004)
|
||
setupInspireUID(svc, uid)
|
||
out, err := chat(svc, uid, "幫我想開場", "chat", "", "")
|
||
require.NoError(t, err)
|
||
require.NotNil(t, out)
|
||
sess := out.Session
|
||
require.GreaterOrEqual(t, len(sess.Messages), 2)
|
||
require.Equal(t, "assistant", sess.Messages[len(sess.Messages)-1].Role)
|
||
require.Nil(t, sess.Messages[len(sess.Messages)-1].Draft)
|
||
require.NotEmpty(t, out.Fingerprint)
|
||
require.Contains(t, out.Prompt, "發想")
|
||
}
|
||
|
||
func TestIN_05_GenerateRequiresMaterialAndRewrites(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_005)
|
||
setupInspireUID(svc, uid)
|
||
// 無素材 → 拒絕
|
||
_, err := chat(svc, uid, "產文", "generate", "", "")
|
||
require.Error(t, err)
|
||
|
||
mat := "遠端上班第三年,會議永遠開不完,想找一種不裝的吐槽角度。"
|
||
out, err := chat(svc, uid, "短一點、口語", "generate", "", mat)
|
||
require.NoError(t, err)
|
||
last := out.Session.Messages[len(out.Session.Messages)-1]
|
||
require.NotNil(t, last.Draft)
|
||
require.NotEmpty(t, last.Draft.Body)
|
||
require.Contains(t, out.Prompt, "待改寫內容")
|
||
require.Contains(t, out.Prompt, "改寫者")
|
||
require.Contains(t, out.Prompt, mat[:8])
|
||
// session 泡泡只留短紀錄,不是整包素材正文
|
||
user := out.Session.Messages[len(out.Session.Messages)-2]
|
||
require.True(t, strings.HasPrefix(user.Text, "【產文】"))
|
||
require.Less(t, len([]rune(user.Text)), len([]rune(mat))+20)
|
||
}
|
||
|
||
func TestIN_07_PreviewMatchesChatFingerprint(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_007)
|
||
setupInspireUID(svc, uid)
|
||
msg := "寫一段開場鉤子"
|
||
prev, err := svc.PreviewPrompt(context.Background(), uid, msg, nil, "chat", "", "", "")
|
||
require.NoError(t, err)
|
||
require.NotEmpty(t, prev.Fingerprint)
|
||
out, err := chat(svc, uid, msg, "chat", "", "")
|
||
require.NoError(t, err)
|
||
require.Equal(t, prev.Fingerprint, out.Fingerprint, "preview and first chat must share fingerprint")
|
||
require.Equal(t, prev.Prompt, out.Prompt)
|
||
}
|
||
|
||
func TestIN_06_ResearchSearch(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_006)
|
||
setupInspireUID(svc, uid)
|
||
hits, err := svc.ResearchSearch(context.Background(), uid, "敏感肌保養")
|
||
require.NoError(t, err)
|
||
require.NotEmpty(t, hits)
|
||
require.NotEmpty(t, hits[0].URL)
|
||
}
|
||
|
||
func TestIN_07_GenerateImage(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_017)
|
||
setupInspireUID(svc, uid)
|
||
img, err := svc.GenerateImage(context.Background(), uid, "溫暖海邊插畫")
|
||
require.NoError(t, err)
|
||
require.NotEmpty(t, img.URL)
|
||
require.NotEmpty(t, img.ID)
|
||
}
|
||
|
||
func TestIN_08_UploadSeparate(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_008)
|
||
setupInspireUID(svc, uid)
|
||
img, err := svc.GenerateImage(context.Background(), uid, "x")
|
||
require.NoError(t, err)
|
||
require.True(t, len(img.URL) > 8)
|
||
}
|
||
|
||
func TestIN_09_LegacyRemoved(t *testing.T) {
|
||
svc := newInspire()
|
||
require.ErrorIs(t, svc.LegacyRemoved(), domain.ErrRemoved)
|
||
}
|
||
|
||
func TestIN_10_ClearSessionCreatesNewKeepsOld(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_010)
|
||
setupInspireUID(svc, uid)
|
||
out, err := chat(svc, uid, "先聊一句", "chat", "", "")
|
||
require.NoError(t, err)
|
||
oldID := out.Session.ID
|
||
require.NotEmpty(t, oldID)
|
||
require.NotEmpty(t, out.Session.Messages)
|
||
|
||
next, err := svc.ClearSession(context.Background(), uid)
|
||
require.NoError(t, err)
|
||
require.NotEqual(t, oldID, next.ID)
|
||
require.Empty(t, next.Messages)
|
||
|
||
list, err := svc.ListSessionSummaries(context.Background(), uid)
|
||
require.NoError(t, err)
|
||
require.GreaterOrEqual(t, len(list), 2)
|
||
ids := map[string]bool{}
|
||
for _, s := range list {
|
||
ids[s.ID] = true
|
||
}
|
||
require.True(t, ids[oldID])
|
||
require.True(t, ids[next.ID])
|
||
|
||
old, err := svc.ActivateSession(context.Background(), uid, oldID)
|
||
require.NoError(t, err)
|
||
require.Equal(t, oldID, old.ID)
|
||
require.NotEmpty(t, old.Messages)
|
||
}
|
||
|
||
func TestIN_11_MultiSessionSwitchAndDelete(t *testing.T) {
|
||
svc := newInspire()
|
||
uid := int64(5_001_011)
|
||
setupInspireUID(svc, uid)
|
||
a, err := chat(svc, uid, "話題A", "chat", "", "")
|
||
require.NoError(t, err)
|
||
b, err := svc.CreateSession(context.Background(), uid, "")
|
||
require.NoError(t, err)
|
||
require.NotEqual(t, a.Session.ID, b.ID)
|
||
outB, err := chat(svc, uid, "話題B", "chat", b.ID, "")
|
||
require.NoError(t, err)
|
||
require.Equal(t, b.ID, outB.Session.ID)
|
||
|
||
after, err := svc.DeleteSession(context.Background(), uid, b.ID)
|
||
require.NoError(t, err)
|
||
require.NotEqual(t, b.ID, after.ID)
|
||
list, err := svc.ListSessionSummaries(context.Background(), uid)
|
||
require.NoError(t, err)
|
||
for _, s := range list {
|
||
require.NotEqual(t, b.ID, s.ID)
|
||
}
|
||
}
|
||
|
||
func TestIN_12_SessionIsolationAndHistoryKept(t *testing.T) {
|
||
svc := newInspire()
|
||
uidA := int64(5_001_012)
|
||
uidB := int64(5_001_013)
|
||
setupInspireUID(svc, uidA)
|
||
setupInspireUID(svc, uidB)
|
||
|
||
outA1, err := chat(svc, uidA, "A的第一則", "chat", "", "")
|
||
require.NoError(t, err)
|
||
sessA1 := outA1.Session.ID
|
||
|
||
sA2, err := svc.CreateSession(context.Background(), uidA, "")
|
||
require.NoError(t, err)
|
||
outA2, err := chat(svc, uidA, "A的第二則對話", "chat", sA2.ID, "")
|
||
require.NoError(t, err)
|
||
require.Equal(t, sA2.ID, outA2.Session.ID)
|
||
|
||
outB, err := chat(svc, uidB, "B的秘密話題", "chat", "", "")
|
||
require.NoError(t, err)
|
||
|
||
_, err = svc.GetSessionByID(context.Background(), uidB, sessA1)
|
||
require.ErrorIs(t, err, domain.ErrNotFound)
|
||
|
||
listA, err := svc.ListSessionSummaries(context.Background(), uidA)
|
||
require.NoError(t, err)
|
||
for _, s := range listA {
|
||
require.NotEqual(t, outB.Session.ID, s.ID)
|
||
}
|
||
require.GreaterOrEqual(t, len(listA), 2)
|
||
|
||
back, err := svc.ActivateSession(context.Background(), uidA, sessA1)
|
||
require.NoError(t, err)
|
||
require.Equal(t, sessA1, back.ID)
|
||
found := false
|
||
for _, m := range back.Messages {
|
||
if m.Role == "user" && m.Text == "A的第一則" {
|
||
found = true
|
||
}
|
||
require.NotEqual(t, "B的秘密話題", m.Text)
|
||
require.NotEqual(t, "A的第二則對話", m.Text)
|
||
}
|
||
require.True(t, found)
|
||
|
||
back2, err := svc.ActivateSession(context.Background(), uidA, sA2.ID)
|
||
require.NoError(t, err)
|
||
found2 := false
|
||
for _, m := range back2.Messages {
|
||
if m.Text == "A的第二則對話" {
|
||
found2 = true
|
||
}
|
||
require.NotEqual(t, "A的第一則", m.Text)
|
||
}
|
||
require.True(t, found2)
|
||
}
|
||
|
||
func TestIN_13_ListTrendsDoesNotHitLiveSearch(t *testing.T) {
|
||
svc := usecase.New(repository.NewMemory())
|
||
uid := int64(5_001_014)
|
||
list, err := svc.ListTrends(context.Background(), uid)
|
||
require.NoError(t, err)
|
||
require.NotEmpty(t, list)
|
||
for _, titem := range list {
|
||
require.Equal(t, "seed", titem.SourceLabel)
|
||
}
|
||
}
|
||
|
||
func TestComposeDraftMaterial(t *testing.T) {
|
||
sess := &domain.Session{
|
||
Title: "周末咖啡",
|
||
Messages: []domain.ChatMessage{
|
||
{Role: "user", Text: "想寫周末放空"},
|
||
{Role: "assistant", Text: "目前共識:慢節奏、不裝"},
|
||
{Role: "user", Text: "【產文】短一點|素材:xxx"},
|
||
},
|
||
}
|
||
mat := usecase.ComposeDraftMaterial(sess, "")
|
||
require.Contains(t, mat, "周末咖啡")
|
||
require.Contains(t, mat, "想寫周末放空")
|
||
require.NotContains(t, mat, "【產文】")
|
||
}
|