//// Cache-hit vs cache-miss behavior for `GET /api/cover//`. The //// atproto client is a fake that branches on the request path: resolveMiniDoc //// for identity resolution, getBlob for the blob fetch. A cache hit must //// never touch either. import at_record_server/context.{type Context} import at_record_server/handlers/cover_proxy import atproto/xrpc import envoy import gleam/bit_array import gleam/http/response import simplifile import support import wisp/simulate const did = "did:plc:pub" const cid = "bafycover123" const image_bytes = "totally-a-jpeg" const pds_host = "pds.test" fn resolve_body() -> String { "{\"did\":\"" <> did <> "\",\"handle\":\"pub.test\",\"pds\":\"https://" <> pds_host <> "\",\"signing_key\":\"zTest\"}" } /// Resolves the did then serves the blob bytes with an `image/png; foo=bar` /// content-type, to also exercise the parameter-stripping. fn network_client() -> xrpc.Client { xrpc.Client(send: fn(req) { case req.host { "resolver.test" -> Ok(response.Response(200, [], bit_array.from_string(resolve_body()))) "pds.test" -> Ok(response.Response( 200, [#("content-type", "image/png; charset=binary")], bit_array.from_string(image_bytes), )) _ -> panic as "unexpected host" } }) } fn failing_resolver_client() -> xrpc.Client { xrpc.Client(send: fn(_req) { Error("resolver unreachable") }) } fn resolves_but_blob_fetch_fails_client() -> xrpc.Client { xrpc.Client(send: fn(req) { case req.host { "resolver.test" -> Ok(response.Response(200, [], bit_array.from_string(resolve_body()))) _ -> Ok(response.Response(404, [], bit_array.from_string("nope"))) } }) } fn panics_if_called_client() -> xrpc.Client { xrpc.Client(send: fn(_req) { panic as "a cache hit must not touch the network" }) } fn test_context(client: xrpc.Client) -> Context { support.stub_context(support.stub_config(client)) } fn fresh_cache_dir(name: String) -> String { let dir = "./test-cover-cache-" <> name let _ = simplifile.delete(dir) envoy.set("COVER_CACHE_DIRECTORY", dir) dir } pub fn cache_miss_fetches_writes_and_serves_the_blob_test() { let dir = fresh_cache_dir("miss") let ctx = test_context(network_client()) let resp = cover_proxy.serve(did, cid, ctx) assert resp.status == 200 assert simulate.read_body_bits(resp) == bit_array.from_string(image_bytes) assert response.get_header(resp, "content-type") == Ok("image/png") assert response.get_header(resp, "cache-control") == Ok("public, max-age=31536000, immutable") // The bytes and mime type are now cached on disk for the next request. let assert Ok(cached_bytes) = simplifile.read_bits(dir <> "/" <> did <> "-" <> cid <> ".bin") assert cached_bytes == bit_array.from_string(image_bytes) let assert Ok(cached_mime) = simplifile.read(dir <> "/" <> did <> "-" <> cid <> ".type") assert cached_mime == "image/png" let _ = simplifile.delete(dir) } pub fn cache_hit_never_touches_the_network_test() { let dir = fresh_cache_dir("hit") let assert Ok(Nil) = simplifile.create_directory_all(dir) let assert Ok(Nil) = simplifile.write_bits( dir <> "/" <> did <> "-" <> cid <> ".bin", bit_array.from_string(image_bytes), ) let assert Ok(Nil) = simplifile.write(dir <> "/" <> did <> "-" <> cid <> ".type", "image/webp") let ctx = test_context(panics_if_called_client()) let resp = cover_proxy.serve(did, cid, ctx) assert resp.status == 200 assert simulate.read_body_bits(resp) == bit_array.from_string(image_bytes) assert response.get_header(resp, "content-type") == Ok("image/webp") let _ = simplifile.delete(dir) } pub fn resolve_failure_returns_502_and_caches_nothing_test() { let dir = fresh_cache_dir("resolve-fail") let ctx = test_context(failing_resolver_client()) let resp = cover_proxy.serve(did, cid, ctx) assert resp.status == 502 assert simplifile.is_file(dir <> "/" <> did <> "-" <> cid <> ".bin") == Ok(False) let _ = simplifile.delete(dir) } pub fn blob_fetch_failure_returns_502_and_caches_nothing_test() { let dir = fresh_cache_dir("blob-fail") let ctx = test_context(resolves_but_blob_fetch_fails_client()) let resp = cover_proxy.serve(did, cid, ctx) assert resp.status == 502 assert simplifile.is_file(dir <> "/" <> did <> "-" <> cid <> ".bin") == Ok(False) let _ = simplifile.delete(dir) }