Change repo attribute icons to be localized

This affects anti-features and categories. Reflection diffing has been made more robust in the process with the earlier FileV2 hack removed and better error messages.
This commit is contained in:
Torsten Grote
2022-09-19 14:59:48 -03:00
committed by Hans-Christoph Steiner
parent 45708edb66
commit ddb6d46b6f
17 changed files with 278 additions and 123 deletions

View File

@@ -11,7 +11,7 @@ import org.fdroid.index.v2.ReleaseChannelV2
import org.fdroid.index.v2.RepoV2
import org.fdroid.test.DiffUtils.applyDiff
import org.fdroid.test.DiffUtils.randomDiff
import org.fdroid.test.TestRepoUtils.getRandomFileV2
import org.fdroid.test.TestRepoUtils.getRandomLocalizedFileV2
import org.fdroid.test.TestRepoUtils.getRandomLocalizedTextV2
import org.fdroid.test.TestRepoUtils.getRandomMirror
import org.fdroid.test.TestRepoUtils.getRandomRepo
@@ -116,13 +116,17 @@ internal class RepositoryDiffTest : DbTest() {
fun antiFeaturesDiff() {
val repo = getRandomRepo().copy(antiFeatures = getRandomMap {
getRandomString() to AntiFeatureV2(
icon = getRandomFileV2(),
icon = getRandomLocalizedFileV2(),
name = getRandomLocalizedTextV2(),
description = getRandomLocalizedTextV2(),
)
})
val antiFeatures = repo.antiFeatures.randomDiff {
AntiFeatureV2(getRandomFileV2(), getRandomLocalizedTextV2(), getRandomLocalizedTextV2())
AntiFeatureV2(
icon = getRandomLocalizedFileV2(),
name = getRandomLocalizedTextV2(),
description = getRandomLocalizedTextV2(),
)
}
val json = """
{
@@ -141,7 +145,7 @@ internal class RepositoryDiffTest : DbTest() {
fun antiFeatureKeyChangeDiff() {
val antiFeatureKey = getRandomString()
val antiFeature = AntiFeatureV2(
icon = getRandomFileV2(),
icon = getRandomLocalizedFileV2(),
name = getRandomLocalizedTextV2(),
description = getRandomLocalizedTextV2(),
)
@@ -150,7 +154,7 @@ internal class RepositoryDiffTest : DbTest() {
@Suppress("UNCHECKED_CAST")
val newAntiFeatures = mapOf(antiFeatureKey to antiFeature.copy(
icon = null,
icon = emptyMap(),
name = getRandomLocalizedTextV2(),
description = getRandomLocalizedTextV2(),
))
@@ -173,13 +177,17 @@ internal class RepositoryDiffTest : DbTest() {
fun categoriesDiff() {
val repo = getRandomRepo().copy(categories = getRandomMap {
getRandomString() to CategoryV2(
icon = getRandomFileV2(),
icon = getRandomLocalizedFileV2(),
name = getRandomLocalizedTextV2(),
description = getRandomLocalizedTextV2(),
)
})
val categories = repo.categories.randomDiff {
CategoryV2(getRandomFileV2(), getRandomLocalizedTextV2(), getRandomLocalizedTextV2())
CategoryV2(
icon = getRandomLocalizedFileV2(),
name = getRandomLocalizedTextV2(),
description = getRandomLocalizedTextV2(),
)
}
val json = """
{

View File

@@ -31,8 +31,8 @@ internal object TestUtils {
val expectedAntiFeatures = repoV2.antiFeatures.toRepoAntiFeatures(repoId).toSet()
assertEquals(expectedAntiFeatures, repo.antiFeatures.toSet())
// categories
val expectedCategories = repoV2.categories.toRepoCategories(repoId).toSet()
assertEquals(expectedCategories, repo.categories.toSet())
val expectedCategories = repoV2.categories.toRepoCategories(repoId).sortedBy { it.id }
assertEquals(expectedCategories, repo.categories.sortedBy { it.id })
// release channels
val expectedReleaseChannels = repoV2.releaseChannels.toRepoReleaseChannel(repoId).toSet()
assertEquals(expectedReleaseChannels, repo.releaseChannels.toSet())

View File

@@ -237,7 +237,7 @@ internal fun MirrorV2.toMirror(repoId: Long) = Mirror(
* An attribute belonging to a [Repository].
*/
public abstract class RepoAttribute {
public abstract val icon: FileV2?
public abstract val icon: LocalizedFileV2
internal abstract val name: LocalizedTextV2
internal abstract val description: LocalizedTextV2
@@ -264,7 +264,7 @@ public abstract class RepoAttribute {
public data class AntiFeature internal constructor(
internal val repoId: Long,
internal val id: String,
@Embedded(prefix = "icon_") public override val icon: FileV2? = null,
override val icon: LocalizedFileV2,
override val name: LocalizedTextV2,
override val description: LocalizedTextV2,
) : RepoAttribute() {
@@ -299,7 +299,7 @@ internal fun Map<String, AntiFeatureV2>.toRepoAntiFeatures(repoId: Long) = map {
public data class Category internal constructor(
internal val repoId: Long,
public val id: String,
@Embedded(prefix = "icon_") public override val icon: FileV2? = null,
override val icon: LocalizedFileV2,
override val name: LocalizedTextV2,
override val description: LocalizedTextV2,
) : RepoAttribute() {
@@ -334,7 +334,7 @@ internal fun Map<String, CategoryV2>.toRepoCategories(repoId: Long) = map {
public data class ReleaseChannel(
internal val repoId: Long,
internal val id: String,
@Embedded(prefix = "icon_") public override val icon: FileV2? = null,
override val icon: LocalizedFileV2 = emptyMap(),
override val name: LocalizedTextV2,
override val description: LocalizedTextV2,
) : RepoAttribute() {

View File

@@ -272,7 +272,7 @@ internal interface RepositoryDaoInt : RepositoryDao {
jsonObjectKey = "antiFeatures",
itemList = repo.antiFeatures,
itemFinder = { key, item -> item.id == key },
newItem = { key -> AntiFeature(repoId, key, null, emptyMap(), emptyMap()) },
newItem = { key -> AntiFeature(repoId, key, emptyMap(), emptyMap(), emptyMap()) },
deleteAll = { deleteAntiFeatures(repoId) },
deleteOne = { key -> deleteAntiFeature(repoId, key) },
insertReplace = { list -> insertAntiFeatures(list) },
@@ -283,7 +283,7 @@ internal interface RepositoryDaoInt : RepositoryDao {
jsonObjectKey = "categories",
itemList = repo.categories,
itemFinder = { key, item -> item.id == key },
newItem = { key -> Category(repoId, key, null, emptyMap(), emptyMap()) },
newItem = { key -> Category(repoId, key, emptyMap(), emptyMap(), emptyMap()) },
deleteAll = { deleteCategories(repoId) },
deleteOne = { key -> deleteCategory(repoId, key) },
insertReplace = { list -> insertCategories(list) },
@@ -294,7 +294,7 @@ internal interface RepositoryDaoInt : RepositoryDao {
jsonObjectKey = "releaseChannels",
itemList = repo.releaseChannels,
itemFinder = { key, item -> item.id == key },
newItem = { key -> ReleaseChannel(repoId, key, null, emptyMap(), emptyMap()) },
newItem = { key -> ReleaseChannel(repoId, key, emptyMap(), emptyMap(), emptyMap()) },
deleteAll = { deleteReleaseChannels(repoId) },
deleteOne = { key -> deleteReleaseChannel(repoId, key) },
insertReplace = { list -> insertReleaseChannels(list) },

View File

@@ -70,6 +70,8 @@ kotlin {
}
}
androidTest {
// needed because of https://issuetracker.google.com/issues/231701341
kotlin.srcDir("src/commonTest/kotlin")
dependencies {
implementation 'junit:junit:4.13.2'
implementation 'io.mockk:mockk:1.12.4'

View File

@@ -17,8 +17,10 @@ import kotlinx.serialization.serializer
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.typeOf
/**
* A class using Kotlin reflection to implement JSON Merge Patch (RFC 7386) against data classes.
@@ -28,32 +30,9 @@ import kotlin.reflect.full.primaryConstructor
*/
public object ReflectionDiffer {
@Throws(SerializationException::class)
public fun applyDiff(
obj: Map<String, *>,
diff: JsonObject,
isFileV2: Boolean = false,
): Map<String, *> = obj.toMutableMap().apply {
diff.entries.forEach { (key, value) ->
when (value) {
is JsonNull -> remove(key)
is JsonPrimitive -> set(key, value.jsonPrimitive.content)
is JsonObject -> {
val newValue: Any = if (isFileV2) {
constructFromJson(FileV2::class.primaryConstructor!!, value.jsonObject)
} else {
applyDiff(HashMap<String, LocalizedTextV2>(), value.jsonObject)
}
set(key, newValue)
}
else -> e("unsupported map value: $value")
}
}
}
@Throws(SerializationException::class)
public fun <T : Any> applyDiff(obj: T, diff: JsonObject): T {
val constructor = obj::class.primaryConstructor ?: e("no primary constructor")
val constructor = obj::class.primaryConstructor ?: e("no primary constructor ${obj::class}")
val params = HashMap<KParameter, Any?>()
constructor.parameters.forEach { parameter ->
val prop = obj::class.memberProperties.find { memberProperty ->
@@ -79,14 +58,7 @@ public object ReflectionDiffer {
List::class -> diff[prop.name]?.jsonArrayOrNull()?.map {
it.primitiveOrNull()?.contentOrNull ?: e("${prop.name} non-primitive array")
} ?: e("${prop.name} no array")
Map::class -> if (prop.name == "icon") applyDiff(
obj = prop.getter.call(obj) as? Map<String, *> ?: emptyMap<String, FileV2>(),
diff = diff[prop.name]?.jsonObjectOrNull() ?: e("${prop.name} no map"),
isFileV2 = true, // yes this is super hacky
) else applyDiff(
obj = prop.getter.call(obj) as? Map<String, *> ?: emptyMap<String, String>(),
diff = diff[prop.name]?.jsonObjectOrNull() ?: e("${prop.name} no map"),
)
Map::class -> diffMap(prop.returnType, prop.getter.call(obj), prop.name, diff)
else -> {
val newObj = prop.getter.call(obj)
val jsonObject = diff[prop.name] as? JsonObject ?: e("${prop.name} no dict")
@@ -134,10 +106,7 @@ public object ReflectionDiffer {
List::class -> diff[prop.name]?.jsonArrayOrNull()?.map {
it.primitiveOrNull()?.contentOrNull ?: e("${prop.name} non-primitive array")
} ?: e("${prop.name} no array")
Map::class -> applyDiff(
obj = HashMap<String, String>(),
diff = diff[prop.name]?.jsonObjectOrNull() ?: e("${prop.name} no dict"),
)
Map::class -> diffMap(prop.type, null, prop.name, diff)
else -> constructFromJson(
factory = (prop.type.classifier as KClass<*>).primaryConstructor!!,
diff = diff[prop.name]?.jsonObjectOrNull() ?: e("${prop.name} no dict"),
@@ -147,6 +116,93 @@ public object ReflectionDiffer {
return factory.callBy(params)
}
@Suppress("UNCHECKED_CAST")
private fun <T> diffMap(type: KType, obj: T?, key: String?, diff: JsonObject) = when (type) {
typeOf<LocalizedTextV2>() -> applyTextDiff(
obj = obj as? LocalizedTextV2 ?: HashMap(),
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
)
typeOf<LocalizedTextV2?>() -> applyTextDiff(
obj = obj as? LocalizedTextV2? ?: HashMap(),
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
)
typeOf<LocalizedFileV2>() -> applyFileDiff(
obj = obj as? LocalizedFileV2 ?: HashMap(),
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
)
typeOf<LocalizedFileV2?>() -> applyFileDiff(
obj = obj as? LocalizedFileV2? ?: HashMap(),
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
)
typeOf<Map<String, LocalizedTextV2>>() -> applyMapTextDiff(
obj = obj as? Map<String, LocalizedTextV2> ?: HashMap(),
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
)
typeOf<Map<String, LocalizedTextV2>?>() -> applyMapTextDiff(
obj = obj as? Map<String, LocalizedTextV2>? ?: HashMap(),
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
)
else -> e("Unknown map: $key: $type = ${diff[key]}")
}
@Throws(SerializationException::class)
private fun applyTextDiff(
obj: LocalizedTextV2,
diff: JsonObject,
): LocalizedTextV2 = obj.toMutableMap().apply {
diff.entries.forEach { (locale, textElement) ->
if (textElement is JsonNull) {
remove(locale)
return@forEach
}
val text = textElement.primitiveOrNull()?.contentOrNull
?: throw SerializationException("no string: $textElement")
set(locale, text)
}
}
@Throws(SerializationException::class)
private fun applyFileDiff(
obj: LocalizedFileV2,
diff: JsonObject,
): LocalizedFileV2 = obj.toMutableMap().apply {
diff.entries.forEach { (locale, fileV2Element) ->
if (fileV2Element is JsonNull) {
remove(locale)
return@forEach
}
val fileV2Object = fileV2Element.jsonObjectOrNull()
?: throw SerializationException("no FileV2: $fileV2Element")
val fileV2 = if (locale in obj) {
applyDiff(obj[locale] as FileV2, fileV2Object)
} else {
constructFromJson(FileV2::class.primaryConstructor!!, fileV2Object)
}
set(locale, fileV2)
}
}
@Throws(SerializationException::class)
private fun applyMapTextDiff(
obj: Map<String, LocalizedTextV2>,
diff: JsonObject,
): Map<String, LocalizedTextV2> = obj.toMutableMap().apply {
diff.entries.forEach { (key, localizedTextElement) ->
if (localizedTextElement is JsonNull) {
remove(key)
return@forEach
}
val localizedTextObject = localizedTextElement.jsonObjectOrNull()
?: throw SerializationException("no FileV2: $localizedTextElement")
val localizedText = if (key in obj) {
applyTextDiff(obj[key] as LocalizedTextV2, localizedTextObject)
} else {
applyTextDiff(HashMap(), localizedTextObject)
}
set(key, localizedText)
}
}
private fun JsonElement.primitiveOrNull(): JsonPrimitive? = try {
jsonPrimitive
} catch (e: IllegalArgumentException) {

View File

@@ -14,6 +14,7 @@ import org.fdroid.test.DiffUtils.clean
import org.fdroid.test.DiffUtils.cleanMetadata
import org.fdroid.test.DiffUtils.cleanRepo
import org.fdroid.test.DiffUtils.cleanVersion
import org.fdroid.test.LOCALE
import java.io.File
import java.io.FileInputStream
import kotlin.reflect.full.primaryConstructor
@@ -66,6 +67,55 @@ internal class ReflectionDifferTest {
endPath = "$assetPath/index-max-v2.json",
)
@Test
fun testLocalizedFileV2() {
val category1 = CategoryV2(
name = mapOf(LOCALE to "Cat1"),
icon = mapOf(LOCALE to FileV2(
name = "file1",
sha256 = "hash",
size = 1,
)),
)
val category2 = CategoryV2(
name = mapOf(LOCALE to "Cat2"),
)
val diff1 = JsonObject(
mapOf("icon" to JsonObject(
mapOf(LOCALE to JsonObject(
mapOf("name" to JsonPrimitive("file1b"))
))
))
)
val diff2 = JsonObject(
mapOf("icon" to JsonObject(
mapOf(LOCALE to JsonObject(
mapOf(
"name" to JsonPrimitive("file2"),
"sha256" to JsonPrimitive("hash2"),
"size" to JsonPrimitive(2L),
)
))
))
)
val diffedCat1 = ReflectionDiffer.applyDiff(category1, diff1)
val diffedCat2 = ReflectionDiffer.applyDiff(category2, diff2)
val diffedIcon1 = diffedCat1.icon[LOCALE]
val diffedIcon2 = diffedCat2.icon[LOCALE]
val expectedIcon1 = FileV2(
name = "file1b",
sha256 = "hash",
size = 1,
)
val expectedIcon2 = FileV2(
name = "file2",
sha256 = "hash2",
size = 2,
)
assertEquals(expectedIcon1, diffedIcon1)
assertEquals(expectedIcon2, diffedIcon2)
}
@Test
fun testClassWithoutPrimaryConstructor() {
class NoConstructor {

View File

@@ -58,6 +58,7 @@ public data class FileV2(
if (string == null) return null
return json.decodeFromString(string)
}
@JvmStatic
public fun fromPath(path: String): FileV2 = FileV2(path)
}
@@ -93,8 +94,8 @@ public data class RepoV2(
) {
public fun walkFiles(fileConsumer: (FileV2?) -> Unit) {
icon.values.forEach { fileConsumer(it) }
antiFeatures.values.forEach { fileConsumer(it.icon) }
categories.values.forEach { fileConsumer(it.icon) }
antiFeatures.values.forEach { it.icon.values.forEach { icon -> fileConsumer(icon) } }
categories.values.forEach { it.icon.values.forEach { icon -> fileConsumer(icon) } }
}
}
@@ -110,14 +111,14 @@ public data class MirrorV2(
@Serializable
public data class AntiFeatureV2(
val icon: FileV2? = null,
val icon: LocalizedFileV2 = emptyMap(),
val name: LocalizedTextV2,
val description: LocalizedTextV2 = emptyMap(),
)
@Serializable
public data class CategoryV2(
val icon: FileV2? = null,
val icon: LocalizedFileV2 = emptyMap(),
val name: LocalizedTextV2,
val description: LocalizedTextV2 = emptyMap(),
)

View File

@@ -47,9 +47,11 @@
},
"AntiFeature": {
"icon": {
"name": "/icons/antifeature.png",
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 254916
"en-US": {
"name": "/icons/antifeature.png",
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 254916
}
}
},
"NonFreeNet": {
@@ -70,9 +72,11 @@
"System": null,
"Cat3": {
"icon": {
"name": "/icons/cat3.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat3.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat3"
@@ -83,9 +87,11 @@
},
"Cat2": {
"icon": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat3"
@@ -96,7 +102,9 @@
},
"Cat1": {
"icon": {
"name": "/icons/cat1.png"
"en-US": {
"name": "/icons/cat1.png"
}
},
"description": {
"en-US": "Cat1"

View File

@@ -45,9 +45,11 @@
},
"AntiFeature": {
"icon": {
"name": "/icons/antifeature.png",
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 254916
"en-US": {
"name": "/icons/antifeature.png",
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 254916
}
},
"name": {
"en-US": "AntiFeature"
@@ -73,9 +75,11 @@
"categories": {
"Cat3": {
"icon": {
"name": "/icons/cat3.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat3.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat3"
@@ -86,9 +90,11 @@
},
"Cat2": {
"icon": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat3"
@@ -99,9 +105,11 @@
},
"Cat1": {
"icon": {
"name": "/icons/cat1.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat1.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat1"

View File

@@ -45,9 +45,11 @@
},
"AntiFeature": {
"icon": {
"name": "/icons/antifeature.png",
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 254916
"en-US": {
"name": "/icons/antifeature.png",
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 254916
}
},
"name": {
"en-US": "AntiFeature"
@@ -73,9 +75,11 @@
"categories": {
"Cat3": {
"icon": {
"name": "/icons/cat3.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat3.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat3"
@@ -86,9 +90,11 @@
},
"Cat2": {
"icon": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat3"
@@ -99,9 +105,11 @@
},
"Cat1": {
"icon": {
"name": "/icons/cat1.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat1.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat1"

View File

@@ -40,9 +40,11 @@
"categories": {
"Cat1": {
"icon": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat1"

View File

@@ -40,9 +40,11 @@
"categories": {
"Cat1": {
"icon": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat1"

View File

@@ -49,9 +49,11 @@
},
"AntiFeature": {
"icon": {
"name": "/icons/antifeature.png",
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 254916
"en-US": {
"name": "/icons/antifeature.png",
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 254916
}
},
"name": {
"en-US": "AntiFeature"
@@ -77,9 +79,11 @@
"categories": {
"Cat3": {
"icon": {
"name": "/icons/cat3.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat3.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat3"
@@ -90,9 +94,11 @@
},
"Cat2": {
"icon": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat3"
@@ -103,9 +109,11 @@
},
"Cat1": {
"icon": {
"name": "/icons/cat1.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat1.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat1"

View File

@@ -40,9 +40,11 @@
"categories": {
"Cat1": {
"icon": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
"en-US": {
"name": "/icons/cat2.png",
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
"size": 9223372036854775807
}
},
"name": {
"en-US": "Cat1"

View File

@@ -187,11 +187,11 @@ object TestDataMidV2 {
categories = mapOf(
"Cat1" to CategoryV2(
name = mapOf(LOCALE to "Cat1"),
icon = FileV2(
icon = mapOf(LOCALE to FileV2(
name = "/icons/cat2.png",
sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
size = Long.MAX_VALUE,
),
)),
),
"System" to CategoryV2(
name = emptyMap(),
@@ -737,11 +737,11 @@ object TestDataMaxV2 {
name = emptyMap(),
),
"AntiFeature" to AntiFeatureV2(
icon = FileV2(
icon = mapOf(LOCALE to FileV2(
name = "/icons/antifeature.png",
sha256 = "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
size = 254916,
),
)),
name = mapOf(LOCALE to "AntiFeature"),
description = mapOf(LOCALE to "A bad anti-feature, we can't show to users."),
),
@@ -756,29 +756,29 @@ object TestDataMaxV2 {
categories = mapOf(
"Cat3" to CategoryV2(
name = mapOf(LOCALE to "Cat3"),
icon = FileV2(
icon = mapOf(LOCALE to FileV2(
name = "/icons/cat3.png",
sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
size = Long.MAX_VALUE,
),
)),
description = mapOf(LOCALE to "Cat3"),
),
"Cat2" to CategoryV2(
name = mapOf(LOCALE to "Cat3"),
icon = FileV2(
icon = mapOf(LOCALE to FileV2(
name = "/icons/cat2.png",
sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
size = Long.MAX_VALUE,
),
)),
description = mapOf(LOCALE to "Cat3"),
),
"Cat1" to CategoryV2(
name = mapOf(LOCALE to "Cat1"),
icon = FileV2(
icon = mapOf(LOCALE to FileV2(
name = "/icons/cat1.png",
sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
size = Long.MAX_VALUE,
),
)),
description = mapOf(LOCALE to "Cat1"),
),
"NoMoreSystem" to CategoryV2(

View File

@@ -46,14 +46,14 @@ object TestRepoUtils {
timestamp = System.currentTimeMillis(),
antiFeatures = TestUtils.getRandomMap {
getRandomString() to AntiFeatureV2(
icon = getRandomFileV2(),
icon = getRandomLocalizedFileV2(),
name = getRandomLocalizedTextV2(),
description = getRandomLocalizedTextV2(),
)
},
categories = TestUtils.getRandomMap {
getRandomString() to CategoryV2(
icon = getRandomFileV2(),
icon = getRandomLocalizedFileV2(),
name = getRandomLocalizedTextV2(),
description = getRandomLocalizedTextV2(),
)