DC-059: claim for Hermes
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled

This commit is contained in:
Hermes
2026-08-08 15:20:09 -07:00
parent 55a50fdeb7
commit c1358df0ec
3 changed files with 393 additions and 0 deletions
@@ -0,0 +1,203 @@
/**
* Unit tests for the Joi validation middleware + schema definitions.
* Verifies that valid inputs pass through and invalid inputs throw
* ValidationError with descriptive messages.
*/
const { validateBody, schemas } = require('../../src/utilities/validate');
function mockReq(body) {
return { body };
}
describe('validateBody middleware', () => {
test('passes valid body through and strips unknown keys', () => {
const schema = schemas.assetUpload;
const req = mockReq({ filename: 'logo.png', data: 'data:image/png;base64,abc', extra: true });
const next = jest.fn();
validateBody(schema)(req, {}, next);
expect(next).toHaveBeenCalled();
expect(req.body).toHaveProperty('filename', 'logo.png');
expect(req.body).not.toHaveProperty('extra');
});
test('throws ValidationError on missing required field', () => {
const req = mockReq({});
expect(() => validateBody(schemas.assetUpload)(req, {}, jest.fn())).toThrow(/filename/);
});
});
describe('schemas.backupConfigUpdate', () => {
test('accepts valid patch', () => {
const { error, value } = schemas.backupConfigUpdate.validate({
backups: { app1: { enabled: true, schedule: 'daily' } },
defaultRetention: { keep: 7 },
});
expect(error).toBeUndefined();
expect(value).toHaveProperty('backups');
});
test('strips unknown top-level keys', () => {
const { error, value } = schemas.backupConfigUpdate.validate({
backups: {},
malicious: true,
}, { stripUnknown: true });
expect(error).toBeUndefined();
expect(value).not.toHaveProperty('malicious');
});
test('rejects unknown keys inside per-app backup config', () => {
const { error } = schemas.backupConfigUpdate.validate({
backups: { app1: { enabled: true, schedule: 'daily', rce: 'yes' } },
}, { stripUnknown: false });
expect(error).toBeDefined();
});
test('rejects invalid retention.keep', () => {
const { error } = schemas.backupConfigUpdate.validate({
defaultRetention: { keep: 0 },
});
expect(error).toBeDefined();
expect(error.details[0].message).toMatch(/keep/);
});
});
describe('schemas.backupScheduleCreate', () => {
test('requires appId', () => {
const { error } = schemas.backupScheduleCreate.validate({});
expect(error).toBeDefined();
});
test('accepts full valid body', () => {
const { error, value } = schemas.backupScheduleCreate.validate({
appId: 'plex',
enabled: true,
schedule: 'daily',
retention: { keep: 7 },
destination: 'local',
maxStorageBytes: '10GB',
});
expect(error).toBeUndefined();
expect(value.appId).toBe('plex');
});
test('rejects invalid destination', () => {
const { error } = schemas.backupScheduleCreate.validate({
appId: 'plex',
destination: 'malicious-cloud',
});
expect(error).toBeDefined();
});
test('accepts numeric custom schedule (e.g. "30m", "6h")', () => {
const { error: e1 } = schemas.backupScheduleCreate.validate({ appId: 'plex', schedule: '30m' });
expect(e1).toBeUndefined();
const { error: e2 } = schemas.backupScheduleCreate.validate({ appId: 'plex', schedule: '6h' });
expect(e2).toBeUndefined();
});
test('rejects garbage schedule string', () => {
const { error } = schemas.backupScheduleCreate.validate({ appId: 'plex', schedule: 'abc' });
expect(error).toBeDefined();
});
});
describe('schemas.appDeploy', () => {
test('requires appId + config.subdomain', () => {
const { error } = schemas.appDeploy.validate({});
expect(error).toBeDefined();
});
test('accepts minimal valid deploy', () => {
const { error, value } = schemas.appDeploy.validate({
appId: 'plex',
config: { subdomain: 'plex' },
});
expect(error).toBeUndefined();
expect(value.config.subdomain).toBe('plex');
});
test('rejects port out of range', () => {
const { error } = schemas.appDeploy.validate({
appId: 'plex',
config: { subdomain: 'plex', port: 99999 },
});
expect(error).toBeDefined();
});
test('accepts valid IP in allowedIPs', () => {
const { error } = schemas.appDeploy.validate({
appId: 'plex',
config: { subdomain: 'plex', allowedIPs: ['192.168.1.1', '10.0.0.0/24'] },
});
expect(error).toBeUndefined();
});
test('rejects malformed CIDR in allowedIPs', () => {
const { error } = schemas.appDeploy.validate({
appId: 'plex',
config: { subdomain: 'plex', allowedIPs: ['999.999.999.999/99'] },
});
expect(error).toBeDefined();
});
test('accepts valid customVolumes objects', () => {
const { error } = schemas.appDeploy.validate({
appId: 'plex',
config: {
subdomain: 'plex',
customVolumes: [{ hostPath: '/data/movies', containerPath: '/movies' }],
},
});
expect(error).toBeUndefined();
});
test('strips unknown keys in config', () => {
const { error, value } = schemas.appDeploy.validate({
appId: 'plex',
config: { subdomain: 'plex', isAdmin: true },
}, { stripUnknown: true });
expect(error).toBeUndefined();
expect(value.config).not.toHaveProperty('isAdmin');
});
});
describe('schemas.assetUpload', () => {
test('requires both fields', () => {
const { error: e1 } = schemas.assetUpload.validate({ filename: 'logo.png' });
expect(e1).toBeDefined();
const { error: e2 } = schemas.assetUpload.validate({ data: 'abc' });
expect(e2).toBeDefined();
});
test('accepts valid upload', () => {
const { error } = schemas.assetUpload.validate({
filename: 'logo.png',
data: 'data:image/png;base64,iVBORw0KGgo=',
});
expect(error).toBeUndefined();
});
});
describe('schemas.backupRestoreFile', () => {
test('accepts empty body', () => {
const { error } = schemas.backupRestoreFile.validate({});
expect(error).toBeUndefined();
});
test('accepts optional fields', () => {
const { error } = schemas.backupRestoreFile.validate({
encryptionKey: 'secret',
restartContainers: true,
});
expect(error).toBeUndefined();
});
test('strips unknown keys', () => {
const { error, value } = schemas.backupRestoreFile.validate({
encryptionKey: 'secret',
malicious: 'yes',
}, { stripUnknown: true });
expect(error).toBeUndefined();
expect(value).not.toHaveProperty('malicious');
});
});