From 796e281d0f870d82bec19c6c71cbd042aaebbac3 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Thu, 22 Aug 2024 02:12:11 -0500 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20complete=20user=20cartridge=20c?= =?UTF-8?q?overage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/shared.ts | 2 + .../migration.sql | 2 +- prisma/schema/meds.prisma | 50 ++++++------- prisma/schema/schema.prisma | 4 +- prisma/schema/user.prisma | 61 ++++++++-------- server/controllers/cartridge.ts | 30 +++----- server/controllers/token.ts | 29 +++----- server/utils/handlers.ts | 2 +- server/utils/user.ts | 70 +++++++++---------- test/auth.ts | 4 +- test/cartridge.test.ts | 46 ++++++++++++ test/pen.test.ts | 8 +++ vitest.config.ts | 6 ++ 13 files changed, 180 insertions(+), 134 deletions(-) rename prisma/migrations/{20240811202849_initial => 20240822065859_init}/migration.sql (99%) create mode 100644 test/cartridge.test.ts diff --git a/app/utils/shared.ts b/app/utils/shared.ts index 165a74b..f1e5fe6 100644 --- a/app/utils/shared.ts +++ b/app/utils/shared.ts @@ -57,3 +57,5 @@ export const shotUnits = [ 25, 50, ] + +export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) diff --git a/prisma/migrations/20240811202849_initial/migration.sql b/prisma/migrations/20240822065859_init/migration.sql similarity index 99% rename from prisma/migrations/20240811202849_initial/migration.sql rename to prisma/migrations/20240822065859_init/migration.sql index 9ba901f..ebb0ad3 100644 --- a/prisma/migrations/20240811202849_initial/migration.sql +++ b/prisma/migrations/20240822065859_init/migration.sql @@ -30,7 +30,7 @@ CREATE TABLE `shots` ( `userId` BIGINT NOT NULL, `cartridgeId` BIGINT NOT NULL, `units` INTEGER NOT NULL, - `date` DATE NOT NULL, + `date` DATETIME(3) NOT NULL, `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updatedAt` DATETIME(3) NOT NULL, diff --git a/prisma/schema/meds.prisma b/prisma/schema/meds.prisma index 38db6aa..49e57ee 100644 --- a/prisma/schema/meds.prisma +++ b/prisma/schema/meds.prisma @@ -1,39 +1,41 @@ - model Cartridge { - id BigInt @id @default(autoincrement()) + id BigInt @id @default(autoincrement()) userId BigInt - user User @relation(fields: [userId], references: [id]) - content String - ml Decimal - mg Decimal - shots Shot[] - pen Pen? + user User @relation(fields: [userId], references: [id]) + content String + ml Decimal + mg Decimal + shots Shot[] + pen Pen? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + @@map("cartridges") } model Pen { - id BigInt @id @default(autoincrement()) - user User @relation(fields: [userId], references: [id]) - userId BigInt - cartridge Cartridge? @relation(fields: [cartridgeId], references: [id], onDelete: Restrict ) - cartridgeId BigInt? @unique - color String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id BigInt @id @default(autoincrement()) + user User @relation(fields: [userId], references: [id]) + userId BigInt + cartridge Cartridge? @relation(fields: [cartridgeId], references: [id], onDelete: Restrict) + cartridgeId BigInt? @unique + color String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + @@map("pens") } model Shot { - id BigInt @id @default(autoincrement()) - userId BigInt - user User @relation(fields: [userId], references: [id]) + id BigInt @id @default(autoincrement()) + userId BigInt + user User @relation(fields: [userId], references: [id]) cartridgeId BigInt - cartridge Cartridge @relation(fields: [cartridgeId], references: [id], onDelete: Cascade, onUpdate: Cascade ) - units Int - date DateTime - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + cartridge Cartridge @relation(fields: [cartridgeId], references: [id], onDelete: Cascade, onUpdate: Cascade) + units Int + date DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + @@map("shots") } diff --git a/prisma/schema/schema.prisma b/prisma/schema/schema.prisma index 4bbb178..0ba8497 100644 --- a/prisma/schema/schema.prisma +++ b/prisma/schema/schema.prisma @@ -1,9 +1,9 @@ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" previewFeatures = ["prismaSchemaFolder"] - binaryTargets = ["native", "rhel-openssl-3.0.x"] + binaryTargets = ["native", "rhel-openssl-3.0.x"] } datasource db { diff --git a/prisma/schema/user.prisma b/prisma/schema/user.prisma index 1590960..cf9a5cf 100644 --- a/prisma/schema/user.prisma +++ b/prisma/schema/user.prisma @@ -1,44 +1,47 @@ model User { - id BigInt @id @default(autoincrement()) - email String @unique - name String? - avatar String? @db.VarChar(1600) - providers Provider[] - sessions Token[] - pens Pen[] - cartridges Cartridge[] - shots Shot[] - payload Json - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id BigInt @id @default(autoincrement()) + email String @unique + name String? + avatar String? @db.VarChar(1600) + providers Provider[] + sessions Token[] + pens Pen[] + cartridges Cartridge[] + shots Shot[] + payload Json + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + @@map("users") } model Provider { - id BigInt @id @default(autoincrement()) + id BigInt @id @default(autoincrement()) userId BigInt - user User @relation(fields: [userId], references: [id]) - name String? - avatar String? @db.VarChar(1600) - payload Json + user User @relation(fields: [userId], references: [id]) + name String? + avatar String? @db.VarChar(1600) + payload Json createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - @@map("providers") + @@unique([userId, name]) + @@map("providers") } model Token { - id BigInt @id @default(autoincrement()) - userId BigInt - user User @relation(fields: [userId], references: [id]) - hash String @unique - source String - ip String - agent String - location Json + id BigInt @id @default(autoincrement()) + userId BigInt + user User @relation(fields: [userId], references: [id]) + hash String @unique + source String + ip String + agent String + location Json // coordinate Unsupported("Point") - coordinate String @default("30.2423 -97.7672") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + coordinate String @default("30.2423 -97.7672") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + @@map("tokens") } diff --git a/server/controllers/cartridge.ts b/server/controllers/cartridge.ts index 2b1dee0..c2e168d 100644 --- a/server/controllers/cartridge.ts +++ b/server/controllers/cartridge.ts @@ -1,8 +1,8 @@ import { z } from 'zod' +import type { Cartridge } from '~/types/models' import { cartridgeContents, cartridgeMgs, cartridgeMls } from '~/utils/shared' -const index = defineEventHandler(async (event) => { - const { user } = await requireUserSession(event) +const index = authedHandler(async ({ user, event }) => { return metapi().render( await prisma.cartridge.findMany({ where: { @@ -16,8 +16,7 @@ const index = defineEventHandler(async (event) => { ) }) -const create = defineEventHandler(async (event) => { - const { user } = await requireUserSession(event) +const create = authedHandler(async ({ user, event }) => { const schema = z.object({ content: z.enum(cartridgeContents as [string, ...string[]]), ml: z.enum(cartridgeMls as [string, ...string[]]), @@ -37,26 +36,19 @@ const create = defineEventHandler(async (event) => { return metapi().success('cartridge created', cartridge) }) -const get = defineEventHandler(async (event) => { - const { user } = await requireUserSession(event) - const schema = z.object({ id: z.number() }) - const parsed = schema.safeParse({ id: event.context.params?.id }) - if (!parsed.success) return metapi().error(event, parsed.error.issues, 403) +const get = authedModelHandler(async ({ event, user, model: cartridge }) => { + if (Number(cartridge.userId) !== Number(user.id)) + return metapi().error(event, 'Unauthorized', 401) - return metapi().renderNullError(event, await prisma.cartridge.findUnique({ - where: { - id: parsed.data.id, - userId: user.id, - }, - })) + return metapi().render(cartridge) }) -const remove = defineEventHandler(async (event) => { - const { user } = await requireUserSession(event) - const id = event.context.params?.id +const remove = authedModelHandler(async ({ event, user, model: cartridge }) => { + if (Number(cartridge.userId) !== Number(user.id)) + return metapi().error(event, 'Unauthorized', 401) await prisma.cartridge.delete({ where: { - id: Number.parseInt(id as string), + id: cartridge.id, userId: user.id, }, }) diff --git a/server/controllers/token.ts b/server/controllers/token.ts index d8f8b5c..5be6591 100644 --- a/server/controllers/token.ts +++ b/server/controllers/token.ts @@ -1,8 +1,7 @@ import { z } from 'zod' +import type { Token } from '~/types/models' -const index = defineEventHandler(async (event) => { - const { user } = await requireUserSession(event) - console.log('we are passed requireUserSession') +const index = authedHandler(async ({ user, event }) => { return metapi().render( await prisma.$extends({ result: { @@ -21,26 +20,18 @@ const index = defineEventHandler(async (event) => { ) }) -const get = defineEventHandler(async (event) => { - const { user } = await requireUserSession(event) - const schema = z.object({ id: z.number() }) - const parsed = schema.safeParse({ id: event.context.params?.id }) - if (!parsed.success) return metapi().error(event, parsed.error.issues, 403) - - return metapi().renderNullError(event, await prisma.token.findUnique({ - where: { - id: parsed.data.id, - userId: user.id, - }, - })) +const get = authedModelHandler(async ({ user, event, model: token }) => { + if (Number(token.userId) !== Number(user.id)) + return metapi().error(event, 'Unauthorized', 401) + return metapi().renderNullError(event, token) }) -const remove = defineEventHandler(async (event) => { - const { user } = await requireUserSession(event) - const id = event.context.params?.id +const remove = authedModelHandler(async ({ user, event, model: token }) => { + if (token.userId !== user.id) + return metapi().error(event, 'Unauthorized', 401) await prisma.token.delete({ where: { - id: Number.parseInt(id as string), + id: token.id, userId: user.id, }, }) diff --git a/server/utils/handlers.ts b/server/utils/handlers.ts index 38751aa..4f7517b 100644 --- a/server/utils/handlers.ts +++ b/server/utils/handlers.ts @@ -125,7 +125,7 @@ export function authedModelHandler( return authedHandler(async ({ user, event }) => { const model = await handleModelLookup(event, mergedOptions, user) - if (mergedOptions.admin && !user.isAdmin) return metapi().notFound(event) + if (model === null || (mergedOptions.admin && !user.isAdmin)) return metapi().notFound(event) return handler({ user, event, model }) }) } diff --git a/server/utils/user.ts b/server/utils/user.ts index cd48b7f..d50a85f 100644 --- a/server/utils/user.ts +++ b/server/utils/user.ts @@ -8,52 +8,46 @@ import type { TokenLocation, UserInfo } from '~/types/oauth' export const createUser = async (info: UserInfo, provider: string, oauthPayload: any, event?: H3Event): Promise => { let user: User | null = null - user = await prisma.user.findUnique({ - where: { + user = await prisma.user.upsert({ + where: { email: info.email }, + create: { email: info.email, - }, - }) as unknown as User - - if (!user) - user = await prisma.user.create({ - data: { - email: info.email, - name: info.name, - avatar: info.avatar, - payload: (info.payload ? info.payload : ({ roles: { admin: false } } as UserPayload)) as unknown as Prisma.JsonObject, - providers: { - create: [ - { + name: info.name, + avatar: info.avatar, + payload: (info.payload ? info.payload : ({ roles: { admin: false } } as UserPayload)) as unknown as Prisma.JsonObject, + providers: { + create: [ + { - name: provider, - avatar: info.avatar, - payload: oauthPayload as unknown as Prisma.JsonObject, - }, + name: provider, + avatar: info.avatar, + payload: oauthPayload as unknown as Prisma.JsonObject, + }, - ], - }, + ], }, - }) as unknown as User + }, + update: {}, + }) as unknown as User - else - await prisma.provider.upsert({ - where: { - userId_name: { - userId: BigInt(user.id), - name: provider, - }, - }, - update: { - avatar: info.avatar, - payload: oauthPayload as unknown as Prisma.JsonObject, - }, - create: { + await prisma.provider.upsert({ + where: { + userId_name: { userId: BigInt(user.id), name: provider, - avatar: info.avatar, - payload: oauthPayload as unknown as Prisma.JsonObject, }, - }) + }, + update: { + avatar: info.avatar, + payload: oauthPayload as unknown as Prisma.JsonObject, + }, + create: { + userId: BigInt(user.id), + name: provider, + avatar: info.avatar, + payload: oauthPayload as unknown as Prisma.JsonObject, + }, + }) const coordinate = event ? `${event.node.req.headers['Cloudfront-Viewer-Latitude']} ${event.node.req.headers['Cloudfront-Viewer-Longitude']}` diff --git a/test/auth.ts b/test/auth.ts index 64a79c2..9537872 100644 --- a/test/auth.ts +++ b/test/auth.ts @@ -36,6 +36,7 @@ async function userFromEmail(email: string): Promise { if (!users[index]) throw new Error('User not found') if (!users[index].session?.id) users[index].session = await createUser(users[index], 'github', {}) + return users[index] } @@ -46,7 +47,8 @@ async function actingAs(email: string) { const get = (url: string) => $fetch>(url, { headers: { cookie: user.cookie as string } }) const post = (url: string, params: object) => $fetch>(url, { method: 'POST', body: params, headers: { cookie: user.cookie as string } }) const put = (url: string, params: object) => $fetch>(url, { method: 'PUT', body: params, headers: { cookie: user.cookie as string } }) - return { get, post, put, user } + const remove = (url: string, params?: object) => $fetch>(url, { method: 'DELETE', body: params, headers: { cookie: user.cookie as string } }) + return { get, post, put, remove, user } } export { diff --git a/test/cartridge.test.ts b/test/cartridge.test.ts new file mode 100644 index 0000000..b7466a9 --- /dev/null +++ b/test/cartridge.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest' +import { setup } from '@nuxt/test-utils' +import { actingAs } from './auth' +import { setupConfig } from './config' +import type { Cartridge } from '~/types/models' + +describe('/api/cartridge', async () => { + await setup(setupConfig()) + + const cartridges: Cartridge[] = [] + + it('post /api/cartridge - create a cartridge', async () => { + const { post, user } = await actingAs('test@test.com') + const { data: cartridge } = await post('/api/cartridge', { + content: cartridgeContents[0], + ml: cartridgeMls[0], + mg: cartridgeMgs[0], + }) + + expect(cartridge.content).toBe(cartridgeContents[0]) + expect(`${cartridge.ml}.0`).toBe(cartridgeMls[0]) + expect(cartridge.mg).toBe(cartridgeMgs[0]) + expect(cartridge.userId).toBe(user.session.id.toString()) + cartridges.push(cartridge) + }) + + it('get /api/cartridge - list all cartridges', async () => { + const { get, user } = await actingAs('test@test.com') + const response = await get('/api/cartridge') + expect(response.data[0]?.userId).toBe(user.session.id.toString()) + }) + + it('get /api/cartridge/:id - get a cartridge', async () => { + const { get } = await actingAs('test@test.com') + const response = await get(`/api/cartridge/${cartridges[0]?.id}`) + expect(response.data).toStrictEqual(cartridges[0]) + }) + + it('delete /api/cartridge/:id - delete a cartridge', async () => { + if (!cartridges[0]) throw new Error('Cartridge not found') + const { remove, get } = await actingAs('test@test.com') + await remove(`/api/cartridge/${cartridges[0]?.id}`) + try { await get(`/api/cartridge/${cartridges[0]?.id}`) } + catch (error: any) { expect(error.response.status).toBe(404) } + }) +}) diff --git a/test/pen.test.ts b/test/pen.test.ts index a094c01..f03a874 100644 --- a/test/pen.test.ts +++ b/test/pen.test.ts @@ -35,4 +35,12 @@ describe('/api/pen', async () => { const { data: pen } = await put(`/api/pen/${pens[0]?.id}`, { color: penColors[1] }) expect(pen.color).toBe(penColors[1]) }) + + it ('delete /api/pen/:id - delete a pen', async () => { + if (!pens[0]) throw new Error('Pen not found') + const { remove, get } = await actingAs('test@test.com') + await remove(`/api/pen/${pens[0]?.id}`) + try { await get(`/api/pen/${pens[0]?.id}`) } + catch (error: any) { expect(error.response.status).toBe(404) } + }) }) diff --git a/vitest.config.ts b/vitest.config.ts index b8da155..0619202 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,12 @@ import { defineVitestConfig } from '@nuxt/test-utils/config' export default defineVitestConfig({ root: './server', test: { + poolOptions: { + forks: { + minForks: 1, + maxForks: 1, + }, + }, environment: 'nuxt', environmentOptions: { nuxt: { From 36484039f42aab10d1d5a1660c75331cbf563450 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Thu, 22 Aug 2024 02:23:51 -0500 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=A8=20support=20for=20ui=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 ++ pnpm-lock.yaml | 58 ++++++++++++++++++++++++++++++------------ test/cartridge.test.ts | 1 + 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index ed8fd15..7c588a3 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "nuxt preview", "test": "vitest run", "test:dev": "DEVRUN=true vitest run", + "test:dev:ui": "DEVRUN=true vitest --ui", "test:reset": "pnpm run db:test:reset; vitest run", "test:dev:reset": "pnpm run db:test:reset; DEVRUN=true vitest run", "db:test:reset": "dotenv -e .env.test -- npx prisma migrate reset --force", @@ -35,6 +36,7 @@ "@prisma/nuxt": "^0.0.35", "@types/ua-parser-js": "^0.7.39", "@vitest/coverage-v8": "^2.0.5", + "@vitest/ui": "^2.0.5", "dotenv-cli": "^7.4.2", "happy-dom": "^14.12.3", "nuxt": "^3.12.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 253db60..322efbc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,7 +17,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^2.26.0 - version: 2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(@vue/compiler-sfc@3.4.38)(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6)) + version: 2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(@vue/compiler-sfc@3.4.38)(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6)) '@iconify-json/logos': specifier: ^1.1.44 version: 1.1.44 @@ -38,7 +38,7 @@ importers: version: 1.7.0(ioredis@5.4.1)(magicast@0.3.4)(rollup@4.20.0) '@nuxt/test-utils': specifier: ^3.14.1 - version: 3.14.1(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)) + version: 3.14.1(@vitest/ui@2.0.5(vitest@2.0.5))(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)) '@prisma/client': specifier: ^5.18.0 version: 5.18.0(prisma@5.18.0) @@ -50,7 +50,10 @@ importers: version: 0.7.39 '@vitest/coverage-v8': specifier: ^2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6)) + version: 2.0.5(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6)) + '@vitest/ui': + specifier: ^2.0.5 + version: 2.0.5(vitest@2.0.5) dotenv-cli: specifier: ^7.4.2 version: 7.4.2 @@ -71,7 +74,7 @@ importers: version: 1.0.38 vitest: specifier: ^2.0.5 - version: 2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6) + version: 2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6) zod: specifier: ^3.23.8 version: 3.23.8 @@ -1631,6 +1634,11 @@ packages: '@vitest/spy@2.0.5': resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + '@vitest/ui@2.0.5': + resolution: {integrity: sha512-m+ZpVt/PVi/nbeRKEjdiYeoh0aOfI9zr3Ria9LO7V2PlMETtAXJS3uETEZkc8Be2oOl8mhd7Ew+5SRBXRYncNw==} + peerDependencies: + vitest: 2.0.5 + '@vitest/utils@2.0.5': resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} @@ -2830,6 +2838,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -5534,7 +5545,7 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@antfu/eslint-config@2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(@vue/compiler-sfc@3.4.38)(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6))': + '@antfu/eslint-config@2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(@vue/compiler-sfc@3.4.38)(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6))': dependencies: '@antfu/install-pkg': 0.3.5 '@clack/prompts': 0.7.0 @@ -5542,7 +5553,7 @@ snapshots: '@stylistic/eslint-plugin': 2.6.4(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) '@typescript-eslint/eslint-plugin': 8.1.0(@typescript-eslint/parser@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) '@typescript-eslint/parser': 8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) - '@vitest/eslint-plugin': 1.0.3(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6)) + '@vitest/eslint-plugin': 1.0.3(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6)) eslint: 9.9.0(jiti@1.21.6) eslint-config-flat-gitignore: 0.1.8 eslint-flat-config-utils: 0.3.0 @@ -6602,7 +6613,7 @@ snapshots: - rollup - supports-color - '@nuxt/test-utils@3.14.1(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4))': + '@nuxt/test-utils@3.14.1(@vitest/ui@2.0.5(vitest@2.0.5))(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4))': dependencies: '@nuxt/kit': 3.12.4(magicast@0.3.4)(rollup@4.20.0) '@nuxt/schema': 3.12.4(rollup@4.20.0) @@ -6629,12 +6640,13 @@ snapshots: unenv: 1.10.0 unplugin: 1.12.2 vite: 5.4.1(@types/node@22.4.0)(terser@5.31.6) - vitest-environment-nuxt: 1.0.1(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)) + vitest-environment-nuxt: 1.0.1(@vitest/ui@2.0.5(vitest@2.0.5))(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)) vue: 3.4.38(typescript@5.5.4) vue-router: 4.4.3(vue@3.4.38(typescript@5.5.4)) optionalDependencies: + '@vitest/ui': 2.0.5(vitest@2.0.5) happy-dom: 14.12.3 - vitest: 2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6) + vitest: 2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6) transitivePeerDependencies: - magicast - rollup @@ -7405,7 +7417,7 @@ snapshots: vite: 5.4.1(@types/node@22.4.0)(terser@5.31.6) vue: 3.4.38(typescript@5.5.4) - '@vitest/coverage-v8@2.0.5(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6))': + '@vitest/coverage-v8@2.0.5(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -7419,17 +7431,17 @@ snapshots: std-env: 3.7.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6) + vitest: 2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6) transitivePeerDependencies: - supports-color - '@vitest/eslint-plugin@1.0.3(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6))': + '@vitest/eslint-plugin@1.0.3(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6))': dependencies: eslint: 9.9.0(jiti@1.21.6) optionalDependencies: '@typescript-eslint/utils': 8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) typescript: 5.5.4 - vitest: 2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6) + vitest: 2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6) '@vitest/expect@2.0.5': dependencies: @@ -7457,6 +7469,17 @@ snapshots: dependencies: tinyspy: 3.0.0 + '@vitest/ui@2.0.5(vitest@2.0.5)': + dependencies: + '@vitest/utils': 2.0.5 + fast-glob: 3.3.2 + fflate: 0.8.2 + flatted: 3.3.1 + pathe: 1.1.2 + sirv: 2.0.4 + tinyrainbow: 1.2.0 + vitest: 2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6) + '@vitest/utils@2.0.5': dependencies: '@vitest/pretty-format': 2.0.5 @@ -8841,6 +8864,8 @@ snapshots: dependencies: reusify: 1.0.4 + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -11883,9 +11908,9 @@ snapshots: fsevents: 2.3.3 terser: 5.31.6 - vitest-environment-nuxt@1.0.1(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)): + vitest-environment-nuxt@1.0.1(@vitest/ui@2.0.5(vitest@2.0.5))(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)): dependencies: - '@nuxt/test-utils': 3.14.1(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)) + '@nuxt/test-utils': 3.14.1(@vitest/ui@2.0.5(vitest@2.0.5))(h3@1.12.0)(happy-dom@14.12.3)(magicast@0.3.4)(nitropack@2.9.7(magicast@0.3.4))(rollup@4.20.0)(vite@5.4.1(@types/node@22.4.0)(terser@5.31.6))(vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)) transitivePeerDependencies: - '@cucumber/cucumber' - '@jest/globals' @@ -11906,7 +11931,7 @@ snapshots: - vue - vue-router - vitest@2.0.5(@types/node@22.4.0)(happy-dom@14.12.3)(terser@5.31.6): + vitest@2.0.5(@types/node@22.4.0)(@vitest/ui@2.0.5)(happy-dom@14.12.3)(terser@5.31.6): dependencies: '@ampproject/remapping': 2.3.0 '@vitest/expect': 2.0.5 @@ -11929,6 +11954,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.4.0 + '@vitest/ui': 2.0.5(vitest@2.0.5) happy-dom: 14.12.3 transitivePeerDependencies: - less diff --git a/test/cartridge.test.ts b/test/cartridge.test.ts index b7466a9..d4a38b7 100644 --- a/test/cartridge.test.ts +++ b/test/cartridge.test.ts @@ -42,5 +42,6 @@ describe('/api/cartridge', async () => { await remove(`/api/cartridge/${cartridges[0]?.id}`) try { await get(`/api/cartridge/${cartridges[0]?.id}`) } catch (error: any) { expect(error.response.status).toBe(404) } + console.log(cartridges) }) }) From 6294251678d300753614666a1bd6bda18cfc2bb1 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Thu, 22 Aug 2024 02:38:50 -0500 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9C=A8=20shot=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/controllers/shot.ts | 3 +-- test/shot.test.ts | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 test/shot.test.ts diff --git a/server/controllers/shot.ts b/server/controllers/shot.ts index 710aa08..ca519ad 100644 --- a/server/controllers/shot.ts +++ b/server/controllers/shot.ts @@ -22,8 +22,7 @@ const index = defineEventHandler(async (event) => { ) }) -const create = defineEventHandler(async (event) => { - const { user } = await requireUserSession(event) +const create = authedHandler(async ({ user, event }) => { const schema = z.object({ cartridge: z.string(), units: z.number(), diff --git a/test/shot.test.ts b/test/shot.test.ts new file mode 100644 index 0000000..11756c5 --- /dev/null +++ b/test/shot.test.ts @@ -0,0 +1,51 @@ +import { setup } from '@nuxt/test-utils' +import { describe, expect, it } from 'vitest' +import { actingAs } from './auth' +import { setupConfig } from './config' +import type { Cartridge, Pen, Shot } from '~/types/models' + +describe('/api/shot', async () => { + await setup(setupConfig()) + + const pens: Pen[] = [] + const cartridges: Cartridge[] = [] + const shots: Shot[] = [] + + it('post /api/shot - create a shot', async () => { + const { post, put, user } = await actingAs('test@test.com') + const { data: pen } = await post('/api/pen', { color: penColors[0] }) + const { data: cartridge } = await post('/api/cartridge', { + content: cartridgeContents[0], + ml: cartridgeMls[0], + mg: cartridgeMgs[0], + }) + cartridges.push(cartridge) + const updateResponse = await put(`/api/pen/${pen.id}`, { cartridgeId: cartridge.id }) + pens.push(updateResponse.data) + + const { data: shot } = await post('/api/shot', { + cartridge: cartridge.id, + units: shotUnits[0], + date: new Date().toISOString(), + }) + + expect(shot.userId).toBe(user.session.id.toString()) + expect(shot.cartridgeId).toBe(cartridge.id.toString()) + expect(shot.units).toBe(shotUnits[0]) + shots.push(shot) + }) + + it('get /api/shot - list all shots', async () => { + const { get, user } = await actingAs('test@test.com') + const response = await get('/api/shot') + expect(response.data[0]?.userId).toBe(user.session.id.toString()) + }) + + it ('delete /api/shot/:id - delete a shot', async () => { + if (!shots[0]) throw new Error('Shot not found') + const { remove, get } = await actingAs('test@test.com') + await remove(`/api/shot/${shots[0]?.id}`) + try { await get(`/api/shot/${shots[0]?.id}`) } + catch (error: any) { expect(error.response.status).toBe(404) } + }) +}) From 6902cce09c988928ede7b23469d633ffeac147e6 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Thu, 22 Aug 2024 03:59:45 -0500 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=A8=20proper=20testing=20for=20admin?= =?UTF-8?q?=20pens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.vue | 2 - app/pages/users/index.vue | 2 +- package.json | 6 +- pnpm-lock.yaml | 1403 +++++++++++++++++------------------- server/api/[...slug].ts | 20 +- server/controllers/pens.ts | 41 +- server/utils/handlers.ts | 7 +- server/utils/routing.ts | 10 +- test/cartridge.test.ts | 2 - test/pen.test.ts | 3 +- test/pens.test.ts | 51 ++ test/shot.test.ts | 1 - test/user.test.ts | 7 +- 13 files changed, 758 insertions(+), 797 deletions(-) create mode 100644 test/pens.test.ts diff --git a/app/app.vue b/app/app.vue index 8bb9a54..b138619 100644 --- a/app/app.vue +++ b/app/app.vue @@ -2,8 +2,6 @@ const colorMode = useColorMode() const color = computed(() => colorMode.value === 'dark' ? '#111827' : 'white') -console.log('we are in app.vue') - useHead({ meta: [ { charset: 'utf-8' }, diff --git a/app/pages/users/index.vue b/app/pages/users/index.vue index 4409a25..ee208d3 100644 --- a/app/pages/users/index.vue +++ b/app/pages/users/index.vue @@ -28,7 +28,7 @@ const columns = [ }, ] -const { data: users } = await useFetch>('/api/user') +const { data: users } = await useFetch>('/api/users')