Files
FEDEO/scripts/release/tests/migrations.test.mjs
flfeders 74c0cf40b3
All checks were successful
Build and Release FEDEO / verify (push) Successful in 25s
Build and Release FEDEO / build-and-release (push) Successful in 5m50s
fix(release): stream large migration checksums from git
2026-09-23 15:37:19 +02:00

74 lines
3.2 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { appendFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { git, migrationManifest } from '../lib.mjs';
function fixture(t) {
const cwd = mkdtempSync(join(tmpdir(), 'fedeo-migrations-test-'));
t.after(() => rmSync(cwd, { recursive: true, force: true }));
const run = args => git(args, cwd, { stdio: ['pipe', 'pipe', 'pipe'] });
run(['init', '-b', 'main']);
run(['config', 'user.name', 'Release Test']);
run(['config', 'user.email', 'release@example.test']);
// Keep CRLF test bytes in Git regardless of the developer's autocrlf setting.
writeFileSync(join(cwd, '.gitattributes'), '*.sql -text\n');
const write = (path, data) => {
mkdirSync(dirname(join(cwd, path)), { recursive: true });
writeFileSync(join(cwd, path), data);
};
const commit = () => {
run(['add', '.']);
run(['commit', '--allow-empty', '-m', 'test: add migrations']);
return run(['rev-parse', 'HEAD']);
};
return { cwd, run, write, commit };
}
test('large migration hashes use the complete committed bytes beyond process buffer limits', async t => {
const f = fixture(t);
const path = 'backend/db/migrations/0058_reference_data.sql';
f.write(path, '');
const expected = createHash('sha256');
const chunk = Buffer.alloc(1024 * 1024, Buffer.from(" INSERT INTO reference VALUES ('Grüße');\r\n"));
// Exceed both Node's 1 MiB default and the text Git helper's 32 MiB buffer.
for (let i = 0; i < 34; i++) {
appendFileSync(join(f.cwd, path), chunk);
expected.update(chunk);
}
const sha = f.commit();
f.write(path, 'local changes must not affect the release checksum');
assert.deepEqual(await migrationManifest(sha, f.cwd), [{ path, sha256: expected.digest('hex') }]);
});
test('both migration folders are hashed byte-for-byte and non-SQL files are excluded', async t => {
const f = fixture(t);
const files = [
['backend/db/migrations/0000_init.sql', Buffer.from(' \r\nSELECT 1;\r\n\t')],
['push-server/packages/db/drizzle/0000_init.sql', Buffer.from('SELECT \'äöü\';\n')],
];
for (const [path, content] of files) f.write(path, content);
f.write('backend/db/migrations/meta/snapshot.json', '{}');
const sha = f.commit();
assert.deepEqual(await migrationManifest(sha, f.cwd), files.map(([path, content]) => ({
path, sha256: createHash('sha256').update(content).digest('hex'),
})));
});
test('a commit without migration folders produces an empty manifest', async t => {
const f = fixture(t);
assert.deepEqual(await migrationManifest(f.commit(), f.cwd), []);
});
test('an unreadable Git blob rejects the manifest instead of recording a partial checksum', async t => {
const f = fixture(t);
const path = 'backend/db/migrations/0000_missing.sql';
f.write(path, 'SELECT 1;');
const sha = f.commit();
const blob = f.run(['rev-parse', `${sha}:${path}`]);
rmSync(join(f.cwd, '.git', 'objects', blob.slice(0, 2), blob.slice(2)));
await assert.rejects(async () => migrationManifest(sha, f.cwd), /0000_missing\.sql/);
});