//// Shared test fixtures: stub `Config`/`Context` builders, default record //// literals for the domain types tests repeatedly hand-build //// (`CatalogRelease`, `ShelfEntry`, `OauthSession`), and the small JSON body //// assertion helpers used across the mint/apply handler tests. Doesn't end //// in `_test`, so gleeunit never picks it up as a test module. import at_record/gen/catalog/release as catalog_release import at_record/gen/shelf/entry import at_record_server/catalog/source as catalog_source import at_record_server/catalog_deps import at_record_server/catalog_index import at_record_server/context.{type Context, Atproto, Context, Discogs, Web} import at_record_server/discogs_client import at_record_server/known_users import at_record_server/oauth/config import at_record_server/oauth/keys import at_record_server/oauth/sessions import at_record_server/oauth/sessions_memory import at_record_server/oauth/store import atproto/xrpc import gleam/dict import gleam/dynamic/decode import gleam/json import gleam/option.{None} import gleam/result import gose import kryptos/ec pub fn unreachable_client() -> xrpc.Client { xrpc.Client(send: fn(_req) { Error("unused") }) } pub fn unreachable_catalog_deps() -> catalog_deps.Deps { catalog_deps.Deps( backlinks: fn(_, _) { panic as "backlinks must not be called" }, fetch_release: fn(_) { panic as "fetch_release must not be called" }, fetch_edit: fn(_) { panic as "fetch_edit must not be called" }, release_mbid: fn(_, _) { panic as "release_mbid must not be called" }, ) } fn known_users_of(users: List(known_users.KnownUser)) -> known_users.Store { known_users.Store(upsert: fn(_) { Nil }, list: fn() { users }) } fn empty_catalog_index() -> catalog_index.Store { catalog_index.Store( releases: catalog_index.ReleaseOps( upsert: fn(_) { Nil }, delete: fn(_) { Nil }, list: fn() { [] }, ), adoptions: catalog_index.AdoptionOps( upsert: fn(_) { Nil }, delete: fn(_) { Nil }, count: fn(_) { 0 }, counts: fn() { dict.new() }, ), edits: catalog_index.EditOps( upsert: fn(_) { Nil }, delete: fn(_) { Nil }, for_subject: fn(_) { [] }, ), cursor: catalog_index.CursorOps(save: fn(_) { Nil }, load: fn() { None }), ) } /// A `variant_source` with no candidate rows and a zero adoption count for /// every uri; use `stub_context_with_variant_source` to override it. pub fn empty_variant_source() -> catalog_source.Source { catalog_source.Source(releases: fn() { [] }, adoption_count: fn(_) { 0 }) } /// A `Config` wired to in-memory pending-flow/session stores, so tests never /// touch a real database. `resolver`/`base_url` default to a plain localhost /// dev client; use `stub_config_with` to exercise the confidential-client /// (https origin) path. pub fn stub_config(client: xrpc.Client) -> config.Config { stub_config_with(client, "https://resolver.test", "http://localhost:8080") } pub fn stub_config_with( client: xrpc.Client, resolver: String, base_url: String, ) -> config.Config { let assert Ok(st) = store.start() let assert Ok(ss) = sessions_memory.start() config.new( client:, resolver:, store: st, sessions: ss, key: keys.load(), base_url:, ) } /// A `Context` wrapping `cfg`, with every other capability defaulted to a /// stub that either does nothing or panics if called. Use /// `stub_context_with` to override `catalog`, `discogs_send`, or /// `known_users`: the parts individual handler tests actually vary. pub fn stub_context(cfg: config.Config) -> Context { stub_context_with( cfg, unreachable_catalog_deps(), fn(_req) { Error("unused") }, [], ) } pub fn stub_context_with( cfg: config.Config, catalog: catalog_deps.Deps, discogs_send: discogs_client.Sender, known_users: List(known_users.KnownUser), ) -> Context { Context( web: Web(static_directory: "", base_url: "http://localhost:8080"), atproto: Atproto(client: cfg.client, resolver: cfg.resolver), discogs: Discogs(auth: None, creds: cfg.sessions, send: discogs_send), catalog:, known_users: known_users_of(known_users), catalog_index: empty_catalog_index(), variant_source: empty_variant_source(), oauth: cfg, ) } /// `stub_context_with`, but also overriding `variant_source`: what the /// browse-handler resolution-strategy tests actually vary. pub fn stub_context_with_variant_source( cfg: config.Config, variant_source: catalog_source.Source, ) -> Context { Context( ..stub_context_with( cfg, unreachable_catalog_deps(), fn(_req) { Error("unused") }, [], ), variant_source:, ) } /// A minimal `CatalogRelease`: only `title` and `created_at` are set, every /// other field `None`. Override whichever fields a test cares about via /// `CatalogRelease(..blank_catalog_release(), field: value)`. pub fn blank_catalog_release() -> catalog_release.CatalogRelease { catalog_release.CatalogRelease( title: "Spiderland", artist_display: None, created_at: "2026-01-01T00:00:00Z", external_ids: None, released: None, country: None, genres: None, styles: None, thumb_url: None, cover: None, based_on: None, credited_artists: None, formats: None, identifiers: None, labels: None, master: None, supersedes: None, tracklist: None, ) } /// A minimal `ShelfEntry`: an "acquired" genesis row with no subject, dated /// 2026-01-01, every other field `None`. Override via /// `ShelfEntry(..blank_shelf_entry(), field: value)`. pub fn blank_shelf_entry() -> entry.ShelfEntry { entry.ShelfEntry( subject: None, action: "acquired", snapshot: None, external_ids: None, media_grade: None, sleeve_grade: None, rating: None, folder: None, notes: None, release: None, price: None, counterparty: None, source: None, created_at: "2026-01-01T00:00:00Z", ) } /// An `OauthSession` for `did:plc:me`: the fixture repeated verbatim across /// the edit-inbox and promotion handler tests. pub fn stub_session() -> sessions.OauthSession { sessions.OauthSession( did: "did:plc:me", handle: "me.test", pds: "https://pds.test", issuer: "https://as.test", token_endpoint: "https://as.test/token", client_id: "client", confidential: False, dpop_key: gose.generate_ec(ec.P256), access_token: "token", refresh_token: "refresh", expires_at: 0, ) } /// An `OauthSession` for `did:plc:x`, with the token fields overridable: the /// shape shared by the oauth and discogs-scan handler tests. pub fn stub_session_with( access_token access_token: String, refresh_token refresh_token: String, expires_at expires_at: Int, ) -> sessions.OauthSession { sessions.OauthSession( did: "did:plc:x", handle: "h.test", pds: "https://pds.example", issuer: "https://as.example", token_endpoint: "https://as.example/token", client_id: "cid", confidential: False, dpop_key: gose.generate_ec(ec.P256), access_token:, refresh_token:, expires_at:, ) } pub fn field_string(body: String, path: List(String)) -> Result(String, Nil) { json.parse(body, decode.at(path, decode.string)) |> result.replace_error(Nil) } pub fn field_strings( body: String, path: List(String), ) -> Result(List(String), Nil) { json.parse(body, decode.at(path, decode.list(decode.string))) |> result.replace_error(Nil) } pub fn field_nested( body: String, path: List(String), inner: List(String), ) -> Result(List(String), Nil) { json.parse( body, decode.at(path, decode.list(decode.at(inner, decode.string))), ) |> result.replace_error(Nil) } pub fn field_present(body: String, path: List(String)) -> Bool { json.parse(body, decode.at(path, decode.dynamic)) |> result.is_ok }