109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
import { NextRequest } from 'next/server';
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
cancelJob: vi.fn(),
|
|
deleteClassroom: vi.fn(),
|
|
deleteJob: vi.fn(),
|
|
isPublishedSource: vi.fn(),
|
|
readJob: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('@/lib/server/classroom-job-store', () => ({
|
|
deleteClassroomGenerationJob: mocks.deleteJob,
|
|
isValidClassroomJobId: (value: string) => /^[a-zA-Z0-9_-]+$/.test(value),
|
|
markClassroomGenerationJobCancelled: vi.fn(),
|
|
readClassroomGenerationJob: mocks.readJob,
|
|
}));
|
|
|
|
vi.mock('@/lib/server/classroom-job-runner', () => ({
|
|
cancelClassroomGenerationJob: mocks.cancelJob,
|
|
}));
|
|
|
|
vi.mock('@/lib/server/classroom-storage', () => ({
|
|
buildRequestOrigin: () => 'https://server.example',
|
|
deleteClassroom: mocks.deleteClassroom,
|
|
}));
|
|
|
|
vi.mock('@/lib/server/published-classroom-access', () => ({
|
|
isPublishedClassroomSource: mocks.isPublishedSource,
|
|
}));
|
|
|
|
describe('DELETE /api/generate-classroom/[jobId]', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mocks.readJob.mockResolvedValue({
|
|
id: 'job-1',
|
|
status: 'succeeded',
|
|
result: { classroomId: 'classroom-1', url: '/classroom/classroom-1', scenesCount: 2 },
|
|
});
|
|
mocks.deleteClassroom.mockResolvedValue(true);
|
|
mocks.deleteJob.mockResolvedValue(true);
|
|
});
|
|
|
|
test('preserves both job and classroom when the source has been published', async () => {
|
|
mocks.isPublishedSource.mockResolvedValue(true);
|
|
const { DELETE } = await import('@/app/api/generate-classroom/[jobId]/route');
|
|
|
|
const response = await DELETE(
|
|
new NextRequest('https://server.example/api/generate-classroom/job-1', {
|
|
method: 'DELETE',
|
|
}),
|
|
{ params: Promise.resolve({ jobId: 'job-1' }) },
|
|
);
|
|
|
|
expect(response.status).toBe(409);
|
|
expect(mocks.cancelJob).not.toHaveBeenCalled();
|
|
expect(mocks.deleteClassroom).not.toHaveBeenCalled();
|
|
expect(mocks.deleteJob).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('keeps the existing delete behavior for an unpublished source', async () => {
|
|
mocks.isPublishedSource.mockResolvedValue(false);
|
|
const { DELETE } = await import('@/app/api/generate-classroom/[jobId]/route');
|
|
|
|
const response = await DELETE(
|
|
new NextRequest('https://server.example/api/generate-classroom/job-1', {
|
|
method: 'DELETE',
|
|
}),
|
|
{ params: Promise.resolve({ jobId: 'job-1' }) },
|
|
);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(mocks.cancelJob).toHaveBeenCalledWith('job-1');
|
|
expect(mocks.deleteClassroom).toHaveBeenCalledWith('classroom-1');
|
|
expect(mocks.deleteJob).toHaveBeenCalledWith('job-1');
|
|
});
|
|
|
|
test('returns 409 without deleting while the classroom source is being published', async () => {
|
|
mocks.isPublishedSource.mockResolvedValue(false);
|
|
const { runClassroomSourcesPublishExclusive } =
|
|
await import('@/lib/course-framework/publish-state');
|
|
let finishPublish!: () => void;
|
|
const publishing = runClassroomSourcesPublishExclusive(
|
|
['classroom-1'],
|
|
() =>
|
|
new Promise<void>((resolve) => {
|
|
finishPublish = resolve;
|
|
}),
|
|
);
|
|
const { DELETE } = await import('@/app/api/generate-classroom/[jobId]/route');
|
|
|
|
const response = await DELETE(
|
|
new NextRequest('https://server.example/api/generate-classroom/job-1', {
|
|
method: 'DELETE',
|
|
}),
|
|
{ params: Promise.resolve({ jobId: 'job-1' }) },
|
|
);
|
|
|
|
expect(response.status).toBe(409);
|
|
expect(mocks.isPublishedSource).not.toHaveBeenCalled();
|
|
expect(mocks.cancelJob).not.toHaveBeenCalled();
|
|
expect(mocks.deleteClassroom).not.toHaveBeenCalled();
|
|
expect(mocks.deleteJob).not.toHaveBeenCalled();
|
|
|
|
finishPublish();
|
|
await publishing;
|
|
});
|
|
});
|