Update Conductarr TV contracts and ratings reads

This commit is contained in:
justinzeus 2026-11-05 22:55:25 +01:00
parent 9dd321f07e
commit f53defad90
13 changed files with 600 additions and 36 deletions

View file

@ -13,6 +13,7 @@ class CacheTest {
Triple(CachePolicy.DAILY, ReadTrigger.IF_STALE, 86_400_000L),
Triple(CachePolicy.DISCOVERY, ReadTrigger.IF_STALE, 1_800_000L),
Triple(CachePolicy.RATINGS, ReadTrigger.IF_STALE, 21_600_000L),
Triple(CachePolicy.SEASON, ReadTrigger.IF_STALE, 21_600_000L),
Triple(CachePolicy.ACTIVITY, ReadTrigger.POLL, 30_000L),
)
for ((policy, trigger, lifetime) in rows) {

View file

@ -121,7 +121,7 @@ class ClientTest {
@Test fun `ratings 404 is cached as none for six hours but server failure is not cached`() = runBlocking {
val client = client()
val call = TvRoutes.enrichment(42, MediaType.MOVIE)
val call = TvRoutes.ratings(42, MediaType.MOVIE)
server.enqueue(response("missing", 404))
assertTrue((client.read(call).toList().single() as ReadResult.Value).saved.absent)
now += 6 * 60 * 60 * 1000L - 1

View file

@ -29,7 +29,7 @@ class ContractTest {
for (name in reviewed.keys) assertEquals("Schema constraint drift: $name", reviewed[name], schemas[name])
}
@Test fun `all twenty TV operations exist with their typed request and response contracts`() {
@Test fun `all twenty two TV operations exist with their typed request and response contracts`() {
val expected = setOf(
"GET /api/v1/operator", "GET /api/v1/recommendations/profiles",
"GET /api/v1/recommendations/profiles/{profile}/feed",
@ -39,6 +39,7 @@ class ContractTest {
"GET /catalogue/options", "GET /catalogue/discover",
"GET /catalogue/titles/{tmdb}/{type}", "GET /catalogue/titles/{tmdb}/{type}/cast",
"GET /catalogue/titles/{tmdb}/{type}/related", "GET /catalogue/titles/{tmdb}/{type}/enrichment",
"GET /catalogue/titles/{tmdb}/{type}/ratings", "GET /catalogue/titles/{tmdb}/{type}/display",
"GET /catalogue/tv/{tmdb}/seasons/{season}", "GET /catalogue/performers/{performer}",
"GET /catalogue/performers/{performer}/filmography",
"GET /api/v1/activity", "GET /api/v1/activity/titles/{tmdb}/{type}",
@ -47,6 +48,19 @@ class ContractTest {
)
assertEquals(expected, TvRoutes.contracts.map { "${it.method} ${it.path}" }.toSet())
assertEquals(expected.size, TvRoutes.contracts.size)
val operations = setOf(
"get-cockpit-operator", "get-recommendation-profiles", "get-library-audiences",
"get-recommendation-feed", "get-catalogue-options", "get-catalogue-discover",
"get-recommendation-dismissals", "post-recommendation-dismissal", "delete-recommendation-dismissal",
"get-catalogue-title", "get-catalogue-cast", "get-catalogue-related", "get-catalogue-season",
"get-catalogue-enrichment", "get-catalogue-display", "get-activity-title", "get-title-requests",
"get-catalogue-performer", "get-catalogue-filmography", "post-request-intent", "get-activity",
"get-catalogue-ratings",
)
assertEquals(operations, TvRoutes.contracts.map { route ->
document.getValue("paths").jsonObject.getValue(route.path).jsonObject
.getValue(route.method.lowercase()).jsonObject.getValue("operationId").jsonPrimitive.content
}.toSet())
val paths = document["paths"]!!.jsonObject
for (route in TvRoutes.contracts) {
val operation = paths.getValue(route.path).jsonObject.getValue(route.method.lowercase()).jsonObject

View file

@ -13,6 +13,7 @@ internal val modelSerializers: List<KSerializer<*>> = listOf(
CastMember.serializer(),
Detail.serializer(),
Discovery.serializer(),
Display.serializer(),
Enrichment.serializer(),
Episode.serializer(),
EpisodeFileChip.serializer(),
@ -72,6 +73,7 @@ internal val modelSerializers: List<KSerializer<*>> = listOf(
TitleCast.serializer(),
TitleMetadata.serializer(),
TitleRequestsBody.serializer(),
TitleRatings.serializer(),
TitleVersion.serializer(),
ViewerReadiness.serializer(),
)

View file

@ -444,6 +444,16 @@
"unknown"
],
"type": "string"
},
"vote_average": {
"description": "Provider episode score on a 0\u201310 scale; absence is not zero.",
"format": "double",
"type": "number"
},
"vote_count": {
"description": "Provider episode vote count; explicit zero is retained.",
"format": "int64",
"type": "integer"
}
},
"required": [
@ -3169,5 +3179,77 @@
"sources"
],
"type": "object"
},
"Display": {
"additionalProperties": false,
"properties": {
"$schema": {
"description": "A URL to the JSON Schema for this object.",
"examples": [
"https://example.com/schemas/Display.json"
],
"format": "uri",
"readOnly": true,
"type": "string"
},
"media_type": {
"enum": [
"movie",
"tv"
],
"type": "string"
},
"metadata_state": {
"enum": [
"observed",
"missing",
"unavailable"
],
"type": "string"
},
"poster_url": {
"type": "string"
},
"title": {
"type": "string"
},
"tmdb_id": {
"format": "int64",
"type": "integer"
}
},
"required": [
"tmdb_id",
"media_type",
"metadata_state"
],
"type": "object"
},
"TitleRatings": {
"additionalProperties": false,
"properties": {
"$schema": {
"description": "A URL to the JSON Schema for this object.",
"examples": [
"https://example.com/schemas/TitleRatings.json"
],
"format": "uri",
"readOnly": true,
"type": "string"
},
"ratings": {
"items": {
"$ref": "#/components/schemas/Rating"
},
"type": [
"array",
"null"
]
}
},
"required": [
"ratings"
],
"type": "object"
}
}