diff --git a/plice/BUILD b/plice/BUILD index 60655ca..fb9675c 100644 --- a/plice/BUILD +++ b/plice/BUILD @@ -5,6 +5,7 @@ files( sources=[ "prisma/**", "src/**/*.ts", + "vite.config.ts", ] ) @@ -48,7 +49,14 @@ adhoc_tool( experimental_test_shell_command( name="test", - tools=["pnpm", "node", "sh", "sed", "dirname", "uname"], - command="pnpm vitest run", + tools=[ + "pnpm", + "node", + "sh", + "sed", + "dirname", + "uname", + ], + command="pnpm prisma generate && pnpm vitest run", execution_dependencies=[":sources", ":package-config", ":node-modules"], ) diff --git a/plice/prisma/mock.sql b/plice/prisma/mock.sql new file mode 100644 index 0000000..74ec70f --- /dev/null +++ b/plice/prisma/mock.sql @@ -0,0 +1,64 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE IF NOT EXISTS "User" ( + "id" TEXT NOT NULL PRIMARY KEY, + "username" TEXT NOT NULL +); +INSERT INTO User VALUES('afo41yngw19cjh2','fake'); +CREATE TABLE IF NOT EXISTS "Session" ( + "id" TEXT NOT NULL PRIMARY KEY, + "user_id" TEXT NOT NULL, + "active_expires" BIGINT NOT NULL, + "idle_expires" BIGINT NOT NULL, + CONSTRAINT "Session_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO Session VALUES('lwigbqkcyudn0h2xksfjmqomf28mwsq3hjge8xaf','afo41yngw19cjh2',1705150414256,1706360014256); +CREATE TABLE IF NOT EXISTS "Key" ( + "id" TEXT NOT NULL PRIMARY KEY, + "hashed_password" TEXT, + "user_id" TEXT NOT NULL, + CONSTRAINT "Key_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "Key" VALUES('username:fake','s2:1wc2gp27g4q9vur0:39545e2a52143d9027dafb8dc8a427cfd2a3cce55eb536506ac868c96ed665289cc75a022b967fd5bc24603c4cde8a152a8d94022a7e0c95c22ffd718aadbf21','afo41yngw19cjh2'); +CREATE TABLE IF NOT EXISTS "Track" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "title" TEXT NOT NULL, + "objectKey" TEXT NOT NULL, + "producerId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Track_producerId_fkey" FOREIGN KEY ("producerId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO Track VALUES(1,'Meow','507ebeff-6f8e-45a0-9c1b-021a262f350a.mp3','afo41yngw19cjh2',1705065010419,1705065010419); +CREATE TABLE IF NOT EXISTS "TrackVersion" ( + "id" TEXT NOT NULL PRIMARY KEY, + "trackId" INTEGER NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "TrackVersion_trackId_fkey" FOREIGN KEY ("trackId") REFERENCES "Track" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO TrackVersion VALUES('700ab0a6-fbf4-482e-b283-8b3d3984324f',1,1705065010424,1705065010424); +CREATE TABLE IF NOT EXISTS "Comment" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "content" TEXT NOT NULL, + "authorId" TEXT NOT NULL, + "trackVersionId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Comment_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "Comment_trackVersionId_fkey" FOREIGN KEY ("trackVersionId") REFERENCES "TrackVersion" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO Comment VALUES (1,'UwU','afo41yngw19cjh2','700ab0a6-fbf4-482e-b283-8b3d3984324f',1705078453000,1705078453000); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('Track',1); +CREATE UNIQUE INDEX "User_id_key" ON "User"("id"); +CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); +CREATE INDEX "User_username_idx" ON "User"("username"); +CREATE UNIQUE INDEX "Session_id_key" ON "Session"("id"); +CREATE INDEX "Session_user_id_idx" ON "Session"("user_id"); +CREATE UNIQUE INDEX "Key_id_key" ON "Key"("id"); +CREATE INDEX "Key_user_id_idx" ON "Key"("user_id"); +CREATE INDEX "Track_producerId_idx" ON "Track"("producerId"); +CREATE INDEX "TrackVersion_trackId_idx" ON "TrackVersion"("trackId"); +CREATE INDEX "Comment_authorId_trackVersionId_idx" ON "Comment"("authorId", "trackVersionId"); +COMMIT; diff --git a/plice/src/index.test.ts b/plice/src/index.test.ts deleted file mode 100644 index 964d287..0000000 --- a/plice/src/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from 'vitest'; - -describe('sum test', () => { - it('adds 1 + 2 to equal 3', () => { - expect(1 + 2).toBe(3); - }); -}); diff --git a/plice/src/lib/server/db/__snapshots__/prisma.test.ts.snap b/plice/src/lib/server/db/__snapshots__/prisma.test.ts.snap new file mode 100644 index 0000000..a6d6eb4 --- /dev/null +++ b/plice/src/lib/server/db/__snapshots__/prisma.test.ts.snap @@ -0,0 +1,53 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`homepage fetch 1`] = ` +[ + { + "_count": { + "versions": 1, + }, + "id": 1, + "producer": { + "username": "fake", + }, + "title": "Meow", + "versions": [ + { + "comments": [ + { + "author": { + "username": "fake", + }, + "content": "UwU", + "createdAt": 2024-01-12T16:54:13.000Z, + }, + ], + }, + ], + }, +] +`; + +exports[`track page fetch 1`] = ` +{ + "createdAt": 2024-01-12T13:10:10.419Z, + "producer": { + "username": "fake", + }, + "title": "Meow", + "versions": [ + { + "comments": [ + { + "author": { + "username": "fake", + }, + "content": "UwU", + "createdAt": 2024-01-12T16:54:13.000Z, + }, + ], + "id": "700ab0a6-fbf4-482e-b283-8b3d3984324f", + }, + ], +} +`; diff --git a/plice/src/lib/server/db/prisma.test.ts b/plice/src/lib/server/db/prisma.test.ts new file mode 100644 index 0000000..eaa1ed0 --- /dev/null +++ b/plice/src/lib/server/db/prisma.test.ts @@ -0,0 +1,13 @@ +import { expect, test } from 'vitest'; +import { DatabasePrisma } from './prisma'; + +const TEST_USER_ID = 'afo41yngw19cjh2'; +const TEST_TRACK_ID = 1; + +const readOnlyDatabase = new DatabasePrisma('file:/tmp/larsen/readonly.db'); + +test('homepage fetch', async () => + expect(await readOnlyDatabase.fetchHomepageData(TEST_USER_ID)).toMatchSnapshot()); + +test('track page fetch', async () => + expect(await readOnlyDatabase.fetchTrackPageData(TEST_TRACK_ID)).toMatchSnapshot()); diff --git a/plice/src/routes/__snapshots__/page.server.test.ts.snap b/plice/src/routes/__snapshots__/page.server.test.ts.snap new file mode 100644 index 0000000..b88b04b --- /dev/null +++ b/plice/src/routes/__snapshots__/page.server.test.ts.snap @@ -0,0 +1,27 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`anonymous homepage load 1`] = ` +{ + "tracks": [], +} +`; + +exports[`user homepage load 1`] = ` +{ + "tracks": [ + { + "comments": [ + { + "author": "other-fake-user", + "content": "Lorem ipsum dolar", + "createdAt": 2024-02-10T13:20:43.426Z, + }, + ], + "id": 1, + "producer": "fake-user", + "title": "Fake Song", + "version": 1, + }, + ], +} +`; diff --git a/plice/src/routes/page.server.test.ts b/plice/src/routes/page.server.test.ts new file mode 100644 index 0000000..9e19c91 --- /dev/null +++ b/plice/src/routes/page.server.test.ts @@ -0,0 +1,36 @@ +import { expect, test } from 'vitest'; +import { load } from './+page.server'; + +import { MockHomepageFetch } from '$lib/server/db/mock'; + +test('anonymous homepage load', async () => { + const db = new MockHomepageFetch(); + const locals = { + // TODO: Replace with authentication adapter + authReq: { + validate: () => { + return null; + } + }, + database: db + }; + + // @ts-expect-error: Faking authentication object + expect(await load({ locals })).toMatchSnapshot(); +}); + +test('user homepage load', async () => { + const db = new MockHomepageFetch(); + const locals = { + // TODO: Replace with authentication adapter + authReq: { + validate: () => { + return { user: { userId: 'fake' } }; + } + }, + database: db + }; + + // @ts-expect-error: Faking authentication object + expect(await load({ locals })).toMatchSnapshot(); +}); diff --git a/plice/src/setup.ts b/plice/src/setup.ts new file mode 100644 index 0000000..6744985 --- /dev/null +++ b/plice/src/setup.ts @@ -0,0 +1,28 @@ +import { DatabasePrisma } from '$lib/server/db/prisma'; +import fs from 'node:fs'; + +const setup = async () => { + fs.mkdirSync('/tmp/larsen', { recursive: true }); + const sql = fs + .readFileSync('prisma/mock.sql') + .toString() + .split('\n') + .filter((line) => line.indexOf('--') !== 0) + .join('\n') + .replace(/(\r\n|\n|\r)/gm, ' ') + .replace(/\s+/g, ' ') + .split(';') + .filter((line) => line != ' '); + + const db = new DatabasePrisma('file:/tmp/larsen/readonly.db'); + + for (const statement of sql) { + await db.client.$executeRawUnsafe(statement); + } +}; + +const teardown = () => { + fs.rmSync('/tmp/larsen/readonly.db'); +}; + +export { setup, teardown }; diff --git a/plice/vite.config.ts b/plice/vite.config.ts index 0131ff9..7ec94ab 100644 --- a/plice/vite.config.ts +++ b/plice/vite.config.ts @@ -4,6 +4,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [sveltekit()], test: { - include: ['src/**/*.{test,spec}.{js,ts}'] + include: ['src/**/*.{test,spec}.{js,ts}'], + globalSetup: './src/setup.ts' } });