veylant/internal/router/rbac_test.go
2026-02-23 13:35:04 +01:00

149 lines
5.9 KiB
Go

package router_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/veylant/ia-gateway/internal/config"
"github.com/veylant/ia-gateway/internal/router"
)
// defaultRBACConfig is a realistic config used across most tests.
var defaultRBACConfig = &config.RBACConfig{
UserAllowedModels: []string{"gpt-4o-mini", "gpt-3.5-turbo", "mistral-small"},
AuditorCanComplete: false,
}
// ─── Admin ───────────────────────────────────────────────────────────────────
func TestHasAccess_Admin_AllModelsAllowed(t *testing.T) {
for _, model := range []string{"gpt-4o", "claude-3-opus", "mistral-medium", "llama3"} {
err := router.HasAccess([]string{"admin"}, model, defaultRBACConfig)
assert.NoError(t, err, "admin should access %q", model)
}
}
func TestHasAccess_AdminCaseInsensitive(t *testing.T) {
err := router.HasAccess([]string{"ADMIN"}, "claude-3-opus", defaultRBACConfig)
require.NoError(t, err)
}
// ─── Manager ─────────────────────────────────────────────────────────────────
func TestHasAccess_Manager_AllModelsAllowed(t *testing.T) {
for _, model := range []string{"gpt-4o", "claude-3-opus", "mistral-medium"} {
err := router.HasAccess([]string{"manager"}, model, defaultRBACConfig)
assert.NoError(t, err, "manager should access %q", model)
}
}
func TestHasAccess_ManagerCaseInsensitive(t *testing.T) {
err := router.HasAccess([]string{"Manager"}, "gpt-4o", defaultRBACConfig)
require.NoError(t, err)
}
// ─── Auditor ─────────────────────────────────────────────────────────────────
func TestHasAccess_Auditor_Blocked_WhenCanCompleteIsFalse(t *testing.T) {
err := router.HasAccess([]string{"auditor"}, "gpt-4o-mini", defaultRBACConfig)
require.Error(t, err)
assert.Contains(t, err.Error(), "auditor")
}
func TestHasAccess_Auditor_Allowed_WhenCanCompleteIsTrue(t *testing.T) {
cfg := &config.RBACConfig{
UserAllowedModels: defaultRBACConfig.UserAllowedModels,
AuditorCanComplete: true,
}
err := router.HasAccess([]string{"auditor"}, "gpt-4o-mini", cfg)
require.NoError(t, err)
}
func TestHasAccess_AuditorCaseInsensitive(t *testing.T) {
err := router.HasAccess([]string{"AUDITOR"}, "gpt-4o-mini", defaultRBACConfig)
require.Error(t, err)
}
// ─── User ─────────────────────────────────────────────────────────────────────
func TestHasAccess_User_AllowedModel_ExactMatch(t *testing.T) {
err := router.HasAccess([]string{"user"}, "gpt-4o-mini", defaultRBACConfig)
require.NoError(t, err)
}
func TestHasAccess_User_AllowedModel_PrefixMatch(t *testing.T) {
// "gpt-4o-mini" prefix matches "gpt-4o-mini-2024-07-18"
err := router.HasAccess([]string{"user"}, "gpt-4o-mini-2024-07-18", defaultRBACConfig)
require.NoError(t, err)
}
func TestHasAccess_User_AllowedModel_MistralSmall(t *testing.T) {
err := router.HasAccess([]string{"user"}, "mistral-small", defaultRBACConfig)
require.NoError(t, err)
}
func TestHasAccess_User_UnauthorizedModel_Claude(t *testing.T) {
err := router.HasAccess([]string{"user"}, "claude-3-opus", defaultRBACConfig)
require.Error(t, err)
assert.Contains(t, err.Error(), "claude-3-opus")
}
func TestHasAccess_User_UnauthorizedModel_GPT4o(t *testing.T) {
err := router.HasAccess([]string{"user"}, "gpt-4o", defaultRBACConfig)
require.Error(t, err)
assert.Contains(t, err.Error(), "gpt-4o")
}
func TestHasAccess_UserCaseInsensitive(t *testing.T) {
err := router.HasAccess([]string{"USER"}, "gpt-4o-mini", defaultRBACConfig)
require.NoError(t, err)
}
// ─── Unknown / empty roles ────────────────────────────────────────────────────
func TestHasAccess_UnknownRole_TreatedAsUser_AllowedModel(t *testing.T) {
err := router.HasAccess([]string{"viewer"}, "gpt-4o-mini", defaultRBACConfig)
require.NoError(t, err) // gpt-4o-mini is in UserAllowedModels
}
func TestHasAccess_UnknownRole_TreatedAsUser_BlockedModel(t *testing.T) {
err := router.HasAccess([]string{"viewer"}, "claude-3-opus", defaultRBACConfig)
require.Error(t, err)
}
func TestHasAccess_EmptyRoles_TreatedAsUser(t *testing.T) {
err := router.HasAccess([]string{}, "gpt-4o-mini", defaultRBACConfig)
require.NoError(t, err)
}
func TestHasAccess_EmptyRoles_BlockedModel(t *testing.T) {
err := router.HasAccess([]string{}, "claude-3-opus", defaultRBACConfig)
require.Error(t, err)
}
// ─── Multi-role ───────────────────────────────────────────────────────────────
func TestHasAccess_MultiRole_AdminWins(t *testing.T) {
// user has both "user" and "admin" — admin takes priority
err := router.HasAccess([]string{"user", "admin"}, "claude-3-opus", defaultRBACConfig)
require.NoError(t, err)
}
func TestHasAccess_MultiRole_ManagerWins(t *testing.T) {
err := router.HasAccess([]string{"auditor", "manager"}, "claude-3-opus", defaultRBACConfig)
require.NoError(t, err)
}
// ─── Empty allowed models ─────────────────────────────────────────────────────
func TestHasAccess_EmptyAllowedModels_UserAlwaysBlocked(t *testing.T) {
cfg := &config.RBACConfig{
UserAllowedModels: []string{},
AuditorCanComplete: false,
}
err := router.HasAccess([]string{"user"}, "gpt-4o-mini", cfg)
require.Error(t, err)
}