Skip to content

Commit

Permalink
fix&tests: Timers Database requests (#174)
Browse files Browse the repository at this point in the history
  • Loading branch information
y9vad9 authored Jan 2, 2024
1 parent 7026258 commit 284a499
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.timemates.backend.data.authorization

import com.timemates.backend.time.UnixTime
import io.timemates.backend.validation.createOrThrowInternally
import io.timemates.backend.authorization.repositories.AuthorizationsRepository
import io.timemates.backend.authorization.types.Authorization
import io.timemates.backend.authorization.types.metadata.ClientMetadata
Expand All @@ -17,6 +16,7 @@ import io.timemates.backend.pagination.Page
import io.timemates.backend.pagination.PageToken
import io.timemates.backend.pagination.map
import io.timemates.backend.users.types.value.UserId
import io.timemates.backend.validation.createOrThrowInternally

class PostgresqlAuthorizationsRepository(
private val tableAuthorizationsDataSource: TableAuthorizationsDataSource,
Expand Down Expand Up @@ -68,11 +68,11 @@ class PostgresqlAuthorizationsRepository(
return tableAuthorizationsDataSource.removeAuthorization(accessToken.string)
}

override suspend fun get(accessToken: AccessHash, afterTime: UnixTime): Authorization? {
override suspend fun get(accessToken: AccessHash, currentTime: UnixTime): Authorization? {
cacheAuthorizations.getAuthorization(accessToken.string)
?.let { return mapper.cacheAuthToDomainAuth(it) }

return tableAuthorizationsDataSource.getAuthorization(accessToken.string, afterTime.inMilliseconds)
return tableAuthorizationsDataSource.getAuthorization(accessToken.string, currentTime.inMilliseconds)
?.also { cacheAuthorizations.saveAuthorization(accessToken.string, mapper.dbAuthToCacheAuth(it)) }
?.let(mapper::dbAuthToDomainAuth)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.timemates.backend.data.timers

import com.timemates.backend.time.UnixTime
import io.timemates.backend.validation.createOrThrowInternally
import io.timemates.backend.common.types.value.Count
import io.timemates.backend.data.timers.cache.CacheTimersDataSource
import io.timemates.backend.data.timers.db.TableTimerParticipantsDataSource
Expand All @@ -17,6 +16,7 @@ import io.timemates.backend.timers.types.value.TimerDescription
import io.timemates.backend.timers.types.value.TimerId
import io.timemates.backend.timers.types.value.TimerName
import io.timemates.backend.users.types.value.UserId
import io.timemates.backend.validation.createOrThrowInternally

class PostgresqlTimersRepository(
private val tableTimers: TableTimersDataSource,
Expand All @@ -35,6 +35,7 @@ class PostgresqlTimersRepository(
name = name.string,
description = null,
ownerId = ownerId.long,
creationTime = creationTime.inMilliseconds,
)

tableTimers.setSettings(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import io.timemates.backend.exposed.update
import io.timemates.backend.pagination.Ordering
import io.timemates.backend.pagination.Page
import io.timemates.backend.pagination.PageToken
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.jetbrains.annotations.TestOnly
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction
Expand All @@ -31,11 +31,13 @@ class TableTimersDataSource(
name: String,
description: String? = null,
ownerId: Long,
creationTime: Long,
): Long = suspendedTransaction(database) {
TimersTable.insert {
it[NAME] = name
it[DESCRIPTION] = description ?: ""
it[DESCRIPTION] = description.orEmpty()
it[OWNER_ID] = ownerId
it[CREATION_TIME] = creationTime
}[TimersTable.ID]
}

Expand Down Expand Up @@ -105,14 +107,17 @@ class TableTimersDataSource(
val decodedPageToken: TimersPageToken? = pageToken?.forInternal()?.let { json.decodeFromString(it) }

val result = TimersTable.select {
TimersTable.ID greater (decodedPageToken?.nextRetrievedTimerId ?: 0) and
TimersTable.CREATION_TIME less (decodedPageToken?.beforeTime ?: Long.MAX_VALUE) and
(TimersTable.ID less (decodedPageToken?.prevReceivedId ?: Long.MAX_VALUE)) and
(TimersTable.OWNER_ID eq userId)
}.orderBy(TimersTable.CREATION_TIME, SortOrder.DESC).map(timersMapper::resultRowToDbTimer)
}.orderBy(
order = arrayOf(TimersTable.CREATION_TIME to SortOrder.DESC, TimersTable.ID to SortOrder.DESC)
).limit(20).map(timersMapper::resultRowToDbTimer)

val lastId = result.lastOrNull()?.id
val nextPageToken = if (lastId != null)
PageToken.toGive(json.encodeToString(TimersPageToken(lastId)))
else pageToken
PageToken.toGive(json.encodeToString(TimersPageToken(lastId, result.lastOrNull()!!.creationTime)))
else null

return@suspendedTransaction Page(
value = result,
Expand All @@ -126,4 +131,9 @@ class TableTimersDataSource(
.singleOrNull()
?.let(timersMapper::resultRowToDbTimer)
}

@TestOnly
suspend fun clear(): Unit = suspendedTransaction(database) {
TimersTable.deleteAll()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ data class DbTimer(
val description: String,
val ownerId: Long,
val settings: Settings,
val creationTime: Long,
) {
class Settings(
data class Settings(
val workTime: Long,
val restTime: Long,
val bigRestTime: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import kotlinx.serialization.Serializable

@Serializable
data class TimersPageToken(
val nextRetrievedTimerId: Long,
val prevReceivedId: Long,
val beforeTime: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.timemates.backend.data.timers.db.tables
import io.timemates.backend.data.users.datasource.PostgresqlUsersDataSource
import io.timemates.backend.exposed.emptyAsDefault
import org.jetbrains.exposed.sql.Table
import kotlin.time.Duration.Companion.minutes

internal object TimersTable : Table("timers") {
val ID = long("id").autoIncrement()
Expand All @@ -13,13 +14,13 @@ internal object TimersTable : Table("timers") {
val CREATION_TIME = long("creation_time")

// Settings
val WORK_TIME = long("work_time")
val REST_TIME = long("rest_time")
val BIG_REST_TIME = long("big_rest_time")
val BIG_REST_TIME_ENABLED = bool("big_rest_time_enabled")
val BIG_REST_PER = integer("big_rest_per")
val IS_EVERYONE_CAN_PAUSE = bool("is_everyone_can_pause")
val IS_CONFIRMATION_REQUIRED = bool("is_confirmation_required")
val WORK_TIME = long("work_time").default(25.minutes.inWholeMilliseconds)
val REST_TIME = long("rest_time").default(5.minutes.inWholeMilliseconds)
val BIG_REST_TIME = long("big_rest_time").default(10.minutes.inWholeMilliseconds)
val BIG_REST_TIME_ENABLED = bool("big_rest_time_enabled").default(false)
val BIG_REST_PER = integer("big_rest_per").default(4)
val IS_EVERYONE_CAN_PAUSE = bool("is_everyone_can_pause").default(false)
val IS_CONFIRMATION_REQUIRED = bool("is_confirmation_required").default(true)

override val primaryKey = PrimaryKey(ID)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package io.timemates.backend.data.timers.mappers

import com.timemates.backend.time.TimeProvider
import io.timemates.backend.validation.createOrThrowInternally
import io.timemates.backend.common.types.value.Count
import io.timemates.backend.data.common.markers.Mapper
import io.timemates.backend.data.timers.db.entities.DbTimer
Expand All @@ -17,6 +16,7 @@ import io.timemates.backend.timers.types.value.TimerDescription
import io.timemates.backend.timers.types.value.TimerId
import io.timemates.backend.timers.types.value.TimerName
import io.timemates.backend.users.types.value.UserId
import io.timemates.backend.validation.createOrThrowInternally
import org.jetbrains.exposed.sql.ResultRow
import kotlin.time.Duration.Companion.minutes

Expand All @@ -28,6 +28,7 @@ class TimersMapper(private val sessionMapper: TimerSessionMapper) : Mapper {
get(TimersTable.DESCRIPTION),
get(TimersTable.OWNER_ID),
resultRowToTimerSettings(resultRow),
get(TimersTable.CREATION_TIME),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package io.timemates.backend.data.timers.datasource

import io.timemates.backend.data.timers.db.TableTimersDataSource
import io.timemates.backend.data.timers.db.entities.DbTimer
import io.timemates.backend.data.timers.mappers.TimerSessionMapper
import io.timemates.backend.data.timers.mappers.TimersMapper
import io.timemates.backend.data.users.datasource.PostgresqlUsersDataSource
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.Database
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.BeforeEach
import kotlin.properties.Delegates
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertNull

class TableTimersDataSourceTest {
private val databaseUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;"
private val databaseDriver = "org.h2.Driver"

private val database = Database.connect(databaseUrl, databaseDriver)
private val timers: TableTimersDataSource = TableTimersDataSource(database, TimersMapper(TimerSessionMapper()), Json)
private val users: PostgresqlUsersDataSource = PostgresqlUsersDataSource(database)

private var ownerId by Delegates.notNull<Long>()

@BeforeEach
fun `clear database`(): Unit = runBlocking {
timers.clear()
}

@Before
fun `create test user`(): Unit = runBlocking {
ownerId = users.createUser(
email = "[email protected]",
userName = "test name",
shortBio = "Test bio",
creationTime = System.currentTimeMillis(),
)
}

@Test
fun `createTimer should return the correct timer ID`(): Unit = runBlocking {
// Arrange
val name = "Test Timer"
val description = "This is a test timer"
val creationTime = System.currentTimeMillis()

// Act
timers.createTimer(name, description, ownerId, creationTime)
}

@Test
fun `createTimer should return a different timer ID for each call`(): Unit = runBlocking {
// Arrange
val name = "Test Timer"
val description = "This is a test timer"
val creationTime = System.currentTimeMillis()

// Act
val timerId1 = timers.createTimer(name, description, ownerId, creationTime)
val timerId2 = timers.createTimer(name, description, ownerId, creationTime)

// Assert
assertEquals(timerId1 + 1, timerId2)
}

@Test
fun `createTimer should return a default description if not provided`(): Unit = runBlocking {
// Arrange
val name = "Test Timer"
val creationTime = System.currentTimeMillis()

// Act
val timerId = timers.createTimer(name, ownerId = ownerId, creationTime = creationTime)

// Assert
assertEquals("", timers.getTimer(timerId)?.description.orEmpty())
}

@Test
fun `editTimer updates name successfully`(): Unit = runBlocking {
val timerId = 1L
val newName = "New Timer Name"
timers.editTimer(timerId, newName = newName)
val updatedTimer = timers.getTimer(timerId)
assertEquals(newName, updatedTimer?.name)
}

@Test
fun `editTimer updates description`(): Unit = runBlocking {
val timerId = 1L
val newDescription = "New Timer Description"
timers.editTimer(timerId, newDescription = newDescription)
val updatedTimer = timers.getTimer(timerId)
assertEquals(newDescription, updatedTimer?.description)
}

@Test
fun `editTimer with timer that does not exist`(): Unit = runBlocking {
val timerId = 999L
val newName = "New Timer Name"
timers.editTimer(timerId, newName = newName)
val updatedTimer = timers.getTimer(timerId)
assertNull(updatedTimer)
}

@Test
fun `setSettings should update the timer settings`(): Unit = runBlocking {
// Arrange
val name = "Test Timer"
val creationTime = System.currentTimeMillis()

// Act
val timerId = timers.createTimer(name, ownerId = ownerId, creationTime = creationTime)

val settings = DbTimer.Settings.Patchable(
workTime = 10,
bigRestEnabled = true,
bigRestPer = 2,
bigRestTime = 10,
isEveryoneCanPause = true,
isConfirmationRequired = true,
restTime = 5,
)

// Act
runBlocking {
timers.setSettings(timerId, settings)
}

// Assert
val updatedTimer = runBlocking { timers.getTimer(timerId) }
assertEquals(settings.workTime, updatedTimer?.settings?.workTime)
assertEquals(settings.bigRestEnabled, updatedTimer?.settings?.bigRestEnabled)
assertEquals(settings.bigRestPer, updatedTimer?.settings?.bigRestPer)
assertEquals(settings.bigRestTime, updatedTimer?.settings?.bigRestTime)
assertEquals(settings.isEveryoneCanPause, updatedTimer?.settings?.isEveryoneCanPause)
assertEquals(settings.isConfirmationRequired, updatedTimer?.settings?.isConfirmationRequired)
assertEquals(settings.restTime, updatedTimer?.settings?.restTime)
}


@Test
fun `check get timers should return empty list if no timers`(): Unit = runBlocking {
val anotherUser = users.createUser("[email protected]", "Test2", null, System.currentTimeMillis())
timers.createTimer("Test", null, anotherUser, System.currentTimeMillis())

assert(timers.getTimers(ownerId, pageToken = null).value.isEmpty())
}

@Test
fun `check get timers should return correct list of timers`(): Unit = runBlocking {
val anotherUser = users.createUser("[email protected]", "Test2", null, System.currentTimeMillis())

val ids = buildList {
add(timers.createTimer("Test", null, anotherUser, System.currentTimeMillis()))
add(timers.createTimer("Test 2", null, anotherUser, System.currentTimeMillis()))
add(timers.createTimer("Test 3", null, anotherUser, System.currentTimeMillis()))
}.reversed()

val expected = ids.map { timers.getTimer(it) }

assertContentEquals(timers.getTimers(anotherUser, pageToken = null).value, expected)
}

@Test
fun `check get timers with page token returns correct page`(): Unit = runBlocking {
List(50) { index ->
timers.createTimer("Test ${index + 1}", null, ownerId, creationTime = index.toLong())
}

val first = timers.getTimers(ownerId, null)
val second = timers.getTimers(ownerId, first.nextPageToken!!)
val third = timers.getTimers(ownerId, second.nextPageToken!!)

assertEquals(actual = first.value.first().name, expected = "Test 50")
assertEquals(actual = first.value.last().name, expected = "Test 31")
assertEquals(actual = second.value.first().name, expected = "Test 30")
assertEquals(actual = third.value.first().name, expected = "Test 10")
assertEquals(actual = third.value.last().name, expected = "Test 1")
}
}

0 comments on commit 284a499

Please sign in to comment.