Files
FEDEO/scripts/storage/tests/keys.test.mjs

38 lines
2.2 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { validateKeys, MAX_OBJECT_BYTES, metadata } from '../s3.mjs';
import { cleanupProbe, atomicJson } from '../s3.mjs';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
test('accepts FEDEO keys, special characters, empty objects and size boundary', () => {
validateKeys([{ Key: 'tenant/filesbyid/123/ä + %#.pdf', Size: 0 }, { Key: 'export.zip', Size: MAX_OBJECT_BYTES }]);
});
test('blocks lossy paths and file/directory collisions regardless of ordering', () => {
for (const Key of ['/a', 'a/', 'a//b', 'a/../b', './a', 'a\\b', 'a\nb'])
assert.throws(() => validateKeys([{ Key, Size: 1 }]));
assert.throws(() => validateKeys([{ Key: 'a/b', Size: 1 }, { Key: 'a', Size: 1 }]));
assert.throws(() => validateKeys([{ Key: 'a', Size: MAX_OBJECT_BYTES + 1 }]));
});
test('rejects encryption and nonportable attributes rather than dropping them', () => {
for (const value of [{ ServerSideEncryption: 'AES256' }, { ObjectLockMode: 'GOVERNANCE' },
{ StorageClass: 'GLACIER' }, { WebsiteRedirectLocation: '/other' }]) assert.throws(() => metadata(value));
assert.deepEqual(metadata({ ContentType: 'application/pdf', Metadata: { custom: 'value' } }),
{ ContentType: 'application/pdf', Metadata: { custom: 'value' } });
});
test('interrupted contract cleanup deletes only its two journalled probe objects', async () => {
const directory = await mkdtemp(join(tmpdir(), 'fedeo-probe-test-'));
try {
const path = join(directory, 'probe.json');
const key = 'fedeo-contract-11111111-1111-1111-1111-111111111111/test';
await atomicJson(path, { key });
const inputs = [];
await cleanupProbe({ send: async command => { inputs.push(command.input); return {}; } }, 'bucket', path);
assert.deepEqual(inputs, [{ Bucket: 'bucket', Key: key }, { Bucket: 'bucket', Key: key + '.stream' }]);
await atomicJson(path, { key: 'real-business-file.pdf' });
await assert.rejects(cleanupProbe({ send: async () => { throw new Error('must not send'); } }, 'bucket', path), /Probejournal/);
} finally { await rm(directory, { recursive: true, force: true }); }
});