418 lines
13 KiB
Python
418 lines
13 KiB
Python
"""Behavioural checks for #5001 active-profile recovery."""
|
|
|
|
import json
|
|
import subprocess
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
BOOT_JS = ROOT / "static" / "boot.js"
|
|
NODE = shutil.which("node")
|
|
BOOT_MARKER_KEY = "hermes-webui-active-profile-bootstrap-401"
|
|
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
NODE is None,
|
|
reason="node is required to execute boot.js recovery-path behavior",
|
|
)
|
|
|
|
|
|
_BOOT_DRIVER = r"""
|
|
const fs = require('fs');
|
|
const bootSrc = fs.readFileSync(process.argv[2], 'utf8');
|
|
const scenario = JSON.parse(process.argv[3] || '{}');
|
|
|
|
function extractFunction(source, name) {
|
|
const marker = `async function ${name}`;
|
|
const start = source.indexOf(marker);
|
|
if (start < 0) {
|
|
throw new Error(`missing function: ${name}`);
|
|
}
|
|
const end = source.indexOf('\n // Fetch active profile', start);
|
|
if (end < 0) {
|
|
throw new Error(`missing marker following function: ${name}`);
|
|
}
|
|
return source.slice(start, end);
|
|
}
|
|
|
|
function extractBlock(source, startMarker, endMarker) {
|
|
const start = source.indexOf(startMarker);
|
|
if (start < 0) {
|
|
throw new Error(`missing block start: ${startMarker}`);
|
|
}
|
|
const end = source.indexOf(endMarker, start);
|
|
if (end < 0) {
|
|
throw new Error(`missing block end: ${endMarker}`);
|
|
}
|
|
return source.slice(start, end);
|
|
}
|
|
|
|
class FakeStorage {
|
|
constructor(seed = {}) {
|
|
this.store = { ...seed };
|
|
}
|
|
|
|
getItem(key) {
|
|
return Object.prototype.hasOwnProperty.call(this.store, key)
|
|
? this.store[key]
|
|
: null;
|
|
}
|
|
|
|
setItem(key, value) {
|
|
this.store[key] = String(value);
|
|
}
|
|
|
|
removeItem(key) {
|
|
delete this.store[key];
|
|
}
|
|
|
|
snapshot() {
|
|
return { ...this.store };
|
|
}
|
|
}
|
|
|
|
function makeAttempt(attempt) {
|
|
return async function () {
|
|
if (attempt.type === 'success') {
|
|
const payload = attempt.payload || {name: 'default', is_default: true};
|
|
return payload;
|
|
}
|
|
if (attempt.type === 'return') {
|
|
return attempt.value;
|
|
}
|
|
if (attempt.type === 'undefined') {
|
|
return undefined;
|
|
}
|
|
const error = new Error(attempt.message || 'active profile bootstrap failure');
|
|
if (attempt.status !== undefined) error.status = attempt.status;
|
|
throw error;
|
|
};
|
|
}
|
|
|
|
function makeFetchResponse(attempt) {
|
|
const status = attempt.status === undefined ? 200 : attempt.status;
|
|
const payload = Object.prototype.hasOwnProperty.call(attempt, 'payload')
|
|
? attempt.payload
|
|
: {name: 'default', is_default: true};
|
|
const textBody = Object.prototype.hasOwnProperty.call(attempt, 'text')
|
|
? attempt.text
|
|
: JSON.stringify(payload);
|
|
return {
|
|
ok: status >= 200 && status < 300,
|
|
status,
|
|
statusText: attempt.statusText || '',
|
|
headers: {
|
|
get(name) {
|
|
return String(name).toLowerCase() === 'content-type'
|
|
? attempt.contentType || 'application/json'
|
|
: '';
|
|
},
|
|
},
|
|
json: async () => payload,
|
|
text: async () => textBody,
|
|
};
|
|
}
|
|
|
|
function makeApiAttempt(attempt) {
|
|
return async function (_path, opts = {}) {
|
|
if (!opts || opts.redirect401 !== false) {
|
|
throw new Error('expected redirect401:false for active-profile bootstrap');
|
|
}
|
|
const response = makeFetchResponse(attempt);
|
|
if (response.status === 401) {
|
|
return undefined;
|
|
}
|
|
if (!response.ok) {
|
|
const text = await response.text();
|
|
let message = text;
|
|
try {
|
|
const parsed = JSON.parse(text);
|
|
message = parsed.error || parsed.message || text;
|
|
} catch (_) {}
|
|
const error = new Error(message);
|
|
error.status = response.status;
|
|
error.statusText = response.statusText;
|
|
error.body = text;
|
|
throw error;
|
|
}
|
|
const ct = response.headers.get('content-type') || '';
|
|
return ct.includes('application/json')
|
|
? await response.json()
|
|
: await response.text();
|
|
};
|
|
}
|
|
|
|
const budgetBlock = extractBlock(
|
|
bootSrc,
|
|
' const _bootActiveProfileUnauthRedirectBudget=(()=>{',
|
|
' // Fetch active profile'
|
|
);
|
|
eval(budgetBlock.replace(
|
|
' const _bootActiveProfileUnauthRedirectBudget=(()=>{',
|
|
' globalThis._bootActiveProfileUnauthRedirectBudget=(()=>{'
|
|
));
|
|
eval(extractFunction(bootSrc, '_resolveActiveProfileBootstrapState'));
|
|
const bootActiveProfileBlock = extractBlock(
|
|
bootSrc,
|
|
'const activeProfileState = await _resolveActiveProfileBootstrapState();',
|
|
'\n // Update profile chip label immediately'
|
|
);
|
|
const runBootActiveProfileBlock = new Function(
|
|
'resolveState',
|
|
'S',
|
|
'applyBotName',
|
|
`
|
|
const _resolveActiveProfileBootstrapState = resolveState;
|
|
return (async () => {
|
|
${bootActiveProfileBlock}
|
|
return {continued: true};
|
|
})();
|
|
`
|
|
);
|
|
|
|
(async () => {
|
|
const attempts = Array.isArray(scenario.attempts) ? scenario.attempts : [];
|
|
const markerKey =
|
|
scenario.markerKey || 'hermes-webui-active-profile-bootstrap-401';
|
|
const storage = new FakeStorage(scenario.initialStorage || {});
|
|
const redirectUrls = [];
|
|
const results = [];
|
|
const storageHistory = [];
|
|
|
|
for (const attempt of attempts) {
|
|
const nextUrl = attempt.nextUrl || '/';
|
|
const originalApi = globalThis.api;
|
|
const originalWindow = globalThis.window;
|
|
const originalLocation = globalThis.location;
|
|
const originalDocument = globalThis.document;
|
|
let state;
|
|
|
|
try {
|
|
if (scenario.useDefaultLoader) {
|
|
globalThis.api = makeApiAttempt(attempt);
|
|
state = await _resolveActiveProfileBootstrapState({
|
|
markerStorage: storage,
|
|
markerKey,
|
|
getNextUrl: () => nextUrl,
|
|
redirectToLogin: (value) => {
|
|
const href = `login?next=${encodeURIComponent(value)}`;
|
|
redirectUrls.push(href);
|
|
},
|
|
});
|
|
} else {
|
|
state = await _resolveActiveProfileBootstrapState({
|
|
loadActiveProfile: makeAttempt(attempt),
|
|
markerStorage: storage,
|
|
markerKey,
|
|
getNextUrl: () => nextUrl,
|
|
redirectToLogin: (value) => {
|
|
redirectUrls.push(`login?next=${encodeURIComponent(value)}`);
|
|
},
|
|
});
|
|
}
|
|
} finally {
|
|
globalThis.api = originalApi;
|
|
globalThis.window = originalWindow;
|
|
globalThis.location = originalLocation;
|
|
globalThis.document = originalDocument;
|
|
}
|
|
|
|
const bootState = {};
|
|
let applyBotNameCalls = 0;
|
|
const bootResult = await runBootActiveProfileBlock(
|
|
async () => state,
|
|
bootState,
|
|
() => {
|
|
applyBotNameCalls += 1;
|
|
}
|
|
);
|
|
|
|
results.push({
|
|
...state,
|
|
bootContinues: !!(bootResult && bootResult.continued === true),
|
|
bootProfile: Object.prototype.hasOwnProperty.call(bootState, 'activeProfile')
|
|
? bootState.activeProfile
|
|
: null,
|
|
bootIsDefault: Object.prototype.hasOwnProperty.call(
|
|
bootState,
|
|
'activeProfileIsDefault'
|
|
)
|
|
? bootState.activeProfileIsDefault
|
|
: null,
|
|
applyBotNameCalls,
|
|
});
|
|
storageHistory.push(storage.snapshot());
|
|
}
|
|
|
|
console.log(
|
|
JSON.stringify({
|
|
attempts: results,
|
|
redirects: redirectUrls,
|
|
storageHistory,
|
|
storageSnapshot: storage.snapshot(),
|
|
loadCalls: attempts.length,
|
|
})
|
|
);
|
|
})();
|
|
"""
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def driver_path(tmp_path_factory):
|
|
path = tmp_path_factory.mktemp("boot-profile-driver") / "boot_profile_driver.js"
|
|
path.write_text(_BOOT_DRIVER, encoding="utf-8")
|
|
return str(path)
|
|
|
|
|
|
def _run_boot_profile_scenario(driver_path, scenario):
|
|
process = subprocess.run(
|
|
[NODE, driver_path, str(BOOT_JS), json.dumps(scenario)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
check=False,
|
|
)
|
|
if process.returncode != 0:
|
|
raise RuntimeError(f"node profile driver failed: {process.stderr.strip()}")
|
|
return json.loads(process.stdout)
|
|
|
|
|
|
def test_active_profile_boot_recovery_is_one_shot_and_bounded(driver_path):
|
|
payload = _run_boot_profile_scenario(
|
|
driver_path,
|
|
{
|
|
"markerKey": "test-5001-active-profile-recovery",
|
|
"useDefaultLoader": True,
|
|
"attempts": [
|
|
{"status": 401, "payload": {"error": "Authentication required"}, "nextUrl": "/"},
|
|
{"status": 401, "payload": {"error": "Authentication required"}, "nextUrl": "/"},
|
|
],
|
|
},
|
|
)
|
|
|
|
assert payload["attempts"][0]["status"] == "recovery-redirect"
|
|
assert payload["attempts"][0]["bootContinues"] is False
|
|
assert payload["attempts"][0]["bootProfile"] is None
|
|
assert payload["attempts"][0]["bootIsDefault"] is None
|
|
assert payload["attempts"][0]["applyBotNameCalls"] == 0
|
|
assert payload["attempts"][1]["status"] == "fallback"
|
|
assert payload["attempts"][1]["bootContinues"] is True
|
|
assert payload["attempts"][1]["profile"] == "default"
|
|
assert payload["attempts"][1]["isDefault"] is True
|
|
assert payload["attempts"][1]["bootProfile"] == "default"
|
|
assert payload["attempts"][1]["bootIsDefault"] is True
|
|
assert payload["attempts"][1]["applyBotNameCalls"] == 1
|
|
assert payload["loadCalls"] == 2
|
|
assert payload["redirects"] == ["login?next=%2F"]
|
|
assert payload["storageHistory"][0].get(BOOT_MARKER_KEY) == "1"
|
|
assert payload["storageHistory"][1].get(BOOT_MARKER_KEY) is None
|
|
assert payload["storageSnapshot"] == {}
|
|
|
|
|
|
def test_active_profile_boot_recovery_handles_loader_thrown_401s(driver_path):
|
|
payload = _run_boot_profile_scenario(
|
|
driver_path,
|
|
{
|
|
"markerKey": "test-5001-active-profile-recovery-throws",
|
|
"attempts": [
|
|
{"type": "error", "status": 401, "nextUrl": "/"},
|
|
{"type": "error", "status": 401, "nextUrl": "/"},
|
|
],
|
|
},
|
|
)
|
|
|
|
assert payload["attempts"][0]["status"] == "recovery-redirect"
|
|
assert payload["attempts"][0]["bootContinues"] is False
|
|
assert payload["attempts"][0]["bootProfile"] is None
|
|
assert payload["attempts"][0]["bootIsDefault"] is None
|
|
assert payload["attempts"][0]["applyBotNameCalls"] == 0
|
|
assert payload["attempts"][1]["status"] == "fallback"
|
|
assert payload["attempts"][1]["bootContinues"] is True
|
|
assert payload["attempts"][1]["profile"] == "default"
|
|
assert payload["attempts"][1]["isDefault"] is True
|
|
assert payload["attempts"][1]["bootProfile"] == "default"
|
|
assert payload["attempts"][1]["bootIsDefault"] is True
|
|
assert payload["attempts"][1]["applyBotNameCalls"] == 1
|
|
assert payload["redirects"] == ["login?next=%2F"]
|
|
assert payload["storageHistory"][0].get(BOOT_MARKER_KEY) == "1"
|
|
assert payload["storageHistory"][1].get(BOOT_MARKER_KEY) is None
|
|
assert payload["storageSnapshot"] == {}
|
|
|
|
|
|
def test_active_profile_boot_non_401_errors_fallback_without_redirect(driver_path):
|
|
payload = _run_boot_profile_scenario(
|
|
driver_path,
|
|
{
|
|
"markerKey": "test-5001-active-profile-recovery-non-401",
|
|
"useDefaultLoader": True,
|
|
"attempts": [
|
|
{"status": 500, "payload": {"error": "server failure"}, "nextUrl": "/"},
|
|
],
|
|
},
|
|
)
|
|
|
|
assert payload["attempts"][0]["status"] == "fallback"
|
|
assert payload["attempts"][0]["bootContinues"] is True
|
|
assert payload["attempts"][0]["profile"] == "default"
|
|
assert payload["attempts"][0]["isDefault"] is True
|
|
assert payload["attempts"][0]["bootProfile"] == "default"
|
|
assert payload["attempts"][0]["bootIsDefault"] is True
|
|
assert payload["attempts"][0]["applyBotNameCalls"] == 1
|
|
assert payload["redirects"] == []
|
|
assert payload["storageHistory"][0].get("test-5001-active-profile-recovery-non-401") is None
|
|
assert payload["storageSnapshot"] == {}
|
|
|
|
|
|
def test_active_profile_boot_invalid_payload_falls_back_without_redirect(driver_path):
|
|
payload = _run_boot_profile_scenario(
|
|
driver_path,
|
|
{
|
|
"markerKey": "test-5001-active-profile-recovery-invalid-payload",
|
|
"useDefaultLoader": True,
|
|
"attempts": [
|
|
{"status": 200, "payload": {"is_default": False}, "nextUrl": "/"},
|
|
],
|
|
},
|
|
)
|
|
|
|
assert payload["attempts"][0]["status"] == "fallback"
|
|
assert payload["attempts"][0]["bootContinues"] is True
|
|
assert payload["attempts"][0]["profile"] == "default"
|
|
assert payload["attempts"][0]["isDefault"] is True
|
|
assert payload["attempts"][0]["bootProfile"] == "default"
|
|
assert payload["attempts"][0]["bootIsDefault"] is True
|
|
assert payload["attempts"][0]["applyBotNameCalls"] == 1
|
|
assert payload["redirects"] == []
|
|
assert payload["storageHistory"][0].get("test-5001-active-profile-recovery-invalid-payload") is None
|
|
assert payload["storageSnapshot"] == {}
|
|
|
|
|
|
def test_active_profile_success_path_applies_boot_state_and_continues(driver_path):
|
|
payload = _run_boot_profile_scenario(
|
|
driver_path,
|
|
{
|
|
"markerKey": "test-5001-active-profile-recovery-success",
|
|
"useDefaultLoader": True,
|
|
"attempts": [
|
|
{
|
|
"status": 200,
|
|
"payload": {"name": "team-profile", "is_default": False},
|
|
}
|
|
],
|
|
},
|
|
)
|
|
|
|
assert payload["attempts"][0]["status"] == "resolved"
|
|
assert payload["attempts"][0]["bootContinues"] is True
|
|
assert payload["attempts"][0]["profile"] == "team-profile"
|
|
assert payload["attempts"][0]["isDefault"] is False
|
|
assert payload["attempts"][0]["bootProfile"] == "team-profile"
|
|
assert payload["attempts"][0]["bootIsDefault"] is False
|
|
assert payload["attempts"][0]["applyBotNameCalls"] == 1
|
|
assert payload["redirects"] == []
|
|
assert payload["storageHistory"][0].get("test-5001-active-profile-recovery-success") is None
|
|
assert payload["storageSnapshot"] == {}
|