spark-server/internal/module/client/logic/service_test.go
DESKTOP-L8OUL9F\gfl0034 5e8ab6e12b test: 补意见反馈登录/游客单测断言
覆盖游客缺 contact 失败与登录态 uid/app_version 回填,避免分流逻辑回归。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 16:25:10 +08:00

246 lines
7.6 KiB
Go

package logic
import (
<<<<<<< HEAD
"context"
"testing"
"spark-server/internal/module/client/entity"
userentity "spark-server/internal/module/user/entity"
"spark-server/internal/pkg/errs"
"spark-server/internal/pkg/i18n"
"spark-server/internal/pkg/reqctx"
)
type stubClientRepo struct {
legal map[string]map[string]*entity.LegalDocument
feedback []entity.Feedback
plans []entity.SubscriptionPlan
passwords map[int64]string
}
func (s *stubClientRepo) GetLegal(_ context.Context, lang string) (*entity.LegalDocument, *entity.LegalDocument, error) {
if s.legal == nil {
return nil, nil, errs.ErrNotFound
}
byLang := s.legal[lang]
if byLang == nil {
return nil, nil, errs.ErrNotFound
}
return byLang[entity.DocPrivacy], byLang[entity.DocTerms], nil
}
func (s *stubClientRepo) UpsertLegal(_ context.Context, d *entity.LegalDocument) error {
if s.legal == nil {
s.legal = map[string]map[string]*entity.LegalDocument{}
}
if s.legal[d.Lang] == nil {
s.legal[d.Lang] = map[string]*entity.LegalDocument{}
}
cp := *d
s.legal[d.Lang][d.Key] = &cp
return nil
}
func (s *stubClientRepo) InsertFeedback(_ context.Context, f *entity.Feedback) error {
f.ID = int64(len(s.feedback) + 1)
s.feedback = append(s.feedback, *f)
return nil
}
func (s *stubClientRepo) ListFeedback(_ context.Context, _, _ int) ([]entity.Feedback, int64, error) {
return s.feedback, int64(len(s.feedback)), nil
}
func (s *stubClientRepo) UpdateFeedbackStatus(_ context.Context, id int64, status string) error {
for i := range s.feedback {
if s.feedback[i].ID == id {
s.feedback[i].Status = status
return nil
}
}
return errs.ErrNotFound
}
func (s *stubClientRepo) ListPlans(_ context.Context, lang string) ([]entity.SubscriptionPlan, error) {
out := make([]entity.SubscriptionPlan, 0)
for i := range s.plans {
p := &s.plans[i]
if p.Lang == lang && p.Enabled {
out = append(out, *p)
}
}
return out, nil
}
func (s *stubClientRepo) ListPlansAdmin(_ context.Context, lang string) ([]entity.SubscriptionPlan, error) {
if lang == "" {
return s.plans, nil
}
out := make([]entity.SubscriptionPlan, 0)
for i := range s.plans {
p := &s.plans[i]
if p.Lang == lang {
out = append(out, *p)
}
}
return out, nil
}
func (s *stubClientRepo) UpsertPlan(_ context.Context, p *entity.SubscriptionPlan) error {
s.plans = append(s.plans, *p)
return nil
}
func (s *stubClientRepo) GetPasswordHash(_ context.Context, userID int64) (string, error) {
if s.passwords == nil {
return "", nil
}
return s.passwords[userID], nil
}
func (s *stubClientRepo) SetPasswordHash(_ context.Context, userID int64, hash string) error {
if s.passwords == nil {
s.passwords = map[int64]string{}
}
s.passwords[userID] = hash
return nil
}
type stubUserFinder struct {
byEmail map[string]*userentity.User
}
func (s stubUserFinder) GetByEmail(_ context.Context, email string) (*userentity.User, error) {
u := s.byEmail[userentity.NormalizeEmail(email)]
if u == nil {
return nil, errs.ErrNotFound
}
return u, nil
}
type stubOTP struct {
user *userentity.User
}
func (s stubOTP) AuthenticateEmail(context.Context, string, string, *userentity.LoginDevice) (*userentity.User, error) {
if s.user == nil {
return nil, errs.ErrUnauthorized
}
return s.user, nil
}
func ctxLang(lang string) context.Context {
return reqctx.With(context.Background(), &reqctx.RequestContext{Language: lang})
}
=======
"testing"
"spark-server/internal/module/client/entity"
"spark-server/internal/pkg/reqctx"
)
>>>>>>> c844c07 (feat: 意见反馈支持登录与游客分流)
func TestLegalPlansFeedbackFallback(t *testing.T) {
svc := NewService(nil, nil, nil)
legal, err := svc.LegalAgreements(t.Context())
if err != nil || legal.Privacy.Title == "" || legal.Terms.Title == "" {
t.Fatalf("legal %+v err=%v", legal, err)
}
plans, err := svc.SubscriptionPlans(t.Context())
if err != nil || len(plans.Plans) != 4 {
t.Fatalf("plans %+v err=%v", plans, err)
}
fb, err := svc.SubmitFeedback(t.Context(), FeedbackInput{Content: "界面很好", Contact: "a@b.com"})
if err != nil || fb.ID == 0 || fb.AuthType != entity.FeedbackAuthGuest {
t.Fatalf("feedback %+v err=%v", fb, err)
}
if _, err := svc.SubmitFeedback(t.Context(), FeedbackInput{Content: "缺联系方式"}); err == nil {
t.Fatal("guest without contact should fail")
}
ctx := reqctx.With(t.Context(), &reqctx.RequestContext{UserID: 42, AppVersion: "1.2.3"})
loginFB, err := svc.SubmitFeedback(ctx, FeedbackInput{Content: "登录用户反馈"})
if err != nil || loginFB.AuthType != entity.FeedbackAuthLogin || loginFB.UserID == nil || *loginFB.UserID != 42 {
t.Fatalf("login feedback %+v err=%v", loginFB, err)
}
if loginFB.AppVersion != "1.2.3" {
t.Fatalf("app_version from header want 1.2.3 got %q", loginFB.AppVersion)
}
}
func TestClientServiceAdminAndRepo(t *testing.T) {
repo := &stubClientRepo{
legal: map[string]map[string]*entity.LegalDocument{
string(i18n.ZhCN): {
entity.DocPrivacy: {Key: entity.DocPrivacy, Lang: string(i18n.ZhCN), Title: "P", Content: "c", Version: "1"},
entity.DocTerms: {Key: entity.DocTerms, Lang: string(i18n.ZhCN), Title: "T", Content: "c", Version: "1"},
},
},
plans: []entity.SubscriptionPlan{{PlanID: "month", Lang: string(i18n.ZhCN), Title: "月", Enabled: true}},
}
svc := NewService(repo, nil, nil)
if !svc.Ready() {
t.Fatal("ready")
}
ctx := ctxLang("en")
bundle, err := svc.LegalAgreements(ctx)
if err != nil || bundle.Privacy.Title != "P" {
t.Fatalf("legal %+v %v", bundle, err)
}
if err := svc.UpsertLegalAdmin(ctx, &entity.LegalDocument{Key: "bad", Lang: "en", Title: "x", Content: "y"}); err == nil {
t.Fatal("bad key")
}
if err := svc.UpsertLegalAdmin(ctx, &entity.LegalDocument{Key: entity.DocPrivacy, Lang: "en", Title: "Privacy", Content: "body"}); err != nil {
t.Fatal(err)
}
list, total, err := svc.ListFeedbackAdmin(ctx, 0, 10)
if err != nil || total != 0 || len(list) != 0 {
t.Fatalf("feedback list %+v %d %v", list, total, err)
}
_, err = svc.SubmitFeedback(ctx, FeedbackInput{Content: "ok"})
if err != nil {
t.Fatal(err)
}
if err := svc.UpdateFeedbackStatus(ctx, 1, "closed"); err != nil {
t.Fatal(err)
}
adminPlans, err := svc.ListPlansAdmin(ctx, string(i18n.ZhCN))
if err != nil || len(adminPlans) != 1 {
t.Fatalf("plans %+v %v", adminPlans, err)
}
if err := svc.UpsertPlanAdmin(ctx, &entity.SubscriptionPlan{PlanID: "week", Lang: "en", Title: "W", PriceCents: 100}); err != nil {
t.Fatal(err)
}
if err := svc.UpsertPlanAdmin(ctx, &entity.SubscriptionPlan{PlanID: "bad", Lang: "en"}); err == nil {
t.Fatal("bad plan")
}
}
func TestClientPasswordFlow(t *testing.T) {
repo := &stubClientRepo{}
user := &userentity.User{ID: 42, Status: userentity.StatusActive}
email := "real@example.com"
svc := NewService(repo, stubUserFinder{byEmail: map[string]*userentity.User{
userentity.NormalizeEmail(email): user,
}}, stubOTP{user: user})
ctx := ctxLang(string(i18n.ZhCN))
if err := svc.SetPassword(ctx, SetPasswordInput{Email: email, VerifyCode: "1", Password: "secret1"}); err != nil {
t.Fatal(err)
}
got, err := svc.VerifyPassword(ctx, email, "secret1")
if err != nil || got.ID != user.ID {
t.Fatalf("verify %+v %v", got, err)
}
if _, err := svc.VerifyPassword(ctx, email, "wrong"); err == nil {
t.Fatal("want auth fail")
}
testUser := &userentity.User{ID: 7, Status: userentity.StatusActive}
testEmail := "test01@spark.com"
svc2 := NewService(nil, stubUserFinder{byEmail: map[string]*userentity.User{
userentity.NormalizeEmail(testEmail): testUser,
}}, nil)
if _, err := svc2.VerifyPassword(ctx, testEmail, "any"); err != nil {
t.Fatalf("test email skip password %v", err)
}
}