Skip to content

Commit

Permalink
fix: forest client api integration (#367)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ricardo Campos authored Sep 12, 2024
1 parent 30acd17 commit 8807620
Show file tree
Hide file tree
Showing 14 changed files with 309 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ca.bc.gov.restapi.results.common.config;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/** This class contains configurations for all external APIs like address and keys. */
@Getter
@Configuration
public class ProvidersConfig {

@Value("${forest-client-api.address}")
private String forestClientBaseUri;

@Value("${forest-client-api.key}")
private String forestClientApiKey;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ public record ForestClientDto(
"A code indicating the status of ministry client. Examples include but are not"
+ " limited to: Active, Deactivated, Deceased...",
example = "ACT")
ForestClientStatusEnum clientStatus,
ForestClientStatusEnum clientStatusCode,
@Schema(
description =
"A code indicating a type of ministry client. Examples include but are not limited"
+ " to: Corporation, Individual, Association, First Nation..",
example = "C")
ForestClientTypeEnum clientType,
ForestClientTypeEnum clientTypeCode,
@Schema(
description = "An acronym for this client; works as an alternative identifier.",
example = "WFP")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package ca.bc.gov.restapi.results.common.enums;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;

/** This enum contains all forest client statuses codes and descriptions. */
@Getter
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ForestClientStatusEnum {
@JsonProperty("ACT")
ACTIVE("ACT", "Active"),
@JsonProperty("DAC")
DEACTIVATED("DAC", "Deactivated"),
@JsonProperty("DEC")
DECEASED("DEC", "Deceased"),
@JsonProperty("REC")
RECEIVERSHIP("REC", "Receivership"),
@JsonProperty("SPN")
SUSPENDED("SPN", "Suspended");

private final String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
package ca.bc.gov.restapi.results.common.enums;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;

/** This enum contains all forest client types codes and descriptions. */
@Getter
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ForestClientTypeEnum {
@JsonProperty("A")
ASSOCIATION('A', "Association"),
@JsonProperty("B")
FIRST_NATION_BAND('B', "First Nation Band"),
@JsonProperty("C")
CORPORATION('C', "Corporation"),
@JsonProperty("F")
MINISTRY_OF_FORESTS_AND_RANGE('F', "Ministry of Forests and Range"),
@JsonProperty("G")
GOVERNMENT('G', "Government"),
@JsonProperty("I")
INDIVIDUAL('I', "Individual"),
@JsonProperty("L")
LIMITED_PARTNERSHIP('L', "Limited Partnership"),
@JsonProperty("P")
GENERAL_PARTNERSHIP('P', "General Partnership"),
@JsonProperty("R")
FIRST_NATION_GROUP('R', "First Nation Group"),
@JsonProperty("S")
SOCIETY('S', "Society"),
@JsonProperty("T")
FIRST_NATION_TRIBAL_COUNCIL('T', "First Nation Tribal Council"),
@JsonProperty("U")
UNREGISTERED_COMPANY('U', "Unregistered Company");

private final Character code;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package ca.bc.gov.restapi.results.common.provider;

import ca.bc.gov.restapi.results.common.config.ProvidersConfig;
import ca.bc.gov.restapi.results.common.dto.ForestClientDto;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

/** This class contains methods to integrate SILVA REST API with ForestClient API. */
@Slf4j
@Component
public class ForestClientApiProvider {

private final ProvidersConfig providersConfig;

private final RestTemplate restTemplate;

private final String rootUri;

private static final String PROVIDER = "ForestClient API";

ForestClientApiProvider(ProvidersConfig providersConfig, RestTemplateBuilder templateBuilder) {
this.providersConfig = providersConfig;
this.restTemplate = templateBuilder.build();
this.rootUri = providersConfig.getForestClientBaseUri();
}

/**
* Fetch a ForestClient by its number.
*
* @param number the client number to search for
* @return the ForestClient with client number, if one exists
*/
public Optional<ForestClientDto> fetchClientByNumber(String number) {
String apiUrl = String.format("%s/clients/findByClientNumber/{number}", rootUri);
log.info("Starting {} request to {}", PROVIDER, apiUrl);

try {
ResponseEntity<ForestClientDto> response =
restTemplate.exchange(
apiUrl,
HttpMethod.GET,
new HttpEntity<>(addHttpHeaders()),
ForestClientDto.class,
createParamsMap("number", number));

log.info("Finished {} request for function {} - 200 OK!", PROVIDER, "fetchClientByNumber");
return Optional.of(response.getBody());
} catch (HttpClientErrorException httpExc) {
log.error("Finished {} request - Response code error: {}", PROVIDER, httpExc.getStatusCode());
}

return Optional.empty();
}

private String[] addAuthorizationHeader() {
String apiKey = this.providersConfig.getForestClientApiKey();
return new String[] {"X-API-KEY", apiKey};
}

private HttpHeaders addHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", MediaType.APPLICATION_JSON_VALUE);
String[] authorization = addAuthorizationHeader();
headers.set(authorization[0], authorization[1]);

return headers;
}

private Map<String, String> createParamsMap(String... values) {
Map<String, String> uriVars = new HashMap<>();
for (int i = 0; i < values.length; i += 2) {
uriVars.put(values[i], values[i + 1]);
}
return uriVars;
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package ca.bc.gov.restapi.results.common.service;

import ca.bc.gov.restapi.results.common.dto.ForestClientDto;
import ca.bc.gov.restapi.results.common.enums.ForestClientStatusEnum;
import ca.bc.gov.restapi.results.common.enums.ForestClientTypeEnum;
import ca.bc.gov.restapi.results.common.provider.ForestClientApiProvider;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;

/** This service contains methods for interacting with Forest Client API. */
@Slf4j
@Service
@RequiredArgsConstructor
public class ForestClientService {

private final ForestClientApiProvider forestClientApiProvider;

/**
* Get a {@link ForestClientDto} given a client number.
*
Expand All @@ -23,11 +27,16 @@ public Optional<ForestClientDto> getClientByNumber(String clientNumber) {
log.info("Received client number to fetch {}", clientNumber);

String fixedNumber = checkClientNumber(clientNumber);
log.info("Fixed client number {}", fixedNumber);
if (!fixedNumber.equals("clientNumber")) {
log.info("Fixed client number to fetch {}", fixedNumber);
}

Optional<ForestClientDto> clientDto = mockForestClient(fixedNumber);
log.info("Client {} found!", (clientDto.isEmpty() ? "not" : ""));
return clientDto;
try {
return forestClientApiProvider.fetchClientByNumber(fixedNumber);
} catch (HttpClientErrorException.NotFound e) {
log.info(String.format("Client %s not found", clientNumber), e);
return Optional.empty();
}
}

private String checkClientNumber(String clientNumber) {
Expand All @@ -42,53 +51,4 @@ private String checkClientNumber(String clientNumber) {
return "00000000";
}
}

private Optional<ForestClientDto> mockForestClient(String clientNumber) {
if (clientNumber.equals("00132184")) {
return Optional.of(
new ForestClientDto(
"00132184",
"TIMBER SALES MANAGER BABINE",
null,
null,
ForestClientStatusEnum.ACTIVE,
ForestClientTypeEnum.MINISTRY_OF_FORESTS_AND_RANGE,
"TBA"));
}
if (clientNumber.equals("00012797")) {
return Optional.of(
new ForestClientDto(
"00012797",
"MINISTRY OF FORESTS",
null,
null,
ForestClientStatusEnum.ACTIVE,
ForestClientTypeEnum.MINISTRY_OF_FORESTS_AND_RANGE,
"MOF"));
}
if (clientNumber.equals("00001012")) {
return Optional.of(
new ForestClientDto(
"00001012",
"BELL LUMBER & POLE CANADA, ULC",
null,
null,
ForestClientStatusEnum.ACTIVE,
ForestClientTypeEnum.CORPORATION,
"BELL"));
}
if (clientNumber.equals("00149081")) {
return Optional.of(
new ForestClientDto(
"00149081",
"WESTERN FOREST PRODUCTS INC.",
null,
null,
ForestClientStatusEnum.ACTIVE,
ForestClientTypeEnum.CORPORATION,
"WFP"));
}

return Optional.empty();
}
}
4 changes: 4 additions & 0 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ nr.results.dashboard-job-users = ${DASHBOARD_JOB_IDIR_USERS}

# Users allowed to see and download WMS layers information
nr.results.config.wms-layers.whitelist = ${WMS_LAYERS_WHITELIST_USERS:NONE}

# Forest Client API
forest-client-api.address = ${FORESTCLIENTAPI_ADDRESS:https://nr-forest-client-api-prod.api.gov.bc.ca/api}
forest-client-api.key = ${FORESTCLIENTAPI_KEY:placeholder-api-key}
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ void getForestClient_happyPath_shouldSucceed() throws Exception {

when(forestClientService.getClientByNumber(clientNumber)).thenReturn(Optional.of(clientDto));

ForestClientStatusEnum active = ForestClientStatusEnum.of(clientDto.clientStatus().getCode());
ForestClientTypeEnum type = ForestClientTypeEnum.of(clientDto.clientType().getCode());
ForestClientStatusEnum active =
ForestClientStatusEnum.of(clientDto.clientStatusCode().getCode());
ForestClientTypeEnum type = ForestClientTypeEnum.of(clientDto.clientTypeCode().getCode());

mockMvc
.perform(
Expand All @@ -59,8 +60,8 @@ void getForestClient_happyPath_shouldSucceed() throws Exception {
.andExpect(jsonPath("$.clientName").value(clientDto.clientName()))
.andExpect(jsonPath("$.legalFirstName").value(clientDto.legalFirstName()))
.andExpect(jsonPath("$.legalMiddleName").value(clientDto.legalMiddleName()))
.andExpect(jsonPath("$.clientStatus.code").value(active.getCode()))
.andExpect(jsonPath("$.clientType.code").value(type.getCode().toString()))
.andExpect(jsonPath("$.clientStatusCode.code").value(active.getCode()))
.andExpect(jsonPath("$.clientTypeCode.code").value(type.getCode().toString()))
.andExpect(jsonPath("$.acronym").value(clientDto.acronym()))
.andReturn();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package ca.bc.gov.restapi.results.common.provider;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

import ca.bc.gov.restapi.results.common.config.ProvidersConfig;
import ca.bc.gov.restapi.results.common.dto.ForestClientDto;
import ca.bc.gov.restapi.results.common.enums.ForestClientStatusEnum;
import ca.bc.gov.restapi.results.common.enums.ForestClientTypeEnum;
import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;

@RestClientTest(ForestClientApiProvider.class)
class ForestClientApiProviderTest {

@Autowired private ForestClientApiProvider forestClientApiProvider;

@Autowired private MockRestServiceServer mockServer;

@MockBean private ProvidersConfig providersConfig;

@Test
@DisplayName("Fetch client by number happy path should succeed")
public void fetchClientByNumber_happyPath_shouldSucceed() {
String clientNumber = "00012797";
String url = "/null/clients/findByClientNumber/" + clientNumber;
String json =
"""
{
"clientNumber": "00012797",
"clientName": "MINISTRY OF FORESTS",
"legalFirstName": null,
"legalMiddleName": null,
"clientStatusCode": "ACT",
"clientTypeCode": "F",
"acronym": "MOF"
}
""";

when(providersConfig.getForestClientApiKey()).thenReturn("1z2x2a4s5d5");

mockServer.expect(requestTo(url)).andRespond(withSuccess(json, MediaType.APPLICATION_JSON));

Optional<ForestClientDto> clientDto = forestClientApiProvider.fetchClientByNumber(clientNumber);

Assertions.assertTrue(clientDto.isPresent());

ForestClientDto forestClient = clientDto.get();
Assertions.assertEquals("00012797", forestClient.clientNumber());
Assertions.assertEquals("MINISTRY OF FORESTS", forestClient.clientName());
Assertions.assertNull(forestClient.legalFirstName());
Assertions.assertNull(forestClient.legalMiddleName());
Assertions.assertEquals(ForestClientStatusEnum.ACTIVE, forestClient.clientStatusCode());
Assertions.assertEquals(
ForestClientTypeEnum.MINISTRY_OF_FORESTS_AND_RANGE, forestClient.clientTypeCode());
Assertions.assertEquals("MOF", forestClient.acronym());
}

@Test
@DisplayName("Fetch client by number client not found should succeed")
void fetchClientByNumber_clientNotFound_shouldSucceed() {
String clientNumber = "00012797";
String url = "/null/clients/findByClientNumber/" + clientNumber;

when(providersConfig.getForestClientApiKey()).thenReturn("1z2x2a4s5d5");

mockServer.expect(requestTo(url)).andRespond(withStatus(HttpStatus.NOT_FOUND));

Optional<ForestClientDto> clientDto = forestClientApiProvider.fetchClientByNumber(clientNumber);

Assertions.assertTrue(clientDto.isEmpty());
}
}
Loading

0 comments on commit 8807620

Please sign in to comment.