Files
user-system/internal/service/sms_provider_test.go
long-agent 582ad7a069 test: add comprehensive test coverage and improve code quality
- 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)
2026-04-17 20:43:50 +08:00

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)
}
}
}