Skip to content

Commit

Permalink
Merge pull request #25 from PeriodPals/feat/authentication/model
Browse files Browse the repository at this point in the history
Define Model for Supabase authentication
  • Loading branch information
coaguila authored Oct 15, 2024
2 parents 2a85093 + d9c539e commit 196ee1b
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 7 deletions.
16 changes: 9 additions & 7 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.ktfmt)
// alias(libs.plugins.sonar)
//alias(libs.plugins.sonar)
alias(libs.plugins.compose.compiler)
id("jacoco")

Expand Down Expand Up @@ -104,18 +104,18 @@ sonar {
// Each path may be absolute or relative to the project base directory.
property(
"sonar.junit.reportPaths",
"${project.layout.buildDirectory.get()}/test-results/testDebugunitTest/",
"${project.layout.buildDirectory.get()}/test-results/testDebugunitTest/"
)
// Paths to xml files with Android Lint issues. If the main flavor is changed, this file will
// have to be changed too.
property(
"sonar.androidLint.reportPaths",
"${project.layout.buildDirectory.get()}/reports/lint-results-debug.xml",
"${project.layout.buildDirectory.get()}/reports/lint-results-debug.xml"
)
// Paths to JaCoCo XML coverage report files.
property(
"sonar.coverage.jacoco.xmlReportPaths",
"${project.layout.buildDirectory.get()}/reports/jacoco/jacocoTestReport/jacocoTestReport.xml",
"${project.layout.buildDirectory.get()}/reports/jacoco/jacocoTestReport/jacocoTestReport.xml"
)
}
}
Expand All @@ -124,6 +124,8 @@ sonar {
fun DependencyHandlerScope.globalTestImplementation(dep: Any) {
androidTestImplementation(dep)
testImplementation(dep)
testImplementation(libs.robolectric)
testImplementation("org.mockito:mockito-core:4.0.0")
}

dependencies {
Expand Down Expand Up @@ -155,6 +157,7 @@ dependencies {
implementation(libs.supabase.postgrest.kt)
implementation(libs.auth.kt)
implementation(libs.realtime.kt)
implementation(libs.ktor.client.android.v300rc1)
implementation(libs.kotlinx.serialization.json.v162)

implementation(libs.androidx.core.ktx)
Expand All @@ -167,7 +170,7 @@ dependencies {
implementation(libs.androidx.espresso.core)

testImplementation(libs.junit)

testImplementation(libs.mockito.kotlin)
globalTestImplementation(libs.androidx.junit)
globalTestImplementation(libs.androidx.espresso.core)

Expand Down Expand Up @@ -243,6 +246,5 @@ tasks.register("jacocoTestReport", JacocoReport::class) {
fileTree(project.layout.buildDirectory.get()) {
include("outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec")
include("outputs/code_coverage/debugAndroidTest/connected/*/coverage.ec")
}
)
})
}
22 changes: 22 additions & 0 deletions app/src/main/java/com/android/periodpals/model/auth/AuthModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.android.periodpals.model.auth

interface AuthModel {

suspend fun login(
userEmail: String,
userPassword: String,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit,
)

suspend fun register(
userEmail: String,
userPassword: String,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit,
)

suspend fun logout(onSuccess: () -> Unit, onFailure: (Exception) -> Unit)

suspend fun isUserLoggedIn(token: String, onSuccess: () -> Unit, onFailure: (Exception) -> Unit)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.android.periodpals.model.auth

import android.util.Log
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.auth.Auth
import io.github.jan.supabase.auth.SignOutScope
import io.github.jan.supabase.auth.providers.builtin.Email

private const val TAG = "AuthModelSupabase"

class AuthModelSupabase(
private val supabase: SupabaseClient,
private val pluginManagerWrapper: PluginManagerWrapper =
PluginManagerWrapperImpl(supabase.pluginManager),
) : AuthModel {

private val supabaseAuth: Auth = pluginManagerWrapper.getAuthPlugin()

override suspend fun register(
userEmail: String,
userPassword: String,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit,
) {
try {
supabaseAuth.signUpWith(Email) {
email = userEmail
password = userPassword
}
Log.d(TAG, "register: successfully registered the user")
onSuccess()
} catch (e: Exception) {
Log.d(TAG, "register: failed to register the user: ${e.message}")
onFailure(e)
}
}

override suspend fun login(
userEmail: String,
userPassword: String,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit,
) {
try {
supabaseAuth.signInWith(Email) {
email = userEmail
password = userPassword
}
Log.d(TAG, "login: successfully logged in the user")
onSuccess()
} catch (e: Exception) {
Log.d(TAG, "login: failed to log in the user: ${e.message}")
onFailure(e)
}
}

override suspend fun logout(onSuccess: () -> Unit, onFailure: (Exception) -> Unit) {
try {
supabaseAuth.signOut(SignOutScope.LOCAL)
Log.d(TAG, "logout: successfully logged out the user")
onSuccess()
} catch (e: Exception) {
Log.d(TAG, "logout: failed to log out the user: ${e.message}")
onFailure(e)
}
}

override suspend fun isUserLoggedIn(
token: String,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit,
) {
try {
supabaseAuth.retrieveUser(token)
supabaseAuth.refreshCurrentSession() // will throw an error if the user is not logged in
Log.d(TAG, "isUserLoggedIn: user is logged in")
onSuccess()
} catch (e: Exception) {
Log.d(TAG, "isUserLoggedIn: user is not logged in: ${e.message}")
onFailure(e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.android.periodpals.model.auth

import io.github.jan.supabase.auth.Auth
import io.github.jan.supabase.plugins.PluginManager

interface PluginManagerWrapper {
fun getAuthPlugin(): Auth
}

class PluginManagerWrapperImpl(private val pluginManager: PluginManager) : PluginManagerWrapper {
override fun getAuthPlugin(): Auth {
return pluginManager.getPlugin(Auth)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.android.periodpals.model.auth

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.auth.Auth
import io.github.jan.supabase.auth.AuthConfig
import io.github.jan.supabase.auth.deepLinkOrNull
import io.github.jan.supabase.auth.providers.builtin.Email
import junit.framework.TestCase.fail
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.anyString
import org.mockito.Mockito.doThrow
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull

class AuthModelSupabaseTest {

@Mock private lateinit var supabaseClient: SupabaseClient

@Mock private lateinit var pluginManagerWrapper: PluginManagerWrapper

@Mock private lateinit var auth: Auth

@Mock private lateinit var authConfig: AuthConfig

private lateinit var authModel: AuthModelSupabase

@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
auth = mock(Auth::class.java)

`when`(auth.config).thenReturn(authConfig)
`when`(pluginManagerWrapper.getAuthPlugin()).thenReturn(auth)
`when`(authConfig.deepLinkOrNull).thenReturn("https://example.com")
authModel = AuthModelSupabase(supabaseClient, pluginManagerWrapper)
}

@Test
fun `register success`() = runBlocking {
`when`(auth.signUpWith(any<Email>(), anyOrNull(), any())).thenAnswer {}

var successCalled = false
authModel.register(
"test@example.com",
"password",
{ successCalled = true },
{ fail("Should not call onFailure") },
)

assert(successCalled)
}

@Test
fun `register failure`() = runBlocking {
val exception = RuntimeException("Registration failed")
doThrow(exception).`when`(auth).signUpWith(any<Email>(), anyOrNull(), any())

var failureCalled = false
authModel.register(
"test@example.com",
"password",
{ fail("Should not call onSuccess") },
{ failureCalled = true },
)

assert(failureCalled)
}

@Test
fun `login success`() = runBlocking {
`when`(auth.signInWith(Email)).thenReturn(Unit)

var successCalled = false
authModel.login(
"test@example.com",
"password",
{ successCalled = true },
{ fail("Should not call onFailure") },
)

assert(successCalled)
}

@Test
fun `login failure`() = runBlocking {
val exception = RuntimeException("Login failed")
doThrow(exception).`when`(auth).signInWith(any<Email>(), anyOrNull(), any())

var failureCalled = false
authModel.login(
"test@example.com",
"password",
{ fail("Should not call onSuccess") },
{ failureCalled = true },
)

assert(failureCalled)
}

@Test
fun `logout success`() = runBlocking {
`when`(auth.signOut(any())).thenReturn(Unit)

var successCalled = false
authModel.logout({ successCalled = true }, { fail("Should not call onFailure") })

assert(successCalled)
}

@Test
fun `logout failure`() = runBlocking {
val exception = RuntimeException("Logout failed")
doThrow(exception).`when`(auth).signOut(any())

var failureCalled = false
authModel.logout({ fail("Should not call onSuccess") }, { failureCalled = true })

assert(failureCalled)
}

@Test
fun `isUserLoggedIn success`() = runBlocking {
`when`(auth.retrieveUser(anyString())).thenReturn(null)
`when`(auth.refreshCurrentSession()).thenReturn(Unit)

var successCalled = false
authModel.isUserLoggedIn(
"token",
{ successCalled = true },
{ fail("Should not call onFailure") },
)

assert(successCalled)
}

@Test
fun `isUserLoggedIn failure`() = runBlocking {
val exception = RuntimeException("User not logged in")
doThrow(exception).`when`(auth).retrieveUser(anyString())
doThrow(exception).`when`(auth).refreshCurrentSession()

var failureCalled = false
authModel.isUserLoggedIn(
"token",
{ fail("Should not call onSuccess") },
{ failureCalled = true },
)

assert(failureCalled)
}
}
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ mockitoCore = "3.12.4"
mockitoInline = "3.12.4"
mockitoKotlin = "5.4.0"
mockk = "1.13.7"
mockitoInlineVersion = "latest"
mockitoJunitJupiter = "1.10"

# Testing UI
espressoCore = "3.6.1"
Expand Down Expand Up @@ -114,6 +116,8 @@ material = { module = "com.google.android.material:material", version.ref = "mat
mockito-android = { module = "org.mockito:mockito-android", version.ref = "mockitoAndroid" }
mockito-core-v540 = { module = "org.mockito:mockito-core", version.ref = "mockitoMockitoCore" }
mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoInline" }
mockito-inline-vlatest = { module = "org.mockito:mockito-inline", version.ref = "mockitoInlineVersion" }
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockitoJunitJupiter" }
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlin" }
mockk-agent = { module = "io.mockk:mockk-agent", version.ref = "mockkAgent" }
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockkAndroid" }
Expand Down

0 comments on commit 196ee1b

Please sign in to comment.