~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
105 lines · 3.9 KB Raw History
// What knotless-serve answers over XRPC, against the lexicons that say
// what it answers: each answer the screenshots recorded (shots/
// fixtures.json), as `just shots-record` takes them from the real server,
// or, given a directory, each one knotless-serve's tests received, as
// harness/cov.mjs gathers them there (lines of `{path, body}`). A query's
// output must be valid for its lexicon, a failure must be XRPC's
// `{error, message}`, and every parameter asked must be one it declares.
//
//   node harness/answers.mjs [dir]
//
// @atproto/lexicon is the harness's, or, where its packages are not
// installed, one NODE_PATH names.

import { readdirSync, readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const { Lexicons } = createRequire(import.meta.url)('@atproto/lexicon')

const files = (d) =>
  readdirSync(d, { withFileTypes: true }).flatMap((e) =>
    e.isDirectory() ? files(join(d, e.name)) : e.name.endsWith('.json') ? [join(d, e.name)] : [],
  )
const docs = files(join(root, 'lexicons')).map((f) => JSON.parse(readFileSync(f, 'utf8')))
const lexicons = new Lexicons(docs)

// A record in a space is named in full, as atproto's permissioned data
// proposal (0016) addresses it:
// at://<space did>/space/<type>/<key>/<author>/<collection>/<rkey>. The
// harness's @atproto/lexicon knows only the public grammar of an at-uri,
// so such a URI is held to it as its public form, its last three segments;
// knotless's own parser (crates/knotless/src/aturi.rs) holds the rest.
const SPACED = /^at:\/\/did:[^/]+\/space\/[^/]+\/[^/]+\/(did:[^/]+\/[^/]+\/[^/]+)$/
const publicForm = (v) =>
  typeof v === 'string'
    ? v.replace(SPACED, 'at://$1')
    : Array.isArray(v)
      ? v.map(publicForm)
      : v && typeof v === 'object'
        ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, publicForm(x)]))
        : v

const [dir] = process.argv.slice(2)
// Each answer as [path, {status, headers, body}], the body as sent.
const answers = dir
  ? readdirSync(dir).flatMap((f) =>
      readFileSync(join(dir, f), 'utf8')
        .split('\n')
        .filter(Boolean)
        .map((l) => JSON.parse(l))
        .map(({ path, body }) => [
          path,
          {
            status: 200,
            headers: { 'content-type': 'application/json' },
            body: JSON.stringify(body),
          },
        ]),
    )
  : Object.entries(
      JSON.parse(readFileSync(join(root, 'harness/shots/fixtures.json'), 'utf8')).answers,
    )
const problems = []
// A path asked again and again is said once.
const said = new Set()
let checked = 0
for (const [path, answer] of answers) {
  const url = new URL(path, 'http://knotless')
  const nsid = url.pathname.match(/^\/xrpc\/(.+)$/)?.[1]
  if (!nsid) continue
  const say = (what) => {
    const p = `${url.pathname}: ${what}`
    if (!said.has(p)) problems.push(`${path}: ${what}`)
    said.add(p)
  }
  const def = lexicons.getDef(nsid)
  if (!def) {
    say('no lexicon names this method')
    continue
  }
  const declared = Object.keys(def.parameters?.properties ?? {})
  for (const name of url.searchParams.keys())
    if (!declared.includes(name)) say(`${name} is no parameter of ${nsid}`)
  checked++
  if (answer.headers['content-type'] !== 'application/json') continue
  const body = JSON.parse(answer.body)
  if (answer.status !== 200) {
    if (typeof body.error !== 'string' || typeof body.message !== 'string')
      say('a failure is {error, message}')
    continue
  }
  try {
    lexicons.assertValidXrpcOutput(nsid, publicForm(body))
  } catch (e) {
    say(e.message)
  }
}

for (const p of problems) console.log(p)
if (problems.length) process.exit(1)
console.log(
  `answers: ${checked} ${dir ? 'XRPC answers the tests received' : 'recorded XRPC answers'} are as their lexicons say`,
)