Files
jiu/backend/internal/service/auth_test.go
T
wangjia 23dff69c62 feat(backend): 授权改为时长兑换券体系 + 退役 ed25519/HMAC + 平台生码工具
- 新增 license_codes 码池表 + model.LicenseCode;licenses 加 tier 档位列
- LicenseService.Redeem:单事务 FOR UPDATE 校验码未用 → 时长叠加(可叠加,0=永久)
  → 写 type/tier/max_devices → 绑设备(超限整笔回滚) → 标记已用 → 即时失效 phase 缓存
  路由仍 POST /license/activate,客户端零破坏
- util.GenerateRedeemCode/NormalizeCode:JIUKU-XXXX-XXXX 短码(crypto/rand)
- cmd/gencode:平台批量生成兑换码并落库;删除 cmd/issue、cmd/genkey
- 退役 ed25519 + HMAC:删 util/license_key、GenerateKey、License 全部 config 字段
  及生产启动私钥校验;trial 改直接建行(无需私钥、去 Fatal)
- tier 档位钩子默认 standard,分档消费模式后续设计
- 测试:Redeem 全场景(叠加/过期重置/永久/一码一次/无效/设备上限回滚)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:14:27 +08:00

168 lines
4.9 KiB
Go
Raw 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 (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/wangjia/jiu/backend/internal/model"
"github.com/wangjia/jiu/backend/testutil"
)
func TestAuthService_Login_Success(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "HOTEL001")
testutil.CreateTestUser(db, shop.ID, "admin", "password123", "admin")
svc := NewAuthService(db)
pair, user, err := svc.Login("HOTEL001", "admin", "password123", DeviceInfo{Platform: "windows"})
require.NoError(t, err)
require.NotNil(t, pair)
require.NotNil(t, user)
assert.NotEmpty(t, pair.AccessToken)
assert.NotEmpty(t, pair.RefreshToken)
assert.Equal(t, "admin", user.Username)
}
func TestAuthService_Login_AutoTrialOnFirstUse(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "TRIAL001")
testutil.CreateTestUser(db, shop.ID, "admin", "password123", "admin")
// 前置:门店「未激活」——无任何授权行
var before int64
db.Model(&model.License{}).Where("shop_id = ?", shop.ID).Count(&before)
require.EqualValues(t, 0, before)
svc := NewAuthService(db)
// 首次登录 → 自动签发 30 天 trial
_, _, err := svc.Login("TRIAL001", "admin", "password123", DeviceInfo{Platform: "windows"})
require.NoError(t, err)
var lic model.License
require.NoError(t, db.Where("shop_id = ? AND is_active = 1", shop.ID).First(&lic).Error)
assert.Equal(t, "trial", lic.Type)
assert.True(t, lic.IsActive)
require.NotNil(t, lic.ExpiresAt)
days := time.Until(*lic.ExpiresAt).Hours() / 24
assert.InDelta(t, 30, days, 1, "试用期应约为 30 天")
// 再次登录不重复发放
_, _, err = svc.Login("TRIAL001", "admin", "password123", DeviceInfo{Platform: "windows"})
require.NoError(t, err)
var count int64
db.Model(&model.License{}).Where("shop_id = ?", shop.ID).Count(&count)
assert.EqualValues(t, 1, count, "已有有效授权时不应再发放 trial")
}
func TestAuthService_Login_WrongPassword(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "HOTEL002")
testutil.CreateTestUser(db, shop.ID, "admin", "password123", "admin")
svc := NewAuthService(db)
pair, user, err := svc.Login("HOTEL002", "admin", "wrongpassword", DeviceInfo{Platform: "windows"})
assert.Error(t, err)
assert.Equal(t, ErrInvalidCredentials, err)
assert.Nil(t, pair)
assert.Nil(t, user)
}
func TestAuthService_Login_WrongHotel(t *testing.T) {
db := testutil.SetupTestDB()
testutil.CreateTestShop(db, "HOTEL003")
svc := NewAuthService(db)
pair, user, err := svc.Login("NONEXISTENT", "admin", "password123", DeviceInfo{Platform: "windows"})
assert.Error(t, err)
assert.Equal(t, ErrInvalidCredentials, err)
assert.Nil(t, pair)
assert.Nil(t, user)
}
func TestAuthService_Login_DisabledUser(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "HOTEL004")
user := testutil.CreateTestUser(db, shop.ID, "disabled", "password123", "operator")
// 禁用用户
db.Model(user).Update("is_active", false)
svc := NewAuthService(db)
pair, u, err := svc.Login("HOTEL004", "disabled", "password123", DeviceInfo{Platform: "windows"})
assert.Error(t, err)
assert.Equal(t, ErrUserInactive, err)
assert.Nil(t, pair)
assert.Nil(t, u)
}
func TestAuthService_Login_WrongUsername(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "HOTEL005")
testutil.CreateTestUser(db, shop.ID, "admin", "password123", "admin")
svc := NewAuthService(db)
pair, user, err := svc.Login("HOTEL005", "nonexistent", "password123", DeviceInfo{Platform: "windows"})
assert.Error(t, err)
assert.Equal(t, ErrInvalidCredentials, err)
assert.Nil(t, pair)
assert.Nil(t, user)
}
func TestAuthService_RefreshTokens(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "HOTEL006")
testutil.CreateTestUser(db, shop.ID, "admin", "password123", "admin")
svc := NewAuthService(db)
pair, _, err := svc.Login("HOTEL006", "admin", "password123", DeviceInfo{Platform: "windows"})
require.NoError(t, err)
require.NotNil(t, pair)
// 用 refresh token 换新 token
newPair, err := svc.RefreshTokens(pair.RefreshToken)
require.NoError(t, err)
require.NotNil(t, newPair)
assert.NotEmpty(t, newPair.AccessToken)
assert.NotEmpty(t, newPair.RefreshToken)
}
func TestAuthService_RefreshTokens_Invalid(t *testing.T) {
db := testutil.SetupTestDB()
testutil.InitConfig()
svc := NewAuthService(db)
newPair, err := svc.RefreshTokens("invalid.token.here")
assert.Error(t, err)
assert.Nil(t, newPair)
}
func TestHashPassword(t *testing.T) {
hash, err := HashPassword("mypassword")
require.NoError(t, err)
assert.NotEmpty(t, hash)
assert.NotEqual(t, "mypassword", hash)
// 第二次哈希应不同(bcrypt 加 salt
hash2, err := HashPassword("mypassword")
require.NoError(t, err)
assert.NotEqual(t, hash, hash2)
}
func TestInitConfig(t *testing.T) {
testutil.InitConfig()
}
func init() {
testutil.InitConfig()
}