__tests__/account-chooser.test.js
Rama main · réplica de lectura del repositorio original.
/*
* "WHICH ACCOUNT?" MUST NOT BE READ AS "SIGNED OUT", AND MUST NEVER BE ANSWERED BY GUESSING.
*
* From production: the gsc.audit job sat on accounts.google.com/v3/signin/accountchooser for 31
* minutes with zero steps while the owner was signed in the entire time. Two Gmail accounts live in
* that profile and one holds the property, so Google asked which — and because the chooser is served
* from the same host as the sign-in form, loginWall called it a wall. The audit then recorded "no
* access" on the property, which reads as a Google penalty rather than an unanswered question. That
* exact misreading has already produced one phantom penalty in this codebase.
*
* The second half matters more than the first: picking the wrong Google account is not a harmless
* retry, it reads a stranger's Search Console and files the numbers as ours. A wrong answer here is
* worse than no answer, so these tests pin that an unconfigured chooser REPORTS and never picks.
*/
import { describe, it, expect } from 'vitest';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const { isChooser, isSignIn, decide } = require('../src/accountChooser');
const CHOOSER = 'https://accounts.google.com/v3/signin/accountchooser?continue=https://search.google.com/search-console';
const SIGNIN = 'https://accounts.google.com/v3/signin/identifier?continue=https://search.google.com/';
describe('an account chooser is its own answer, not a missing login', () => {
it('recognises the chooser the audit actually sat on', () => {
expect(isChooser(CHOOSER)).toBe(true);
expect(isChooser('https://accounts.google.com/signin/selectaccount?prompt=select_account')).toBe(true);
expect(isChooser('https://accounts.google.com/AccountChooser?hl=pl')).toBe(true);
});
/* The distinction the whole fix rests on: same host, different question. */
it('does not confuse it with a sign-in form, which IS a missing session', () => {
expect(isChooser(SIGNIN)).toBe(false);
expect(isSignIn(SIGNIN)).toBe(true);
expect(isSignIn(CHOOSER)).toBe(false);
});
it('says nothing about pages that are neither', () => {
for (const u of ['https://search.google.com/search-console', 'https://search.google.com/search-console/about', '', null, undefined]) {
expect(isChooser(u)).toBe(false);
expect(isSignIn(u)).toBe(false);
}
});
it('is not fooled by another host that merely mentions the word', () => {
expect(isChooser('https://example.com/accountchooser')).toBe(false);
});
});
describe('which account is configured, never inferred', () => {
it('picks the configured account when the chooser is up', () => {
const d = decide(CHOOSER, 'owner@example.org');
expect(d.act).toBe('pick');
expect(d.email).toBe('owner@example.org');
});
/*
* THE IMPORTANT ONE. With two accounts and nothing configured, a flow that picks the first tile
* reads the wrong property and files it as ours. Reporting is the correct outcome.
*/
it('refuses to choose when no account is configured, and says why', () => {
const d = decide(CHOOSER, '');
expect(d.act).toBe('report');
expect(d.why).toMatch(/no account is configured/);
expect(d.why).toMatch(/wrong account/);
});
it('refuses a configured value that is not an address rather than hunting for it', () => {
const d = decide(CHOOSER, 'the main one');
expect(d.act).toBe('report');
expect(d.why).toMatch(/not an address/);
});
it('is case and whitespace tolerant about the configured address', () => {
expect(decide(CHOOSER, ' Owner@Example.ORG ').email).toBe('owner@example.org');
});
it('does nothing at all when the page is not a chooser, configured or not', () => {
expect(decide('https://search.google.com/search-console', 'owner@example.org').act).toBe('none');
expect(decide(SIGNIN, 'owner@example.org').act).toBe('none'); // a wall is the wall detector's job
});
});
/*
* ── A SIGNED-OUT PROFILE IS NOT A GOOGLE PENALTY, AND A RE-AUTH PROMPT IS NOT A STEP ───────────
*
* Measured 2026-09-21. The gsc.connect job spent 39 minutes walking Google's re-auth gauntlet,
* /signin/challenge/pwd then /signin/challenge/pk, holding the `google` profile the owner needed in
* order to sign in, and reporting nothing usable. The dashboard meanwhile showed "Je hebt geen
* toegang tot deze property" and raised a MANUAL ACTION flag, which is the phantom-penalty failure
* this codebase has already produced once: it could not open the manual-actions tab, so "we did not
* look" was rendered as an alarm and sent the owner hunting for a Google sanction.
*
* The cause sat one layer below the account chooser. That profile held 63 google.com cookies and
* not one auth cookie, so it was simply signed out. The owner's real session was in a different
* profile entirely.
*
* Both guards exist to turn a 39-minute stall into one accurate sentence, and neither may ever try
* to answer a password or passkey prompt: signing in is the owner's act, by hand, once.
*/
describe('signed out, challenged, or actually in', () => {
const CH_PWD = 'https://accounts.google.com/v3/signin/challenge/pwd?TL=ACv9tzFibv4YKbZh';
const CH_PK = 'https://accounts.google.com/v3/signin/challenge/pk?TL=ACv9tzFibv4YKbZh';
const INSIDE = 'https://search.google.com/search-console?resource_id=sc-domain%3Amy-app.engineer';
const ck = (names, domain) => names.map((n) => ({ name: n, domain: domain || '.google.com' }));
const LIVE = ck(['SID', 'HSID', 'SSID', 'APISID', 'SAPISID', '__Secure-1PSID', 'LSID']);
const { isChallenge, hasSession, googleState } = require('../src/accountChooser');
it('recognises the exact challenges the job was caught walking', () => {
expect(isChallenge(CH_PWD)).toBe(true);
expect(isChallenge(CH_PK)).toBe(true);
expect(isChallenge('https://accounts.google.com/v3/signin/challenge/totp')).toBe(true);
});
it('does not call the console or the chooser a challenge', () => {
expect(isChallenge(INSIDE)).toBe(false);
expect(isChallenge('https://accounts.google.com/v3/signin/accountchooser')).toBe(false);
expect(isChallenge('')).toBe(false);
});
/* THE MEASURED CASE: many google cookies, no auth cookie. 63 of them, and signed out. */
it('calls a jar with cookies but no auth cookie signed out', () => {
const junk = ck(['NID', 'AEC', '1P_JAR', 'CONSENT', 'OTZ', 'SEARCH_SAMESITE']);
const r = hasSession(junk);
expect(r.signedIn).toBe(false);
expect(r.found).toEqual([]);
});
it('calls a real session signed in', () => {
const r = hasSession(LIVE);
expect(r.signedIn).toBe(true);
expect(r.found).toContain('SAPISID');
});
it('ignores another site cookies named the same', () => {
expect(hasSession(ck(['SID', 'SAPISID'], '.example.com')).signedIn).toBe(false);
});
it('survives an empty or missing jar instead of throwing mid-run', () => {
for (const j of [[], null, undefined, [null, {}]]) expect(hasSession(j).signedIn).toBe(false);
});
/*
* THE POINT OF ALL OF IT: the sentence handed back must say a login is missing and must NOT read
* as a Google penalty, because that misreading already shut the whole search channel once.
*/
it('blames the missing login, not Google, and says who to sign in as', () => {
const r = googleState(INSIDE, [], 'owner.example@example.org');
expect(r.state).toBe('signed-out');
expect(r.why).toMatch(/no Google session/i);
expect(r.why).toMatch(/by hand/i);
expect(r.why).toContain('owner.example@example.org');
expect(r.why).toMatch(/not a Google permission problem/i);
});
it('reports a challenge as a wall to be signed in by hand, never pushed through', () => {
const r = googleState(CH_PK, LIVE, 'owner.example@example.org');
expect(r.state).toBe('challenge');
expect(r.why).toMatch(/re-verify/i);
expect(r.why).toMatch(/by hand/i);
});
/* Signed out beats everything: there is no point naming a chooser we will never reach. */
it('answers signed-out first, even when the url looks like a chooser', () => {
expect(googleState('https://accounts.google.com/v3/signin/accountchooser', [], '').state).toBe('signed-out');
});
it('says plainly when it is actually inside', () => {
const r = googleState(INSIDE, LIVE, 'owner.example@example.org');
expect(r.state).toBe('in');
expect(r.why).toBe('');
});
it('still points at the chooser when signed in and asked which account', () => {
const r = googleState('https://accounts.google.com/v3/signin/accountchooser', LIVE, '');
expect(r.state).toBe('chooser');
});
});