From b0e12010872f85a4463da06c884e3911ac08a2ef Mon Sep 17 00:00:00 2001 From: Joe Barnett Date: Mon, 14 Sep 2020 13:22:13 -0700 Subject: [PATCH] Bump to leakycauldron 1.14.x Have Loaders use suspend functions instead of CompletableFutures which integrates with the scope cancellation work in LC 1.14.x Note that due to the way `java-dataloader` works, the model fetcher functions can't be async/suspend functions, as we need the dataloader.load() invocation to happen synchronously to ensure they preceed any dispatch() calls; otherwise queries can hang forever, similar to: https://github.com/graphql-java/java-dataloader/issues/54 Add tests to validate the suspend function implemented loaders and existing model fetchers work properly. --- pom.xml | 4 +- server/pom.xml | 4 - .../dataloaders/BatchQuestionLoader.kt | 16 +- .../graphql/dataloaders/BatchUserLoader.kt | 16 +- .../graphql/dataloaders/BulkInstanceLoader.kt | 16 +- .../dataloaders/CoroutineMappedBatchLoader.kt | 23 +++ .../dataloaders/QuestionResponseLoader.kt | 27 +-- .../dataloaders/ResponseGradeLoader.kt | 16 +- .../graphql/dataloaders/UserGradeLoader.kt | 16 +- .../server/graphql/models/ApiResponse.kt | 5 +- .../joe/quizzy/server/graphql/MutationTest.kt | 6 +- .../dataloaders/BatchQuestionLoaderTest.kt | 35 ++++ .../dataloaders/BatchUserLoaderTest.kt | 32 ++++ .../dataloaders/BulkInstanceLoaderTest.kt | 32 ++++ .../CoroutineMappedBatchLoaderTest.kt | 62 +++++++ .../DataLoaderRegistryFactoryProviderTest.kt | 33 +++- .../dataloaders/QuestionResponseLoaderTest.kt | 60 +++++++ .../dataloaders/ResponseGradeLoaderTest.kt | 32 ++++ .../dataloaders/UserGradeLoaderTest.kt | 35 ++++ .../server/graphql/models/ApiQuestionTest.kt | 126 ++++++++++++++ .../server/graphql/models/ApiResponseTest.kt | 160 ++++++++++++++++++ .../server/graphql/models/ApiUserTest.kt | 86 ++++++++++ 22 files changed, 751 insertions(+), 91 deletions(-) create mode 100644 server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/CoroutineMappedBatchLoader.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchQuestionLoaderTest.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchUserLoaderTest.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BulkInstanceLoaderTest.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/CoroutineMappedBatchLoaderTest.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/QuestionResponseLoaderTest.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/ResponseGradeLoaderTest.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/UserGradeLoaderTest.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiQuestionTest.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiResponseTest.kt create mode 100644 server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiUserTest.kt diff --git a/pom.xml b/pom.xml index a17ca8d8..06a85a6c 100644 --- a/pom.xml +++ b/pom.xml @@ -9,11 +9,11 @@ com.trib3 parent-pom - [1.12.1,1.13-SNAPSHOT) + [1.14.1,1.15-SNAPSHOT) - [1.12.1,1.13-SNAPSHOT) + [1.14.1,1.15-SNAPSHOT) diff --git a/server/pom.xml b/server/pom.xml index 90465f4c..44e82602 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -146,10 +146,6 @@ org.jetbrains.kotlinx kotlinx-coroutines-jdk8 - - org.jetbrains.kotlinx - kotlinx-coroutines-slf4j - com.graphql-java java-dataloader diff --git a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchQuestionLoader.kt b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchQuestionLoader.kt index 6ba21c2b..14c8bf60 100644 --- a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchQuestionLoader.kt +++ b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchQuestionLoader.kt @@ -2,23 +2,15 @@ package com.joe.quizzy.server.graphql.dataloaders import com.joe.quizzy.api.models.Question import com.joe.quizzy.persistence.api.QuestionDAO -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.future.future -import kotlinx.coroutines.slf4j.MDCContext -import org.dataloader.MappedBatchLoader +import org.dataloader.BatchLoaderEnvironment import java.util.UUID -import java.util.concurrent.CompletionStage /** * Batch load Questions by ID */ class BatchQuestionLoader(private val questionDAO: QuestionDAO) : - MappedBatchLoader { - override fun load(keys: Set): CompletionStage> { - return CoroutineScope(Dispatchers.IO + MDCContext()) - .future { - questionDAO.get(keys.toList()).associateBy { it.id!! } - } + CoroutineMappedBatchLoader() { + override suspend fun loadSuspend(keys: Set, environment: BatchLoaderEnvironment): Map { + return questionDAO.get(keys.toList()).associateBy { it.id!! } } } diff --git a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchUserLoader.kt b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchUserLoader.kt index 02d9a5e5..247befb2 100644 --- a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchUserLoader.kt +++ b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchUserLoader.kt @@ -2,23 +2,15 @@ package com.joe.quizzy.server.graphql.dataloaders import com.joe.quizzy.api.models.User import com.joe.quizzy.persistence.api.UserDAO -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.future.future -import kotlinx.coroutines.slf4j.MDCContext -import org.dataloader.MappedBatchLoader +import org.dataloader.BatchLoaderEnvironment import java.util.UUID -import java.util.concurrent.CompletionStage /** * Batch load Users by ID */ class BatchUserLoader(private val userDAO: UserDAO) : - MappedBatchLoader { - override fun load(keys: Set): CompletionStage> { - return CoroutineScope(Dispatchers.IO + MDCContext()) - .future { - userDAO.get(keys.toList()).associateBy { it.id!! } - } + CoroutineMappedBatchLoader() { + override suspend fun loadSuspend(keys: Set, environment: BatchLoaderEnvironment): Map { + return userDAO.get(keys.toList()).associateBy { it.id!! } } } diff --git a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BulkInstanceLoader.kt b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BulkInstanceLoader.kt index cb16af9a..5730d013 100644 --- a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BulkInstanceLoader.kt +++ b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/BulkInstanceLoader.kt @@ -2,23 +2,15 @@ package com.joe.quizzy.server.graphql.dataloaders import com.joe.quizzy.api.models.Instance import com.joe.quizzy.persistence.api.InstanceDAO -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.future.future -import kotlinx.coroutines.slf4j.MDCContext -import org.dataloader.MappedBatchLoader +import org.dataloader.BatchLoaderEnvironment import java.util.UUID -import java.util.concurrent.CompletionStage /** * Batch load Instance by Id */ class BulkInstanceLoader(private val instanceDAO: InstanceDAO) : - MappedBatchLoader { - override fun load(keys: Set): CompletionStage> { - return CoroutineScope(Dispatchers.IO + MDCContext()) - .future { - instanceDAO.get(keys.toList()).associateBy { it.id!! } - } + CoroutineMappedBatchLoader() { + override suspend fun loadSuspend(keys: Set, environment: BatchLoaderEnvironment): Map { + return instanceDAO.get(keys.toList()).associateBy { it.id!! } } } diff --git a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/CoroutineMappedBatchLoader.kt b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/CoroutineMappedBatchLoader.kt new file mode 100644 index 00000000..77dd4ee1 --- /dev/null +++ b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/CoroutineMappedBatchLoader.kt @@ -0,0 +1,23 @@ +package com.joe.quizzy.server.graphql.dataloaders + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.future.future +import org.dataloader.BatchLoaderEnvironment +import org.dataloader.MappedBatchLoaderWithContext +import java.util.concurrent.CompletionStage + +abstract class CoroutineMappedBatchLoader : MappedBatchLoaderWithContext { + + private fun scope(environment: BatchLoaderEnvironment): CoroutineScope { + return (environment.getContext() as? CoroutineScope) ?: GlobalScope + } + + abstract suspend fun loadSuspend(keys: Set, environment: BatchLoaderEnvironment): Map + + override fun load(keys: Set, environment: BatchLoaderEnvironment): CompletionStage> { + return scope(environment).future { + loadSuspend(keys, environment) + } + } +} diff --git a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/QuestionResponseLoader.kt b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/QuestionResponseLoader.kt index e91d386e..460a9d5b 100644 --- a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/QuestionResponseLoader.kt +++ b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/QuestionResponseLoader.kt @@ -4,32 +4,23 @@ import com.joe.quizzy.api.models.Response import com.joe.quizzy.persistence.api.ResponseDAO import com.joe.quizzy.server.auth.UserPrincipal import com.trib3.graphql.resources.GraphQLResourceContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.future.future -import kotlinx.coroutines.slf4j.MDCContext import org.dataloader.BatchLoaderEnvironment -import org.dataloader.MappedBatchLoaderWithContext import java.util.UUID -import java.util.concurrent.CompletionStage /** * Batch load Question ID -> Response for context User */ class QuestionResponseLoader(private val responseDAO: ResponseDAO) : - MappedBatchLoaderWithContext { - override fun load( + CoroutineMappedBatchLoader() { + override suspend fun loadSuspend( keys: Set, environment: BatchLoaderEnvironment - ): CompletionStage> { - return CoroutineScope(Dispatchers.IO + MDCContext()) - .future { - val principal = environment.getContext().principal - if (principal is UserPrincipal) { - responseDAO.byUserQuestions(principal.user.id!!, keys.toList()) - } else { - emptyMap() - } - } + ): Map { + val principal = environment.getContext().principal + return if (principal is UserPrincipal) { + responseDAO.byUserQuestions(principal.user.id!!, keys.toList()) + } else { + emptyMap() + } } } diff --git a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/ResponseGradeLoader.kt b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/ResponseGradeLoader.kt index cdc0d55e..4384999e 100644 --- a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/ResponseGradeLoader.kt +++ b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/ResponseGradeLoader.kt @@ -2,23 +2,15 @@ package com.joe.quizzy.server.graphql.dataloaders import com.joe.quizzy.api.models.Grade import com.joe.quizzy.persistence.api.GradeDAO -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.future.future -import kotlinx.coroutines.slf4j.MDCContext -import org.dataloader.MappedBatchLoader +import org.dataloader.BatchLoaderEnvironment import java.util.UUID -import java.util.concurrent.CompletionStage /** * Batch load Response ID -> Grade */ class ResponseGradeLoader(private val gradeDAO: GradeDAO) : - MappedBatchLoader { - override fun load(keys: Set): CompletionStage> { - return CoroutineScope(Dispatchers.IO + MDCContext()) - .future { - gradeDAO.forResponses(keys.toList()) - } + CoroutineMappedBatchLoader() { + override suspend fun loadSuspend(keys: Set, environment: BatchLoaderEnvironment): Map { + return gradeDAO.forResponses(keys.toList()) } } diff --git a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/UserGradeLoader.kt b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/UserGradeLoader.kt index 46880941..5e642421 100644 --- a/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/UserGradeLoader.kt +++ b/server/src/main/kotlin/com/joe/quizzy/server/graphql/dataloaders/UserGradeLoader.kt @@ -2,23 +2,15 @@ package com.joe.quizzy.server.graphql.dataloaders import com.joe.quizzy.api.models.Grade import com.joe.quizzy.persistence.api.GradeDAO -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.future.future -import kotlinx.coroutines.slf4j.MDCContext -import org.dataloader.MappedBatchLoader +import org.dataloader.BatchLoaderEnvironment import java.util.UUID -import java.util.concurrent.CompletionStage /** * Batch load User ID -> List */ class UserGradeLoader(private val gradeDAO: GradeDAO) : - MappedBatchLoader> { - override fun load(userIds: Set): CompletionStage>> { - return CoroutineScope(Dispatchers.IO + MDCContext()) - .future { - gradeDAO.forUsers(userIds.toList()) - } + CoroutineMappedBatchLoader>() { + override suspend fun loadSuspend(keys: Set, environment: BatchLoaderEnvironment): Map> { + return gradeDAO.forUsers(keys.toList()) } } diff --git a/server/src/main/kotlin/com/joe/quizzy/server/graphql/models/ApiResponse.kt b/server/src/main/kotlin/com/joe/quizzy/server/graphql/models/ApiResponse.kt index 229d557c..dade18ba 100644 --- a/server/src/main/kotlin/com/joe/quizzy/server/graphql/models/ApiResponse.kt +++ b/server/src/main/kotlin/com/joe/quizzy/server/graphql/models/ApiResponse.kt @@ -33,7 +33,7 @@ data class ApiResponse( val principal = context.principal if (principal is UserPrincipal) { return dfe.getDataLoader("batchusers").load(userId).thenApply { - ApiUser(it) + it?.let(::ApiUser) } } return CompletableFuture.completedFuture(null) @@ -42,7 +42,8 @@ data class ApiResponse( fun question(context: GraphQLResourceContext, dfe: DataFetchingEnvironment): CompletableFuture { val principal = context.principal if (principal is UserPrincipal) { - return dfe.getDataLoader("batchquestions").load(questionId).thenApply { ApiQuestion(it) } + return dfe.getDataLoader("batchquestions").load(questionId) + .thenApply { it?.let(::ApiQuestion) } } return CompletableFuture.completedFuture(null) } diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/MutationTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/MutationTest.kt index 4e292a5f..9177f907 100644 --- a/server/src/test/kotlin/com/joe/quizzy/server/graphql/MutationTest.kt +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/MutationTest.kt @@ -268,7 +268,7 @@ class MutationTest { }.test { assertThat( mutation.changePassword( - GraphQLResourceContext(UserPrincipal(user.copy(id = UUID.randomUUID()), null), null), + GraphQLResourceContext(UserPrincipal(user.copy(id = UUID.randomUUID()), null)), "pass", "newpass" ) @@ -287,7 +287,7 @@ class MutationTest { }.test { assertThat( mutation.changePassword( - GraphQLResourceContext(UserPrincipal(user.copy(email = "user2"), null), null), + GraphQLResourceContext(UserPrincipal(user.copy(email = "user2"), null)), "pass", "newpass" ) @@ -305,7 +305,7 @@ class MutationTest { }.test { assertThat( mutation.changePassword( - GraphQLResourceContext(UserPrincipal(user.copy(email = "user3"), null), null), + GraphQLResourceContext(UserPrincipal(user.copy(email = "user3"), null)), "pass", "newpass" ) diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchQuestionLoaderTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchQuestionLoaderTest.kt new file mode 100644 index 00000000..706f78b8 --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchQuestionLoaderTest.kt @@ -0,0 +1,35 @@ +package com.joe.quizzy.server.graphql.dataloaders + +import assertk.assertThat +import assertk.assertions.isEqualTo +import com.joe.quizzy.api.models.Question +import com.joe.quizzy.persistence.api.QuestionDAO +import com.trib3.testing.LeakyMock +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.BatchLoaderEnvironment +import org.easymock.EasyMock +import org.testng.annotations.Test +import java.time.OffsetDateTime +import java.util.UUID + +class BatchQuestionLoaderTest { + @Test + fun testQuestionLoader() = runBlocking { + val questionDAO = LeakyMock.mock() + val mockEnv = LeakyMock.mock() + val loader = BatchQuestionLoader(questionDAO) + val now = OffsetDateTime.now() + val questions = listOf( + Question(UUID.randomUUID(), UUID.randomUUID(), "q1", "a1", "r1", now, now), + Question(UUID.randomUUID(), UUID.randomUUID(), "q2", "a2", "r2", now, now), + Question(UUID.randomUUID(), UUID.randomUUID(), "q3", "a3", "r3", now, now) + ) + EasyMock.expect(questionDAO.get(EasyMock.anyObject>() ?: listOf())).andReturn(questions) + EasyMock.expect(mockEnv.getContext()).andReturn(null) + EasyMock.replay(questionDAO, mockEnv) + val qs = loader.load(questions.mapNotNull { it.id }.toSet(), mockEnv).await() + assertThat(qs).isEqualTo(questions.associateBy { it.id }) + EasyMock.verify(questionDAO, mockEnv) + } +} diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchUserLoaderTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchUserLoaderTest.kt new file mode 100644 index 00000000..fb80aa78 --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BatchUserLoaderTest.kt @@ -0,0 +1,32 @@ +package com.joe.quizzy.server.graphql.dataloaders + +import assertk.assertThat +import assertk.assertions.isEqualTo +import com.joe.quizzy.api.models.User +import com.joe.quizzy.persistence.api.UserDAO +import com.trib3.testing.LeakyMock +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.BatchLoaderEnvironment +import org.easymock.EasyMock +import org.testng.annotations.Test +import java.util.UUID + +class BatchUserLoaderTest { + @Test + fun testUserLoader() = runBlocking { + val userDAO = LeakyMock.mock() + val mockEnv = LeakyMock.mock() + val loader = BatchUserLoader(userDAO) + val users = listOf( + User(UUID.randomUUID(), UUID.randomUUID(), "joe", "joe@joe.com", "", false, ""), + User(UUID.randomUUID(), UUID.randomUUID(), "bill", "bill@bill.com", "", false, "") + ) + EasyMock.expect(userDAO.get(EasyMock.anyObject>() ?: listOf())).andReturn(users) + EasyMock.expect(mockEnv.getContext()).andReturn(null) + EasyMock.replay(userDAO, mockEnv) + val us = loader.load(users.mapNotNull { it.id }.toSet(), mockEnv).await() + assertThat(us).isEqualTo(users.associateBy { it.id }) + EasyMock.verify(userDAO, mockEnv) + } +} diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BulkInstanceLoaderTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BulkInstanceLoaderTest.kt new file mode 100644 index 00000000..7d4b85d2 --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/BulkInstanceLoaderTest.kt @@ -0,0 +1,32 @@ +package com.joe.quizzy.server.graphql.dataloaders + +import assertk.assertThat +import assertk.assertions.isEqualTo +import com.joe.quizzy.api.models.Instance +import com.joe.quizzy.persistence.api.InstanceDAO +import com.trib3.testing.LeakyMock +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.BatchLoaderEnvironment +import org.easymock.EasyMock +import org.testng.annotations.Test +import java.util.UUID + +class BulkInstanceLoaderTest { + @Test + fun testInstanceLoader() = runBlocking { + val instanceDAO = LeakyMock.mock() + val mockEnv = LeakyMock.mock() + val loader = BulkInstanceLoader(instanceDAO) + val instances = listOf( + Instance(UUID.randomUUID(), "i1", "ACTIVE", ""), + Instance(UUID.randomUUID(), "i2", "ACTIVE", "") + ) + EasyMock.expect(instanceDAO.get(EasyMock.anyObject>() ?: listOf())).andReturn(instances) + EasyMock.expect(mockEnv.getContext()).andReturn(null) + EasyMock.replay(instanceDAO, mockEnv) + val insts = loader.load(instances.mapNotNull { it.id }.toSet(), mockEnv).await() + assertThat(insts).isEqualTo(instances.associateBy { it.id }) + EasyMock.verify(instanceDAO, mockEnv) + } +} diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/CoroutineMappedBatchLoaderTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/CoroutineMappedBatchLoaderTest.kt new file mode 100644 index 00000000..8a2519dc --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/CoroutineMappedBatchLoaderTest.kt @@ -0,0 +1,62 @@ +package com.joe.quizzy.server.graphql.dataloaders + +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isFailure +import assertk.assertions.isLessThan +import assertk.assertions.messageContains +import com.trib3.testing.LeakyMock +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.delay +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.BatchLoaderEnvironment +import org.easymock.EasyMock +import org.testng.annotations.Test + +class CoroutineMappedBatchLoaderTest { + @Test + fun testLoad() = runBlocking { + val loader = object : CoroutineMappedBatchLoader() { + override suspend fun loadSuspend( + keys: Set, + environment: BatchLoaderEnvironment + ): Map { + return keys.associateBy { it } + } + } + val mockEnv = LeakyMock.mock() + EasyMock.expect(mockEnv.getContext()).andReturn(null) + EasyMock.replay(mockEnv) + val loaded = loader.load(setOf("1", "2", "3"), mockEnv).await() + assertThat(loaded).isEqualTo(mapOf("1" to "1", "2" to "2", "3" to "3")) + EasyMock.verify(mockEnv) + } + + @Test + fun testCancellation() = runBlocking(Dispatchers.Unconfined) { + val loader = object : CoroutineMappedBatchLoader() { + override suspend fun loadSuspend( + keys: Set, + environment: BatchLoaderEnvironment + ): Map { + delay(20000) + throw IllegalStateException("Should not get here") + } + } + val mockEnv = LeakyMock.mock() + EasyMock.expect(mockEnv.getContext()).andReturn(this) + EasyMock.replay(mockEnv) + val loading = loader.load(setOf("1", "2", "3"), mockEnv) + this.coroutineContext[Job]?.cancelChildren() + val startAwaitTime = System.currentTimeMillis() + assertThat { + loading.await() + }.isFailure().messageContains("was cancelled") + // ensure the delay() is not hit, but allow for slow test machines + assertThat(System.currentTimeMillis() - startAwaitTime).isLessThan(19000) + EasyMock.verify(mockEnv) + } +} diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/DataLoaderRegistryFactoryProviderTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/DataLoaderRegistryFactoryProviderTest.kt index 3f6e23a2..916dda07 100644 --- a/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/DataLoaderRegistryFactoryProviderTest.kt +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/DataLoaderRegistryFactoryProviderTest.kt @@ -1,14 +1,21 @@ package com.joe.quizzy.server.graphql.dataloaders import assertk.assertThat +import assertk.assertions.isEqualTo import assertk.assertions.isNotNull import com.joe.quizzy.api.models.Grade import com.joe.quizzy.api.models.Instance import com.joe.quizzy.api.models.Question import com.joe.quizzy.api.models.Response import com.joe.quizzy.api.models.User +import com.joe.quizzy.persistence.api.ResponseDAO +import com.joe.quizzy.server.auth.UserPrincipal import com.trib3.graphql.execution.GraphQLRequest +import com.trib3.graphql.resources.GraphQLResourceContext import com.trib3.testing.mock +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.easymock.EasyMock import org.easymock.EasyMockSupport import org.testng.annotations.Test import java.util.UUID @@ -16,16 +23,32 @@ import java.util.UUID class DataLoaderRegistryFactoryProviderTest : EasyMockSupport() { @Test fun testRegistry() { + val responseDAO = mock() val factoryProvider = DataLoaderRegistryFactoryProvider( mock(), mock(), mock(), - mock(), + responseDAO, mock() ) + val userUUID = UUID.randomUUID() + val questionUUID = UUID.randomUUID() + // expect `byUserQuestions` to be called with context user UUID + EasyMock.expect( + responseDAO.byUserQuestions(EasyMock.eq(userUUID) ?: userUUID, EasyMock.anyObject() ?: listOf()) + ).andReturn(mapOf(questionUUID to Response(UUID.randomUUID(), userUUID, questionUUID, "r", "rr"))) replayAll() val factory = factoryProvider.get() - val registry = factory(GraphQLRequest("{}", mapOf(), null), null) + val registry = factory( + GraphQLRequest("{}", mapOf(), null), + GraphQLResourceContext( + UserPrincipal( + User(userUUID, UUID.randomUUID(), "name", "email", "", false, ""), + null + ) + ) + ) + val batchQuestionLoader = registry.getDataLoader("batchquestions") assertThat(batchQuestionLoader).isNotNull() @@ -40,8 +63,14 @@ class DataLoaderRegistryFactoryProviderTest : EasyMockSupport() { val questionResponseLoader = registry.getDataLoader("questionresponses") assertThat(questionResponseLoader).isNotNull() + val loadedQR = questionResponseLoader.load(questionUUID) + questionResponseLoader.dispatch() + runBlocking { + assertThat(loadedQR.await().response).isEqualTo("r") + } val batchInstanceLoader = registry.getDataLoader("batchinstances") assertThat(batchInstanceLoader).isNotNull() + verifyAll() } } diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/QuestionResponseLoaderTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/QuestionResponseLoaderTest.kt new file mode 100644 index 00000000..bf920c27 --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/QuestionResponseLoaderTest.kt @@ -0,0 +1,60 @@ +package com.joe.quizzy.server.graphql.dataloaders + +import assertk.assertThat +import assertk.assertions.isEqualTo +import com.joe.quizzy.api.models.Response +import com.joe.quizzy.api.models.User +import com.joe.quizzy.persistence.api.ResponseDAO +import com.joe.quizzy.server.auth.UserPrincipal +import com.trib3.graphql.resources.GraphQLResourceContext +import com.trib3.testing.LeakyMock +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.BatchLoaderEnvironment +import org.easymock.EasyMock +import org.testng.annotations.Test +import java.util.UUID + +class QuestionResponseLoaderTest { + @Test + fun testQuestionResponseLoader() = runBlocking { + val responseDAO = LeakyMock.mock() + val mockEnv = LeakyMock.mock() + val loader = QuestionResponseLoader(responseDAO) + val questionResponses = mapOf( + UUID.randomUUID() to Response(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), "a1", "rr1"), + UUID.randomUUID() to Response(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), "a2", "rr2") + ) + val userId = UUID.randomUUID() + EasyMock.expect( + responseDAO.byUserQuestions( + EasyMock.eq(userId) ?: userId, + EasyMock.anyObject>() ?: listOf() + ) + ).andReturn(questionResponses) + EasyMock.expect(mockEnv.getContext()).andReturn( + GraphQLResourceContext( + UserPrincipal( + User(userId, UUID.randomUUID(), "user", "user@user.com", "", false, ""), + null + ) + ) + ).atLeastOnce() + EasyMock.replay(responseDAO, mockEnv) + val insts = loader.load(questionResponses.mapNotNull { it.key }.toSet(), mockEnv).await() + assertThat(insts).isEqualTo(questionResponses) + EasyMock.verify(responseDAO, mockEnv) + } + + @Test + fun testNoContextUser() = runBlocking { + val responseDAO = LeakyMock.mock() + val mockEnv = LeakyMock.mock() + val loader = QuestionResponseLoader(responseDAO) + EasyMock.expect(mockEnv.getContext()).andReturn(GraphQLResourceContext(null)).atLeastOnce() + EasyMock.replay(responseDAO, mockEnv) + val insts = loader.load(setOf(UUID.randomUUID(), UUID.randomUUID()), mockEnv).await() + assertThat(insts).isEqualTo(mapOf()) + EasyMock.verify(responseDAO, mockEnv) + } +} diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/ResponseGradeLoaderTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/ResponseGradeLoaderTest.kt new file mode 100644 index 00000000..ee9c8286 --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/ResponseGradeLoaderTest.kt @@ -0,0 +1,32 @@ +package com.joe.quizzy.server.graphql.dataloaders + +import assertk.assertThat +import assertk.assertions.isEqualTo +import com.joe.quizzy.api.models.Grade +import com.joe.quizzy.persistence.api.GradeDAO +import com.trib3.testing.LeakyMock +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.BatchLoaderEnvironment +import org.easymock.EasyMock +import org.testng.annotations.Test +import java.util.UUID + +class ResponseGradeLoaderTest { + @Test + fun testResponseGradeLoader() = runBlocking { + val gradeDAO = LeakyMock.mock() + val mockEnv = LeakyMock.mock() + val loader = ResponseGradeLoader(gradeDAO) + val grades = mapOf( + UUID.randomUUID() to Grade(UUID.randomUUID(), UUID.randomUUID(), true, 1), + UUID.randomUUID() to Grade(UUID.randomUUID(), UUID.randomUUID(), true, 2) + ) + EasyMock.expect(gradeDAO.forResponses(EasyMock.anyObject>() ?: listOf())).andReturn(grades) + EasyMock.expect(mockEnv.getContext()).andReturn(null) + EasyMock.replay(gradeDAO, mockEnv) + val gs = loader.load(grades.mapNotNull { it.key }.toSet(), mockEnv).await() + assertThat(gs).isEqualTo(grades) + EasyMock.verify(gradeDAO, mockEnv) + } +} diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/UserGradeLoaderTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/UserGradeLoaderTest.kt new file mode 100644 index 00000000..6714ae73 --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/dataloaders/UserGradeLoaderTest.kt @@ -0,0 +1,35 @@ +package com.joe.quizzy.server.graphql.dataloaders + +import assertk.assertThat +import assertk.assertions.isEqualTo +import com.joe.quizzy.api.models.Grade +import com.joe.quizzy.persistence.api.GradeDAO +import com.trib3.testing.LeakyMock +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.BatchLoaderEnvironment +import org.easymock.EasyMock +import org.testng.annotations.Test +import java.util.UUID + +class UserGradeLoaderTest { + @Test + fun testResponseGradeLoader() = runBlocking { + val gradeDAO = LeakyMock.mock() + val mockEnv = LeakyMock.mock() + val loader = UserGradeLoader(gradeDAO) + val grades = mapOf( + UUID.randomUUID() to listOf(Grade(UUID.randomUUID(), UUID.randomUUID(), true, 1)), + UUID.randomUUID() to listOf( + Grade(UUID.randomUUID(), UUID.randomUUID(), true, 2), + Grade(UUID.randomUUID(), UUID.randomUUID(), true, 3) + ) + ) + EasyMock.expect(gradeDAO.forUsers(EasyMock.anyObject>() ?: listOf())).andReturn(grades) + EasyMock.expect(mockEnv.getContext()).andReturn(null) + EasyMock.replay(gradeDAO, mockEnv) + val gs = loader.load(grades.mapNotNull { it.key }.toSet(), mockEnv).await() + assertThat(gs).isEqualTo(grades) + EasyMock.verify(gradeDAO, mockEnv) + } +} diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiQuestionTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiQuestionTest.kt new file mode 100644 index 00000000..2e53365f --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiQuestionTest.kt @@ -0,0 +1,126 @@ +package com.joe.quizzy.server.graphql.models + +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isNull +import com.joe.quizzy.api.models.Question +import com.joe.quizzy.api.models.Response +import com.joe.quizzy.api.models.User +import com.joe.quizzy.server.auth.UserPrincipal +import com.trib3.graphql.resources.GraphQLResourceContext +import com.trib3.testing.LeakyMock +import graphql.schema.DataFetchingEnvironment +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.DataLoader +import org.easymock.EasyMock +import org.testng.annotations.Test +import java.time.OffsetDateTime +import java.util.UUID +import java.util.concurrent.CompletableFuture + +class ApiQuestionTest { + val now = OffsetDateTime.now() + val q = ApiQuestion( + UUID.randomUUID(), + UUID.randomUUID(), + "q1", + "a1", + "r1", + now, + now + ) + val u = User(UUID.randomUUID(), UUID.randomUUID(), "name", "email", "", false, "") + + @Test + fun testCopyConstructor() { + assertThat(q).isEqualTo( + ApiQuestion(Question(q.id, q.authorId, q.body, q.answer, q.ruleReferences, q.activeAt, q.closedAt)) + ) + } + + @Test + fun testResponse() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("questionresponses")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(q.id)).andReturn( + CompletableFuture.completedFuture( + Response( + UUID.randomUUID(), + u.id!!, + q.id!!, + "answer", + "reference" + ) + ) + ) + EasyMock.replay(mockEnv, mockDataLoader) + val resp = q.response(context, mockEnv).await() + assertThat(resp?.questionId).isEqualTo(q.id) + assertThat(resp?.response).isEqualTo("answer") + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNullResponse() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("questionresponses")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(q.id)).andReturn( + CompletableFuture.completedFuture(null) + ) + EasyMock.replay(mockEnv, mockDataLoader) + val resp = q.response(context, mockEnv).await() + assertThat(resp).isNull() + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNullContextUser() = runBlocking { + val context = GraphQLResourceContext(null) + val mockEnv = LeakyMock.mock() + EasyMock.replay(mockEnv) + val resp = q.response(context, mockEnv).await() + assertThat(resp).isNull() + EasyMock.verify(mockEnv) + } + + @Test + fun testNullId() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + EasyMock.replay(mockEnv) + val resp = q.copy(id = null).response(context, mockEnv).await() + assertThat(resp).isNull() + EasyMock.verify(mockEnv) + } + + @Test + fun testAuthor() = runBlocking { + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("batchusers")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(q.authorId)).andReturn(CompletableFuture.completedFuture(u)) + EasyMock.replay(mockEnv, mockDataLoader) + val resp = q.author(mockEnv).await() + assertThat(resp).isEqualTo(ApiUser(u)) + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNullAuthor() = runBlocking { + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("batchusers")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(q.authorId)).andReturn( + CompletableFuture.completedFuture(null) + ) + EasyMock.replay(mockEnv, mockDataLoader) + val resp = q.author(mockEnv).await() + assertThat(resp).isNull() + EasyMock.verify(mockEnv, mockDataLoader) + } +} diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiResponseTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiResponseTest.kt new file mode 100644 index 00000000..f8114cea --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiResponseTest.kt @@ -0,0 +1,160 @@ +package com.joe.quizzy.server.graphql.models + +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isNull +import com.joe.quizzy.api.models.Grade +import com.joe.quizzy.api.models.Question +import com.joe.quizzy.api.models.Response +import com.joe.quizzy.api.models.User +import com.joe.quizzy.server.auth.UserPrincipal +import com.trib3.graphql.resources.GraphQLResourceContext +import com.trib3.testing.LeakyMock +import graphql.schema.DataFetchingEnvironment +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.DataLoader +import org.easymock.EasyMock +import org.testng.annotations.Test +import java.time.OffsetDateTime +import java.util.UUID +import java.util.concurrent.CompletableFuture + +class ApiResponseTest { + val now = OffsetDateTime.now() + val r = ApiResponse( + UUID.randomUUID(), + UUID.randomUUID(), + UUID.randomUUID(), + "answer", + "references" + ) + val u = User(UUID.randomUUID(), UUID.randomUUID(), "name", "email", "", false, "") + + @Test + fun testCopyConstructor() { + assertThat(r).isEqualTo(ApiResponse(Response(r.id, r.userId, r.questionId, r.response, r.ruleReferences))) + } + + @Test + fun testUser() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("batchusers")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(r.userId)).andReturn( + CompletableFuture.completedFuture(u) + ) + EasyMock.replay(mockEnv, mockDataLoader) + assertThat(r.user(context, mockEnv).await()).isEqualTo(ApiUser(u)) + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNullUser() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("batchusers")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(r.userId)).andReturn( + CompletableFuture.completedFuture(null) + ) + EasyMock.replay(mockEnv, mockDataLoader) + assertThat(r.user(context, mockEnv).await()).isNull() + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNoContextUser() = runBlocking { + val context = GraphQLResourceContext(null) + val mockEnv = LeakyMock.mock() + EasyMock.replay(mockEnv) + assertThat(r.user(context, mockEnv).await()).isNull() + EasyMock.verify(mockEnv) + } + + @Test + fun testQuestion() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("batchquestions")).andReturn(mockDataLoader) + val q = Question(r.questionId, UUID.randomUUID(), "b", "a", "r", now, now) + EasyMock.expect(mockDataLoader.load(r.questionId)).andReturn( + CompletableFuture.completedFuture(q) + ) + EasyMock.replay(mockEnv, mockDataLoader) + assertThat(r.question(context, mockEnv).await()).isEqualTo(ApiQuestion(q)) + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNullQuestion() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("batchquestions")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(r.questionId)).andReturn( + CompletableFuture.completedFuture(null) + ) + EasyMock.replay(mockEnv, mockDataLoader) + assertThat(r.question(context, mockEnv).await()).isNull() + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNoContextQuestion() = runBlocking { + val context = GraphQLResourceContext(null) + val mockEnv = LeakyMock.mock() + EasyMock.replay(mockEnv) + assertThat(r.question(context, mockEnv).await()).isNull() + EasyMock.verify(mockEnv) + } + + @Test + fun testGrade() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("responsegrades")).andReturn(mockDataLoader) + val g = Grade(UUID.randomUUID(), r.id!!, true, 5) + EasyMock.expect(mockDataLoader.load(r.id)).andReturn( + CompletableFuture.completedFuture(g) + ) + EasyMock.replay(mockEnv, mockDataLoader) + assertThat(r.grade(context, mockEnv).await()).isEqualTo(g) + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNullGrade() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("responsegrades")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(r.id)).andReturn( + CompletableFuture.completedFuture(null) + ) + EasyMock.replay(mockEnv, mockDataLoader) + assertThat(r.grade(context, mockEnv).await()).isNull() + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNoContextGrade() = runBlocking { + val context = GraphQLResourceContext(null) + val mockEnv = LeakyMock.mock() + EasyMock.replay(mockEnv) + assertThat(r.grade(context, mockEnv).await()).isNull() + EasyMock.verify(mockEnv) + } + + @Test + fun testNoIdGrade() = runBlocking { + val context = GraphQLResourceContext(UserPrincipal(u, null)) + val mockEnv = LeakyMock.mock() + EasyMock.replay(mockEnv) + assertThat(r.copy(id = null).grade(context, mockEnv).await()).isNull() + EasyMock.verify(mockEnv) + } +} diff --git a/server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiUserTest.kt b/server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiUserTest.kt new file mode 100644 index 00000000..e7504254 --- /dev/null +++ b/server/src/test/kotlin/com/joe/quizzy/server/graphql/models/ApiUserTest.kt @@ -0,0 +1,86 @@ +package com.joe.quizzy.server.graphql.models + +import assertk.assertThat +import assertk.assertions.isEqualTo +import com.joe.quizzy.api.models.Grade +import com.joe.quizzy.api.models.Instance +import com.joe.quizzy.api.models.User +import com.trib3.testing.LeakyMock +import graphql.schema.DataFetchingEnvironment +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.dataloader.DataLoader +import org.easymock.EasyMock +import org.testng.annotations.Test +import java.util.UUID +import java.util.concurrent.CompletableFuture + +class ApiUserTest { + + val u = ApiUser(UUID.randomUUID(), UUID.randomUUID(), "name", "email", false, "", false) + + @Test + fun testCopyConstructor() { + assertThat(u).isEqualTo( + ApiUser( + User( + u.id, + u.instanceId, + u.name, + u.email, + "", + u.admin, + u.timeZoneId, + u.notifyViaEmail + ) + ) + ) + } + + @Test + fun testScore() = runBlocking { + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>>() + EasyMock.expect(mockEnv.getDataLoader>("usergrades")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(u.id)).andReturn( + CompletableFuture.completedFuture( + listOf( + Grade(UUID.randomUUID(), UUID.randomUUID(), true, 5), + Grade(UUID.randomUUID(), UUID.randomUUID(), false, 1) + ) + ) + ) + EasyMock.replay(mockEnv, mockDataLoader) + assertThat(u.score(mockEnv).await()).isEqualTo(20) + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testNullScore() = runBlocking { + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>>() + EasyMock.expect(mockEnv.getDataLoader>("usergrades")).andReturn(mockDataLoader) + EasyMock.expect(mockDataLoader.load(u.id)).andReturn( + CompletableFuture.completedFuture( + null + ) + ) + EasyMock.replay(mockEnv, mockDataLoader) + assertThat(u.score(mockEnv).await()).isEqualTo(0) + EasyMock.verify(mockEnv, mockDataLoader) + } + + @Test + fun testInstance() = runBlocking { + val mockEnv = LeakyMock.mock() + val mockDataLoader = LeakyMock.mock>() + EasyMock.expect(mockEnv.getDataLoader("batchinstances")).andReturn(mockDataLoader) + val i = Instance(u.instanceId, "Test inst", "ACTIVE") + EasyMock.expect(mockDataLoader.load(u.instanceId)).andReturn( + CompletableFuture.completedFuture(i) + ) + EasyMock.replay(mockEnv, mockDataLoader) + assertThat(u.instance(mockEnv).await()).isEqualTo(i) + EasyMock.verify(mockEnv, mockDataLoader) + } +}