feat(backend): 首次登录无有效授权时自动发放 30 天试用

未激活门店(seed/手动建店/老数据,无任何 license 行)此前被当作永久授权,
既不拦截也无提示。现在登录时若门店无有效授权,自动签发 30 天 trial,
之后照常走 grace/readonly/locked 降级链。须在 issueTokens 之前发放,
使 JWT 的 lic_exp 带上新到期日。幂等(已有有效授权不重发),签发失败
仅记日志不阻断登录。复用注册路径的发放逻辑(抽出 issueTrialLicense)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2Wdwo7SmgBJU37cBrkhPK
This commit is contained in:
wangjia
2026-06-18 07:59:44 +08:00
parent 2ac1cbfb24
commit 64a64e7c0a
3 changed files with 81 additions and 7 deletions
+40
View File
@@ -2,10 +2,14 @@ package service
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/wangjia/jiu/backend/config"
"github.com/wangjia/jiu/backend/internal/model"
"github.com/wangjia/jiu/backend/internal/util"
"github.com/wangjia/jiu/backend/testutil"
)
@@ -25,6 +29,42 @@ func TestAuthService_Login_Success(t *testing.T) {
assert.Equal(t, "admin", user.Username)
}
func TestAuthService_Login_AutoTrialOnFirstUse(t *testing.T) {
db := testutil.SetupTestDB()
priv, _, err := util.GenerateEd25519KeyPair()
require.NoError(t, err)
config.C.License.Ed25519PrivateKey = priv
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")