import std/[json, os, sequtils, strutils, tables, unittest] import ../src/freeqsay const didA = "did:plc:ewvi7nmzuoqusbcablsm7c4h" let fixtures = parseFile(currentSourcePath().parentDir / ".." / "fixtures" / "avatar-conformance.json") proc stubGet(did: string): HttpGet = ## Test hook standing in for the network: always answers with `did`. result = proc (url: string): HttpResponse {.gcsafe.} = HttpResponse(status: 200, body: %*{"did": did}) suite "sha256": test "vectors": check toHex(digest("")) == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" check toHex(digest("abc")) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" # RFC 4231 test case 1 check toHex(hmac(repeat(0x0b'u8, 20), toBytes("Hi There"))) == "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" suite "avatar-v1 conformance": test "fixture replays": check fixtures.len > 0 for f in fixtures: let did = f["did"].getStr av = deriveAvatar(did) checkpoint did check f["canonical_seed"].getStr == toHex(canonicalSeed(did)) for key, want in f["expected_traits"]: check av.traits[key] == want.getStr check f["sprite_hash"].getStr == spriteHash(av) suite "balloons": test "wraps long lines": let lines = wrapText("one two three four five", 10) check lines.allIt(it.len <= 10) check "one" in lines.join(" ") test "single-line speech balloon": let b = makeBalloon("hi") check "< hi" in b let top = b.splitLines[0] check top.startsWith(" ") and top[1 .. ^1].allIt(it == '_') test "multi-line balloon": let b = makeBalloon("hello there friend", width = 6) check "/ " in b check "\\ " in b test "thought bubble": check "( hmm )" in makeBalloon("hmm", think = true) suite "balloons preserve ANSI shape": let esc = "\x1b" art = didToAnsi(didA, scale = 1) lines = art.splitLines test "escape sequences cost no columns": check visibleWidth(esc & "[38;2;1;2;3mabc" & esc & "[0m") == 3 check lines.allIt(visibleWidth(it) == 16) test "art lines survive a balloon verbatim": let b = makeBalloon(art, width = 40) body = b.splitLines[1 .. ^2] # every row is padded to one width, so the right border lines up let borders = body.mapIt(it.filterIt(it in {'|', '/', '\\'}).len) check borders.deduplicate.len == 1 check body.len == lines.len for i, l in lines: check l in body[i] test "indentation is not collapsed": check wrapText(" indented", 40) == @[" indented"] suite "say": test "balloon + character for handle + message": let said = freeqsay("alice.test", "hello world", scale = 1, resolveOpts = ResolveOpts(httpGet: stubGet(didA))) check said.did == didA check said.text == "hello world" check "< hello world >" in said.output check "\\" in said.output check "\x1b[" in said.output test "raw DID needs no network": check "< yo >" in freeqsay(didA, "yo", scale = 1).output suite "resolving": test "handle normalization": check normalizeHandle("@Jay.Bsky.Team") == "jay.bsky.team" check isDid(didA) check not isDid("jay.bsky.team") test "DIDs pass through without a request": check resolveIdentity(didA).did == didA test "handles resolve through the endpoint": var seen = "" let get = proc (url: string): HttpResponse {.gcsafe.} = {.cast(gcsafe).}: seen = url HttpResponse(status: 200, body: %*{"did": didA}) check resolveHandle("@example.bsky.social", ResolveOpts(httpGet: get)) == didA check "handle=example.bsky.social" in seen test "a dead transport says so instead of blaming the handle": let get = proc (url: string): HttpResponse {.gcsafe.} = HttpResponse(status: 0, error: "no TLS here") expect IOError: discard resolveHandle("alice.test", ResolveOpts(httpGet: get)) suite "ansi": test "truecolor escapes": check "\x1b[" in didToAnsi(didA, scale = 1) test "handle->ansi matches did->ansi after resolve": let id = resolveIdentity("alice.test", ResolveOpts(httpGet: stubGet(didA))) check didToAnsi(didA, scale = 1) == didToAnsi(id.did, scale = 1) suite "png": test "signature": let bs = didToPng(didA, scale = 2) check bs[0 .. 1] == @[137'u8, 80'u8] check bs[12 .. 15] == toBytes("IHDR") check bs[^4 .. ^1] == @[0xae'u8, 0x42, 0x60, 0x82] # IEND crc