-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: otp interactor verify otp code feature
- Loading branch information
Showing
3 changed files
with
134 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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>() | ||
|
@@ -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", | ||
|
@@ -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) | ||
|
@@ -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() | ||
|
@@ -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() | ||
|
@@ -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() | ||
|
@@ -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() | ||
|
@@ -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() | ||
|
@@ -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() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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), | ||
|
@@ -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) | ||
} | ||
} | ||
} | ||
} | ||
|
85 changes: 85 additions & 0 deletions
85
src/test/kotlin/io/j0a0m4/portsandadapters/domain/usecases/OtpInteractorTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} | ||
} |