Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: forest client api integration #367

Merged
merged 6 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading