165 lines
4.9 KiB
Go
165 lines
4.9 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"io"
|
||
"log/slog"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"ws_usernode/internal/model"
|
||
)
|
||
|
||
// recordingMail 捕获邮件发送,用于断言审批通知。
|
||
type recordingMail struct {
|
||
lastTo string
|
||
lastSubject string
|
||
lastBody string
|
||
}
|
||
|
||
func (m *recordingMail) Send(_ context.Context, to, subject, body string) error {
|
||
m.lastTo = to
|
||
m.lastSubject = subject
|
||
m.lastBody = body
|
||
return nil
|
||
}
|
||
|
||
func testApprovalSvc(t *testing.T) (*ApprovalService, *UserService, *recordingMail) {
|
||
t.Helper()
|
||
db := testDB(t)
|
||
sys := newFakeSys()
|
||
users := NewUserService(db, sys, testConfig())
|
||
mailer := &recordingMail{}
|
||
log := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||
return NewApprovalService(db, testConfig(), users, mailer, log), users, mailer
|
||
}
|
||
|
||
func TestApprovalServiceSubmitValidate(t *testing.T) {
|
||
svc, _, _ := testApprovalSvc(t)
|
||
ctx := context.Background()
|
||
|
||
// 非法用户名 / 邮箱
|
||
if _, err := svc.Submit(ctx, "Bad Name", "x@example.com", "", ""); err == nil {
|
||
t.Fatal("invalid username should fail")
|
||
}
|
||
if _, err := svc.Submit(ctx, "okname", "not-an-email", "", ""); err == nil {
|
||
t.Fatal("invalid email should fail")
|
||
}
|
||
|
||
// 合法提交
|
||
a, err := svc.Submit(ctx, "zhangsan", "zs@example.com", "prof.li", "科研")
|
||
if err != nil {
|
||
t.Fatalf("submit: %v", err)
|
||
}
|
||
if a.Status != model.StatusPending || a.UsernameRequested != "zhangsan" {
|
||
t.Fatalf("approval = %+v", a)
|
||
}
|
||
|
||
// 同名待审批 → 冲突
|
||
if _, err := svc.Submit(ctx, "zhangsan", "z2@example.com", "", ""); !errors.Is(err, ErrUsernameTaken) {
|
||
t.Fatalf("duplicate pending err = %v, want ErrUsernameTaken", err)
|
||
}
|
||
}
|
||
|
||
func TestApprovalServiceReviewApproveCreatesUser(t *testing.T) {
|
||
svc, users, mailer := testApprovalSvc(t)
|
||
ctx := context.Background()
|
||
|
||
a, err := svc.Submit(ctx, "lisi", "ls@example.com", "prof.wang", "毕设")
|
||
if err != nil {
|
||
t.Fatalf("submit: %v", err)
|
||
}
|
||
|
||
// 拒绝必须填理由
|
||
if _, err := svc.Review(ctx, a.ID, false, 1, " "); !errors.Is(err, ErrReasonRequired) {
|
||
t.Fatalf("reject without reason err = %v, want ErrReasonRequired", err)
|
||
}
|
||
|
||
// 通过 → 自动建号(系统账号 + DB 用户)+ 通知邮件
|
||
got, err := svc.Review(ctx, a.ID, true, 1, "")
|
||
if err != nil {
|
||
t.Fatalf("approve: %v", err)
|
||
}
|
||
if got.Status != model.StatusApproved || got.ReviewerID == nil || *got.ReviewerID != 1 {
|
||
t.Fatalf("approved = %+v", got)
|
||
}
|
||
u, err := users.GetByUsername(ctx, "ext_lisi")
|
||
if err != nil {
|
||
t.Fatalf("user not created: %v", err)
|
||
}
|
||
if u.Status != model.UserStatusActive {
|
||
t.Fatalf("user status = %s", u.Status)
|
||
}
|
||
if mailer.lastTo != "ls@example.com" || !strings.Contains(mailer.lastBody, "ext_lisi") {
|
||
t.Fatalf("approval mail = %s / %s", mailer.lastTo, mailer.lastBody)
|
||
}
|
||
// 默认 TTL(90 天)生效
|
||
if u.ExpireAt == nil || time.Until(*u.ExpireAt) < 80*24*time.Hour {
|
||
t.Fatalf("expire_at = %v", u.ExpireAt)
|
||
}
|
||
|
||
// 重复审批 → 冲突
|
||
if _, err := svc.Review(ctx, a.ID, false, 1, "x"); !errors.Is(err, ErrApprovalReviewed) {
|
||
t.Fatalf("re-review err = %v, want ErrApprovalReviewed", err)
|
||
}
|
||
// 用户名已被占用(已建号)→ 新申请冲突
|
||
if _, err := svc.Submit(ctx, "lisi", "x@example.com", "", ""); !errors.Is(err, ErrUsernameTaken) {
|
||
t.Fatalf("resubmit taken err = %v, want ErrUsernameTaken", err)
|
||
}
|
||
}
|
||
|
||
func TestApprovalServiceRejectAndResubmit(t *testing.T) {
|
||
svc, _, mailer := testApprovalSvc(t)
|
||
ctx := context.Background()
|
||
|
||
a, err := svc.Submit(ctx, "wangwu", "ww@example.com", "", "")
|
||
if err != nil {
|
||
t.Fatalf("submit: %v", err)
|
||
}
|
||
got, err := svc.Review(ctx, a.ID, false, 1, "用途不明确")
|
||
if err != nil {
|
||
t.Fatalf("reject: %v", err)
|
||
}
|
||
if got.Status != model.StatusRejected || got.Reason != "用途不明确" {
|
||
t.Fatalf("rejected = %+v", got)
|
||
}
|
||
if mailer.lastTo != "ww@example.com" || !strings.Contains(mailer.lastBody, "用途不明确") {
|
||
t.Fatalf("reject mail = %s / %s", mailer.lastTo, mailer.lastBody)
|
||
}
|
||
|
||
// 被拒后可重新提交同名申请
|
||
if _, err := svc.Submit(ctx, "wangwu", "ww2@example.com", "", ""); err != nil {
|
||
t.Fatalf("resubmit after reject: %v", err)
|
||
}
|
||
// 不存在 → ErrApprovalNotFound
|
||
if _, err := svc.Review(ctx, 99999, true, 1, ""); !errors.Is(err, ErrApprovalNotFound) {
|
||
t.Fatalf("missing review err = %v, want ErrApprovalNotFound", err)
|
||
}
|
||
}
|
||
|
||
func TestApprovalServiceList(t *testing.T) {
|
||
svc, _, _ := testApprovalSvc(t)
|
||
ctx := context.Background()
|
||
for _, n := range []string{"a1", "a2", "a3"} {
|
||
if _, err := svc.Submit(ctx, n, n+"@example.com", "", ""); err != nil {
|
||
t.Fatalf("submit %s: %v", n, err)
|
||
}
|
||
}
|
||
rows, err := svc.List(ctx, model.StatusPending)
|
||
if err != nil {
|
||
t.Fatalf("list: %v", err)
|
||
}
|
||
if len(rows) != 3 {
|
||
t.Fatalf("pending count = %d, want 3", len(rows))
|
||
}
|
||
// 最新在前
|
||
if rows[0].UsernameRequested != "a3" {
|
||
t.Fatalf("first = %s, want a3", rows[0].UsernameRequested)
|
||
}
|
||
if all, err := svc.List(ctx, ""); err != nil || len(all) != 3 {
|
||
t.Fatalf("list all = %d, %v", len(all), err)
|
||
}
|
||
}
|