fix: harden release gate and page delivery
Memind CI / Test, build, and release guards (push) Failing after 12m3s
Memind CI / Test, build, and release guards (push) Failing after 12m3s
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
hashArtifact,
|
||||
inspectPortalRuntime,
|
||||
assertPortalRuntimePath,
|
||||
removeForbiddenPortalRuntimePaths,
|
||||
REQUIRED_PORTAL_RUNTIME_PATHS,
|
||||
} from './artifact.mjs';
|
||||
|
||||
async function withTempDir(fn) {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-release-gate-'));
|
||||
try {
|
||||
return await fn(dir);
|
||||
} finally {
|
||||
await fs.rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
test('directory artifact hash is stable across mtime changes', async () => {
|
||||
await withTempDir(async (dir) => {
|
||||
await fs.mkdir(path.join(dir, 'nested'));
|
||||
const file = path.join(dir, 'nested', 'value.txt');
|
||||
await fs.writeFile(file, 'stable\n');
|
||||
const first = await hashArtifact(dir);
|
||||
await fs.utimes(file, new Date(), new Date(Date.now() + 10_000));
|
||||
const second = await hashArtifact(dir);
|
||||
assert.equal(first.sha256, second.sha256);
|
||||
assert.equal(first.files, 1);
|
||||
});
|
||||
});
|
||||
|
||||
test('artifact hashing refuses symlinks that escape the artifact root', async () => {
|
||||
await withTempDir(async (dir) => {
|
||||
await fs.symlink('/tmp', path.join(dir, 'escape'));
|
||||
await assert.rejects(() => hashArtifact(dir), /symlink escapes root/);
|
||||
});
|
||||
});
|
||||
|
||||
test('Portal runtime inspection requires dependency closure and no persisted state', async () => {
|
||||
await withTempDir(async (dir) => {
|
||||
for (const relative of REQUIRED_PORTAL_RUNTIME_PATHS) {
|
||||
const absolute = path.join(dir, relative);
|
||||
if (relative === 'dist') {
|
||||
await fs.mkdir(absolute, { recursive: true });
|
||||
} else {
|
||||
await fs.mkdir(path.dirname(absolute), { recursive: true });
|
||||
await fs.writeFile(absolute, 'fixture\n');
|
||||
}
|
||||
}
|
||||
assert.deepEqual(await inspectPortalRuntime(dir), {
|
||||
missing: [],
|
||||
forbidden: [],
|
||||
passed: true,
|
||||
});
|
||||
await fs.writeFile(path.join(dir, '.env'), 'SECRET=not-real\n');
|
||||
const unsafe = await inspectPortalRuntime(dir);
|
||||
assert.deepEqual(unsafe.forbidden, ['.env']);
|
||||
assert.equal(unsafe.passed, false);
|
||||
});
|
||||
});
|
||||
|
||||
test('runtime sanitizer removes persisted paths before artifact hashing', async () => {
|
||||
await withTempDir(async (dir) => {
|
||||
const runtime = path.join(dir, '.runtime', 'portal');
|
||||
await fs.mkdir(path.join(runtime, 'public', 'plaza-covers'), { recursive: true });
|
||||
await fs.mkdir(path.join(runtime, 'dist', 'dev'), { recursive: true });
|
||||
await fs.mkdir(path.join(runtime, 'users'), { recursive: true });
|
||||
await fs.writeFile(path.join(runtime, '.env'), 'SECRET=fixture\n');
|
||||
await fs.writeFile(path.join(runtime, 'public', 'plaza-covers', 'cover.jpg'), 'fixture');
|
||||
await fs.writeFile(path.join(runtime, 'dist', 'dev', 'local-demo.html'), 'http://127.0.0.1');
|
||||
await fs.writeFile(path.join(runtime, 'dist', 'hello-john.html'), 'development fixture');
|
||||
await fs.writeFile(path.join(runtime, 'users', 'profile.json'), '{}');
|
||||
await fs.writeFile(path.join(runtime, 'server.mjs'), 'export {};\n');
|
||||
await removeForbiddenPortalRuntimePaths(runtime);
|
||||
await assert.rejects(() => fs.access(path.join(runtime, '.env')));
|
||||
await assert.rejects(() => fs.access(path.join(runtime, 'dist', 'dev')));
|
||||
await assert.rejects(() => fs.access(path.join(runtime, 'dist', 'hello-john.html')));
|
||||
await assert.rejects(() => fs.access(path.join(runtime, 'users')));
|
||||
await assert.rejects(() => fs.access(path.join(runtime, 'public', 'plaza-covers')));
|
||||
await fs.access(path.join(runtime, 'server.mjs'));
|
||||
});
|
||||
});
|
||||
|
||||
test('runtime sanitizer and gate artifact path refuse broad targets', async () => {
|
||||
await withTempDir(async (dir) => {
|
||||
await assert.rejects(
|
||||
() => removeForbiddenPortalRuntimePaths(dir),
|
||||
/Refusing to sanitize/,
|
||||
);
|
||||
assert.throws(
|
||||
() => assertPortalRuntimePath(dir, { repoRoot: dir }),
|
||||
/must be/,
|
||||
);
|
||||
assert.equal(
|
||||
assertPortalRuntimePath(path.join(dir, '.runtime', 'portal'), { repoRoot: dir }),
|
||||
path.join(dir, '.runtime', 'portal'),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user