package cel_test import ( "context" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/dexidp/dex/connector" dexcel "github.com/dexidp/dex/pkg/cel" ) func TestCompileBool(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) tests := map[string]struct { expr string wantErr bool }{ "true literal": { expr: "true", }, "comparison": { expr: "1 == 1", }, "string type mismatch": { expr: "'hello'", wantErr: true, }, "int type mismatch": { expr: "42", wantErr: true, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { result, err := compiler.CompileBool(tc.expr) if tc.wantErr { assert.Error(t, err) assert.Nil(t, result) } else { assert.NoError(t, err) assert.NotNil(t, result) } }) } } func TestCompileString(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) tests := map[string]struct { expr string wantErr bool }{ "string literal": { expr: "'hello'", }, "string concatenation": { expr: "'hello' + ' ' + 'world'", }, "bool type mismatch": { expr: "true", wantErr: true, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { result, err := compiler.CompileString(tc.expr) if tc.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.NotNil(t, result) } }) } } func TestCompileStringList(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) result, err := compiler.CompileStringList("['a', 'b', 'c']") assert.NoError(t, err) assert.NotNil(t, result) _, err = compiler.CompileStringList("'not a list'") assert.Error(t, err) } func TestCompile(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) // Compile accepts any type result, err := compiler.Compile("true") assert.NoError(t, err) assert.NotNil(t, result) result, err = compiler.Compile("'hello'") assert.NoError(t, err) assert.NotNil(t, result) result, err = compiler.Compile("42") assert.NoError(t, err) assert.NotNil(t, result) } func TestCompileErrors(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) tests := map[string]struct { expr string }{ "syntax error": { expr: "1 +", }, "undefined variable": { expr: "undefined_var", }, "undefined function": { expr: "undefinedFunc()", }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { _, err := compiler.Compile(tc.expr) assert.Error(t, err) }) } } func TestCompileRejectsUnknownFields(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) // Typo in field name: should fail at compile time with ObjectType _, err = compiler.CompileBool("identity.emial == 'test@example.com'") assert.Error(t, err) assert.Contains(t, err.Error(), "compilation failed") // Type mismatch: comparing string field to int should fail at compile time _, err = compiler.CompileBool("identity.email == 123") assert.Error(t, err) assert.Contains(t, err.Error(), "compilation failed") // Valid field: should compile fine _, err = compiler.CompileBool("identity.email == 'test@example.com'") assert.NoError(t, err) } func TestMaxExpressionLength(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) longExpr := "'" + strings.Repeat("a", dexcel.MaxExpressionLength) + "'" _, err = compiler.Compile(longExpr) assert.Error(t, err) assert.Contains(t, err.Error(), "maximum length") } func TestEvalBool(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) tests := map[string]struct { expr string identity dexcel.IdentityVal want bool }{ "email endsWith": { expr: "identity.email.endsWith('@example.com')", identity: dexcel.IdentityVal{Email: "user@example.com"}, want: true, }, "email endsWith false": { expr: "identity.email.endsWith('@example.com')", identity: dexcel.IdentityVal{Email: "user@other.com"}, want: false, }, "email_verified": { expr: "identity.email_verified == true", identity: dexcel.IdentityVal{EmailVerified: true}, want: true, }, "group membership": { expr: "identity.groups.exists(g, g == 'admin')", identity: dexcel.IdentityVal{Groups: []string{"admin", "dev"}}, want: true, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { prog, err := compiler.CompileBool(tc.expr) require.NoError(t, err) result, err := dexcel.EvalBool(context.Background(), prog, map[string]any{ "identity": tc.identity, }) require.NoError(t, err) assert.Equal(t, tc.want, result) }) } } func TestEvalString(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) // With ObjectType, identity.email is typed as string, so CompileString works. prog, err := compiler.CompileString("identity.email") require.NoError(t, err) result, err := dexcel.EvalString(context.Background(), prog, map[string]any{ "identity": dexcel.IdentityVal{Email: "user@example.com"}, }) require.NoError(t, err) assert.Equal(t, "user@example.com", result) } func TestEvalWithIdentityAndRequest(t *testing.T) { vars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...) compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) prog, err := compiler.CompileBool( `identity.email.endsWith('@example.com') && 'admin' in identity.groups && request.connector_id == 'okta'`, ) require.NoError(t, err) identity := dexcel.IdentityFromConnector(connector.Identity{ UserID: "123", Username: "john", Email: "john@example.com", Groups: []string{"admin", "dev"}, }) request := dexcel.RequestFromContext(dexcel.RequestContext{ ClientID: "my-app", ConnectorID: "okta", Scopes: []string{"openid", "email"}, }) result, err := dexcel.EvalBool(context.Background(), prog, map[string]any{ "identity": identity, "request": request, }) require.NoError(t, err) assert.True(t, result) } func TestNewCompilerWithVariables(t *testing.T) { // Claims variable — remains map(string, dyn) compiler, err := dexcel.NewCompiler(dexcel.ClaimsVariable()) require.NoError(t, err) // claims.email returns dyn from map access, use Compile (not CompileString) prog, err := compiler.Compile("claims.email") require.NoError(t, err) result, err := dexcel.EvalString(context.Background(), prog, map[string]any{ "claims": map[string]any{ "email": "test@example.com", }, }) require.NoError(t, err) assert.Equal(t, "test@example.com", result) }