fix(cms): stable bottom bar and self-save no longer trips the 409 lock
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
MITHOME-68: the optimistic-lock fingerprint was frozen at page load, so the user's own second save 409'd. /save now returns the hash of the written content and the client refreshes CONTENT_HASH on success — 409 only fires for genuine external changes (deploy, other tab, restore). Also: successful logins no longer consume the auth failure budget (only failed attempts do). MITHOME-69: bottom bar items no longer shift while saving/publishing — the status message occupies a constant flex slot (visibility instead of display), the publish button locks its width while running and restores its env-specific label, auto margins removed. Layout guard test added. Test markers are now run-unique so a crashed run can never poison the next one's expectations.
This commit is contained in:
@@ -67,17 +67,74 @@ async function call(method, pathName, { body, headers } = {}) {
|
||||
async function main() {
|
||||
await waitForServer();
|
||||
const contentFile = path.join(ROOT, 'proto', 'src', 'content', 'pages', 'contact.json');
|
||||
const original = fs.readFileSync(contentFile, 'utf8');
|
||||
|
||||
const login = await fetch(`${BASE}/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user: 'conflict-test-user', pass: 'conflict-test-pass' }),
|
||||
});
|
||||
const cookie = (login.headers.get('set-cookie') || '').split(';')[0];
|
||||
const csrfPage = await (await fetch(`${BASE}/?file=contact`, { headers: { Cookie: cookie } })).text();
|
||||
const csrf = csrfPage.match(/CSRF_TOKEN = "([a-f0-9]+)"/)[1];
|
||||
|
||||
// Baseline: read the page and capture the served fingerprint
|
||||
const page = await (await call('GET', '/?file=contact')).text();
|
||||
const servedHash = page.match(/const CONTENT_HASH = "([a-f0-9]+)"/)[1];
|
||||
const servedHash = page.match(/let CONTENT_HASH = "([a-f0-9]+)"/)[1];
|
||||
const diskBefore = fs.readFileSync(contentFile, 'utf8');
|
||||
assert.equal(servedHash, hashOf(diskBefore), 'served fingerprint matches the file on disk');
|
||||
|
||||
const payload = diskBefore; // unchanged content is still a valid save payload
|
||||
const jsonHeaders = { 'Content-Type': 'application/json' };
|
||||
|
||||
// 1. correct hash → 200
|
||||
// MITHOME-68: the save response returns the new content hash; a tab that
|
||||
// adopts it can save again — only a genuinely external change may 409.
|
||||
const v3 = JSON.parse(original);
|
||||
v3.hero.subtitle = 'Hash frissítés teszt ' + Date.now();
|
||||
const first = await fetch(`${BASE}/save?file=contact`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrf,
|
||||
'X-Content-Hash': hashOf(fs.readFileSync(contentFile, 'utf8')),
|
||||
},
|
||||
body: JSON.stringify(v3, null, 2),
|
||||
});
|
||||
assert.equal(first.status, 200);
|
||||
const firstBody = await first.json();
|
||||
assert.match(firstBody.contentHash, /^[a-f0-9]{64}$/, 'save must return the new content hash');
|
||||
|
||||
// same tab continues with the returned hash → 200
|
||||
const again = await fetch(`${BASE}/save?file=contact`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrf,
|
||||
'X-Content-Hash': firstBody.contentHash,
|
||||
},
|
||||
body: JSON.stringify(v3, null, 2),
|
||||
});
|
||||
assert.equal(again.status, 200, 'save with the refreshed hash must succeed');
|
||||
|
||||
// a stale (pre-save) hash still 409s
|
||||
const stale = await fetch(`${BASE}/save?file=contact`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrf,
|
||||
'X-Content-Hash': hashOf(original),
|
||||
},
|
||||
body: JSON.stringify(v3, null, 2),
|
||||
});
|
||||
assert.equal(stale.status, 409, 'stale hash must still be rejected');
|
||||
|
||||
// leave the disk as the following sections expect it
|
||||
fs.writeFileSync(contentFile, original);
|
||||
|
||||
// 1. correct hash → 200
|
||||
const ok = await call('POST', '/save?file=contact', {
|
||||
headers: { ...jsonHeaders, 'X-Content-Hash': servedHash },
|
||||
body: payload,
|
||||
|
||||
Reference in New Issue
Block a user