enforce minimum 8 character slug on create/update. Closes #4376

This commit is contained in:
Francis Cao committed 2026-07-04 08:44:23 -07:00
1 parent 735d85c7af
commit a4a0167515
4 files changed
+138 -2

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
import { beforeEach, expect, test, vi } from 'vitest';
import { parseRequest } from '@/lib/request';
import { canCreateTeamWebsite, canCreateWebsite } from '@/permissions';
import { createLink } from '@/queries/prisma';
import { POST } from './route';
vi.mock('@/lib/request', () => ({
getQueryFilters: vi.fn(),
parseRequest: vi.fn(),
}));
vi.mock('@/permissions', () => ({
canCreateTeamWebsite: vi.fn(),
canCreateWebsite: vi.fn(),
}));
vi.mock('@/queries/prisma', () => ({
createLink: vi.fn(),
getUserLinks: vi.fn(),
}));
const parseRequestMock = vi.mocked(parseRequest);
const canCreateTeamWebsiteMock = vi.mocked(canCreateTeamWebsite);
const canCreateWebsiteMock = vi.mocked(canCreateWebsite);
const createLinkMock = vi.mocked(createLink);
beforeEach(() => {
parseRequestMock.mockReset();
canCreateTeamWebsiteMock.mockReset();
canCreateWebsiteMock.mockReset();
createLinkMock.mockReset();
});
test('POST requires link slugs to be at least 8 characters so create matches edit validation', async () => {
parseRequestMock.mockResolvedValue({
auth: {
user: {
id: 'user-1',
},
},
body: {
name: 'Docs',
url: 'https://example.com',
slug: 'abcdefgh',
},
error: undefined,
});
canCreateWebsiteMock.mockResolvedValue(true);
createLinkMock.mockResolvedValue({ id: 'link-1' } as any);
const response = await POST(new Request('http://localhost/api/links', { method: 'POST' }));
const schema = parseRequestMock.mock.calls[0][1] as {
safeParse: (value: unknown) => { success: boolean };
};
expect(schema.safeParse({ name: 'Docs', url: 'https://example.com', slug: '1234567' }).success).toBe(
false,
);
expect(schema.safeParse({ name: 'Docs', url: 'https://example.com', slug: '12345678' }).success).toBe(
true,
);
expect(createLinkMock).toHaveBeenCalledWith({
id: expect.any(String),
name: 'Docs',
url: 'https://example.com',
slug: 'abcdefgh',
teamId: undefined,
userId: 'user-1',
});
expect(response.status).toBe(200);
});
+1 -1
View File
@@ -30,7 +30,7 @@ export async function POST(request: Request) {
const schema = z.object({
name: z.string().max(100),
url: z.string().max(500),
slug: z.string().max(100),
slug: z.string().min(8).max(100),
teamId: z.string().nullable().optional(),
id: z.uuid().nullable().optional(),
});
+65
View File
@@ -0,0 +1,65 @@
import { beforeEach, expect, test, vi } from 'vitest';
import { parseRequest } from '@/lib/request';
import { canCreateTeamWebsite, canCreateWebsite } from '@/permissions';
import { createPixel } from '@/queries/prisma';
import { POST } from './route';
vi.mock('@/lib/request', () => ({
getQueryFilters: vi.fn(),
parseRequest: vi.fn(),
}));
vi.mock('@/permissions', () => ({
canCreateTeamWebsite: vi.fn(),
canCreateWebsite: vi.fn(),
}));
vi.mock('@/queries/prisma', () => ({
createPixel: vi.fn(),
getUserPixels: vi.fn(),
}));
const parseRequestMock = vi.mocked(parseRequest);
const canCreateTeamWebsiteMock = vi.mocked(canCreateTeamWebsite);
const canCreateWebsiteMock = vi.mocked(canCreateWebsite);
const createPixelMock = vi.mocked(createPixel);
beforeEach(() => {
parseRequestMock.mockReset();
canCreateTeamWebsiteMock.mockReset();
canCreateWebsiteMock.mockReset();
createPixelMock.mockReset();
});
test('POST requires pixel slugs to be at least 8 characters so create matches edit validation', async () => {
parseRequestMock.mockResolvedValue({
auth: {
user: {
id: 'user-1',
},
},
body: {
name: 'Pixel',
slug: 'abcdefgh',
},
error: undefined,
});
canCreateWebsiteMock.mockResolvedValue(true);
createPixelMock.mockResolvedValue({ id: 'pixel-1' } as any);
const response = await POST(new Request('http://localhost/api/pixels', { method: 'POST' }));
const schema = parseRequestMock.mock.calls[0][1] as {
safeParse: (value: unknown) => { success: boolean };
};
expect(schema.safeParse({ name: 'Pixel', slug: '1234567' }).success).toBe(false);
expect(schema.safeParse({ name: 'Pixel', slug: '12345678' }).success).toBe(true);
expect(createPixelMock).toHaveBeenCalledWith({
id: expect.any(String),
name: 'Pixel',
slug: 'abcdefgh',
teamId: undefined,
userId: 'user-1',
});
expect(response.status).toBe(200);
});
+1 -1
View File
@@ -29,7 +29,7 @@ export async function GET(request: Request) {
export async function POST(request: Request) {
const schema = z.object({
name: z.string().max(100),
slug: z.string().max(100),
slug: z.string().min(8).max(100),
teamId: z.string().nullable().optional(),
id: z.uuid().nullable().optional(),
});