mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 21:59:38 +01:00
89 lines
5.6 KiB
JavaScript
89 lines
5.6 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { test } from 'node:test';
|
|
import { createRequire } from 'node:module';
|
|
import { h5Enabled, H5_MODE, observeStreaming, originalTerminal, requireH5Candidate,
|
|
H5_LIMITS, ORIGINAL_BUDGETS } from '../tests/e2e/fixtures/native-ingestion-h5.mjs';
|
|
|
|
const timeout = milliseconds => Object.assign(new Error(`Timeout ${milliseconds}ms exceeded while waiting on the predicate`),
|
|
{ matcherResult: { name: 'toBeGreaterThanOrEqual', pass: false } });
|
|
|
|
test('real frozen Playwright tiny poll exposes the matcher deadline shape used by H5', async () => {
|
|
const require = createRequire(import.meta.url), { expect } = require('@playwright/test');
|
|
assert.equal(require('@playwright/test/package.json').version, '1.62.1');
|
|
await assert.rejects(expect.poll(async () => 0, { timeout: 15, intervals: [5] }).toBeGreaterThanOrEqual(1), error => {
|
|
assert.equal(error.matcherResult?.name, 'toBeGreaterThanOrEqual');
|
|
assert.equal(error.matcherResult.pass, false);
|
|
assert.ok(error.message.includes('Timeout 15ms exceeded while waiting on the predicate'));
|
|
return true;
|
|
});
|
|
// This checks the real library's format; it does not execute a120s wait or
|
|
// claim runtime qualification of H5's extended application path.
|
|
});
|
|
|
|
test('H5 requires explicit mode and leaves default H4c selected', () => {
|
|
assert.equal(h5Enabled(undefined), false); assert.equal(h5Enabled(H5_MODE), true);
|
|
assert.throws(() => h5Enabled('almost-h5'));
|
|
});
|
|
|
|
test('first successful observation uses only the original wait with no collection delay', async () => {
|
|
const calls = [];
|
|
const value = await observeStreaming({ readCount: async () => 16, target: 16, now: () => 1000,
|
|
poll: async (read, milliseconds, target) => { calls.push(milliseconds); assert.ok(await read() >= target); } });
|
|
assert.deepEqual(calls, [120000]); assert.equal(value.original.status, 'passed');
|
|
assert.equal(value.observation.complete, true); assert.equal(value.observation.completedAtMs, 1000);
|
|
});
|
|
|
|
test('known120s miss remains failed while the same observation finishes before600s', async () => {
|
|
let clock = 1000, count = 30; const calls = [], records = [];
|
|
const value = await observeStreaming({ readCount: async () => count, target: 512, now: () => clock,
|
|
onOutcome: value => records.push(structuredClone(value)),
|
|
poll: async (read, milliseconds) => {
|
|
calls.push(milliseconds); await read();
|
|
if (calls.length === 1) { clock += milliseconds; throw timeout(milliseconds); }
|
|
count = 512; clock += 90000; await read();
|
|
} });
|
|
assert.deepEqual(calls, [120000, 480000]);
|
|
assert.equal(value.original.status, 'failed'); assert.equal(value.original.lastObservedCount, 30);
|
|
assert.equal(value.original.lastObservedAtMs, 1000); assert.equal(value.original.deadlineMs, 121000);
|
|
assert.equal(value.observation.completedAtMs, 211000); assert.equal(records[0].observation.complete, false);
|
|
assert.equal(value.original.reason, 'original_streaming_deadline');
|
|
});
|
|
|
|
test('page read failures and unknown poll failures never become an expected SLA miss', async () => {
|
|
const failure = new Error('page detached');
|
|
await assert.rejects(observeStreaming({ readCount: async () => { throw failure; }, target: 1,
|
|
poll: async read => { try { await read(); } catch {} throw timeout(120000); } }), error => error === failure);
|
|
await assert.rejects(observeStreaming({ readCount: async () => 0, target: 1,
|
|
poll: async () => { throw new Error('unrelated assertion'); } }), /Only the original streaming matcher deadline/);
|
|
});
|
|
|
|
test('observation ceiling leaves a censored failure, never a substituted latency', async () => {
|
|
let clock = 1000, calls = 0; const records = [];
|
|
await assert.rejects(observeStreaming({ readCount: async () => 30, target: 512, now: () => clock,
|
|
onOutcome: value => records.push(structuredClone(value)),
|
|
poll: async (read, milliseconds) => { await read(); calls++; clock += milliseconds; throw timeout(milliseconds); } }), /Timeout/);
|
|
assert.equal(calls, 2); assert.equal(clock, 601000);
|
|
assert.equal(records.at(-1).observation.complete, false); assert.equal(records.at(-1).observation.censored, true);
|
|
assert.equal(records.at(-1).observation.completedAtMs, null);
|
|
});
|
|
|
|
test('original terminal is not reached after120s miss; observational180s is distinct', () => {
|
|
const terminal = { complete: true, startedAtMs: 211000, deadlineMs: 391000, assessedAtMs: 212000 };
|
|
assert.deepEqual(originalTerminal({ original: { status: 'failed' } }, terminal), {
|
|
status: 'not-reached', reason: 'original_streaming_deadline', startedAtMs: null, deadlineMs: null, assessedAtMs: null });
|
|
assert.equal(originalTerminal({ original: { status: 'passed' } }, terminal).status, 'passed');
|
|
assert.equal(H5_LIMITS.terminalDrainageMs, ORIGINAL_BUDGETS.terminalDrainageMs);
|
|
});
|
|
|
|
test('cursor inherits only paired exact known misses; final candidate inherits none', () => {
|
|
const base = { misses: ['burst512:streaming120'] };
|
|
requireH5Candidate('cursor-nonregression', base, base);
|
|
const slowInvocation = { misses: ['invocation:observed-total-duration900'] };
|
|
requireH5Candidate('cursor-nonregression', slowInvocation, slowInvocation);
|
|
requireH5Candidate('final-b2', base, { misses: [] });
|
|
for (const candidate of [{ misses: ['paced:streaming120'] }, { misses: ['invocation:observed-total-duration900'] },
|
|
{ misses: ['operator-stop:streaming120'] }]) assert.throws(() => requireH5Candidate('cursor-nonregression', base, candidate));
|
|
assert.throws(() => requireH5Candidate('final-b2', base, base), /every original SLA/);
|
|
assert.throws(() => requireH5Candidate('final-b2', slowInvocation, slowInvocation), /every original SLA/);
|
|
});
|