mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-04-17 21:38:29 +00:00
Trying AI generated UI tests
This commit is contained in:
parent
721e379b50
commit
0aca829ba7
7 changed files with 197 additions and 9 deletions
|
|
@ -267,6 +267,12 @@ dependencies {
|
||||||
implementation(libs.openid.appauth)
|
implementation(libs.openid.appauth)
|
||||||
|
|
||||||
implementation(libs.linphone)
|
implementation(libs.linphone)
|
||||||
|
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
testImplementation(libs.androidx.test.core)
|
||||||
|
testImplementation(libs.androidx.arch.core.testing)
|
||||||
|
testImplementation(libs.kotlinx.coroutines.test)
|
||||||
|
testImplementation(libs.mockk)
|
||||||
}
|
}
|
||||||
|
|
||||||
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
|
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
android:theme="@style/Theme.Linphone"
|
android:theme="@style/Theme.Linphone"
|
||||||
android:appCategory="social"
|
android:appCategory="social"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
tools:targetApi="35">
|
tools:targetApi="35">
|
||||||
|
|
||||||
<!-- Required for chat message & call notifications to be displayed in Android auto -->
|
<!-- Required for chat message & call notifications to be displayed in Android auto -->
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||||
|
*
|
||||||
|
* This file is part of linphone-android
|
||||||
|
* (see https://www.linphone.org).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.linphone.ui.main.sso.utils
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import net.openid.appauth.connectivity.ConnectionBuilder
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import javax.net.ssl.*
|
||||||
|
|
||||||
|
class AppAuthConnectionBuilder(private val trustAll: Boolean) : ConnectionBuilder {
|
||||||
|
override fun openConnection(uri: Uri): HttpURLConnection {
|
||||||
|
val conn = URL(uri.toString()).openConnection() as HttpURLConnection
|
||||||
|
if (trustAll && conn is HttpsURLConnection) {
|
||||||
|
conn.sslSocketFactory = TRUSTING_CONTEXT.socketFactory
|
||||||
|
conn.hostnameVerifier = HostnameVerifier { _, _ -> true }
|
||||||
|
}
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TRUSTING_CONTEXT: SSLContext by lazy {
|
||||||
|
val context = SSLContext.getInstance("TLS")
|
||||||
|
context.init(null, arrayOf<TrustManager>(object : X509TrustManager {
|
||||||
|
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
|
||||||
|
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
|
||||||
|
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
|
||||||
|
}), java.security.SecureRandom())
|
||||||
|
context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,10 +21,12 @@ package org.linphone.ui.main.sso.viewmodel
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import net.openid.appauth.AppAuthConfiguration
|
||||||
import net.openid.appauth.AuthState
|
import net.openid.appauth.AuthState
|
||||||
import net.openid.appauth.AuthorizationException
|
import net.openid.appauth.AuthorizationException
|
||||||
import net.openid.appauth.AuthorizationRequest
|
import net.openid.appauth.AuthorizationRequest
|
||||||
|
|
@ -39,10 +41,10 @@ import org.linphone.R
|
||||||
import org.linphone.core.Factory
|
import org.linphone.core.Factory
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.ui.GenericViewModel
|
import org.linphone.ui.GenericViewModel
|
||||||
|
import org.linphone.ui.main.sso.utils.AppAuthConnectionBuilder
|
||||||
import org.linphone.utils.Event
|
import org.linphone.utils.Event
|
||||||
import org.linphone.utils.FileUtils
|
import org.linphone.utils.FileUtils
|
||||||
import org.linphone.utils.TimestampUtils
|
import org.linphone.utils.TimestampUtils
|
||||||
import androidx.core.net.toUri
|
|
||||||
|
|
||||||
class SingleSignOnViewModel
|
class SingleSignOnViewModel
|
||||||
@UiThread
|
@UiThread
|
||||||
|
|
@ -127,9 +129,12 @@ class SingleSignOnViewModel
|
||||||
@UiThread
|
@UiThread
|
||||||
private fun singleSignOn() {
|
private fun singleSignOn() {
|
||||||
Log.i("$TAG Fetch from issuer [$singleSignOnUrl]")
|
Log.i("$TAG Fetch from issuer [$singleSignOnUrl]")
|
||||||
AuthorizationServiceConfiguration.fetchFromIssuer(
|
val connectionBuilder = AppAuthConnectionBuilder(true)
|
||||||
singleSignOnUrl.toUri(),
|
val callback = object : AuthorizationServiceConfiguration.RetrieveConfigurationCallback {
|
||||||
AuthorizationServiceConfiguration.RetrieveConfigurationCallback { serviceConfiguration, ex ->
|
override fun onFetchConfigurationCompleted(
|
||||||
|
serviceConfiguration: AuthorizationServiceConfiguration?,
|
||||||
|
ex: AuthorizationException?
|
||||||
|
) {
|
||||||
if (ex != null) {
|
if (ex != null) {
|
||||||
Log.e(
|
Log.e(
|
||||||
"$TAG Failed to fetch configuration from issuer [$singleSignOnUrl]: ${ex.errorDescription}"
|
"$TAG Failed to fetch configuration from issuer [$singleSignOnUrl]: ${ex.errorDescription}"
|
||||||
|
|
@ -137,13 +142,13 @@ class SingleSignOnViewModel
|
||||||
onErrorEvent.postValue(
|
onErrorEvent.postValue(
|
||||||
Event("Failed to fetch configuration from issuer $singleSignOnUrl")
|
Event("Failed to fetch configuration from issuer $singleSignOnUrl")
|
||||||
)
|
)
|
||||||
return@RetrieveConfigurationCallback
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceConfiguration == null) {
|
if (serviceConfiguration == null) {
|
||||||
Log.e("$TAG Service configuration is null!")
|
Log.e("$TAG Service configuration is null!")
|
||||||
onErrorEvent.postValue(Event("Service configuration is null"))
|
onErrorEvent.postValue(Event("Service configuration is null"))
|
||||||
return@RetrieveConfigurationCallback
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!::authState.isInitialized) {
|
if (!::authState.isInitialized) {
|
||||||
|
|
@ -169,10 +174,19 @@ class SingleSignOnViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
val authRequest = authRequestBuilder.build()
|
val authRequest = authRequestBuilder.build()
|
||||||
authService = AuthorizationService(coreContext.context)
|
val authConfig = AppAuthConfiguration.Builder()
|
||||||
|
.setConnectionBuilder(connectionBuilder)
|
||||||
|
.build()
|
||||||
|
authService = AuthorizationService(coreContext.context, authConfig)
|
||||||
val authIntent = authService.getAuthorizationRequestIntent(authRequest)
|
val authIntent = authService.getAuthorizationRequestIntent(authRequest)
|
||||||
startAuthIntentEvent.postValue(Event(authIntent))
|
startAuthIntentEvent.postValue(Event(authIntent))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorizationServiceConfiguration.fetchFromIssuer(
|
||||||
|
singleSignOnUrl.toUri(),
|
||||||
|
callback,
|
||||||
|
connectionBuilder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,7 +194,10 @@ class SingleSignOnViewModel
|
||||||
private fun performRefreshToken() {
|
private fun performRefreshToken() {
|
||||||
if (::authState.isInitialized) {
|
if (::authState.isInitialized) {
|
||||||
if (!::authService.isInitialized) {
|
if (!::authService.isInitialized) {
|
||||||
authService = AuthorizationService(coreContext.context)
|
val authConfig = AppAuthConfiguration.Builder()
|
||||||
|
.setConnectionBuilder(AppAuthConnectionBuilder(true))
|
||||||
|
.build()
|
||||||
|
authService = AuthorizationService(coreContext.context, authConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
val authStateJsonFile = File(corePreferences.ssoCacheFile)
|
val authStateJsonFile = File(corePreferences.ssoCacheFile)
|
||||||
|
|
|
||||||
9
app/src/main/res/xml/network_security_config.xml
Normal file
9
app/src/main/res/xml/network_security_config.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<base-config>
|
||||||
|
<trust-anchors>
|
||||||
|
<certificates src="system" />
|
||||||
|
<certificates src="user" />
|
||||||
|
</trust-anchors>
|
||||||
|
</base-config>
|
||||||
|
</network-security-config>
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||||
|
*
|
||||||
|
* This file is part of linphone-android
|
||||||
|
* (see https://www.linphone.org).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.linphone.ui.assistant.viewmodel
|
||||||
|
|
||||||
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkObject
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.linphone.LinphoneApplication
|
||||||
|
import org.linphone.core.CoreContext
|
||||||
|
import org.linphone.core.CorePreferences
|
||||||
|
|
||||||
|
class AccountLoginViewModelTest {
|
||||||
|
@get:Rule
|
||||||
|
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||||
|
|
||||||
|
private lateinit var viewModel: AccountLoginViewModel
|
||||||
|
private val coreContext: CoreContext = mockk(relaxed = true)
|
||||||
|
private val corePreferences: CorePreferences = mockk(relaxed = true)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mockkObject(LinphoneApplication.Companion)
|
||||||
|
every { LinphoneApplication.coreContext } returns coreContext
|
||||||
|
every { LinphoneApplication.corePreferences } returns corePreferences
|
||||||
|
|
||||||
|
// Mock postOnCoreThread to execute the lambda immediately
|
||||||
|
every { coreContext.postOnCoreThread(any()) } answers {
|
||||||
|
val lambda = invocation.args[0] as (org.linphone.core.Core) -> Unit
|
||||||
|
lambda(mockk(relaxed = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel = AccountLoginViewModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLoginButtonEnabledOnlyWhenBothFieldsAreFilled() {
|
||||||
|
// MediatorLiveData needs an observer to be active and trigger its logic
|
||||||
|
viewModel.loginEnabled.observeForever { }
|
||||||
|
|
||||||
|
// Initial state: both empty
|
||||||
|
viewModel.sipIdentity.value = ""
|
||||||
|
viewModel.password.value = ""
|
||||||
|
assertEquals("Login button should be disabled when both fields are empty", false, viewModel.loginEnabled.value)
|
||||||
|
|
||||||
|
// Only username filled
|
||||||
|
viewModel.sipIdentity.value = "testuser"
|
||||||
|
viewModel.password.value = ""
|
||||||
|
assertEquals("Login button should be disabled when password is empty", false, viewModel.loginEnabled.value)
|
||||||
|
|
||||||
|
// Only password filled
|
||||||
|
viewModel.sipIdentity.value = ""
|
||||||
|
viewModel.password.value = "testpassword"
|
||||||
|
assertEquals("Login button should be disabled when username is empty", false, viewModel.loginEnabled.value)
|
||||||
|
|
||||||
|
// Both filled
|
||||||
|
viewModel.sipIdentity.value = "testuser"
|
||||||
|
viewModel.password.value = "testpassword"
|
||||||
|
assertEquals("Login button should be enabled when both fields are filled", true, viewModel.loginEnabled.value)
|
||||||
|
|
||||||
|
// Username with only spaces
|
||||||
|
viewModel.sipIdentity.value = " "
|
||||||
|
viewModel.password.value = "testpassword"
|
||||||
|
assertEquals("Login button should be disabled when username is only whitespace", false, viewModel.loginEnabled.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,11 @@ dotsIndicator = "5.1.0"
|
||||||
photoview = "2.3.0"
|
photoview = "2.3.0"
|
||||||
openidAppauth = "0.11.1"
|
openidAppauth = "0.11.1"
|
||||||
linphone = "5.5.+"
|
linphone = "5.5.+"
|
||||||
|
junit = "4.13.2"
|
||||||
|
androidxTestCore = "1.6.1"
|
||||||
|
androidxArchCore = "2.2.0"
|
||||||
|
kotlinxCoroutinesTest = "1.10.1"
|
||||||
|
mockk = "1.13.16"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-annotations = { group = "androidx.annotation", name = "annotation", version.ref = "annotations" }
|
androidx-annotations = { group = "androidx.annotation", name = "annotation", version.ref = "annotations" }
|
||||||
|
|
@ -69,6 +74,12 @@ openid-appauth = { group = "net.openid", name = "appauth", version.ref = "openid
|
||||||
|
|
||||||
linphone = { group = "org.linphone", name = "linphone-sdk-android", version.ref = "linphone" }
|
linphone = { group = "org.linphone", name = "linphone-sdk-android", version.ref = "linphone" }
|
||||||
|
|
||||||
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" }
|
||||||
|
androidx-arch-core-testing = { group = "androidx.arch.core", name = "core-testing", version.ref = "androidxArchCore" }
|
||||||
|
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
|
||||||
|
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue