- Add new test files for auth, service, and handler modules - Improve test organization and coverage - Refactor code for better maintainability - Add captcha, settings, stats, and theme handler tests - Add auth module tests (CAS, OAuth, password, SSO, state) - Add service layer tests for auth, export, permissions, roles - All Go tests pass (exit code 0) - All frontend tests pass (325 tests in 59 files)
302 lines
7.4 KiB
Go
302 lines
7.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// =============================================================================
|
|
// SMS Provider Tests
|
|
// =============================================================================
|
|
|
|
// mockCacheForSMS implements cacheInterface for testing
|
|
type mockCacheForSMS struct{}
|
|
|
|
func (m *mockCacheForSMS) Get(ctx context.Context, key string) (interface{}, bool) {
|
|
return nil, false
|
|
}
|
|
|
|
func (m *mockCacheForSMS) Set(ctx context.Context, key string, value interface{}, l1TTL, l2TTL time.Duration) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCacheForSMS) Delete(ctx context.Context, key string) error {
|
|
return nil
|
|
}
|
|
|
|
func TestNewAliyunSMSProvider(t *testing.T) {
|
|
t.Run("incomplete config returns error", func(t *testing.T) {
|
|
cfg := AliyunSMSConfig{}
|
|
_, err := NewAliyunSMSProvider(cfg)
|
|
if err == nil {
|
|
t.Error("Expected error for incomplete config")
|
|
}
|
|
})
|
|
|
|
t.Run("missing access key", func(t *testing.T) {
|
|
cfg := AliyunSMSConfig{
|
|
AccessKeySecret: "secret",
|
|
SignName: "sign",
|
|
TemplateCode: "template",
|
|
}
|
|
_, err := NewAliyunSMSProvider(cfg)
|
|
if err == nil {
|
|
t.Error("Expected error for missing access key")
|
|
}
|
|
})
|
|
|
|
t.Run("missing sign name", func(t *testing.T) {
|
|
cfg := AliyunSMSConfig{
|
|
AccessKeyID: "key",
|
|
AccessKeySecret: "secret",
|
|
TemplateCode: "template",
|
|
}
|
|
_, err := NewAliyunSMSProvider(cfg)
|
|
if err == nil {
|
|
t.Error("Expected error for missing sign name")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNewTencentSMSProvider(t *testing.T) {
|
|
t.Run("incomplete config returns error", func(t *testing.T) {
|
|
cfg := TencentSMSConfig{}
|
|
_, err := NewTencentSMSProvider(cfg)
|
|
if err == nil {
|
|
t.Error("Expected error for incomplete config")
|
|
}
|
|
})
|
|
|
|
t.Run("missing secret ID", func(t *testing.T) {
|
|
cfg := TencentSMSConfig{
|
|
SecretKey: "key",
|
|
AppID: "app",
|
|
SignName: "sign",
|
|
TemplateID: "template",
|
|
}
|
|
_, err := NewTencentSMSProvider(cfg)
|
|
if err == nil {
|
|
t.Error("Expected error for missing secret ID")
|
|
}
|
|
})
|
|
|
|
t.Run("missing app ID", func(t *testing.T) {
|
|
cfg := TencentSMSConfig{
|
|
SecretID: "id",
|
|
SecretKey: "key",
|
|
SignName: "sign",
|
|
TemplateID: "template",
|
|
}
|
|
_, err := NewTencentSMSProvider(cfg)
|
|
if err == nil {
|
|
t.Error("Expected error for missing app ID")
|
|
}
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// SMS Code Service Tests
|
|
// =============================================================================
|
|
|
|
type mockSMSProvider struct {
|
|
sendErr error
|
|
}
|
|
|
|
func (m *mockSMSProvider) SendVerificationCode(ctx context.Context, phone, code string) error {
|
|
return m.sendErr
|
|
}
|
|
|
|
func TestNewSMSCodeService(t *testing.T) {
|
|
provider := &mockSMSProvider{}
|
|
cache := &mockCacheForSMS{}
|
|
|
|
t.Run("with default config", func(t *testing.T) {
|
|
cfg := SMSCodeConfig{}
|
|
svc := NewSMSCodeService(provider, cache, cfg)
|
|
if svc == nil {
|
|
t.Error("Expected non-nil service")
|
|
}
|
|
if svc.cfg.CodeTTL <= 0 {
|
|
t.Error("Expected default CodeTTL to be set")
|
|
}
|
|
})
|
|
|
|
t.Run("with custom config", func(t *testing.T) {
|
|
cfg := SMSCodeConfig{
|
|
CodeTTL: 10 * time.Minute,
|
|
ResendCooldown: 2 * time.Minute,
|
|
MaxDailyLimit: 20,
|
|
}
|
|
svc := NewSMSCodeService(provider, cache, cfg)
|
|
if svc == nil {
|
|
t.Error("Expected non-nil service")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSMSCodeService_DefaultConfig(t *testing.T) {
|
|
cfg := DefaultSMSCodeConfig()
|
|
if cfg.CodeTTL <= 0 {
|
|
t.Error("Expected positive CodeTTL")
|
|
}
|
|
if cfg.ResendCooldown <= 0 {
|
|
t.Error("Expected positive ResendCooldown")
|
|
}
|
|
if cfg.MaxDailyLimit <= 0 {
|
|
t.Error("Expected positive MaxDailyLimit")
|
|
}
|
|
}
|
|
|
|
// mockCacheWithGet implements cacheInterface with controllable Get behavior
|
|
type mockCacheWithGet struct {
|
|
getResult interface{}
|
|
getFound bool
|
|
setErr error
|
|
deleteErr error
|
|
lastSetKey string
|
|
}
|
|
|
|
func (m *mockCacheWithGet) Get(ctx context.Context, key string) (interface{}, bool) {
|
|
return m.getResult, m.getFound
|
|
}
|
|
|
|
func (m *mockCacheWithGet) Set(ctx context.Context, key string, value interface{}, l1TTL, l2TTL time.Duration) error {
|
|
m.lastSetKey = key
|
|
return m.setErr
|
|
}
|
|
|
|
func (m *mockCacheWithGet) Delete(ctx context.Context, key string) error {
|
|
return m.deleteErr
|
|
}
|
|
|
|
func TestSMSCodeService_SendCode(t *testing.T) {
|
|
provider := &mockSMSProvider{}
|
|
cache := &mockCacheWithGet{}
|
|
svc := NewSMSCodeService(provider, cache, SMSCodeConfig{
|
|
CodeTTL: 5 * time.Minute,
|
|
ResendCooldown: time.Minute,
|
|
MaxDailyLimit: 10,
|
|
})
|
|
ctx := context.Background()
|
|
|
|
t.Run("nil service returns error", func(t *testing.T) {
|
|
var nilSvc *SMSCodeService
|
|
_, err := nilSvc.SendCode(ctx, &SendCodeRequest{Phone: "13800138000"})
|
|
if err == nil {
|
|
t.Error("Expected error for nil service")
|
|
}
|
|
})
|
|
|
|
t.Run("nil request returns error", func(t *testing.T) {
|
|
_, err := svc.SendCode(ctx, nil)
|
|
if err == nil {
|
|
t.Error("Expected error for nil request")
|
|
}
|
|
})
|
|
|
|
t.Run("invalid phone returns error", func(t *testing.T) {
|
|
_, err := svc.SendCode(ctx, &SendCodeRequest{Phone: "invalid"})
|
|
if err == nil {
|
|
t.Error("Expected error for invalid phone")
|
|
}
|
|
})
|
|
|
|
t.Run("cooldown active returns error", func(t *testing.T) {
|
|
cache.getResult = true
|
|
cache.getFound = true
|
|
_, err := svc.SendCode(ctx, &SendCodeRequest{Phone: "13800138000"})
|
|
if err == nil {
|
|
t.Error("Expected error when cooldown is active")
|
|
}
|
|
cache.getFound = false
|
|
})
|
|
|
|
t.Run("successful send", func(t *testing.T) {
|
|
cache.getResult = nil
|
|
cache.getFound = false
|
|
resp, err := svc.SendCode(ctx, &SendCodeRequest{Phone: "13800138000", Purpose: "login"})
|
|
if err != nil {
|
|
t.Fatalf("SendCode failed: %v", err)
|
|
}
|
|
if resp == nil {
|
|
t.Error("Expected non-nil response")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSMSCodeService_VerifyCode(t *testing.T) {
|
|
provider := &mockSMSProvider{}
|
|
cache := &mockCacheWithGet{}
|
|
svc := NewSMSCodeService(provider, cache, SMSCodeConfig{
|
|
CodeTTL: 5 * time.Minute,
|
|
ResendCooldown: time.Minute,
|
|
MaxDailyLimit: 10,
|
|
})
|
|
ctx := context.Background()
|
|
|
|
t.Run("nil service returns error", func(t *testing.T) {
|
|
var nilSvc *SMSCodeService
|
|
err := nilSvc.VerifyCode(ctx, "13800138000", "login", "123456")
|
|
if err == nil {
|
|
t.Error("Expected error for nil service")
|
|
}
|
|
})
|
|
|
|
t.Run("empty code returns error", func(t *testing.T) {
|
|
err := svc.VerifyCode(ctx, "13800138000", "login", "")
|
|
if err == nil {
|
|
t.Error("Expected error for empty code")
|
|
}
|
|
})
|
|
|
|
t.Run("code not found returns error", func(t *testing.T) {
|
|
cache.getResult = nil
|
|
cache.getFound = false
|
|
err := svc.VerifyCode(ctx, "13800138000", "login", "123456")
|
|
if err == nil {
|
|
t.Error("Expected error when code not found")
|
|
}
|
|
})
|
|
|
|
t.Run("wrong code returns error", func(t *testing.T) {
|
|
cache.getResult = "654321"
|
|
cache.getFound = true
|
|
err := svc.VerifyCode(ctx, "13800138000", "login", "123456")
|
|
if err == nil {
|
|
t.Error("Expected error for wrong code")
|
|
}
|
|
})
|
|
|
|
t.Run("correct code succeeds", func(t *testing.T) {
|
|
cache.getResult = "123456"
|
|
cache.getFound = true
|
|
err := svc.VerifyCode(ctx, "13800138000", "login", "123456")
|
|
if err != nil {
|
|
t.Fatalf("VerifyCode failed: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestIsValidPhone(t *testing.T) {
|
|
tests := []struct {
|
|
phone string
|
|
expected bool
|
|
}{
|
|
{"13800138000", true},
|
|
{"15912345678", true},
|
|
{"18612345678", true},
|
|
{"12345678901", false},
|
|
{"1380013800", false},
|
|
{"", false},
|
|
{"invalid", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := isValidPhone(tt.phone)
|
|
if result != tt.expected {
|
|
t.Errorf("isValidPhone(%q) = %v, want %v", tt.phone, result, tt.expected)
|
|
}
|
|
}
|
|
}
|