diff --git a/tests-bdd/README.md b/tests-bdd/README.md index b82dddcabc..64dce7cb97 100644 --- a/tests-bdd/README.md +++ b/tests-bdd/README.md @@ -302,6 +302,99 @@ The extension uses the configuration in `.vscode/settings.json` to locate your f - After installing the extension, you may need to reload VSCode or the extension for autocompletion to work. - If autocompletion is not working for specific steps, make sure that the steps are defined properly. Run the tests locally and ensure that no undefined steps are found. +### Multi-Strategy ERS Testing (Claims + LDAP) + +The `@multi-strategy-ers` scenarios validate that multi-strategy entity resolution works end-to-end through the full gRPC stack: **SDK → Connect RPC → platform server → ERS → LDAP**. + +These tests exist because direct-call Go integration tests (in `service/entityresolution/integration/`) bypass the gRPC/Connect RPC serialization layer. Bugs like structpb coercion (#3645) and pgx driver issues (#3672) only manifest at the serialization boundary — invisible to unit tests but caught by these BDD tests. + +#### What's tested + +| Scenario | User | LDAP department | Resource | Expected | +|----------|------|----------------|----------|----------| +| 1 | alice | engineering | engineering | PERMIT | +| 2 | bob | marketing | engineering | DENY | +| 3 | charlie | security | security | PERMIT | + +Each scenario creates its own namespace, attribute definitions, and subject mappings, then issues an authorization decision request. The ERS resolves the `user_name` entity against LDAP to retrieve department claims, which are then evaluated against subject mapping conditions. + +#### Architecture + +```text +┌──────────┐ ┌─────────────┐ ┌──────────┐ ┌─────────────────┐ ┌──────────┐ +│ SDK │───▶│ Connect RPC │───▶│ Platform │───▶│ Multi-Strategy │───▶│ LDAP │ +│ (client) │ │ (gRPC) │ │ (server) │ │ ERS (Claims + │ │ (osixia/ │ +│ │◀───│ │◀───│ │◀───│ LDAP providers)│◀───│ openldap)│ +└──────────┘ └─────────────┘ └──────────┘ └─────────────────┘ └──────────┘ +``` + +The multi-strategy ERS config uses two providers: +- **claims_passthrough** — passes through JWT claims (e.g. `userName`) +- **ldap_by_username** — looks up the user in LDAP by `uid`, returns `departmentNumber`, `mail`, `uid` + +#### Getting started + +**Prerequisites:** +- Docker (via Colima or Docker Desktop) +- Go 1.25+ +- JDK (`keytool` required for Keycloak truststore generation) + + ```bash + brew install openjdk + ``` + +**1. Set environment variables** (Colima users): + +```bash +export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock" +export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock +export TESTCONTAINERS_RYUK_DISABLED=true +export PLATFORM_IMAGE=DEBUG +``` + +Add `keytool` to PATH if using brew-installed OpenJDK: + +```bash +export PATH="/opt/homebrew/opt/openjdk/bin:$PATH" +``` + +**2. Run the multi-strategy ERS tests:** + +```bash +go test ./tests-bdd/ -v --tags=cukes --godog.tags=@multi-strategy-ers --count=1 +``` + +With console logging for debugging: + +```bash +CUKES_LOG_HANDLER=console go test ./tests-bdd/ -v --tags=cukes \ + --godog.tags=@multi-strategy-ers --godog.format=pretty --count=1 +``` + +**3. Verify output:** +All 3 scenarios should pass (57 steps, 0 failures). You'll see LDAP testcontainer startup, platform service registration, and authorization decision logs. + +#### Key files + +| File | Purpose | +|------|---------| +| [`features/multi-strategy-ers.feature`](features/multi-strategy-ers.feature) | Gherkin scenarios (3 scenarios, `@stateless`) | +| [`cukes/steps_ldap.go`](cukes/steps_ldap.go) | LDAP testcontainer step definition | +| [`cukes/steps_ers.go`](cukes/steps_ers.go) | Inline ERS configuration step definitions (DocString-based) | + +#### How it works + +1. **LDAP testcontainer** starts `osixia/openldap:1.5.0` with LDIF fixtures from `service/entityresolution/integration/ldap_test_data/` (8 test users with distinct department values) +2. **Inline ERS configuration** in the feature file's Background defines providers (Claims + LDAP) and mapping strategies with DocString YAML, making the full config visible without referencing external template files +3. **Each scenario** creates a namespace, department attribute (anyOf rule), subject mapping with `.department` selector, and then issues an authorization decision for a `user_name` entity +4. The platform resolves the entity through ERS → LDAP, gets department claims, evaluates subject mappings, and returns PERMIT or DENY + +#### Notes + +- The feature uses `@stateless` so the platform instance and LDAP container are shared across all scenarios within the feature. Each scenario uses a unique namespace to avoid data conflicts. +- LDAP test data lives in `service/entityresolution/integration/ldap_test_data/` and is shared with the Go integration tests. +- The `--copy-service` flag is used with the LDAP container to avoid macOS `sed -i` compatibility issues with bind mounts. + ## TODO - Improve execution time for platform testing - Remove keycloak with wiremock/mock or mock diff --git a/tests-bdd/cukes/glue_platform.go b/tests-bdd/cukes/glue_platform.go index 5c2b3ed09c..e851841348 100644 --- a/tests-bdd/cukes/glue_platform.go +++ b/tests-bdd/cukes/glue_platform.go @@ -70,6 +70,7 @@ type LocalDevScenarioOptions struct { InsecureSkipVerifyConn bool DatabaseName string PlatformPort int + LDAPPort int } func (d *DockerComposeLogger) Printf(format string, v ...interface{}) { @@ -135,6 +136,7 @@ func (c *PlatformTestSuiteContext) InitializeScenario(scenarioContext *godog.Sce KeycloakRealm: trackedScenarioContext.ScenarioOptions.KeycloakRealm, PlatformEndpoint: trackedScenarioContext.ScenarioOptions.PlatformEndpoint, InsecureSkipVerifyConn: trackedScenarioContext.ScenarioOptions.InsecureSkipVerifyConn, + LDAPPort: trackedScenarioContext.ScenarioOptions.LDAPPort, }, TestSuiteContext: c, } diff --git a/tests-bdd/cukes/steps_ers.go b/tests-bdd/cukes/steps_ers.go new file mode 100644 index 0000000000..ec5212398b --- /dev/null +++ b/tests-bdd/cukes/steps_ers.go @@ -0,0 +1,165 @@ +package cukes + +import ( + "context" + "errors" + "fmt" + "text/template" + + "github.com/cucumber/godog" + "gopkg.in/yaml.v2" +) + +const ersConfigKey = "ers_inline_config" + +type ERSInlineConfig struct { + Mode string + FailureStrategy string + Providers map[string]ERSProviderConfig + Strategies []ERSMappingStrategyConfig +} + +type ERSProviderConfig struct { + Type string + AutoWireLDAP bool +} + +type ERSMappingStrategyConfig struct { + Name string + Provider string + RawYAML string +} + +type ERSStepDefinitions struct { + PlatformCukesContext *PlatformTestSuiteContext + PlatformSteps *LocalPlatformStepDefinitions +} + +func getERSConfig(sc *PlatformScenarioContext) (*ERSInlineConfig, error) { + obj := sc.GetObject(ersConfigKey) + if obj == nil { + return nil, errors.New("no ERS configuration found; use 'an ERS configuration' step first") + } + cfg, ok := obj.(*ERSInlineConfig) + if !ok { + return nil, errors.New("invalid ERS configuration object") + } + return cfg, nil +} + +func (s *ERSStepDefinitions) anERSConfiguration(ctx context.Context, mode string, failureStrategy string) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + cfg := &ERSInlineConfig{ + Mode: mode, + FailureStrategy: failureStrategy, + Providers: make(map[string]ERSProviderConfig), + } + scenarioContext.RecordObject(ersConfigKey, cfg) + return ctx, nil +} + +func (s *ERSStepDefinitions) anERSProvider(ctx context.Context, name string, providerType string) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + cfg, err := getERSConfig(scenarioContext) + if err != nil { + return ctx, err + } + cfg.Providers[name] = ERSProviderConfig{Type: providerType} + return ctx, nil +} + +func (s *ERSStepDefinitions) anERSProviderConnectedToLDAP(ctx context.Context, name string, providerType string) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + cfg, err := getERSConfig(scenarioContext) + if err != nil { + return ctx, err + } + cfg.Providers[name] = ERSProviderConfig{ + Type: providerType, + AutoWireLDAP: true, + } + return ctx, nil +} + +func (s *ERSStepDefinitions) anERSMappingStrategy(ctx context.Context, name string, provider string, doc *godog.DocString) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + cfg, err := getERSConfig(scenarioContext) + if err != nil { + return ctx, err + } + cfg.Strategies = append(cfg.Strategies, ERSMappingStrategyConfig{ + Name: name, + Provider: provider, + RawYAML: doc.Content, + }) + return ctx, nil +} + +func (s *ERSStepDefinitions) aLocalPlatformWithInlineERSConfiguration(ctx context.Context) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + cfg, err := getERSConfig(scenarioContext) + if err != nil { + return ctx, err + } + kt := template.Must(template.New("kc").Parse(keycloakBaseTemplate)) + return s.PlatformSteps.commonLocalPlatform(ctx, &platformStartOptions{ + kcProvisionPath: kt, + ersConfig: cfg, + }) +} + +func buildEntityResolutionConfig(cfg *ERSInlineConfig, hostname string, ldapPort int) (map[string]interface{}, error) { + providers := map[string]interface{}{} + for name, p := range cfg.Providers { + provider := map[string]interface{}{ + "type": p.Type, + } + if p.AutoWireLDAP { + provider["connection"] = map[string]interface{}{ + "host": hostname, + "port": ldapPort, + "use_tls": false, + "bind_dn": "cn=admin,dc=opentdf,dc=test", + "bind_password": "admin123", + } + } else { + provider["connection"] = map[string]interface{}{} + } + providers[name] = provider + } + + var strategies []interface{} + for _, s := range cfg.Strategies { + var rawStrategy map[interface{}]interface{} + if err := yaml.Unmarshal([]byte(s.RawYAML), &rawStrategy); err != nil { + return nil, fmt.Errorf("failed to parse mapping strategy %q YAML: %w", s.Name, err) + } + strategy := map[string]interface{}{ + "name": s.Name, + "provider": s.Provider, + } + for k, v := range rawStrategy { + strategy[fmt.Sprintf("%v", k)] = v + } + strategies = append(strategies, strategy) + } + + return map[string]interface{}{ + "mode": cfg.Mode, + "failure_strategy": cfg.FailureStrategy, + "providers": providers, + "mapping_strategies": strategies, + }, nil +} + +func RegisterERSStepDefinitions(ctx *godog.ScenarioContext, x *PlatformTestSuiteContext) { + steps := &ERSStepDefinitions{ + PlatformCukesContext: x, + PlatformSteps: &LocalPlatformStepDefinitions{PlatformCukesContext: x}, + } + ctx.Step(`^an ERS configuration with mode "([^"]*)" and failure strategy "([^"]*)"$`, steps.anERSConfiguration) + ctx.Step(`^an ERS provider "([^"]*)" of type "([^"]*)"$`, steps.anERSProvider) + ctx.Step(`^an ERS provider "([^"]*)" of type "([^"]*)" connected to the LDAP directory$`, steps.anERSProviderConnectedToLDAP) + ctx.Step(`^an ERS mapping strategy "([^"]*)" using provider "([^"]*)"$`, steps.anERSMappingStrategy) + ctx.Step(`^a local platform with inline ERS configuration$`, steps.aLocalPlatformWithInlineERSConfiguration) +} diff --git a/tests-bdd/cukes/steps_ldap.go b/tests-bdd/cukes/steps_ldap.go new file mode 100644 index 0000000000..b1e92fb6b4 --- /dev/null +++ b/tests-bdd/cukes/steps_ldap.go @@ -0,0 +1,119 @@ +package cukes + +import ( + "context" + "errors" + "fmt" + "log/slog" + "path/filepath" + "time" + + "github.com/cucumber/godog" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + ldapFileMode = 0o644 + ldapStartupTimeout = 60 * time.Second +) + +type LDAPStepDefinitions struct { + PlatformCukesContext *PlatformTestSuiteContext +} + +func (s *LDAPStepDefinitions) anLDAPDirectoryWithTestUsers(ctx context.Context) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + logger := scenarioContext.TestSuiteContext.Logger + + // In @stateless mode, reuse the LDAP container from the first scenario + if scenarioContext.Stateless && scenarioContext.ScenarioOptions.LDAPPort > 0 { + logger.Info("reusing existing LDAP testcontainer", slog.Int("port", scenarioContext.ScenarioOptions.LDAPPort)) + return ctx, nil + } + + glue, ok := (*scenarioContext.TestSuiteContext.PlatformGlue).(*LocalDevPlatformGlue) + if !ok { + return ctx, errors.New("platform glue is not LocalDevPlatformGlue") + } + projectDir := glue.Options.ProjectDir + + ldapDataDir := filepath.Join(projectDir, "service", "entityresolution", "integration", "ldap_test_data") + ouFile := filepath.Join(ldapDataDir, "01_organizational_units.ldif") + usersFile := filepath.Join(ldapDataDir, "02_test_users.ldif") + + logger.Info("starting LDAP testcontainer", slog.String("ldap_data_dir", ldapDataDir)) + + containerRequest := testcontainers.ContainerRequest{ + Image: "osixia/openldap:1.5.0", + ExposedPorts: []string{"389/tcp"}, + Env: map[string]string{ + "LDAP_ORGANISATION": "OpenTDF Test", + "LDAP_DOMAIN": "opentdf.test", + "LDAP_ADMIN_PASSWORD": "admin123", + }, + Cmd: []string{"--copy-service"}, + Files: []testcontainers.ContainerFile{ + { + HostFilePath: ouFile, + ContainerFilePath: "/container/service/slapd/assets/config/bootstrap/ldif/custom/01_organizational_units.ldif", + FileMode: ldapFileMode, + }, + { + HostFilePath: usersFile, + ContainerFilePath: "/container/service/slapd/assets/config/bootstrap/ldif/custom/02_test_users.ldif", + FileMode: ldapFileMode, + }, + }, + WaitingFor: wait.ForLog("slapd starting").WithStartupTimeout(ldapStartupTimeout), + } + + ldapContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: containerRequest, + Started: true, + }) + if err != nil { + if ldapContainer != nil { + _ = ldapContainer.Terminate(context.WithoutCancel(ctx)) + } + return ctx, fmt.Errorf("failed to start LDAP container: %w", err) + } + + setupComplete := false + defer func() { + if !setupComplete { + _ = ldapContainer.Terminate(context.WithoutCancel(ctx)) + } + }() + + host, err := ldapContainer.Host(ctx) + if err != nil { + return ctx, fmt.Errorf("failed to get LDAP container host: %w", err) + } + + mappedPort, err := ldapContainer.MappedPort(ctx, "389") + if err != nil { + return ctx, fmt.Errorf("failed to get LDAP container port: %w", err) + } + + scenarioContext.ScenarioOptions.LDAPPort = int(mappedPort.Num()) + + logger.Info("LDAP testcontainer started", + slog.String("host", host), + slog.Int("port", scenarioContext.ScenarioOptions.LDAPPort)) + + scenarioContext.RegisterPlatformShutdownHook(func() error { + logger.Info("terminating LDAP testcontainer") + return ldapContainer.Terminate(context.WithoutCancel(ctx)) + }) + setupComplete = true + + return ctx, nil +} + +func RegisterLDAPStepDefinitions(ctx *godog.ScenarioContext, x *PlatformTestSuiteContext) { + steps := &LDAPStepDefinitions{ + PlatformCukesContext: x, + } + ctx.Step(`^an LDAP directory with test users$`, steps.anLDAPDirectoryWithTestUsers) +} diff --git a/tests-bdd/cukes/steps_localplatform.go b/tests-bdd/cukes/steps_localplatform.go index e9fb1b8882..042133d1ef 100644 --- a/tests-bdd/cukes/steps_localplatform.go +++ b/tests-bdd/cukes/steps_localplatform.go @@ -59,6 +59,7 @@ type platformStartOptions struct { platformProvisionPath *string kcProvisionPath *template.Template provisionDefaultPolicy bool + ersConfig *ERSInlineConfig } func (s *LocalPlatformStepDefinitions) aUser(ctx context.Context, username string, email string, attributes *godog.Table) (context.Context, error) { @@ -170,7 +171,7 @@ func (s *LocalPlatformStepDefinitions) commonLocalPlatform(ctx context.Context, if !exists { version = platformImageEnvironmentLocalImage } - platformConfigPath, err := createPlatformConfiguration(localPlatformOptions, scenarioContext.ScenarioOptions, version == debugVersion, options.platformProvisionPath) + platformConfigPath, err := createPlatformConfiguration(localPlatformOptions, scenarioContext.ScenarioOptions, version == debugVersion, options.platformProvisionPath, options.ersConfig) if err != nil { return ctx, err } @@ -530,7 +531,7 @@ func createPlatformComposeConfiguration(options *LocalDevOptions) (string, error } // createPlatformConfiguration generates a platform configuration from a go text template for platform option settings -func createPlatformConfiguration(options *LocalDevOptions, scenarioOptions *LocalDevScenarioOptions, devMode bool, platformTemplatePath *string) (string, error) { +func createPlatformConfiguration(options *LocalDevOptions, scenarioOptions *LocalDevScenarioOptions, devMode bool, platformTemplatePath *string, ersConfig *ERSInlineConfig) (string, error) { tempFileName := path.Join(options.CukesDir, "opentdf.yaml") platformKeysDir := options.KeysDir pgHost := "localhost" @@ -557,10 +558,33 @@ func createPlatformConfiguration(options *LocalDevOptions, scenarioOptions *Loca "pgHost": pgHost, "platformKeysDir": platformKeysDir, "authRealm": scenarioOptions.KeycloakRealm, + "ldapPort": scenarioOptions.LDAPPort, }); err != nil { return tempFileName, err } - err := os.WriteFile(tempFileName, strBuffer.Bytes(), os.FileMode(0o755)) //nolint:mnd // mkdir dir + + renderedBytes := strBuffer.Bytes() + if ersConfig != nil { + var configMap map[interface{}]interface{} + if err := yaml.Unmarshal(renderedBytes, &configMap); err != nil { + return tempFileName, fmt.Errorf("failed to parse rendered platform config: %w", err) + } + services, ok := configMap["services"].(map[interface{}]interface{}) + if !ok { + return tempFileName, errors.New("services section not found in platform config") + } + ersSection, err := buildEntityResolutionConfig(ersConfig, options.Hostname, scenarioOptions.LDAPPort) + if err != nil { + return tempFileName, fmt.Errorf("failed to build ERS config: %w", err) + } + services["entityresolution"] = ersSection + renderedBytes, err = yaml.Marshal(configMap) + if err != nil { + return tempFileName, fmt.Errorf("failed to marshal patched platform config: %w", err) + } + } + + err := os.WriteFile(tempFileName, renderedBytes, os.FileMode(0o755)) //nolint:mnd // mkdir dir if err != nil { return tempFileName, err } diff --git a/tests-bdd/features/multi-strategy-ers.feature b/tests-bdd/features/multi-strategy-ers.feature new file mode 100644 index 0000000000..85583d06af --- /dev/null +++ b/tests-bdd/features/multi-strategy-ers.feature @@ -0,0 +1,107 @@ +@multi-strategy-ers @stateless +Feature: Multi-strategy ERS entity resolution (Claims + LDAP) + Validate that multi-strategy ERS resolves entities from LDAP through the full + gRPC stack (SDK -> Connect RPC -> platform -> ERS -> LDAP). This catches + serialization bugs at the gRPC/structpb boundary that direct-call integration + tests miss. + + Background: + Given an LDAP directory with test users + And an ERS configuration with mode "multi-strategy" and failure strategy "continue" + And an ERS provider "jwt_claims" of type "claims" + And an ERS provider "ldap_directory" of type "ldap" connected to the LDAP directory + And an ERS mapping strategy "claims_passthrough" using provider "jwt_claims" + """ + entity_type: subject + conditions: + jwt_claims: + - claim: userName + operator: exists + output_mapping: + - source_claim: userName + claim_name: username + """ + And an ERS mapping strategy "ldap_by_username" using provider "ldap_directory" + """ + entity_type: subject + conditions: + jwt_claims: + - claim: userName + operator: exists + ldap_search: + base_dn: "ou=users,dc=opentdf,dc=test" + filter: "(&(objectClass=inetOrgPerson)(uid={username}))" + scope: subtree + attributes: ["uid", "mail", "departmentNumber"] + input_mapping: + - jwt_claim: userName + parameter: username + output_mapping: + - source_attribute: departmentNumber + claim_name: department + - source_attribute: mail + claim_name: email + - source_attribute: uid + claim_name: username + """ + And a local platform with inline ERS configuration + + Scenario: LDAP-resolved engineering user gets PERMIT + Given I submit a request to create a namespace with name "eng-permit.test" and reference id "ns_eng_permit" + And I send a request to create an attribute with: + | namespace_id | name | rule | values | + | ns_eng_permit | department | anyOf | engineering,marketing,security | + Then the response should be successful + Given a condition group referenced as "cg_eng" with an "or" operator with conditions: + | selector_value | operator | values | + | .department | in | engineering | + And a subject set referenced as "ss_eng" containing the condition groups "cg_eng" + And I send a request to create a subject condition set referenced as "scs_eng" containing subject sets "ss_eng" + And I send a request to create a subject mapping with: + | reference_id | attribute_value | condition_set_name | standard actions | custom actions | + | sm_eng | https://eng-permit.test/attr/department/value/engineering | scs_eng | read | | + Then the response should be successful + Given there is a "user_name" subject entity with value "alice" and referenced as "alice" + When I send a decision request for entity chain "alice" for "read" action on resource "https://eng-permit.test/attr/department/value/engineering" + Then the response should be successful + And I should get a "PERMIT" decision response + + Scenario: LDAP-resolved marketing user gets DENY for engineering resource + Given I submit a request to create a namespace with name "eng-deny.test" and reference id "ns_eng_deny" + And I send a request to create an attribute with: + | namespace_id | name | rule | values | + | ns_eng_deny | department | anyOf | engineering,marketing,security | + Then the response should be successful + Given a condition group referenced as "cg_eng2" with an "or" operator with conditions: + | selector_value | operator | values | + | .department | in | engineering | + And a subject set referenced as "ss_eng2" containing the condition groups "cg_eng2" + And I send a request to create a subject condition set referenced as "scs_eng2" containing subject sets "ss_eng2" + And I send a request to create a subject mapping with: + | reference_id | attribute_value | condition_set_name | standard actions | custom actions | + | sm_eng2 | https://eng-deny.test/attr/department/value/engineering | scs_eng2 | read | | + Then the response should be successful + Given there is a "user_name" subject entity with value "bob" and referenced as "bob" + When I send a decision request for entity chain "bob" for "read" action on resource "https://eng-deny.test/attr/department/value/engineering" + Then the response should be successful + And I should get a "DENY" decision response + + Scenario: LDAP-resolved security user gets PERMIT for security resource + Given I submit a request to create a namespace with name "sec-permit.test" and reference id "ns_sec_permit" + And I send a request to create an attribute with: + | namespace_id | name | rule | values | + | ns_sec_permit | department | anyOf | engineering,marketing,security | + Then the response should be successful + Given a condition group referenced as "cg_sec" with an "or" operator with conditions: + | selector_value | operator | values | + | .department | in | security | + And a subject set referenced as "ss_sec" containing the condition groups "cg_sec" + And I send a request to create a subject condition set referenced as "scs_sec" containing subject sets "ss_sec" + And I send a request to create a subject mapping with: + | reference_id | attribute_value | condition_set_name | standard actions | custom actions | + | sm_sec | https://sec-permit.test/attr/department/value/security | scs_sec | read | | + Then the response should be successful + Given there is a "user_name" subject entity with value "charlie" and referenced as "charlie" + When I send a decision request for entity chain "charlie" for "read" action on resource "https://sec-permit.test/attr/department/value/security" + Then the response should be successful + And I should get a "PERMIT" decision response diff --git a/tests-bdd/platform_test.go b/tests-bdd/platform_test.go index 34987b5f5a..4475b66432 100644 --- a/tests-bdd/platform_test.go +++ b/tests-bdd/platform_test.go @@ -114,6 +114,8 @@ func runTests() int { cukes.RegisterObligationsStepDefinitions(ctx, platformCukesContext) cukes.RegisterKasRegistryStepDefinitions(ctx) cukes.RegisterEncryptionStepDefinitions(ctx) + cukes.RegisterLDAPStepDefinitions(ctx, platformCukesContext) + cukes.RegisterERSStepDefinitions(ctx, platformCukesContext) platformCukesContext.InitializeScenario(ctx) }, Options: &opts,