test: migration from Jest to Vitest

This commit is contained in:
Ravinou 2025-04-06 15:35:37 +02:00
commit 52d8bca2ad
No known key found for this signature in database
GPG key ID: EEEE670C40F6A4D7
39 changed files with 4238 additions and 6479 deletions

View file

@ -0,0 +1,96 @@
import { getServerSession } from 'next-auth/next';
import { createMocks } from 'node-mocks-http';
import { getUsersList } from '~/services';
import handler from '~/pages/api/account/getAppriseAlert';
vi.mock('next-auth/next');
vi.mock('~/services', () => ({
getUsersList: vi.fn(),
}));
describe('Get Apprise Alert API', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.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);
expect(res._getStatusCode()).toBe(405);
});
it('should return 401 if the user is not authenticated', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
expect(res._getJSONData()).toEqual({ message: 'You must be logged in.' });
});
it('should return 400 if the user does not exist', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'nonexistent' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseAlert: true,
},
]);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
expect(res._getJSONData()).toEqual({
message: 'User is incorrect. Please, logout to update your session.',
});
});
it('should return appriseAlert value if the user exists', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseAlert: true,
},
]);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({ appriseAlert: true });
});
it('should return 500 if there is an error reading the file', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockImplementation(() => {
throw new Error();
});
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
expect(res._getJSONData()).toEqual({
status: 500,
message: 'API error, contact the administrator',
});
});
});

View file

@ -0,0 +1,102 @@
import { getServerSession } from 'next-auth/next';
import { createMocks } from 'node-mocks-http';
import { getUsersList } from '~/services';
import handler from '~/pages/api/account/getAppriseMode';
import { AppriseModeEnum } from '~/types/domain/config.types';
vi.mock('next-auth/next');
vi.mock('~/services', () => ({
getUsersList: vi.fn(),
}));
describe('Get Apprise Mode API', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.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);
expect(res._getStatusCode()).toBe(405);
});
it('should return 401 if the user is not authenticated', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
expect(res._getJSONData()).toEqual({ message: 'You must be logged in.' });
});
it('should return 400 if the user does not exist', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'nonexistent' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseMode: AppriseModeEnum.STATELESS,
appriseStatelessURL: 'https://example.com',
},
]);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
expect(res._getJSONData()).toEqual({
message: 'User is incorrect. Please, logout to update your session.',
});
});
it('should return appriseMode and appriseStatelessURL if the user exists', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseMode: AppriseModeEnum.STATELESS,
appriseStatelessURL: 'https://example.com',
},
]);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({
appriseMode: 'stateless',
appriseStatelessURL: 'https://example.com',
});
});
it('should return 500 if there is an error reading the file', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockImplementation(() => {
throw new Error();
});
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
expect(res._getJSONData()).toEqual({
status: 500,
message: 'API error, contact the administrator',
});
});
});

View file

@ -0,0 +1,99 @@
import { getServerSession } from 'next-auth/next';
import { createMocks } from 'node-mocks-http';
import { getUsersList } from '~/services';
import handler from '~/pages/api/account/getAppriseServices';
vi.mock('next-auth/next');
vi.mock('~/services', () => ({
getUsersList: vi.fn(),
}));
describe('Get Apprise Services API', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.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);
expect(res._getStatusCode()).toBe(405);
});
it('should return 401 if the user is not authenticated', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
expect(res._getJSONData()).toEqual({ message: 'You must be logged in.' });
});
it('should return 400 if the user does not exist', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'nonexistent' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseServices: ['service1', 'service2'],
},
]);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
expect(res._getJSONData()).toEqual({
message: 'User is incorrect. Please, logout to update your session.',
});
});
it('should return appriseServices if the user exists', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseServices: ['service1', 'service2'],
},
]);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({
appriseServices: ['service1', 'service2'],
});
});
it('should return 500 if there is an error reading the file', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockImplementation(() => {
throw new Error();
});
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
expect(res._getJSONData()).toEqual({
status: 500,
message: 'API error, contact the administrator',
});
});
});

View file

@ -0,0 +1,99 @@
import { getServerSession } from 'next-auth/next';
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/account/getEmailAlert';
import { getUsersList } from '~/services';
vi.mock('next-auth/next');
vi.mock('~/services', () => ({
getUsersList: vi.fn(),
}));
describe('Get Email Alert API', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.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);
expect(res._getStatusCode()).toBe(405);
});
it('should return 401 if the user is not authenticated', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
expect(res._getJSONData()).toEqual({ message: 'You must be logged in.' });
});
it('should return 400 if the user does not exist', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'nonexistent' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
email: 'testuser@example.com',
password: 'hashedpassword',
roles: ['user'],
emailAlert: true,
},
]);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
expect(res._getJSONData()).toEqual({
message: 'User is incorrect. Please, logout to update your session.',
});
});
it('should return emailAlert if the user exists', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
email: 'testuser@example.com',
password: 'hashedpassword',
roles: ['user'],
emailAlert: true,
},
]);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({
emailAlert: true,
});
});
it('should return 500 if there is an error reading the file', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockImplementation(() => {
throw new Error();
});
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
expect(res._getJSONData()).toEqual({
status: 500,
message: 'API error, contact the administrator',
});
});
});

View file

@ -0,0 +1,48 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/account/getWizardEnv';
import { getServerSession } from 'next-auth/next';
vi.mock('next-auth/next');
describe('Get Wizard Env API', () => {
it('should return 405 if the method is not GET', async () => {
const { req, res } = createMocks({ method: 'POST' });
await handler(req, res);
expect(res._getStatusCode()).toBe(405);
});
it('should return 401 if the user is not authenticated', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 200 with wizardEnv if the user is authenticated', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'testuser' } });
process.env.UNIX_USER = 'borgwarehouse';
process.env.FQDN = 'localhost';
process.env.SSH_SERVER_PORT = '22';
process.env.HIDE_SSH_PORT = 'false';
process.env.DISABLE_INTEGRATIONS = 'false';
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({
UNIX_USER: 'borgwarehouse',
FQDN: 'localhost',
SSH_SERVER_PORT: '22',
FQDN_LAN: '',
SSH_SERVER_PORT_LAN: '',
SSH_SERVER_FINGERPRINT_RSA: '',
SSH_SERVER_FINGERPRINT_ED25519: '',
SSH_SERVER_FINGERPRINT_ECDSA: '',
HIDE_SSH_PORT: 'false',
DISABLE_INTEGRATIONS: 'false',
DISABLE_DELETE_REPO: 'false',
});
});
});

View file

@ -0,0 +1,48 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/account/sendTestEmail';
import { getServerSession } from 'next-auth/next';
vi.mock('next-auth/next', () => ({
__esModule: true,
getServerSession: vi.fn(),
}));
vi.mock('~/helpers/functions/nodemailerSMTP', () => ({
__esModule: true,
default: vi.fn(() => ({
sendMail: vi.fn().mockResolvedValue({ messageId: 'fake-message-id' }),
})),
}));
describe('Email API', () => {
beforeEach(() => {
vi.spyOn(console, 'log').mockImplementation(() => {});
});
it('should return 401 if not authenticated', async () => {
// Mock unauthenticated session
vi.mocked(getServerSession).mockResolvedValue(null);
// Simulate a POST request
const { req, res } = createMocks({ method: 'POST' });
await handler(req, res);
// Expect 401 unauthorized
expect(res._getStatusCode()).toBe(401);
});
it('should send an email if authenticated', async () => {
// Mock unauthenticated session
vi.mocked(getServerSession).mockResolvedValue({
user: { email: 'ada-lovelace@example.com', name: 'Lovelace' },
});
// Simulate a POST request
const { req, res } = createMocks({ method: 'POST' });
await handler(req, res);
// Expect 200 and a success message
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({ message: 'Mail successfully sent' });
});
});

View file

@ -0,0 +1,208 @@
import handler from '~/pages/api/account/tokenManager';
import { createMocks } from 'node-mocks-http';
import { getServerSession } from 'next-auth/next';
import { getUsersList, updateUsersList } from '~/services';
import ApiResponse from '~/helpers/functions/apiResponse';
vi.mock('next-auth/next', () => ({
__esModule: true,
getServerSession: vi.fn(),
}));
vi.mock('~/services', () => ({
getUsersList: vi.fn(),
updateUsersList: vi.fn(),
}));
vi.mock('~/helpers/functions/apiResponse');
describe('Token Manager API', () => {
afterEach(() => {
vi.clearAllMocks();
vi.spyOn(console, 'log').mockImplementation(() => {});
});
it('should return unauthorized if session is not found', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
const { req, res } = createMocks({
method: 'POST',
body: {
name: 'testToken',
permissions: { create: true, read: true, update: true, delete: true },
},
});
await handler(req, res);
expect(ApiResponse.unauthorized).toHaveBeenCalledWith(res);
});
it('should create a new token if valid data is provided', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'testUser' } });
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testUser',
password: 'hashedPassword',
email: 'testUser@example.com',
roles: ['user'],
tokens: [],
},
]);
vi.mocked(updateUsersList).mockResolvedValue();
const { req, res } = createMocks({
method: 'POST',
body: {
name: 'testToken',
permissions: { create: true, read: true, update: true, delete: true },
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
const responseData = JSON.parse(res._getData());
expect(responseData).toHaveProperty('token');
expect(updateUsersList).toHaveBeenCalled();
});
it('should return bad request if token name already exists', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'testUser' } });
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testUser',
password: 'hashedPassword',
email: 'testUser@example.com',
roles: ['user'],
tokens: [
{
token: 'sampleToken123',
name: 'testToken',
permissions: { create: true, read: true, update: true, delete: true },
creation: 123,
},
],
},
]);
const { req, res } = createMocks({
method: 'POST',
body: {
name: 'testToken',
permissions: { create: true, read: true, update: true, delete: true },
},
});
await handler(req, res);
expect(ApiResponse.badRequest).toHaveBeenCalledWith(res, 'Token name already exists');
});
it('should return token list for GET request', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'testUser' } });
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testUser',
password: 'hashedPassword',
email: 'testUser@example.com',
roles: ['user'],
tokens: [
{
token: 'sampleToken1',
name: 'token1',
permissions: { create: false, read: false, update: false, delete: false },
creation: 123,
},
{
token: 'sampleToken2',
name: 'token2',
permissions: { create: false, read: false, update: false, delete: false },
creation: 456,
},
],
},
]);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
const responseData = JSON.parse(res._getData());
expect(responseData).toEqual([
{
name: 'token1',
permissions: { create: false, read: false, update: false, delete: false },
creation: 123,
},
{
name: 'token2',
permissions: { create: false, read: false, update: false, delete: false },
creation: 456,
},
]);
});
it('should delete a token for DELETE request', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'testUser' } });
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testUser',
password: 'hashedPassword',
email: 'testUser@example.com',
roles: ['user'],
tokens: [
{
token: 'sampleToken1',
name: 'token1',
permissions: { create: false, read: false, update: false, delete: false },
creation: 123,
},
{
token: 'sampleToken2',
name: 'token2',
permissions: { create: false, read: false, update: false, delete: false },
creation: 456,
},
],
},
]);
vi.mocked(updateUsersList).mockResolvedValue();
const { req, res } = createMocks({
method: 'DELETE',
body: { name: 'token1' },
});
await handler(req, res);
expect(ApiResponse.success).toHaveBeenCalledWith(res, 'Token deleted');
expect(updateUsersList).toHaveBeenCalled();
});
it('should return bad request if token name is missing in DELETE request', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'testUser' } });
const { req, res } = createMocks({
method: 'DELETE',
body: {},
});
await handler(req, res);
expect(ApiResponse.badRequest).toHaveBeenCalledWith(res, 'Missing token name');
});
it('should return method not allowed for unsupported HTTP methods', async () => {
const { req, res } = createMocks({ method: 'PUT' });
await handler(req, res);
expect(ApiResponse.methodNotAllowed).toHaveBeenCalledWith(res);
});
});

View file

@ -3,7 +3,7 @@ import { getServerSession } from 'next-auth/next';
import { NextApiRequest, NextApiResponse } from 'next';
import { IntegrationTokenType, TokenPermissionsType } from '~/types/api/integration.types';
import ApiResponse from '~/helpers/functions/apiResponse';
import { getUsersList, updateUsersList } from '~/helpers/functions';
import { getUsersList, updateUsersList } from '~/services';
import { getUnixTime } from 'date-fns';
import { v4 as uuidv4 } from 'uuid';
import { BorgWarehouseApiResponse } from '~/types/api/error.types';

View file

@ -0,0 +1,116 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/account/updateAppriseAlert';
import { getServerSession } from 'next-auth/next';
import { getUsersList, updateUsersList } from '~/services';
vi.mock('next-auth/next');
vi.mock('~/services', () => ({
__esModule: true,
getUsersList: vi.fn(),
updateUsersList: vi.fn(),
}));
describe('Notifications API', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.resetAllMocks();
vi.spyOn(console, 'log').mockImplementation(() => {});
});
it('should return 405 if the method is not PUT', async () => {
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(405);
});
it('should return 401 if the user is not authenticated', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
const { req, res } = createMocks({ method: 'PUT', body: { appriseAlert: true } });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 422 if the request body is invalid', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
const { req, res } = createMocks({ method: 'PUT', body: { appriseAlert: 'not-boolean' } });
await handler(req, res);
expect(res._getStatusCode()).toBe(422);
expect(res._getJSONData()).toEqual({ message: 'Unexpected data' });
});
it('should return 400 if the user does not exist', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'nonexistent' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseAlert: false,
},
]);
const { req, res } = createMocks({ method: 'PUT', body: { appriseAlert: true } });
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
expect(res._getJSONData()).toEqual({
message: 'User is incorrect. Please, logout to update your session.',
});
});
it('should update appriseAlert and return 200 if everything is correct', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseAlert: false,
},
]);
const { req, res } = createMocks({ method: 'PUT', body: { appriseAlert: true } });
await handler(req, res);
expect(updateUsersList).toHaveBeenCalledWith([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseAlert: true,
},
]);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({ message: 'Successful API send' });
});
it('should return 500 if there is an error reading users file', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'testuser' },
});
vi.mocked(getUsersList).mockRejectedValue({ code: 'ENOENT' });
const { req, res } = createMocks({ method: 'PUT', body: { appriseAlert: true } });
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
expect(res._getJSONData()).toEqual({ status: 500, message: 'No such file or directory' });
});
});

View file

@ -0,0 +1,97 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/account/updateAppriseMode';
import { getServerSession } from 'next-auth/next';
import { getUsersList, updateUsersList } from '~/services';
import { AppriseModeEnum } from '~/types/domain/config.types';
vi.mock('next-auth/next');
vi.mock('~/services', () => ({
getUsersList: vi.fn(),
updateUsersList: vi.fn(),
}));
describe('Apprise Mode API', () => {
it('should return 405 if method is not PUT', async () => {
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(405);
});
it('should return 401 if not authenticated', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
const { req, res } = createMocks({ method: 'PUT' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 422 if invalid data is provided', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'testuser' } });
const { req, res } = createMocks({
method: 'PUT',
body: { appriseMode: 'invalid-mode', appriseStatelessURL: 'https://example.com' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(422);
});
it('should return 400 if user does not exist', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'unknownuser' } });
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseMode: AppriseModeEnum.PACKAGE,
},
]);
const { req, res } = createMocks({
method: 'PUT',
body: { appriseMode: 'stateless', appriseStatelessURL: 'https://example.com' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
});
it('should update user settings and return 200', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'testuser' } });
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseMode: AppriseModeEnum.PACKAGE,
},
]);
vi.mocked(updateUsersList).mockResolvedValue();
const { req, res } = createMocks({
method: 'PUT',
body: { appriseMode: 'stateless', appriseStatelessURL: 'https://example.com' },
});
await handler(req, res);
expect(updateUsersList).toHaveBeenCalledWith([
{
id: 1,
username: 'testuser',
password: 'hashedpassword',
roles: ['user'],
email: 'testuser@example.com',
appriseMode: AppriseModeEnum.STATELESS,
appriseStatelessURL: 'https://example.com',
},
]);
expect(res._getStatusCode()).toBe(200);
});
});

View file

@ -0,0 +1,131 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/account/updateAppriseServices';
import { getServerSession } from 'next-auth/next';
import { getUsersList, updateUsersList } from '~/services';
vi.mock('next-auth/next');
vi.mock('~/services', () => ({
getUsersList: vi.fn(),
updateUsersList: vi.fn(),
}));
describe('PUT /api/account/updateAppriseURLs', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.resetAllMocks();
vi.spyOn(console, 'log').mockImplementation(() => {});
});
it('should return 401 if not authenticated', async () => {
// Mock unauthenticated session
vi.mocked(getServerSession).mockResolvedValue(null);
const { req, res } = createMocks({ method: 'PUT' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 405 if method is not PUT', async () => {
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(405);
});
it('should return 400 if user is not found in the users list', async () => {
// Mock authenticated session
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'Lovelace' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'Ada',
password: 'securepassword',
roles: ['user'],
email: 'ada@example.com',
},
]);
const { req, res } = createMocks({
method: 'PUT',
body: { appriseURLs: 'http://example.com' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
expect(res._getJSONData()).toEqual({
message: 'User is incorrect. Please, logout to update your session.',
});
});
it('should return 200 and successfully update the appriseURLs', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'Lovelace' },
});
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
username: 'Lovelace',
password: 'securepassword',
roles: ['user'],
email: 'lovelace@example.com',
appriseServices: [],
},
]);
vi.mocked(updateUsersList).mockResolvedValue();
const { req, res } = createMocks({
method: 'PUT',
body: { appriseURLs: 'http://example.com\nhttp://anotherurl.com' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({ message: 'Successful API send' });
});
it('should return 500 if there is an error reading the users file', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'Lovelace' },
});
vi.mocked(getUsersList).mockRejectedValue({ code: 'ENOENT' });
const { req, res } = createMocks({
method: 'PUT',
body: { appriseURLs: 'http://example.com' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
expect(res._getJSONData()).toEqual({
status: 500,
message: 'No such file or directory',
});
});
it('should return 500 if there is an API error', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'Lovelace' },
});
vi.mocked(getUsersList).mockRejectedValue({ code: 'UNKNOWN_ERROR' });
const { req, res } = createMocks({
method: 'PUT',
body: { appriseURLs: 'http://example.com' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
expect(res._getJSONData()).toEqual({
status: 500,
message: 'API error, contact the administrator',
});
});
});

View file

@ -0,0 +1,137 @@
import { describe, it, expect, vi } from 'vitest';
import handler from './updateEmail';
import { getUsersList, updateUsersList } from '~/services';
import { getServerSession } from 'next-auth/next';
import { NextApiRequest, NextApiResponse } from 'next';
vi.mock('~/services', () => ({
getUsersList: vi.fn(),
updateUsersList: vi.fn(),
}));
vi.mock('next-auth/next', () => ({
getServerSession: vi.fn(),
}));
describe('updateEmail API handler', () => {
const mockReq = {
method: 'PUT',
body: { email: 'newemail@example.com' },
} as unknown as NextApiRequest;
const mockRes = {
status: vi.fn().mockReturnThis(),
json: vi.fn(),
} as unknown as NextApiResponse;
it('should return 405 if method is not PUT', async () => {
mockReq.method = 'GET';
await handler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(405);
});
it('should return 401 if session is not found', async () => {
vi.mocked(getServerSession).mockResolvedValueOnce(null);
await handler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(401);
});
it('should return 422 if email is not provided', async () => {
mockReq.body = {};
vi.mocked(getServerSession).mockResolvedValueOnce({ user: { name: 'testuser' } });
await handler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(422);
expect(mockRes.json).toHaveBeenCalledWith({ message: 'Unexpected data' });
});
it('should return 400 if user is not found in users list', async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({ user: { name: 'testuser' } });
vi.mocked(getUsersList).mockResolvedValueOnce([]);
await handler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith({
message: 'User is incorrect. Please, logout to update your session.',
});
});
it('should return 400 if email already exists', async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({ user: { name: 'testuser' } });
vi.mocked(getUsersList).mockResolvedValueOnce([
{
username: 'testuser',
email: 'oldemail@example.com',
id: 1,
password: 'hashedpassword',
roles: ['user'],
},
{
username: 'otheruser',
email: 'newemail@example.com',
id: 2,
password: 'hashedpassword',
roles: ['user'],
},
]);
await handler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith({ message: 'Email already exists' });
});
it('should update email and return 200 on success', async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({ user: { name: 'testuser' } });
vi.mocked(getUsersList).mockResolvedValueOnce([
{
username: 'testuser',
email: 'oldemail@example.com',
id: 1,
password: 'hashedpassword',
roles: ['user'],
},
{
username: 'otheruser',
email: 'otheremail@example.com',
id: 2,
password: 'hashedpassword',
roles: ['user'],
},
]);
vi.mocked(updateUsersList).mockResolvedValueOnce(undefined);
await handler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith({ message: 'Successful API send' });
expect(updateUsersList).toHaveBeenCalledWith([
{ username: 'testuser', email: 'newemail@example.com' },
{ username: 'otheruser', email: 'otheremail@example.com' },
]);
});
it('should return 500 if an unexpected error occurs', async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({ user: { name: 'testuser' } });
vi.mocked(getUsersList).mockRejectedValueOnce(new Error('Unexpected error'));
await handler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.json).toHaveBeenCalledWith({
status: 500,
message: 'API error, contact the administrator',
});
});
it('should return 500 with specific message if ENOENT error occurs', async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({ user: { name: 'testuser' } });
const enoentError = new Error('No such file or directory');
(enoentError as any).code = 'ENOENT';
vi.mocked(getUsersList).mockRejectedValueOnce(enoentError);
await handler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.json).toHaveBeenCalledWith({
status: 500,
message: 'No such file or directory',
});
});
});

View file

@ -1,5 +1,6 @@
import { getUsersList, updateUsersList, hashPassword, verifyPassword } from '~/helpers/functions';
import { hashPassword, verifyPassword } from '~/helpers/functions';
import { authOptions } from '../auth/[...nextauth]';
import { getUsersList, updateUsersList } from '~/services';
import { getServerSession } from 'next-auth/next';
import { NextApiRequest, NextApiResponse } from 'next';
import { ErrorResponse } from '~/types/api/error.types';

View file

View file

@ -1,4 +1,4 @@
import { getUsersList, updateUsersList } from '~/helpers/functions';
import { getUsersList, updateUsersList } from '~/services';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
import { UsernameSettingDTO } from '~/types/api/setting.types';

View file

@ -0,0 +1,514 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/cronjob/checkStatus';
import { getRepoList, getUsersList, updateRepoList } from '~/services';
import { getLastSaveListShell } from '~/helpers/functions/shell.utils';
import nodemailerSMTP from '~/helpers/functions/nodemailerSMTP';
import { AppriseModeEnum } from '~/types/domain/config.types';
vi.mock('~/services', () => ({
getRepoList: vi.fn(),
getUsersList: vi.fn(),
updateRepoList: vi.fn(),
}));
vi.mock('~/helpers/functions/shell.utils', () => ({
getLastSaveListShell: vi.fn(),
}));
vi.mock('~/helpers/functions/nodemailerSMTP', () => ({
default: vi.fn(() => ({
sendMail: vi.fn().mockResolvedValue({ messageId: 'fake-message-id' }),
})),
}));
vi.mock('~/helpers/templates/emailAlertStatus', () => ({
default: vi.fn(() => ({
subject: 'Alert',
text: 'Alert text',
})),
}));
vi.mock('node:child_process', () => ({
exec: vi.fn(
(
command: string,
callback: (err: Error | null, result: { stdout: string; stderr: string }) => void
) => {
callback(null, { stdout: 'mocked output', stderr: '' });
}
),
}));
describe('Cronjob API Handler', () => {
beforeEach(() => {
process.env.CRONJOB_KEY = 'test-key';
vi.clearAllMocks();
vi.resetModules();
vi.spyOn(console, 'log').mockImplementation(() => {});
});
it('should return 401 if no authorization header', async () => {
const { req, res } = createMocks({ method: 'POST' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 401 if method is not POST', async () => {
const { req, res } = createMocks({
method: 'GET',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 401 if wrong authorization key', async () => {
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer wrong-key' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 200 with message if no repository to check (empty repoList)', async () => {
vi.mocked(getRepoList).mockResolvedValue([]);
vi.mocked(getLastSaveListShell).mockResolvedValue([{ repositoryName: 'repo1', lastSave: 123 }]);
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({
status: 200,
message: 'Status cron executed. No repository to check.',
});
});
it('should return 200 with message if no repository to check (empty lastSaveList)', async () => {
vi.mocked(getRepoList).mockResolvedValue([
{
repositoryName: 'repo1',
alert: 100,
alias: 'Repo1',
id: 1,
status: true,
lastSave: 0,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
vi.mocked(getLastSaveListShell).mockResolvedValue([]);
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({
status: 200,
message: 'Status cron executed. No repository to check.',
});
});
it('should execute successfully without alerts if all repositories are OK', async () => {
const currentTime = Math.floor(Date.now() / 1000);
vi.mocked(getRepoList).mockResolvedValue([
{
repositoryName: 'repo1',
alert: 1000,
alias: 'Repo1',
status: true,
id: 1,
lastSave: 0,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
vi.mocked(getLastSaveListShell).mockResolvedValue([
{ repositoryName: 'repo1', lastSave: currentTime },
]);
vi.mocked(updateRepoList).mockResolvedValue(undefined);
vi.mocked(getUsersList).mockResolvedValue([]);
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({
status: 200,
message: 'Status cron executed successfully.',
});
expect(updateRepoList).toHaveBeenCalled();
});
it('should return 500 if an error occurs', async () => {
vi.mocked(getRepoList).mockRejectedValue(new Error('Test error'));
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
expect(res._getJSONData()).toEqual({
status: 500,
message: 'API error, contact the administrator.',
});
});
it('should not send email alert if emailAlert is false', async () => {
const currentTime = Math.floor(Date.now() / 1000);
vi.mocked(getRepoList).mockResolvedValue([
{
repositoryName: 'repo1',
alert: 100,
alias: 'Repo1',
id: 1,
status: true,
lastSave: 0,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
vi.mocked(getLastSaveListShell).mockResolvedValue([
{ repositoryName: 'repo1', lastSave: currentTime - 200 },
]);
// User has disabled email alert but enabled Apprise alert
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
password: 'hashed-password',
roles: ['user'],
emailAlert: false,
appriseAlert: true,
appriseServices: ['http://example.com'],
appriseMode: AppriseModeEnum.PACKAGE,
appriseStatelessURL: 'http://example.com',
email: 'test@example.com',
username: 'testuser',
},
]);
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(nodemailerSMTP).not.toHaveBeenCalled();
});
it('should not send apprise alert if appriseAlert is false', async () => {
const currentTime = Math.floor(Date.now() / 1000);
vi.mocked(getRepoList).mockResolvedValue([
{
repositoryName: 'repo1',
alert: 100,
alias: 'Repo1',
id: 1,
status: true,
lastSave: 0,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
vi.mocked(getLastSaveListShell).mockResolvedValue([
{ repositoryName: 'repo1', lastSave: currentTime - 200 },
]);
// User has disabled Apprise alert but enabled email alert
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
password: 'hashed-password',
roles: ['user'],
emailAlert: true,
appriseAlert: false,
appriseServices: ['http://example.com'],
appriseMode: AppriseModeEnum.PACKAGE,
appriseStatelessURL: 'http://example.com',
email: 'test@example.com',
username: 'testuser',
},
]);
// Spy on exec to check if it is called
const execSpy = vi.spyOn(require('node:child_process'), 'exec');
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(execSpy).not.toHaveBeenCalled();
execSpy.mockRestore();
});
it('should not send alert if alert is disabled on repo (repo.alert === 0)', async () => {
const currentTime = Math.floor(Date.now() / 1000);
vi.mocked(getRepoList).mockResolvedValue([
{
repositoryName: 'repo1',
alert: 0,
alias: 'Repo1',
id: 1,
status: false,
lastSave: 0,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
vi.mocked(getLastSaveListShell).mockResolvedValue([
{ repositoryName: 'repo1', lastSave: currentTime - 1000 },
]);
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
password: 'hashed-password',
roles: ['user'],
emailAlert: true,
appriseAlert: true,
appriseServices: ['http://example.com'],
appriseMode: AppriseModeEnum.PACKAGE,
appriseStatelessURL: 'http://example.com',
email: 'test@example.com',
username: 'testuser',
},
]);
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(nodemailerSMTP).not.toHaveBeenCalled();
const childProcess = await import('node:child_process');
expect(childProcess.exec).not.toHaveBeenCalled();
});
it('should not update lastStatusAlertSend or add to repoListToSendAlert if repo status is OK', async () => {
vi.mocked(getRepoList).mockResolvedValue([
{
repositoryName: 'repo1',
status: true,
alert: 100,
id: 1,
alias: 'Repo1',
lastSave: 0,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
lastStatusAlertSend: 1000,
},
]);
vi.mocked(updateRepoList).mockResolvedValue(undefined);
vi.mocked(getLastSaveListShell).mockResolvedValue([
{ repositoryName: 'repo1', lastSave: Math.floor(Date.now() / 1000) },
]);
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(updateRepoList).toHaveBeenCalledWith([
{
repositoryName: 'repo1',
status: true,
alert: 100,
id: 1,
alias: 'Repo1',
lastSave: expect.any(Number),
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
lastStatusAlertSend: 1000,
},
]);
expect(res._getStatusCode()).toBe(200);
});
it('should update lastStatusAlertSend if repo is down and alert is enabled', async () => {
const currentTime = 1741535661;
vi.mocked(getRepoList).mockResolvedValue([
{
repositoryName: 'repo1',
alias: 'Repo1',
status: false,
alert: 100,
id: 1,
lastSave: 0,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
vi.mocked(getLastSaveListShell).mockResolvedValue([
{ repositoryName: 'repo1', lastSave: currentTime - 200 },
]);
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
password: 'hashed-password',
roles: ['user'],
emailAlert: true,
email: 'test@example.com',
username: 'TestUser',
},
]);
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(updateRepoList).toHaveBeenCalledWith([
{
repositoryName: 'repo1',
alias: 'Repo1',
status: false,
alert: 100,
id: 1,
lastSave: currentTime - 200,
lastStatusAlertSend: expect.any(Number),
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
expect(res._getStatusCode()).toBe(200);
});
it('should not update lastStatusAlertSend or send alerts if alert is disabled', async () => {
const currentTime = Math.floor(Date.now() / 1000);
vi.mocked(getRepoList).mockResolvedValue([
{
repositoryName: 'repo1',
alias: 'Repo1',
status: false,
alert: 0,
lastStatusAlertSend: undefined,
id: 1,
lastSave: 0,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
vi.mocked(getLastSaveListShell).mockResolvedValue([
{ repositoryName: 'repo1', lastSave: currentTime - 200 },
]);
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(updateRepoList).toHaveBeenCalledWith([
{
repositoryName: 'repo1',
alias: 'Repo1',
status: false,
alert: 0,
lastStatusAlertSend: undefined,
id: 1,
lastSave: currentTime - 200,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
expect(nodemailerSMTP).not.toHaveBeenCalled();
expect(res._getStatusCode()).toBe(200);
});
it('should update lastStatusAlertSend only if the last alert was sent more than 90000 seconds ago', async () => {
const currentTime = Math.floor(Date.now() / 1000);
vi.mocked(getRepoList).mockResolvedValue([
{
repositoryName: 'repo1',
alias: 'Repo1',
status: false,
alert: 100,
lastStatusAlertSend: currentTime - 80000,
id: 1,
lastSave: 0,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
vi.mocked(getLastSaveListShell).mockResolvedValue([
{ repositoryName: 'repo1', lastSave: currentTime - 200 },
]);
vi.mocked(getUsersList).mockResolvedValue([
{
id: 1,
password: 'hashed-password',
roles: ['user'],
emailAlert: true,
email: 'test@example.com',
username: 'TestUser',
},
]);
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer test-key' },
});
await handler(req, res);
expect(updateRepoList).toHaveBeenCalledWith([
{
repositoryName: 'repo1',
alias: 'Repo1',
status: false,
alert: 100,
lastStatusAlertSend: expect.any(Number),
id: 1,
lastSave: currentTime - 200,
storageSize: 0,
storageUsed: 0,
sshPublicKey: '',
comment: '',
},
]);
expect(res._getStatusCode()).toBe(200);
});
});

View file

@ -0,0 +1,135 @@
import handler from '~/pages/api/cronjob/getStorageUsed';
import { createMocks } from 'node-mocks-http';
import { getRepoList, updateRepoList } from '~/services';
import { getStorageUsedShell } from '~/helpers/functions/shell.utils';
vi.mock('~/services', () => ({
getRepoList: vi.fn(),
updateRepoList: vi.fn(),
}));
vi.mock('~/helpers/functions/shell.utils', () => ({
getStorageUsedShell: vi.fn(),
}));
describe('GET /api/cronjob/getStorageUsed', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(console, 'log').mockImplementation(() => {});
});
const CRONJOB_KEY = 'test-cronjob-key';
process.env.CRONJOB_KEY = CRONJOB_KEY;
it('should return unauthorized if no authorization header is provided', async () => {
const { req, res } = createMocks({
method: 'POST',
});
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return unauthorized if the authorization key is invalid', async () => {
const { req, res } = createMocks({
method: 'POST',
headers: {
authorization: 'Bearer invalid-key',
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return success if no repositories are found', async () => {
vi.mocked(getRepoList).mockResolvedValue([]);
const { req, res } = createMocks({
method: 'POST',
headers: {
authorization: `Bearer ${CRONJOB_KEY}`,
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getData()).toContain('No repository to check');
});
it('should update repositories with storage used and return success', async () => {
const mockRepoList = [
{ repositoryName: 'repo1', storageUsed: 0 },
{ repositoryName: 'repo2', storageUsed: 0 },
];
const mockStorageUsed = [
{ name: 'repo1', size: 100 },
{ name: 'repo2', size: 200 },
];
vi.mocked(getRepoList).mockResolvedValue(mockRepoList);
vi.mocked(getStorageUsedShell).mockResolvedValue(mockStorageUsed);
vi.mocked(updateRepoList).mockResolvedValue(undefined);
const { req, res } = createMocks({
method: 'POST',
headers: {
authorization: `Bearer ${CRONJOB_KEY}`,
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getData()).toContain('Storage cron has been executed');
expect(updateRepoList).toHaveBeenCalledWith([
{ repositoryName: 'repo1', storageUsed: 100 },
{ repositoryName: 'repo2', storageUsed: 200 },
]);
});
it('should return server error if an exception occurs', async () => {
vi.mocked(getRepoList).mockRejectedValue(new Error('Test error'));
const { req, res } = createMocks({
method: 'POST',
headers: {
authorization: `Bearer ${CRONJOB_KEY}`,
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
});
it('should not touch to a repository if it is not found in the storage used list', async () => {
const mockRepoList = [
{ repositoryName: 'repo1', storageUsed: 0 },
{ repositoryName: 'repo2', storageUsed: 0 },
];
const mockStorageUsed = [{ name: 'repo1', size: 100 }];
vi.mocked(getRepoList).mockResolvedValue(mockRepoList);
vi.mocked(getStorageUsedShell).mockResolvedValue(mockStorageUsed);
vi.mocked(updateRepoList).mockResolvedValue(undefined);
const { req, res } = createMocks({
method: 'POST',
headers: {
authorization: `Bearer ${CRONJOB_KEY}`,
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(updateRepoList).toHaveBeenCalledWith([
{ repositoryName: 'repo1', storageUsed: 100 },
{ repositoryName: 'repo2', storageUsed: 0 },
]);
});
});

View file

@ -1,5 +1,5 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { getRepoList, updateRepoList } from '~/helpers/functions';
import { getRepoList, updateRepoList } from '~/services';
import ApiResponse from '~/helpers/functions/apiResponse';
import { getStorageUsedShell } from '~/helpers/functions/shell.utils';
import { BorgWarehouseApiResponse } from '~/types/api/error.types';

190
pages/api/repo/add.test.ts Normal file
View file

@ -0,0 +1,190 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/repo/add';
import { getServerSession } from 'next-auth/next';
import { tokenController, isSshPubKeyDuplicate } from '~/helpers/functions';
import { getRepoList, updateRepoList } from '~/services';
import { createRepoShell } from '~/helpers/functions/shell.utils';
vi.mock('next-auth/next', () => ({
getServerSession: vi.fn(),
}));
vi.mock('~/helpers/functions', () => ({
tokenController: vi.fn(),
isSshPubKeyDuplicate: vi.fn(),
}));
vi.mock('~/services', () => ({
getRepoList: vi.fn(),
updateRepoList: vi.fn(),
}));
vi.mock('~/helpers/functions/shell.utils', () => ({
createRepoShell: vi.fn(),
}));
describe('POST /api/repo/add', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.resetAllMocks();
vi.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 () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).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 () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).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 () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([{ id: 1, sshPublicKey: 'duplicate-key' }]);
(isSshPubKeyDuplicate as vi.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 () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([]);
(createRepoShell as vi.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 () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([]);
(createRepoShell as vi.Mock).mockResolvedValue({ stdout: 'new-repo' });
vi.mocked(updateRepoList).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 () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([]);
(createRepoShell as vi.Mock).mockResolvedValue({ stdout: 'new-repo' });
vi.mocked(updateRepoList).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 () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).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 vi.Mock).mockResolvedValue({ stdout: 'new-repo' });
vi.mocked(updateRepoList).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
);
});
});

View file

@ -1,16 +1,12 @@
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
import {
getRepoList,
updateRepoList,
tokenController,
isSshPubKeyDuplicate,
} from '~/helpers/functions';
import { tokenController, isSshPubKeyDuplicate } from '~/helpers/functions';
import { NextApiRequest, NextApiResponse } from 'next';
import ApiResponse from '~/helpers/functions/apiResponse';
import { Repository } from '~/types/domain/config.types';
import { createRepoShell } from '~/helpers/functions/shell.utils';
import { getUnixTime } from 'date-fns';
import { getRepoList, updateRepoList } from '~/services';
export default async function handler(
req: NextApiRequest & { body: Partial<Repository> },

View file

@ -0,0 +1,190 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/repo/id/[slug]/delete';
import { getServerSession } from 'next-auth/next';
import { deleteRepoShell } from '~/helpers/functions/shell.utils';
import { getRepoList, updateRepoList } from '~/services';
import { tokenController } from '~/helpers/functions';
vi.mock('next-auth/next', () => ({
getServerSession: vi.fn(),
}));
vi.mock('~/helpers/functions', () => ({
tokenController: vi.fn(),
}));
vi.mock('~/services', () => ({
getRepoList: vi.fn(),
updateRepoList: vi.fn(),
}));
vi.mock('~/helpers/functions/shell.utils', () => {
return {
deleteRepoShell: vi.fn(),
};
});
describe('DELETE /api/repo/id/[slug]/delete', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.spyOn(console, 'log').mockImplementation(() => {});
});
it('should return 405 if method is not DELETE', 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: 'DELETE' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 403 if deletion is disabled via environment variable', async () => {
process.env.DISABLE_DELETE_REPO = 'true';
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'USER' },
});
const { req, res } = createMocks({ method: 'DELETE' });
await handler(req, res);
expect(res._getStatusCode()).toBe(403);
expect(res._getJSONData()).toEqual({
status: 403,
message: 'Deletion is disabled on this server',
});
delete process.env.DISABLE_DELETE_REPO;
});
it('should return 400 if slug is missing or malformed', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'USER' },
});
const { req, res } = createMocks({
method: 'DELETE',
query: { slug: undefined },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
expect(res._getJSONData()).toEqual({
status: 400,
message: 'Missing slug or slug is malformed',
});
});
it('should return 404 if repository is not found', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'USER' },
});
const { req, res } = createMocks({
method: 'DELETE',
query: { slug: '123' },
});
vi.mocked(getRepoList).mockResolvedValue([]);
await handler(req, res);
expect(res._getStatusCode()).toBe(404);
expect(res._getJSONData()).toEqual({
status: 404,
message: 'Repository not found',
});
});
it('should return 500 if deleteRepo fails', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'USER' },
});
const { req, res } = createMocks({
method: 'DELETE',
query: { slug: '123' },
});
vi.mocked(getRepoList).mockResolvedValue([{ id: 123, repositoryName: 'test-repo' }]);
vi.mocked(deleteRepoShell).mockResolvedValue({ stderr: 'Error' });
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
expect(res._getJSONData()).toEqual({
status: 500,
message: 'API error, contact the administrator.',
});
expect(updateRepoList).not.toHaveBeenCalled();
});
it('should delete the repository and return 200 on success with a session', async () => {
vi.mocked(getServerSession).mockResolvedValue({
user: { name: 'USER' },
});
const { req, res } = createMocks({
method: 'DELETE',
query: { slug: '1234' },
});
vi.mocked(getRepoList).mockResolvedValue([{ id: 1234, repositoryName: 'test-repo' }]);
vi.mocked(deleteRepoShell).mockResolvedValue({ stderr: null });
vi.mocked(updateRepoList).mockResolvedValue(true);
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({
status: 200,
message: 'Repository test-repo deleted',
});
expect(updateRepoList).toHaveBeenCalledWith([], true);
});
it('should delete the repository and return 200 on success with an API key', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue({ delete: true });
const { req, res } = createMocks({
method: 'DELETE',
query: { slug: '12345' },
headers: {
authorization: 'Bearer API_KEY',
},
});
vi.mocked(getRepoList).mockResolvedValue([{ id: 12345, repositoryName: 'test-repo2' }]);
vi.mocked(deleteRepoShell).mockResolvedValue({ stderr: null });
vi.mocked(updateRepoList).mockResolvedValue(true);
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({
status: 200,
message: 'Repository test-repo2 deleted',
});
expect(updateRepoList).toHaveBeenCalledWith([], true);
});
it('should return 401 if the API key is invalid', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue(null);
const { req, res } = createMocks({
method: 'DELETE',
query: { slug: '12345' },
headers: {
authorization: 'Bearer API_KEY',
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
expect(res._getJSONData()).toEqual({
status: 401,
message: 'Invalid API key',
});
});
it('should return 403 if the API key does not have delete permissions', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue({ delete: false });
const { req, res } = createMocks({
method: 'DELETE',
query: { slug: '12345' },
headers: {
authorization: 'Bearer API_KEY',
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(403);
expect(res._getJSONData()).toEqual({
status: 403,
message: 'Insufficient permissions',
});
});
});

View file

@ -1,7 +1,8 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { getServerSession } from 'next-auth/next';
import { getRepoList, updateRepoList, tokenController } from '~/helpers/functions';
import { tokenController } from '~/helpers/functions';
import { deleteRepoShell } from '~/helpers/functions/shell.utils';
import { getRepoList, updateRepoList } from '~/services';
import ApiResponse from '~/helpers/functions/apiResponse';
import { BorgWarehouseApiResponse } from '~/types/api/error.types';

View file

@ -0,0 +1,188 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/repo/id/[slug]/edit';
import { getServerSession } from 'next-auth/next';
import { updateRepoShell } from '~/helpers/functions/shell.utils';
import { tokenController, isSshPubKeyDuplicate } from '~/helpers/functions';
import { getRepoList, updateRepoList } from '~/services';
vi.mock('next-auth/next', () => ({
__esModule: true,
getServerSession: vi.fn(),
}));
vi.mock('~/helpers/functions', () => ({
tokenController: vi.fn(),
isSshPubKeyDuplicate: vi.fn(),
}));
vi.mock('~/helpers/functions/shell.utils', () => ({
updateRepoShell: vi.fn(),
}));
vi.mock('~/services', () => ({
getRepoList: vi.fn(),
updateRepoList: vi.fn(),
}));
describe('PATCH /api/repo/id/[slug]/edit', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.resetAllMocks();
vi.spyOn(console, 'log').mockImplementation(() => {});
});
it('should return 405 if method is not PATCH', 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: 'PATCH' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 401 if API key is invalid', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue(null);
const { req, res } = createMocks({
method: 'PATCH',
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 update permissions', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue({ update: false });
const { req, res } = createMocks({
method: 'PATCH',
headers: { authorization: 'Bearer API_KEY' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(403);
});
it('should return 400 if slug is missing or malformed', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
const { req, res } = createMocks({ method: 'PATCH', query: { slug: undefined } });
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
});
it('should return 404 if repository is not found', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([]);
const { req, res } = createMocks({ method: 'PATCH', query: { slug: '123' } });
await handler(req, res);
expect(res._getStatusCode()).toBe(404);
});
it('should return 409 if SSH key is duplicated', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([{ id: 123, repositoryName: 'test-repo' }]);
(isSshPubKeyDuplicate as vi.Mock).mockReturnValue(true);
const { req, res } = createMocks({
method: 'PATCH',
query: { slug: '123' },
body: { sshPublicKey: 'duplicate-key' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(409);
});
it('should return 500 if updateRepoShell fails', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([{ id: 123, repositoryName: 'test-repo' }]);
(updateRepoShell as vi.Mock).mockResolvedValue({ stderr: 'Error' });
const { req, res } = createMocks({
method: 'PATCH',
query: { slug: '123' },
body: { alias: 'new-alias' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(500);
});
it('should successfully update repository with a session', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([{ id: 123, repositoryName: 'test-repo' }]);
(updateRepoShell as vi.Mock).mockResolvedValue({ stderr: null });
vi.mocked(updateRepoList).mockResolvedValue(true);
const { req, res } = createMocks({
method: 'PATCH',
query: { slug: '123' },
body: { alias: 'new-alias' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({ message: 'Repository test-repo has been edited' });
});
it('should successfully update repository with API key', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue({ update: true });
vi.mocked(getRepoList).mockResolvedValue([{ id: 456, repositoryName: 'repo-key' }]);
(updateRepoShell as vi.Mock).mockResolvedValue({ stderr: null });
vi.mocked(updateRepoList).mockResolvedValue(true);
const { req, res } = createMocks({
method: 'PATCH',
query: { slug: '456' },
headers: { authorization: 'Bearer API_KEY' },
body: { alias: 'updated-repo' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({ message: 'Repository repo-key has been edited' });
});
it('should only update the provided fields, keep the rest unchanged and history the modification.', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([
{
id: 123,
repositoryName: 'test-repo',
alias: 'old-alias',
sshPublicKey: 'old-key',
storageSize: 100,
lanCommand: false,
},
]);
(updateRepoShell as vi.Mock).mockResolvedValue({ stderr: null });
vi.mocked(updateRepoList).mockResolvedValue(true);
const { req, res } = createMocks({
method: 'PATCH',
query: { slug: '123' },
body: {
alias: 'new-alias',
sshPublicKey: 'new-key',
comment: 'new-comment',
alert: 0,
appendOnlyMode: true,
},
});
await handler(req, res);
expect(updateRepoList).toHaveBeenCalledWith(
[
{
id: 123,
repositoryName: 'test-repo',
alias: 'new-alias',
sshPublicKey: 'new-key',
comment: 'new-comment',
alert: 0,
appendOnlyMode: true,
storageSize: 100,
lanCommand: false,
},
],
true
);
expect(updateRepoShell).toHaveBeenCalledWith('test-repo', 'new-key', 100, true);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({ message: 'Repository test-repo has been edited' });
});
});

View file

@ -1,15 +1,11 @@
import { authOptions } from '../../../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
import {
getRepoList,
updateRepoList,
tokenController,
isSshPubKeyDuplicate,
} from '~/helpers/functions';
import { tokenController, isSshPubKeyDuplicate } from '~/helpers/functions';
import { NextApiRequest, NextApiResponse } from 'next';
import ApiResponse from '~/helpers/functions/apiResponse';
import { Repository } from '~/types/domain/config.types';
import { updateRepoShell } from '~/helpers/functions/shell.utils';
import { getRepoList, updateRepoList } from '~/services';
export default async function handler(
req: NextApiRequest & { body: Partial<Repository> },

View file

@ -3,8 +3,9 @@ import { getServerSession } from 'next-auth/next';
import { NextApiRequest, NextApiResponse } from 'next';
import { BorgWarehouseApiResponse } from '~/types/api/error.types';
import ApiResponse from '~/helpers/functions/apiResponse';
import { getRepoList, tokenController } from '~/helpers/functions';
import { tokenController } from '~/helpers/functions';
import { Repository } from '~/types/domain/config.types';
import { getRepoList } from '~/services';
export default async function handler(
req: NextApiRequest,

View file

@ -0,0 +1,117 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/repo/id/[slug]';
import { getServerSession } from 'next-auth/next';
import { tokenController } from '~/helpers/functions';
import { getRepoList } from '~/services';
import { Repository } from '~/types/domain/config.types';
vi.mock('next-auth/next', () => ({
getServerSession: vi.fn(),
}));
vi.mock('~/helpers/functions', () => ({
tokenController: vi.fn(),
}));
vi.mock('~/services', () => ({
getRepoList: vi.fn(),
}));
const mockRepoList: Repository[] = [
{
id: 1,
alias: 'repo1',
repositoryName: 'Test Repository 1',
status: true,
lastSave: 1678901234,
alert: 1,
storageSize: 100,
storageUsed: 50,
sshPublicKey: 'ssh-rsa AAAAB3Nza...fakekey1',
comment: 'Test repository 1',
displayDetails: true,
unixUser: 'user1',
lanCommand: false,
appendOnlyMode: false,
lastStatusAlertSend: 1678901234,
},
{
id: 2,
alias: 'repo2',
repositoryName: 'Test Repository 2',
status: false,
lastSave: 1678905678,
storageSize: 200,
storageUsed: 150,
sshPublicKey: 'ssh-rsa AAAAB3Nza...fakekey2',
comment: 'Test repository 2',
displayDetails: false,
unixUser: 'user2',
},
];
describe('GET /api/repo/id/[slug]', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.spyOn(console, 'log').mockImplementation(() => {});
});
it('should return 405 if method is not GET', async () => {
const { req, res } = createMocks({ method: 'POST' });
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: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 401 if API key is invalid', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue(null);
const { req, res } = createMocks({
method: 'GET',
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 read permissions', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue({ read: false });
const { req, res } = createMocks({
method: 'GET',
headers: { authorization: 'Bearer API_KEY' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(403);
});
it('should return 400 if slug is missing or malformed', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
const { req, res } = createMocks({ method: 'GET', query: { slug: undefined } });
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
});
it('should return 404 if repository is not found', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue([]);
const { req, res } = createMocks({ method: 'GET', query: { slug: '3' } });
await handler(req, res);
expect(res._getStatusCode()).toBe(404);
});
it('should return 200 and the repository data if found', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue(mockRepoList);
const { req, res } = createMocks({ method: 'GET', query: { slug: '1' } });
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({ repo: mockRepoList[0] });
});
});

View file

@ -3,7 +3,8 @@ import { getServerSession } from 'next-auth/next';
import { NextApiRequest, NextApiResponse } from 'next';
import { BorgWarehouseApiResponse } from '~/types/api/error.types';
import ApiResponse from '~/helpers/functions/apiResponse';
import { getRepoList, tokenController } from '~/helpers/functions';
import { tokenController } from '~/helpers/functions';
import { getRepoList } from '~/services';
import { Repository } from '~/types/domain/config.types';
export default async function handler(

View file

@ -0,0 +1,102 @@
import { createMocks } from 'node-mocks-http';
import handler from '~/pages/api/repo';
import { getServerSession } from 'next-auth/next';
import { tokenController } from '~/helpers/functions';
import { getRepoList } from '~/services';
import { Repository } from '~/types/domain/config.types';
vi.mock('next-auth/next', () => ({
getServerSession: vi.fn(),
}));
vi.mock('~/helpers/functions', () => ({
tokenController: vi.fn(),
}));
vi.mock('~/services', () => ({
getRepoList: vi.fn(),
}));
const mockRepoList: Repository[] = [
{
id: 1,
alias: 'repo1',
repositoryName: 'Test Repository 1',
status: true,
lastSave: 1678901234,
alert: 1,
storageSize: 100,
storageUsed: 50,
sshPublicKey: 'ssh-rsa AAAAB3Nza...fakekey1',
comment: 'Test repository 1',
displayDetails: true,
unixUser: 'user1',
lanCommand: false,
appendOnlyMode: false,
lastStatusAlertSend: 1678901234,
},
{
id: 2,
alias: 'repo2',
repositoryName: 'Test Repository 2',
status: false,
lastSave: 1678905678,
storageSize: 200,
storageUsed: 150,
sshPublicKey: 'ssh-rsa AAAAB3Nza...fakekey2',
comment: 'Test repository 2',
displayDetails: false,
unixUser: 'user2',
},
];
describe('GET /api/repo/id/[slug]', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.spyOn(console, 'log').mockImplementation(() => {});
});
it('should return 405 if method is not GET', async () => {
const { req, res } = createMocks({ method: 'POST' });
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: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(401);
});
it('should return 401 if API key is invalid', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue(null);
const { req, res } = createMocks({
method: 'GET',
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 read permissions', async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
vi.mocked(tokenController).mockResolvedValue({ read: false });
const { req, res } = createMocks({
method: 'GET',
headers: { authorization: 'Bearer API_KEY' },
});
await handler(req, res);
expect(res._getStatusCode()).toBe(403);
});
it('should return 200 and the repoList data if found', async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { name: 'USER' } });
vi.mocked(getRepoList).mockResolvedValue(mockRepoList);
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getJSONData()).toEqual({ repoList: mockRepoList });
});
});