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) }