mirror of
https://github.com/ProtonMail/protoncore_android.git
synced 2026-05-15 09:50:41 +00:00
chore: Introduced mock-proxy module with SDK and pull mock files plugin.
This commit is contained in:
@@ -234,6 +234,7 @@ Core libraries coordinates can be found under [coordinates section](#coordinates
|
||||
| me.proton.core:test-android |
|
||||
| me.proton.core:test-android-instrumented |
|
||||
| me.proton.core:test-kotlin |
|
||||
| me.proton.core:test-mock-proxy |
|
||||
| me.proton.core:test-quark |
|
||||
| me.proton.core:test-performance |
|
||||
| me.proton.core:test-rule |
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"request": {
|
||||
"exactUrl": [
|
||||
"/api/v4/test1"
|
||||
],
|
||||
"method": "post"
|
||||
},
|
||||
"response": {
|
||||
"statusCode": 200,
|
||||
"headers": {
|
||||
"header1": "header1-1",
|
||||
"header2": "header2-2"
|
||||
},
|
||||
"body": {
|
||||
"Code": 1000,
|
||||
"Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nY+6iVlkGjwe9tBlF3aDN8dXR4W7U3mX0r4RjfwxckmX3YSwfKCm/zM33b0As8mS8A5kpMSnvGs+E/Mc8CNOPQW6Oulxw4e0prdW9gEZEUCSJA3z02HWGk13/7zjmdzyWiU8yiWtmFga6i8GwfXUyjufS8T+1UYMvCa/HE8R6i2HNgfhhIQ0lKWqcO9DpAK/icPZUUtjVE4Xh2IDLDvjoQbiRJEo4bU6zMYEGcqy0g7a1Vz0IvSvQRFeMHxRuqtV8NtuDzISSm/gj/4DylPmOUQBdt1VPD4UKhPHLfS/MKt7MJz4cdnc6vrIsY2QDVCZvNa0zckaARjanz3MLPOwd/A==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1j8JEDUFhcTpUY8mAABgEAEA/sDprqlXbiuE+RFWe9HW\n3u9jxwTXpPVXHtkkFCILzokBAOvXQ7yKDrAEXH4f045IyVoFwnhBc6OcPD/x\ncmCxAokC\n=JC9y\n-----END PGP SIGNATURE-----\n",
|
||||
"ServerEphemeral": "S0R4Lfi2t4Bu+StgU39uXpZEYPfG/4vo2WuCGB7f0CfLFfg2he1+HvKCatly/n89KBQx+ei7uoVvD+iDxlaKzPyMvJ+ZiLB73QNaDwsWesnEYmA8ozJgoCRRJf+IBwx7fZAMD/mp9T5J6xscPULmLdFVNrAGjX+v99iTpVanXbFDZ7I/YA3gxjtcrgwyUuQkpkoHyVkuKnodH206e5cYX3szyKHzI48MsZy+UJ2gfuzCT1sYY43+tC1AKet7DNbthfDN9ERMfjWTUghba54DHoscy9GiVHibjD33xoQXpIeubMvI87mrv5Uu0voBYKsJ8TfZIhs/I0j+Xtrmskc5xQ==",
|
||||
"Version": 4,
|
||||
"Salt": "DnJh2bj2RBChNQ==",
|
||||
"SRPSession": "2f34224721344fdb5a62e6f441a496a7"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"test": "test case name2",
|
||||
"description": "test case description 2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"request": {
|
||||
"exactUrl": [
|
||||
"/api/v4/test2"
|
||||
],
|
||||
"method": "post"
|
||||
},
|
||||
"response": {
|
||||
"statusCode": 200,
|
||||
"headers": {"header11": "header11-11", "header22": "header22-22"},
|
||||
"body": {
|
||||
"Code": 1000,
|
||||
"Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nY+6iVlkGjwe9tBlF3aDN8dXR4W7U3mX0r4RjfwxckmX3YSwfKCm/zM33b0As8mS8A5kpMSnvGs+E/Mc8CNOPQW6Oulxw4e0prdW9gEZEUCSJA3z02HWGk13/7zjmdzyWiU8yiWtmFga6i8GwfXUyjufS8T+1UYMvCa/HE8R6i2HNgfhhIQ0lKWqcO9DpAK/icPZUUtjVE4Xh2IDLDvjoQbiRJEo4bU6zMYEGcqy0g7a1Vz0IvSvQRFeMHxRuqtV8NtuDzISSm/gj/4DylPmOUQBdt1VPD4UKhPHLfS/MKt7MJz4cdnc6vrIsY2QDVCZvNa0zckaARjanz3MLPOwd/A==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1j8JEDUFhcTpUY8mAABgEAEA/sDprqlXbiuE+RFWe9HW\n3u9jxwTXpPVXHtkkFCILzokBAOvXQ7yKDrAEXH4f045IyVoFwnhBc6OcPD/x\ncmCxAokC\n=JC9y\n-----END PGP SIGNATURE-----\n",
|
||||
"ServerEphemeral": "S0R4Lfi2t4Bu+StgU39uXpZEYPfG/4vo2WuCGB7f0CfLFfg2he1+HvKCatly/n89KBQx+ei7uoVvD+iDxlaKzPyMvJ+ZiLB73QNaDwsWesnEYmA8ozJgoCRRJf+IBwx7fZAMD/mp9T5J6xscPULmLdFVNrAGjX+v99iTpVanXbFDZ7I/YA3gxjtcrgwyUuQkpkoHyVkuKnodH206e5cYX3szyKHzI48MsZy+UJ2gfuzCT1sYY43+tC1AKet7DNbthfDN9ERMfjWTUghba54DHoscy9GiVHibjD33xoQXpIeubMvI87mrv5Uu0voBYKsJ8TfZIhs/I0j+Xtrmskc5xQ==",
|
||||
"Version": 4,
|
||||
"Salt": "DnJh2bj2RBChNQ==",
|
||||
"SRPSession": "2f34224721344fdb5a62e6f441a496a7"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"test": "test case name2",
|
||||
"description": "test case description 2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"description": "scenario description",
|
||||
"updateFile": false,
|
||||
"mockFiles": ["scenarios/auth_scenario1/auth_mock1.json", "scenarios/auth_scenario1/auth_mock2.json"]
|
||||
}
|
||||
@@ -29,6 +29,7 @@ class ProtonIssueRegistry : IssueRegistry() {
|
||||
override val issues: List<Issue> get() = listOf(
|
||||
HardcodedCoroutineDispatcherDetector,
|
||||
NoSerialNameAnnotationDetector,
|
||||
NotConstantStringDetector
|
||||
NotConstantStringDetector,
|
||||
UsesCleartextTrafficManifestDetector
|
||||
).flatMap { it.ISSUES }
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package ch.protonmail.libs.lint.detectors
|
||||
|
||||
import com.android.tools.lint.detector.api.*
|
||||
import org.w3c.dom.Element
|
||||
|
||||
class UsesCleartextTrafficManifestDetector : Detector(), XmlScanner {
|
||||
|
||||
override fun getApplicableElements(): Collection<String> {
|
||||
// Look for <uses-permission> and <application> tags
|
||||
return listOf("uses-permission", "application")
|
||||
}
|
||||
|
||||
override fun visitElement(context: XmlContext, element: Element) {
|
||||
if (element.tagName == "application") {
|
||||
val cleartextTraffic = element.getAttribute("android:usesCleartextTraffic")
|
||||
if (cleartextTraffic == "true") {
|
||||
context.report(
|
||||
ISSUE_FORBIDDEN_USES_CLEARTEXT,
|
||||
element,
|
||||
context.getLocation(element),
|
||||
"`usesCleartextTraffic=\"true\"` must not be included in the release manifest."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ISSUE_FORBIDDEN_USES_CLEARTEXT = Issue.create(
|
||||
id = "ForbiddenUsesCleartextTraffic",
|
||||
briefDescription = "Forbidden usesCleartextTraffic in release build",
|
||||
explanation = "Cleartext traffic should not be enabled in the release manifest.",
|
||||
category = Category.SECURITY,
|
||||
priority = 9,
|
||||
severity = Severity.ERROR,
|
||||
implementation = Implementation(
|
||||
ForbiddenManifestCheck::class.java,
|
||||
Scope.MANIFEST_SCOPE
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Changes
|
||||
|
||||
- Added product flavor dimension support to environment config plugin.
|
||||
- Added product flavor dimension support to environment config plugin.
|
||||
|
||||
## [1.3.0]
|
||||
|
||||
|
||||
+10
-2
@@ -152,8 +152,6 @@ Use internally in core project to orchestrate [core libraries publication](../RE
|
||||
This plugin should be applied to either build flavor or application build type android extension
|
||||
in `build.gradle.kts` file.
|
||||
|
||||
*
|
||||
|
||||
Generates `build/generated/source/envConfig/{flavor}/{buildType}/EnvironmentConfigurationDefaults.java`
|
||||
similarly to `BuildConfig.java`.
|
||||
Generated class is then added to source directories and can be accessed at runtime
|
||||
@@ -162,6 +160,16 @@ Generated class is then added to source directories and can be accessed at runti
|
||||
* By default (if no configuration provided in build.gradle.kts) generates a default production
|
||||
config (`api.proton.me`)
|
||||
|
||||
## Mock-proxy file puller plugin
|
||||
|
||||
- Plugin id: `me.proton.core.gradle-plugins.mock-proxy`
|
||||
- Published on MavenCentral.
|
||||
|
||||
Allows to automatically pull recorded mock files from local mock-proxy server.
|
||||
In order to use it define below environment variables in your `local.properties` file:
|
||||
1. `MOCK_PROXY_RECORD_DIR=/path-to-local-mock-roxy-repository`
|
||||
2. `PROJECT_MOCK_FILES_DIR=/path-to-mock-files-dir-in-android-project`
|
||||
|
||||
```kotlin
|
||||
build.gradle.kts
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ public object Module {
|
||||
public const val androidTest: String = "$test:test-android"
|
||||
public const val androidInstrumentedTest: String = "$androidTest:test-android-instrumented"
|
||||
public const val quark: String = "$test:test-quark"
|
||||
public const val mockProxy: String = "$test:test-mock-proxy"
|
||||
public const val testPerformance: String = "$test:test-performance"
|
||||
public const val testRule: String = "$test:test-rule"
|
||||
// endregion
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Proton Technologies AG
|
||||
* This file is part of Proton Technologies AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore 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.
|
||||
*
|
||||
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import org.gradle.kotlin.dsl.`java-gradle-plugin`
|
||||
import org.gradle.kotlin.dsl.`kotlin-dsl`
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
import org.gradle.kotlin.dsl.gradlePlugin
|
||||
import org.gradle.kotlin.dsl.implementation
|
||||
import org.gradle.kotlin.dsl.kotlin
|
||||
import org.gradle.kotlin.dsl.repositories
|
||||
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
kotlin("jvm")
|
||||
`java-gradle-plugin`
|
||||
}
|
||||
|
||||
publishOption.shouldBePublishedAsPlugin = true
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("mockFilesPuller") {
|
||||
id = "me.proton.core.gradle-plugins.mock-proxy"
|
||||
displayName = "Proton pull recordings mock-proxy plugin."
|
||||
description = "Pulls recorded files from and to defined locations."
|
||||
implementationClass = "mockproxy.MockFilesPullerPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven("https://plugins.gradle.org/m2/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(gradleApi())
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin")
|
||||
compileOnly(libs.android.gradle)
|
||||
api(libs.easyGradle.androidDsl)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package mockproxy
|
||||
|
||||
import com.android.build.gradle.internal.coverage.JacocoReportTask.JacocoReportWorkerAction.Companion.logger
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.util.Properties
|
||||
|
||||
class MockFilesPullerPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
val properties = Properties().apply {
|
||||
runCatching {
|
||||
load(project.rootDir.resolve("local.properties").inputStream())
|
||||
}.recoverCatching {
|
||||
load(project.rootDir.resolve("private.properties").inputStream())
|
||||
}.getOrElse { throwable ->
|
||||
// Provide empty properties to allow the app to be built without secrets
|
||||
logger.warn(
|
||||
"MockFilePullerPlugin: local.properties or private.properties " +
|
||||
"file not found. Proceeding with empty properties." +
|
||||
"\n Message ${throwable.message}"
|
||||
)
|
||||
Properties()
|
||||
}
|
||||
}
|
||||
|
||||
project.tasks.register("pullMockFiles", PullMockFilesTask::class.java) {
|
||||
// Default setup: destination directory within the project
|
||||
destinationDir =
|
||||
project.layout.projectDirectory.dir(
|
||||
properties.getProperty("PROJECT_MOCK_FILES_DIR")
|
||||
).asFile
|
||||
sourceDir =
|
||||
project.layout.projectDirectory.dir(
|
||||
properties.getProperty("MOCK_PROXY_RECORD_DIR")
|
||||
).asFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class PullMockFilesTask : DefaultTask() {
|
||||
@InputDirectory
|
||||
lateinit var sourceDir: File
|
||||
|
||||
@OutputDirectory
|
||||
lateinit var destinationDir: File
|
||||
|
||||
init {
|
||||
group = "Mock"
|
||||
description = "Pulls mock files from a source directory to the destination directory."
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun pullMockFiles() {
|
||||
logger.lifecycle(
|
||||
"MockFilePullerPlugin: Pulling mock files from $sourceDir to $destinationDir"
|
||||
)
|
||||
|
||||
if (!sourceDir.exists()) {
|
||||
throw IllegalArgumentException("Source directory does not exist: $sourceDir")
|
||||
}
|
||||
destinationDir.mkdirs()
|
||||
sourceDir.walkTopDown().forEach { file ->
|
||||
val destinationFile = File(destinationDir, file.relativeTo(sourceDir).path)
|
||||
if (file.isDirectory) {
|
||||
destinationFile.mkdirs()
|
||||
} else {
|
||||
Files.copy(
|
||||
file.toPath(),
|
||||
destinationFile.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
logger.lifecycle(
|
||||
"MockFilePullerPlugin: Copied: ${file.absolutePath} ->" +
|
||||
" ${destinationFile.absolutePath}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,8 @@ include(
|
||||
"tests",
|
||||
"include-core-build",
|
||||
"publish-core-libraries",
|
||||
"environment-configuration"
|
||||
"environment-configuration",
|
||||
"mock-proxy"
|
||||
)
|
||||
|
||||
pluginManagement {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,76 @@
|
||||
# Mock Proxy SDK
|
||||
|
||||
Mock proxy SDK allows to control mock proxy server parameters from inside your test.
|
||||
|
||||
Supported features:
|
||||
1. Single route mock.
|
||||
2. Scenario mocking (collection of routes).
|
||||
3. Control bandwidth.
|
||||
4. Control latency.
|
||||
|
||||
## Start using
|
||||
To start using SDK create an instance of `MockClient(baseUrl: String)`:
|
||||
```kotlin
|
||||
val mockClient = MockClient(baseUrl)
|
||||
```
|
||||
Here `baseUrl` can be an atlas environment or the local host URL.
|
||||
|
||||
If you run tests locally using emulator use Android localhost baseUrl value from `Constants`:
|
||||
```kotlin
|
||||
public const val EMULATOR_LOCALHOST: String = "http://10.0.2.2:3001/"
|
||||
```
|
||||
|
||||
`MockClient` instance creation with pre-defined local host value for Android emulator:
|
||||
```kotlin
|
||||
val mockClient = MockClient(EMULATOR_LOCALHOST)
|
||||
```
|
||||
|
||||
## Mock single route
|
||||
SDK assumes mock files are stored in app or test assets.
|
||||
|
||||
Mock single route from app assets:
|
||||
```kotlin
|
||||
mockClient.setStaticMockFromAppAssets(routeFilePath)
|
||||
```
|
||||
|
||||
Mock single route from test assets:
|
||||
```kotlin
|
||||
mockClient.setStaticMockFromTestAssets(routeFilePath)
|
||||
```
|
||||
## Mock scenario
|
||||
Mock scenario route from app assets (both app and test assets):
|
||||
```kotlin
|
||||
mockClient.setScenarioFromAssets(scenarioFilePath)
|
||||
```
|
||||
|
||||
Reset scenario from app assets:
|
||||
```kotlin
|
||||
mockClient.resetScenarioFromAppAssets(scenarioFilePath)
|
||||
```
|
||||
|
||||
Reset scenario from test assets:
|
||||
```kotlin
|
||||
mockClient.resetScenarioFromTestAssets(scenarioFilePath)
|
||||
```
|
||||
|
||||
## Get, set and reset latency
|
||||
|
||||
```kotlin
|
||||
|
||||
val latencyInfo = mockClient.getLatency()
|
||||
mockClient.setLatency(LatencyLevel.HIGH)
|
||||
mockClient.resetLatency()
|
||||
```
|
||||
|
||||
## Get, set and reset bandwidth
|
||||
|
||||
```kotlin
|
||||
val bandwidth = mockClient.getBandwidth()
|
||||
mockClient.setBandwidth(BandwidthLimit._4G)
|
||||
mockClient.resetBandwidth()
|
||||
```
|
||||
|
||||
## Resetting the state (reset all)
|
||||
```kotlin
|
||||
mockClient.resetAllMocks()
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
import studio.forface.easygradle.dsl.android.`android-test-runner`
|
||||
import studio.forface.easygradle.dsl.android.androidTestImplementation
|
||||
import studio.forface.easygradle.dsl.android.`hilt-android-testing`
|
||||
import studio.forface.easygradle.dsl.android.retrofit
|
||||
import studio.forface.easygradle.dsl.android.`retrofit-kotlin-serialization`
|
||||
import studio.forface.easygradle.dsl.implementation
|
||||
import studio.forface.easygradle.dsl.`kotlin-test`
|
||||
import studio.forface.easygradle.dsl.`kotlin-test-junit`
|
||||
import studio.forface.easygradle.dsl.`serialization-json`
|
||||
import studio.forface.easygradle.dsl.squareup
|
||||
import studio.forface.easygradle.dsl.version
|
||||
|
||||
plugins {
|
||||
protonAndroidLibrary
|
||||
kotlin("plugin.serialization")
|
||||
jacoco
|
||||
}
|
||||
|
||||
protonCoverage.disabled.set(true)
|
||||
publishOption.shouldBePublishedAsLib = true
|
||||
|
||||
android {
|
||||
namespace = "me.proton.core.test.mockproxy"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(
|
||||
junit,
|
||||
retrofit,
|
||||
okhttp,
|
||||
`android-test-runner`,
|
||||
`hilt-android-testing`,
|
||||
`kotlin-test`,
|
||||
`kotlin-test-junit`,
|
||||
`retrofit-kotlin-serialization`,
|
||||
`serialization-json`,
|
||||
squareup("okhttp3", "okhttp-tls") version `okHttp version`,
|
||||
)
|
||||
|
||||
androidTestImplementation(
|
||||
`kotlin-test`
|
||||
)
|
||||
|
||||
androidTestUtil(`androidx-test-orchestrator`)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"request": {
|
||||
"exactUrl": [
|
||||
"/api/v4/test1"
|
||||
],
|
||||
"method": "post"
|
||||
},
|
||||
"response": {
|
||||
"statusCode": 200,
|
||||
"headers": {
|
||||
"header1": "header1-1",
|
||||
"header2": "header2-2"
|
||||
},
|
||||
"body": {
|
||||
"Code": 1000,
|
||||
"Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nY+6iVlkGjwe9tBlF3aDN8dXR4W7U3mX0r4RjfwxckmX3YSwfKCm/zM33b0As8mS8A5kpMSnvGs+E/Mc8CNOPQW6Oulxw4e0prdW9gEZEUCSJA3z02HWGk13/7zjmdzyWiU8yiWtmFga6i8GwfXUyjufS8T+1UYMvCa/HE8R6i2HNgfhhIQ0lKWqcO9DpAK/icPZUUtjVE4Xh2IDLDvjoQbiRJEo4bU6zMYEGcqy0g7a1Vz0IvSvQRFeMHxRuqtV8NtuDzISSm/gj/4DylPmOUQBdt1VPD4UKhPHLfS/MKt7MJz4cdnc6vrIsY2QDVCZvNa0zckaARjanz3MLPOwd/A==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1j8JEDUFhcTpUY8mAABgEAEA/sDprqlXbiuE+RFWe9HW\n3u9jxwTXpPVXHtkkFCILzokBAOvXQ7yKDrAEXH4f045IyVoFwnhBc6OcPD/x\ncmCxAokC\n=JC9y\n-----END PGP SIGNATURE-----\n",
|
||||
"ServerEphemeral": "S0R4Lfi2t4Bu+StgU39uXpZEYPfG/4vo2WuCGB7f0CfLFfg2he1+HvKCatly/n89KBQx+ei7uoVvD+iDxlaKzPyMvJ+ZiLB73QNaDwsWesnEYmA8ozJgoCRRJf+IBwx7fZAMD/mp9T5J6xscPULmLdFVNrAGjX+v99iTpVanXbFDZ7I/YA3gxjtcrgwyUuQkpkoHyVkuKnodH206e5cYX3szyKHzI48MsZy+UJ2gfuzCT1sYY43+tC1AKet7DNbthfDN9ERMfjWTUghba54DHoscy9GiVHibjD33xoQXpIeubMvI87mrv5Uu0voBYKsJ8TfZIhs/I0j+Xtrmskc5xQ==",
|
||||
"Version": 4,
|
||||
"Salt": "DnJh2bj2RBChNQ==",
|
||||
"SRPSession": "2f34224721344fdb5a62e6f441a496a7"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"test": "test case name2",
|
||||
"description": "test case description 2"
|
||||
},
|
||||
"name": "test mock 1",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"request": {
|
||||
"exactUrl": [
|
||||
"/api/v4/test2"
|
||||
],
|
||||
"method": "post"
|
||||
},
|
||||
"response": {
|
||||
"statusCode": 200,
|
||||
"headers": {"header11": "header11-11", "header22": "header22-22"},
|
||||
"body": {
|
||||
"Code": 1000,
|
||||
"Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nY+6iVlkGjwe9tBlF3aDN8dXR4W7U3mX0r4RjfwxckmX3YSwfKCm/zM33b0As8mS8A5kpMSnvGs+E/Mc8CNOPQW6Oulxw4e0prdW9gEZEUCSJA3z02HWGk13/7zjmdzyWiU8yiWtmFga6i8GwfXUyjufS8T+1UYMvCa/HE8R6i2HNgfhhIQ0lKWqcO9DpAK/icPZUUtjVE4Xh2IDLDvjoQbiRJEo4bU6zMYEGcqy0g7a1Vz0IvSvQRFeMHxRuqtV8NtuDzISSm/gj/4DylPmOUQBdt1VPD4UKhPHLfS/MKt7MJz4cdnc6vrIsY2QDVCZvNa0zckaARjanz3MLPOwd/A==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1j8JEDUFhcTpUY8mAABgEAEA/sDprqlXbiuE+RFWe9HW\n3u9jxwTXpPVXHtkkFCILzokBAOvXQ7yKDrAEXH4f045IyVoFwnhBc6OcPD/x\ncmCxAokC\n=JC9y\n-----END PGP SIGNATURE-----\n",
|
||||
"ServerEphemeral": "S0R4Lfi2t4Bu+StgU39uXpZEYPfG/4vo2WuCGB7f0CfLFfg2he1+HvKCatly/n89KBQx+ei7uoVvD+iDxlaKzPyMvJ+ZiLB73QNaDwsWesnEYmA8ozJgoCRRJf+IBwx7fZAMD/mp9T5J6xscPULmLdFVNrAGjX+v99iTpVanXbFDZ7I/YA3gxjtcrgwyUuQkpkoHyVkuKnodH206e5cYX3szyKHzI48MsZy+UJ2gfuzCT1sYY43+tC1AKet7DNbthfDN9ERMfjWTUghba54DHoscy9GiVHibjD33xoQXpIeubMvI87mrv5Uu0voBYKsJ8TfZIhs/I0j+Xtrmskc5xQ==",
|
||||
"Version": 4,
|
||||
"Salt": "DnJh2bj2RBChNQ==",
|
||||
"SRPSession": "2f34224721344fdb5a62e6f441a496a7"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"test": "test case name2",
|
||||
"description": "test case description 2"
|
||||
},
|
||||
"name": "test mock 2",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"description": "scenario description",
|
||||
"updateFile": false,
|
||||
"mockFiles": ["scenarios/auth_scenario1/auth_mock1.json", "scenarios/auth_scenario1/auth_mock2.json"]
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package me.proton.core.test.mockproxy
|
||||
|
||||
import me.proton.core.test.mockproxy.Constants.EMULATOR_LOCALHOST
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import java.io.FileNotFoundException
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class MockProxyTest {
|
||||
|
||||
private val mockClient = MockClient(EMULATOR_LOCALHOST)
|
||||
|
||||
@After
|
||||
fun afterTest() {
|
||||
mockClient.resetAllMocks()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScenarioMockingFromTestAssets() {
|
||||
val scenarioFilePath = "scenarios/auth_scenario1/auth_scenario.json"
|
||||
val staticMocksResponseList: List<MockObject> =
|
||||
mockClient.setScenarioFromAssets(scenarioFilePath)
|
||||
assertTrue(staticMocksResponseList.count() == 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScenarioMockingFromAppAssets() {
|
||||
val scenarioFilePath = "scenarios/auth_scenario1/auth_scenario.json"
|
||||
mockClient.setLatency(LatencyLevel.EXTREME)
|
||||
val staticMocksResponseList: List<MockObject> =
|
||||
mockClient.setScenarioFromAssets(scenarioFilePath)
|
||||
assertTrue(staticMocksResponseList.count() == 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testResetScenarioMockingFromAppAssets() {
|
||||
val scenarioFilePath = "scenarios/auth_scenario1/auth_scenario.json"
|
||||
mockClient.setLatency(LatencyLevel.EXTREME)
|
||||
var staticMocksResponseList: List<MockObject> =
|
||||
mockClient.setScenarioFromAssets(scenarioFilePath)
|
||||
assert(staticMocksResponseList.count() == 2)
|
||||
staticMocksResponseList.forEach { staticMock ->
|
||||
assertTrue(staticMock.enabled, "Static mock should be enabled but it is not.")
|
||||
}
|
||||
staticMocksResponseList = mockClient.resetScenarioFromAssets(scenarioFilePath)
|
||||
staticMocksResponseList.forEach { staticMock ->
|
||||
assertFalse(staticMock.enabled, "Static mock should be disabled but it is not.")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testResetScenarioMockingFromTestAssets() {
|
||||
val scenarioFilePath = "scenarios/auth_scenario1/auth_scenario.json"
|
||||
mockClient.setLatency(LatencyLevel.EXTREME)
|
||||
var staticMocksResponseList: List<MockObject> =
|
||||
mockClient.setScenarioFromAssets(scenarioFilePath)
|
||||
assert(staticMocksResponseList.count() == 2)
|
||||
staticMocksResponseList.forEach { staticMock ->
|
||||
assertTrue(staticMock.enabled, "Static mock should be enabled but it is not.")
|
||||
}
|
||||
staticMocksResponseList = mockClient.resetScenarioFromAssets(scenarioFilePath)
|
||||
staticMocksResponseList.forEach { staticMock ->
|
||||
assertFalse(staticMock.enabled, "Static mock should be disabled but it is not.")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleAuthRouteMockingFromAppAssets() {
|
||||
val routeFilePath = "scenarios/auth_scenario1/auth_mock1.json"
|
||||
val staticMockResponse: MockObject = mockClient.setStaticMockFromAssets(routeFilePath)
|
||||
assertTrue(staticMockResponse.response.statusCode == 200)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleAuthRouteMockingFromTestAssets() {
|
||||
val routeFilePath = "scenarios/auth_scenario1/auth_mock1.json"
|
||||
val staticMockResponse: MockObject = mockClient.setStaticMockFromAssets(routeFilePath)
|
||||
assertTrue(staticMockResponse.response.statusCode == 200)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFailNotExistingFileLoadFromTestAssets() {
|
||||
val notExistingFilePath = "scenarios/auth_scenario1/not_exists.json"
|
||||
val exception = assertThrows(FileNotFoundException::class.java) {
|
||||
mockClient.setStaticMockFromAssets(notExistingFilePath)
|
||||
}
|
||||
assertTrue(exception.message?.contains(notExistingFilePath) == true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFailNotExistingFileLoadFromAppAssets() {
|
||||
val notExistingFilePath = "scenarios/auth_scenario1/not_exists.json"
|
||||
val exception = assertThrows(FileNotFoundException::class.java) {
|
||||
mockClient.setStaticMockFromAssets(notExistingFilePath)
|
||||
}
|
||||
assertTrue(exception.message?.contains(notExistingFilePath) == true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetLatency() {
|
||||
mockClient.setLatency(LatencyLevel.HIGH)
|
||||
val latencyInfo = mockClient.getLatency()
|
||||
assertTrue(latencyInfo.enabled && latencyInfo.latency == LatencyLevel.HIGH.latencyMs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetAndResetLatency() {
|
||||
mockClient.setLatency(LatencyLevel.HIGH)
|
||||
var latencyInfo = mockClient.getLatency()
|
||||
assertTrue(latencyInfo.enabled && latencyInfo.latency == LatencyLevel.HIGH.latencyMs)
|
||||
|
||||
mockClient.resetLatency()
|
||||
latencyInfo = mockClient.getLatency()
|
||||
assertTrue(!latencyInfo.enabled && latencyInfo.latency == LatencyLevel.NONE.latencyMs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetBandwidth() {
|
||||
mockClient.setBandwidth(BandwidthLimit.NONE)
|
||||
val bandwidthInfo = mockClient.getBandwidth()
|
||||
assertTrue(bandwidthInfo.enabled && bandwidthInfo.limit == BandwidthLimit.NONE.speedKbps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetAndResetBandwidth() {
|
||||
mockClient.setBandwidth(BandwidthLimit._4G)
|
||||
var bandwidthInfo = mockClient.getBandwidth()
|
||||
assert(bandwidthInfo.enabled && bandwidthInfo.limit == BandwidthLimit._4G.speedKbps)
|
||||
|
||||
mockClient.resetBandwidth()
|
||||
bandwidthInfo = mockClient.getBandwidth()
|
||||
assertTrue(!bandwidthInfo.enabled && bandwidthInfo.limit == BandwidthLimit.NONE.speedKbps)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2023 Proton AG
|
||||
~ This file is part of Proton AG and ProtonCore.
|
||||
~
|
||||
~ ProtonCore 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.
|
||||
~
|
||||
~ ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application>
|
||||
<!-- android:usesCleartextTraffic="true">-->
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"request": {
|
||||
"exactUrl": [
|
||||
"/api/v4/test1"
|
||||
],
|
||||
"method": "post"
|
||||
},
|
||||
"response": {
|
||||
"statusCode": 200,
|
||||
"headers": {
|
||||
"header1": "header1-1",
|
||||
"header2": "header2-2"
|
||||
},
|
||||
"body": {
|
||||
"Code": 1000,
|
||||
"Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nY+6iVlkGjwe9tBlF3aDN8dXR4W7U3mX0r4RjfwxckmX3YSwfKCm/zM33b0As8mS8A5kpMSnvGs+E/Mc8CNOPQW6Oulxw4e0prdW9gEZEUCSJA3z02HWGk13/7zjmdzyWiU8yiWtmFga6i8GwfXUyjufS8T+1UYMvCa/HE8R6i2HNgfhhIQ0lKWqcO9DpAK/icPZUUtjVE4Xh2IDLDvjoQbiRJEo4bU6zMYEGcqy0g7a1Vz0IvSvQRFeMHxRuqtV8NtuDzISSm/gj/4DylPmOUQBdt1VPD4UKhPHLfS/MKt7MJz4cdnc6vrIsY2QDVCZvNa0zckaARjanz3MLPOwd/A==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1j8JEDUFhcTpUY8mAABgEAEA/sDprqlXbiuE+RFWe9HW\n3u9jxwTXpPVXHtkkFCILzokBAOvXQ7yKDrAEXH4f045IyVoFwnhBc6OcPD/x\ncmCxAokC\n=JC9y\n-----END PGP SIGNATURE-----\n",
|
||||
"ServerEphemeral": "S0R4Lfi2t4Bu+StgU39uXpZEYPfG/4vo2WuCGB7f0CfLFfg2he1+HvKCatly/n89KBQx+ei7uoVvD+iDxlaKzPyMvJ+ZiLB73QNaDwsWesnEYmA8ozJgoCRRJf+IBwx7fZAMD/mp9T5J6xscPULmLdFVNrAGjX+v99iTpVanXbFDZ7I/YA3gxjtcrgwyUuQkpkoHyVkuKnodH206e5cYX3szyKHzI48MsZy+UJ2gfuzCT1sYY43+tC1AKet7DNbthfDN9ERMfjWTUghba54DHoscy9GiVHibjD33xoQXpIeubMvI87mrv5Uu0voBYKsJ8TfZIhs/I0j+Xtrmskc5xQ==",
|
||||
"Version": 4,
|
||||
"Salt": "DnJh2bj2RBChNQ==",
|
||||
"SRPSession": "2f34224721344fdb5a62e6f441a496a7"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"test": "test case name2",
|
||||
"description": "test case description 2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"request": {
|
||||
"exactUrl": [
|
||||
"/api/v4/test2"
|
||||
],
|
||||
"method": "post"
|
||||
},
|
||||
"response": {
|
||||
"statusCode": 200,
|
||||
"headers": {"header11": "header11-11", "header22": "header22-22"},
|
||||
"body": {
|
||||
"Code": 1000,
|
||||
"Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nY+6iVlkGjwe9tBlF3aDN8dXR4W7U3mX0r4RjfwxckmX3YSwfKCm/zM33b0As8mS8A5kpMSnvGs+E/Mc8CNOPQW6Oulxw4e0prdW9gEZEUCSJA3z02HWGk13/7zjmdzyWiU8yiWtmFga6i8GwfXUyjufS8T+1UYMvCa/HE8R6i2HNgfhhIQ0lKWqcO9DpAK/icPZUUtjVE4Xh2IDLDvjoQbiRJEo4bU6zMYEGcqy0g7a1Vz0IvSvQRFeMHxRuqtV8NtuDzISSm/gj/4DylPmOUQBdt1VPD4UKhPHLfS/MKt7MJz4cdnc6vrIsY2QDVCZvNa0zckaARjanz3MLPOwd/A==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1j8JEDUFhcTpUY8mAABgEAEA/sDprqlXbiuE+RFWe9HW\n3u9jxwTXpPVXHtkkFCILzokBAOvXQ7yKDrAEXH4f045IyVoFwnhBc6OcPD/x\ncmCxAokC\n=JC9y\n-----END PGP SIGNATURE-----\n",
|
||||
"ServerEphemeral": "S0R4Lfi2t4Bu+StgU39uXpZEYPfG/4vo2WuCGB7f0CfLFfg2he1+HvKCatly/n89KBQx+ei7uoVvD+iDxlaKzPyMvJ+ZiLB73QNaDwsWesnEYmA8ozJgoCRRJf+IBwx7fZAMD/mp9T5J6xscPULmLdFVNrAGjX+v99iTpVanXbFDZ7I/YA3gxjtcrgwyUuQkpkoHyVkuKnodH206e5cYX3szyKHzI48MsZy+UJ2gfuzCT1sYY43+tC1AKet7DNbthfDN9ERMfjWTUghba54DHoscy9GiVHibjD33xoQXpIeubMvI87mrv5Uu0voBYKsJ8TfZIhs/I0j+Xtrmskc5xQ==",
|
||||
"Version": 4,
|
||||
"Salt": "DnJh2bj2RBChNQ==",
|
||||
"SRPSession": "2f34224721344fdb5a62e6f441a496a7"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"test": "test case name2",
|
||||
"description": "test case description 2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "loginMock",
|
||||
"enabled": true,
|
||||
"request": {
|
||||
"exactUrl": [
|
||||
"/api/core/v4/auth"
|
||||
]
|
||||
},
|
||||
"response": {
|
||||
"statusCode": 422,
|
||||
"body": {
|
||||
"Code": 8002,
|
||||
"Error": "Incorrect login credentials. Please do not try again.",
|
||||
"Details": {},
|
||||
"exception": "Proton\\Http\\Exceptions\\UnprocessableEntityException",
|
||||
"message": "Incorrect login credentials. Please do not try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"description": "scenario description",
|
||||
"updateFile": false,
|
||||
"mockFiles": ["scenarios/auth_scenario1/auth_mock1.json", "scenarios/auth_scenario1/auth_mock2.json"]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore 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.
|
||||
*
|
||||
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.test.mockproxy
|
||||
|
||||
public object Constants {
|
||||
public const val EMULATOR_LOCALHOST: String = "http://10.0.2.2:3001/"
|
||||
public const val MOCK_PROXY_TAG: String = "Mock-proxy"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore 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.
|
||||
*
|
||||
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.test.mockproxy
|
||||
|
||||
import retrofit2.http.*
|
||||
|
||||
internal interface MockApi {
|
||||
|
||||
/** Single route mock **/
|
||||
@POST("/mock/route/static")
|
||||
suspend fun setStaticMock(@Body mocks: MockObject): MockObject
|
||||
|
||||
/** List of routes to mock **/
|
||||
@GET("/mock/routes/static")
|
||||
suspend fun getStaticMocks(): List<MockObject>
|
||||
@POST("/mock/routes/static")
|
||||
suspend fun setStaticMocks(@Body mocks: List<MockObject>): List<MockObject>
|
||||
|
||||
/** List of routes to mock dynamically **/
|
||||
@GET("/mock/routes/dynamic")
|
||||
suspend fun getDynamicMocks(): List<DynamicMockObject>
|
||||
@POST("/mock/routes/dynamic")
|
||||
suspend fun setDynamicMocks(@Body mocks: DynamicMockObject): DynamicMockObject
|
||||
|
||||
/** Latency endpoints **/
|
||||
@GET("/mock/latency")
|
||||
suspend fun getLatency(): LatencyObject
|
||||
@POST("/mock/latency")
|
||||
suspend fun setLatency(@Body latencyInfo: LatencyObject): LatencyObject
|
||||
|
||||
/** Bandwidth endpoints **/
|
||||
@GET("/mock/bandwidth")
|
||||
suspend fun getBandwidth(): BandwidthObject
|
||||
@POST("/mock/bandwidth")
|
||||
suspend fun setBandwidth(@Body bandwidthInfo: BandwidthObject): BandwidthObject
|
||||
|
||||
/** Reset endpoints **/
|
||||
@POST("/mock/reset/all")
|
||||
suspend fun resetAllMocks(): ResponseMessage
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore 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.
|
||||
*
|
||||
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.test.mockproxy
|
||||
|
||||
import android.util.Log
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
|
||||
public class MockClient(private val baseUrl: String, private val proxyToken: String = "") {
|
||||
|
||||
private val retrofitBuilder: Retrofit.Builder = Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.client(
|
||||
OkHttpClient.Builder()
|
||||
.addInterceptor { chain ->
|
||||
val request = chain.request()
|
||||
.newBuilder()
|
||||
.addHeader("x-atlas-secret", proxyToken)
|
||||
.build()
|
||||
chain.proceed(request)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
|
||||
private var mockApi: MockApi = retrofitBuilder
|
||||
.addConverterFactory(Json.asConverterFactory(MimeType.JSON.value.toMediaType()))
|
||||
.build()
|
||||
.create(MockApi::class.java)
|
||||
|
||||
public fun setSRPMock(
|
||||
shouldEnable: Boolean = false,
|
||||
password: String
|
||||
) {
|
||||
Log.d(
|
||||
"MockProxy",
|
||||
"Executing SRP request with baseUrl: ${this.baseUrl} and proxyToken: ${this.proxyToken}"
|
||||
)
|
||||
val srpMockList = DynamicMockObject(
|
||||
name = "loginWithSrp",
|
||||
enabled = shouldEnable,
|
||||
parameters = mapOf("password" to password)
|
||||
)
|
||||
runBlocking { mockApi.setDynamicMocks(srpMockList) }
|
||||
}
|
||||
|
||||
public fun setRecording(
|
||||
shouldEnable: Boolean = false,
|
||||
recordingDirPath: String
|
||||
) {
|
||||
Log.d(
|
||||
"MockProxy",
|
||||
"Executing set recording request with baseUrl: ${this.baseUrl} and proxyToken: ${this.proxyToken}"
|
||||
)
|
||||
val mock = DynamicMockObject(
|
||||
name = "recordAll",
|
||||
enabled = shouldEnable,
|
||||
updateFile = true,
|
||||
parameters = mapOf("mockDirectory" to recordingDirPath)
|
||||
)
|
||||
runBlocking {
|
||||
mockApi.setDynamicMocks(mock)
|
||||
}
|
||||
}
|
||||
|
||||
public fun setScenarioFromAssets(
|
||||
scenarioFilePath: String,
|
||||
shouldUpdateFile: Boolean = false
|
||||
): List<MockObject> {
|
||||
val staticMocksList =
|
||||
MockParser.parseScenarioFileFromAssets(scenarioFilePath, true, shouldUpdateFile)
|
||||
return this.setStaticMocks(staticMocksList)
|
||||
}
|
||||
|
||||
public fun resetScenarioFromAssets(scenarioFilePath: String): List<MockObject> {
|
||||
val staticMocksList = MockParser.parseScenarioFileFromAssets(scenarioFilePath, false)
|
||||
lateinit var mockObjectResponse: List<MockObject>
|
||||
runBlocking { mockObjectResponse = mockApi.setStaticMocks(staticMocksList) }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun getScenarioDirPath(scenarioFilePath: String): String =
|
||||
MockParser.getScenarioDirectoryOrThrow(scenarioFilePath)
|
||||
|
||||
public fun getStaticMocks(): List<MockObject> {
|
||||
lateinit var mockObjectResponse: List<MockObject>
|
||||
runBlocking { mockObjectResponse = mockApi.getStaticMocks() }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun setStaticMockFromAssets(filePath: String): MockObject {
|
||||
val staticMockFile = MockParser.parseMockFileFromAssets(filePath)
|
||||
lateinit var mockObjectResponse: MockObject
|
||||
runBlocking { mockObjectResponse = mockApi.setStaticMock(staticMockFile) }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
private fun setStaticMocks(mockRoutesList: List<MockObject>): List<MockObject> {
|
||||
lateinit var mockObjectResponse: List<MockObject>
|
||||
runBlocking { mockObjectResponse = mockApi.setStaticMocks(mockRoutesList) }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun setStaticMock(staticMock: MockObject): MockObject {
|
||||
lateinit var mockObjectResponse: MockObject
|
||||
runBlocking { mockObjectResponse = mockApi.setStaticMock(staticMock) }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun resetStaticMock(staticMock: MockObject): MockObject {
|
||||
staticMock.enabled = false
|
||||
lateinit var mockObjectResponse: MockObject
|
||||
runBlocking { mockObjectResponse = mockApi.setStaticMock(staticMock) }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun getLatency(): LatencyObject {
|
||||
lateinit var mockObjectResponse: LatencyObject
|
||||
runBlocking { mockObjectResponse = mockApi.getLatency() }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun setLatency(latencyLevel: LatencyLevel): LatencyObject {
|
||||
val latencyInfo = LatencyObject(
|
||||
enabled = true,
|
||||
latency = latencyLevel.latencyMs
|
||||
)
|
||||
lateinit var mockObjectResponse: LatencyObject
|
||||
runBlocking { mockObjectResponse = mockApi.setLatency(latencyInfo) }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun resetLatency(): LatencyObject {
|
||||
val latencyInfo = LatencyObject(
|
||||
enabled = false,
|
||||
latency = LatencyLevel.NONE.latencyMs
|
||||
)
|
||||
lateinit var mockObjectResponse: LatencyObject
|
||||
runBlocking { mockObjectResponse = mockApi.setLatency(latencyInfo) }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun getBandwidth(): BandwidthObject {
|
||||
lateinit var mockObjectResponse: BandwidthObject
|
||||
runBlocking { mockObjectResponse = mockApi.getBandwidth() }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun setBandwidth(bandwidthLimit: BandwidthLimit = BandwidthLimit.NONE): BandwidthObject {
|
||||
val bandwidthInfo = BandwidthObject(
|
||||
enabled = true,
|
||||
limit = bandwidthLimit.speedKbps
|
||||
)
|
||||
lateinit var mockObjectResponse: BandwidthObject
|
||||
runBlocking { mockObjectResponse = mockApi.setBandwidth(bandwidthInfo) }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun resetBandwidth(): BandwidthObject {
|
||||
val bandwidthInfo = BandwidthObject(
|
||||
enabled = false,
|
||||
limit = BandwidthLimit.NONE.speedKbps
|
||||
)
|
||||
lateinit var mockObjectResponse: BandwidthObject
|
||||
runBlocking { mockObjectResponse = mockApi.setBandwidth(bandwidthInfo) }
|
||||
return mockObjectResponse
|
||||
}
|
||||
|
||||
public fun resetAllMocks(): String {
|
||||
lateinit var mockObjectResponse: String
|
||||
runBlocking { mockObjectResponse = mockApi.resetAllMocks().message }
|
||||
return mockObjectResponse
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore 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.
|
||||
*
|
||||
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.test.mockproxy
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
@Serializable
|
||||
public data class MockObject(
|
||||
var name: String,
|
||||
var enabled: Boolean,
|
||||
var updateFile: Boolean? = null,
|
||||
val isRawFileContent: Boolean = false,
|
||||
var request: MockFile.RequestData,
|
||||
var response: MockFile.ResponseData
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class DynamicMockObject(
|
||||
var name: String,
|
||||
var enabled: Boolean,
|
||||
var description: String? = null,
|
||||
var updateFile: Boolean? = null,
|
||||
var parameters: Map<String, String>? = null,
|
||||
var mocks: List<DynamicMock>? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class DynamicMock(
|
||||
var request: MockFile.RequestData? = null,
|
||||
var response: MockFile.ResponseData? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class ScenarioMockObject(
|
||||
val scenarioName: String,
|
||||
var enabled: Boolean,
|
||||
val updateFile: Boolean? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class LatencyObject(
|
||||
var enabled: Boolean,
|
||||
val latency: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class BandwidthObject(
|
||||
var enabled: Boolean,
|
||||
val limit: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class ScenarioFileObject(
|
||||
val description: String,
|
||||
val updateFile: Boolean,
|
||||
val mockFiles: List<String>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class MockFile(
|
||||
val request: RequestData,
|
||||
val response: ResponseData,
|
||||
val name: String,
|
||||
var enabled: Boolean,
|
||||
val meta: MetaData? = null
|
||||
) {
|
||||
@Serializable
|
||||
public data class RequestData(
|
||||
val exactUrl: List<String>? = listOf(),
|
||||
val matchUrl: List<String>? = listOf(),
|
||||
val method: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class ResponseData(
|
||||
val headers: Map<String, JsonElement>? = null,
|
||||
val body: JsonElement? = null,
|
||||
val statusCode: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class MetaData(
|
||||
val test: String,
|
||||
val description: String,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
public data class ParsedScenarioFile(
|
||||
val name: String,
|
||||
var enabled: Boolean,
|
||||
val request: MockFile.RequestData,
|
||||
val response: MockFile.ResponseData
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class ResponseMessage(val message: String)
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore 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.
|
||||
*
|
||||
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.test.mockproxy
|
||||
|
||||
import android.content.res.AssetManager
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
internal object MockParser {
|
||||
private val testContext
|
||||
get() = InstrumentationRegistry.getInstrumentation().context
|
||||
private val testAssetManager
|
||||
get() = testContext.assets ?: error("Could not load app assets.")
|
||||
|
||||
private fun readFileFromAssets(filePath: String, assetManager: AssetManager): String =
|
||||
assetManager.open(filePath).bufferedReader().use { it.readText() }
|
||||
|
||||
internal fun parseMockFileFromAssets(mockFilePath: String): MockObject {
|
||||
val assetManager = getAssetManager(mockFilePath)
|
||||
return parseAssetFileToStaticMock(mockFilePath, assetManager)
|
||||
}
|
||||
|
||||
private fun parseAssetFileToStaticMock(
|
||||
mockFilePath: String,
|
||||
assetManager: AssetManager,
|
||||
isEnabled: Boolean = false
|
||||
): MockObject {
|
||||
val mockFile = File(mockFilePath)
|
||||
val mockFileContent = readFileFromAssets(mockFilePath, assetManager)
|
||||
val decodedMockFile: MockFile = Json.decodeFromString(mockFileContent)
|
||||
val fileName = mockFile.nameWithoutExtension
|
||||
return MockObject(
|
||||
name = fileName,
|
||||
enabled = isEnabled,
|
||||
updateFile = false,
|
||||
request = decodedMockFile.request,
|
||||
response = decodedMockFile.response
|
||||
)
|
||||
}
|
||||
|
||||
internal fun getScenarioDirectoryOrThrow(
|
||||
scenarioFilePath: String
|
||||
): String {
|
||||
val assetManager = getAssetManager(scenarioFilePath)
|
||||
val fileContent = readFileFromAssets(scenarioFilePath, assetManager)
|
||||
val scenarioFileObject: ScenarioFileObject = Json.decodeFromString(fileContent)
|
||||
// Get the first mock file and check if it is a folder
|
||||
val firstMockFile = scenarioFileObject.mockFiles[0]
|
||||
val assetList = assetManager.list(firstMockFile)
|
||||
|
||||
if (assetList.isNullOrEmpty()) {
|
||||
throw IllegalStateException("The first mock file is not a folder: $firstMockFile")
|
||||
}
|
||||
return firstMockFile
|
||||
}
|
||||
|
||||
internal fun parseScenarioFileFromAssets(
|
||||
scenarioFilePath: String,
|
||||
isEnabled: Boolean,
|
||||
shouldUpdateFile: Boolean = false
|
||||
): List<MockObject> {
|
||||
val assetManager = getAssetManager(scenarioFilePath)
|
||||
return parseAssetFileToListOfStaticMocks(
|
||||
scenarioFilePath,
|
||||
assetManager,
|
||||
isEnabled,
|
||||
shouldUpdateFile
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAssetManager(scenarioFilePath: String): AssetManager {
|
||||
val assetManager = runCatching {
|
||||
readFileFromAssets(
|
||||
scenarioFilePath,
|
||||
testAssetManager
|
||||
).also { println("Read from testAssetManager") }
|
||||
testAssetManager
|
||||
}.getOrElse {
|
||||
throw FileNotFoundException(
|
||||
"Failed to read file from both test and app assets. File path: $scenarioFilePath"
|
||||
)
|
||||
}
|
||||
return assetManager
|
||||
}
|
||||
|
||||
private fun parseAssetFileToListOfStaticMocks(
|
||||
scenarioFilePath: String,
|
||||
assetManager: AssetManager,
|
||||
isEnabled: Boolean = false,
|
||||
shouldUpdateFile: Boolean = false
|
||||
): List<MockObject> {
|
||||
val mockRoutesList = mutableListOf<MockObject>()
|
||||
val fileContent = readFileFromAssets(scenarioFilePath, assetManager)
|
||||
val scenarioFileObject: ScenarioFileObject = Json.decodeFromString(fileContent)
|
||||
processMockFiles(
|
||||
scenarioFileObject.mockFiles,
|
||||
assetManager,
|
||||
isEnabled,
|
||||
shouldUpdateFile,
|
||||
mockRoutesList
|
||||
)
|
||||
return mockRoutesList
|
||||
}
|
||||
|
||||
private fun processMockFiles(
|
||||
mockFiles: List<String>,
|
||||
assetManager: AssetManager,
|
||||
isEnabled: Boolean,
|
||||
shouldUpdateFile: Boolean,
|
||||
mockRoutesList: MutableList<MockObject>
|
||||
) {
|
||||
mockFiles.forEach { mockFilePath ->
|
||||
val assetList = assetManager.list(mockFilePath)
|
||||
if (assetList.isNullOrEmpty()) {
|
||||
val mockFile = File(mockFilePath)
|
||||
val mockName =
|
||||
"${System.currentTimeMillis()}-${mockFile.parentFile?.name}-${mockFile.nameWithoutExtension}"
|
||||
val mockFileContent = readFileFromAssets(mockFilePath, assetManager)
|
||||
val decodedMockFile: MockFile = Json.decodeFromString(mockFileContent)
|
||||
|
||||
mockRoutesList.add(
|
||||
MockObject(
|
||||
name = mockName,
|
||||
enabled = isEnabled,
|
||||
updateFile = shouldUpdateFile,
|
||||
request = decodedMockFile.request,
|
||||
response = decodedMockFile.response
|
||||
)
|
||||
)
|
||||
} else {
|
||||
assetList.forEach { nestedFileName ->
|
||||
val nestedFilePath = "$mockFilePath/$nestedFileName"
|
||||
processMockFiles(
|
||||
listOf(nestedFilePath),
|
||||
assetManager,
|
||||
isEnabled,
|
||||
shouldUpdateFile,
|
||||
mockRoutesList
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore 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.
|
||||
*
|
||||
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.test.mockproxy
|
||||
|
||||
public enum class MimeType(public val value: String) {
|
||||
JSON("application/json"),
|
||||
IMAGE_SVG("image/svg+xml"),
|
||||
IMAGE_PNG("image/png"),
|
||||
OCTET_STREAM("application/octet-stream"),
|
||||
MULTIPART_FORM_DATA("multipart/form-data");
|
||||
}
|
||||
|
||||
public enum class LatencyLevel(public val latencyMs: Int) {
|
||||
NONE(0),
|
||||
LOW(50),
|
||||
MODERATE(150),
|
||||
HIGH(300),
|
||||
VERY_HIGH(600),
|
||||
CRITICAL(1000),
|
||||
EXTREME(5000),
|
||||
}
|
||||
|
||||
public enum class BandwidthLimit(public val speedKbps: Int) {
|
||||
GPRS(56), // Real-world maximum
|
||||
EDGE(236), // Real-world maximum
|
||||
_2G(256), // Real-world maximum
|
||||
_3G(42000), // Theoretical maximum for HSPA+
|
||||
_4G(1000000), // 1 Gbps = Theoretical maximum
|
||||
WIFI(9600000), // WiFi 6 theoretical maximum (9.6 Gbps)
|
||||
BROADBAND(1000000), // Common high-speed broadband (1 Gbps)
|
||||
NONE(Int.MAX_VALUE) // Unlimited
|
||||
}
|
||||
@@ -37,6 +37,7 @@ dependencies {
|
||||
project(Module.configurationData),
|
||||
project(Module.configurationDaggerContentResolver),
|
||||
project(Module.quark),
|
||||
project(Module.mockProxy),
|
||||
project(Module.authDomain),
|
||||
project(Module.authPresentation),
|
||||
`coroutines-android`,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore 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.
|
||||
*
|
||||
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.test.rule
|
||||
|
||||
import me.proton.core.test.mockproxy.MockClient
|
||||
import me.proton.core.test.rule.annotation.MockTest
|
||||
import me.proton.core.test.rule.annotation.PrepareUser
|
||||
import me.proton.core.util.kotlin.takeIfNotEmpty
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
|
||||
/**
|
||||
* Responsible for mock-proxy behavior configuration.
|
||||
* Can act in test replay and record mode based on [shouldRecord] parameter.
|
||||
* Enables SRP mocking in replay mode.
|
||||
*/
|
||||
public class MockTestRule(
|
||||
private val mockClient: MockClient,
|
||||
private val shouldRecord: Boolean = false
|
||||
) : TestRule {
|
||||
override fun apply(base: Statement, description: Description): Statement {
|
||||
val mockTest = description.getAnnotation(MockTest::class.java)
|
||||
?: description.testClass.getAnnotation(MockTest::class.java)
|
||||
|
||||
mockTest?.let {
|
||||
mockTest.scenario.let { scenario ->
|
||||
if (shouldRecord && mockTest.isReference) {
|
||||
mockClient.setRecording(
|
||||
true,
|
||||
mockClient.getScenarioDirPath(scenario)
|
||||
)
|
||||
} else {
|
||||
description.getAnnotation(PrepareUser::class.java)?.let { user ->
|
||||
mockClient.setSRPMock(
|
||||
true,
|
||||
// Fall back to the default password value as we have to
|
||||
// pass password to SRP anyway here.
|
||||
user.userData.password.takeIfNotEmpty() ?: "password"
|
||||
)
|
||||
mockClient.setScenarioFromAssets(scenario)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return base
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,8 @@ public class QuarkTestDataRule(
|
||||
prepareUserAnnotations = method?.findAnnotations<PrepareUser>().orEmpty()
|
||||
paymentMethodAnnotation = method?.findAnnotation<TestPaymentMethods>()
|
||||
|
||||
quarkCommand = getQuarkCommand(environmentConfiguration())
|
||||
|
||||
if (prepareUserAnnotations.isNotEmpty()) {
|
||||
prepareUserAnnotations.forEach { prepareUser ->
|
||||
|
||||
@@ -140,7 +142,6 @@ public class QuarkTestDataRule(
|
||||
*/
|
||||
var handledUserData = prepareUser.userData.handleUserData()
|
||||
var createdUserResponse: CreateUserQuarkResponse?
|
||||
quarkCommand = getQuarkCommand(environmentConfiguration())
|
||||
|
||||
val userSeedingTime = measureTime {
|
||||
createdUserResponse = prepareUser.annotationTestData.implementation(
|
||||
@@ -300,10 +301,17 @@ public class QuarkTestDataRule(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getQuarkCommand(envConfig: EnvironmentConfiguration): QuarkCommand =
|
||||
QuarkCommand(quarkClient)
|
||||
.baseUrl("https://${envConfig.host}/api/internal")
|
||||
private fun getQuarkCommand(envConfig: EnvironmentConfiguration): QuarkCommand {
|
||||
lateinit var baseUrl: String
|
||||
if (envConfig.host.contains("10.0.2.2")) {
|
||||
baseUrl = "http://${envConfig.host}/api/internal"
|
||||
} else {
|
||||
baseUrl = "https://${envConfig.host}/api/internal"
|
||||
}
|
||||
return QuarkCommand(quarkClient)
|
||||
.baseUrl(baseUrl)
|
||||
.proxyToken(envConfig.proxyToken)
|
||||
}
|
||||
|
||||
public fun getAnnotationProperty(annotation: Annotation, propertyName: String): Any? {
|
||||
return try {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore 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.
|
||||
*
|
||||
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.test.rule.annotation
|
||||
|
||||
import me.proton.core.test.rule.MockTestRule
|
||||
|
||||
/**
|
||||
* Test function or test class annotated with this annotation will be treated as mock test by
|
||||
* [MockTestRule].
|
||||
*
|
||||
* @property scenario - path to the scenario file in tests assets.
|
||||
* @property isReference - if "true" indicates that this test should be used in recording mode and
|
||||
* is the reference for recording for a given scenario.
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
public annotation class MockTest(
|
||||
val scenario: String,
|
||||
val isReference: Boolean = false
|
||||
)
|
||||
Reference in New Issue
Block a user