Files

165 lines
4.9 KiB
Go
Raw Permalink 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 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)
}
// 默认 TTL90 天)生效
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)
}
}