package routing_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/veylant/ia-gateway/internal/routing" ) func rctx() *routing.RoutingContext { return &routing.RoutingContext{ TenantID: "t1", UserRole: "user", Department: "finance", Sensitivity: routing.SensitivityHigh, Model: "gpt-4o", UseCase: "summarization", TokenEstimate: 500, } } func cond(field, op string, val interface{}) routing.Condition { return routing.Condition{Field: field, Operator: op, Value: val} } // ─── eq / neq ──────────────────────────────────────────────────────────────── func TestCondition_Eq_Role_Match(t *testing.T) { assert.True(t, cond("user.role", "eq", "user").Evaluate(rctx())) } func TestCondition_Eq_Role_NoMatch(t *testing.T) { assert.False(t, cond("user.role", "eq", "admin").Evaluate(rctx())) } func TestCondition_Neq_Role(t *testing.T) { assert.True(t, cond("user.role", "neq", "admin").Evaluate(rctx())) assert.False(t, cond("user.role", "neq", "user").Evaluate(rctx())) } func TestCondition_Eq_Department(t *testing.T) { assert.True(t, cond("user.department", "eq", "finance").Evaluate(rctx())) assert.False(t, cond("user.department", "eq", "engineering").Evaluate(rctx())) } func TestCondition_Eq_Model(t *testing.T) { assert.True(t, cond("request.model", "eq", "gpt-4o").Evaluate(rctx())) } func TestCondition_Eq_UseCase(t *testing.T) { assert.True(t, cond("request.use_case", "eq", "summarization").Evaluate(rctx())) } // ─── in / nin ──────────────────────────────────────────────────────────────── func TestCondition_In_Role_Match(t *testing.T) { assert.True(t, cond("user.role", "in", []interface{}{"admin", "user"}).Evaluate(rctx())) } func TestCondition_In_Role_NoMatch(t *testing.T) { assert.False(t, cond("user.role", "in", []interface{}{"admin", "manager"}).Evaluate(rctx())) } func TestCondition_Nin_Department(t *testing.T) { assert.True(t, cond("user.department", "nin", []interface{}{"rh", "engineering"}).Evaluate(rctx())) assert.False(t, cond("user.department", "nin", []interface{}{"finance", "rh"}).Evaluate(rctx())) } // ─── sensitivity gte / lte ─────────────────────────────────────────────────── func TestCondition_Sensitivity_Gte_Match(t *testing.T) { // rctx sensitivity = high (3) assert.True(t, cond("request.sensitivity", "gte", "medium").Evaluate(rctx())) assert.True(t, cond("request.sensitivity", "gte", "high").Evaluate(rctx())) assert.False(t, cond("request.sensitivity", "gte", "critical").Evaluate(rctx())) } func TestCondition_Sensitivity_Lte_Match(t *testing.T) { assert.True(t, cond("request.sensitivity", "lte", "critical").Evaluate(rctx())) assert.True(t, cond("request.sensitivity", "lte", "high").Evaluate(rctx())) assert.False(t, cond("request.sensitivity", "lte", "medium").Evaluate(rctx())) } func TestCondition_Sensitivity_Eq(t *testing.T) { assert.True(t, cond("request.sensitivity", "eq", "high").Evaluate(rctx())) assert.False(t, cond("request.sensitivity", "eq", "critical").Evaluate(rctx())) } func TestCondition_Sensitivity_In(t *testing.T) { assert.True(t, cond("request.sensitivity", "in", []interface{}{"high", "critical"}).Evaluate(rctx())) } func TestCondition_Sensitivity_InvalidValue_ReturnsFalse(t *testing.T) { assert.False(t, cond("request.sensitivity", "gte", "invalid_level").Evaluate(rctx())) } // ─── token_estimate numeric ─────────────────────────────────────────────────── func TestCondition_TokenEstimate_Gte(t *testing.T) { assert.True(t, cond("request.token_estimate", "gte", float64(100)).Evaluate(rctx())) assert.True(t, cond("request.token_estimate", "gte", float64(500)).Evaluate(rctx())) assert.False(t, cond("request.token_estimate", "gte", float64(1000)).Evaluate(rctx())) } func TestCondition_TokenEstimate_Lte(t *testing.T) { assert.True(t, cond("request.token_estimate", "lte", float64(1000)).Evaluate(rctx())) assert.False(t, cond("request.token_estimate", "lte", float64(499)).Evaluate(rctx())) } // ─── contains / matches ─────────────────────────────────────────────────────── func TestCondition_Contains_Model(t *testing.T) { assert.True(t, cond("request.model", "contains", "4o").Evaluate(rctx())) assert.False(t, cond("request.model", "contains", "claude").Evaluate(rctx())) } func TestCondition_Matches_Model_Prefix(t *testing.T) { assert.True(t, cond("request.model", "matches", "gpt-").Evaluate(rctx())) assert.False(t, cond("request.model", "matches", "claude-").Evaluate(rctx())) } // ─── unknown field / operator ───────────────────────────────────────────────── func TestCondition_UnknownField_ReturnsFalse(t *testing.T) { assert.False(t, cond("user.unknown", "eq", "x").Evaluate(rctx())) } func TestCondition_UnknownOperator_ReturnsFalse(t *testing.T) { assert.False(t, cond("user.role", "startswith", "u").Evaluate(rctx())) } // ─── ValidateConditions ─────────────────────────────────────────────────────── func TestValidateConditions_Valid(t *testing.T) { err := routing.ValidateConditions([]routing.Condition{ {Field: "request.sensitivity", Operator: "gte", Value: "high"}, {Field: "user.department", Operator: "eq", Value: "finance"}, }) require.NoError(t, err) } func TestValidateConditions_InvalidField(t *testing.T) { err := routing.ValidateConditions([]routing.Condition{ {Field: "user.age", Operator: "eq", Value: "30"}, }) require.Error(t, err) assert.Contains(t, err.Error(), "user.age") } func TestValidateConditions_InvalidOperator(t *testing.T) { err := routing.ValidateConditions([]routing.Condition{ {Field: "user.role", Operator: "starts_with", Value: "ad"}, }) require.Error(t, err) assert.Contains(t, err.Error(), "starts_with") }