package service // App→网页免登录一次性换票的回归测试:签票/兑票/单次即焚/过期/sso 不占配额。 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/testutil" ) func TestWebTicket_ExchangeOnce(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "SSO001") user := testutil.CreateTestUser(db, shop.ID, "admin", "password123", "admin") svc := NewAuthService(db) ticket, ttl, err := svc.IssueWebTicket(shop.ID, user.ID) require.NoError(t, err) assert.NotEmpty(t, ticket) assert.Equal(t, 60, ttl) dev := DeviceInfo{Platform: "web", IP: "1.2.3.4", UserAgent: "test"} pair, u, sh, err := svc.LoginWithWebTicket(ticket, dev) require.NoError(t, err) assert.NotEmpty(t, pair.AccessToken) assert.Equal(t, user.ID, u.ID) assert.Equal(t, "SSO001", sh.Code) // 兑出的 refresh token 可正常续期(会话有效) _, err = svc.RefreshTokens(pair.RefreshToken) assert.NoError(t, err) // 单次即焚:二次兑换必失败 _, _, _, err = svc.LoginWithWebTicket(ticket, dev) assert.ErrorIs(t, err, ErrTicketInvalid) } func TestWebTicket_Expired(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "SSO002") user := testutil.CreateTestUser(db, shop.ID, "admin", "password123", "admin") svc := NewAuthService(db) ticket, _, err := svc.IssueWebTicket(shop.ID, user.ID) require.NoError(t, err) // 手动把票据改成已过期 require.NoError(t, db.Model(&model.WebTicket{}). Where("ticket = ?", ticket). Update("expires_at", time.Now().Add(-time.Minute)).Error) _, _, _, err = svc.LoginWithWebTicket(ticket, DeviceInfo{Platform: "web"}) assert.ErrorIs(t, err, ErrTicketInvalid) } func TestWebTicket_SsoSessionNotCountedInQuota(t *testing.T) { db := testutil.SetupTestDB() old := config.C.Session.LimitTotal config.C.Session.LimitTotal = 2 defer func() { config.C.Session.LimitTotal = old }() shop := testutil.CreateTestShop(db, "SSO003") user := testutil.CreateTestUser(db, shop.ID, "admin", "password123", "admin") svc := NewAuthService(db) // 占满 2 个正常配额 _, _, err := svc.Login("SSO003", "admin", "password123", DeviceInfo{DeviceID: "d1", Platform: "macos"}) require.NoError(t, err) _, _, err = svc.Login("SSO003", "admin", "password123", DeviceInfo{DeviceID: "d2", Platform: "ios"}) require.NoError(t, err) // 配额已满仍可换票登录(sso 不受配额限制) ticket, _, err := svc.IssueWebTicket(shop.ID, user.ID) require.NoError(t, err) _, _, _, err = svc.LoginWithWebTicket(ticket, DeviceInfo{Platform: "web"}) require.NoError(t, err, "sso 会话不受设备配额限制") // sso 会话短时效 + 打标记 var sess model.UserSession require.NoError(t, db.Where("shop_id = ? AND kind = 'sso'", shop.ID). First(&sess).Error) assert.Equal(t, "web", sess.Platform) assert.WithinDuration(t, time.Now().Add(12*time.Hour), sess.RefreshExpAt, time.Minute) // 反向:sso 会话在场时,正常登录的配额统计不把它算进去 _, _, err = svc.Login("SSO003", "admin", "password123", DeviceInfo{DeviceID: "d1", Platform: "macos"}) // 同设备重登复用坑位 assert.NoError(t, err, "sso 会话不占配额,正常登录不受影响") }