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

159 lines
6.4 KiB
Go

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