import crate/gen/catalog/artist import crate/gen/catalog/edit import crate/gen/catalog/genre import crate/gen/defs.{ExternalId, Snapshot, Source} import crate/gen/internal import crate/gen/shelf/entry.{type ShelfEntry, ShelfEntry} import crate/storage.{StoredItem} import gleam/dynamic/decode import gleam/json import gleam/option.{None, Some} import gleam/result import gleeunit pub fn main() -> Nil { gleeunit.main() } pub fn cid_link_round_trips_test() { let encoded = internal.encode_cid_link("bafyabc") |> json.to_string assert encoded == "{\"$link\":\"bafyabc\"}" let assert Ok(cid) = json.parse(encoded, internal.cid_link_decoder()) assert cid == "bafyabc" } pub fn bytes_round_trips_test() { let original = <<1, 2, 3, 250>> let encoded = internal.encode_bytes(original) |> json.to_string let assert Ok(decoded) = json.parse(encoded, internal.bytes_decoder()) assert decoded == original } pub fn unknown_round_trips_test() { let assert Ok(value) = json.parse("[1,2,3]", decode.dynamic) assert internal.dynamic_to_json(value) |> json.to_string == "[1,2,3]" } fn sample() -> ShelfEntry { ShelfEntry( action: "acquired", counterparty: None, created_at: "2026-06-26T00:00:00Z", external_ids: None, folder: None, media_grade: Some("NM"), notes: None, price: None, rating: Some(5), release: None, sleeve_grade: None, snapshot: Snapshot( title: "Spiderland", artist_display: "Slint", year: Some(1991), format: Some("LP"), thumb_url: None, cover: None, ) |> Some, source: Source( origin: Some("manual"), client_agent: None, origin_url: None, external: None, record: None, ) |> Some, subject: None, ) } pub fn shelf_entry_round_trips_test() { let value = sample() let assert Ok(decoded) = value |> entry.encode_shelf_entry |> json.to_string |> json.parse(entry.shelf_entry_decoder()) assert decoded == value } pub fn collection_constant_test() { assert entry.collection == "dev.mokkenstorm.crate.shelf.entry" } pub fn optional_fields_are_omitted_not_nulled_test() { // `release` is None, so the key must be absent (not `"release":null`). let release_present = sample() |> entry.encode_shelf_entry |> json.to_string |> json.parse(decode.at(["release"], decode.dynamic)) |> result.is_ok assert release_present == False } pub fn stored_item_round_trips_test() { let stored = StoredItem(uri: "at://x/y/z", cid: "bafy", rkey: "z", value: sample()) let assert Ok(decoded) = stored |> storage.encode_stored_item(entry.encode_shelf_entry) |> json.to_string |> json.parse(storage.stored_item_decoder(entry.shelf_entry_decoder())) assert decoded == stored } pub fn catalog_edit_union_round_trips_test() { let value = edit.CatalogEdit( created_at: "2026-07-07T00:00:00Z", entity: "release", fields: Some( edit.CatalogEditFieldsReleaseFields(edit.ReleaseFields( country: None, cover: None, genres: Some(["ambient"]), released: None, styles: None, thumb_url: None, title: Some("Selected Ambient Works 85-92"), )), ), op: "update", rationale: None, source: None, subject: None, ) let encoded = value |> edit.encode_catalog_edit |> json.to_string // the union member self-describes on the wire let assert Ok("dev.mokkenstorm.crate.catalog.edit#releaseFields") = json.parse(encoded, decode.at(["fields", "$type"], decode.string)) let assert Ok(decoded) = json.parse(encoded, edit.catalog_edit_decoder()) assert decoded == value } pub fn catalog_edit_union_unknown_variant_round_trips_test() { // A variant this build doesn't know (a future artistFields) must survive // decode -> encode untouched via the Other escape hatch. let raw = "{\"op\":\"update\",\"entity\":\"artist\",\"createdAt\":\"t\",\"fields\":{\"$type\":\"dev.mokkenstorm.crate.catalog.edit#artistFields\",\"name\":\"Aphex Twin\"}}" let assert Ok(decoded) = json.parse(raw, edit.catalog_edit_decoder()) let assert Some(edit.CatalogEditFieldsOther(_)) = decoded.fields let re = decoded |> edit.encode_catalog_edit |> json.to_string let assert Ok("Aphex Twin") = json.parse(re, decode.at(["fields", "name"], decode.string)) let assert Ok("dev.mokkenstorm.crate.catalog.edit#artistFields") = json.parse(re, decode.at(["fields", "$type"], decode.string)) } fn sample_artist() -> artist.CatalogArtist { artist.CatalogArtist( based_on: None, created_at: "2026-07-09T00:00:00Z", disambiguation: Some("Welsh electronic musician"), external_ids: Some([ ExternalId( id: "7e5cc599-2c00-4d80-9d24-d2c0143d2775", provider: "musicbrainz", url: None, ), ]), name: "Aphex Twin", sort_name: Some("Aphex Twin"), supersedes: None, thumb_url: None, ) } pub fn catalog_artist_round_trips_test() { let value = sample_artist() let assert Ok(decoded) = value |> artist.encode_catalog_artist |> json.to_string |> json.parse(artist.catalog_artist_decoder()) assert decoded == value assert artist.collection == "dev.mokkenstorm.crate.catalog.artist" } pub fn catalog_genre_round_trips_test() { let value = genre.CatalogGenre( created_at: "2026-07-09T00:00:00Z", description: Some("Fourth-world ambient with a rhythmic backbone."), external_ids: None, name: "Ambient", ) let assert Ok(decoded) = value |> genre.encode_catalog_genre |> json.to_string |> json.parse(genre.catalog_genre_decoder()) assert decoded == value assert genre.collection == "dev.mokkenstorm.crate.catalog.genre" } pub fn catalog_artist_optional_fields_are_omitted_not_nulled_test() { let required_only = artist.CatalogArtist( based_on: None, created_at: "2026-07-09T00:00:00Z", disambiguation: None, external_ids: None, name: "Aphex Twin", sort_name: None, supersedes: None, thumb_url: None, ) let sort_name_present = required_only |> artist.encode_catalog_artist |> json.to_string |> json.parse(decode.at(["sortName"], decode.dynamic)) |> result.is_ok assert sort_name_present == False }