mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-14 17:26:26 +01:00
✅ Add tests for invite router (#1456)
* ✅ Add test for invite router * ✅ Add cleanup to invite-router tests, add tests for creation and deletion of invites * ♻️ Fix typo * ♻️ Remove nullish for limit of invite router all procedure
This commit is contained in:
@@ -29,7 +29,7 @@ export const DeleteInviteModal = ({ id, innerProps }: ContextModalProps<{ tokenI
|
|||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await deleteAsync({
|
await deleteAsync({
|
||||||
tokenId: innerProps.tokenId,
|
id: innerProps.tokenId,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
|
import { createTRPCRouter } from '~/server/api/trpc';
|
||||||
|
|
||||||
import { appRouter } from './routers/app';
|
import { appRouter } from './routers/app';
|
||||||
import { boardRouter } from './routers/board';
|
import { boardRouter } from './routers/board';
|
||||||
import { calendarRouter } from './routers/calendar';
|
import { calendarRouter } from './routers/calendar';
|
||||||
import { configRouter } from './routers/config';
|
import { configRouter } from './routers/config';
|
||||||
import { dashDotRouter } from './routers/dash-dot';
|
import { dashDotRouter } from './routers/dash-dot';
|
||||||
import { dnsHoleRouter } from './routers/dns-hole/router';
|
import { dnsHoleRouter } from './routers/dns-hole/router';
|
||||||
|
import { dockerRouter } from './routers/docker/router';
|
||||||
import { downloadRouter } from './routers/download';
|
import { downloadRouter } from './routers/download';
|
||||||
import { iconRouter } from './routers/icon';
|
import { iconRouter } from './routers/icon';
|
||||||
import { inviteRouter } from './routers/invite';
|
import { inviteRouter } from './routers/invite/invite-router';
|
||||||
import { mediaRequestsRouter } from './routers/media-request';
|
import { mediaRequestsRouter } from './routers/media-request';
|
||||||
import { mediaServerRouter } from './routers/media-server';
|
import { mediaServerRouter } from './routers/media-server';
|
||||||
|
import { notebookRouter } from './routers/notebook';
|
||||||
import { overseerrRouter } from './routers/overseerr';
|
import { overseerrRouter } from './routers/overseerr';
|
||||||
import { passwordRouter } from './routers/password';
|
import { passwordRouter } from './routers/password';
|
||||||
import { rssRouter } from './routers/rss';
|
import { rssRouter } from './routers/rss';
|
||||||
|
import { timezoneRouter } from './routers/timezone';
|
||||||
|
import { usenetRouter } from './routers/usenet/router';
|
||||||
import { userRouter } from './routers/user';
|
import { userRouter } from './routers/user';
|
||||||
import { weatherRouter } from './routers/weather';
|
import { weatherRouter } from './routers/weather';
|
||||||
import { dockerRouter } from './routers/docker/router';
|
|
||||||
import { usenetRouter } from './routers/usenet/router';
|
|
||||||
import { createTRPCRouter } from '~/server/api/trpc';
|
|
||||||
import { timezoneRouter } from './routers/timezone';
|
|
||||||
import { notebookRouter } from './routers/notebook';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the primary router for your server.
|
* This is the primary router for your server.
|
||||||
@@ -45,7 +46,7 @@ export const rootRouter = createTRPCRouter({
|
|||||||
invites: inviteRouter,
|
invites: inviteRouter,
|
||||||
boards: boardRouter,
|
boards: boardRouter,
|
||||||
password: passwordRouter,
|
password: passwordRouter,
|
||||||
notebook: notebookRouter
|
notebook: notebookRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|||||||
234
src/server/api/routers/invite/invite-router.spec.ts
Normal file
234
src/server/api/routers/invite/invite-router.spec.ts
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
|
||||||
|
import { Session, User } from 'next-auth';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
import { db, sqlite } from '~/server/db';
|
||||||
|
import { invites, users } from '~/server/db/schema';
|
||||||
|
|
||||||
|
import { rootRouter } from '../../root';
|
||||||
|
import { inviteRouter } from './invite-router';
|
||||||
|
|
||||||
|
const sessionMock = (user?: Partial<User>): Session => ({
|
||||||
|
user: {
|
||||||
|
id: user?.id ?? '123',
|
||||||
|
name: user?.name ?? 'John Doe',
|
||||||
|
language: user?.language ?? 'en',
|
||||||
|
colorScheme: user?.colorScheme ?? 'dark',
|
||||||
|
autoFocusSearch: user?.autoFocusSearch ?? false,
|
||||||
|
isAdmin: user?.isAdmin ?? false,
|
||||||
|
},
|
||||||
|
expires: '1',
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('invite router', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.stubEnv('DATABASE_URL', ':memory:');
|
||||||
|
migrate(db, { migrationsFolder: './drizzle' });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
// Delete all data from database
|
||||||
|
const tables = sqlite.prepare(`select name from sqlite_master where type = 'table';`).all() as {
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
for (const table of tables) {
|
||||||
|
if (table.name.startsWith('__')) continue;
|
||||||
|
sqlite.prepare(`delete from ${table.name}`).run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Admin procedure check', async () => {
|
||||||
|
const caller = inviteRouter.createCaller({
|
||||||
|
session: sessionMock(),
|
||||||
|
cookies: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
(async () => {
|
||||||
|
await caller.all({ page: 0 });
|
||||||
|
})()
|
||||||
|
).rejects.toThrowError('FORBIDDEN');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('All invites should return invites from database', async () => {
|
||||||
|
const expireDate = new Date(2021, 1, 1);
|
||||||
|
await db.insert(users).values({
|
||||||
|
id: '123',
|
||||||
|
name: 'John Doe',
|
||||||
|
});
|
||||||
|
await db.insert(invites).values({
|
||||||
|
id: '123',
|
||||||
|
createdById: '123',
|
||||||
|
expires: expireDate,
|
||||||
|
token: 'token',
|
||||||
|
});
|
||||||
|
await db.insert(invites).values({
|
||||||
|
id: v4(),
|
||||||
|
createdById: '123',
|
||||||
|
expires: expireDate,
|
||||||
|
token: v4(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const caller = inviteRouter.createCaller({
|
||||||
|
session: sessionMock({ isAdmin: true }),
|
||||||
|
cookies: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await caller.all({ page: 0, limit: 1 });
|
||||||
|
|
||||||
|
expect(result.countPages).toEqual(2);
|
||||||
|
expect(result.invites[0]).toStrictEqual({
|
||||||
|
id: '123',
|
||||||
|
creator: 'John Doe',
|
||||||
|
expires: expireDate,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Create should create new invite in database with expiration in 6 minutes', async () => {
|
||||||
|
const expireDate = dayjs().add(6, 'minutes').set('milliseconds', 0).toDate();
|
||||||
|
await db.insert(users).values({
|
||||||
|
id: '123',
|
||||||
|
name: 'John Doe',
|
||||||
|
});
|
||||||
|
|
||||||
|
const caller = inviteRouter.createCaller({
|
||||||
|
session: sessionMock({ isAdmin: true }),
|
||||||
|
cookies: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await caller.create({ expiration: expireDate });
|
||||||
|
|
||||||
|
const dbInvite = await db.query.invites.findFirst({
|
||||||
|
where: eq(invites.id, result.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.id).toBeDefined();
|
||||||
|
expect(result.expires).toEqual(expireDate);
|
||||||
|
expect(result.token).toHaveLength(40);
|
||||||
|
expect(dbInvite).toStrictEqual({
|
||||||
|
id: result.id,
|
||||||
|
createdById: '123',
|
||||||
|
expires: expireDate,
|
||||||
|
token: result.token,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Create should create new invite in database with expiration in 30 days', async () => {
|
||||||
|
const expireDate = dayjs().add(6, 'months').add(-1, 'minute').set('milliseconds', 0).toDate();
|
||||||
|
await db.insert(users).values({
|
||||||
|
id: '123',
|
||||||
|
name: 'John Doe',
|
||||||
|
});
|
||||||
|
|
||||||
|
const caller = inviteRouter.createCaller({
|
||||||
|
session: sessionMock({ isAdmin: true }),
|
||||||
|
cookies: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await caller.create({ expiration: expireDate });
|
||||||
|
|
||||||
|
const dbInvite = await db.query.invites.findFirst({
|
||||||
|
where: eq(invites.id, result.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.id).toBeDefined();
|
||||||
|
expect(result.expires).toEqual(expireDate);
|
||||||
|
expect(result.token).toHaveLength(40);
|
||||||
|
expect(dbInvite).toStrictEqual({
|
||||||
|
id: result.id,
|
||||||
|
createdById: '123',
|
||||||
|
expires: expireDate,
|
||||||
|
token: result.token,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Create should throw too_small with expiration in 4 minutes', async () => {
|
||||||
|
const expireDate = dayjs().add(4, 'minutes').set('milliseconds', 0).toDate();
|
||||||
|
await db.insert(users).values({
|
||||||
|
id: '123',
|
||||||
|
name: 'John Doe',
|
||||||
|
});
|
||||||
|
|
||||||
|
const caller = inviteRouter.createCaller({
|
||||||
|
session: sessionMock({ isAdmin: true }),
|
||||||
|
cookies: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const act = async () => await caller.create({ expiration: expireDate });
|
||||||
|
|
||||||
|
expect(act).rejects.toThrowError(/"code": "too_small"/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Create should throw too_big with expiration in 7 months', async () => {
|
||||||
|
const expireDate = dayjs().add(7, 'months').set('milliseconds', 0).toDate();
|
||||||
|
await db.insert(users).values({
|
||||||
|
id: '123',
|
||||||
|
name: 'John Doe',
|
||||||
|
});
|
||||||
|
|
||||||
|
const caller = inviteRouter.createCaller({
|
||||||
|
session: sessionMock({ isAdmin: true }),
|
||||||
|
cookies: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const act = async () => await caller.create({ expiration: expireDate });
|
||||||
|
|
||||||
|
expect(act).rejects.toThrowError(/"code": "too_big"/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Delete should delete invite from database', async () => {
|
||||||
|
const inviteId = '123';
|
||||||
|
await db.insert(users).values({
|
||||||
|
id: '123',
|
||||||
|
name: 'John Doe',
|
||||||
|
});
|
||||||
|
await db.insert(invites).values({
|
||||||
|
id: inviteId,
|
||||||
|
createdById: '123',
|
||||||
|
expires: new Date(2023, 1, 1),
|
||||||
|
token: 'token',
|
||||||
|
});
|
||||||
|
|
||||||
|
const caller = inviteRouter.createCaller({
|
||||||
|
session: sessionMock({ isAdmin: true }),
|
||||||
|
cookies: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await caller.delete({ id: inviteId });
|
||||||
|
|
||||||
|
const dbInvite = await db.query.invites.findFirst({
|
||||||
|
where: eq(invites.id, inviteId),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(dbInvite).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Delete should delete invite from database', async () => {
|
||||||
|
const inviteId = '123';
|
||||||
|
await db.insert(users).values({
|
||||||
|
id: '123',
|
||||||
|
name: 'John Doe',
|
||||||
|
});
|
||||||
|
await db.insert(invites).values({
|
||||||
|
id: inviteId,
|
||||||
|
createdById: '123',
|
||||||
|
expires: new Date(2023, 1, 1),
|
||||||
|
token: 'token',
|
||||||
|
});
|
||||||
|
|
||||||
|
const caller = inviteRouter.createCaller({
|
||||||
|
session: sessionMock({ isAdmin: true }),
|
||||||
|
cookies: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await caller.delete({ id: inviteId });
|
||||||
|
|
||||||
|
const dbInvite = await db.query.invites.findFirst({
|
||||||
|
where: eq(invites.id, inviteId),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(dbInvite).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,18 +5,18 @@ import { z } from 'zod';
|
|||||||
import { db } from '~/server/db';
|
import { db } from '~/server/db';
|
||||||
import { invites } from '~/server/db/schema';
|
import { invites } from '~/server/db/schema';
|
||||||
|
|
||||||
import { adminProcedure, createTRPCRouter, publicProcedure } from '../trpc';
|
import { adminProcedure, createTRPCRouter, publicProcedure } from '../../trpc';
|
||||||
|
|
||||||
export const inviteRouter = createTRPCRouter({
|
export const inviteRouter = createTRPCRouter({
|
||||||
all: adminProcedure
|
all: adminProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
limit: z.number().min(1).max(100).nullish().default(10),
|
limit: z.number().min(1).max(100).default(10),
|
||||||
page: z.number().min(0),
|
page: z.number().min(0),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const limit = input.limit ?? 50;
|
const limit = input.limit;
|
||||||
const dbInvites = await db.query.invites.findMany({
|
const dbInvites = await db.query.invites.findMany({
|
||||||
limit: limit,
|
limit: limit,
|
||||||
offset: limit * input.page,
|
offset: limit * input.page,
|
||||||
@@ -67,7 +67,7 @@ export const inviteRouter = createTRPCRouter({
|
|||||||
expires: inviteToInsert.expires,
|
expires: inviteToInsert.expires,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
delete: adminProcedure.input(z.object({ tokenId: z.string() })).mutation(async ({ input }) => {
|
delete: adminProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => {
|
||||||
await db.delete(invites).where(eq(invites.id, input.tokenId));
|
await db.delete(invites).where(eq(invites.id, input.id));
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -4,6 +4,6 @@ import { env } from '~/env';
|
|||||||
|
|
||||||
import * as schema from './schema';
|
import * as schema from './schema';
|
||||||
|
|
||||||
const sqlite = new Database(env.DATABASE_URL?.replace('file:', ''));
|
export const sqlite = new Database(env.DATABASE_URL?.replace('file:', ''));
|
||||||
|
|
||||||
export const db = drizzle(sqlite, { schema });
|
export const db = drizzle(sqlite, { schema });
|
||||||
|
|||||||
Reference in New Issue
Block a user