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
|
||||
onClick={async () => {
|
||||
await deleteAsync({
|
||||
tokenId: innerProps.tokenId,
|
||||
id: innerProps.tokenId,
|
||||
});
|
||||
}}
|
||||
disabled={isLoading}
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { createTRPCRouter } from '~/server/api/trpc';
|
||||
|
||||
import { appRouter } from './routers/app';
|
||||
import { boardRouter } from './routers/board';
|
||||
import { calendarRouter } from './routers/calendar';
|
||||
import { configRouter } from './routers/config';
|
||||
import { dashDotRouter } from './routers/dash-dot';
|
||||
import { dnsHoleRouter } from './routers/dns-hole/router';
|
||||
import { dockerRouter } from './routers/docker/router';
|
||||
import { downloadRouter } from './routers/download';
|
||||
import { iconRouter } from './routers/icon';
|
||||
import { inviteRouter } from './routers/invite';
|
||||
import { inviteRouter } from './routers/invite/invite-router';
|
||||
import { mediaRequestsRouter } from './routers/media-request';
|
||||
import { mediaServerRouter } from './routers/media-server';
|
||||
import { notebookRouter } from './routers/notebook';
|
||||
import { overseerrRouter } from './routers/overseerr';
|
||||
import { passwordRouter } from './routers/password';
|
||||
import { rssRouter } from './routers/rss';
|
||||
import { timezoneRouter } from './routers/timezone';
|
||||
import { usenetRouter } from './routers/usenet/router';
|
||||
import { userRouter } from './routers/user';
|
||||
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.
|
||||
@@ -45,7 +46,7 @@ export const rootRouter = createTRPCRouter({
|
||||
invites: inviteRouter,
|
||||
boards: boardRouter,
|
||||
password: passwordRouter,
|
||||
notebook: notebookRouter
|
||||
notebook: notebookRouter,
|
||||
});
|
||||
|
||||
// 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 { invites } from '~/server/db/schema';
|
||||
|
||||
import { adminProcedure, createTRPCRouter, publicProcedure } from '../trpc';
|
||||
import { adminProcedure, createTRPCRouter, publicProcedure } from '../../trpc';
|
||||
|
||||
export const inviteRouter = createTRPCRouter({
|
||||
all: adminProcedure
|
||||
.input(
|
||||
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),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const limit = input.limit ?? 50;
|
||||
const limit = input.limit;
|
||||
const dbInvites = await db.query.invites.findMany({
|
||||
limit: limit,
|
||||
offset: limit * input.page,
|
||||
@@ -67,7 +67,7 @@ export const inviteRouter = createTRPCRouter({
|
||||
expires: inviteToInsert.expires,
|
||||
};
|
||||
}),
|
||||
delete: adminProcedure.input(z.object({ tokenId: z.string() })).mutation(async ({ input }) => {
|
||||
await db.delete(invites).where(eq(invites.id, input.tokenId));
|
||||
delete: adminProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => {
|
||||
await db.delete(invites).where(eq(invites.id, input.id));
|
||||
}),
|
||||
});
|
||||
@@ -4,6 +4,6 @@ import { env } from '~/env';
|
||||
|
||||
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 });
|
||||
|
||||
Reference in New Issue
Block a user