feat: harden content workflows and staging smoke tests
CI — Test & Build / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI — Test & Build / 🏗️ Build Docker Image (push) Blocked by required conditions
CI — Test & Build / 🐳 Docker integration & API E2E (push) Blocked by required conditions
CI — Test & Build / 🌐 Staging Playwright smoke (push) Waiting to run

This commit is contained in:
Do Siki
2026-08-17 16:08:57 +02:00
parent 35e341d5b2
commit 0047e1b441
28 changed files with 1208 additions and 360 deletions
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env node
const assert = require('assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { backupAndWriteAtomically } = require('../content-editor');
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mozdit-content-save-'));
try {
const target = path.join(tempDir, 'home.json');
const backupDir = path.join(tempDir, 'backups');
const original = { title: 'Eredeti', enabled: true };
const replacement = { title: 'Új', enabled: false };
fs.writeFileSync(target, JSON.stringify(original) + '\n');
const backupFile = backupAndWriteAtomically(target, replacement, backupDir);
assert.deepEqual(JSON.parse(fs.readFileSync(target, 'utf8')), replacement);
assert.deepEqual(JSON.parse(fs.readFileSync(backupFile, 'utf8')), original);
assert.equal(fs.readdirSync(tempDir).some(name => name.endsWith('.tmp')), false);
console.log('Content Editor atomic save test: OK');
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env node
const assert = require('assert/strict');
process.env.CMS_USER = 'test-editor';
process.env.CMS_PASS = 'not-a-real-secret';
const {
hasValidCredentials,
hasValidCsrfToken,
securityConfigIsValid,
csrfToken,
} = require('../content-editor');
const authorization = Buffer.from('test-editor:not-a-real-secret').toString('base64');
assert.equal(securityConfigIsValid(), true);
assert.equal(hasValidCredentials({ headers: { authorization: `Basic ${authorization}` } }), true);
assert.equal(hasValidCredentials({ headers: { authorization: 'Basic invalid' } }), false);
assert.equal(hasValidCredentials({ headers: {} }), false);
assert.equal(hasValidCsrfToken({ headers: { 'x-csrf-token': csrfToken } }), true);
assert.equal(hasValidCsrfToken({ headers: { 'x-csrf-token': 'invalid-token' } }), false);
assert.equal(hasValidCsrfToken({ headers: {} }), false);
console.log('Content Editor security guard test: OK');
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env node
/**
* Regressziós teszt a Content Editor böngészőben futó serializeréhez.
* A szerver által generált tényleges JavaScriptet futtatja minimális DOM-mal,
* így a szerkesztő és a teszt nem két eltérő implementációt vizsgál.
*/
const assert = require('assert/strict');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const { createRequire } = require('module');
const source = fs.readFileSync('content-editor.js', 'utf8')
.replace('if (require.main === module) {', 'globalThis.renderContentEditor = HTML;\nif (false) {');
const editorRequire = createRequire(path.join(process.cwd(), 'content-editor.js'));
const serverContext = { require: editorRequire, console, process, Buffer, module: { exports: {} }, __dirname: process.cwd(), globalThis: {} };
vm.createContext(serverContext);
new vm.Script(source).runInContext(serverContext);
const fixture = {
title: 'Teszt',
enabled: true,
limit: 42,
sections: [{ id: 'first', items: ['egy', 'kettő'], settings: { visible: false, weight: 1 } }],
};
const html = serverContext.globalThis.renderContentEditor('home', JSON.stringify(fixture), null);
const browserSource = [...html.matchAll(/<script(?: [^>]*)?>([\s\S]*?)<\/script>/g)].at(-1)[1]
.replace("render(DATA, document.getElementById('editor'));", '')
.replace("const toast = document.querySelector('.toast');", 'const toast = null;');
const fields = [
{ dataset: { path: 'title', type: 'string' }, value: 'Módosított' },
{ dataset: { path: 'enabled', type: 'boolean' }, checked: true },
{ dataset: { path: 'limit', type: 'number' }, value: '99' },
{ dataset: { path: 'sections[0].id', type: 'string' }, value: 'first' },
{ dataset: { path: 'sections[0].items[0]', type: 'string' }, value: 'egy' },
{ dataset: { path: 'sections[0].items[1]', type: 'string' }, value: 'kettő' },
{ dataset: { path: 'sections[0].settings.visible', type: 'boolean' }, checked: false },
{ dataset: { path: 'sections[0].settings.weight', type: 'number' }, value: '3' },
];
const document = {
getElementById: id => id === 'page-data' ? { textContent: JSON.stringify(fixture) } : {},
querySelectorAll: selector => selector === '[data-path]' ? fields : [],
querySelector: () => null,
};
const browserContext = { document, console, setTimeout, fetch: async () => ({ json: async () => ({ ok: true }) }) };
vm.createContext(browserContext);
new vm.Script(`${browserSource}\nglobalThis.__collect = collect;`).runInContext(browserContext);
assert.deepStrictEqual(JSON.parse(JSON.stringify(browserContext.__collect())), {
title: 'Módosított',
enabled: true,
limit: 99,
sections: [{ id: 'first', items: ['egy', 'kettő'], settings: { visible: false, weight: 3 } }],
});
console.log('Content Editor serializer regression test: OK');
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env node
const assert = require('assert/strict');
const fs = require('fs');
const { validateContent } = require('../proto/src/content/schema');
const files = {
common: 'proto/src/content/common.json',
home: 'proto/src/content/pages/home.json',
about: 'proto/src/content/pages/about.json',
services: 'proto/src/content/pages/services.json',
contact: 'proto/src/content/pages/contact.json',
adatvedelem: 'proto/src/content/pages/adatvedelem.json',
hasznalatiFeltetelek: 'proto/src/content/pages/hasznalati-feltetelek.json',
};
for (const [key, file] of Object.entries(files)) {
const result = validateContent(key, JSON.parse(fs.readFileSync(file, 'utf8')));
assert.equal(result.ok, true, `${key}: ${result.errors.join('; ')}`);
}
const invalidHome = JSON.parse(fs.readFileSync(files.home, 'utf8'));
invalidHome.hero.trustBullets = [];
invalidHome.services['items[0]'] = invalidHome.services.items[0];
assert.deepEqual(validateContent('home', invalidHome).ok, false);
assert.match(validateContent('home', invalidHome).errors.join(' '), /nem engedélyezett mező/);
console.log('Content schema validation test: OK');