diff --git a/src/test/kotlin/no/nb/mlt/wls/order/controller/OrderControllerTest.kt b/src/test/kotlin/no/nb/mlt/wls/order/controller/OrderControllerTest.kt index 0ff12502..8852b950 100644 --- a/src/test/kotlin/no/nb/mlt/wls/order/controller/OrderControllerTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/order/controller/OrderControllerTest.kt @@ -11,6 +11,7 @@ import no.nb.mlt.wls.EnableTestcontainers import no.nb.mlt.wls.core.data.HostName import no.nb.mlt.wls.core.data.Owner import no.nb.mlt.wls.core.data.synq.SynqError +import no.nb.mlt.wls.order.model.Order import no.nb.mlt.wls.order.model.OrderLineStatus import no.nb.mlt.wls.order.model.OrderReceiver import no.nb.mlt.wls.order.model.OrderStatus @@ -18,6 +19,7 @@ import no.nb.mlt.wls.order.model.OrderType import no.nb.mlt.wls.order.model.ProductLine import no.nb.mlt.wls.order.payloads.ApiOrderPayload import no.nb.mlt.wls.order.payloads.toOrder +import no.nb.mlt.wls.order.payloads.toUpdateOrderPayload import no.nb.mlt.wls.order.repository.OrderRepository import no.nb.mlt.wls.order.service.SynqOrderService import org.assertj.core.api.Assertions.assertThat @@ -32,6 +34,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT import org.springframework.context.ApplicationContext import org.springframework.data.mongodb.repository.config.EnableMongoRepositories +import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf @@ -56,7 +59,7 @@ class OrderControllerTest( private lateinit var webTestClient: WebTestClient - val clientName: String = HostName.AXIELL.name + val client: String = HostName.AXIELL.name @BeforeEach fun setUp() { @@ -80,7 +83,7 @@ class OrderControllerTest( webTestClient .mutateWith(csrf()) - .mutateWith(mockJwt().jwt { it.subject(clientName) }) + .mutateWith(mockJwt().jwt { it.subject(client) }) .post() .accept(MediaType.APPLICATION_JSON) .bodyValue(testOrderPayload) @@ -99,7 +102,7 @@ class OrderControllerTest( fun `createOrder with duplicate payload returns OK`() { webTestClient .mutateWith(csrf()) - .mutateWith(mockJwt().jwt { it.subject(clientName) }) + .mutateWith(mockJwt().jwt { it.subject(client) }) .post() .accept(MediaType.APPLICATION_JSON) .bodyValue(duplicateOrderPayload) @@ -117,7 +120,7 @@ class OrderControllerTest( fun `createOrder payload with different data but same ID returns DB entry`() { webTestClient .mutateWith(csrf()) - .mutateWith(mockJwt().jwt { it.subject(clientName) }) + .mutateWith(mockJwt().jwt { it.subject(client) }) .post() .accept(MediaType.APPLICATION_JSON) .bodyValue( @@ -139,7 +142,7 @@ class OrderControllerTest( webTestClient .mutateWith(csrf()) - .mutateWith(mockJwt().jwt { it.subject(clientName) }) + .mutateWith(mockJwt().jwt { it.subject(client) }) .post() .accept(MediaType.APPLICATION_JSON) .bodyValue(testOrderPayload) @@ -156,7 +159,7 @@ class OrderControllerTest( webTestClient .mutateWith(csrf()) - .mutateWith(mockJwt().jwt { it.subject(clientName) }) + .mutateWith(mockJwt().jwt { it.subject(client) }) .post() .accept(MediaType.APPLICATION_JSON) .bodyValue(testOrderPayload) @@ -164,6 +167,86 @@ class OrderControllerTest( .expectStatus().is5xxServerError } + // FIXME - Endpoint should be returning DTO instead of direct Orders + @Test + fun `getOrder returns the order`() { + webTestClient + .mutateWith(csrf()) + .mutateWith(mockJwt().jwt { it.subject(client) }) + .get() + .uri("/{hostName}/{hostOrderId}", duplicateOrderPayload.hostName, duplicateOrderPayload.hostOrderId) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk + .expectBody(Order::class.java) + .consumeWith { response -> + assertThat(response?.responseBody?.hostOrderId.equals(duplicateOrderPayload.hostOrderId)) + assertThat(response?.responseBody?.status?.equals(duplicateOrderPayload.status)) + } + } + + @Test + fun `getOrder for wrong client throws`() { + webTestClient + .mutateWith(csrf()) + .mutateWith(mockJwt().jwt { it.subject("ALMA") }) + .get() + .uri("/{hostName}/{hostOrderId}", duplicateOrderPayload.hostName, duplicateOrderPayload.hostOrderId) + .exchange() + .expectStatus().isForbidden + } + + @Test + fun `updateOrder with valid payload updates order`() { + coEvery { + synqOrderService.updateOrder(any()) + } returns ResponseEntity.ok().build() + + val testPayload = + duplicateOrderPayload.toOrder().toUpdateOrderPayload() + .copy( + productLine = + listOf( + ProductLine("mlt-420", OrderLineStatus.NOT_STARTED), + ProductLine("mlt-421", OrderLineStatus.NOT_STARTED) + ) + ) + + webTestClient + .mutateWith(csrf()) + .mutateWith(mockJwt().jwt { it.subject(client) }) + .put() + .bodyValue(testPayload) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk + .expectBody() + .consumeWith { response -> + val products = response.responseBody?.productLine + products?.map { + assertThat(testPayload.productLine.contains(it)) + } + } + } + + @Test + fun `updateOrder when order is being processed errors`() { + val testPayload = testOrderPayload.copy(orderId = "mlt-test-order-processing", status = OrderStatus.IN_PROGRESS) + val testUpdatePayload = testPayload.toOrder().toUpdateOrderPayload().copy(orderType = OrderType.DIGITIZATION) + runTest { + repository.save(testPayload.toOrder()).awaitSingle() + + webTestClient + .mutateWith(csrf()) + .mutateWith(mockJwt().jwt { it.subject(client) }) + .put() + .bodyValue(testUpdatePayload) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isEqualTo(HttpStatus.CONFLICT) + } + } + @Test fun `deleteOrder with valid data deletes order`() = runTest { @@ -175,7 +258,7 @@ class OrderControllerTest( .mutateWith(csrf()) .mutateWith(mockJwt().jwt { it.subject("axiell") }) .delete() - .uri("/${duplicateOrderPayload.hostName}/${duplicateOrderPayload.hostOrderId}") + .uri("/{hostName}/{hostOrderId}", duplicateOrderPayload.hostName, duplicateOrderPayload.hostOrderId) .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk @@ -185,11 +268,28 @@ class OrderControllerTest( assertThat(order).isNull() } + @Test + fun `deleteOrder handles synq error`() { + coEvery { + synqOrderService.deleteOrder(any(), any()) + } returns ResponseEntity.internalServerError().build() + webTestClient + .mutateWith(csrf()) + .mutateWith(mockJwt().jwt { it.subject(client) }) + .delete() + .uri("/{hostName}/{hostOrderId}", duplicateOrderPayload.hostName, duplicateOrderPayload.hostOrderId) + .exchange() + .expectStatus().is5xxServerError + assertThat(true) + } + // ///////////////////////////////////////////////////////////////////////////// // //////////////////////////////// Test Help ////////////////////////////////// // ///////////////////////////////////////////////////////////////////////////// - // Will be used in most tests + /** + * Payload which is used in most tests + */ private val testOrderPayload = ApiOrderPayload( orderId = "axiell-order-69", @@ -211,7 +311,9 @@ class OrderControllerTest( callbackUrl = "callbackUrl" ) - // Will exist in the database + /** + * Payload which will exist in the database + */ private val duplicateOrderPayload = ApiOrderPayload( orderId = "order-123456", diff --git a/src/test/kotlin/no/nb/mlt/wls/order/service/OrderServiceTest.kt b/src/test/kotlin/no/nb/mlt/wls/order/service/OrderServiceTest.kt index c4d7dfbf..297c3f3e 100644 --- a/src/test/kotlin/no/nb/mlt/wls/order/service/OrderServiceTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/order/service/OrderServiceTest.kt @@ -120,11 +120,11 @@ class OrderServiceTest { @Test fun `update existing order with no errors returns ok`() { runTest { - every { db.findByHostNameAndHostOrderId(uop.hostName, uop.hostOrderId) } returns Mono.just(op.toOrder()) + every { db.findByHostNameAndHostOrderId(updateOrderPayload.hostName, updateOrderPayload.hostOrderId) } returns Mono.just(op.toOrder()) coEvery { synq.updateOrder(any()) } returns ResponseEntity.ok().build() every { db.save(any()) } returns Mono.just(op.toOrder()) - assertThat(cut.updateOrder(uop, client).statusCode.is2xxSuccessful) + assertThat(cut.updateOrder(updateOrderPayload, client).statusCode.is2xxSuccessful) } } @@ -134,7 +134,7 @@ class OrderServiceTest { assertThatExceptionOfType(ResponseStatusException::class.java).isThrownBy { runTest { - cut.updateOrder(uop, client) + cut.updateOrder(updateOrderPayload, client) } }.withMessageContaining("does not exist") } @@ -149,7 +149,7 @@ class OrderServiceTest { assertThatExceptionOfType(ResponseStatusException::class.java).isThrownBy { runTest { - cut.updateOrder(uop, client) + cut.updateOrder(updateOrderPayload, client) } }.withMessageContaining("409 CONFLICT") } @@ -158,7 +158,7 @@ class OrderServiceTest { fun `update order which you don't own throws`() { assertThatExceptionOfType(ResponseStatusException::class.java).isThrownBy { runBlocking { - cut.updateOrder(uop, "Alma") + cut.updateOrder(updateOrderPayload, "Alma") } }.withMessageContaining("403 FORBIDDEN") } @@ -170,7 +170,7 @@ class OrderServiceTest { assertThatExceptionOfType(ServerErrorException::class.java).isThrownBy { runTest { - cut.updateOrder(uop, client) + cut.updateOrder(updateOrderPayload, client) } } } @@ -205,9 +205,9 @@ class OrderServiceTest { ) /** - * Used for testing order update functionality (uop = update order payload) + * Used for testing order update functionality */ - private val uop = + private val updateOrderPayload = ApiUpdateOrderPayload( hostName = HostName.AXIELL, hostOrderId = "axiell-order-69", diff --git a/src/test/kotlin/no/nb/mlt/wls/product/controller/ProductControllerTest.kt b/src/test/kotlin/no/nb/mlt/wls/product/controller/ProductControllerTest.kt index 845df6e5..866f12d3 100644 --- a/src/test/kotlin/no/nb/mlt/wls/product/controller/ProductControllerTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/product/controller/ProductControllerTest.kt @@ -169,7 +169,9 @@ class ProductControllerTest( // //////////////////////////////// Test Help ////////////////////////////////// // ///////////////////////////////////////////////////////////////////////////// - // Will be used in most tests + /** + * Payload which will be used in most tests + */ private val testProductPayload = ApiProductPayload( hostId = "mlt-420", @@ -183,7 +185,9 @@ class ProductControllerTest( quantity = 1.0 ) - // Will exist in the database + /** + * Payload which will exist in the database + */ private val duplicateProductPayload = ApiProductPayload( hostId = "product-12346", diff --git a/src/test/kotlin/no/nb/mlt/wls/product/service/ProductServiceTest.kt b/src/test/kotlin/no/nb/mlt/wls/product/service/ProductServiceTest.kt index b07d0cf1..3db3fbcf 100644 --- a/src/test/kotlin/no/nb/mlt/wls/product/service/ProductServiceTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/product/service/ProductServiceTest.kt @@ -43,78 +43,99 @@ class ProductServiceTest { @Test fun `save called with payload missing hostId throws`() { - assertExceptionThrownWithMessage(tpp.copy(hostId = ""), "hostId is required", ServerWebInputException::class.java) - assertExceptionThrownWithMessage(tpp.copy(hostId = "\t\n"), "hostId is required", ServerWebInputException::class.java) - assertExceptionThrownWithMessage(tpp.copy(hostId = " "), "hostId is required", ServerWebInputException::class.java) + assertExceptionThrownWithMessage(testProductPayload.copy(hostId = ""), "hostId is required", ServerWebInputException::class.java) + assertExceptionThrownWithMessage(testProductPayload.copy(hostId = "\t\n"), "hostId is required", ServerWebInputException::class.java) + assertExceptionThrownWithMessage(testProductPayload.copy(hostId = " "), "hostId is required", ServerWebInputException::class.java) } @Test fun `save called with payload missing description throws`() { - assertExceptionThrownWithMessage(tpp.copy(description = ""), "description is required", ServerWebInputException::class.java) - assertExceptionThrownWithMessage(tpp.copy(description = "\t\n"), "description is required", ServerWebInputException::class.java) - assertExceptionThrownWithMessage(tpp.copy(description = " "), "description is required", ServerWebInputException::class.java) + assertExceptionThrownWithMessage(testProductPayload.copy(description = ""), "description is required", ServerWebInputException::class.java) + assertExceptionThrownWithMessage( + testProductPayload.copy(description = "\t\n"), + "description is required", + ServerWebInputException::class.java + ) + assertExceptionThrownWithMessage( + testProductPayload.copy(description = " "), + "description is required", + ServerWebInputException::class.java + ) } @Test fun `save called with payload missing productCategory throws`() { - assertExceptionThrownWithMessage(tpp.copy(productCategory = ""), "category is required", ServerWebInputException::class.java) - assertExceptionThrownWithMessage(tpp.copy(productCategory = "\t\n"), "category is required", ServerWebInputException::class.java) - assertExceptionThrownWithMessage(tpp.copy(productCategory = " "), "category is required", ServerWebInputException::class.java) + assertExceptionThrownWithMessage(testProductPayload.copy(productCategory = ""), "category is required", ServerWebInputException::class.java) + assertExceptionThrownWithMessage( + testProductPayload.copy(productCategory = "\t\n"), + "category is required", + ServerWebInputException::class.java + ) + assertExceptionThrownWithMessage( + testProductPayload.copy(productCategory = " "), + "category is required", + ServerWebInputException::class.java + ) } @Test fun `save called with existing product, returns existing product`() = runTest { - every { db.findByHostNameAndHostId(tpp.hostName, tpp.hostId) } returns Mono.just(tpp.toProduct()) + every { + db.findByHostNameAndHostId( + testProductPayload.hostName, + testProductPayload.hostId + ) + } returns Mono.just(testProductPayload.toProduct()) - val response = cut.save(tpp) + val response = cut.save(testProductPayload) - assertThat(response.body).isEqualTo(tpp) + assertThat(response.body).isEqualTo(testProductPayload) assertThat(response.statusCode).isEqualTo(OK) } @Test fun `save called with product that SynQ says exists, returns without a product`() = runTest { - every { db.findByHostNameAndHostId(tpp.hostName, tpp.hostId) } returns Mono.empty() + every { db.findByHostNameAndHostId(testProductPayload.hostName, testProductPayload.hostId) } returns Mono.empty() coEvery { synq.createProduct(any()) } returns ResponseEntity.ok().build() - val response = cut.save(tpp) + val response = cut.save(testProductPayload) assertThat(response.body).isNull() assertThat(response.statusCode).isEqualTo(OK) } @Test fun `save when synq fails handles it gracefully`() { - every { db.findByHostNameAndHostId(tpp.hostName, tpp.hostId) } returns Mono.empty() + every { db.findByHostNameAndHostId(testProductPayload.hostName, testProductPayload.hostId) } returns Mono.empty() coEvery { synq.createProduct(any()) } throws ServerErrorException( "Failed to create product in SynQ, the storage system responded with error code: '420' and error text: 'Blaze it LMAO'", Exception("420 Blaze it") ) - assertExceptionThrownWithMessage(tpp, "error code: '420' and error text: 'Blaze it LMAO'", ServerErrorException::class.java) + assertExceptionThrownWithMessage(testProductPayload, "error code: '420' and error text: 'Blaze it LMAO'", ServerErrorException::class.java) } @Suppress("ReactiveStreamsUnusedPublisher") @Test fun `save when DB fails handles it gracefully`() { - every { db.findByHostNameAndHostId(tpp.hostName, tpp.hostId) } returns Mono.error(TimeoutException()) + every { db.findByHostNameAndHostId(testProductPayload.hostName, testProductPayload.hostId) } returns Mono.error(TimeoutException()) coEvery { synq.createProduct(any()) } returns ResponseEntity.created(URI.create("")).build() every { db.save(any()) } returns Mono.error(Exception("DB is down")) - assertExceptionThrownWithMessage(tpp, "Failed to save product in the database", ServerErrorException::class.java) + assertExceptionThrownWithMessage(testProductPayload, "Failed to save product in the database", ServerErrorException::class.java) } @Test fun `save with no errors returns created product`() = runTest { - every { db.findByHostNameAndHostId(tpp.hostName, tpp.hostId) } returns Mono.empty() + every { db.findByHostNameAndHostId(testProductPayload.hostName, testProductPayload.hostId) } returns Mono.empty() coEvery { synq.createProduct(any()) } returns ResponseEntity.created(URI.create("")).build() - every { db.save(any()) } returns Mono.just(tpp.toProduct()) + every { db.save(any()) } returns Mono.just(testProductPayload.toProduct()) - val response = cut.save(tpp) - val cleanedProduct = tpp.copy(quantity = 0.0, location = null) + val response = cut.save(testProductPayload) + val cleanedProduct = testProductPayload.copy(quantity = 0.0, location = null) assertThat(response.body).isEqualTo(cleanedProduct) assertThat(response.statusCode).isEqualTo(CREATED) @@ -124,8 +145,8 @@ class ProductServiceTest { // //////////////////////////////// Test Help ////////////////////////////////// // ///////////////////////////////////////////////////////////////////////////// - // Will be used in most tests (tpp = test product payload) - private val tpp = + // Will be used in most tests + private val testProductPayload = ApiProductPayload( hostId = "mlt-420", hostName = HostName.AXIELL,