refactor(auth): delegate access control to be-system
Remove Agent Desk users, roles, login sessions, tokens, and local permission persistence. Expose the backend as an embeddable ai-agent module with host-provided subject lookup and operation authorization callbacks, and complete the frontend/backend repository split.
This commit is contained in:
@@ -1,488 +1,68 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/pkg/config"
|
||||
"agent-desk/internal/pkg/dto/request"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"code.tczkiot.com/wlw/ai-agent/identity"
|
||||
"code.tczkiot.com/wlw/ai-agent/internal/pkg/constants"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"github.com/mlogclub/simple/web"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestExtractBearerToken(t *testing.T) {
|
||||
svc := newAuthService()
|
||||
|
||||
if got := svc.extractBearerToken("Bearer token_123"); got != "token_123" {
|
||||
t.Fatalf("expected bearer token to be extracted, got %q", got)
|
||||
}
|
||||
|
||||
if got := svc.extractBearerToken("token_123"); got != "" {
|
||||
t.Fatalf("expected raw token to be rejected by bearer extractor, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthServiceLoginCreatesSingleAccessSession(t *testing.T) {
|
||||
db := setupAuthServiceTestDB(t)
|
||||
user := createAuthTestUser(t, db, "admin", "secret")
|
||||
svc := newAuthService()
|
||||
|
||||
ret, err := svc.Login(request.LoginRequest{
|
||||
Username: " admin ",
|
||||
Password: "secret",
|
||||
}, config.AuthConfig{TokenTTLHours: 2, MaxFailedAttempts: 5, CredentialLockMinute: 15}, "127.0.0.1", "go-test")
|
||||
if err != nil {
|
||||
t.Fatalf("login failed: %v", err)
|
||||
}
|
||||
|
||||
if ret.AccessToken == "" || !strings.HasPrefix(ret.AccessToken, "ak_") {
|
||||
t.Fatalf("expected ak_ access token, got %q", ret.AccessToken)
|
||||
}
|
||||
if ret.ExpiresAt == "" {
|
||||
t.Fatal("expected expiresAt to be returned")
|
||||
}
|
||||
|
||||
var sessions []models.LoginSession
|
||||
if err := db.Find(&sessions).Error; err != nil {
|
||||
t.Fatalf("query login sessions: %v", err)
|
||||
}
|
||||
if len(sessions) != 1 {
|
||||
t.Fatalf("expected exactly one session, got %d", len(sessions))
|
||||
}
|
||||
if sessions[0].Token != ret.AccessToken {
|
||||
t.Fatalf("expected session token %q, got %q", ret.AccessToken, sessions[0].Token)
|
||||
}
|
||||
if sessions[0].UserID != user.ID {
|
||||
t.Fatalf("expected session user %d, got %d", user.ID, sessions[0].UserID)
|
||||
}
|
||||
if sessions[0].ClientType != "admin_web" {
|
||||
t.Fatalf("expected admin_web client type, got %q", sessions[0].ClientType)
|
||||
}
|
||||
|
||||
logs := findCredentialLogs(t, db)
|
||||
if len(logs) != 1 {
|
||||
t.Fatalf("expected one credential log, got %d", len(logs))
|
||||
}
|
||||
if !logs[0].Success || logs[0].Principal != "admin" || logs[0].UserID != user.ID {
|
||||
t.Fatalf("unexpected success credential log: %+v", logs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthServiceLoginFailureWritesCredentialLogs(t *testing.T) {
|
||||
db := setupAuthServiceTestDB(t)
|
||||
createAuthTestUser(t, db, "admin", "secret")
|
||||
svc := newAuthService()
|
||||
authCfg := config.AuthConfig{TokenTTLHours: 2, MaxFailedAttempts: 5, CredentialLockMinute: 15}
|
||||
|
||||
if _, err := svc.Login(request.LoginRequest{Username: "missing", Password: "secret"}, authCfg, "127.0.0.1", "go-test"); !hasCode(err, errorsx.CodeAuthInvalidAccount) {
|
||||
t.Fatalf("expected invalid account for missing user, got %v", err)
|
||||
}
|
||||
if _, err := svc.Login(request.LoginRequest{Username: "admin", Password: "wrong"}, authCfg, "127.0.0.1", "go-test"); !hasCode(err, errorsx.CodeAuthInvalidAccount) {
|
||||
t.Fatalf("expected invalid account for password mismatch, got %v", err)
|
||||
}
|
||||
|
||||
logs := findCredentialLogs(t, db)
|
||||
if len(logs) != 2 {
|
||||
t.Fatalf("expected two credential logs, got %d", len(logs))
|
||||
}
|
||||
if logs[0].Reason != "user not found" || logs[0].Success {
|
||||
t.Fatalf("unexpected missing-user log: %+v", logs[0])
|
||||
}
|
||||
if logs[1].Reason != "password mismatch" || logs[1].Success {
|
||||
t.Fatalf("unexpected password-mismatch log: %+v", logs[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthServiceLoginCredentialLockout(t *testing.T) {
|
||||
db := setupAuthServiceTestDB(t)
|
||||
user := createAuthTestUser(t, db, "admin", "secret")
|
||||
now := time.Now()
|
||||
for i := 0; i < 2; i++ {
|
||||
if err := db.Create(&models.LoginCredentialLog{
|
||||
Principal: "admin",
|
||||
UserID: user.ID,
|
||||
Success: false,
|
||||
Reason: "password mismatch",
|
||||
CreatedAt: now.Add(-time.Duration(i+1) * time.Minute),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed credential log: %v", err)
|
||||
func TestExternalAuthDelegatesOperationToHost(t *testing.T) {
|
||||
SetQuerySubjects(func(_ context.Context, query identity.Query) ([]identity.Subject, error) {
|
||||
if !query.Current {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
if err := db.Create(&models.LoginCredentialLog{
|
||||
Principal: "admin",
|
||||
UserID: user.ID,
|
||||
Success: false,
|
||||
Reason: "password mismatch",
|
||||
CreatedAt: now.Add(-30 * time.Minute),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed old credential log: %v", err)
|
||||
}
|
||||
|
||||
svc := newAuthService()
|
||||
_, err := svc.Login(request.LoginRequest{Username: "admin", Password: "secret"}, config.AuthConfig{
|
||||
TokenTTLHours: 2,
|
||||
MaxFailedAttempts: 2,
|
||||
CredentialLockMinute: 15,
|
||||
}, "127.0.0.1", "go-test")
|
||||
if !hasCode(err, errorsx.CodeAuthCredentialLocked) {
|
||||
t.Fatalf("expected credential locked error, got %v", err)
|
||||
}
|
||||
|
||||
var lockedLog models.LoginCredentialLog
|
||||
if err := db.Order("id DESC").Take(&lockedLog).Error; err != nil {
|
||||
t.Fatalf("query latest credential log: %v", err)
|
||||
}
|
||||
if lockedLog.Reason != "credential locked" || lockedLog.Success {
|
||||
t.Fatalf("unexpected locked credential log: %+v", lockedLog)
|
||||
}
|
||||
|
||||
var sessionCount int64
|
||||
if err := db.Model(&models.LoginSession{}).Count(&sessionCount).Error; err != nil {
|
||||
t.Fatalf("count sessions: %v", err)
|
||||
}
|
||||
if sessionCount != 0 {
|
||||
t.Fatalf("expected no session while credential locked, got %d", sessionCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthServiceCredentialLockoutDoesNotExtendWhileLocked(t *testing.T) {
|
||||
db := setupAuthServiceTestDB(t)
|
||||
createAuthTestUser(t, db, "admin", "secret")
|
||||
now := time.Now()
|
||||
entries := []models.LoginCredentialLog{
|
||||
{
|
||||
Principal: "admin",
|
||||
UserID: 1,
|
||||
Success: false,
|
||||
Reason: "password mismatch",
|
||||
CreatedAt: now.Add(-2 * time.Minute),
|
||||
},
|
||||
{
|
||||
Principal: "admin",
|
||||
UserID: 0,
|
||||
Success: false,
|
||||
Reason: "credential locked",
|
||||
CreatedAt: now.Add(-1 * time.Minute),
|
||||
},
|
||||
}
|
||||
if err := db.Create(&entries).Error; err != nil {
|
||||
t.Fatalf("seed credential logs: %v", err)
|
||||
}
|
||||
|
||||
ret, err := newAuthService().Login(request.LoginRequest{Username: "admin", Password: "secret"}, config.AuthConfig{
|
||||
TokenTTLHours: 2,
|
||||
MaxFailedAttempts: 2,
|
||||
CredentialLockMinute: 15,
|
||||
}, "127.0.0.1", "go-test")
|
||||
if err != nil {
|
||||
t.Fatalf("expected locked attempt logs not to extend lockout, got %v", err)
|
||||
}
|
||||
if ret == nil || !strings.HasPrefix(ret.AccessToken, "ak_") {
|
||||
t.Fatalf("expected login response with ak_ token, got %+v", ret)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthServiceCredentialLockoutNormalizesPrincipalCase(t *testing.T) {
|
||||
db := setupAuthServiceTestDB(t)
|
||||
createAuthTestUser(t, db, "admin", "secret")
|
||||
if err := db.Create(&models.LoginCredentialLog{
|
||||
Principal: "admin",
|
||||
UserID: 1,
|
||||
Success: false,
|
||||
Reason: "password mismatch",
|
||||
CreatedAt: time.Now().Add(-time.Minute),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed credential log: %v", err)
|
||||
}
|
||||
|
||||
_, err := newAuthService().Login(request.LoginRequest{Username: "ADMIN", Password: "secret"}, config.AuthConfig{
|
||||
TokenTTLHours: 2,
|
||||
MaxFailedAttempts: 1,
|
||||
CredentialLockMinute: 15,
|
||||
}, "127.0.0.1", "go-test")
|
||||
if !hasCode(err, errorsx.CodeAuthCredentialLocked) {
|
||||
t.Fatalf("expected normalized principal to be locked, got %v", err)
|
||||
}
|
||||
|
||||
var lockedLog models.LoginCredentialLog
|
||||
if err := db.Order("id DESC").Take(&lockedLog).Error; err != nil {
|
||||
t.Fatalf("query latest credential log: %v", err)
|
||||
}
|
||||
if lockedLog.Principal != "admin" || lockedLog.Reason != "credential locked" {
|
||||
t.Fatalf("unexpected locked log: %+v", lockedLog)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthServiceCredentialLockoutDisabledWhenMaxAttemptsNonPositive(t *testing.T) {
|
||||
db := setupAuthServiceTestDB(t)
|
||||
user := createAuthTestUser(t, db, "admin", "secret")
|
||||
now := time.Now()
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := db.Create(&models.LoginCredentialLog{
|
||||
Principal: "admin",
|
||||
UserID: user.ID,
|
||||
Success: false,
|
||||
Reason: "credential locked",
|
||||
CreatedAt: now.Add(-time.Duration(i+1) * time.Minute),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed credential log: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ret, err := newAuthService().Login(request.LoginRequest{Username: "admin", Password: "secret"}, config.AuthConfig{
|
||||
TokenTTLHours: 2,
|
||||
MaxFailedAttempts: 0,
|
||||
CredentialLockMinute: 15,
|
||||
}, "127.0.0.1", "go-test")
|
||||
if err != nil {
|
||||
t.Fatalf("expected lockout to be disabled, got %v", err)
|
||||
}
|
||||
if ret == nil || ret.AccessToken == "" {
|
||||
t.Fatalf("expected login response with access token, got %+v", ret)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSessionTokenStates(t *testing.T) {
|
||||
db := setupAuthServiceTestDB(t)
|
||||
svc := newAuthService()
|
||||
now := time.Now()
|
||||
|
||||
if _, err := svc.validateSessionToken(" "); !hasCode(err, errorsx.CodeAuthUnauthorized) {
|
||||
t.Fatalf("expected unauthorized for empty token, got %v", err)
|
||||
}
|
||||
if _, err := svc.validateSessionToken("missing"); !hasCode(err, errorsx.CodeAuthInvalidToken) {
|
||||
t.Fatalf("expected invalid token for missing session, got %v", err)
|
||||
}
|
||||
|
||||
revokedAt := now
|
||||
if err := db.Create(&models.LoginSession{
|
||||
UserID: 1,
|
||||
Token: "ak_revoked",
|
||||
ClientType: "admin_web",
|
||||
ExpiredAt: now.Add(time.Hour),
|
||||
RevokedAt: &revokedAt,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed revoked session: %v", err)
|
||||
}
|
||||
if _, err := svc.validateSessionToken("ak_revoked"); !hasCode(err, errorsx.CodeAuthInvalidToken) {
|
||||
t.Fatalf("expected invalid token for revoked session, got %v", err)
|
||||
}
|
||||
|
||||
if err := db.Create(&models.LoginSession{
|
||||
UserID: 1,
|
||||
Token: "ak_expired",
|
||||
ClientType: "admin_web",
|
||||
ExpiredAt: now.Add(-time.Hour),
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed expired session: %v", err)
|
||||
}
|
||||
if _, err := svc.validateSessionToken("ak_expired"); !hasCode(err, errorsx.CodeAuthInvalidToken) {
|
||||
t.Fatalf("expected invalid token for expired session, got %v", err)
|
||||
}
|
||||
|
||||
if err := db.Create(&models.LoginSession{
|
||||
UserID: 1,
|
||||
Token: "ak_valid",
|
||||
ClientType: "admin_web",
|
||||
ExpiredAt: now.Add(time.Hour),
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed valid session: %v", err)
|
||||
}
|
||||
session, err := svc.validateSessionToken("ak_valid")
|
||||
if err != nil {
|
||||
t.Fatalf("expected valid session token, got %v", err)
|
||||
}
|
||||
if session.Token != "ak_valid" {
|
||||
t.Fatalf("expected valid session token ak_valid, got %q", session.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthServiceLogoutRevokesCurrentTokenOnly(t *testing.T) {
|
||||
db := setupAuthServiceTestDB(t)
|
||||
now := time.Now()
|
||||
sessions := []models.LoginSession{
|
||||
{
|
||||
UserID: 1,
|
||||
Token: "ak_current",
|
||||
ClientType: "admin_web",
|
||||
ExpiredAt: now.Add(time.Hour),
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
},
|
||||
{
|
||||
UserID: 1,
|
||||
Token: "ak_other",
|
||||
ClientType: "admin_web",
|
||||
ExpiredAt: now.Add(time.Hour),
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := db.Create(&sessions).Error; err != nil {
|
||||
t.Fatalf("seed sessions: %v", err)
|
||||
}
|
||||
|
||||
if err := newAuthService().Logout("Bearer ak_current"); err != nil {
|
||||
t.Fatalf("logout failed: %v", err)
|
||||
}
|
||||
|
||||
var current models.LoginSession
|
||||
if err := db.Take(¤t, "token = ?", "ak_current").Error; err != nil {
|
||||
t.Fatalf("query current session: %v", err)
|
||||
}
|
||||
if current.RevokedAt == nil {
|
||||
t.Fatal("expected current session to be revoked")
|
||||
}
|
||||
var other models.LoginSession
|
||||
if err := db.Take(&other, "token = ?", "ak_other").Error; err != nil {
|
||||
t.Fatalf("query other session: %v", err)
|
||||
}
|
||||
if other.RevokedAt != nil {
|
||||
t.Fatal("expected other session to remain active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadUserPermissionCodesReturnsSortedDistinctCodes(t *testing.T) {
|
||||
db := setupAuthServiceTestDB(t)
|
||||
user := createAuthTestUser(t, db, "admin", "secret")
|
||||
now := time.Now()
|
||||
auditFields := models.AuditFields{CreatedAt: now, UpdatedAt: now}
|
||||
|
||||
permissions := []models.Permission{
|
||||
{Name: "Zulu", Code: "z.read", SortNo: 1, Status: enums.StatusOk, AuditFields: auditFields},
|
||||
{Name: "Alpha", Code: "a.read", SortNo: 2, Status: enums.StatusOk, AuditFields: auditFields},
|
||||
}
|
||||
if err := db.Create(&permissions).Error; err != nil {
|
||||
t.Fatalf("seed permissions: %v", err)
|
||||
}
|
||||
|
||||
roles := []models.Role{
|
||||
{Name: "Role One", Code: "role_one", Status: enums.StatusOk, AuditFields: auditFields},
|
||||
{Name: "Role Two", Code: "role_two", Status: enums.StatusOk, AuditFields: auditFields},
|
||||
}
|
||||
if err := db.Create(&roles).Error; err != nil {
|
||||
t.Fatalf("seed roles: %v", err)
|
||||
}
|
||||
|
||||
userRoles := []models.UserRole{
|
||||
{UserID: user.ID, RoleID: roles[0].ID, AuditFields: auditFields},
|
||||
{UserID: user.ID, RoleID: roles[1].ID, AuditFields: auditFields},
|
||||
}
|
||||
if err := db.Create(&userRoles).Error; err != nil {
|
||||
t.Fatalf("seed user roles: %v", err)
|
||||
}
|
||||
|
||||
rolePermissions := []models.RolePermission{
|
||||
{RoleID: roles[0].ID, PermissionID: permissions[0].ID, AuditFields: auditFields},
|
||||
{RoleID: roles[0].ID, PermissionID: permissions[1].ID, AuditFields: auditFields},
|
||||
{RoleID: roles[1].ID, PermissionID: permissions[0].ID, AuditFields: auditFields},
|
||||
}
|
||||
if err := db.Create(&rolePermissions).Error; err != nil {
|
||||
t.Fatalf("seed role permissions: %v", err)
|
||||
}
|
||||
|
||||
codes, err := newAuthService().loadUserPermissionCodes(db, user.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("load user permission codes: %v", err)
|
||||
}
|
||||
if got, want := strings.Join(codes, ","), "a.read,z.read"; got != want {
|
||||
t.Fatalf("permission codes = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func setupAuthServiceTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: "t_",
|
||||
SingularTable: true,
|
||||
},
|
||||
return []identity.Subject{{
|
||||
Type: identity.SubjectAdmin,
|
||||
Category: identity.CategorySystem,
|
||||
ID: 9,
|
||||
Username: "admin",
|
||||
Name: "Admin",
|
||||
Enabled: true,
|
||||
}}, nil
|
||||
})
|
||||
|
||||
var gotOperation string
|
||||
SetAuthorize(func(_ context.Context, operation string) error {
|
||||
gotOperation = operation
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
ctx.Request = httptest.NewRequest("GET", "/api/dashboard/conversation/list", nil)
|
||||
principal, err := AuthService.RequirePermission(ctx, constants.PermissionConversationView)
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite db: %v", err)
|
||||
t.Fatalf("RequirePermission() error = %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.UserIdentity{},
|
||||
&models.Role{},
|
||||
&models.Permission{},
|
||||
&models.UserRole{},
|
||||
&models.RolePermission{},
|
||||
&models.UserPermission{},
|
||||
&models.LoginSession{},
|
||||
&models.LoginCredentialLog{},
|
||||
); err != nil {
|
||||
t.Fatalf("migrate auth tables: %v", err)
|
||||
if principal.UserID != 9 || principal.SubjectType != identity.SubjectAdmin {
|
||||
t.Fatalf("principal = %#v", principal)
|
||||
}
|
||||
if gotOperation != constants.PermissionConversationView.Code {
|
||||
t.Fatalf("operation = %q, want %q", gotOperation, constants.PermissionConversationView.Code)
|
||||
}
|
||||
sqls.SetDB(db)
|
||||
return db
|
||||
}
|
||||
|
||||
func createAuthTestUser(t *testing.T, db *gorm.DB, username, password string) *models.User {
|
||||
t.Helper()
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
t.Fatalf("hash password: %v", err)
|
||||
}
|
||||
now := time.Now()
|
||||
user := &models.User{
|
||||
Username: username,
|
||||
Nickname: username,
|
||||
Password: string(passwordHash),
|
||||
Status: enums.StatusOk,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create auth test user: %v", err)
|
||||
}
|
||||
return user
|
||||
}
|
||||
func TestExternalAuthRejectsHostDeniedOperation(t *testing.T) {
|
||||
SetQuerySubjects(func(_ context.Context, query identity.Query) ([]identity.Subject, error) {
|
||||
if !query.Current {
|
||||
return nil, nil
|
||||
}
|
||||
return []identity.Subject{{
|
||||
Type: identity.SubjectAgent, Category: identity.CategorySystem, ID: 10, Enabled: true,
|
||||
}}, nil
|
||||
})
|
||||
SetAuthorize(func(_ context.Context, _ string) error {
|
||||
return errors.New("denied by host")
|
||||
})
|
||||
|
||||
func findCredentialLogs(t *testing.T, db *gorm.DB) []models.LoginCredentialLog {
|
||||
t.Helper()
|
||||
var logs []models.LoginCredentialLog
|
||||
if err := db.Order("id ASC").Find(&logs).Error; err != nil {
|
||||
t.Fatalf("query credential logs: %v", err)
|
||||
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
ctx.Request = httptest.NewRequest("POST", "/api/dashboard/ai-config/delete", nil)
|
||||
if _, err := AuthService.RequirePermission(ctx, constants.PermissionAIConfigDelete); err == nil {
|
||||
t.Fatal("RequirePermission() error = nil, want forbidden")
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
func hasCode(err error, code int) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var codeErr *web.CodeError
|
||||
if errors.As(err, &codeErr) {
|
||||
return codeErr.Code == code
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user