82 lines
2.9 KiB
TypeScript
82 lines
2.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
vi.mock('@/lib/admin/auth', () => ({
|
|
isAdminEnabled: vi.fn(() => true),
|
|
isAdminAuthenticated: vi.fn(async () => true),
|
|
COOKIE_NAME: 'folio_admin_session',
|
|
}));
|
|
|
|
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }));
|
|
|
|
// The route imports from 'fs' directly; atomicWrite (used for the actual
|
|
// write) imports from 'fs/promises'. Both need the same mock functions so a
|
|
// test can see calls made through either.
|
|
const fsMock = vi.hoisted(() => ({
|
|
mkdir: vi.fn(async () => undefined),
|
|
access: vi.fn(async () => undefined),
|
|
copyFile: vi.fn(async () => undefined),
|
|
readdir: vi.fn(async () => []),
|
|
unlink: vi.fn(async () => undefined),
|
|
writeFile: vi.fn(async (..._args: unknown[]) => undefined),
|
|
rename: vi.fn(async () => undefined),
|
|
readFile: vi.fn(async (..._args: unknown[]): Promise<Buffer> => {
|
|
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
|
|
}),
|
|
}));
|
|
vi.mock('fs', () => ({ promises: fsMock }));
|
|
vi.mock('fs/promises', () => ({ default: fsMock, ...fsMock }));
|
|
|
|
const fs = fsMock;
|
|
import { PUT } from '../about/route';
|
|
|
|
const put = (body: unknown) =>
|
|
PUT(
|
|
new Request('http://localhost/api/admin/about', {
|
|
method: 'PUT',
|
|
body: JSON.stringify(body),
|
|
}),
|
|
);
|
|
|
|
/**
|
|
* "No file yet" and "the backup could not be written" used to share one
|
|
* catch, so a `.backups/` this save couldn't write to looked exactly like a
|
|
* brand-new file and the save went ahead with no snapshot (#630).
|
|
*/
|
|
describe('PUT /api/admin/about backup handling', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
fs.readFile.mockImplementation(async () => {
|
|
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
|
|
});
|
|
});
|
|
|
|
it('saves a brand-new file with no backup', async () => {
|
|
const res = await put({ meta: { name: 'Me' }, body: 'Hello' });
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(fs.copyFile).not.toHaveBeenCalled();
|
|
expect(fs.writeFile).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('aborts the save when the file exists but the backup copy fails', async () => {
|
|
vi.mocked(fs.readFile).mockResolvedValue(Buffer.from('---\nname: Old\n---\n'));
|
|
vi.mocked(fs.copyFile).mockRejectedValueOnce(
|
|
Object.assign(new Error('EACCES'), { code: 'EACCES' }),
|
|
);
|
|
|
|
await expect(put({ meta: { name: 'Me' }, body: 'Hello' })).rejects.toThrow('EACCES');
|
|
expect(fs.writeFile).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('takes no backup for a save that changes nothing (QA A-19)', async () => {
|
|
await put({ meta: { name: 'Me' }, body: 'Hello' });
|
|
const written = vi.mocked(fs.writeFile).mock.calls[0][1] as string;
|
|
vi.mocked(fs.writeFile).mockClear();
|
|
|
|
vi.mocked(fs.readFile).mockResolvedValue(Buffer.from(written));
|
|
const res = await put({ meta: { name: 'Me' }, body: 'Hello' });
|
|
expect(res.status).toBe(200);
|
|
expect(fs.copyFile).not.toHaveBeenCalled();
|
|
});
|
|
});
|