From 205a85ba7567eae15710485f9d142983b8dcda07 Mon Sep 17 00:00:00 2001 From: Dylan Hemsworth Date: Mon, 25 Nov 2024 16:20:17 -0800 Subject: [PATCH] Add liquibase unit test example --- .../wfprev/data/entities/ProjectEntity.java | 4 +- .../nrs/wfprev/data/models/ProjectModel.java | 4 +- ...rojectBoundaryControllerLiquibaseTest.java | 172 ++++++++++++++++++ .../gov/nrs/wfprev/ProjectControllerTest.java | 5 +- .../wfprev/TestcontainersConfiguration.java | 15 +- .../java/resources/application.properties | 3 - .../resources/application-test.properties | 6 + 7 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/ProjectBoundaryControllerLiquibaseTest.java delete mode 100644 server/wfprev-api/src/test/java/resources/application.properties create mode 100644 server/wfprev-api/src/test/resources/application-test.properties diff --git a/server/wfprev-api/src/main/java/ca/bc/gov/nrs/wfprev/data/entities/ProjectEntity.java b/server/wfprev-api/src/main/java/ca/bc/gov/nrs/wfprev/data/entities/ProjectEntity.java index 125de2453..dc7b51345 100644 --- a/server/wfprev-api/src/main/java/ca/bc/gov/nrs/wfprev/data/entities/ProjectEntity.java +++ b/server/wfprev-api/src/main/java/ca/bc/gov/nrs/wfprev/data/entities/ProjectEntity.java @@ -134,11 +134,11 @@ public class ProjectEntity implements Serializable { @Column(name = "latitude", precision = 9, scale = 6) @Latitude - private Double latitude; + private BigDecimal latitude; @Column(name = "longitude", precision = 9, scale = 6) @Longitude - private Double longitude; + private BigDecimal longitude; @Column(name="last_progress_update_timestamp") private Date lastProgressUpdateTimestamp; diff --git a/server/wfprev-api/src/main/java/ca/bc/gov/nrs/wfprev/data/models/ProjectModel.java b/server/wfprev-api/src/main/java/ca/bc/gov/nrs/wfprev/data/models/ProjectModel.java index 7b1163895..f325b3f76 100644 --- a/server/wfprev-api/src/main/java/ca/bc/gov/nrs/wfprev/data/models/ProjectModel.java +++ b/server/wfprev-api/src/main/java/ca/bc/gov/nrs/wfprev/data/models/ProjectModel.java @@ -50,7 +50,7 @@ public class ProjectModel extends CommonModel { private BigDecimal totalProjectSizeHa; private BigDecimal totalCostPerHectareAmount; private Boolean isMultiFiscalYearProj; - private Double latitude; - private Double longitude; + private BigDecimal latitude; + private BigDecimal longitude; private Date lastProgressUpdateTimestamp; } diff --git a/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/ProjectBoundaryControllerLiquibaseTest.java b/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/ProjectBoundaryControllerLiquibaseTest.java new file mode 100644 index 000000000..1e298c85f --- /dev/null +++ b/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/ProjectBoundaryControllerLiquibaseTest.java @@ -0,0 +1,172 @@ +package ca.bc.gov.nrs.wfprev; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.when; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.hateoas.CollectionModel; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.nimbusds.jose.shaded.gson.Gson; +import com.nimbusds.jose.shaded.gson.GsonBuilder; +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import ca.bc.gov.nrs.wfprev.common.serializers.GeoJsonJacksonDeserializer; +import ca.bc.gov.nrs.wfprev.common.serializers.GeoJsonJacksonSerializer; +import ca.bc.gov.nrs.wfprev.data.models.ProjectBoundaryModel; +import ca.bc.gov.nrs.wfprev.services.ProjectBoundaryService; + +/** + * Identical test to the Project Boundary Controller test, + * However this one will instantiate and execute the Liquibase scripts + * so instead of just mocks, we could use the container and write to the DB + * directly. + * + * Note that this will mean that the "DB" folder will need to be on the classpath + * for liquibase to find it, either copy to resources, or link through target on build + * Currently DB must be on classpath, but there is an open ticket to change to relative + * https://github.com/liquibase/liquibase/issues/2687 + */ + +@SpringBootTest +@AutoConfigureMockMvc +@Import({TestcontainersConfiguration.class, MockMvcRestExceptionConfiguration.class}) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@TestPropertySource(value = { + "classpath:application-test.properties" +}) +class ProjectBoundaryControllerLiquibaseTest { + @MockBean + private ProjectBoundaryService projectBoundaryService; + + @Autowired + private MockMvc mockMvc; + + private Gson gson; + + @BeforeEach + void setup() { + GsonBuilder builder = new GsonBuilder(); + builder.serializeNulls().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX").serializeSpecialFloatingPointValues(); + gson = builder.create(); + } + + @Test + @WithMockUser + void testGetProjectBoundary() throws Exception { + String guid = UUID.randomUUID().toString(); + + ProjectBoundaryModel project = new ProjectBoundaryModel(); + project.setProjectGuid(guid); + + List projectList = Arrays.asList(project); + CollectionModel projectModel = CollectionModel.of(projectList); + + when(projectBoundaryService.getAllProjectBoundaries()).thenReturn(projectModel); + + mockMvc.perform(get("/projectBoundaries") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + when(projectBoundaryService.getProjectBoundaryById(guid)).thenReturn(project); + + mockMvc.perform(get("/projectBoundaries/{id}", guid) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void testCreateUpdateProjectBoundary() throws Exception { + ProjectBoundaryModel project = new ProjectBoundaryModel(); + + project.setBoundaryComment("test"); + project.setCollectionMethod("test"); + project.setSystemStartTimestamp(new Date()); + project.setSystemEndTimestamp(new Date()); + project.setProjectBoundaryGuid(UUID.randomUUID().toString()); + project.setCollectionDate(new Date()); + project.setBoundarySizeHa(BigDecimal.ONE); + project.setLocationGeometry((Geometry) new GeometryFactory().createPoint(new Coordinate(1, 1))); + + Coordinate[] coords = new Coordinate[] {new Coordinate(1, 1), new Coordinate(2, 1), new Coordinate(2, 2), new Coordinate(1, 2), new Coordinate(1, 1)}; + project.setBoundaryGeometry(new GeometryFactory().createPolygon(coords)); + + when(projectBoundaryService.createOrUpdateProjectBoundary(project)).thenReturn(project); + + // use the jackson mapper for handling Geometry types, Gson converters aren't + // implemented yet + ObjectMapper mapper = new ObjectMapper(); + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(new GeoJsonJacksonSerializer()); + simpleModule.addDeserializer(Geometry.class, new GeoJsonJacksonDeserializer()); + mapper.registerModule(simpleModule); + + String json = mapper.writeValueAsString(project); + + mockMvc.perform(post("/projectBoundaries") + .content(json) + .contentType(MediaType.APPLICATION_JSON) + .accept("application/json") + .header("Authorization", "Bearer admin-token")) + .andExpect(status().isCreated()); + + + project.setBoundaryComment("Test"); + when(projectBoundaryService.createOrUpdateProjectBoundary(project)).thenReturn(project); + + json = gson.toJson(project); + + mockMvc.perform(put("/projectBoundaries/{id}") + .content(json) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer admin-token")) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser + void testDeleteProjectBoundary() throws Exception { + ProjectBoundaryModel project = new ProjectBoundaryModel(); + when(projectBoundaryService.createOrUpdateProjectBoundary(project)).thenReturn(project); + + String json = gson.toJson(project); + + mockMvc.perform(post("/projectBoundaries") + .content(json) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer admin-token")) + .andExpect(status().isCreated()); + + when(projectBoundaryService.deleteProjectBoundary(project.getProjectGuid())).thenReturn(null); + + mockMvc.perform(delete("/projectBoundaries/{id}", project.getProjectGuid()) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer admin-token")) + .andExpect(status().isOk()); + } +} diff --git a/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/ProjectControllerTest.java b/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/ProjectControllerTest.java index 310e3de72..05add4df1 100644 --- a/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/ProjectControllerTest.java +++ b/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/ProjectControllerTest.java @@ -1,5 +1,6 @@ package ca.bc.gov.nrs.wfprev; +import java.math.BigDecimal; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -82,8 +83,8 @@ void testCreateUpdateProject() throws Exception { project.setProgramAreaGuid(UUID.randomUUID().toString()); project.setProjectName("Test"); project.setIsMultiFiscalYearProj(false); - project.setLatitude(40.99); - project.setLongitude(-115.23); + project.setLatitude(new BigDecimal(40.99)); + project.setLongitude(new BigDecimal(-115.23)); project.setLastProgressUpdateTimestamp(new Date()); when(projectService.createOrUpdateProject(project)).thenReturn(project); diff --git a/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/TestcontainersConfiguration.java b/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/TestcontainersConfiguration.java index 5f4b8f51e..d1d49eed2 100644 --- a/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/TestcontainersConfiguration.java +++ b/server/wfprev-api/src/test/java/ca/bc/gov/nrs/wfprev/TestcontainersConfiguration.java @@ -6,6 +6,8 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.context.annotation.Bean; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; import org.testcontainers.utility.DockerImageName; @@ -14,9 +16,10 @@ class TestcontainersConfiguration { @Bean @ServiceConnection - PostgreSQLContainer postgresContainer() { + static PostgreSQLContainer postgresContainer() { DockerImageName image = DockerImageName.parse("postgis/postgis:16-3.4").asCompatibleSubstituteFor("postgres"); PostgreSQLContainer container = new PostgreSQLContainer<>(image) + .withDatabaseName("wfprev") .withUsername("wfprev") .withPassword("password") .withExposedPorts(5432); @@ -30,4 +33,12 @@ PostgreSQLContainer postgresContainer() { return container; } -} + + @DynamicPropertySource + static void postgresqlProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", postgresContainer()::getJdbcUrl); + registry.add("spring.datasource.username", postgresContainer()::getUsername); + registry.add("spring.datasource.password", postgresContainer()::getPassword); + registry.add("spring.datasource.password", postgresContainer()::getPassword); + } +} \ No newline at end of file diff --git a/server/wfprev-api/src/test/java/resources/application.properties b/server/wfprev-api/src/test/java/resources/application.properties deleted file mode 100644 index 81b65e514..000000000 --- a/server/wfprev-api/src/test/java/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -spring.liquibase.change-log=classpath:db/main-changelog.xml -spring.liquibase.drop-first=true -spring.liquibase.contexts=test \ No newline at end of file diff --git a/server/wfprev-api/src/test/resources/application-test.properties b/server/wfprev-api/src/test/resources/application-test.properties new file mode 100644 index 000000000..bf5ffb49d --- /dev/null +++ b/server/wfprev-api/src/test/resources/application-test.properties @@ -0,0 +1,6 @@ +spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver +spring.datasource.url=jdbc:tc:postgresql://wfprev +spring.jpa.hibernate.ddl-auto=none +spring.liquibase.contexts=test +spring.liquibase.enabled=true +spring.liquibase.change-log=classpath:/db/main-changelog.json \ No newline at end of file