Skip to content

Commit

Permalink
test: otp interactor verify otp code feature
Browse files Browse the repository at this point in the history
  • Loading branch information
j0a0m4 committed Jun 19, 2023
1 parent e67162a commit 583f60d
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import io.j0a0m4.portsandadapters.domain.model.SendMethod
import io.j0a0m4.portsandadapters.domain.model.contact
import io.j0a0m4.portsandadapters.domain.usecases.*
import io.kotest.core.spec.style.BehaviorSpec
import io.mockk.every
import io.mockk.mockk
import io.mockk.*
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
Expand All @@ -28,7 +27,7 @@ internal class ContactVerificationApiTest() : BehaviorSpec() {
@TestConfiguration
class ApiTestConfig {
@Bean
fun otpPort() = mockk<OtpUseCases>()
fun otpPort() = mockk<OtpUseCases>(relaxed = true)

@Bean
fun contactPort() = mockk<ContactUseCases>()
Expand All @@ -44,8 +43,13 @@ internal class ContactVerificationApiTest() : BehaviorSpec() {
private lateinit var contactPort: ContactUseCases

init {
afterTest {
clearMocks(otpPort, contactPort)
}

Given("a POST request to /api/contact") {
val request = client.post().uri("/api/contact")

When("the payload is processable") {
val payload = AddContactRequest(
"Thom Yorke",
Expand All @@ -54,6 +58,7 @@ internal class ContactVerificationApiTest() : BehaviorSpec() {
)
val contactId = UUID.randomUUID()
every { contactPort.add(any()) } returns Result.success(contactId)

Then("it returns status <201 CREATED> and the location of the resource in the header") {
request.bodyValue(payload)
.accept(APPLICATION_JSON)
Expand All @@ -67,13 +72,15 @@ internal class ContactVerificationApiTest() : BehaviorSpec() {
Given("a GET request to /api/contact/{id}") {
val contactId = UUID.randomUUID()
val request = client.get().uri("/api/contact/$contactId")

When("the contact id is found") {
every { contactPort.findBy(contactId) } returns
Result.success(contact {
it.name = "Jonny Greenwood"
it.email = "[email protected]"
it.phone = "+44217742329"
})

Then("it returns status <200 OK> and payload in body") {
request.accept(APPLICATION_JSON)
.exchange()
Expand All @@ -88,9 +95,11 @@ internal class ContactVerificationApiTest() : BehaviorSpec() {
.jsonPath("$.phone").isEqualTo("+44217742329")
}
}

When("the contact id is not found") {
every { contactPort.findBy(contactId) } returns
Result.failure(NoSuchUUID(contactId))

Then("it returns status <404 NOT_FOUND> and empty body") {
request.accept(APPLICATION_JSON)
.exchange()
Expand All @@ -104,8 +113,9 @@ internal class ContactVerificationApiTest() : BehaviorSpec() {
val sendMethod = SendMethod.Phone
val uri = "/api/contact/$contactId/otp?sendMethod=$sendMethod"
val request = client.post().uri(uri)

When("send method query param is present") {
every { otpPort.send(contactId, sendMethod) } answers { }

Then("it should return status <201 ACCEPTED> and empty body") {
request.accept(APPLICATION_JSON)
.exchange()
Expand All @@ -120,9 +130,11 @@ internal class ContactVerificationApiTest() : BehaviorSpec() {
val otp = 423891
val request = client.patch().uri("/api/contact/$contactId")
.bodyValue(PatchContactRequest(sendMethod, otp))

When("it verifies given OTP matches with stored OTP value") {
every { otpPort.verify(contactId, sendMethod, otp) } returns
Result.success(Unit)

Then("it should return status <204 NO_CONTENT> and empty body") {
request.accept(APPLICATION_JSON)
.exchange()
Expand All @@ -133,6 +145,7 @@ internal class ContactVerificationApiTest() : BehaviorSpec() {
When("it verifies given OTP does not match with stored OTP value") {
every { otpPort.verify(contactId, sendMethod, otp) } returns
Result.failure(OtpMismatch())

Then("it should return status <422 UNPROCESSABLE_ENTITY> and empty body") {
request.accept(APPLICATION_JSON)
.exchange()
Expand All @@ -143,6 +156,7 @@ internal class ContactVerificationApiTest() : BehaviorSpec() {
When("given OTP key is not found") {
every { otpPort.verify(contactId, sendMethod, otp) } returns
Result.failure(NoSuchOtpKey(contactId to sendMethod))

Then("it should return status <404 NOT_FOUND> and empty body") {
request.accept(APPLICATION_JSON)
.exchange()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import io.kotest.data.forAll
import io.kotest.data.row
import io.kotest.matchers.result.shouldBeFailure
import io.kotest.matchers.result.shouldBeSuccess
import io.mockk.every
import io.mockk.mockk
import io.mockk.*
import org.junit.jupiter.api.assertDoesNotThrow
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
Expand All @@ -19,9 +18,9 @@ import org.springframework.context.annotation.Bean
@SpringBootTest
class ContactInteractorTest : FeatureSpec() {
@TestConfiguration
class ApiTestConfig {
class ContactInteractorTestConfig {
@Bean
fun storage() = mockk<ContactStorage>()
fun storage() = mockk<ContactStorage>(relaxed = true)
}

@Autowired
Expand All @@ -30,26 +29,31 @@ class ContactInteractorTest : FeatureSpec() {
@Autowired
private lateinit var storage: ContactStorage

private val contact = contact {
it.name = "Dustin Kensrue"
it.email = "[email protected]"
it.phone = "+1923543194"
}

init {
val contact = contact {
it.name = "Dustin Kensrue"
it.email = "[email protected]"
it.phone = "+1923543194"
afterTest {
clearMocks(storage)
}

feature("add contact") {
scenario("should persist to storage") {
every { storage.persist(contact) } answers {}
}
scenario("and should return new UUID") {
scenario("should persist to storage and return new UUID") {
assertDoesNotThrow {
interactor.add(contact) shouldBeSuccess contact.id
}
verify(exactly = 1) {
storage persist contact
}
}
}

feature("update contact status by id") {
every { storage.update(any()) } answers { Result.success(contact) }

scenario("should persist to storage") {
forAll(
row(VerifiedStatus.Verified),
Expand All @@ -60,18 +64,33 @@ class ContactInteractorTest : FeatureSpec() {
interactor.updateStatus(contact.id, status)
}
}

verify(exactly = 3) {
storage update (any())
}
}
}

feature("find contact by id") {
scenario("should find contact") {
every { storage.findBy(contact.id) } answers { Result.success(contact) }

interactor.findBy(contact.id) shouldBeSuccess contact

verify(exactly = 1) {
storage.findBy(contact.id)
}
}
scenario("id is not found") {
val exception = NoSuchUUID(contact.id)

every { storage.findBy(contact.id) } answers { Result.failure(exception) }

interactor.findBy(contact.id) shouldBeFailure exception

verify(exactly = 1) {
storage.findBy(contact.id)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.j0a0m4.portsandadapters.domain.usecases

import io.j0a0m4.portsandadapters.domain.model.*
import io.kotest.core.spec.style.FeatureSpec
import io.kotest.matchers.result.shouldBeSuccess
import io.mockk.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.core.task.AsyncTaskExecutor

@SpringBootTest
class OtpInteractorTest : FeatureSpec() {
@TestConfiguration
class OtpInteractorTestConfig {
@Bean
fun storage() = mockk<OtpStorage>(relaxed = true)

@Bean
fun sender() = mockk<OtpSender>(relaxed = true)

@Bean
fun contactPort() = mockk<ContactUseCases>(relaxed = true)

@Bean
fun async() = mockk<AsyncTaskExecutor>(relaxed = true)
}

@Autowired
private lateinit var storage: OtpStorage

@Autowired
private lateinit var sender: OtpSender

@Autowired
private lateinit var contactPort: ContactUseCases

@Autowired
private lateinit var async: AsyncTaskExecutor

@Autowired
private lateinit var interactor: OtpInteractor

private val contact = contact {
it.name = "Apashe"
it.email = "[email protected]"
it.phone = "+12505550199"
}

init {
afterTest {
clearMocks(contactPort, storage, sender, async)
}

feature("verify OTP code") {
val sendMethod = SendMethod.Phone
val otp: VerificationCode = 835214

scenario("provided OTP matches record") {
every { storage.retrieveOtp(contact.id, sendMethod) } returns
Result.success(otp)

interactor.verify(contact.id, sendMethod, otp) shouldBeSuccess Unit

verifyOrder {
contactPort.verified(contact.id)
storage.invalidate(contact.id, sendMethod)
}
}

scenario("provided OTP does not match record") {
every { storage.retrieveOtp(contact.id, sendMethod) } returns
Result.failure(OtpMismatch())

interactor.verify(contact.id, sendMethod, otp)

verify(inverse = true) {
contactPort.verified(contact.id)
storage.invalidate(contact.id, sendMethod)
}
}
}
}
}

0 comments on commit 583f60d

Please sign in to comment.