test: cinny feat/keychain-session (#105 step 2) + a keychain smoke check

Points the cinny submodule at cinny feat/keychain-session for a Windows
smoke build, and adds a smoke check: a full session in localStorage must
move to Credential Manager on boot and stay signed in across a restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
Lotus CI
2026-10-23 20:03:10 -04:00
co-authored by Claude Opus 5.5
parent 8cb562841e
commit 1b5e428e8d
2 changed files with 66 additions and 1 deletions
+1 -1
Submodule cinny updated: 4120ccdbcc...5f3c5d370a
+65
View File
@@ -375,6 +375,71 @@ record(
}; app still responsive: ${alive}`, }; app still responsive: ${alive}`,
); );
// 11. Login tokens only in the keychain (cinny #105 step 2). Simulate an
// install from before step 2: a full session in localStorage, pointed at an
// unreachable server so nothing ever rejects (and logs out) the fake token.
// After a reload the tokens must have moved: localStorage keeps a token-less
// shell, Credential Manager holds them; after another reload the app is still
// signed in. Builds without step 2 only mirror (tokens stay): n/a.
{
const name = 'login tokens move to the keychain and stay signed in (#105)';
const KEY = 'cinny_session_v1';
const fake = {
accessToken: `syt_smoke_${Date.now()}`,
refreshToken: 'mar_smoke',
deviceId: 'SMOKEDEV',
userId: '@smoke:ci.invalid',
baseUrl: 'https://127.0.0.1:9',
};
const state = () =>
page.evaluate(async (k) => {
const blob = JSON.parse(localStorage.getItem(k) ?? 'null');
const kc = await window.__TAURI_INTERNALS__.invoke('secure_session_get').catch(() => null);
return {
tokensIn: blob?.tokensIn,
blobHasToken: typeof blob?.accessToken === 'string',
legacyToken: localStorage.getItem('cinny_access_token') !== null,
kcToken: kc?.accessToken ?? null,
loginScreen: /Username or email/.test(document.body.innerText),
};
}, KEY);
try {
await page.evaluate(([k, f]) => localStorage.setItem(k, JSON.stringify(f)), [KEY, fake]);
await page.reload();
const moved = await until(async () => {
const st = await state();
return st.tokensIn === 'keychain' || (st.blobHasToken && st.kcToken === fake.accessToken) ? st : undefined;
}, 30_000);
if (moved && moved.tokensIn !== 'keychain') {
record(name, 'n/a', 'build mirrors the tokens but keeps them in localStorage (before step 2)');
} else {
await page.reload();
await new Promise((r) => setTimeout(r, 8000));
const after = await state();
const ok =
moved &&
!moved.blobHasToken &&
!moved.legacyToken &&
moved.kcToken === fake.accessToken &&
after.tokensIn === 'keychain' &&
after.kcToken === fake.accessToken &&
!after.loginScreen;
record(name, ok ? 'pass' : 'fail', `after migration: ${JSON.stringify(moved)}; after restart: ${JSON.stringify(after)}`);
}
} catch (e) {
record(name, 'fail', String(e).slice(0, 200));
} finally {
// Leave the runner as we found it: no session, no keychain entry.
await page
.evaluate(async (k) => {
localStorage.removeItem(k);
await window.__TAURI_INTERNALS__.invoke('secure_session_clear').catch(() => undefined);
}, KEY)
.catch(() => undefined);
await page.reload().catch(() => undefined);
}
}
await page.screenshot({ path: `${OUT}/02-end.png` }).catch(() => undefined); await page.screenshot({ path: `${OUT}/02-end.png` }).catch(() => undefined);
writeFileSync(`${OUT}/results.json`, JSON.stringify(results, null, 2)); writeFileSync(`${OUT}/results.json`, JSON.stringify(results, null, 2));