Skip to content

Commit

Permalink
Finished ServerEntitySynchronizerTests
Browse files Browse the repository at this point in the history
  • Loading branch information
Wavesonics committed Jul 10, 2024
1 parent 2573859 commit f8fef68
Show file tree
Hide file tree
Showing 8 changed files with 611 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.darkrockstudios.apps.hammer.utilities

import com.darkrockstudios.apps.hammer.base.http.ApiProjectEntity
import com.darkrockstudios.apps.hammer.base.http.synchronizer.EntityHasher

fun EntityHasher.hashEntity(entity: ApiProjectEntity): String {
return when (entity) {
is ApiProjectEntity.SceneEntity -> hashScene(
id = entity.id,
name = entity.name,
order = entity.order,
path = entity.path,
type = entity.sceneType,
content = entity.content,
outline = entity.outline,
notes = entity.notes,
)

is ApiProjectEntity.SceneDraftEntity -> hashSceneDraft(
id = entity.id,
created = entity.created,
name = entity.name,
content = entity.content,
)

is ApiProjectEntity.NoteEntity -> hashNote(
id = entity.id,
created = entity.created,
content = entity.content,
)

is ApiProjectEntity.TimelineEventEntity -> hashTimelineEvent(
id = entity.id,
order = entity.order,
content = entity.content,
date = entity.date,
)

is ApiProjectEntity.EncyclopediaEntryEntity -> hashEncyclopediaEntry(
id = entity.id,
name = entity.name,
entryType = entity.entryType,
text = entity.text,
tags = entity.tags,
image = entity.image,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.darkrockstudios.apps.hammer.project.synchronizers

import com.darkrockstudios.apps.hammer.base.http.ApiProjectEntity
import org.junit.Before
import kotlin.reflect.KClass

class ServerEncyclopediaEntrySynchronizerTest :
ServerEntitySynchronizerTest<ApiProjectEntity.EncyclopediaEntryEntity, ServerEncyclopediaSynchronizer>() {

override val entityType: ApiProjectEntity.Type = ApiProjectEntity.Type.ENCYCLOPEDIA_ENTRY
override val entityClazz: KClass<ApiProjectEntity.EncyclopediaEntryEntity> =
ApiProjectEntity.EncyclopediaEntryEntity::class
override val pathStub: String = "encyclopedia_entry"

@Before
override fun setup() {
super.setup()
}

override fun createSynchronizer(): ServerEncyclopediaSynchronizer {
return ServerEncyclopediaSynchronizer(datasource)
}

override fun createNewEntity(): ApiProjectEntity.EncyclopediaEntryEntity {
return ApiProjectEntity.EncyclopediaEntryEntity(
id = 1,
name = "Test Name",
entryType = "Test Type",
text = "Test Text",
tags = setOf("Test Tag"),
image = null,
)
}

override fun createExistingEntity(): ApiProjectEntity.EncyclopediaEntryEntity {
return ApiProjectEntity.EncyclopediaEntryEntity(
id = 1,
name = "Test Name Different",
entryType = "Test Type",
text = "Test Text",
tags = setOf(),
image = null,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.darkrockstudios.apps.hammer.project.synchronizers

import com.darkrockstudios.apps.hammer.base.http.ApiProjectEntity
import com.darkrockstudios.apps.hammer.base.http.ApiSceneType
import com.darkrockstudios.apps.hammer.base.http.synchronizer.EntityConflictException
import com.darkrockstudios.apps.hammer.base.http.synchronizer.EntityHasher
import com.darkrockstudios.apps.hammer.project.EntityDefinition
import com.darkrockstudios.apps.hammer.project.EntityNotFound
import com.darkrockstudios.apps.hammer.project.ProjectDatasource
import com.darkrockstudios.apps.hammer.utilities.SResult
import com.darkrockstudios.apps.hammer.utilities.hashEntity
import com.darkrockstudios.apps.hammer.utilities.isSuccess
import com.darkrockstudios.apps.hammer.utils.BaseTest
import io.mockk.coEvery
Expand All @@ -16,6 +21,8 @@ import org.junit.Before
import kotlin.reflect.KClass
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertTrue

@OptIn(InternalSerializationApi::class)
Expand Down Expand Up @@ -86,4 +93,179 @@ abstract class ServerEntitySynchronizerTest<C : ApiProjectEntity, T : ServerEnti
assertTrue(isSuccess(result))
coVerify { datasource.storeEntity(userId, any(), entity, any(), entityClazz.serializer()) }
}

@Test
fun `Save Entity - Original Hash Match`() = runTest {
val userId = 1L
val existingEntity = createExistingEntity()
val newEntity = createNewEntity()

val synchronizer = createSynchronizer()
val originalHash = synchronizer.hashEntity(existingEntity)

coEvery {
datasource.loadEntity(any(), any(), any(), any(), entityClazz.serializer())
} returns SResult.success(existingEntity)
coEvery {
datasource.storeEntity(any(), any(), newEntity, any(), entityClazz.serializer())
} returns SResult.success(true)

val result = synchronizer.saveEntity(userId, mockk(), newEntity, originalHash, false)

assertTrue(isSuccess(result))
coVerify {
datasource.storeEntity(
userId,
any(),
newEntity,
any(),
entityClazz.serializer()
)
}
}

@Test
fun `Save Entity - Original Hash Mismatch`() = runTest {
val userId = 1L
val existingEntity = createExistingEntity()
val newEntity = createNewEntity()

val synchronizer = createSynchronizer()
val originalHash = "fake-hash"

coEvery {
datasource.loadEntity(any(), any(), any(), any(), entityClazz.serializer())
} returns SResult.success(existingEntity)
coEvery {
datasource.storeEntity(any(), any(), newEntity, any(), entityClazz.serializer())
} returns SResult.success(true)

val result = synchronizer.saveEntity(userId, mockk(), newEntity, originalHash, false)

assertFalse(isSuccess(result))
assertIs<EntityConflictException>(result.exception)
coVerify(exactly = 0) {
datasource.storeEntity(
userId,
any(),
newEntity,
any(),
entityClazz.serializer()
)
}
}

@Test
fun `Load Entity`() = runTest {
val userId = 1L
val entityId = 1
val entity = createExistingEntity()

coEvery {
datasource.loadEntity(userId, any(), entityId, any(), entityClazz.serializer())
} returns SResult.success(entity)

val synchronizer = createSynchronizer()
val result = synchronizer.loadEntity(userId, mockk(), entityId)

assertTrue(isSuccess(result))
assertEquals(entity, result.data)
coVerify { datasource.loadEntity(userId, any(), entityId, any(), entityClazz.serializer()) }
}

@Test
fun `Delete Entity`() = runTest {
val userId = 1L
val entityId = 1

coEvery {
datasource.deleteEntity(userId, any(), any(), entityId)
} returns SResult.success(Unit)

val synchronizer = createSynchronizer()
val result = synchronizer.deleteEntity(userId, mockk(), entityId)

assertTrue(isSuccess(result))
coVerify { datasource.deleteEntity(userId, any(), any(), entityId) }
}

@Test
fun `Hash Entity`() = runTest {
val entity = createExistingEntity()
val synchronizer = createSynchronizer()
val hash = EntityHasher.hashEntity(entity)
val result = synchronizer.hashEntity(entity)
assertEquals(hash, result)
}

@Test
open fun `Get Update Sequence - No Client State`() = runTest {
val userId = 1L

val entities = entityDefs().filter { it.type == entityType }

coEvery {
datasource.getEntityDefs(userId, any(), any())
} returns entities

val synchronizer = createSynchronizer()
val result = synchronizer.getUpdateSequence(userId, mockk(), null)

val entityIds = entities.map { it.id }
assertEquals(entityIds, result)
}

private fun entityDefs() = listOf(
EntityDefinition(1, ApiProjectEntity.Type.SCENE),
EntityDefinition(2, ApiProjectEntity.Type.SCENE),
EntityDefinition(3, ApiProjectEntity.Type.SCENE),
EntityDefinition(4, ApiProjectEntity.Type.NOTE),
EntityDefinition(5, ApiProjectEntity.Type.SCENE),
EntityDefinition(6, ApiProjectEntity.Type.NOTE),
EntityDefinition(7, ApiProjectEntity.Type.NOTE),
EntityDefinition(8, ApiProjectEntity.Type.TIMELINE_EVENT),
EntityDefinition(9, ApiProjectEntity.Type.TIMELINE_EVENT),
EntityDefinition(10, ApiProjectEntity.Type.ENCYCLOPEDIA_ENTRY),
EntityDefinition(11, ApiProjectEntity.Type.TIMELINE_EVENT),
EntityDefinition(12, ApiProjectEntity.Type.TIMELINE_EVENT),
EntityDefinition(13, ApiProjectEntity.Type.SCENE_DRAFT),
EntityDefinition(14, ApiProjectEntity.Type.ENCYCLOPEDIA_ENTRY),
EntityDefinition(15, ApiProjectEntity.Type.ENCYCLOPEDIA_ENTRY),
EntityDefinition(16, ApiProjectEntity.Type.ENCYCLOPEDIA_ENTRY),
EntityDefinition(17, ApiProjectEntity.Type.SCENE_DRAFT),
EntityDefinition(18, ApiProjectEntity.Type.SCENE_DRAFT),
)

private val entities = listOf(
ApiProjectEntity.SceneEntity(
id = 1,
sceneType = ApiSceneType.Scene,
order = 0,
name = "Test Scene",
path = listOf(0),
content = "Test Content",
outline = "Test Outline",
notes = "Test Notes",
),
ApiProjectEntity.SceneEntity(
id = 2,
sceneType = ApiSceneType.Scene,
order = 0,
name = "Test Scene",
path = listOf(0),
content = "Test Content",
outline = "Test Outline",
notes = "Test Notes",
),
ApiProjectEntity.SceneEntity(
id = 2,
sceneType = ApiSceneType.Scene,
order = 0,
name = "Test Scene",
path = listOf(0),
content = "Test Content",
outline = "Test Outline",
notes = "Test Notes",
)
)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
package com.darkrockstudios.apps.hammer.project.synchronizers

import com.darkrockstudios.apps.hammer.base.http.ApiProjectEntity
import com.darkrockstudios.apps.hammer.base.http.ClientEntityState
import com.darkrockstudios.apps.hammer.base.http.EntityHash
import com.darkrockstudios.apps.hammer.base.http.synchronizer.EntityHasher
import com.darkrockstudios.apps.hammer.project.EntityDefinition
import com.darkrockstudios.apps.hammer.utilities.SResult
import com.darkrockstudios.apps.hammer.utilities.hashEntity
import io.mockk.coEvery
import io.mockk.mockk
import io.mockk.slot
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.serializer
import org.junit.Before
import kotlin.reflect.KClass
import kotlin.test.Test
import kotlin.test.assertEquals

class ServerNoteSynchronizerTest :
ServerEntitySynchronizerTest<ApiProjectEntity.NoteEntity, ServerNoteSynchronizer>() {
Expand Down Expand Up @@ -37,4 +51,66 @@ class ServerNoteSynchronizerTest :
created = Instant.fromEpochSeconds(123),
)
}

@OptIn(InternalSerializationApi::class)
@Test
fun `Get Note Update Sequence - All the same`() = runTest {
val userId = 1L

val entityDefList = entityDefs()

coEvery {
datasource.getEntityDefs(userId, any(), any())
} returns entityDefList

val entityIdSlot = slot<Int>()
coEvery {
datasource.loadEntity(
userId,
any(),
capture(entityIdSlot),
any(),
entityClazz.serializer()
)
} answers {
return@answers SResult.success(entities[entityIdSlot.captured - 1])
}

val clientState = ClientEntityState(
entities = setOf(
EntityHash(1, EntityHasher.hashEntity(entities[0])),
EntityHash(2, EntityHasher.hashEntity(entities[1])),
EntityHash(3, EntityHasher.hashEntity(entities[2])),
)
)

val synchronizer = createSynchronizer()
val result = synchronizer.getUpdateSequence(userId, mockk(), clientState)

assertEquals(emptyList(), result)
}

private val entities = listOf(
ApiProjectEntity.NoteEntity(
id = 1,
content = "Test Content 1",
created = Instant.fromEpochSeconds(1)
),
ApiProjectEntity.NoteEntity(
id = 2,
content = "Test Content 1",
created = Instant.fromEpochSeconds(12)
),
ApiProjectEntity.NoteEntity(
id = 3,
content = "Test Content 2",
created = Instant.fromEpochSeconds(123)
)
)

private fun entityDefs() = listOf(
EntityDefinition(1, ApiProjectEntity.Type.NOTE),
EntityDefinition(2, ApiProjectEntity.Type.NOTE),
EntityDefinition(3, ApiProjectEntity.Type.NOTE),
)
}
Loading

0 comments on commit f8fef68

Please sign in to comment.