diff options
Diffstat (limited to 'app/src/test')
7 files changed, 1098 insertions, 0 deletions
diff --git a/app/src/test/java/org/onap/portal/history/BaseIntegrationTest.java b/app/src/test/java/org/onap/portal/history/BaseIntegrationTest.java new file mode 100644 index 0000000..e00b770 --- /dev/null +++ b/app/src/test/java/org/onap/portal/history/BaseIntegrationTest.java @@ -0,0 +1,180 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.history; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.nimbusds.jose.jwk.JWKSet; +import org.onap.portal.history.util.IdTokenExchange; +import io.restassured.RestAssured; +import io.restassured.filter.log.RequestLoggingFilter; +import io.restassured.filter.log.ResponseLoggingFilter; +import io.restassured.specification.RequestSpecification; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.http.MediaType; + +import java.util.List; +import java.util.UUID; + +/** Base class for all tests that has the common config including port, realm, logging and auth. */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureWireMock(port = 0) +public abstract class BaseIntegrationTest { + +// @TestConfiguration +// public static class Config { +// @Bean +// WireMockConfigurationCustomizer optionsCustomizer() { +// return options -> options.extensions(new ResponseTemplateTransformer(true)); +// } +// } + + @LocalServerPort protected int port; + @Value("${portal-history.realm}") + protected String realm; + + @Value("${portal-history.delete-interval}") + protected String deleteInterval; + + @Autowired protected ObjectMapper objectMapper; + @Autowired private TokenGenerator tokenGenerator; + + @BeforeAll + public static void setup() { + RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter()); + } + + /** Mocks the OIDC auth flow. */ + @BeforeEach + public void mockAuth() { + WireMock.reset(); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/realms/%s/protocol/openid-connect/certs", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", JWKSet.MIME_TYPE) + .withBody(tokenGenerator.getJwkSet().toString()))); + + final TokenGenerator.TokenGeneratorConfig config = + TokenGenerator.TokenGeneratorConfig.builder().port(port).realm(realm).sub("test-user").build(); + + WireMock.stubFor( + WireMock.post( + WireMock.urlMatching( + String.format("/auth/realms/%s/protocol/openid-connect/token", realm))) + .withBasicAuth("test", "test") + .withRequestBody(WireMock.containing("grant_type=client_credentials")) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody( + objectMapper + .createObjectNode() + .put("token_type", "bearer") + .put("access_token", tokenGenerator.generateToken(config)) + .put("expires_in", config.getExpireIn().getSeconds()) + .put("refresh_token", tokenGenerator.generateToken(config)) + .put("refresh_expires_in", config.getExpireIn().getSeconds()) + .put("not-before-policy", 0) + .put("session_state", UUID.randomUUID().toString()) + .put("scope", "email profile") + .toString()))); + } + + /** + * Builds an OAuth2 configuration including the roles, port and realm. This config can be used to + * generate OAuth2 access tokens. + * + * @param sub the userId + * @param roles the roles used for RBAC + * @return the OAuth2 configuration + */ + protected TokenGenerator.TokenGeneratorConfig getTokenGeneratorConfig(String sub, List<String> roles) { + return TokenGenerator.TokenGeneratorConfig.builder() + .port(port) + .sub(sub) + .realm(realm) + .roles(roles) + .build(); + } + + /** Get a RequestSpecification that does not have an Identity header. */ + protected RequestSpecification unauthenticatedRequestSpecification() { + return RestAssured.given().port(port); + } + + /** + * Object to store common attributes of requests that are going to be made. Adds an Identity + * header for the <code>onap_admin</code> role to the request. + * @return the definition of the incoming request (northbound) + */ + protected RequestSpecification requestSpecification() { + final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig("test-user", List.of("foo"))); + + return unauthenticatedRequestSpecification() + .auth() + .preemptive() + .oauth2(idToken) + .header(IdTokenExchange.X_AUTH_IDENTITY_HEADER, "Bearer " + idToken); + } + + /** + * Object to store common attributes of requests that are going to be made. Adds an Identity + * header for the <code>onap_admin</code> role to the request. + * @param userId the userId that should be contained in the incoming request + * @return the definition of the incoming request (northbound) + */ + protected RequestSpecification requestSpecification(String userId) { + final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig(userId, List.of("foo"))); + + return unauthenticatedRequestSpecification() + .auth() + .preemptive() + .oauth2(idToken) + .header(IdTokenExchange.X_AUTH_IDENTITY_HEADER, "Bearer " + idToken); + } + + /** + * Object to store common attributes of requests that are going to be made. Adds an Identity + * header for the <code>onap_admin</code> role to the request. + * @param userId the userId that should be contained in the incoming request + * @return the definition of the incoming request (northbound) + */ + protected RequestSpecification wrongHeaderRequestSpecification(String userId) { + final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig(userId, List.of("foo"))); + + return unauthenticatedRequestSpecification() + .auth() + .preemptive() + .oauth2(idToken) + .header("X-WRONG-HEADER", "Bearer " + idToken); + } +} diff --git a/app/src/test/java/org/onap/portal/history/TokenGenerator.java b/app/src/test/java/org/onap/portal/history/TokenGenerator.java new file mode 100644 index 0000000..986507c --- /dev/null +++ b/app/src/test/java/org/onap/portal/history/TokenGenerator.java @@ -0,0 +1,129 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.history; + +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +@Component +public class TokenGenerator { + + private static final String ROLES_CLAIM = "roles"; + private static final String USERID_CLAIM = "sub"; + + private final Clock clock; + private final RSAKey jwk; + private final JWKSet jwkSet; + private final JWSSigner signer; + + @Autowired + public TokenGenerator(Clock clock) { + try { + this.clock = clock; + jwk = + new RSAKeyGenerator(2048) + .keyUse(KeyUse.SIGNATURE) + .keyID(UUID.randomUUID().toString()) + .generate(); + jwkSet = new JWKSet(jwk); + signer = new RSASSASigner(jwk); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public JWKSet getJwkSet() { + return jwkSet; + } + + public String generateToken(TokenGeneratorConfig config) { + final Instant iat = clock.instant(); + final Instant exp = iat.plus(config.expireIn); + + final JWTClaimsSet claims = + new JWTClaimsSet.Builder() + .jwtID(UUID.randomUUID().toString()) + .subject(UUID.randomUUID().toString()) + .issuer(config.issuer()) + .issueTime(Date.from(iat)) + .expirationTime(Date.from(exp)) + .claim(ROLES_CLAIM, config.getRoles()) + .claim(USERID_CLAIM, config.getSub()) + .build(); + + final SignedJWT jwt = + new SignedJWT( + new JWSHeader.Builder(JWSAlgorithm.RS256) + .keyID(jwk.getKeyID()) + .type(JOSEObjectType.JWT) + .build(), + claims); + + try { + jwt.sign(signer); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return jwt.serialize(); + } + + @Getter + @Builder + public static class TokenGeneratorConfig { + private final int port; + + @NonNull private final String sub; + + @NonNull private final String realm; + + @NonNull @Builder.Default private final Duration expireIn = Duration.ofMinutes(5); + + @Builder.Default private final List<String> roles = Collections.emptyList(); + + public String issuer() { + return String.format("http://localhost:%d/auth/realms/%s", port, realm); + } + } +} diff --git a/app/src/test/java/org/onap/portal/history/actions/ActionDto.java b/app/src/test/java/org/onap/portal/history/actions/ActionDto.java new file mode 100644 index 0000000..2deec8e --- /dev/null +++ b/app/src/test/java/org/onap/portal/history/actions/ActionDto.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.history.actions; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ActionDto { + String type; + String action; + String message; + String downStreamSystem; + String downStreamId; +} diff --git a/app/src/test/java/org/onap/portal/history/actions/ActionFixtures.java b/app/src/test/java/org/onap/portal/history/actions/ActionFixtures.java new file mode 100644 index 0000000..efab59a --- /dev/null +++ b/app/src/test/java/org/onap/portal/history/actions/ActionFixtures.java @@ -0,0 +1,126 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.history.actions; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.onap.portal.history.entities.ActionsDao; +import org.onap.portal.history.openapi.model.CreateActionRequest; + +public class ActionFixtures { + + public static List<CreateActionRequest> createActionRequestList( + Integer numberOfActions, String userId, OffsetDateTime createdAt){ + List<CreateActionRequest> createActionRequestList = new ArrayList<>(); + for (Integer i = 1; i <= numberOfActions; i++) { + createActionRequestList.add( + generateActionRequest( + "Instantiation", "create", "action" + i, i.toString(), "SO", i, i, i, userId, createdAt)); + } + return createActionRequestList; + } + + public static List<CreateActionRequest> createActionRequestListHourOffsetOnly( + Integer numberOfActions, String userId, OffsetDateTime createdAt){ + List<CreateActionRequest> createActionRequestList = new ArrayList<>(); + for (Integer i = 1; i <= numberOfActions; i++) { + createActionRequestList.add( + generateActionRequest( + "Instantiation", "create", "action" + i, i.toString(), "SO", i, 0, 0, userId, createdAt)); + } + return createActionRequestList; + } + + public static CreateActionRequest generateActionRequest( + String type, + String action, + String message, + String id, + String downStreamSystem, + Integer deltaHours, + Integer deltaMinutes, + Integer deltaSeconds, + String userId, + OffsetDateTime createdAt) { + ActionDto actionDto = new ActionDto(); + actionDto.setType(type); + actionDto.setAction(action); + actionDto.setMessage(message); + actionDto.setDownStreamSystem(downStreamSystem); + actionDto.setDownStreamId(id); + + return new CreateActionRequest() + .userId(userId) + .action(actionDto) + .actionCreatedAt(createdAt.minusHours(deltaHours).minusMinutes(deltaMinutes).minusSeconds(deltaSeconds)); + } + + public static List<ActionsDao> actionsDaoList( + Integer numberOfActions, String userId, OffsetDateTime createdAt){ + List<ActionsDao> actionsDaoList = new ArrayList<>(); + for (Integer i = 1; i <= numberOfActions; i++) { + actionsDaoList.add( + generateActionsDao( + "Instantiation", "create", "action" + i, i.toString(), "SO", i, i, i, userId, createdAt)); + } + return actionsDaoList; + } + + public static ActionsDao generateActionsDao( + String type, + String action, + String message, + String id, + String downStreamSystem, + Integer deltaHours, + Integer deltaMinutes, + Integer deltaSeconds, + String userId, + OffsetDateTime createdAt) { + ActionDto actionDto = new ActionDto(); + actionDto.setType(type); + actionDto.setAction(action); + actionDto.setMessage(message); + actionDto.setDownStreamSystem(downStreamSystem); + actionDto.setDownStreamId(id); + + ActionsDao actionsDao = new ActionsDao(); + actionsDao.setUserId(userId); + actionsDao.setAction(actionDto); + actionsDao.setActionCreatedAt(new Date(createdAt.minusHours(deltaHours).minusMinutes(deltaMinutes).minusSeconds(deltaSeconds).toEpochSecond()*1000)); + return actionsDao; + } + + public static List<ActionsDao> actionsDaoListHourOffsetOnly( + Integer numberOfActions, String userId, OffsetDateTime createdAt){ + List<ActionsDao> actionsDaoList = new ArrayList<>(); + for (Integer i = 1; i <= numberOfActions; i++) { + actionsDaoList.add( + generateActionsDao( + "Instantiation", "create", "action" + i, i.toString(), "SO", i, 0, 0, userId, createdAt)); + } + return actionsDaoList; + } +} diff --git a/app/src/test/java/org/onap/portal/history/actions/ActionsControllerIntegrationTest.java b/app/src/test/java/org/onap/portal/history/actions/ActionsControllerIntegrationTest.java new file mode 100644 index 0000000..c5fa17b --- /dev/null +++ b/app/src/test/java/org/onap/portal/history/actions/ActionsControllerIntegrationTest.java @@ -0,0 +1,571 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * + */ + +package org.onap.portal.history.actions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onap.portal.history.BaseIntegrationTest; +import org.onap.portal.history.entities.ActionsDao; +import org.onap.portal.history.openapi.model.ActionResponse; +import org.onap.portal.history.openapi.model.ActionsListResponse; +import org.onap.portal.history.openapi.model.CreateActionRequest; +import org.onap.portal.history.openapi.model.Problem; +import org.onap.portal.history.repository.ActionsRepository; +import org.onap.portal.history.services.ActionsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import io.restassured.http.Header; + +class ActionsControllerIntegrationTest extends BaseIntegrationTest { + + protected static final String X_REQUEST_ID = "addf6005-3075-4c80-b7bc-2c70b7d42b57"; + protected static final String X_REQUEST_ID2 = "addf6005-3075-4c80-b7bc-2c70b7d42b22"; + + @Autowired + ActionsService actionsService; + + @Autowired + private ActionsRepository repository; + + // @Value("${portal-history.save-interval}") + protected Integer saveInterval = 72; + + @BeforeEach + void deleteMongoDataBase(){ + repository.deleteAll().block(); + } + + @Test + void thatUserCanHaveNoHistoryYet() throws JsonProcessingException { + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertNotNull(response); + assertThat(response.getTotalCount()).isEqualTo(0); + } + + @Test + void thatActionCanBeSaved() throws Exception{ + ActionDto actionDto = new ActionDto(); + actionDto.setType("instantiation"); + actionDto.setAction("create"); + actionDto.setDownStreamId("1234"); + actionDto.setDownStreamSystem("SO"); + actionDto.setMessage("no details"); + + CreateActionRequest actionRequest = new CreateActionRequest() + .actionCreatedAt(OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)) + .userId("test-user") + .action(actionDto); + + ActionResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .body(actionRequest) + .when() + .post( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionResponse.class); + + assertThat(response.getActionCreatedAt()).isEqualTo(actionRequest.getActionCreatedAt().truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ISO_DATE_TIME)); + assertThat(response.getSaveInterval()).isEqualTo(saveInterval); + assertThat(objectMapper.writeValueAsString(response.getAction())).isEqualTo(objectMapper.writeValueAsString(actionRequest.getAction())); + } + + @Test + void thatActionsCanBeListedWithoutParameter() throws JsonProcessingException { + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoList(500, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(10); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + assertThat(response.getActionsList().get(9).getSaveInterval()).isEqualTo(saveInterval); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(0).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(0).getAction())); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(9).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(9).getAction())); + } + + @Test + void thatActionsCanBeListedWithParameter() throws JsonProcessingException { + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoList(20, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions?page=1&pageSize=5") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(5); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + assertThat(response.getActionsList().get(4).getSaveInterval()).isEqualTo(saveInterval); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(0).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(0).getAction())); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(4).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(4).getAction())); + } + + @Test + void thatActionsCanBeListedWithParameterInOrderByActionCreatedAt() { + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusDays(2), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS))); + actionsDaoList.addAll(ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(6), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS))); + actionsDaoList.addAll(ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(12), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS))); + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions?page=1&pageSize=5") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(5); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + assertThat(response.getActionsList().get(4).getSaveInterval()).isEqualTo(saveInterval); + assertThat(response.getActionsList().get(0).getActionCreatedAt()).isEqualTo(actionsDaoList.get(5).getActionCreatedAt().toInstant().atOffset(ZoneOffset.UTC)); + assertThat(response.getActionsList().get(4).getActionCreatedAt()).isEqualTo(actionsDaoList.get(9).getActionCreatedAt().toInstant().atOffset(ZoneOffset.UTC)); + } + + @Test + void thatActionsCanBeListedWithShowLastHours() throws JsonProcessingException { + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(20, "test-user", OffsetDateTime.now().plusMinutes(30).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions?page=1&pageSize=20&showLastHours=12") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(12); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(0).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(0).getAction())); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(11).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(11).getAction())); + } + + @Test + void thatActionsCanNotBeListedWithWrongPageParameter() { + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusDays(2), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + + Problem response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions?page=0&pageSize=5") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract() + .body() + .as(Problem.class); + + assertThat(response.getStatus()).isEqualTo(500); + } + + @Test + void thatActionsCanBeGetForUserWithShowLastHours(){ + // First mixed user actions for different users + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.now().plusMinutes(30).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user?page=1&pageSize=20&showLastHours=2") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(2); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + } + + @Test + void thatActionsCanBeGottenForUserWithShowLastHoursWithMinusValue(){ + // First mixed user actions for different users + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now().plusHours(48), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + actionsDaoList.addAll(actionsDaoList4); + + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user?page=1&pageSize=20&showLastHours=-2") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(10); + } + + @Test + void thatActionsCanBeGottenForUserWithoutParameter(){ + // First mixed user actions for different users + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + actionsDaoList.addAll(actionsDaoList4); + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(10); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + } + + @Test + void thatActionsCanBeGottenForUserWithShowLastHoursWithEmptyList() { + // First mixed user actions for different users + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + actionsDaoList.addAll(actionsDaoList4); + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification("test4-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test4-user?page=1&pageSize=20&showLastHours=2") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isZero(); + } + + @Test + void thatActionsCanBeDeleted(){ + // First mixed user actions for different users + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.now().plusMinutes(30).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList2 = ActionFixtures.actionsDaoList(5, "test2-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList3 = ActionFixtures.actionsDaoList(3, "test3-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + repository + .saveAll(actionsDaoList) + .blockLast(); + + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .delete( "/v1/actions/test-user?deleteAfterHours=2") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test-user?page=1&pageSize=20") + .then() + .header("X-Request-Id", X_REQUEST_ID2) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser2 = requestSpecification("test2-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test2-user") + .then() + .header("X-Request-Id", X_REQUEST_ID2) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser3 = requestSpecification("test3-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test3-user") + .then() + .header("X-Request-Id", X_REQUEST_ID2) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(responseGetUser.getTotalCount()).isEqualTo(2); + assertThat(responseGetUser2.getTotalCount()).isEqualTo(5); + assertThat(responseGetUser3.getTotalCount()).isEqualTo(3); + } + + @Test + void thatActionsCanNotBeGetForUserBecauseOfWrongUserIdInToken(){ + // First mixed user actions for different users + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + + Problem response = requestSpecification("wrong-userId") + .given() + .accept(MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract() + .body() + .as(Problem.class); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()); + } + + @Test + void thatActionsCanNotBeGetForUserBecauseOfWrongHeader(){ + // First mixed user actions for different users + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + + Problem response = wrongHeaderRequestSpecification("test-user") + .given() + .accept(MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract() + .body() + .as(Problem.class); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()); + } + + @Test + void thatActionsCanBeDeletedForAllUsers(){ + // First mixed user actions for different users + List<ActionsDao> actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(96), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList2 = ActionFixtures.actionsDaoList(8, "test2-user", OffsetDateTime.of(LocalDateTime.now().minusHours(24), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList3 = ActionFixtures.actionsDaoList(5, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List<ActionsDao> actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(48), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + actionsDaoList.addAll(actionsDaoList4); + repository + .saveAll(actionsDaoList) + .blockLast(); + + actionsService.deleteActions(72).block(); + + ActionsListResponse responseGetUser = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test-user?page=1&pageSize=20") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser2 = requestSpecification("test2-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test2-user") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser3 = requestSpecification("test3-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test3-user") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(responseGetUser.getTotalCount()).isEqualTo(10); + assertThat(responseGetUser2.getTotalCount()).isEqualTo(8); + assertThat(responseGetUser3.getTotalCount()).isEqualTo(5); + } +} diff --git a/app/src/test/resources/application.yml b/app/src/test/resources/application.yml new file mode 100644 index 0000000..521befe --- /dev/null +++ b/app/src/test/resources/application.yml @@ -0,0 +1,38 @@ +server: + port: 9002 + address: 0.0.0.0 + +spring: + mongodb: + embedded: + version: 3.2.8 + jackson: + serialization: + # needed for serializing objects of type object + FAIL_ON_EMPTY_BEANS: false + security: + oauth2: + resourceserver: + jwt: + jwk-set-uri: http://localhost:${wiremock.server.port}/auth/realms/ONAP/protocol/openid-connect/certs #Keycloak Endpoint + +portal-history: + realm: ONAP + save-interval: 72 + delete-interval: '0 0 0 1 1 *' + +management: + endpoints: + web: + exposure: + include: "*" + info: + build: + enabled: true + env: + enabled: true + git: + enabled: true + java: + enabled: true + diff --git a/app/src/test/resources/logback-spring.xml b/app/src/test/resources/logback-spring.xml new file mode 100644 index 0000000..f4ef0bf --- /dev/null +++ b/app/src/test/resources/logback-spring.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration scan="true"> + <include resource="org/springframework/boot/logging/logback/defaults.xml"/> + + <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> + <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> + <level>${LOGBACK_LEVEL:-info}</level> + </filter> + <encoder class="net.logstash.logback.encoder.LogstashEncoder"/> + </appender> + + <root level="all"> + <appender-ref ref="stdout"/> + </root> +</configuration> |