Repository navigation
Hotfix/195 username blank #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8f6bfac
#195: Verify username not blank
KaiRaiChu a739919
#195: Add username validation usecase
PPartisan 6c0191b
#195: Handled server timeout when requesting new username
KaiRaiChu 5ca388c
#195: Add validate username requests tests
KaiRaiChu c0c4589
#195: Up version to 0.1.5
KaiRaiChu c317619
#195: move logic out of repository class
sorellla File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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,74 @@ | ||
| package profile | ||
|
|
||
| import chatserver.LoggedInUserId | ||
| import chatserver.ProfileResult | ||
| import chatserver.ReadChatRepository | ||
| import chatserver.WriteChatRepository | ||
| import chatserver.profiles.SupabaseProfilesRepository.Profile | ||
| import kotlinx.coroutines.flow.Flow | ||
| import kotlinx.coroutines.flow.first | ||
| import kotlinx.coroutines.withTimeoutOrNull | ||
| import profile.ValidateUsernameRequest.Result.* | ||
| import kotlin.time.Duration | ||
| import kotlin.time.Duration.Companion.seconds | ||
|
|
||
| class ValidateUsernameRequest( | ||
| private val write: WriteChatRepository<String>, | ||
| private val read: ReadChatRepository<ProfileResult>, | ||
| private val userId: LoggedInUserId, | ||
| ) { | ||
| suspend operator fun invoke(username: String): Result { | ||
| if (username.isBlank()) { | ||
| return UsernameBlank | ||
| } | ||
|
|
||
| val loggedInUser = userId() | ||
| if (loggedInUser.isBlank()) { | ||
| return LoggedInUsernameBlank | ||
| } | ||
|
|
||
| val profiles = read.latest() | ||
| val me = profiles.item[loggedInUser] | ||
| if (me?.username == username) { | ||
| return UsernameUnchanged | ||
| } | ||
|
|
||
| return read.observe().waitForValue(Profile(loggedInUser, username)) | ||
| } | ||
|
|
||
| private suspend fun Flow<ProfileResult>.waitForValue( | ||
| target: Profile, | ||
| timeout: Duration = 4.seconds, | ||
| ): Result { | ||
| write.write(target.username) | ||
| val result = | ||
| withTimeoutOrNull(timeout) { | ||
| first { it.isOk && it.item[target.id] == target } | ||
| } | ||
| return if (result != null) Ok else Timeout | ||
| } | ||
|
|
||
| sealed interface Result { | ||
| val message: String | ||
|
|
||
| data object Ok : Result { | ||
| override val message = "" | ||
| } | ||
|
|
||
| data object UsernameBlank : Result { | ||
| override val message: String = "Requested username must not be blank." | ||
| } | ||
|
|
||
| data object LoggedInUsernameBlank : Result { | ||
| override val message = "User ID must not be blank." | ||
| } | ||
|
|
||
| data object UsernameUnchanged : Result { | ||
| override val message: String = "Current username must not match requested username." | ||
| } | ||
|
|
||
| data object Timeout : Result { | ||
| override val message: String = "Server timeout! Trying to change username" | ||
| } | ||
| } | ||
| } |
This file contains hidden or 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,104 @@ | ||
| package profile | ||
|
|
||
| import chatserver.LoggedInUserId | ||
| import chatserver.ProfileResult | ||
| import chatserver.ReadChatRepository | ||
| import chatserver.WriteChatRepository | ||
| import chatserver.profiles.SupabaseProfilesRepository.Profile | ||
| import io.kotest.matchers.equals.shouldBeEqual | ||
| import io.mockk.every | ||
| import io.mockk.just | ||
| import io.mockk.mockk | ||
| import io.mockk.runs | ||
| import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
| import kotlinx.coroutines.delay | ||
| import kotlinx.coroutines.flow.flow | ||
| import kotlinx.coroutines.flow.flowOf | ||
| import kotlinx.coroutines.launch | ||
| import kotlinx.coroutines.test.advanceTimeBy | ||
| import kotlinx.coroutines.test.runTest | ||
| import org.junit.jupiter.api.BeforeEach | ||
| import org.junit.jupiter.api.Test | ||
| import profile.ValidateUsernameRequest.Result.* | ||
| import kotlin.time.Duration.Companion.seconds | ||
|
|
||
| @OptIn(ExperimentalCoroutinesApi::class) | ||
| class ValidateUsernameRequestTest { | ||
| private lateinit var write: WriteChatRepository<String> | ||
| private lateinit var read: ReadChatRepository<ProfileResult> | ||
| private lateinit var userId: LoggedInUserId | ||
|
|
||
| private lateinit var request: ValidateUsernameRequest | ||
|
|
||
| @BeforeEach | ||
| fun setUp() { | ||
| write = | ||
| mockk<WriteChatRepository<String>>().also { | ||
| every { it.write(any()) } just runs | ||
| } | ||
| read = | ||
| mockk<ReadChatRepository<ProfileResult>>().also { | ||
| every { it.latest() } returns ProfileResult.ok(emptyMap()) | ||
| every { it.observe() } returns flowOf() | ||
| } | ||
| userId = | ||
| mockk<LoggedInUserId>().also { | ||
| every { it() } returns "Logged in user" | ||
| } | ||
|
|
||
| request = ValidateUsernameRequest(write, read, userId) | ||
| } | ||
|
|
||
| @Test | ||
| fun `when username blank, then return UsernameBlank`() = | ||
| runTest { | ||
| request("") shouldBeEqual UsernameBlank | ||
| } | ||
|
|
||
| @Test | ||
| fun `when logged in username is blank, then return LoggedInUsernameBlank`() = | ||
| runTest { | ||
| every { userId() } returns "" | ||
| request("Kai") shouldBeEqual LoggedInUsernameBlank | ||
| } | ||
|
|
||
| @Test | ||
| fun `when user requests username that matches their current username, then return UsernameUnchanged`() = | ||
| runTest { | ||
| every { userId() } returns "ka1" | ||
| every { read.latest() } returns ProfileResult.ok(mapOf("ka1" to Profile(id = "ka1", username = "kai"))) | ||
| request("kai") shouldBeEqual UsernameUnchanged | ||
| } | ||
|
|
||
| @Test | ||
| fun `when requesting valid username, and server does not respond, then return Timeout`() = | ||
| runTest { | ||
| every { userId() } returns "ka1" | ||
| every { read.latest() } returns ProfileResult.ok(mapOf("ka1" to Profile(id = "ka1", username = "tom"))) | ||
| every { read.observe() } returns flow { (delay(5.seconds)) } | ||
| val job = | ||
| backgroundScope.launch { | ||
| request("kai") shouldBeEqual Timeout | ||
| } | ||
| advanceTimeBy(5.seconds) | ||
| job.cancel() | ||
| } | ||
|
|
||
| @Test | ||
| fun `when requesting valid username, and server accepts, then return Ok`() = | ||
| runTest { | ||
| every { userId() } returns "ka1" | ||
| every { read.latest() } returns ProfileResult.ok(mapOf("ka1" to Profile(id = "ka1", username = "tom"))) | ||
| every { read.observe() } returns | ||
| flow { | ||
| delay(1.seconds) | ||
| emit(ProfileResult.ok(mapOf("ka1" to Profile(id = "ka1", username = "kai")))) | ||
| } | ||
| val job = | ||
| backgroundScope.launch { | ||
| request("kai") shouldBeEqual Ok | ||
| } | ||
| advanceTimeBy(5.seconds) | ||
| job.cancel() | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.