__tests__/gsc-pages.test.js
Rama main · réplica de lectura del repositorio original.
/*
* THE ADDRESSES BEHIND A REASON — the half of the reading that was missing.
*
* Pulse showed "Geblokkeerd door robots.txt = 9" for days. True, and useless: nobody can fix a
* number. The nine URLs exist, one click deeper in the console, and the walk never went there.
*
* Two of these tests exist because of traps that were sitting in the way:
*
* THE DUPLICATE CHECK WOULD HAVE EATEN THEM. The walk records the reason row first, then opens it
* and records the URLs behind it — same kind, same label, same value. addGscHealth refuses a
* finding it already has, so that second call was thrown away, and with it the only actionable
* part. A repeat that carries addresses is not a repeat.
*
* THE COLLECTOR IS LAST-WINS, WHICH WOULD HAVE DROPPED THEM TOO. The count moving from 9 to 8 must
* update the row, but it must not lose the addresses read on the earlier call, because the row and
* its addresses arrive separately.
*/
import { describe, it, expect } from 'vitest';
import { createRequire } from 'module';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const require = createRequire(import.meta.url);
// jobs.js resolves its directory at load time and persists every change to disk. Point it at a
// temp dir BEFORE requiring it, so a test never writes into a real /profiles/jobs.
process.env.PROFILE_DIR = join(tmpdir(), `gb-gsc-pages-test-${process.pid}`);
const gsc = require('../src/gscWatch');
const jobsMod = require('../src/jobs');
const PROPERTY = 'sc-domain:my-app.engineer';
const runWith = (...jobIds) => ({ steps: jobIds.map((id) => ({ output: { __jobId: id } })) });
const jobs = (map) => (id) => map[id] || null;
const REASON = 'Geblokkeerd door robots.txt';
const NINE = [
'https://my-app.engineer/api/me/export',
'https://my-app.engineer/api/showcase',
'https://my-app.engineer/api/explore/screenshot?owner=carla&app=shop',
];
describe('a finding carries the addresses behind it', () => {
it('the collector keeps pages, and merges the ones that came in a separate call', () => {
const job = {
gscHealth: [
{ kind: 'indexing', label: REASON, value: '9', detail: 'ours to fix' },
{ kind: 'indexing', label: REASON, value: '9', pages: NINE },
],
};
const [found] = gsc.findingsOf(runWith('j1'), jobs({ j1: job }));
expect(found.label).toBe(REASON);
expect(found.value).toBe('9');
expect(found.detail).toBe('ours to fix');
expect(found.pages).toEqual(NINE);
});
it('a later pass with a NEW count keeps the addresses the earlier one read', () => {
// The row and its addresses arrive in two calls; last-wins on the value must not mean
// last-wins on the pages, or a changed count silently empties the only useful part.
const job = {
gscHealth: [
{ kind: 'indexing', label: REASON, value: '9', pages: NINE },
{ kind: 'indexing', label: REASON, value: '8' },
],
};
const [found] = gsc.findingsOf(runWith('j1'), jobs({ j1: job }));
expect(found.value).toBe('8');
expect(found.pages).toEqual(NINE);
});
it('a finding with no pages still works and simply has none', () => {
const job = { gscHealth: [{ kind: 'manual_action', label: 'manual actions', value: 'Geen problemen' }] };
const [found] = gsc.findingsOf(runWith('j1'), jobs({ j1: job }));
expect(found.pages).toEqual([]);
});
});
describe('what Pulse is shown', () => {
const rowFor = (pages) => gsc.feedRowsFor(
[{ kind: 'indexing', label: REASON, value: String(pages.length), detail: '', pages }],
{ property: PROPERTY },
)[0];
it('the addresses are in the fields, numbered, with a count', () => {
const row = rowFor(NINE);
expect(row.fields.pages).toBe('3');
expect(row.fields['page 1']).toBe(NINE[0]);
expect(row.fields['page 3']).toBe(NINE[2]);
});
it('the title stays the reason alone — the key must not move when the addresses do', () => {
// A title carrying the count or the URLs makes every pass a new row, and the page fills with
// history instead of showing the current state. This is the same rule the counts already follow.
const one = rowFor([NINE[0]]);
const three = rowFor(NINE);
expect(one.title).toBe(REASON);
expect(three.title).toBe(REASON);
expect(one.url).toBe(three.url);
});
it('a long list is capped and says how many it did not list', () => {
const many = Array.from({ length: 40 }, (_, i) => `https://my-app.engineer/learn/page-${i}`);
const row = rowFor(many);
expect(row.fields.pages).toBe('40');
expect(row.fields['page 25']).toBe(many[24]);
expect(row.fields['page 26']).toBeUndefined();
expect(row.fields['and more']).toMatch(/15 further/);
});
it('a finding without addresses gets no pages fields at all', () => {
const row = rowFor([]);
expect(row.fields.pages).toBeUndefined();
expect(row.fields['page 1']).toBeUndefined();
});
});
describe('storing them on the job', () => {
const freshJob = () => ({ id: 'job-1', gscHealth: [], steps: [] });
it('a repeat that carries addresses is NOT a duplicate — it fills them in', () => {
const j = freshJob();
const first = jobsMod.addGscHealth(j, { kind: 'indexing', label: REASON, value: '9' });
expect(first).toBeTruthy();
expect(first.pages).toEqual([]);
const second = jobsMod.addGscHealth(j, { kind: 'indexing', label: REASON, value: '9', pages: NINE });
expect(second, 'the addresses must not be refused as a duplicate').toBeTruthy();
expect(second.pages).toEqual(NINE);
expect(j.gscHealth).toHaveLength(1); // one finding, now complete — not two
});
it('a true repeat, with nothing new, is still refused', () => {
const j = freshJob();
jobsMod.addGscHealth(j, { kind: 'indexing', label: REASON, value: '9', pages: NINE });
expect(jobsMod.addGscHealth(j, { kind: 'indexing', label: REASON, value: '9', pages: NINE })).toBeNull();
expect(j.gscHealth).toHaveLength(1);
});
it('only real addresses are kept — a count or a label is not a URL', () => {
const j = freshJob();
const r = jobsMod.addGscHealth(j, {
kind: 'indexing', label: REASON, value: '9',
pages: ['https://my-app.engineer/a', '9', 'Geblokkeerd door robots.txt', '', null,
'/relative/path', 'https://my-app.engineer/a'],
});
expect(r.pages).toEqual(['https://my-app.engineer/a']);
});
});