Merge branch 'PM-37255/fill-assist-network-layer' into PM-37255/fill-assist-data-layer

This commit is contained in:
Andre Rosado committed 2026-06-09 14:46:07 +01:00
commit bbdc2f10da
22 files changed
+118 -515

No files matched your search

@@ -27,4 +27,10 @@ interface EnvironmentDiskSource {
* Stores the [urls] for the given [userEmail].
*/
fun storePreAuthEnvironmentUrlDataForEmail(userEmail: String, urls: EnvironmentUrlDataJson)
/**
* The fill-assist URL provided by the server config, or `null` if the server does not
* configure fill-assist targeting rules.
*/
var fillAssistRulesUrl: String?
}
@@ -11,6 +11,7 @@ import kotlinx.serialization.json.Json
private const val PRE_AUTH_URLS_KEY = "preAuthEnvironmentUrls"
private const val EMAIL_VERIFICATION_URLS = "emailVerificationUrls"
private const val FILL_ASSIST_RULES_URL_KEY = "fillAssistRulesUrl"
/**
* Primary implementation of [EnvironmentDiskSource].
@@ -54,4 +55,8 @@ class EnvironmentDiskSourceImpl(
value = json.encodeToString(urls),
)
}
override var fillAssistRulesUrl: String?
get() = getString(key = FILL_ASSIST_RULES_URL_KEY)
set(value) = putString(key = FILL_ASSIST_RULES_URL_KEY, value = value)
}
@@ -31,4 +31,6 @@ class BaseUrlsProviderImpl(
.toEnvironmentUrlsOrDefault()
.environmentUrlData
.baseEventsUrl
override fun getBaseFillAssistUrl(): String? = environmentDiskSource.fillAssistRulesUrl
}
@@ -95,11 +95,26 @@ class EnvironmentDiskSourceTest {
json.parseToJsonElement(requireNotNull(actual)),
)
}
@Test
fun `fillAssistRulesUrl should pull from and update SharedPreferences`() {
assertNull(environmentDiskSource.fillAssistRulesUrl)
assertNull(fakeSharedPreferences.getString(FILL_ASSIST_RULES_URL_KEY, null))
environmentDiskSource.fillAssistRulesUrl = "https://fill-assist.example.com/"
assertEquals(
"https://fill-assist.example.com/",
fakeSharedPreferences.getString(FILL_ASSIST_RULES_URL_KEY, null),
)
environmentDiskSource.fillAssistRulesUrl = null
assertNull(fakeSharedPreferences.getString(FILL_ASSIST_RULES_URL_KEY, null))
}
}
private const val EMAIL = "email@example.com"
private const val EMAIL_VERIFICATION_URLS_KEY = "bwPreferencesStorage:emailVerificationUrls"
private const val PRE_AUTH_URLS_KEY = "bwPreferencesStorage:preAuthEnvironmentUrls"
private const val FILL_ASSIST_RULES_URL_KEY = "bwPreferencesStorage:fillAssistRulesUrl"
private const val ENVIRONMENT_URL_DATA_JSON = """
{
@@ -29,6 +29,8 @@ class FakeEnvironmentDiskSource : EnvironmentDiskSource {
storedEmailVerificationUrls[userEmail] = urls
}
override var fillAssistRulesUrl: String? = null
private val mutablePreAuthEnvironmentUrlDataFlow =
bufferedMutableSharedFlow<EnvironmentUrlDataJson?>(replay = 1)
}
@@ -4,6 +4,7 @@ import com.bitwarden.data.repository.model.Environment
import com.x8bit.bitwarden.data.platform.datasource.disk.FakeEnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.provider.BaseUrlsProviderImpl
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
class BaseUrlsProviderTest {
@@ -66,4 +67,16 @@ class BaseUrlsProviderTest {
baseUrlsManager.getBaseEventsUrl(),
)
}
@Test
fun `getBaseFillAssistUrl should return url from disk source when present`() {
fakeEnvironmentDiskSource.fillAssistRulesUrl = "https://example.com/"
assertEquals("https://example.com/", baseUrlsManager.getBaseFillAssistUrl())
}
@Test
fun `getBaseFillAssistUrl should return null when not set`() {
fakeEnvironmentDiskSource.fillAssistRulesUrl = null
assertNull(baseUrlsManager.getBaseFillAssistUrl())
}
}
@@ -36,6 +36,7 @@ class SdkRepositoryFactoryTests {
override fun getBaseApiUrl(): String = BASE_API_URL
override fun getBaseIdentityUrl(): String = BASE_IDENTITY_URL
override fun getBaseEventsUrl(): String = BASE_EVENTS_URL
override fun getBaseFillAssistUrl(): String? = null
},
authTokenProvider = mockk(),
certificateProvider = mockk(),
@@ -20,4 +20,6 @@ object BaseUrlsProviderImpl : BaseUrlsProvider {
override fun getBaseEventsUrl(): String =
Environment.Us.environmentUrlData.baseEventsUrl
override fun getBaseFillAssistUrl(): String? = null
}
@@ -158,9 +158,7 @@ internal class BitwardenServiceClientImpl(
}
override val fillAssistService: FillAssistService by lazy {
FillAssistServiceImpl(
api = retrofits.createStaticRetrofit().create(),
)
FillAssistServiceImpl(api = retrofits.fillAssistRetrofit.create())
}
override val haveIBeenPwnedService: HaveIBeenPwnedService by lazy {
@@ -4,26 +4,24 @@ import com.bitwarden.network.model.FillAssistFormsJson
import com.bitwarden.network.model.FillAssistManifestJson
import com.bitwarden.network.model.NetworkResult
import retrofit2.http.GET
import retrofit2.http.Url
import retrofit2.http.Path
/**
* Defines endpoints for retrieving fill-assist targeting rules from the fill-assist service.
* Uses [Url] to support the dynamic base URL provided by server config at runtime.
* Defines endpoints for retrieving fill-assist targeting rules. The base URL is set dynamically
* at runtime via [com.bitwarden.network.interceptor.BaseUrlInterceptors.fillAssistInterceptor].
*/
internal interface FillAssistApi {
/**
* Fetches the fill-assist manifest from the given [url].
* Fetches the fill-assist manifest.
*/
@GET
suspend fun getManifest(
@Url url: String,
): NetworkResult<FillAssistManifestJson>
@GET("manifest.json")
suspend fun getManifest(): NetworkResult<FillAssistManifestJson>
/**
* Fetches and decodes the forms rules file from [url].
* Fetches the forms rules file by [filename] (e.g. "forms.v1.json").
*/
@GET
@GET("{filename}")
suspend fun getForms(
@Url url: String,
@Path("filename") filename: String,
): NetworkResult<FillAssistFormsJson>
}
@@ -29,4 +29,11 @@ internal class BaseUrlInterceptors(
val eventsInterceptor: BaseUrlInterceptor = BaseUrlInterceptor {
baseUrlsProvider.getBaseEventsUrl()
}
/**
* An interceptor for fill-assist calls.
*/
val fillAssistInterceptor: BaseUrlInterceptor = BaseUrlInterceptor {
baseUrlsProvider.getBaseFillAssistUrl()
}
}
@@ -18,4 +18,10 @@ interface BaseUrlsProvider {
* Gets the base URL for "/events" calls.
*/
fun getBaseEventsUrl(): String
/**
* Gets the base URL for fill-assist calls, or null if the server does not provide
* fill-assist targeting rules.
*/
fun getBaseFillAssistUrl(): String?
}
@@ -62,6 +62,8 @@ data class ConfigResponseJson(
* @param identityUrl The URL of the identity service in the environment.
* @param notificationsUrl The URL of the notifications service in the environment.
* @param ssoUrl The URL of the single sign-on (SSO) service in the environment.
* @param fillAssistRulesUrl The base URL of the fill-assist targeting rules, or null if
* the server does not provide fill-assist rules.
*/
@Serializable
data class EnvironmentJson(
@@ -14,10 +14,10 @@ import kotlinx.serialization.json.JsonElement
@Serializable
data class FillAssistFormsJson(
@SerialName("schemaVersion")
val schemaVersion: String? = null,
val schemaVersion: String,
@SerialName("hosts")
val hosts: Map<String, HostEntryJson?>? = null,
val hosts: Map<String, HostEntryJson?>,
) {
/**
* Form descriptions and pathname-specific overrides for a single host.
@@ -28,10 +28,10 @@ data class FillAssistFormsJson(
@Serializable
data class HostEntryJson(
@SerialName("forms")
val forms: List<FormJson>? = null,
val forms: List<FormJson>?,
@SerialName("pathnames")
val pathnames: Map<String, PathnameEntryJson?>? = null,
val pathnames: Map<String, PathnameEntryJson?>?,
)
/**
@@ -42,7 +42,7 @@ data class FillAssistFormsJson(
@Serializable
data class PathnameEntryJson(
@SerialName("forms")
val forms: List<FormJson>? = null,
val forms: List<FormJson>,
)
/**
@@ -57,12 +57,12 @@ data class FillAssistFormsJson(
@Serializable
data class FormJson(
@SerialName("category")
val category: String? = null,
val category: String,
@SerialName("container")
val container: List<String>? = null,
val container: List<String>?,
@SerialName("fields")
val fields: Map<String, JsonElement>? = null,
val fields: Map<String, JsonElement>,
)
}
@@ -14,16 +14,16 @@ import kotlinx.serialization.Serializable
@Serializable
data class FillAssistManifestJson(
@SerialName("buildId")
val buildId: String? = null,
val buildId: String,
@SerialName("timestamp")
val timestamp: String? = null,
val timestamp: String,
@SerialName("gitSha")
val gitSha: String? = null,
val gitSha: String,
@SerialName("maps")
val maps: MapsJson? = null,
val maps: MapsJson,
) {
/**
* Container for all available maps.
@@ -34,13 +34,13 @@ data class FillAssistManifestJson(
@Serializable
data class MapsJson(
@SerialName("forms")
val forms: Map<String, FileEntryJson?>?,
val forms: Map<String, FileEntryJson>,
)
/**
* Metadata for a single versioned file in a map.
*
* @property filename The filename to fetch (e.g. "forms.v0.json").
* @property filename The filename to fetch (e.g. "forms.v1.json").
* @property cid The SHA-256 content hash in "sha256:<hex>" format. Used as a staleness key
* to detect when the forms file has changed on the server, avoiding unnecessary re-downloads.
* @property schema The schema filename associated with this file version.
@@ -50,15 +50,15 @@ data class FillAssistManifestJson(
@Serializable
data class FileEntryJson(
@SerialName("filename")
val filename: String? = null,
val filename: String,
@SerialName("cid")
val cid: String? = null,
val cid: String,
@SerialName("schema")
val schema: String? = null,
val schema: String,
@SerialName("deprecated")
val deprecated: Boolean? = null,
val deprecated: Boolean?,
)
}
@@ -36,6 +36,12 @@ internal interface Retrofits {
*/
val unauthenticatedIdentityRetrofit: Retrofit
/**
* Allows access to fill-assist calls. The base URL is determined dynamically via the
* [BaseUrlInterceptors.fillAssistInterceptor].
*/
val fillAssistRetrofit: Retrofit
/**
* Allows access to static API calls (ex: external APIs).
*
@@ -64,6 +64,16 @@ internal class RetrofitsImpl(
//endregion Unauthenticated Retrofits
//region Fill-Assist Retrofit
override val fillAssistRetrofit: Retrofit by lazy {
createUnauthenticatedRetrofit(
baseUrlInterceptor = baseUrlInterceptors.fillAssistInterceptor,
)
}
//endregion Fill-Assist Retrofit
//region Static Retrofit
override fun createStaticRetrofit(isAuthenticated: Boolean, baseUrl: String): Retrofit {
@@ -8,15 +8,14 @@ import com.bitwarden.network.model.FillAssistManifestJson
*/
interface FillAssistService {
/**
* Fetches and parses the fill-assist manifest from [url].
* Fetches and parses the fill-assist manifest.
*/
suspend fun getManifest(url: String): Result<FillAssistManifestJson>
suspend fun getManifest(): Result<FillAssistManifestJson>
/**
* Downloads and parses the forms rules file from [formsUrl].
* Downloads and parses the forms rules file identified by [filename] (e.g. "forms.v1.json").
*
* Returns [Result.failure] if the network request fails or parsing fails.
* Version-agnostic: any forms file URL can be passed regardless of schema version.
*/
suspend fun getForms(formsUrl: String): Result<FillAssistFormsJson>
suspend fun getForms(filename: String): Result<FillAssistFormsJson>
}
@@ -12,9 +12,9 @@ internal class FillAssistServiceImpl(
private val api: FillAssistApi,
) : FillAssistService {
override suspend fun getManifest(url: String): Result<FillAssistManifestJson> =
api.getManifest(url = url).toResult()
override suspend fun getManifest(): Result<FillAssistManifestJson> =
api.getManifest().toResult()
override suspend fun getForms(formsUrl: String): Result<FillAssistFormsJson> =
api.getForms(url = formsUrl).toResult()
override suspend fun getForms(filename: String): Result<FillAssistFormsJson> =
api.getForms(filename = filename).toResult()
}
@@ -1,305 +0,0 @@
package com.bitwarden.network.model
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonPrimitive
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
class FillAssistFormsJsonTest {
private val json = Json { ignoreUnknownKeys = true }
@Test
fun `deserialize simple login form`() {
assertEquals(
SIMPLE_LOGIN_FORMS,
json.decodeFromString<FillAssistFormsJson>(SIMPLE_LOGIN_JSON),
)
}
@Test
fun `deserialize host with null value is excluded host`() {
val result = json.decodeFromString<FillAssistFormsJson>(NULL_HOST_JSON)
assertNull(result.hosts?.get("excluded.com"))
}
@Test
fun `deserialize null pathname is excluded path`() {
val result = json.decodeFromString<FillAssistFormsJson>(NULL_PATHNAME_JSON)
assertNull(result.hosts?.get("example.com")?.pathnames?.get("/excluded"))
}
@Test
fun `deserialize composite OTP field`() {
assertEquals(
OTP_FORMS,
json.decodeFromString<FillAssistFormsJson>(OTP_JSON),
)
}
@Test
fun `deserialize compound selector with multiple alternatives`() {
assertEquals(
HONEYPOT_FORMS,
json.decodeFromString<FillAssistFormsJson>(HONEYPOT_JSON),
)
}
@Test
fun `deserialize form with container`() {
assertEquals(
CONTAINER_FORMS,
json.decodeFromString<FillAssistFormsJson>(CONTAINER_JSON),
)
}
@Test
fun `deserialize form with actions field is graceful`() {
// actions is intentionally excluded from FormJson — handled by ignoreUnknownKeys.
assertEquals(
SIMPLE_LOGIN_FORMS,
json.decodeFromString<FillAssistFormsJson>(SIMPLE_LOGIN_WITH_ACTIONS_JSON),
)
}
}
private val SIMPLE_LOGIN_FORMS = FillAssistFormsJson(
schemaVersion = "1.0.0",
hosts = mapOf(
"example.com" to FillAssistFormsJson.HostEntryJson(
forms = listOf(
FillAssistFormsJson.FormJson(
category = "account-login",
container = null,
fields = mapOf(
"username" to JsonArray(listOf(JsonPrimitive("input#username"))),
"password" to JsonArray(listOf(JsonPrimitive("input#password"))),
),
),
),
pathnames = null,
),
),
)
private val OTP_FORMS = FillAssistFormsJson(
schemaVersion = "1.0.0",
hosts = mapOf(
"example.com" to FillAssistFormsJson.HostEntryJson(
forms = null,
pathnames = mapOf(
"/login" to FillAssistFormsJson.PathnameEntryJson(
forms = listOf(
FillAssistFormsJson.FormJson(
category = "account-login",
container = null,
fields = mapOf(
"oneTimeCode" to JsonArray(
listOf(
JsonArray(
listOf(
JsonPrimitive("input[name='otp-0']"),
JsonPrimitive("input[name='otp-1']"),
JsonPrimitive("input[name='otp-2']"),
JsonPrimitive("input[name='otp-3']"),
JsonPrimitive("input[name='otp-4']"),
JsonPrimitive("input[name='otp-5']"),
),
),
),
),
),
),
),
),
),
),
),
)
private val HONEYPOT_FORMS = FillAssistFormsJson(
schemaVersion = "1.0.0",
hosts = mapOf(
"example.com" to FillAssistFormsJson.HostEntryJson(
forms = listOf(
FillAssistFormsJson.FormJson(
category = "account-login",
container = null,
fields = mapOf(
"username" to JsonArray(
listOf(
JsonPrimitive("input#password[name='password']"),
JsonPrimitive("input[name='password']"),
),
),
"password" to JsonArray(
listOf(
JsonPrimitive("input#username[name='username']"),
JsonPrimitive("input[name='username']"),
),
),
),
),
),
pathnames = null,
),
),
)
private val CONTAINER_FORMS = FillAssistFormsJson(
schemaVersion = "1.0.0",
hosts = mapOf(
"example.com" to FillAssistFormsJson.HostEntryJson(
forms = null,
pathnames = mapOf(
"/login" to FillAssistFormsJson.PathnameEntryJson(
forms = listOf(
FillAssistFormsJson.FormJson(
category = "account-login",
container = listOf("div#login-container"),
fields = mapOf(
"username" to JsonArray(listOf(JsonPrimitive("input#user"))),
"password" to JsonArray(listOf(JsonPrimitive("input#pass"))),
),
),
),
),
),
),
),
)
private const val SIMPLE_LOGIN_JSON = """
{
"schemaVersion": "1.0.0",
"hosts": {
"example.com": {
"forms": [
{
"category": "account-login",
"fields": {
"username": ["input#username"],
"password": ["input#password"]
}
}
]
}
}
}
"""
private const val NULL_HOST_JSON = """
{
"schemaVersion": "1.0.0",
"hosts": {
"excluded.com": null
}
}
"""
private const val NULL_PATHNAME_JSON = """
{
"schemaVersion": "1.0.0",
"hosts": {
"example.com": {
"pathnames": {
"/excluded": null
}
}
}
}
"""
private const val OTP_JSON = """
{
"schemaVersion": "1.0.0",
"hosts": {
"example.com": {
"pathnames": {
"/login": {
"forms": [
{
"category": "account-login",
"fields": {
"oneTimeCode": [
["input[name='otp-0']","input[name='otp-1']","input[name='otp-2']",
"input[name='otp-3']","input[name='otp-4']","input[name='otp-5']"]
]
}
}
]
}
}
}
}
}
"""
private const val HONEYPOT_JSON = """
{
"schemaVersion": "1.0.0",
"hosts": {
"example.com": {
"forms": [
{
"category": "account-login",
"fields": {
"username": ["input#password[name='password']", "input[name='password']"],
"password": ["input#username[name='username']", "input[name='username']"]
}
}
]
}
}
}
"""
private const val CONTAINER_JSON = """
{
"schemaVersion": "1.0.0",
"hosts": {
"example.com": {
"pathnames": {
"/login": {
"forms": [
{
"category": "account-login",
"container": ["div#login-container"],
"fields": {
"username": ["input#user"],
"password": ["input#pass"]
}
}
]
}
}
}
}
}
"""
// Same structure as SIMPLE_LOGIN_JSON with the actions field present — verifies that
// actions (intentionally omitted from FormJson) is handled by ignoreUnknownKeys = true.
private const val SIMPLE_LOGIN_WITH_ACTIONS_JSON = """
{
"schemaVersion": "1.0.0",
"hosts": {
"example.com": {
"forms": [
{
"category": "account-login",
"fields": {
"username": ["input#username"],
"password": ["input#password"]
},
"actions": {
"submit": ["button#submit"]
}
}
]
}
}
}
"""
@@ -1,165 +0,0 @@
package com.bitwarden.network.model
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
class FillAssistManifestJsonTest {
private val json = Json { ignoreUnknownKeys = true }
@Test
fun `deserialize full manifest with v0 entry`() {
assertEquals(
FULL_MANIFEST,
json.decodeFromString<FillAssistManifestJson>(MANIFEST_JSON),
)
}
@Test
fun `deserialize manifest with multiple version entries`() {
assertEquals(
MULTI_VERSION_MANIFEST,
json.decodeFromString<FillAssistManifestJson>(MANIFEST_MULTI_VERSION_JSON),
)
}
@Test
fun `deserialize manifest with unknown top-level fields is graceful`() {
assertEquals(
FULL_MANIFEST,
json.decodeFromString<FillAssistManifestJson>(MANIFEST_EXTRA_FIELDS_JSON),
)
}
@Test
fun `deserialize minimal manifest with null fields`() {
val result = json.decodeFromString<FillAssistManifestJson>("{}")
assertNull(result.buildId)
assertNull(result.maps)
}
@Test
fun `deserialize manifest with deprecated version entry`() {
assertEquals(
DEPRECATED_MANIFEST,
json.decodeFromString<FillAssistManifestJson>(MANIFEST_DEPRECATED_JSON),
)
}
}
private val FULL_MANIFEST = FillAssistManifestJson(
buildId = "local-build",
timestamp = "2026-05-20T15:01:02.956Z",
gitSha = "abc123",
maps = FillAssistManifestJson.MapsJson(
forms = mapOf(
"v0" to FillAssistManifestJson.FileEntryJson(
filename = "forms.v0.json",
cid = "sha256:abc123def456",
schema = "forms.v0.schema.json",
),
),
),
)
private val MULTI_VERSION_MANIFEST = FillAssistManifestJson(
buildId = "local-build",
timestamp = null,
gitSha = null,
maps = FillAssistManifestJson.MapsJson(
forms = mapOf(
"v0" to FillAssistManifestJson.FileEntryJson(
filename = "forms.v0.json",
cid = "sha256:aaa",
schema = "forms.v0.schema.json",
),
"v1" to FillAssistManifestJson.FileEntryJson(
filename = "forms.v1.json",
cid = "sha256:bbb",
schema = "forms.v1.schema.json",
),
),
),
)
private const val MANIFEST_JSON = """
{
"buildId": "local-build",
"timestamp": "2026-05-20T15:01:02.956Z",
"gitSha": "abc123",
"maps": {
"forms": {
"v0": {
"filename": "forms.v0.json",
"cid": "sha256:abc123def456",
"schema": "forms.v0.schema.json"
}
}
}
}
"""
private const val MANIFEST_MULTI_VERSION_JSON = """
{
"buildId": "local-build",
"maps": {
"forms": {
"v0": { "filename": "forms.v0.json", "cid": "sha256:aaa", "schema": "forms.v0.schema.json" },
"v1": { "filename": "forms.v1.json", "cid": "sha256:bbb", "schema": "forms.v1.schema.json" }
}
}
}
"""
private val DEPRECATED_MANIFEST = FillAssistManifestJson(
buildId = "local-build",
timestamp = null,
gitSha = null,
maps = FillAssistManifestJson.MapsJson(
forms = mapOf(
"v0" to FillAssistManifestJson.FileEntryJson(
filename = "forms.v0.json",
cid = "sha256:abc123def456",
schema = "forms.v0.schema.json",
deprecated = true,
),
),
),
)
private const val MANIFEST_DEPRECATED_JSON = """
{
"buildId": "local-build",
"maps": {
"forms": {
"v0": {
"filename": "forms.v0.json",
"cid": "sha256:abc123def456",
"schema": "forms.v0.schema.json",
"deprecated": true
}
}
}
}
"""
// Same structure as MANIFEST_JSON with an extra unknown key — verifies ignoreUnknownKeys = true.
private const val MANIFEST_EXTRA_FIELDS_JSON = """
{
"buildId": "local-build",
"timestamp": "2026-05-20T15:01:02.956Z",
"gitSha": "abc123",
"checksums": "ignored",
"maps": {
"forms": {
"v0": {
"filename": "forms.v0.json",
"cid": "sha256:abc123def456",
"schema": "forms.v0.schema.json"
}
}
}
}
"""
@@ -22,25 +22,25 @@ class FillAssistServiceTest : BaseServiceTest() {
@Test
fun `getManifest should parse manifest response`() = runTest {
server.enqueue(MockResponse().setBody(MANIFEST_JSON))
assertEquals(MANIFEST.asSuccess(), service.getManifest(url = "$urlPrefix/manifest.json"))
assertEquals(MANIFEST.asSuccess(), service.getManifest())
}
@Test
fun `getManifest should return failure on server error`() = runTest {
server.enqueue(MockResponse().setResponseCode(500))
assertTrue(service.getManifest(url = "$urlPrefix/manifest.json").isFailure)
assertTrue(service.getManifest().isFailure)
}
@Test
fun `getForms should parse and return forms`() = runTest {
server.enqueue(MockResponse().setBody(FORMS_V1_JSON))
assertEquals(FORMS_V1.asSuccess(), service.getForms(formsUrl = "$urlPrefix/forms.v1.json"))
assertEquals(FORMS_V1.asSuccess(), service.getForms(filename = "forms.v1.json"))
}
@Test
fun `getForms should return failure on server error`() = runTest {
server.enqueue(MockResponse().setResponseCode(404))
assertTrue(service.getForms(formsUrl = "$urlPrefix/forms.v1.json").isFailure)
assertTrue(service.getForms(filename = "forms.v1.json").isFailure)
}
}
@@ -54,6 +54,7 @@ private val MANIFEST = FillAssistManifestJson(
filename = "forms.v1.json",
cid = "sha256:5b8f688d24bb9c38b4094838fa2baacb3cc4ab302e3545adf016b05f6b6b96db",
schema = "forms.v1.schema.json",
deprecated = null,
),
),
),