diff --git a/.secrets.baseline b/.secrets.baseline index 227b25edf..441718175 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -75,6 +75,10 @@ { "path": "detect_secrets.filters.allowlist.is_line_allowlisted" }, + { + "path": "detect_secrets.filters.common.is_baseline_file", + "filename": ".secrets.baseline" + }, { "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", "min_level": 2 @@ -256,14 +260,14 @@ "filename": "src/main/java/uk/gov/pay/adminusers/resources/ServiceResource.java", "hashed_secret": "614770647df3ab100b871bfc0d20e72c8625a5c4", "is_verified": false, - "line_number": 149 + "line_number": 142 }, { "type": "Hex High Entropy String", "filename": "src/main/java/uk/gov/pay/adminusers/resources/ServiceResource.java", "hashed_secret": "1976a945a8d2733655e4b2453bd49fb59cb7ba19", "is_verified": false, - "line_number": 383 + "line_number": 368 } ], "src/main/java/uk/gov/pay/adminusers/resources/ToolboxEndpointResource.java": [ @@ -462,5 +466,5 @@ } ] }, - "generated_at": "2024-07-22T11:44:53Z" + "generated_at": "2024-09-06T16:24:31Z" } diff --git a/src/main/java/uk/gov/pay/adminusers/model/CreateServiceRequest.java b/src/main/java/uk/gov/pay/adminusers/model/CreateServiceRequest.java new file mode 100644 index 000000000..9559d6c76 --- /dev/null +++ b/src/main/java/uk/gov/pay/adminusers/model/CreateServiceRequest.java @@ -0,0 +1,16 @@ +package uk.gov.pay.adminusers.model; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import uk.gov.service.payments.commons.model.SupportedLanguage; + +import java.util.List; +import java.util.Map; + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public record CreateServiceRequest( + List gatewayAccountIds, + @JsonDeserialize(using = ServiceNamesDeserializer.class) Map serviceName) { +} + diff --git a/src/main/java/uk/gov/pay/adminusers/model/ServiceNamesDeserializer.java b/src/main/java/uk/gov/pay/adminusers/model/ServiceNamesDeserializer.java new file mode 100644 index 000000000..d0c174639 --- /dev/null +++ b/src/main/java/uk/gov/pay/adminusers/model/ServiceNamesDeserializer.java @@ -0,0 +1,23 @@ +package uk.gov.pay.adminusers.model; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import uk.gov.service.payments.commons.model.SupportedLanguage; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class ServiceNamesDeserializer extends JsonDeserializer> { + @Override + public Map deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + Map supportedLanguageToServiceName = new HashMap<>(); + jsonParser.getCodec().readValue(jsonParser, new TypeReference>() {}) + .forEach((key, value) -> + supportedLanguageToServiceName.put(SupportedLanguage.fromIso639AlphaTwoCode(key), value)); + return Collections.unmodifiableMap(supportedLanguageToServiceName); + } +} diff --git a/src/main/java/uk/gov/pay/adminusers/resources/ServiceResource.java b/src/main/java/uk/gov/pay/adminusers/resources/ServiceResource.java index 59394fda1..afbf3e38d 100644 --- a/src/main/java/uk/gov/pay/adminusers/resources/ServiceResource.java +++ b/src/main/java/uk/gov/pay/adminusers/resources/ServiceResource.java @@ -13,6 +13,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.gov.pay.adminusers.exception.ServiceNotFoundException; +import uk.gov.pay.adminusers.model.CreateServiceRequest; import uk.gov.pay.adminusers.model.GovUkPayAgreement; import uk.gov.pay.adminusers.model.SearchServicesResponse; import uk.gov.pay.adminusers.model.Service; @@ -33,7 +34,6 @@ import uk.gov.pay.adminusers.service.StripeAgreementService; import uk.gov.pay.adminusers.utils.Errors; import uk.gov.service.payments.commons.api.exception.ValidationException; -import uk.gov.service.payments.commons.model.SupportedLanguage; import javax.validation.Valid; import javax.validation.constraints.NotNull; @@ -53,13 +53,9 @@ import java.net.UnknownHostException; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static java.util.stream.Collectors.toUnmodifiableList; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; @@ -71,15 +67,12 @@ import static javax.ws.rs.core.Response.Status.NO_CONTENT; import static javax.ws.rs.core.Response.Status.OK; import static org.apache.commons.lang3.StringUtils.isBlank; -import static uk.gov.pay.adminusers.resources.ServiceResource.SERVICES_RESOURCE; -import static uk.gov.pay.adminusers.service.ServiceUpdater.FIELD_GATEWAY_ACCOUNT_IDS; -@Path(SERVICES_RESOURCE) +@Path("/v1/api/services") public class ServiceResource { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceResource.class); /* default */static final String HEADER_USER_CONTEXT = "GovUkPay-User-Context"; - public static final String SERVICES_RESOURCE = "/v1/api/services"; public static final String FIELD_NAME = "name"; @@ -230,25 +223,17 @@ public Response searchServices(JsonNode payload) { @ApiResponse(responseCode = "500", description = "Invalid JSON payload") } ) - public Response createService(JsonNode payload) { - LOGGER.info("Create Service POST request - [ {} ]", payload); - List gatewayAccountIds = extractGatewayAccountIds(payload); - Map serviceNameVariants = getServiceNameVariants(payload); + public Response createService(@Valid CreateServiceRequest createServiceRequest) { + LOGGER.info("Create Service POST request - [ {} ]", createServiceRequest); - Service service = serviceServicesFactory.serviceCreator().doCreate(gatewayAccountIds, serviceNameVariants); + var nullableCreateServiceRequest = Optional.ofNullable(createServiceRequest); + Service service = serviceServicesFactory.serviceCreator().doCreate( + nullableCreateServiceRequest.map(CreateServiceRequest::gatewayAccountIds).orElse(List.of()), + nullableCreateServiceRequest.map(CreateServiceRequest::serviceName).orElse(Map.of())); return Response.status(CREATED).entity(service).build(); } - private List extractGatewayAccountIds(JsonNode payload) { - List gatewayAccountIds = new ArrayList<>(); - if (payload != null && payload.get(FIELD_GATEWAY_ACCOUNT_IDS) != null) { - payload.get(FIELD_GATEWAY_ACCOUNT_IDS) - .elements().forEachRemaining((node) -> gatewayAccountIds.add(node.textValue())); - } - return List.copyOf(gatewayAccountIds); - } - @Path("/{serviceExternalId}") @PATCH @Produces(APPLICATION_JSON) @@ -533,16 +518,4 @@ public Response sendLiveAccountCreatedEmail(@Parameter(example = "7d19aff33f8948 sendLiveAccountCreatedEmailService.sendEmail(serviceExternalId); return Response.status(OK).build(); } - - private Map getServiceNameVariants(JsonNode payload) { - if (payload.hasNonNull("service_name")) { - JsonNode serviceName = payload.get("service_name"); - return Stream.of(SupportedLanguage.values()) - .filter(supportedLanguage -> serviceName.hasNonNull(supportedLanguage.toString())) - .collect(Collectors.toUnmodifiableMap( - supportedLanguage -> supportedLanguage, - supportedLanguage -> serviceName.get(supportedLanguage.toString()).asText())); - } - return Collections.emptyMap(); - } } diff --git a/src/main/java/uk/gov/pay/adminusers/service/LinksBuilder.java b/src/main/java/uk/gov/pay/adminusers/service/LinksBuilder.java index 3c5445edf..0b8177575 100644 --- a/src/main/java/uk/gov/pay/adminusers/service/LinksBuilder.java +++ b/src/main/java/uk/gov/pay/adminusers/service/LinksBuilder.java @@ -12,7 +12,6 @@ import static javax.ws.rs.core.UriBuilder.fromUri; import static uk.gov.pay.adminusers.resources.ForgottenPasswordResource.FORGOTTEN_PASSWORDS_RESOURCE; -import static uk.gov.pay.adminusers.resources.ServiceResource.SERVICES_RESOURCE; import static uk.gov.pay.adminusers.resources.UserResource.USERS_RESOURCE; public class LinksBuilder { @@ -32,7 +31,7 @@ public User decorate(User user) { } public Service decorate(Service service) { - URI uri = fromUri(baseUrl).path(SERVICES_RESOURCE).path(String.valueOf(service.getExternalId())) + URI uri = fromUri(baseUrl).path("/v1/api/services").path(String.valueOf(service.getExternalId())) .build(); Link selfLink = Link.from(Rel.SELF, "GET", uri.toString()); service.setLinks(List.of(selfLink)); diff --git a/src/test/java/uk/gov/pay/adminusers/queue/event/EventMessageHandlerTest.java b/src/test/java/uk/gov/pay/adminusers/queue/event/EventMessageHandlerTest.java index b9d96ae48..697cbc64a 100644 --- a/src/test/java/uk/gov/pay/adminusers/queue/event/EventMessageHandlerTest.java +++ b/src/test/java/uk/gov/pay/adminusers/queue/event/EventMessageHandlerTest.java @@ -158,7 +158,7 @@ void shouldHandleDisputeCreatedEvent() throws QueueException { assertThat(emails.size(), is(2)); assertThat(emails, hasItems("admin1@service.gov.uk", "admin2@service.gov.uk")); - assertThat(personalisation.get("serviceName"), is(service.getName())); + assertThat(personalisation.get("serviceNames"), is(service.getName())); assertThat(personalisation.get("paymentExternalId"), is("456")); assertThat(personalisation.get("serviceReference"), is("tx ref")); assertThat(personalisation.get("sendEvidenceToPayDueDate"), is("28 February 2022")); @@ -207,7 +207,7 @@ void shouldHandleDisputeLostEvent() throws QueueException { assertThat(emails.size(), is(2)); assertThat(emails, hasItems("admin1@service.gov.uk", "admin2@service.gov.uk")); - assertThat(personalisation.get("serviceName"), is(service.getName())); + assertThat(personalisation.get("serviceNames"), is(service.getName())); assertThat(personalisation.get("serviceReference"), is("tx ref")); assertThat(personalisation.get("organisationName"), is(service.getMerchantDetails().getName())); @@ -244,7 +244,7 @@ void shouldHandleDisputeWonEvent() throws QueueException { assertThat(emails.size(), is(2)); assertThat(emails, hasItems("admin1@service.gov.uk", "admin2@service.gov.uk")); - assertThat(personalisation.get("serviceName"), is(service.getName())); + assertThat(personalisation.get("serviceNames"), is(service.getName())); assertThat(personalisation.get("serviceReference"), is("tx ref")); assertThat(personalisation.get("organisationName"), is(service.getMerchantDetails().getName())); @@ -281,7 +281,7 @@ void shouldHandleDisputeEvidenceSubmittedEvent() throws QueueException { assertThat(emails.size(), is(2)); assertThat(emails, hasItems("admin1@service.gov.uk", "admin2@service.gov.uk")); - assertThat(personalisation.get("serviceName"), is(service.getName())); + assertThat(personalisation.get("serviceNames"), is(service.getName())); assertThat(personalisation.get("serviceReference"), is("tx ref")); assertThat(personalisation.get("organisationName"), is(service.getMerchantDetails().getName())); diff --git a/src/test/java/uk/gov/pay/adminusers/resources/IntegrationTest.java b/src/test/java/uk/gov/pay/adminusers/resources/IntegrationTest.java index 9a50cfa1d..0d08e4f73 100644 --- a/src/test/java/uk/gov/pay/adminusers/resources/IntegrationTest.java +++ b/src/test/java/uk/gov/pay/adminusers/resources/IntegrationTest.java @@ -83,6 +83,7 @@ public Response sendEmail() { @BeforeEach public void initialise() { databaseHelper = APP.getDatabaseTestHelper(); + databaseHelper.truncateAllData(); } protected RequestSpecification givenSetup() { diff --git a/src/test/java/uk/gov/pay/adminusers/resources/ServiceResourceCreateIT.java b/src/test/java/uk/gov/pay/adminusers/resources/ServiceResourceCreateIT.java index 0fd5da35f..3db18f20c 100644 --- a/src/test/java/uk/gov/pay/adminusers/resources/ServiceResourceCreateIT.java +++ b/src/test/java/uk/gov/pay/adminusers/resources/ServiceResourceCreateIT.java @@ -1,37 +1,117 @@ package uk.gov.pay.adminusers.resources; -import com.fasterxml.jackson.databind.JsonNode; +import io.restassured.response.ValidatableResponse; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; -import uk.gov.service.payments.commons.model.SupportedLanguage; import java.util.List; import java.util.Map; import static io.restassured.http.ContentType.JSON; +import static org.hamcrest.Matchers.emptyIterable; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; +import static uk.gov.service.payments.commons.model.SupportedLanguage.ENGLISH; +import static uk.gov.service.payments.commons.model.SupportedLanguage.WELSH; +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class ServiceResourceCreateIT extends IntegrationTest { @Test - void shouldCreateService() { - JsonNode payload = mapper - .valueToTree(Map.of( - "service_name", Map.of(SupportedLanguage.ENGLISH.toString(), "Service name"), - "gateway_account_ids", List.of("1") - )); - - givenSetup() + void create_service_with_all_parameters_successfully() { + var validatableResponse = givenSetup() .when() .contentType(JSON) - .body(payload) + .body(Map.of("service_name", Map.of(ENGLISH.toString(), "Service name"), + "gateway_account_ids", List.of("1"))) .post("v1/api/services") .then() .statusCode(201) .body("created_date", is(not(nullValue()))) .body("gateway_account_ids", is(List.of("1"))) .body("service_name", hasEntry("en", "Service name")); + + assertStandardFields(validatableResponse); + } + + private void assertStandardFields(ValidatableResponse validatableResponse) { + validatableResponse + .body("current_psp_test_account_stage", is("NOT_STARTED")) + .body("current_go_live_stage", is("NOT_STARTED")) + .body("default_billing_address_country", is("GB")) + .body("agent_initiated_moto_enabled", is(false)) + .body("takes_payments_over_phone", is(false)) + .body("experimental_features_enabled", is(false)) + .body("internal", is(false)) + .body("archived", is(false)) + .body("redirect_to_service_immediately_on_terminal_state", is(false)) + .body("collect_billing_address", is(true)); + } + + @Test + void can_create_default_service_with_empty_request_body() { + var validatableResponse = givenSetup() + .when() + .contentType(JSON) + .post("v1/api/services") + .then() + .statusCode(201) + .body("created_date", is(not(nullValue()))) + .body("gateway_account_ids", is(emptyIterable())) + .body("service_name", hasEntry("en", "System Generated")) + .body("name", is("System Generated")); + + assertStandardFields(validatableResponse); + } + + @Test + void can_create_default_service_with_gateway_account_ids_only() { + var validatableResponse = givenSetup() + .when() + .contentType(JSON) + .body(Map.of("gateway_account_ids", List.of("1", "2"))) + .post("v1/api/services") + .then() + .statusCode(201) + .body("created_date", is(not(nullValue()))) + .body("gateway_account_ids", is(List.of("1", "2"))) + .body("service_name", hasEntry("en", "System Generated")) + .body("name", is("System Generated")); + + assertStandardFields(validatableResponse); + } + + @Test + void can_create_default_service_with_service_name_only() { + var validatableResponse = givenSetup() + .when() + .contentType(JSON) + .body(Map.of("service_name", Map.of(ENGLISH.toString(), "Service name", WELSH.toString(), "Welsh name"))) + .post("v1/api/services") + .then() + .statusCode(201) + .body("created_date", is(not(nullValue()))) + .body("gateway_account_ids", is(emptyIterable())) + .body("service_name", hasEntry("en", "Service name")) + .body("service_name", hasEntry("cy", "Welsh name")) + .body("name", is("Service name")); + + assertStandardFields(validatableResponse); + } + + @Test + void return_bad_request_when_invalid_supported_language_provided() { + givenSetup() + .when() + .contentType(JSON) + .body(Map.of("service_name", Map.of("fr", "Service name"))) + .post("v1/api/services") + .then() + .statusCode(400) + .body("message", is("Unable to process JSON")) + .body("details", is("fr is not a supported ISO 639-1 code")); } } diff --git a/src/test/java/uk/gov/pay/adminusers/unit/service/ServiceResourceCreateTest.java b/src/test/java/uk/gov/pay/adminusers/unit/service/ServiceResourceCreateTest.java index 0e21aacef..6693f084c 100644 --- a/src/test/java/uk/gov/pay/adminusers/unit/service/ServiceResourceCreateTest.java +++ b/src/test/java/uk/gov/pay/adminusers/unit/service/ServiceResourceCreateTest.java @@ -48,7 +48,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static uk.gov.pay.adminusers.resources.ServiceResource.FIELD_NAME; -import static uk.gov.pay.adminusers.resources.ServiceResource.SERVICES_RESOURCE; import static uk.gov.pay.adminusers.service.ServiceUpdater.FIELD_GATEWAY_ACCOUNT_IDS; @ExtendWith(DropwizardExtensionsSupport.class) @@ -103,7 +102,7 @@ public void shouldSuccess_whenCreateAServiceWithoutParameters() { Service service = buildService(Collections.emptyList(), Collections.emptyMap()); given(mockedServiceCreator.doCreate(Collections.emptyList(), Collections.emptyMap())) .willReturn(service); - Response response = RESOURCES.target(SERVICES_RESOURCE) + Response response = RESOURCES.target("/v1/api/services") .request(MediaType.APPLICATION_JSON) .post(Entity.json(PAYLOAD_MAP), Response.class); @@ -129,7 +128,7 @@ public void shouldSuccess_whenCreateAServiceWithNameOnly() { Service service = buildService(Collections.emptyList(), Map.of(SupportedLanguage.ENGLISH, EN_SERVICE_NAME)); given(mockedServiceCreator.doCreate(Collections.emptyList(), Map.of(SupportedLanguage.ENGLISH, EN_SERVICE_NAME))) .willReturn(service); - Response response = RESOURCES.target(SERVICES_RESOURCE) + Response response = RESOURCES.target("/v1/api/services") .request(MediaType.APPLICATION_JSON) .post(Entity.json(PAYLOAD_MAP), Response.class); @@ -152,7 +151,7 @@ public void shouldSuccess_whenCreateAServiceWithEnglishNameOnly() { Service service = buildService(Collections.emptyList(), Map.of(SupportedLanguage.ENGLISH, EN_SERVICE_NAME)); given(mockedServiceCreator.doCreate(Collections.emptyList(), Map.of(SupportedLanguage.ENGLISH, EN_SERVICE_NAME))) .willReturn(service); - Response response = RESOURCES.target(SERVICES_RESOURCE) + Response response = RESOURCES.target("/v1/api/services") .request(MediaType.APPLICATION_JSON) .post(Entity.json(PAYLOAD_MAP), Response.class); @@ -179,7 +178,7 @@ public void shouldSuccess_whenCreateAServiceWithName_andGatewayAccountIds() { given(mockedServiceCreator.doCreate(gatewayAccounts, Map.of(SupportedLanguage.ENGLISH, EN_SERVICE_NAME))) .willReturn(service); - Response response = RESOURCES.target(SERVICES_RESOURCE) + Response response = RESOURCES.target("/v1/api/services") .request(MediaType.APPLICATION_JSON) .post(Entity.json(PAYLOAD_MAP), Response.class); assertThat(response.getStatus(), is(201)); @@ -205,7 +204,7 @@ public void shouldSuccess_whenCreateAServiceWithEnglishName_andGatewayAccountIds given(mockedServiceCreator.doCreate(gatewayAccounts, Map.of(SupportedLanguage.ENGLISH, EN_SERVICE_NAME))) .willReturn(service); - Response response = RESOURCES.target(SERVICES_RESOURCE) + Response response = RESOURCES.target("/v1/api/services") .request(MediaType.APPLICATION_JSON) .post(Entity.json(PAYLOAD_MAP), Response.class); assertThat(response.getStatus(), is(201)); @@ -235,7 +234,7 @@ public void shouldSuccess_whenCreateAServiceWithName_andGatewayAccountIds_andSer given(mockedServiceCreator.doCreate(gatewayAccounts, serviceName)) .willReturn(service); - Response response = RESOURCES.target(SERVICES_RESOURCE) + Response response = RESOURCES.target("/v1/api/services") .request(MediaType.APPLICATION_JSON) .post(Entity.json(PAYLOAD_MAP), Response.class); @@ -260,7 +259,7 @@ public void shouldError409_whenGatewayAccountsAreAlreadyAssignedToAService() { given(mockedServicesFactory.serviceCreator()).willReturn(serviceCreator); given(mockedServiceDao.checkIfGatewayAccountsUsed(anyList())).willReturn(true); - Response response = RESOURCES.target(SERVICES_RESOURCE) + Response response = RESOURCES.target("/v1/api/services") .request(MediaType.APPLICATION_JSON) .post(Entity.json(PAYLOAD_MAP), Response.class); assertThat(response.getStatus(), is(409));