Files
qinglong/test/back/localArtifactTruncation.test.cjs

112 lines
3.5 KiB
JavaScript

require('ts-node/register/transpile-only');
const assert = require('node:assert/strict');
const fs = require('node:fs/promises');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
LocalArtifactTruncationFactStore,
UnsafeLocalArtifactTruncationFactError,
localArtifactTruncationFactFileName,
} = require('../../back/runtime/adapters/fs/localArtifactTruncationFactStore');
const {
decodeLocalArtifactTruncationFact,
encodeLocalArtifactTruncationFact,
} = require('../../back/runtime/domain/localArtifactTruncation');
const LOG_ARTIFACT_ID = `local-${'c'.repeat(30)}`;
const OTHER_ARTIFACT_ID = `local-${'d'.repeat(30)}`;
const FACT = {
schemaVersion: 1,
runId: '019f7500-0000-7000-8000-000000000001',
attemptId: '019f7500-0000-7000-8000-000000000002',
logArtifactId: LOG_ARTIFACT_ID,
maximumBytes: 64 * 1024,
quotaReached: true,
observedAtMs: 1_800_000_000_000,
};
async function temporaryRoot(t) {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql3-truncation-'));
t.after(() => fs.rm(root, { recursive: true, force: true }));
await fs.chmod(root, 0o700);
return root;
}
test('round-trips one exact canonical truncation fact', () => {
const encoded = encodeLocalArtifactTruncationFact(FACT);
assert.deepEqual(decodeLocalArtifactTruncationFact(encoded), FACT);
assert.throws(
() =>
decodeLocalArtifactTruncationFact(
JSON.stringify({ ...FACT, unknown: true }),
),
/shape is invalid/,
);
assert.throws(
() =>
decodeLocalArtifactTruncationFact(
JSON.stringify({ attemptId: FACT.attemptId, ...FACT }),
),
/not canonical/,
);
assert.throws(
() => encodeLocalArtifactTruncationFact({ ...FACT, maximumBytes: 1 }),
/maximumBytes is invalid/,
);
});
test('reads only the exact private Artifact fact and handles absence', async (t) => {
const root = await temporaryRoot(t);
const store = new LocalArtifactTruncationFactStore(root);
assert.equal(await store.read(LOG_ARTIFACT_ID), null);
const directory = path.join(root, LOG_ARTIFACT_ID.slice(6, 8));
await fs.mkdir(directory, { mode: 0o700 });
const target = path.join(
directory,
localArtifactTruncationFactFileName(LOG_ARTIFACT_ID),
);
await fs.writeFile(target, encodeLocalArtifactTruncationFact(FACT), {
mode: 0o600,
});
assert.deepEqual(await store.read(LOG_ARTIFACT_ID), FACT);
await fs.writeFile(
target,
encodeLocalArtifactTruncationFact({
...FACT,
logArtifactId: OTHER_ARTIFACT_ID,
}),
);
await assert.rejects(
store.read(LOG_ARTIFACT_ID),
UnsafeLocalArtifactTruncationFactError,
);
});
test('refuses symlink files and shard escapes', async (t) => {
const root = await temporaryRoot(t);
const store = new LocalArtifactTruncationFactStore(root);
const outside = path.join(root, 'outside.json');
await fs.writeFile(outside, encodeLocalArtifactTruncationFact(FACT));
const directory = path.join(root, LOG_ARTIFACT_ID.slice(6, 8));
await fs.mkdir(directory, { mode: 0o700 });
const target = path.join(
directory,
localArtifactTruncationFactFileName(LOG_ARTIFACT_ID),
);
await fs.symlink(outside, target);
await assert.rejects(
store.read(LOG_ARTIFACT_ID),
UnsafeLocalArtifactTruncationFactError,
);
await fs.unlink(target);
await fs.rmdir(directory);
await fs.symlink(path.dirname(outside), directory);
await assert.rejects(
store.read(LOG_ARTIFACT_ID),
UnsafeLocalArtifactTruncationFactError,
);
});