From e4dc585fe5bfbc343e07dc110438c5f0c898777c Mon Sep 17 00:00:00 2001 From: Ravinou Date: Sat, 29 Mar 2025 10:34:28 +0100 Subject: [PATCH] =?UTF-8?q?test:=20=E2=9C=85=20add=20repo=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/api/repo/add.ts | 24 +-- tests/supertest/add.test.ts | 201 +++++++++++++++++++++ tests/supertest/getAppriseAlert.test.ts | 4 + tests/supertest/getAppriseMode.test.ts | 4 + tests/supertest/getAppriseServices.test.ts | 4 + tests/supertest/getEmailAlert.test.ts | 4 + 6 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 tests/supertest/add.test.ts diff --git a/pages/api/repo/add.ts b/pages/api/repo/add.ts index 3e1c8eb..400f788 100644 --- a/pages/api/repo/add.ts +++ b/pages/api/repo/add.ts @@ -64,18 +64,18 @@ export default async function handler( const newRepo: Repository = { id: repoList.length > 0 ? Math.max(...repoList.map((repo) => repo.id)) + 1 : 0, - alias: alias!, + alias: alias, repositoryName: '', status: false, lastSave: 0, lastStatusAlertSend: getUnixTime(new Date()), - alert: alert!, - storageSize: storageSize!, + alert: alert ?? 0, + storageSize: storageSize, storageUsed: 0, - sshPublicKey: sshPublicKey!, - comment: comment!, - lanCommand: lanCommand!, - appendOnlyMode: appendOnlyMode!, + sshPublicKey: sshPublicKey, + comment: comment ?? '', + lanCommand: lanCommand ?? false, + appendOnlyMode: appendOnlyMode ?? false, }; const { stdout, stderr } = await createRepoShell( @@ -101,6 +101,7 @@ export default async function handler( const validateRequestBody = (req: NextApiRequest) => { const { alias, sshPublicKey, storageSize, comment, alert, lanCommand, appendOnlyMode } = req.body; + // Required fields if (!alias || typeof alias !== 'string') { throw new Error('Alias must be a non-empty string'); } @@ -110,16 +111,17 @@ const validateRequestBody = (req: NextApiRequest) => { if (typeof storageSize !== 'number' || storageSize <= 0 || !Number.isInteger(storageSize)) { throw new Error('Storage Size must be a positive integer'); } - if (typeof comment !== 'string') { + // Optional fields + if (comment != undefined && typeof comment !== 'string') { throw new Error('Comment must be a string'); } - if (typeof alert !== 'number') { + if (alert != undefined && typeof alert !== 'number') { throw new Error('Alert must be a number'); } - if (typeof lanCommand !== 'boolean') { + if (lanCommand != undefined && typeof lanCommand !== 'boolean') { throw new Error('Lan Command must be a boolean'); } - if (typeof appendOnlyMode !== 'boolean') { + if (appendOnlyMode != undefined && typeof appendOnlyMode !== 'boolean') { throw new Error('Append Only Mode must be a boolean'); } }; diff --git a/tests/supertest/add.test.ts b/tests/supertest/add.test.ts new file mode 100644 index 0000000..23b3918 --- /dev/null +++ b/tests/supertest/add.test.ts @@ -0,0 +1,201 @@ +import { createMocks } from 'node-mocks-http'; +import handler from '~/pages/api/repo/add'; +import { getServerSession } from 'next-auth/next'; +import { + getRepoList, + updateRepoList, + tokenController, + isSshPubKeyDuplicate, +} from '~/helpers/functions'; +import { createRepoShell } from '~/helpers/functions/shell.utils'; + +jest.mock('next-auth', () => { + return jest.fn(() => { + return { + auth: { session: {} }, + GET: jest.fn(), + POST: jest.fn(), + }; + }); +}); + +jest.mock('next-auth/next', () => ({ + getServerSession: jest.fn(), +})); + +jest.mock('~/helpers/functions', () => ({ + getRepoList: jest.fn(), + updateRepoList: jest.fn(), + tokenController: jest.fn(), + isSshPubKeyDuplicate: jest.fn(), +})); + +jest.mock('~/helpers/functions/shell.utils', () => ({ + createRepoShell: jest.fn(), +})); + +describe('POST /api/repo/add', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + jest.resetAllMocks(); + jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + + it('should return 405 if method is not POST', async () => { + const { req, res } = createMocks({ method: 'GET' }); + await handler(req, res); + expect(res._getStatusCode()).toBe(405); + }); + + it('should return 401 if no session or authorization header is provided', async () => { + const { req, res } = createMocks({ method: 'POST' }); + await handler(req, res); + expect(res._getStatusCode()).toBe(401); + }); + + it('should return 401 if API key is invalid', async () => { + (getServerSession as jest.Mock).mockResolvedValue(null); + (tokenController as jest.Mock).mockResolvedValue(null); + const { req, res } = createMocks({ + method: 'POST', + headers: { authorization: 'Bearer INVALID_API_KEY' }, + }); + await handler(req, res); + expect(res._getStatusCode()).toBe(401); + }); + + it('should return 403 if API key does not have create permissions', async () => { + (getServerSession as jest.Mock).mockResolvedValue(null); + (tokenController as jest.Mock).mockResolvedValue({ create: false }); + const { req, res } = createMocks({ + method: 'POST', + headers: { authorization: 'Bearer API_KEY' }, + }); + await handler(req, res); + expect(res._getStatusCode()).toBe(403); + }); + + it('should return 409 if SSH key is duplicated', async () => { + (getServerSession as jest.Mock).mockResolvedValue({ user: { name: 'USER' } }); + (getRepoList as jest.Mock).mockResolvedValue([{ id: 1, sshPublicKey: 'duplicate-key' }]); + (isSshPubKeyDuplicate as jest.Mock).mockReturnValue(true); + const { req, res } = createMocks({ + method: 'POST', + body: { alias: 'repo1', sshPublicKey: 'duplicate-key', storageSize: 10 }, + }); + await handler(req, res); + expect(res._getStatusCode()).toBe(409); + }); + + it('should return 500 if createRepoShell fails', async () => { + (getServerSession as jest.Mock).mockResolvedValue({ user: { name: 'USER' } }); + (getRepoList as jest.Mock).mockResolvedValue([]); + (createRepoShell as jest.Mock).mockResolvedValue({ stderr: 'Error' }); + const { req, res } = createMocks({ + method: 'POST', + body: { alias: 'repo1', sshPublicKey: 'valid-key', storageSize: 10 }, + }); + await handler(req, res); + expect(res._getStatusCode()).toBe(500); + }); + + it('should successfully create a repository with a session', async () => { + (getServerSession as jest.Mock).mockResolvedValue({ user: { name: 'USER' } }); + (getRepoList as jest.Mock).mockResolvedValue([]); + (createRepoShell as jest.Mock).mockResolvedValue({ stdout: 'new-repo' }); + (updateRepoList as jest.Mock).mockResolvedValue(true); + const { req, res } = createMocks({ + method: 'POST', + body: { alias: 'repo1', sshPublicKey: 'valid-key', storageSize: 10 }, + }); + await handler(req, res); + expect(res._getStatusCode()).toBe(200); + expect(res._getJSONData()).toEqual({ id: 0, repositoryName: 'new-repo' }); + }); + + it('should add missing optional properties with default values and update repo list correctly', async () => { + (getServerSession as jest.Mock).mockResolvedValue({ user: { name: 'USER' } }); + (getRepoList as jest.Mock).mockResolvedValue([]); + (createRepoShell as jest.Mock).mockResolvedValue({ stdout: 'new-repo' }); + (updateRepoList as jest.Mock).mockResolvedValue(true); + + const { req, res } = createMocks({ + method: 'POST', + body: { alias: 'repo1', sshPublicKey: 'valid-key', storageSize: 10 }, + }); + + await handler(req, res); + + expect(res._getStatusCode()).toBe(200); + expect(res._getJSONData()).toEqual({ id: 0, repositoryName: 'new-repo' }); + + expect(updateRepoList).toHaveBeenCalledWith( + [ + { + id: 0, + alias: 'repo1', + repositoryName: 'new-repo', + status: false, + lastSave: 0, + lastStatusAlertSend: expect.any(Number), + alert: 0, + storageSize: 10, + storageUsed: 0, + sshPublicKey: 'valid-key', + comment: '', + lanCommand: false, + appendOnlyMode: false, + }, + ], + true + ); + }); + + it('should assign the correct ID based on existing repositories', async () => { + (getServerSession as jest.Mock).mockResolvedValue({ user: { name: 'USER' } }); + + (getRepoList as jest.Mock).mockResolvedValue([ + { id: 0, alias: 'repo0', sshPublicKey: 'key0', storageSize: 10 }, + { id: 1, alias: 'repo1', sshPublicKey: 'key1', storageSize: 20 }, + { id: 3, alias: 'repo3', sshPublicKey: 'key3', storageSize: 30 }, + ]); + + (createRepoShell as jest.Mock).mockResolvedValue({ stdout: 'new-repo' }); + (updateRepoList as jest.Mock).mockResolvedValue(true); + + const { req, res } = createMocks({ + method: 'POST', + body: { alias: 'repo-new', sshPublicKey: 'new-key', storageSize: 50 }, + }); + + await handler(req, res); + + expect(res._getStatusCode()).toBe(200); + expect(res._getJSONData()).toEqual({ id: 4, repositoryName: 'new-repo' }); + + expect(updateRepoList).toHaveBeenCalledWith( + [ + { id: 0, alias: 'repo0', sshPublicKey: 'key0', storageSize: 10 }, + { id: 1, alias: 'repo1', sshPublicKey: 'key1', storageSize: 20 }, + { id: 3, alias: 'repo3', sshPublicKey: 'key3', storageSize: 30 }, + { + id: 4, + alias: 'repo-new', + repositoryName: 'new-repo', + status: false, + lastSave: 0, + lastStatusAlertSend: expect.any(Number), + alert: 0, + storageSize: 50, + storageUsed: 0, + sshPublicKey: 'new-key', + comment: '', + lanCommand: false, + appendOnlyMode: false, + }, + ], + true + ); + }); +}); diff --git a/tests/supertest/getAppriseAlert.test.ts b/tests/supertest/getAppriseAlert.test.ts index 720f5b3..66721ec 100644 --- a/tests/supertest/getAppriseAlert.test.ts +++ b/tests/supertest/getAppriseAlert.test.ts @@ -12,6 +12,10 @@ jest.mock('fs', () => ({ })); describe('Get Apprise Alert API', () => { + beforeEach(() => { + jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + it('should return 405 if the method is not GET', async () => { const { req, res } = createMocks({ method: 'POST' }); await handler(req, res); diff --git a/tests/supertest/getAppriseMode.test.ts b/tests/supertest/getAppriseMode.test.ts index 368490e..b883077 100644 --- a/tests/supertest/getAppriseMode.test.ts +++ b/tests/supertest/getAppriseMode.test.ts @@ -12,6 +12,10 @@ jest.mock('fs', () => ({ })); describe('Get Apprise Mode API', () => { + beforeEach(() => { + jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + it('should return 405 if the method is not GET', async () => { const { req, res } = createMocks({ method: 'POST' }); await handler(req, res); diff --git a/tests/supertest/getAppriseServices.test.ts b/tests/supertest/getAppriseServices.test.ts index b277e04..bf92f63 100644 --- a/tests/supertest/getAppriseServices.test.ts +++ b/tests/supertest/getAppriseServices.test.ts @@ -12,6 +12,10 @@ jest.mock('fs', () => ({ })); describe('Get Apprise Services API', () => { + beforeEach(() => { + jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + it('should return 405 if the method is not GET', async () => { const { req, res } = createMocks({ method: 'POST' }); await handler(req, res); diff --git a/tests/supertest/getEmailAlert.test.ts b/tests/supertest/getEmailAlert.test.ts index 168a1da..392db76 100644 --- a/tests/supertest/getEmailAlert.test.ts +++ b/tests/supertest/getEmailAlert.test.ts @@ -12,6 +12,10 @@ jest.mock('fs', () => ({ })); describe('Get Email Alert API', () => { + beforeEach(() => { + jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + it('should return 405 if the method is not GET', async () => { const { req, res } = createMocks({ method: 'POST' }); await handler(req, res);