← back to Dw Activation Debug TK11314
test/vision-http.test.js
70 lines
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const { EventEmitter } = require('node:events');
const { errorDetails, cooldownMs, requestVision } = require('../lib/vision-http');
const { SettlementGate } = require('../lib/settlement-gate');
const gate = () => Object.assign(Object.create(SettlementGate.prototype), {
lockOk: true, geminiKeys: ['test-key-one', 'test-key-two'], visionRetryAt: 0,
visionHttpAttempts: 0, visionThrottledSkips: 0, lastVisionError: null,
});
test('upstream quota details and retry delay retained with secrets redacted', () => {
const details = errorDetails({ status: 429, body: JSON.stringify({ error: {
status: 'RESOURCE_EXHAUSTED', message: 'quota exhausted key=secret-one https://api.test/?key=secret-two',
details: [{ '@type': 'type.googleapis.com/google.rpc.RetryInfo', retryDelay: '70s' },
{ '@type': 'type.googleapis.com/google.rpc.QuotaFailure', violations: [{ quotaId: 'RequestsPerDay', quotaValue: '20' }] }],
} }) }, ['secret-one', 'secret-two']);
assert.equal(details.retryAfterMs, 70000);
assert.equal(details.quota[0].value, '20');
assert.ok(!JSON.stringify(details).includes('secret-'));
assert.equal(cooldownMs(details), 3600000);
});
test('429 across existing keys opens circuit; later drafts stay HELD without downloads', async () => {
const g = gate(); let calls = 0, downloads = 0;
g._visionCall = async () => { calls++; return { ok: false, why: 'vision-http-429', details: { httpStatus: 429 } }; };
g.fetchImageB64 = async () => { downloads++; return 'bytes'; };
assert.equal((await g.visionDetect('bytes', 'image/jpeg')).ok, false);
const result = await g.evaluate({ title: 'Tropical Birds', material: 'paper', imageUrl: 'https://example.test/p.jpg' });
assert.equal(result.verdict, 'HELD'); assert.equal(result.cost, 0);
assert.equal(calls, 2); assert.equal(downloads, 0);
assert.equal(result.reason, 'vision-rate-limit-cooldown');
});
test('cooldown expiration retries service; no fail-open during outage', async () => {
const g = gate(); g.visionRetryAt = Date.now() - 1;
g._visionCall = async () => ({ ok: true, partA: true, partB: true });
const v = await g.visionDetect('bytes', 'image/jpeg');
assert.equal(g.verdictFromVision(v).verdict, 'BLOCK');
assert.equal(g.visionRetryAt, 0);
});
test('authentication and malformed response errors never fall through as PASS', async () => {
for (const why of ['vision-http-401', 'vision-http-403', 'vision-parse', 'incomplete-verdict']) {
const g = gate(); let calls = 0;
g._visionCall = async () => { calls++; return { ok: false, why }; };
assert.equal((await g.visionDetect('x', 'image/jpeg')).ok, false);
assert.equal(calls, 1);
}
});
test('HTTP request uses header auth and propagates response details', async () => {
let options;
const request = (opts, callback) => {
options = opts;
const req = new EventEmitter(); req.setTimeout = () => {};
req.end = () => { const res = new EventEmitter(); res.statusCode = 429; res.headers = { 'retry-after': '90' };
callback(res); res.emit('data', '{"error":{"status":"RESOURCE_EXHAUSTED"}}'); res.emit('end'); };
return req;
};
const response = await requestVision({}, 'fake-secret', { request });
assert.ok(!options.path.includes('fake-secret')); assert.equal(options.headers['x-goog-api-key'], 'fake-secret');
assert.equal(errorDetails(response).retryAfterMs, 90000);
});
test('timeout resolves as an unavailable service rather than hanging', async () => {
const request = () => {
const req = new EventEmitter(); let timeout;
req.setTimeout = (_, callback) => { timeout = callback; };
req.destroy = error => req.emit('error', error);
req.end = () => timeout(); return req;
};
const response = await requestVision({}, 'fake-secret', { request });
assert.equal(response.status, 0); assert.equal(response.err, 'vision-request-timeout');
});