Skip to content

Commit

Permalink
Post use cases + more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanfallet committed Dec 9, 2023
1 parent db5c1f9 commit 45a87cb
Show file tree
Hide file tree
Showing 11 changed files with 415 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,4 @@ object Posts : Table() {
row.getOrNull(likesIn)?.let { it >= 1L }
)

fun delete(id: String) {
LikesInPosts.deleteWhere {
Op.build { postId eq id }
}
Posts.deleteWhere {
Op.build { Posts.id eq id }
}
Posts.select {
repliedToId eq id or (repostOfId eq id)
}.forEach {
delete(it[Posts.id])
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,26 @@ import me.nathanfallet.extopy.controllers.users.UsersController
import me.nathanfallet.extopy.controllers.users.UsersRouter
import me.nathanfallet.extopy.database.Database
import me.nathanfallet.extopy.database.application.DatabaseCodesInEmailsRepository
import me.nathanfallet.extopy.database.posts.DatabasePostsRepository
import me.nathanfallet.extopy.database.users.DatabaseUsersRepository
import me.nathanfallet.extopy.models.auth.LoginPayload
import me.nathanfallet.extopy.models.auth.RegisterCodePayload
import me.nathanfallet.extopy.models.auth.RegisterPayload
import me.nathanfallet.extopy.models.posts.Post
import me.nathanfallet.extopy.models.posts.PostPayload
import me.nathanfallet.extopy.models.users.CreateUserPayload
import me.nathanfallet.extopy.models.users.UpdateUserPayload
import me.nathanfallet.extopy.models.users.User
import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository
import me.nathanfallet.extopy.repositories.posts.IPostsRepository
import me.nathanfallet.extopy.repositories.users.IUsersRepository
import me.nathanfallet.extopy.services.emails.EmailsService
import me.nathanfallet.extopy.services.emails.IEmailsService
import me.nathanfallet.extopy.usecases.application.SendEmailUseCase
import me.nathanfallet.extopy.usecases.auth.*
import me.nathanfallet.extopy.usecases.posts.CreatePostUseCase
import me.nathanfallet.extopy.usecases.posts.DeletePostUseCase
import me.nathanfallet.extopy.usecases.posts.UpdatePostUseCase
import me.nathanfallet.extopy.usecases.users.CreateUserUseCase
import me.nathanfallet.extopy.usecases.users.GetUserForCallUseCase
import me.nathanfallet.extopy.usecases.users.UpdateUserUseCase
Expand All @@ -37,6 +44,8 @@ import me.nathanfallet.ktorx.usecases.users.RequireUserForCallUseCase
import me.nathanfallet.usecases.emails.ISendEmailUseCase
import me.nathanfallet.usecases.localization.ITranslateUseCase
import me.nathanfallet.usecases.models.create.ICreateModelSuspendUseCase
import me.nathanfallet.usecases.models.create.context.ICreateModelWithContextSuspendUseCase
import me.nathanfallet.usecases.models.delete.IDeleteModelSuspendUseCase
import me.nathanfallet.usecases.models.get.context.GetModelWithContextFromRepositorySuspendUseCase
import me.nathanfallet.usecases.models.get.context.IGetModelWithContextSuspendUseCase
import me.nathanfallet.usecases.models.update.IUpdateModelSuspendUseCase
Expand Down Expand Up @@ -69,6 +78,7 @@ fun Application.configureKoin() {
val repositoryModule = module {
single<ICodesInEmailsRepository> { DatabaseCodesInEmailsRepository(get()) }
single<IUsersRepository> { DatabaseUsersRepository(get()) }
single<IPostsRepository> { DatabasePostsRepository(get()) }
}
val useCaseModule = module {
// Application
Expand Down Expand Up @@ -109,6 +119,20 @@ fun Application.configureKoin() {
single<IUpdateModelSuspendUseCase<User, String, UpdateUserPayload>>(named<User>()) {
UpdateUserUseCase(get(), get())
}

// Posts
single<IGetModelWithContextSuspendUseCase<Post, String>>(named<Post>()) {
GetModelWithContextFromRepositorySuspendUseCase(get<IPostsRepository>())

Check warning on line 125 in extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt

View check run for this annotation

Codecov / codecov/patch

extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt#L125

Added line #L125 was not covered by tests
}
single<ICreateModelWithContextSuspendUseCase<Post, PostPayload>>(named<Post>()) {
CreatePostUseCase(get())

Check warning on line 128 in extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt

View check run for this annotation

Codecov / codecov/patch

extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt#L128

Added line #L128 was not covered by tests
}
single<IUpdateModelSuspendUseCase<Post, String, PostPayload>>(named<Post>()) {
UpdatePostUseCase(get())

Check warning on line 131 in extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt

View check run for this annotation

Codecov / codecov/patch

extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt#L131

Added line #L131 was not covered by tests
}
single<IDeleteModelSuspendUseCase<Post, String>>(named<Post>()) {
DeletePostUseCase(get())

Check warning on line 134 in extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt

View check run for this annotation

Codecov / codecov/patch

extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt#L134

Added line #L134 was not covered by tests
}
}
val controllerModule = module {
// Auth
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package me.nathanfallet.extopy.usecases.posts

import io.ktor.http.*
import me.nathanfallet.extopy.models.posts.Post
import me.nathanfallet.extopy.models.posts.PostPayload
import me.nathanfallet.extopy.repositories.posts.IPostsRepository
import me.nathanfallet.ktorx.models.exceptions.ControllerException
import me.nathanfallet.usecases.context.IContext
import me.nathanfallet.usecases.models.create.context.ICreateModelWithContextSuspendUseCase

class CreatePostUseCase(
private val repository: IPostsRepository,
) : ICreateModelWithContextSuspendUseCase<Post, PostPayload> {

override suspend fun invoke(input1: PostPayload, input2: IContext): Post? {
if (input1.body.isBlank() && input1.repostOfId == null) throw ControllerException(
HttpStatusCode.BadRequest,
"posts_body_empty"
)
if (input1.repostOfId != null && input1.repliedToId != null) throw ControllerException(
HttpStatusCode.BadRequest,
"posts_can_only_one_in_reply_or_repost"

Check warning on line 22 in extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/posts/CreatePostUseCase.kt

View check run for this annotation

Codecov / codecov/patch

extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/posts/CreatePostUseCase.kt#L21-L22

Added lines #L21 - L22 were not covered by tests
)
input1.repliedToId?.let { repliedToId ->
if (repository.get(repliedToId) == null) throw ControllerException(
HttpStatusCode.BadRequest,
"posts_replied_to_not_found"
)
}
input1.repostOfId?.let { repostOfId ->
if (repository.get(repostOfId) == null) throw ControllerException(
HttpStatusCode.BadRequest,
"posts_repost_of_not_found"
)
}
return repository.create(input1, input2)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package me.nathanfallet.extopy.usecases.posts

import me.nathanfallet.extopy.models.posts.Post
import me.nathanfallet.extopy.repositories.posts.IPostsRepository
import me.nathanfallet.usecases.models.delete.IDeleteModelSuspendUseCase

class DeletePostUseCase(
private val repository: IPostsRepository,
) : IDeleteModelSuspendUseCase<Post, String> {

override suspend fun invoke(input: String): Boolean {
return repository.delete(input)
/*
// TODO: Delete related data
LikesInPosts.deleteWhere {
Op.build { postId eq id }
}
Posts.deleteWhere {
Op.build { Posts.id eq id }
}
Posts.select {
repliedToId eq id or (repostOfId eq id)
}.forEach {
delete(it[Posts.id])
}
*/
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.nathanfallet.extopy.usecases.posts

import io.ktor.http.*
import me.nathanfallet.extopy.models.posts.Post
import me.nathanfallet.extopy.models.posts.PostPayload
import me.nathanfallet.extopy.repositories.posts.IPostsRepository
import me.nathanfallet.ktorx.models.exceptions.ControllerException
import me.nathanfallet.usecases.models.update.IUpdateModelSuspendUseCase

class UpdatePostUseCase(
private val repository: IPostsRepository,
) : IUpdateModelSuspendUseCase<Post, String, PostPayload> {

override suspend fun invoke(input1: String, input2: PostPayload): Post? {
if (input2.body.isBlank()) throw ControllerException(
HttpStatusCode.BadRequest,
"posts_body_empty"
)
return if (repository.update(input1, input2)) repository.get(input1)
else null
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ class UpdateUserUseCase(

override suspend fun invoke(input1: String, input2: UpdateUserPayload): User? {
input2.username?.let {
repository.getForUsernameOrEmail(it, false)?.let {
repository.getForUsernameOrEmail(it, false)?.takeIf { result ->
result.id != input1
}?.let {
throw ControllerException(
HttpStatusCode.BadRequest,
"auth_register_username_taken"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package me.nathanfallet.extopy.usecases.posts

import io.ktor.http.*
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import me.nathanfallet.extopy.models.posts.Post
import me.nathanfallet.extopy.models.posts.PostPayload
import me.nathanfallet.extopy.models.users.UserContext
import me.nathanfallet.extopy.repositories.posts.IPostsRepository
import me.nathanfallet.ktorx.models.exceptions.ControllerException
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class CreatePostUseCaseTest {

@Test
fun testInvokeWithBody() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = CreatePostUseCase(repository)
val post = mockk<Post>()
val payload = PostPayload("body")
val context = mockk<UserContext>()
coEvery { repository.create(payload, context) } returns post
val result = useCase(payload, context)
assertEquals(post, result)
}

@Test
fun testInvokeWithEmpty() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = CreatePostUseCase(repository)
val post = mockk<Post>()
val payload = PostPayload("")
val context = mockk<UserContext>()
coEvery { repository.create(payload, context) } returns post
val exception = assertFailsWith<ControllerException> {
useCase(payload, context)
}
assertEquals(HttpStatusCode.BadRequest, exception.code)
assertEquals("posts_body_empty", exception.key)
}

@Test
fun testInvokeReply() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = CreatePostUseCase(repository)
val post = mockk<Post>()
val payload = PostPayload("body", repliedToId = "replyId")
val context = mockk<UserContext>()
coEvery { repository.get("replyId") } returns mockk()
coEvery { repository.create(payload, context) } returns post
val result = useCase(payload, context)
assertEquals(post, result)
}

@Test
fun testInvokeReplyNotExists() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = CreatePostUseCase(repository)
val post = mockk<Post>()
val payload = PostPayload("body", repliedToId = "replyId")
val context = mockk<UserContext>()
coEvery { repository.get("replyId") } returns null
coEvery { repository.create(payload, context) } returns post
val exception = assertFailsWith<ControllerException> {
useCase(payload, context)
}
assertEquals(HttpStatusCode.BadRequest, exception.code)
assertEquals("posts_replied_to_not_found", exception.key)
}

@Test
fun testInvokeReplyNoBody() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = CreatePostUseCase(repository)
val post = mockk<Post>()
val payload = PostPayload("", repliedToId = "replyId")
val context = mockk<UserContext>()
coEvery { repository.get("replyId") } returns mockk()
coEvery { repository.create(payload, context) } returns post
val exception = assertFailsWith<ControllerException> {
useCase(payload, context)
}
assertEquals(HttpStatusCode.BadRequest, exception.code)
assertEquals("posts_body_empty", exception.key)
}

@Test
fun testInvokeRepost() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = CreatePostUseCase(repository)
val post = mockk<Post>()
val payload = PostPayload("", repostOfId = "repostId")
val context = mockk<UserContext>()
coEvery { repository.get("repostId") } returns mockk()
coEvery { repository.create(payload, context) } returns post
val result = useCase(payload, context)
assertEquals(post, result)
}

@Test
fun testInvokeRepostWithBody() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = CreatePostUseCase(repository)
val post = mockk<Post>()
val payload = PostPayload("body", repostOfId = "repostId")
val context = mockk<UserContext>()
coEvery { repository.get("repostId") } returns mockk()
coEvery { repository.create(payload, context) } returns post
val result = useCase(payload, context)
assertEquals(post, result)
}

@Test
fun testInvokeRepostNotExists() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = CreatePostUseCase(repository)
val post = mockk<Post>()
val payload = PostPayload("", repostOfId = "repostId")
val context = mockk<UserContext>()
coEvery { repository.get("repostId") } returns null
coEvery { repository.create(payload, context) } returns post
val exception = assertFailsWith<ControllerException> {
useCase(payload, context)
}
assertEquals(HttpStatusCode.BadRequest, exception.code)
assertEquals("posts_repost_of_not_found", exception.key)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package me.nathanfallet.extopy.usecases.posts

import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import me.nathanfallet.extopy.repositories.posts.IPostsRepository
import kotlin.test.Test
import kotlin.test.assertEquals

class DeletePostUseCaseTest {

@Test
fun testInvoke() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = DeletePostUseCase(repository)
coEvery { repository.delete("id") } returns true
assertEquals(true, useCase("id"))
}

@Test
fun testInvokeFails() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = DeletePostUseCase(repository)
coEvery { repository.delete("id") } returns false
assertEquals(false, useCase("id"))
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package me.nathanfallet.extopy.usecases.posts

import io.ktor.http.*
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import me.nathanfallet.extopy.models.posts.Post
import me.nathanfallet.extopy.models.posts.PostPayload
import me.nathanfallet.extopy.repositories.posts.IPostsRepository
import me.nathanfallet.ktorx.models.exceptions.ControllerException
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class UpdatePostUseCaseTest {

@Test
fun testInvoke() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = UpdatePostUseCase(repository)
val payload = PostPayload("body")
val post = mockk<Post>()
coEvery { repository.update("id", payload) } returns true
coEvery { repository.get("id") } returns post
assertEquals(post, useCase("id", payload))
}

@Test
fun testInvokeFails() = runBlocking {
val repository = mockk<IPostsRepository>()
val useCase = UpdatePostUseCase(repository)
val payload = PostPayload("body")
coEvery { repository.update("id", payload) } returns false
assertEquals(null, useCase("id", payload))
}

@Test
fun testInvokeNoBody() = runBlocking {
val useCase = UpdatePostUseCase(mockk())
val payload = PostPayload("")
val exception = assertFailsWith<ControllerException> {
useCase("id", payload)
}
assertEquals(HttpStatusCode.BadRequest, exception.code)
assertEquals("posts_body_empty", exception.key)
}

}
Loading

0 comments on commit 45a87cb

Please sign in to comment.