Add multi-tenancy integration tests 42/121142/6
authorEric Santos <eric.santos@yoppworks.com>
Thu, 6 May 2021 18:18:58 +0000 (14:18 -0400)
committerM.Hosnidokht <mohammad.hosnidokht@yoppworks.com>
Fri, 28 May 2021 13:47:11 +0000 (09:47 -0400)
- Created a separate directory under 'src' for integration tests
- Moved all multi-tenancy related integration test classes and files into 'it' directory
- Depends on https://gerrit.onap.org/r/c/ci-management/+/121243

Issue-ID: AAI-3324
Signed-off-by: Santos, Eric <eric.santos@yoppworks.com>
Change-Id: Ie50e81bad6b72fdd02ff2e937f2fda5e6b8228e9

aai-resources/pom.xml
aai-resources/src/it/java/org/onap/aai/multitenancy/KeycloakTestConfiguration.java [new file with mode: 0644]
aai-resources/src/it/java/org/onap/aai/multitenancy/KeycloakTestProperties.java [new file with mode: 0644]
aai-resources/src/it/java/org/onap/aai/multitenancy/MultiTenancyIT.java [new file with mode: 0644]
aai-resources/src/it/java/org/onap/aai/multitenancy/RoleHandler.java [new file with mode: 0644]
aai-resources/src/it/resources/application-keycloak-test.properties [new file with mode: 0644]
aai-resources/src/it/resources/multi-tenancy-realm.json [new file with mode: 0644]
aai-resources/src/it/resources/payloads/resource/pnf.json [new file with mode: 0644]
readme.md

index 908e1ee..2f8736c 100644 (file)
@@ -82,7 +82,9 @@
         <micrometer-core.version>1.6.6</micrometer-core.version>
         <micrometer-registry-prometheus.version>1.6.6</micrometer-registry-prometheus.version>
         <micrometer-jersey2>1.6.6</micrometer-jersey2>
+        <testcontainers.version>1.6.1</testcontainers.version>
         <!-- Setting some default value to not complain by editor but it will be overridden by gmaven plugin -->
+        <build.helper-maven-plugin.version>3.2.0</build.helper-maven-plugin.version>
     </properties>
     <profiles>
         <!-- Docker profile to be used for building docker image and pushing to nexus -->
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-spring-boot-starter</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.github.dasniko</groupId>
+            <artifactId>testcontainers-keycloak</artifactId>
+            <version>${testcontainers.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-admin-client</artifactId>
+            <version>${keycloak.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <dependencyManagement>
         <dependencies>
                 <configuration>
                     <mainClass>${start-class}</mainClass>
                     <layout>ZIP</layout>
+                    <classifier>exec</classifier>
                 </configuration>
                 <executions>
                     <execution>
                     </excludes>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <version>${build.helper-maven-plugin.version}</version>
+                <executions>
+                    <execution>
+                        <id>add-integration-test-source</id>
+                        <phase>generate-test-sources</phase>
+                        <goals>
+                            <goal>add-test-source</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>src/it/java</source>
+                            </sources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>add-integration-test-resource</id>
+                        <phase>generate-test-resources</phase>
+                        <goals>
+                            <goal>add-test-resource</goal>
+                        </goals>
+                        <configuration>
+                            <resources>
+                                <resource>
+                                    <directory>src/it/resources</directory>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
     <reporting>
diff --git a/aai-resources/src/it/java/org/onap/aai/multitenancy/KeycloakTestConfiguration.java b/aai-resources/src/it/java/org/onap/aai/multitenancy/KeycloakTestConfiguration.java
new file mode 100644 (file)
index 0000000..01f335a
--- /dev/null
@@ -0,0 +1,73 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.multitenancy;
+
+import com.github.dockerjava.api.model.ExposedPort;
+import com.github.dockerjava.api.model.HostConfig;
+import com.github.dockerjava.api.model.PortBinding;
+import com.github.dockerjava.api.model.Ports;
+import dasniko.testcontainers.keycloak.KeycloakContainer;
+import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.KeycloakBuilder;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+
+@TestConfiguration
+class KeycloakTestConfiguration {
+
+    @Bean
+    public AdapterConfig adapterConfig() {
+        return new KeycloakSpringBootProperties();
+    }
+
+    @Bean
+    KeycloakContainer keycloakContainer(KeycloakTestProperties properties) {
+        KeycloakContainer keycloak = new KeycloakContainer("jboss/keycloak:12.0.4")
+                .withRealmImportFile(properties.realmJson)
+                .withCreateContainerCmdModifier(cmd -> cmd.withHostConfig(
+                    new HostConfig().withPortBindings(new PortBinding(Ports.Binding.bindPort(Integer.parseInt(properties.port)), new ExposedPort(8080)))
+                ));
+        keycloak.start();
+        return keycloak;
+    }
+
+    @Bean
+    Keycloak keycloakAdminClient(KeycloakContainer keycloak, KeycloakTestProperties properties) {
+        return KeycloakBuilder.builder()
+                .serverUrl(keycloak.getAuthServerUrl())
+                .realm(properties.realm)
+                .clientId(properties.adminCli)
+                .username(keycloak.getAdminUsername())
+                .password(keycloak.getAdminPassword())
+                .build();
+    }
+
+    @Bean
+    RoleHandler roleHandler(Keycloak adminClient, KeycloakTestProperties properties) {
+        return new RoleHandler(adminClient, properties);
+    }
+
+    @Bean
+    KeycloakTestProperties properties() {
+        return new KeycloakTestProperties();
+    }
+}
diff --git a/aai-resources/src/it/java/org/onap/aai/multitenancy/KeycloakTestProperties.java b/aai-resources/src/it/java/org/onap/aai/multitenancy/KeycloakTestProperties.java
new file mode 100644 (file)
index 0000000..de62d2d
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.multitenancy;
+
+import org.springframework.beans.factory.annotation.Value;
+
+class KeycloakTestProperties {
+
+    @Value("${test.keycloak.realm.json}")
+    public String realmJson;
+
+    @Value("${keycloak.realm}")
+    public String realm;
+
+    @Value("${keycloak.resource}")
+    public String clientId;
+
+    @Value("${test.keycloak.client.secret}")
+    public String clientSecret;
+
+    @Value("${test.keycloak.admin.cli}")
+    public String adminCli;
+
+    @Value("${test.keycloak.auth-server-port}")
+    public String port;
+
+}
diff --git a/aai-resources/src/it/java/org/onap/aai/multitenancy/MultiTenancyIT.java b/aai-resources/src/it/java/org/onap/aai/multitenancy/MultiTenancyIT.java
new file mode 100644 (file)
index 0000000..2ad9616
--- /dev/null
@@ -0,0 +1,119 @@
+/**
+ * ============LICENSE_START==================================================
+ * org.onap.aai
+ * ===========================================================================
+ * Copyright © 2017-2020 AT&T Intellectual Property. All rights reserved.
+ * ===========================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END====================================================
+ */
+package org.onap.aai.multitenancy;
+
+import dasniko.testcontainers.keycloak.KeycloakContainer;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.KeycloakBuilder;
+import org.keycloak.representations.AccessTokenResponse;
+import org.onap.aai.PayloadUtil;
+import org.onap.aai.rest.AbstractSpringRestTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.*;
+import org.springframework.test.context.TestPropertySource;
+
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+@Import(KeycloakTestConfiguration.class)
+@TestPropertySource(locations = "classpath:application-keycloak-test.properties")
+public class MultiTenancyIT extends AbstractSpringRestTest {
+
+    @Autowired
+    private KeycloakContainer keycloakContainer;
+    @Autowired
+    private RoleHandler roleHandler;
+    @Autowired
+    private KeycloakTestProperties properties;
+
+    @Test
+    public void testCreateAndGetPnf() throws Exception {
+        baseUrl = "http://localhost:" + randomPort;
+        String endpoint = baseUrl + "/aai/v23/network/pnfs/pnf/pnf-1";
+        ResponseEntity responseEntity = null;
+
+        // create pnf with ran (operator)
+        String username = "ran", password = "ran";
+        headers = this.getHeaders(username, password);
+        httpEntity = new HttpEntity(PayloadUtil.getResourcePayload("pnf.json"), headers);
+        responseEntity = restTemplate.exchange(endpoint, HttpMethod.PUT, httpEntity, String.class);
+        assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
+
+        // get pnf with bob (operator_readOnly)
+        username = "bob"; password = "bob";
+        headers = this.getHeaders(username, password);
+        httpEntity = new HttpEntity("", headers);
+        responseEntity = restTemplate.exchange(endpoint, HttpMethod.GET, httpEntity, String.class);
+        assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+
+        // get pnf with ted (selector)
+        username = "ted"; password = "ted";
+        headers = this.getHeaders(username, password);
+        httpEntity = new HttpEntity("", headers);
+        responseEntity = restTemplate.exchange(endpoint, HttpMethod.GET, httpEntity, String.class);
+        assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode());
+
+        // add role to ted and try to get pnf again
+        roleHandler.addToUser(RoleHandler.OPERATOR_READ_ONLY, username);
+        headers = this.getHeaders(username, password);
+        httpEntity = new HttpEntity("", headers);
+        responseEntity = restTemplate.exchange(endpoint, HttpMethod.GET, httpEntity, String.class);
+        assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+
+        // get pnf with ran
+        username = "ran"; password = "ran";
+        headers = this.getHeaders(username, password);
+        httpEntity = new HttpEntity("", headers);
+        responseEntity = restTemplate.exchange(endpoint, HttpMethod.GET, httpEntity, String.class);
+        assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+    }
+
+    private HttpHeaders getHeaders(String username, String password) {
+        HttpHeaders headers = new HttpHeaders();
+
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+        headers.add("Real-Time", "true");
+        headers.add("X-FromAppId", "JUNIT");
+        headers.add("X-TransactionId", "JUNIT");
+        headers.add("Authorization", "Bearer " + getStringToken(username, password));
+
+        return headers;
+    }
+
+    private String getStringToken(String username, String password) {
+        Keycloak keycloakClient = KeycloakBuilder.builder()
+                .serverUrl(keycloakContainer.getAuthServerUrl())
+                .realm(properties.realm)
+                .clientId(properties.clientId)
+                .clientSecret(properties.clientSecret)
+                .username(username)
+                .password(password)
+                .build();
+
+        AccessTokenResponse tokenResponse = keycloakClient.tokenManager().getAccessToken();
+        assertNotNull(tokenResponse);
+        return tokenResponse.getToken();
+    }
+}
diff --git a/aai-resources/src/it/java/org/onap/aai/multitenancy/RoleHandler.java b/aai-resources/src/it/java/org/onap/aai/multitenancy/RoleHandler.java
new file mode 100644 (file)
index 0000000..0769c15
--- /dev/null
@@ -0,0 +1,57 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.multitenancy;
+
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.RealmResource;
+
+import java.util.Collections;
+
+class RoleHandler {
+
+     /**
+        Following roles should be the same as given roles in multi-tenancy-realm json file
+      */
+    final static String OPERATOR = "operator";
+    final static String OPERATOR_READ_ONLY = "operator_readOnly";
+    private final Keycloak adminClient;
+    private final KeycloakTestProperties properties;
+
+    RoleHandler(Keycloak adminClient, KeycloakTestProperties properties) {
+        this.adminClient = adminClient;
+        this.properties = properties;
+    }
+
+    void addToUser(String role, String username) {
+        RealmResource realm = adminClient.realm(properties.realm);
+        realm.users().get(username)
+                .roles()
+                .realmLevel()
+                .add(Collections.singletonList(realm.roles().get(role).toRepresentation()));
+    }
+
+    void removeFromUser(String role, String username) {
+        RealmResource realm = adminClient.realm(properties.realm);
+        realm.users().get(username)
+                .roles()
+                .realmLevel()
+                .remove(Collections.singletonList(realm.roles().get(role).toRepresentation()));
+    }
+}
diff --git a/aai-resources/src/it/resources/application-keycloak-test.properties b/aai-resources/src/it/resources/application-keycloak-test.properties
new file mode 100644 (file)
index 0000000..ca0266b
--- /dev/null
@@ -0,0 +1,17 @@
+test.keycloak.realm.json=multi-tenancy-realm.json
+test.keycloak.client.secret=secret
+test.keycloak.admin.cli=admin-cli
+test.keycloak.auth-server-port=58180
+
+keycloak.auth-server-url=http://localhost:58180/auth
+keycloak.realm=aai-resources
+keycloak.resource=aai-resources-app
+keycloak.public-client=true
+keycloak.principal-attribute=preferred_username
+
+keycloak.ssl-required=external
+keycloak.bearer-only=true
+
+multi.tenancy.enabled=true
+spring.profiles.active=production,keycloak
+schema.version.list=v10,v11,v12,v13,v14,v15,v23
diff --git a/aai-resources/src/it/resources/multi-tenancy-realm.json b/aai-resources/src/it/resources/multi-tenancy-realm.json
new file mode 100644 (file)
index 0000000..401187b
--- /dev/null
@@ -0,0 +1,173 @@
+{
+    "id": "aai-resources",
+    "realm": "aai-resources",
+    "notBefore": 0,
+    "revokeRefreshToken": false,
+    "refreshTokenMaxReuse": 0,
+    "accessTokenLifespan": 300,
+    "accessTokenLifespanForImplicitFlow": 900,
+    "ssoSessionIdleTimeout": 1800,
+    "ssoSessionMaxLifespan": 36000,
+    "ssoSessionIdleTimeoutRememberMe": 0,
+    "ssoSessionMaxLifespanRememberMe": 0,
+    "offlineSessionIdleTimeout": 2592000,
+    "offlineSessionMaxLifespanEnabled": false,
+    "offlineSessionMaxLifespan": 5184000,
+    "clientSessionIdleTimeout": 0,
+    "clientSessionMaxLifespan": 0,
+    "clientOfflineSessionIdleTimeout": 0,
+    "clientOfflineSessionMaxLifespan": 0,
+    "accessCodeLifespan": 60,
+    "accessCodeLifespanUserAction": 300,
+    "accessCodeLifespanLogin": 1800,
+    "actionTokenGeneratedByAdminLifespan": 43200,
+    "actionTokenGeneratedByUserLifespan": 300,
+    "enabled": true,
+    "sslRequired": "external",
+    "registrationAllowed": false,
+    "registrationEmailAsUsername": false,
+    "rememberMe": false,
+    "verifyEmail": false,
+    "loginWithEmailAllowed": true,
+    "duplicateEmailsAllowed": false,
+    "resetPasswordAllowed": false,
+    "editUsernameAllowed": false,
+    "bruteForceProtected": false,
+    "permanentLockout": false,
+    "maxFailureWaitSeconds": 900,
+    "minimumQuickLoginWaitSeconds": 60,
+    "waitIncrementSeconds": 60,
+    "quickLoginCheckMilliSeconds": 1000,
+    "maxDeltaTimeSeconds": 43200,
+    "failureFactor": 30,
+    "users": [
+        {
+            "username": "admin",
+            "enabled": true,
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "admin"
+                }
+            ],
+            "clientRoles": {
+                "realm-management": ["manage-users", "view-clients", "view-realm", "view-users"]
+            }
+        },
+        {
+            "id": "ran",
+            "username": "ran",
+            "enabled": true,
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "ran"
+                }
+            ],
+            "realmRoles": [
+                "operator"
+            ]
+        },
+        {
+            "id": "bob",
+            "username": "bob",
+            "enabled": true,
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "bob"
+                }
+            ],
+            "realmRoles": [
+                "operator_readOnly"
+            ]
+        },
+        {
+            "id": "ted",
+            "username": "ted",
+            "enabled": true,
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "ted"
+                }
+            ],
+            "realmRoles": [
+                "selector"
+            ]
+        }
+    ],
+    "roles": {
+        "realm": [
+            {
+                "name": "operator",
+                "description": "Operator privileges"
+            },
+            {
+                "name": "operator_readOnly",
+                "description": "Operator's read only privileges"
+            },
+            {
+                "name": "selector",
+                "description": "Selector privileges"
+            },
+            {
+                "name": "selector_readOnly",
+                "description": "Selector's read only privileges"
+            },
+            {
+                "name": "admin",
+                "description": "Administrator privileges"
+            }
+        ]
+    },
+    "clients": [
+        {
+            "clientId": "aai-resources-app",
+            "enabled": true,
+            "secret": "secret",
+            "directAccessGrantsEnabled": true,
+            "authorizationServicesEnabled": true,
+            "authorizationSettings": {
+                "allowRemoteResourceManagement": true,
+                "policyEnforcementMode": "ENFORCING"
+            }
+        }
+    ],
+    "defaultDefaultClientScopes": [
+        "roles",
+        "email",
+        "web-origins",
+        "profile",
+        "role_list"
+    ],
+    "clientScopes": [
+        {
+            "id": "0f7dfd8b-c230-4664-8d77-da85bcc4fe2a",
+            "name": "roles",
+            "description": "OpenID Connect scope for add user roles to the access token",
+            "protocol": "openid-connect",
+            "attributes": {
+                "include.in.token.scope": "true",
+                "display.on.consent.screen": "true",
+                "consent.screen.text": "${rolesScopeConsentText}"
+            },
+            "protocolMappers": [
+                {
+                    "id": "4b9f8798-8990-4c0d-87d3-034e72655e3b",
+                    "name": "realm roles",
+                    "protocol": "openid-connect",
+                    "protocolMapper": "oidc-usermodel-realm-role-mapper",
+                    "consentRequired": false,
+                    "config": {
+                        "multivalued": "true",
+                        "user.attribute": "foo",
+                        "access.token.claim": "true",
+                        "claim.name": "realm_access.roles",
+                        "jsonType.label": "String"
+                    }
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/aai-resources/src/it/resources/payloads/resource/pnf.json b/aai-resources/src/it/resources/payloads/resource/pnf.json
new file mode 100644 (file)
index 0000000..64523d1
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "frame-id": 999,
+  "in-maint": false,
+  "ipaddress-v4-oam": "1.1.1.1",
+  "pnf-name": "pnf-1",
+  "pnf-name-2": "pnf-test-1",
+  "data-owner": "operator",
+  "prov-status": "in_service"
+}
\ No newline at end of file
index 367867f..db3d526 100644 (file)
--- a/readme.md
+++ b/readme.md
@@ -49,5 +49,10 @@ The REST interface logs can be found under /opt/app/aai-resources/logs/rest.
 # Testing AAI Functionalities
 Any RESTful client such as SoapUI may be configured and setup to use for testing AAI requests.
 
+# Integration Tests
+In order to isolate integration tests from unit tests, `it` directory has been created under `src`.
+
+As a naming convention, All integration test classes should end with `IT` to be executed, and can be run along with all other unit tests with `mvn integration-test`
+