this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(api): make test cases for getTracks

ansxor 8cebdd43 c7529905

+202 -53
+21 -16
apps/api/index.ts
··· 56 56 } satisfies TrackOutput; 57 57 } 58 58 59 - const db = createDb(); 59 + export function createRouter(db: ReturnType<typeof createDb>) { 60 + const router = new XRPCRouter({ middlewares: [cors()] }); 60 61 61 - const router = new XRPCRouter({ middlewares: [cors()] }); 62 - 63 - router.addQuery(CaAnsxorCatnipGetTracks, { 64 - async handler({ params }) { 65 - const tracks = await db.query.tracks.findMany({ 66 - where: inArray(dbschema.tracks.uri, params.uris), 67 - with: { 68 - trackArtists: { 69 - with: { 70 - artist: true, 62 + router.addQuery(CaAnsxorCatnipGetTracks, { 63 + async handler({ params }) { 64 + const tracks = await db.query.tracks.findMany({ 65 + where: inArray(dbschema.tracks.uri, params.uris), 66 + with: { 67 + trackArtists: { 68 + with: { 69 + artist: true, 70 + }, 71 71 }, 72 72 }, 73 - }, 74 - }); 73 + }); 75 74 76 - return json({ tracks: tracks.map((t) => dbTrackToLexicon(t as DbTrack)) }); 77 - }, 78 - }); 75 + return json({ tracks: tracks.map((t) => dbTrackToLexicon(t as DbTrack)) }); 76 + }, 77 + }); 78 + 79 + return router; 80 + } 81 + 82 + const db = createDb(); 83 + const router = createRouter(db); 79 84 80 85 export default router;
+3 -2
apps/api/package.json
··· 6 6 "dependencies": { 7 7 "@atcute/lexicons": "^1.2.9", 8 8 "@atcute/xrpc-server": "^0.1.11", 9 + "@faker-js/faker": "^10.3.0", 10 + "db": "workspace:*", 9 11 "drizzle-orm": "^0.45.1", 10 12 "lexicon": "workspace:*", 11 - "postgres": "^3.4.8", 12 - "db": "workspace:*" 13 + "postgres": "^3.4.8" 13 14 }, 14 15 "devDependencies": { 15 16 "@types/bun": "latest"
+177
apps/api/tests/server.test.ts
··· 1 + import { test, expect, beforeAll, afterAll, beforeEach, afterEach } from "bun:test"; 2 + import { setupTestDb, teardownTestDb } from "db/test-utils"; 3 + import * as dbschema from "db/schema"; 4 + import type { BlobRef } from "db/schema"; 5 + import { createRouter } from "../index"; 6 + import { faker } from "@faker-js/faker"; 7 + import type { XRPCRouter } from "@atcute/xrpc-server"; 8 + 9 + let db: Awaited<ReturnType<typeof setupTestDb>>; 10 + let txDb: typeof db; 11 + let rollback: () => void; 12 + 13 + beforeAll(async () => { 14 + db = await setupTestDb(); 15 + }); 16 + 17 + afterAll(async () => { 18 + await teardownTestDb(); 19 + }); 20 + 21 + beforeEach(async () => { 22 + await new Promise<void>((resolveSetup) => { 23 + let rejectTx: (err: Error) => void; 24 + db.transaction(async (tx) => { 25 + txDb = tx as unknown as typeof db; 26 + resolveSetup(); 27 + await new Promise<void>((_, reject) => { 28 + rejectTx = reject; 29 + }); 30 + }).catch(() => {}); 31 + rollback = () => rejectTx(new Error("rollback")); 32 + }); 33 + }); 34 + 35 + afterEach(() => { 36 + rollback(); 37 + }); 38 + 39 + function fakeDid(): `did:${string}:${string}` { 40 + return `did:plc:${faker.string.alphanumeric({ length: 24, casing: "lower" })}`; 41 + } 42 + 43 + function fakeCid(): string { 44 + return `bafyrei${faker.string.alphanumeric({ length: 52, casing: "lower" })}`; 45 + } 46 + 47 + function fakeTid(): string { 48 + return faker.string.alphanumeric(13); 49 + } 50 + 51 + function fakeBlobRef(mimeType = "audio/ogg"): BlobRef { 52 + return { 53 + $type: "blob", 54 + ref: { $link: fakeCid() }, 55 + mimeType, 56 + size: faker.number.int({ min: 100_000, max: 50_000_000 }), 57 + }; 58 + } 59 + 60 + const COLLECTION = "ca.ansxor.catnip.track"; 61 + 62 + async function insertTrack( 63 + db: typeof txDb, 64 + overrides?: { 65 + did?: `did:${string}:${string}`; 66 + rkey?: string; 67 + cid?: string; 68 + title?: string; 69 + audio?: BlobRef; 70 + artists?: { did?: string; name?: string }[]; 71 + }, 72 + ) { 73 + const did = overrides?.did ?? fakeDid(); 74 + const rkey = overrides?.rkey ?? fakeTid(); 75 + const uri = `at://${did}/${COLLECTION}/${rkey}`; 76 + const cid = overrides?.cid ?? fakeCid(); 77 + const title = overrides?.title ?? faker.music.songName(); 78 + const audio = overrides?.audio ?? fakeBlobRef(); 79 + 80 + await db.insert(dbschema.tracks).values({ 81 + uri, 82 + did, 83 + rkey, 84 + cid, 85 + title, 86 + createdAt: new Date(), 87 + audio, 88 + }); 89 + 90 + const artists = overrides?.artists ?? [{ did, name: faker.person.fullName() }]; 91 + for (let i = 0; i < artists.length; i++) { 92 + const a = artists[i]!; 93 + const [inserted] = await db 94 + .insert(dbschema.artists) 95 + .values({ did: a.did ?? null, name: a.name ?? null }) 96 + .onConflictDoNothing() 97 + .returning(); 98 + 99 + const artistId = 100 + inserted?.id ?? 101 + (await db.query.artists.findFirst({ 102 + where: (art, { eq }) => eq(art.did, a.did!), 103 + }))!.id; 104 + 105 + await db.insert(dbschema.trackArtists).values({ 106 + trackUri: uri, 107 + artistId, 108 + position: i, 109 + }); 110 + } 111 + 112 + return { uri, did, rkey, cid, title, audio }; 113 + } 114 + 115 + function makeRouter() { 116 + return createRouter(txDb); 117 + } 118 + 119 + async function fetchTracks(router: XRPCRouter, uris: string[]) { 120 + const url = new URL("http://localhost/xrpc/ca.ansxor.catnip.getTracks"); 121 + for (const uri of uris) { 122 + url.searchParams.append("uris", uri); 123 + } 124 + return router.fetch(new Request(url, { method: "GET" })); 125 + } 126 + 127 + test("returns a track by URI", async () => { 128 + const title = faker.music.songName(); 129 + const artistName = faker.person.fullName(); 130 + const did = fakeDid(); 131 + const { uri } = await insertTrack(txDb, { 132 + did, 133 + title, 134 + artists: [{ did, name: artistName }], 135 + }); 136 + 137 + const router = makeRouter(); 138 + const response = await fetchTracks(router, [uri]); 139 + expect(response.status).toBe(200); 140 + 141 + const data = await response.json(); 142 + expect(data.tracks).toHaveLength(1); 143 + 144 + const track = data.tracks[0]; 145 + expect(track.title).toBe(title); 146 + expect(track.$type).toBe("ca.ansxor.catnip.track"); 147 + expect(track.artists).toHaveLength(1); 148 + expect(track.artists[0].did).toBe(did); 149 + expect(track.artists[0].name).toBe(artistName); 150 + expect(track.audio).toBeDefined(); 151 + }); 152 + 153 + test("returns empty array for unknown URIs", async () => { 154 + const router = makeRouter(); 155 + const response = await fetchTracks(router, [ 156 + `at://${fakeDid()}/${COLLECTION}/${fakeTid()}`, 157 + ]); 158 + expect(response.status).toBe(200); 159 + 160 + const data = await response.json(); 161 + expect(data.tracks).toHaveLength(0); 162 + }); 163 + 164 + test("returns multiple tracks", async () => { 165 + const { uri: uri1 } = await insertTrack(txDb, { title: "Track One" }); 166 + const { uri: uri2 } = await insertTrack(txDb, { title: "Track Two" }); 167 + 168 + const router = makeRouter(); 169 + const response = await fetchTracks(router, [uri1, uri2]); 170 + expect(response.status).toBe(200); 171 + 172 + const data = await response.json(); 173 + expect(data.tracks).toHaveLength(2); 174 + 175 + const titles = data.tracks.map((t: any) => t.title).sort(); 176 + expect(titles).toEqual(["Track One", "Track Two"]); 177 + });
-35
apps/api/tests/utils.ts
··· 1 - import { test, expect, beforeAll, afterAll, beforeEach, afterEach } from "bun:test"; 2 - import { setupTestDb, teardownTestDb } from "db/test-utils"; 3 - import type { InferOutput } from "@atcute/lexicons/validations"; 4 - import type { CaAnsxorCatnipTrack } from "lexicon/atcute-lexicon"; 5 - import router from "../index"; 6 - 7 - let db: Awaited<ReturnType<typeof setupTestDb>>; 8 - let txDb: typeof db; 9 - let rollback: () => void; 10 - 11 - beforeAll(async () => { 12 - db = await setupTestDb(); 13 - }); 14 - 15 - afterAll(async () => { 16 - await teardownTestDb(); 17 - }); 18 - 19 - beforeEach(async () => { 20 - await new Promise<void>((resolveSetup) => { 21 - let rejectTx: (err: Error) => void; 22 - db.transaction(async (tx) => { 23 - txDb = tx as unknown as typeof db; 24 - resolveSetup(); 25 - await new Promise<void>((_, reject) => { 26 - rejectTx = reject; 27 - }); 28 - }).catch(() => {}); 29 - rollback = () => rejectTx(new Error("rollback")); 30 - }); 31 - }); 32 - 33 - afterEach(() => { 34 - rollback(); 35 - });
+1
bun.lock
··· 20 20 "dependencies": { 21 21 "@atcute/lexicons": "^1.2.9", 22 22 "@atcute/xrpc-server": "^0.1.11", 23 + "@faker-js/faker": "^10.3.0", 23 24 "db": "workspace:*", 24 25 "drizzle-orm": "^0.45.1", 25 26 "lexicon": "workspace:*",