mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-16 22:35:32 +08:00
feat: add script version history (#3085)
* feat: add script version history * fix: validate canonical save-as paths before backup * fix: stack script history footer on small screens
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
const path = require('node:path');
|
||||
const load = require('../helpers/load-security-module.cjs');
|
||||
|
||||
function fixture(send) {
|
||||
const dialogs = [],
|
||||
notices = [],
|
||||
calls = [];
|
||||
const request = {};
|
||||
for (const method of ['put', 'post'])
|
||||
request[method] = async (url, body, options) => {
|
||||
calls.push({ method, body });
|
||||
const result = await send(body);
|
||||
if (result.status) return options.onError(result);
|
||||
return result;
|
||||
};
|
||||
const { saveWithHistory } = load(
|
||||
path.resolve(__dirname, '../../src/pages/script/saveWithHistory.ts'),
|
||||
{
|
||||
'@/utils/http': { request },
|
||||
antd: {
|
||||
Modal: {
|
||||
confirm: (dialog) => {
|
||||
dialogs.push(dialog);
|
||||
return { update: (options) => Object.assign(dialog, options) };
|
||||
},
|
||||
},
|
||||
message: Object.fromEntries(
|
||||
['error', 'warning', 'success'].map((type) => [
|
||||
type,
|
||||
(text) => notices.push({ type, text }),
|
||||
]),
|
||||
),
|
||||
},
|
||||
'react-intl-universal': {
|
||||
__esModule: true,
|
||||
default: { get: (key) => key },
|
||||
},
|
||||
},
|
||||
);
|
||||
return { saveWithHistory, dialogs, notices, calls };
|
||||
}
|
||||
const unavailable = {
|
||||
status: 413,
|
||||
data: { historyUnavailable: true, currentHash: 'a'.repeat(64) },
|
||||
};
|
||||
const tick = () => new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
test('oversized editor save requires explicit confirmation and cancellation keeps the draft unsaved', async () => {
|
||||
const f = fixture(() => unavailable);
|
||||
const pending = f.saveWithHistory('put', '/api/scripts', {
|
||||
filename: 'large.js',
|
||||
content: 'small',
|
||||
});
|
||||
await tick();
|
||||
assert.equal(f.calls.length, 1);
|
||||
assert.equal(f.dialogs.length, 1);
|
||||
assert.match(f.dialogs[0].content, /不会保留/);
|
||||
f.dialogs[0].onCancel();
|
||||
assert.equal(await pending, undefined);
|
||||
assert.equal(f.calls.length, 1);
|
||||
assert.equal(f.notices.length, 0);
|
||||
});
|
||||
|
||||
test('debugger confirmation retries POST with the exact preview hash and displays a warning', async () => {
|
||||
const f = fixture((body) =>
|
||||
body.skipHistory
|
||||
? { code: 200, data: { historyRecorded: false } }
|
||||
: unavailable,
|
||||
);
|
||||
const pending = f.saveWithHistory('post', '/api/scripts', {
|
||||
content: 'small',
|
||||
});
|
||||
await tick();
|
||||
await f.dialogs[0].onOk();
|
||||
assert.equal((await pending).code, 200);
|
||||
assert.equal(f.dialogs[0].cancelButtonProps.disabled, true);
|
||||
assert.equal(f.dialogs[0].keyboard, false);
|
||||
assert.equal(f.calls[1].method, 'post');
|
||||
assert.equal(f.calls[1].body.expectedHash, unavailable.data.currentHash);
|
||||
assert.equal(f.calls[1].body.skipHistory, true);
|
||||
assert.deepEqual(
|
||||
f.notices.map((v) => v.type),
|
||||
['warning'],
|
||||
);
|
||||
});
|
||||
|
||||
test('a stale confirmation reports the conflict without marking the editor saved', async () => {
|
||||
const f = fixture((body) =>
|
||||
body.skipHistory
|
||||
? { status: 409, data: { message: 'changed' } }
|
||||
: unavailable,
|
||||
);
|
||||
const pending = f.saveWithHistory('put', '/api/scripts', {});
|
||||
await tick();
|
||||
await f.dialogs[0].onOk();
|
||||
assert.equal(await pending, undefined);
|
||||
assert.deepEqual(f.notices, [{ type: 'error', text: 'changed' }]);
|
||||
});
|
||||
|
||||
test('unrelated errors never offer history bypass, and post-commit cleanup failures remain successful saves', async () => {
|
||||
const failed = fixture(() => ({
|
||||
status: 500,
|
||||
data: { message: 'storage failed' },
|
||||
}));
|
||||
assert.equal(
|
||||
await failed.saveWithHistory('put', '/api/scripts', {}),
|
||||
undefined,
|
||||
);
|
||||
assert.equal(failed.dialogs.length, 0);
|
||||
const committed = fixture(() => ({
|
||||
code: 200,
|
||||
data: { cleanupPending: true },
|
||||
}));
|
||||
assert.equal(
|
||||
(await committed.saveWithHistory('put', '/api/scripts', {})).code,
|
||||
200,
|
||||
);
|
||||
assert.equal(committed.notices[0].type, 'warning');
|
||||
});
|
||||
Reference in New Issue
Block a user