From ca0c018270304ac1ffca0b82dacb1a36c92ee734 Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Fri, 19 Jun 2026 12:39:31 -0500 Subject: [PATCH] refactor: tests with shared helpers and tables --- package.json | 10 ++++- scripts/smoke.prod.hurl | 90 +++++++++++++++++++++++++++++++++++++++++ test/lexicon.test.ts | 84 +------------------------------------- test/main.test.ts | 67 +----------------------------- test/nsid.test.ts | 71 +++++++++++++++----------------- test/shared.ts | 84 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 218 insertions(+), 188 deletions(-) create mode 100644 scripts/smoke.prod.hurl create mode 100644 test/shared.ts diff --git a/package.json b/package.json index 644de47..9ad2ca1 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,16 @@ { "name": "lexdns", - "version": "0.0.0", + "version": "0.1.0", "private": true, "type": "module", + "author": { + "email": "me@desertthunder.dev", + "name": "Owais J.", + "url": "https://desertthunder.dev" + }, + "repository": { + "url": "https://github.com/desertthunder/lexdns" + }, "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy", diff --git a/scripts/smoke.prod.hurl b/scripts/smoke.prod.hurl new file mode 100644 index 0000000..994f252 --- /dev/null +++ b/scripts/smoke.prod.hurl @@ -0,0 +1,90 @@ +# Production API smoke test. +# Run with: hurl --test scripts/smoke.prod.hurl + +GET https://lex.desertthunder.dev/api/resolve/app.bsky.feed.post +HTTP 200 +[Asserts] +header "content-type" contains "application/json" +jsonpath "$.version" isString +jsonpath "$.nsid" == "app.bsky.feed.post" +jsonpath "$.parsed.authority" == "app.bsky.feed" +jsonpath "$.parsed.domain" == "feed.bsky.app" +jsonpath "$.parsed.name" == "post" +jsonpath "$.parsed.dnsName" == "_lexicon.feed.bsky.app" +jsonpath "$.cache" isString +jsonpath "$.trace.dnsName" == "_lexicon.feed.bsky.app" +jsonpath "$.trace.txtRecords" isCollection +jsonpath "$.trace.selectedDid" startsWith "did:" +jsonpath "$.trace.final.success" == true +jsonpath "$.trace.final.message" isString +jsonpath "$.lexicon.lexicon" == 1 +jsonpath "$.lexicon.id" == "app.bsky.feed.post" +jsonpath "$._links.self.href" == "https://lex.desertthunder.dev/api/resolve/app.bsky.feed.post" +jsonpath "$._links.source.href" startsWith "at://did:" + +GET https://lex.desertthunder.dev/api/resolve/app.bsky.feed.post +HTTP 200 +[Asserts] +jsonpath "$.version" isString +jsonpath "$.nsid" == "app.bsky.feed.post" +jsonpath "$.cache" == "hit" +jsonpath "$.trace.final.success" == true +jsonpath "$.trace.final.message" == "Resolved lexicon schema from cache" +jsonpath "$.lexicon.id" == "app.bsky.feed.post" + +GET https://lex.desertthunder.dev/api/uri/at%3A%2F%2Fdid%3Aplc%3Aexample%2Fcom.atproto.lexicon.schema%2Fapp.bsky.feed.post +HTTP 200 +[Asserts] +jsonpath "$.version" isString +jsonpath "$.nsid" == "app.bsky.feed.post" +jsonpath "$.parsed.dnsName" == "_lexicon.feed.bsky.app" +jsonpath "$.trace.final.success" == true +jsonpath "$.lexicon.id" == "app.bsky.feed.post" + +GET https://lex.desertthunder.dev/api/uri?uri=https%3A%2F%2Fbsky.social%2Fxrpc%2Fcom.atproto.repo.getRecord%3Fcollection%3Dcom.atproto.lexicon.schema%26rkey%3Dapp.bsky.feed.post +HTTP 200 +[Asserts] +jsonpath "$.version" isString +jsonpath "$.nsid" == "app.bsky.feed.post" +jsonpath "$.parsed.dnsName" == "_lexicon.feed.bsky.app" +jsonpath "$.trace.final.success" == true +jsonpath "$.lexicon.id" == "app.bsky.feed.post" + +GET https://lex.desertthunder.dev/api/uri/https://shaggymane.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Axg2vq45muivyy3xwatcehspu&collection=com.atproto.lexicon.schema&rkey=org.stormlightlabs.malfestio.card +HTTP 200 +[Asserts] +jsonpath "$.version" isString +jsonpath "$.nsid" == "org.stormlightlabs.malfestio.card" +jsonpath "$.trace.selectedDid" == "did:plc:xg2vq45muivyy3xwatcehspu" +jsonpath "$.trace.final.success" == true +jsonpath "$.lexicon.id" == "org.stormlightlabs.malfestio.card" + +GET https://lex.desertthunder.dev/api/uri?uri=https://shaggymane.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Axg2vq45muivyy3xwatcehspu&collection=com.atproto.lexicon.schema&rkey=org.stormlightlabs.malfestio.card +HTTP 200 +[Asserts] +jsonpath "$.version" isString +jsonpath "$.nsid" == "org.stormlightlabs.malfestio.card" +jsonpath "$.trace.selectedDid" == "did:plc:xg2vq45muivyy3xwatcehspu" +jsonpath "$.trace.final.success" == true +jsonpath "$.lexicon.id" == "org.stormlightlabs.malfestio.card" + +GET https://lex.desertthunder.dev/api/resolve/not-a-valid-nsid +HTTP 400 +[Asserts] +header "content-type" contains "application/json" +jsonpath "$.version" isString +jsonpath "$.error.code" == "bad_request" +jsonpath "$.error.message" == "Invalid NSID" +jsonpath "$.trace.final.success" == false +jsonpath "$.trace.final.code" == "bad_request" +jsonpath "$.trace.final.message" == "Invalid NSID" + +GET https://lex.desertthunder.dev/openapi +HTTP 200 +[Asserts] +header "content-type" contains "application/json" +jsonpath "$.openapi" == "3.1.0" +jsonpath "$.info.title" == "lexdns API" +jsonpath "$.info.version" isString +jsonpath "$.paths['/api/resolve/{nsid}'].get.responses['200'].content['application/json'].schema" exists +jsonpath "$.paths['/api/uri/{aturi}'].get.responses['400'].content['application/json'].schema" exists diff --git a/test/lexicon.test.ts b/test/lexicon.test.ts index c918820..efeab5a 100644 --- a/test/lexicon.test.ts +++ b/test/lexicon.test.ts @@ -1,87 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { isLexiconDocument, normalizeResolveInput, parseNsid, resolveLexicon } from '../src/lexicon'; +import { isLexiconDocument, normalizeResolveInput, resolveLexicon } from '../src/lexicon'; import { API_VERSION } from '../src/meta'; -import type { RuntimeEnv, ResolvedLexicon } from '../src/types'; - -const originalFetch = globalThis.fetch; - -function resolvedLexicon(nsid = 'com.example.foo'): ResolvedLexicon { - const parsed = parseNsid(nsid); - if (parsed === null) { - throw new Error(`Invalid test NSID: ${nsid}`); - } - - return { - version: API_VERSION, - nsid, - parsed, - hash: 'sha256:test', - fetchedAt: '2026-06-18T00:00:00.000Z', - source: { name: '_lexicon.example.com', url: `at://did:plc:example/com.atproto.lexicon.schema/${nsid}` }, - lexicon: { lexicon: 1, id: nsid, defs: {} }, - cache: 'miss', - trace: { - dnsName: parsed.dnsName, - txtRecords: [], - selectedDid: 'did:plc:example', - didDocument: { url: null, status: null }, - pdsEndpoint: null, - repoGetRecord: { url: null, status: null }, - final: { success: true, message: 'Resolved lexicon schema' } - } - }; -} - -function testEnv(storage = new Map()): RuntimeEnv { - const kv = { - async get(key: string, type?: 'json'): Promise { - const value = storage.get(key); - if (value === undefined) { - return null; - } - - return type === 'json' ? (JSON.parse(value) as T) : value; - }, - async put(key: string, value: string): Promise { - storage.set(key, value); - }, - async list(options?: KVNamespaceListOptions): Promise> { - const prefix = options?.prefix ?? ''; - const limit = options?.limit ?? 1000; - const keys = [...storage.keys()] - .filter((name) => name.startsWith(prefix)) - .slice(0, limit) - .map((name) => ({ name })); - - return { keys, list_complete: true, cacheStatus: null }; - } - }; - - return { - LEXICONS: kv as KVNamespace, - CACHE_TTL_SECONDS: '86400', - DOH_ENDPOINT: 'https://cloudflare-dns.com/dns-query' - }; -} - -function mockFetch(response: Response) { - const fetchMock = vi.fn(async (..._args: Parameters): Promise => response); - globalThis.fetch = fetchMock as unknown as typeof fetch; - return fetchMock; -} - -function mockFetchSequence(responses: Response[]) { - const fetchMock = vi.fn(async (..._args: Parameters): Promise => { - const response = responses.shift(); - if (response === undefined) { - throw new Error('No mocked response'); - } - - return response; - }); - globalThis.fetch = fetchMock as unknown as typeof fetch; - return fetchMock; -} +import { mockFetch, mockFetchSequence, originalFetch, resolvedLexicon, testEnv } from './shared'; afterEach(() => { globalThis.fetch = originalFetch; diff --git a/test/main.test.ts b/test/main.test.ts index 89ad451..fb0d9e2 100644 --- a/test/main.test.ts +++ b/test/main.test.ts @@ -1,72 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { app, withLinks } from '../src/main'; -import { parseNsid } from '../src/lexicon'; import { API_VERSION } from '../src/meta'; -import type { ResolvedLexicon } from '../src/types'; - -const originalFetch = globalThis.fetch; - -function resolvedLexicon(nsid = 'com.example.foo'): ResolvedLexicon { - const parsed = parseNsid(nsid); - if (parsed === null) { - throw new Error(`Invalid test NSID: ${nsid}`); - } - - return { - version: API_VERSION, - nsid, - parsed, - hash: 'sha256:test', - fetchedAt: '2026-06-18T00:00:00.000Z', - source: { name: '_lexicon.example.com', url: `at://did:plc:example/com.atproto.lexicon.schema/${nsid}` }, - lexicon: { lexicon: 1, id: nsid, defs: {} }, - cache: 'miss', - trace: { - dnsName: parsed.dnsName, - txtRecords: [], - selectedDid: 'did:plc:example', - didDocument: { url: null, status: null }, - pdsEndpoint: null, - repoGetRecord: { url: null, status: null }, - final: { success: true, message: 'Resolved lexicon schema' } - } - }; -} - -function testEnv(storage = new Map()): Env { - const kv = { - async get(key: string, type?: 'json'): Promise { - const value = storage.get(key); - if (value === undefined) { - return null; - } - - return type === 'json' ? (JSON.parse(value) as T) : value; - }, - async put(key: string, value: string): Promise { - storage.set(key, value); - } - }; - - return { - LEXICONS: kv as KVNamespace, - CACHE_TTL_SECONDS: '86400', - DOH_ENDPOINT: 'https://cloudflare-dns.com/dns-query' - }; -} - -function mockFetchSequence(responses: Response[]) { - const fetchMock = vi.fn(async (): Promise => { - const response = responses.shift(); - if (response === undefined) { - throw new Error('No mocked response'); - } - - return response; - }); - globalThis.fetch = fetchMock as unknown as typeof fetch; - return fetchMock; -} +import { mockFetchSequence, originalFetch, resolvedLexicon, testEnv } from './shared'; afterEach(() => { globalThis.fetch = originalFetch; diff --git a/test/nsid.test.ts b/test/nsid.test.ts index 5b7f7d5..4c7480a 100644 --- a/test/nsid.test.ts +++ b/test/nsid.test.ts @@ -2,47 +2,40 @@ import { describe, expect, it } from 'vitest'; import { isValidNsid, parseNsid } from '../src/lexicon'; describe('NSID helpers', () => { - // TODO: turn into it.each - it('validates NSIDs', () => { - expect(isValidNsid('com.example.fooBar')).toBe(true); - expect(isValidNsid('net.users.bob.ping')).toBe(true); - expect(isValidNsid('a.b.c')).toBe(true); + it.each(['com.example.fooBar', 'net.users.bob.ping', 'a.b.c'])('validates %s', (nsid) => { + expect(isValidNsid(nsid)).toBe(true); }); - // TODO: turn into it.each - it('rejects invalid NSIDs', () => { - expect(isValidNsid('com.example')).toBe(false); - expect(isValidNsid('com.example.3')).toBe(false); - expect(isValidNsid('1com.example.foo')).toBe(false); - expect(isValidNsid('com.-example.foo')).toBe(false); - }); + it.each(['com.example', 'com.example.3', '1com.example.foo', 'com.-example.foo'])( + 'rejects invalid NSID %s', + (nsid) => { + expect(isValidNsid(nsid)).toBe(false); + } + ); - // TODO: turn into it.each - it('maps NSIDs to lexicon DNS names', () => { - expect(parseNsid('com.example.foo')).toEqual({ - nsid: 'com.example.foo', - authority: 'com.example', - domain: 'example.com', - name: 'foo', - dnsName: '_lexicon.example.com' - }); - expect(parseNsid('site.standard.publication')).toMatchObject({ - authority: 'site.standard', - domain: 'standard.site', - name: 'publication', - dnsName: '_lexicon.standard.site' - }); - expect(parseNsid('app.bsky.feed.post')).toMatchObject({ - authority: 'app.bsky.feed', - domain: 'feed.bsky.app', - name: 'post', - dnsName: '_lexicon.feed.bsky.app' - }); - expect(parseNsid('com.atproto.repo.getRecord')).toMatchObject({ - authority: 'com.atproto.repo', - domain: 'repo.atproto.com', - name: 'getRecord', - dnsName: '_lexicon.repo.atproto.com' - }); + it.each([ + [ + 'com.example.foo', + { authority: 'com.example', domain: 'example.com', name: 'foo', dnsName: '_lexicon.example.com' } + ], + [ + 'site.standard.publication', + { authority: 'site.standard', domain: 'standard.site', name: 'publication', dnsName: '_lexicon.standard.site' } + ], + [ + 'app.bsky.feed.post', + { authority: 'app.bsky.feed', domain: 'feed.bsky.app', name: 'post', dnsName: '_lexicon.feed.bsky.app' } + ], + [ + 'com.atproto.repo.getRecord', + { + authority: 'com.atproto.repo', + domain: 'repo.atproto.com', + name: 'getRecord', + dnsName: '_lexicon.repo.atproto.com' + } + ] + ])('maps %s to lexicon DNS metadata', (nsid, parsed) => { + expect(parseNsid(nsid)).toEqual({ nsid, ...parsed }); }); }); diff --git a/test/shared.ts b/test/shared.ts new file mode 100644 index 0000000..3ef1cb0 --- /dev/null +++ b/test/shared.ts @@ -0,0 +1,84 @@ +import { vi } from 'vitest'; +import { parseNsid } from '../src/lexicon'; +import { API_VERSION } from '../src/meta'; +import type { ResolvedLexicon, RuntimeEnv } from '../src/types'; + +export const originalFetch = globalThis.fetch; + +export function resolvedLexicon(nsid = 'com.example.foo'): ResolvedLexicon { + const parsed = parseNsid(nsid); + if (parsed === null) { + throw new Error(`Invalid test NSID: ${nsid}`); + } + + return { + version: API_VERSION, + nsid, + parsed, + hash: 'sha256:test', + fetchedAt: '2026-06-18T00:00:00.000Z', + source: { name: '_lexicon.example.com', url: `at://did:plc:example/com.atproto.lexicon.schema/${nsid}` }, + lexicon: { lexicon: 1, id: nsid, defs: {} }, + cache: 'miss', + trace: { + dnsName: parsed.dnsName, + txtRecords: [], + selectedDid: 'did:plc:example', + didDocument: { url: null, status: null }, + pdsEndpoint: null, + repoGetRecord: { url: null, status: null }, + final: { success: true, message: 'Resolved lexicon schema' } + } + }; +} + +export function testEnv(storage = new Map()): RuntimeEnv { + const kv = { + async get(key: string, type?: 'json'): Promise { + const value = storage.get(key); + if (value === undefined) { + return null; + } + + return type === 'json' ? (JSON.parse(value) as T) : value; + }, + async put(key: string, value: string): Promise { + storage.set(key, value); + }, + async list(options?: KVNamespaceListOptions): Promise> { + const prefix = options?.prefix ?? ''; + const limit = options?.limit ?? 1000; + const keys = [...storage.keys()] + .filter((name) => name.startsWith(prefix)) + .slice(0, limit) + .map((name) => ({ name })); + + return { keys, list_complete: true, cacheStatus: null }; + } + }; + + return { + LEXICONS: kv as KVNamespace, + CACHE_TTL_SECONDS: '86400', + DOH_ENDPOINT: 'https://cloudflare-dns.com/dns-query' + }; +} + +export function mockFetch(response: Response) { + const fetchMock = vi.fn(async (..._args: Parameters): Promise => response); + globalThis.fetch = fetchMock as unknown as typeof fetch; + return fetchMock; +} + +export function mockFetchSequence(responses: Response[]) { + const fetchMock = vi.fn(async (..._args: Parameters): Promise => { + const response = responses.shift(); + if (response === undefined) { + throw new Error('No mocked response'); + } + + return response; + }); + globalThis.fetch = fetchMock as unknown as typeof fetch; + return fetchMock; +} -- 2.51.2