Files
pi-extensions/test/aws-sso.test.mjs
T
pi@mbp-m1-2020 32a397edb4 aws-sso: renew just after the browser sign-in ends, not before; auto-continue
AWS: CLI sessions refresh only while the user interactive session (browser
sign-in) is valid; a login inside it is silent but inherits its end (green
box 'can be used until your session expires', 2026-10-11). Renewing early
gains nothing, so renew 2 min after the estimated end, forcing a fresh IdP
sign-in while the last 1-hour token still works. Silent renewals keep the
anchor. On an expired-session request failure, renew then send 'continue'
(once per 10 min). New: /aws-sso continue on|off, signed-in HH:MM.
2026-10-11 17:40:40 +02:00

131 lines
6.3 KiB
JavaScript

// Two-sided tests for the pure parts of extensions/aws-sso.ts (node >= 23 strips
// the types; the only pi import is type-only). Shapes observed 2026-10-10/11 on
// mbp-m1-2020.
import { test } from "node:test";
import assert from "node:assert/strict";
import {
fingerprint, cacheKeyFor, parseIni, resolveSsoSession, parseDeviceOutput,
isSsoExpiredError, assess, reanchor, fmtLeft, DEFAULTS,
} from "../extensions/aws-sso.ts";
const START = "https://d-1234567890.awsapps.com/start";
const HOST = "d-1234567890.awsapps.com";
const CONFIG = `
[profile Base]
sso_session = MySession
sso_account_id = 111111111111
sso_role_name = Dev
[sso-session MySession]
sso_start_url = ${START}
sso_region = eu-west-1
[profile Bedrock]
role_arn = arn:aws:iam::111111111111:role/X
source_profile = Base
[profile LoopA]
source_profile = LoopB
[profile LoopB]
source_profile = LoopA
[profile Legacy]
sso_start_url = ${START}
[profile Dangling]
sso_session = Nope
`;
test("fingerprint: 8 hex chars, stable, never the value", () => {
const v = "aoaAAAAAGh0dHBzOi8vZXhhbXBsZS5jb20v";
assert.match(fingerprint(v), /^[0-9a-f]{8}$/);
assert.equal(fingerprint(v), fingerprint(v));
assert.notEqual(fingerprint(v), fingerprint(v + "x"));
assert.ok(!v.includes(fingerprint(v)));
assert.equal(fingerprint(""), "-");
});
test("cache key = sha1(session name) — the measured botocore file name", () => {
assert.equal(cacheKeyFor("GFTLIntelligenceOnTap"), "c8ef84539c753bc7f3a20b5dceef5c2f03e744a7");
});
test("resolveSsoSession follows source_profile to the sso-session", () => {
const r = resolveSsoSession(CONFIG, "Bedrock");
assert.equal(r.sessionName, "MySession");
assert.equal(r.startUrl, START);
assert.deepEqual(r.chain, ["Bedrock", "Base"]);
});
test("resolveSsoSession refuses broken configs with a reason", () => {
assert.throws(() => resolveSsoSession(CONFIG, "LoopA"), /loop/);
assert.throws(() => resolveSsoSession(CONFIG, "Missing"), /not found/);
assert.throws(() => resolveSsoSession(CONFIG, "Legacy"), /legacy/);
assert.throws(() => resolveSsoSession(CONFIG, "Dangling"), /sso-session Nope/);
assert.deepEqual(parseIni("# c\n[profile A]\nk = v = w\n"), { "profile A": { k: "v = w" } });
});
const CLI = (code, host = HOST) => `Browser will not be automatically opened.
Please visit the following URL:
https://${host}/start/#/device
Then enter the code:
${code}
Alternatively, you may visit the following URL which will autofill the code upon loading:
https://${host}/start/#/device?user_code=${code}
`;
test("parseDeviceOutput: real shape accepted; foreign host / bad code refused", () => {
const r = parseDeviceOutput(CLI("PCDB-WSGH"), START);
assert.equal(r.code, "PCDB-WSGH");
assert.equal(new URL(r.url).host, HOST);
assert.equal(parseDeviceOutput(CLI("PCDB-WSGH", "evil.example.com"), START), null);
assert.equal(parseDeviceOutput(CLI("PCDB-WSGH", `${HOST}.evil.example.com`), START), null);
assert.equal(parseDeviceOutput(CLI("pcdb-wsgh"), START), null);
assert.equal(parseDeviceOutput("Browser will not be automatically opened.\n", START), null);
});
test("isSsoExpiredError: the real pi error matches; unrelated failures do not", () => {
assert.ok(isSsoExpiredError("Token is expired. To refresh this SSO session run 'aws sso login' with the corresponding profile.")); // measured 2026-10-11T14:18:08Z
assert.ok(isSsoExpiredError("The SSO session associated with this profile has expired or is otherwise invalid"));
assert.ok(isSsoExpiredError("ExpiredTokenException: The security token included in the request is expired"));
for (const m of ["Provider stopped with: content_filtered", "getaddrinfo ENOTFOUND bedrock-runtime.eu-west-1.amazonaws.com", "AccessDeniedException: not authorized", "ThrottlingException: Too many requests"]) {
assert.ok(!isSsoExpiredError(m), m);
}
});
const H = 3600e3;
const T0 = Date.UTC(2026, 9, 11, 14, 19, 0);
const tok = (fp, accessExp) => ({ refreshFp: fp, accessExpiresAt: accessExp });
test("assess: ok until the end, due just AFTER it (renew → fresh sign-in), expired once the 1-h token is gone too", () => {
const st = { anchor: { refreshFp: "aaaa1111", since: T0 } };
assert.equal(assess(st, tok("aaaa1111", T0 + H), T0 + 1 * H).status, "ok");
assert.equal(assess(st, tok("aaaa1111", T0 + 8.5 * H), T0 + 7.9 * H).status, "ok"); // early renewal gains nothing: do not renew before the end
assert.equal(assess(st, tok("aaaa1111", T0 + 8.5 * H), T0 + 8 * H + 60e3).status, "ok"); // inside the grace minutes
assert.equal(assess(st, tok("aaaa1111", T0 + 8.5 * H), T0 + 8 * H + DEFAULTS.renewAfterMinutes * 60e3).status, "due"); // 1-h token still valid: renew without an outage
assert.equal(assess(st, tok("aaaa1111", T0 + 8 * H), T0 + 8.2 * H).status, "expired");
assert.equal(assess(st, null, T0).status, "none");
assert.equal(assess({}, tok("aaaa1111", T0 + H), T0 + 30 * H).status, "ok"); // no anchor: never invent an end
assert.equal(assess(st, tok("bbbb2222", T0 + H), T0 + 30 * H).endsAt, null); // anchor belongs to another token
});
test("assess honours a configured session length", () => {
const st = { sessionHours: 72, anchor: { refreshFp: "aaaa1111", since: T0 } };
assert.equal(assess(st, tok("aaaa1111", T0 + 9 * H), T0 + 8.2 * H).status, "ok");
assert.equal(assess(st, tok("aaaa1111", T0 + 72.5 * H), T0 + 72.1 * H).status, "due");
});
test("reanchor: a new token BEFORE the end inherits it (silent renewal); AFTER the end or an expiry starts fresh", () => {
const st = { anchor: { refreshFp: "aaaa1111", since: T0 } };
assert.equal(reanchor(st, "aaaa1111", T0 + 3 * H, T0 + 3 * H), null); // hourly access refresh: same refresh token
assert.deepEqual(reanchor(st, "bbbb2222", T0 + 1 * H, T0 + 1 * H), { refreshFp: "bbbb2222", since: T0 }); // measured 2026-10-11: green box, same session
assert.deepEqual(reanchor(st, "bbbb2222", T0 + 8.05 * H, T0 + 8.05 * H), { refreshFp: "bbbb2222", since: T0 + 8.05 * H });
assert.deepEqual(reanchor({ ...st, sessionEnded: true }, "bbbb2222", T0 + 2 * H, T0 + 2 * H), { refreshFp: "bbbb2222", since: T0 + 2 * H });
assert.deepEqual(reanchor({ ...st, sessionHours: 1 }, "bbbb2222", T0 + 2 * H, T0 + 2 * H), { refreshFp: "bbbb2222", since: T0 + 2 * H });
assert.deepEqual(reanchor({}, "aaaa1111", T0, T0), { refreshFp: "aaaa1111", since: T0 });
});
test("fmtLeft", () => {
assert.equal(fmtLeft(-5), "0m");
assert.equal(fmtLeft(25 * 60e3), "25m");
assert.equal(fmtLeft(7 * H + 5 * 60e3), "7h05m");
});