__tests__/pulse-client.test.js
Rama main · réplica de lectura del repositorio original.
/*
* FILING WHAT THE BROWSER READ, WITHOUT A COURIER.
*
* The old chain was: the browser reads Search Console and stores findings on the job, the master
* polls the job and forwards them to Pulse. The middle step was the single point of failure — a
* locked-out walk spent the master's whole daily audit slot, and nothing reached Pulse for eleven
* days while the screen showed a 2026-09-10 reading as if it were current.
*
* So the browser files directly. These tests pin the two things that decide whether that is safe:
*
* NOTHING THROWS UPWARD. A watcher's job is to read the console and show what it read. If Pulse is
* unconnected, unreachable, or refuses the key, the walk still happened and the findings are still
* worth showing. Every call answers with what happened rather than failing the pass.
*
* "FILED" IS A NUMBER THAT MEANS SOMETHING. Pulse silently skips a finding with no kind or no
* label, so sending one and reporting success would make the count a lie. They are counted here.
*/
import { describe, it, expect } from 'vitest';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const pulse = require('../src/pulse');
const WIRED = { PULSE_OPS_URL: 'http://pulse:3000', PULSE_REPORTER_KEY: 'k_reporter' };
/*
* THE REAL DOOR'S SHAPE. POST /v1/ops/:name answers { ok, operation, effect, result } — the payload is
* one level down. A fake that hands the payload back flat is why a client that could not read Pulse at
* all passed twenty-one tests: it answered the shape I had assumed instead of the shape Pulse sends.
*/
const ok = (result, name = 'op') => async () => ({
ok: true, status: 200,
text: async () => JSON.stringify({ ok: true, operation: name, effect: 'read', result }),
});
/* A door that does NOT wrap (an older build, another service) must still be read. */
const okFlat = (body) => async () => ({ ok: true, status: 200, text: async () => JSON.stringify(body) });
describe('is Pulse connected to this browser at all', () => {
it('needs both halves — a URL with no key is a 401 waiting to happen', () => {
expect(pulse.wired(WIRED)).toBe(true);
expect(pulse.wired({ PULSE_OPS_URL: 'http://pulse:3000' })).toBe(false);
expect(pulse.wired({ PULSE_REPORTER_KEY: 'k' })).toBe(false);
expect(pulse.wired({})).toBe(false);
});
it('says WHICH half is missing, rather than a silent false', () => {
expect(pulse.why({})).toMatch(/not connected/i);
expect(pulse.why({ PULSE_REPORTER_KEY: 'k' })).toMatch(/no address/i);
expect(pulse.why({ PULSE_OPS_URL: 'http://pulse:3000' })).toMatch(/no reporter key/i);
expect(pulse.why(WIRED)).toBe('');
});
/*
* PULSE_URL is the APP door's name and on this cluster points at a service that does not resolve,
* so the operator URL is preferred and PULSE_URL is only ever a last resort.
*/
it('prefers the operator address over the app one', () => {
expect(pulse.opsUrl({ PULSE_OPS_URL: 'http://pulse:3000', PULSE_URL: 'http://broken:3000' })).toBe('http://pulse:3000');
expect(pulse.opsUrl({ PULSE_URL: 'http://fallback:3000' })).toBe('http://fallback:3000');
});
it('never reads the app key as a credential', () => {
expect(pulse.opsKey({ PULSE_APP_KEY: 'pls_app' })).toBe('');
expect(pulse.wired({ PULSE_OPS_URL: 'http://pulse:3000', PULSE_APP_KEY: 'pls_app' })).toBe(false);
});
it('tolerates a trailing slash on the address', () => {
expect(pulse.opsUrl({ PULSE_OPS_URL: 'http://pulse:3000/' })).toBe('http://pulse:3000');
});
});
describe('the rows Pulse will actually store', () => {
it('drops what Pulse would drop, so the count is honest', () => {
const rows = pulse.rowsFor([
{ kind: 'indexing', label: 'not indexed', value: '12' },
{ kind: 'manual_action', label: '' }, // no label: Pulse skips it
{ label: 'orphan', value: 'x' }, // no kind: Pulse skips it
null,
]);
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({ kind: 'indexing', label: 'not indexed', value: '12' });
});
it('keeps every kind the console actually has', () => {
for (const k of ['message', 'indexing', 'sitemap', 'manual_action', 'vitals']) {
expect(pulse.rowsFor([{ kind: k, label: 'x' }])[0].kind).toBe(k);
}
});
/* An unknown kind is data we still want, filed under the one that means "about the pages". */
it('files an unknown kind rather than losing the finding', () => {
expect(pulse.rowsFor([{ kind: 'weather', label: 'x' }])[0].kind).toBe('indexing');
});
it('clamps to the lengths Pulse stores, instead of being truncated silently there', () => {
const r = pulse.rowsFor([{ kind: 'indexing', label: 'L'.repeat(400), value: 'V'.repeat(400), detail: 'D'.repeat(900) }])[0];
expect(r.label).toHaveLength(200);
expect(r.value).toHaveLength(200);
expect(r.detail).toHaveLength(600);
});
it('turns a missing value into an empty string, never the word undefined', () => {
expect(pulse.rowsFor([{ kind: 'sitemap', label: 'sitemap.xml' }])[0].value).toBe('');
});
});
describe('filing, and every way it can fail without failing the pass', () => {
it('files and reports how many Pulse saved', async () => {
const r = await pulse.recordGscHealth('my-app.engineer',
[{ kind: 'indexing', label: 'indexed', value: '4' }],
{ env: WIRED, fetch: ok({ app: 'my-app.engineer', day: '2026-09-21', saved: 1 }) });
expect(r).toMatchObject({ ok: true, filed: 1, skipped: 0 });
});
it('counts what it skipped, so "filed 1" is not mistaken for "read 2"', async () => {
const r = await pulse.recordGscHealth('my-app.engineer',
[{ kind: 'indexing', label: 'indexed', value: '4' }, { kind: 'indexing' }],
{ env: WIRED, fetch: ok({ saved: 1 }) });
expect(r).toMatchObject({ filed: 1, skipped: 1 });
});
it('does not throw when Pulse was never connected', async () => {
const r = await pulse.recordGscHealth('my-app.engineer', [{ kind: 'indexing', label: 'indexed' }], { env: {} });
expect(r.ok).toBe(false);
expect(r.wired).toBe(false);
expect(r.why).toMatch(/not connected/i);
});
it('does not throw when Pulse is unreachable, and says so', async () => {
const r = await pulse.recordGscHealth('my-app.engineer', [{ kind: 'indexing', label: 'indexed' }], {
env: WIRED, fetch: async () => { throw new Error('getaddrinfo ENOTFOUND pulse'); },
});
expect(r.ok).toBe(false);
expect(r.wired).toBe(true);
expect(r.why).toMatch(/could not reach Pulse/);
});
it('does not throw when Pulse refuses the key, and keeps its words', async () => {
const r = await pulse.recordGscHealth('my-app.engineer', [{ kind: 'indexing', label: 'indexed' }], {
env: WIRED,
fetch: async () => ({ ok: false, status: 403, text: async () => JSON.stringify({ error: 'an app key only reports events' }) }),
});
expect(r.ok).toBe(false);
expect(r.status).toBe(403);
expect(r.why).toMatch(/an app key only reports events/);
});
it('does not call Pulse at all when there is nothing worth filing', async () => {
let called = 0;
const r = await pulse.recordGscHealth('my-app.engineer', [{ kind: 'indexing' }], {
env: WIRED, fetch: async () => { called += 1; return ok({})(); },
});
expect(called).toBe(0);
expect(r).toMatchObject({ ok: false, filed: 0, skipped: 1 });
expect(r.why).toMatch(/nothing worth filing/);
});
it('sends the day in the only format Pulse accepts', async () => {
let sent = null;
await pulse.recordGscHealth('my-app.engineer', [{ kind: 'indexing', label: 'indexed' }], {
env: WIRED,
fetch: async (url, init) => { sent = JSON.parse(init.body); return ok({ saved: 1 })(); },
});
expect(sent.day).toMatch(/^\d{4}-\d{2}-\d{2}$/);
expect(sent.app).toBe('my-app.engineer');
});
it('carries the reporter key as a bearer token, and the app key never', async () => {
let hdr = null;
await pulse.recordGscHealth('a', [{ kind: 'indexing', label: 'x' }], {
env: { ...WIRED, PULSE_APP_KEY: 'pls_app' },
fetch: async (url, init) => { hdr = init.headers.authorization; return ok({ saved: 1 })(); },
});
expect(hdr).toBe('Bearer k_reporter');
expect(hdr).not.toMatch(/pls_app/);
});
});
describe('is Pulse up to date — read back, never assumed', () => {
it('reports the newest day Pulse actually holds', async () => {
const r = await pulse.gscHealth('my-app.engineer', {
env: WIRED,
fetch: ok({ findings: [{ kind: 'indexing', label: 'indexed', day: '2026-09-10' }, { kind: 'sitemap', label: 's', day: '2026-09-21' }] }),
});
expect(r.ok).toBe(true);
expect(r.latestDay).toBe('2026-09-21');
expect(r.findings).toHaveLength(2);
});
it('says nothing rather than something when it cannot read', async () => {
const r = await pulse.gscHealth('my-app.engineer', { env: {} });
expect(r.ok).toBe(false);
expect(r.latestDay).toBe(null);
expect(r.findings).toEqual([]);
});
/* Eleven days of a stale reading shown as current is what this rule exists to prevent. */
it('calls only today up to date', () => {
const now = new Date('2026-09-21T18:00:00Z');
expect(pulse.upToDate('2026-09-21', now)).toBe(true);
expect(pulse.upToDate('2026-09-10', now)).toBe(false);
expect(pulse.upToDate(null, now)).toBe(false);
expect(pulse.upToDate('', now)).toBe(false);
});
});
/*
* THE ENVELOPE. Pulse's operator door wraps every answer: { ok, operation, effect, result }. Reading
* it flat does not fail — it succeeds and reports nothing, which is how a store holding six findings
* from today came back as "Pulse holds nothing yet". These two are the cheapest guard against a
* confident wrong answer, and the reason the fake above mirrors the real shape.
*/
describe('the shape Pulse actually answers in', () => {
it('reads the payload out of result, not off the envelope', async () => {
const r = await pulse.gscHealth('my-app.engineer', {
env: WIRED,
fetch: ok({ app: 'my-app-engineer', findings: [{ kind: 'sitemap', label: 's', day: '2026-09-21' }] }, 'gsc_health'),
});
expect(r.findings).toHaveLength(1);
expect(r.latestDay).toBe('2026-09-21');
});
it('counts what Pulse saved, which is also inside result', async () => {
const r = await pulse.recordGscHealth('my-app.engineer', [{ kind: 'indexing', label: 'indexed', value: '4' }], {
env: WIRED, fetch: ok({ app: 'my-app-engineer', day: '2026-09-21', saved: 1 }, 'record_gsc_health'),
});
expect(r).toMatchObject({ ok: true, filed: 1 });
});
it('still reads a door that answers flat', async () => {
const r = await pulse.gscHealth('my-app.engineer', {
env: WIRED, fetch: okFlat({ findings: [{ kind: 'sitemap', label: 's', day: '2026-09-20' }] }),
});
expect(r.findings).toHaveLength(1);
expect(r.latestDay).toBe('2026-09-20');
});
/* An empty result is an answer, not a parse failure: the property is simply not filed yet. */
it('reads an empty result as nothing held, without erroring', async () => {
const r = await pulse.gscHealth('my-app.engineer', { env: WIRED, fetch: ok({ app: 'x', findings: [] }, 'gsc_health') });
expect(r.ok).toBe(true);
expect(r.latestDay).toBe(null);
expect(r.findings).toEqual([]);
});
});