mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
fix(settings): case sync tests follow lastPickedCase, a failed pick is not undone, rollback is covered
- sync-case-state browser and stale-refresh tests use lastPickedCase (the stale test now fails without its guard) - saveLastUsedCase runs on the case-sync chain, and the follow is skipped while this device's own pick was not stored - _caseStateSyncOn tolerates a harness without loadAppSettingsFromStorage - lastPickedCase added to the config-backup volatile-key test and docs; rename rollback tests cover caseSettings and lastPickedCase - wiki row moved to System
This commit is contained in:
1 parent
4808ee7085
commit
bb088bcd3e
9 files changed
+104
-32
No files matched your search
@@ -173,7 +173,11 @@ describe('createConfigBackup', () => {
|
||||
'hostId',
|
||||
],
|
||||
['users.json', (n: number) => JSON.stringify({ users: [{ name: 'a', role: 'admin', lastLoginAt: n }] }), 'role'],
|
||||
['settings.json', (n: number) => JSON.stringify({ displayName: 'x', lastUsedCase: `case-${n}` }), 'displayName'],
|
||||
[
|
||||
'settings.json',
|
||||
(n: number) => JSON.stringify({ displayName: 'x', lastUsedCase: `case-${n}`, lastPickedCase: `case-${n}` }),
|
||||
'displayName',
|
||||
],
|
||||
[
|
||||
'intents.json',
|
||||
(n: number) => JSON.stringify({ profiles: [{ key: 'k', goals: 'g', updatedAt: n, recentPrompts: [`p${n}`] }] }),
|
||||
|
||||
@@ -302,7 +302,13 @@ describe('POST /api/cases/:name/rename (a linked case)', () => {
|
||||
const real = join(work, 'my-project');
|
||||
mkdirSync(real);
|
||||
writeFileSync(LINKED(), JSON.stringify({ proj: real }));
|
||||
writeFileSync(SETTINGS_PATH, JSON.stringify({ caseOrder: ['a', 'proj', 'b'], lastUsedCase: 'proj' }));
|
||||
const seeded = {
|
||||
caseOrder: ['a', 'proj', 'b'],
|
||||
lastUsedCase: 'proj',
|
||||
lastPickedCase: 'proj',
|
||||
caseSettings: { proj: { agentTeams: true, opusContext1m: false } },
|
||||
};
|
||||
writeFileSync(SETTINGS_PATH, JSON.stringify(seeded));
|
||||
// the registry reads fine but cannot be written: the rename must undo the folder move
|
||||
const spy = failRegistryWrite();
|
||||
let res;
|
||||
@@ -316,7 +322,7 @@ describe('POST /api/cases/:name/rename (a linked case)', () => {
|
||||
expect(linked()).toEqual({ proj: real });
|
||||
expect(existsSync(join(work, 'renamed'))).toBe(false);
|
||||
// settings.json is written first, so it must be put back too
|
||||
expect(settings()).toEqual({ caseOrder: ['a', 'proj', 'b'], lastUsedCase: 'proj' });
|
||||
expect(settings()).toEqual(seeded);
|
||||
});
|
||||
|
||||
it('puts back only the keys it wrote, keeping a settings write made in between', async () => {
|
||||
@@ -324,17 +330,38 @@ describe('POST /api/cases/:name/rename (a linked case)', () => {
|
||||
const real = join(work, 'my-project');
|
||||
mkdirSync(real);
|
||||
writeFileSync(LINKED(), JSON.stringify({ proj: real }));
|
||||
writeFileSync(SETTINGS_PATH, JSON.stringify({ caseOrder: ['a', 'proj', 'b'], lastUsedCase: 'proj' }));
|
||||
// Another writer (a settings save) lands after the rename wrote settings.json and before the
|
||||
// registry write fails.
|
||||
const spy = failRegistryWrite(() => mutateSettingsFile((current) => ({ ...current, agentSkillEnabled: true })));
|
||||
const entry = { agentTeams: true, opusContext1m: false };
|
||||
writeFileSync(
|
||||
SETTINGS_PATH,
|
||||
JSON.stringify({
|
||||
caseOrder: ['a', 'proj', 'b'],
|
||||
lastUsedCase: 'proj',
|
||||
lastPickedCase: 'proj',
|
||||
caseSettings: { proj: entry },
|
||||
})
|
||||
);
|
||||
// Another writer (a settings save, and a per-case save from another device) lands after the rename wrote
|
||||
// settings.json and before the registry write fails.
|
||||
const spy = failRegistryWrite(() =>
|
||||
mutateSettingsFile((current) => ({
|
||||
...current,
|
||||
agentSkillEnabled: true,
|
||||
caseSettings: { ...(current.caseSettings as object), other: { agentTeams: false, opusContext1m: true } },
|
||||
}))
|
||||
);
|
||||
try {
|
||||
const res = await rename(app, 'proj', { newName: 'renamed', renameFolder: true });
|
||||
expect(res.statusCode).toBe(500);
|
||||
} finally {
|
||||
spy.mockRestore();
|
||||
}
|
||||
expect(settings()).toEqual({ caseOrder: ['a', 'proj', 'b'], lastUsedCase: 'proj', agentSkillEnabled: true });
|
||||
expect(settings()).toEqual({
|
||||
caseOrder: ['a', 'proj', 'b'],
|
||||
lastUsedCase: 'proj',
|
||||
lastPickedCase: 'proj',
|
||||
caseSettings: { proj: entry, other: { agentTeams: false, opusContext1m: true } },
|
||||
agentSkillEnabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the registry changes after the first check', () => {
|
||||
|
||||
@@ -1107,7 +1107,6 @@ describe('case selector refresh', () => {
|
||||
{ name: 'kept-case', path: '/tmp/kept-case' },
|
||||
];
|
||||
app.showToast = vi.fn();
|
||||
app.loadAppSettingsFromStorage = () => ({}); // saveLastUsedCase asks whether case sync is on
|
||||
|
||||
// deleteCase re-renders the case-manage list, whose path label goes through
|
||||
// _shortenHomePath. That method lives in terminal-ui.js, which this harness
|
||||
|
||||
@@ -75,11 +75,11 @@ describe('Sync cases across devices in two browsers', () => {
|
||||
});
|
||||
|
||||
it('follows the selected case while sync is on, and stops once the server says it is off', async () => {
|
||||
await put({ lastUsedCase: two });
|
||||
await put({ lastPickedCase: two });
|
||||
await returnToTab(a);
|
||||
expect(await a.evaluate(() => (document.getElementById('quickStartCase') as HTMLSelectElement).value)).toBe(two);
|
||||
|
||||
await put({ syncCaseState: false, lastUsedCase: one });
|
||||
await put({ syncCaseState: false, lastPickedCase: one });
|
||||
await returnToTab(a);
|
||||
expect(await a.evaluate(() => (document.getElementById('quickStartCase') as HTMLSelectElement).value)).toBe(two);
|
||||
expect(await a.evaluate(() => (window as any).app.loadAppSettingsFromStorage().syncCaseState)).toBe(false);
|
||||
|
||||
@@ -150,6 +150,38 @@ describe('recording the picked case', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('a pick that did not reach the server', () => {
|
||||
it('is not undone by the next tab return, and following resumes once a pick is stored', async () => {
|
||||
let putOk = false;
|
||||
const { app, select } = makeApp({ syncCaseState: true }, (_url, init) =>
|
||||
init?.method === 'PUT'
|
||||
? putOk
|
||||
? { body: { success: true } }
|
||||
: { status: 500, body: { error: 'x' } }
|
||||
: { body: { success: true, data: { syncCaseState: true, lastPickedCase: 'alpha' } } }
|
||||
);
|
||||
select.value = 'beta';
|
||||
await app.saveLastUsedCase('beta'); // the pick fails
|
||||
await app._syncCaseStateFromServer(); // the server still says alpha
|
||||
expect(select.value).toBe('beta');
|
||||
putOk = true;
|
||||
await app.saveLastUsedCase('beta');
|
||||
await app._syncCaseStateFromServer();
|
||||
expect(select.value).toBe('alpha');
|
||||
});
|
||||
|
||||
it('lands before the copy a tab return reads', async () => {
|
||||
const order: string[] = [];
|
||||
const { app } = makeApp({ syncCaseState: true }, async (_url, init) => {
|
||||
order.push(init?.method ?? 'GET');
|
||||
return { body: { success: true, data: { syncCaseState: true } } };
|
||||
});
|
||||
void app.saveLastUsedCase('beta');
|
||||
await app._syncCaseStateFromServer();
|
||||
expect(order).toEqual(['PUT', 'GET']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('taking the server copy', () => {
|
||||
const server = { syncCaseState: true, caseSettings: { demo: entry } };
|
||||
|
||||
@@ -253,8 +285,8 @@ describe('returning to the tab', () => {
|
||||
it('discards an older refresh that answers after a newer one', async () => {
|
||||
const releases: (() => void)[] = [];
|
||||
const answers = [
|
||||
env({ syncCaseState: true, lastUsedCase: 'beta' }), // the older request
|
||||
env({ syncCaseState: true, lastUsedCase: 'alpha' }), // the newer request
|
||||
env({ syncCaseState: true, lastPickedCase: 'beta' }), // the older request
|
||||
env({ syncCaseState: true, lastPickedCase: 'alpha' }), // the newer request
|
||||
];
|
||||
let i = 0;
|
||||
const { app, select } = makeApp({ syncCaseState: true }, async () => {
|
||||
|
||||
Reference in new issue
Block a user