Skip to content

Commit

Permalink
MLT-0032 Tests for OrderService (#28)
Browse files Browse the repository at this point in the history
Added tests relating to the Orders
  • Loading branch information
anotheroneofthese authored Aug 20, 2024
1 parent 5781a4d commit 31328bf
Show file tree
Hide file tree
Showing 6 changed files with 479 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import reactor.core.publisher.Mono

@Repository
interface OrderRepository : ReactiveMongoRepository<Order, String> {
fun getByHostNameAndHostOrderId(
fun findByHostNameAndHostOrderId(
hostName: HostName,
hostOrderId: String
): Mono<Order>
Expand Down
39 changes: 32 additions & 7 deletions src/main/kotlin/no/nb/mlt/wls/order/service/OrderService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import org.springframework.web.server.ServerErrorException
import org.springframework.web.server.ServerWebInputException
import java.time.Duration
import java.util.concurrent.TimeoutException

Expand All @@ -20,32 +21,56 @@ private val logger = KotlinLogging.logger {}
@Service
class OrderService(val db: OrderRepository, val synqService: SynqOrderService) {
suspend fun createOrder(payload: ApiOrderPayload): ResponseEntity<ApiOrderPayload> {
throwIfInvalidPayload(payload)

val existingOrder =
db.getByHostNameAndHostOrderId(payload.hostName, payload.orderId)
db.findByHostNameAndHostOrderId(payload.hostName, payload.orderId)
.timeout(Duration.ofSeconds(8))
.onErrorMap(TimeoutException::class.java) {
logger.error(it) {
"Timed out while fetching from WLS database. Relevant payload: $payload"
.onErrorMap {
if (it is TimeoutException) {
logger.error(it) {
"Timed out while fetching from WLS database. Relevant payload: $payload"
}
} else {
logger.error(it) { "Unexpected error for $payload" }
}
ServerErrorException("Failed to create the order in storage system", it)
ServerErrorException("Failed while checking if order already exists in the database", it)
}
.awaitSingleOrNull()

if (existingOrder != null) {
return ResponseEntity.badRequest().build()
}

synqService.createOrder(payload.toOrder().toSynqPayload())
val synqResponse = synqService.createOrder(payload.toOrder().toSynqPayload())
// If SynQ didn't throw an error, but returned something that wasn't a new order,
// then it is likely some error or edge-case
if (!synqResponse.statusCode.isSameCodeAs(HttpStatus.CREATED)) {
throw ServerErrorException("Unexpected error with SynQ", null)
}

// Return what the database saved, as it could contain changes
val order =
db.save(payload.toOrder())
.timeout(Duration.ofSeconds(6))
.onErrorMap(TimeoutException::class.java) {
logger.error { "Saving order timed out for payload: %s".format(payload.toString()) }
ServerErrorException("Failed to create the order in storage system", it)
ServerErrorException("Failed to save the order in the database", it)
}
.awaitSingle()

return ResponseEntity.status(HttpStatus.CREATED).body(order.toApiOrderPayload())
}

private fun throwIfInvalidPayload(payload: ApiOrderPayload) {
if (payload.orderId.isBlank()) {
throw ServerWebInputException("The order's orderId is required, and can not be blank")
}
if (payload.hostOrderId.isBlank()) {
throw ServerWebInputException("The order's hostOrderId is required, and can not be blank")
}
if (payload.productLine.isEmpty()) {
throw ServerWebInputException("The order must contain product lines")
}
}
}
181 changes: 181 additions & 0 deletions src/test/kotlin/no/nb/mlt/wls/order/controller/OrderControllerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package no.nb.mlt.wls.order.controller

import com.ninjasquad.springmockk.MockkBean
import io.mockk.coEvery
import io.mockk.junit5.MockKExtension
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
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.OrderLineStatus
import no.nb.mlt.wls.order.model.OrderReceiver
import no.nb.mlt.wls.order.model.OrderStatus
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.repository.OrderRepository
import no.nb.mlt.wls.order.service.OrderService
import no.nb.mlt.wls.order.service.SynqOrderService
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.test.context.support.WithMockUser
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf
import org.springframework.test.web.reactive.server.WebTestClient
import java.net.URI

@EnableTestcontainers
@TestInstance(PER_CLASS)
@AutoConfigureWebTestClient
@ExtendWith(MockKExtension::class)
@EnableMongoRepositories("no.nb.mlt.wls")
@SpringBootTest(webEnvironment = RANDOM_PORT)
class OrderControllerTest(
@Autowired val repository: OrderRepository
) {
@MockkBean
private lateinit var synqOrderService: SynqOrderService

private lateinit var webTestClient: WebTestClient

@BeforeEach
fun setUp() {
webTestClient =
WebTestClient
.bindToController(OrderController(OrderService(repository, synqOrderService)))
.configureClient()
.baseUrl("/v1/order")
.build()

populateDb()
}

@Test
@WithMockUser
fun `createOrder with valid payload creates order`() =
runTest {
coEvery {
synqOrderService.createOrder(any())
} returns ResponseEntity.created(URI.create("")).build()

webTestClient
.mutateWith(csrf())
.post()
.uri("/batch/create")
.accept(MediaType.APPLICATION_JSON)
.bodyValue(testOrderPayload)
.exchange()
.expectStatus().isCreated

val order = repository.findByHostNameAndHostOrderId(testOrderPayload.hostName, testOrderPayload.hostOrderId).awaitSingle()

assertThat(order)
.isNotNull
.extracting("callbackUrl", "status")
.containsExactly("callbackUrl", OrderStatus.NOT_STARTED)
}

@Test
@WithMockUser
fun `createOrder with duplicate payload returns OK`() {
// How to handle this? OK or error?
}

@Test
@WithMockUser
fun `createOrder payload with different data but same ID returns DB entry`() {
// How to handle this? OK or error?
}

@Test
@WithMockUser
fun `createOrder where SynQ says it's a duplicate returns OK`() {
// How to handle this? OK or error?
}

@Test
@WithMockUser
fun `createOrder handles SynQ error`() {
coEvery {
synqOrderService.createOrder(any())
} returns ResponseEntity.internalServerError().body(SynqError(9001, "Unexpected error"))

webTestClient
.mutateWith(csrf())
.post()
.uri("/batch/create")
.accept(MediaType.APPLICATION_JSON)
.bodyValue(testOrderPayload)
.exchange()
.expectStatus().is5xxServerError
}

// /////////////////////////////////////////////////////////////////////////////
// //////////////////////////////// Test Help //////////////////////////////////
// /////////////////////////////////////////////////////////////////////////////

// Will be used in most tests
private val testOrderPayload =
ApiOrderPayload(
orderId = "axiell-order-69",
hostName = HostName.AXIELL,
hostOrderId = "axiell-order-69",
status = OrderStatus.NOT_STARTED,
productLine = listOf(ProductLine("mlt-420", OrderLineStatus.NOT_STARTED)),
orderType = OrderType.LOAN,
owner = Owner.NB,
receiver =
OrderReceiver(
name = "name",
address = "address",
postalCode = "postalCode",
city = "city",
phoneNumber = "phoneNumber",
location = "location"
),
callbackUrl = "callbackUrl"
)

// Will exist in the database
private val duplicateOrderPayload =
ApiOrderPayload(
orderId = "order-123456",
hostName = HostName.AXIELL,
hostOrderId = "order-123456",
status = OrderStatus.NOT_STARTED,
productLine = listOf(ProductLine("product-123456", OrderLineStatus.NOT_STARTED)),
orderType = OrderType.LOAN,
owner = Owner.NB,
receiver =
OrderReceiver(
name = "name",
address = "address",
postalCode = "postalCode",
city = "city",
phoneNumber = "phoneNumber",
location = "location"
),
callbackUrl = "callbackUrl"
)

fun populateDb() {
// Make sure we start with clean DB instance for each test
runBlocking {
repository.deleteAll().then(repository.save(duplicateOrderPayload.toOrder())).awaitSingle()
}
}
}
108 changes: 108 additions & 0 deletions src/test/kotlin/no/nb/mlt/wls/order/model/OrderModelConversionTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package no.nb.mlt.wls.order.model

import no.nb.mlt.wls.core.data.HostName
import no.nb.mlt.wls.core.data.Owner
import no.nb.mlt.wls.core.data.synq.SynqOwner
import no.nb.mlt.wls.order.payloads.ApiOrderPayload
import no.nb.mlt.wls.order.payloads.SynqOrderPayload
import no.nb.mlt.wls.order.payloads.toApiOrderPayload
import no.nb.mlt.wls.order.payloads.toOrder
import no.nb.mlt.wls.order.payloads.toSynqPayload
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.time.LocalDateTime

class OrderModelConversionTest {
private val testApiOrderPayload =
ApiOrderPayload(
orderId = "hostOrderId",
hostName = HostName.AXIELL,
hostOrderId = "hostOrderId",
status = OrderStatus.NOT_STARTED,
productLine = listOf(ProductLine("hostProductId", OrderLineStatus.NOT_STARTED)),
orderType = OrderType.LOAN,
owner = Owner.NB,
receiver =
OrderReceiver(
name = "name",
address = "address",
postalCode = "postalCode",
city = "city",
phoneNumber = "phoneNumber",
location = "location"
),
callbackUrl = "callbackUrl"
)

private val testOrder =
Order(
hostName = HostName.AXIELL,
hostOrderId = "hostOrderId",
status = OrderStatus.NOT_STARTED,
productLine = listOf(ProductLine("hostProductId", OrderLineStatus.NOT_STARTED)),
orderType = OrderType.LOAN,
owner = Owner.NB,
receiver =
OrderReceiver(
name = "name",
address = "address",
postalCode = "postalCode",
city = "city",
phoneNumber = "phoneNumber",
location = "location"
),
callbackUrl = "callbackUrl"
)

private val testSynqOrderPayload =
SynqOrderPayload(
orderId = "hostOrderId",
orderType = SynqOrderPayload.SynqOrderType.STANDARD,
dispatchDate = LocalDateTime.now(),
orderDate = LocalDateTime.now(),
priority = 1,
owner = SynqOwner.NB,
orderLine = listOf(SynqOrderPayload.OrderLine(1, "hostProductId", 1.0))
)

@Test
fun `order converts to API payload`() {
val payload = testOrder.toApiOrderPayload()

assertThat(payload.orderId).isEqualTo(testApiOrderPayload.orderId)
assertThat(payload.hostName).isEqualTo(testApiOrderPayload.hostName)
assertThat(payload.hostOrderId).isEqualTo(testApiOrderPayload.hostOrderId)
assertThat(payload.status).isEqualTo(testApiOrderPayload.status)
assertThat(payload.productLine).isEqualTo(testApiOrderPayload.productLine)
assertThat(payload.orderType).isEqualTo(testApiOrderPayload.orderType)
assertThat(payload.owner).isEqualTo(testApiOrderPayload.owner)
assertThat(payload.receiver).isEqualTo(testApiOrderPayload.receiver)
assertThat(payload.callbackUrl).isEqualTo(testApiOrderPayload.callbackUrl)
}

@Test
fun `order converts to SynQ payload`() {
val synqPayload = testOrder.toSynqPayload()

// Dates are not compared as they are generated in the function
assertThat(synqPayload.orderId).isEqualTo(testSynqOrderPayload.orderId)
assertThat(synqPayload.orderType).isEqualTo(testSynqOrderPayload.orderType)
assertThat(synqPayload.priority).isEqualTo(testSynqOrderPayload.priority)
assertThat(synqPayload.owner).isEqualTo(testSynqOrderPayload.owner)
assertThat(synqPayload.orderLine).isEqualTo(testSynqOrderPayload.orderLine)
}

@Test
fun `API payload converts to order`() {
val order = testApiOrderPayload.toOrder()

assertThat(order.hostName).isEqualTo(testOrder.hostName)
assertThat(order.hostOrderId).isEqualTo(testOrder.hostOrderId)
assertThat(order.status).isEqualTo(testOrder.status)
assertThat(order.productLine).isEqualTo(testOrder.productLine)
assertThat(order.orderType).isEqualTo(testOrder.orderType)
assertThat(order.owner).isEqualTo(testOrder.owner)
assertThat(order.receiver).isEqualTo(testOrder.receiver)
assertThat(order.callbackUrl).isEqualTo(testOrder.callbackUrl)
}
}
Loading

0 comments on commit 31328bf

Please sign in to comment.