feat(cms): add maintained user guide with Súgó menu entry
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
- docs/felhasznaloi-utmutato.md: user guide for the website and the CMS
(login, editing, arrays, save/validation, publish, security)
- /guide endpoint renders the markdown auth-protected via a dependency-free
renderer (scripts/markdown-render.js) in the CMS dark theme
- new ❓ Súgó entry in the CMS bottom bar
- steering rule: the guide must be updated in the same commit as any CMS or
website feature change
Closes MITHOME-57
This commit is contained in:
@@ -74,6 +74,12 @@ Kritikus hibák javításánál az alábbi sorrendet KÖTELEZŐ követni:
|
|||||||
git commit -m "fix(<scope>): <mi volt a hiba és hogyan lett javítva>"
|
git commit -m "fix(<scope>): <mi volt a hiba és hogyan lett javítva>"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Felhasználói dokumentáció karbantartása
|
||||||
|
|
||||||
|
- A CMS **❓ Súgó** menüpontja a `docs/felhasznaloi-utmutato.md` fájlt rendereli (`/guide`).
|
||||||
|
- **Kötelező**: minden CMS- vagy honlapfunkció változtatásánál (új gomb, viselkedésváltozás, útvonal) ugyanabban a commitban frissítsd az útmutatót.
|
||||||
|
- Támogatott markdown forma a `scripts/markdown-render.js` részhalmaza: címsorok, **félkövér**, `kód`, listák, linkek, `---` elválasztók (táblázat nem).
|
||||||
|
|
||||||
## Nyelvhasználat
|
## Nyelvhasználat
|
||||||
|
|
||||||
- **Kód, kommentek, commit üzenetek**: Angol
|
- **Kód, kommentek, commit üzenetek**: Angol
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ Next.js 15 alapú weboldal a mozdIT Bt. számára, Docker Compose-szal deployolv
|
|||||||
| MITHOME-39 | Production: közös natív MongoDB több alkalmazás biztonságos kiszolgálására | 📋 |
|
| MITHOME-39 | Production: közös natív MongoDB több alkalmazás biztonságos kiszolgálására | 📋 |
|
||||||
| MITHOME-40 | Production MongoDB: automatizált mentés, visszaállítási próba és monitoring | 📋 |
|
| MITHOME-40 | Production MongoDB: automatizált mentés, visszaállítási próba és monitoring | 📋 |
|
||||||
| MITHOME-55 | Contact API válaszformátum igazítása a projekt konvencióhoz | 📋 |
|
| MITHOME-55 | Contact API válaszformátum igazítása a projekt konvencióhoz | 📋 |
|
||||||
|
| MITHOME-57 | Felhasználói útmutató (CMS + Honlap) Súgó menüponttal | 📋 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+61
-2
@@ -14,12 +14,14 @@ const path = require('path');
|
|||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const { validateContent } = require('./proto/src/content/schema');
|
const { validateContent } = require('./proto/src/content/schema');
|
||||||
|
const { renderMarkdown } = require('./scripts/markdown-render');
|
||||||
|
|
||||||
const PORT = Number(process.env.CONTENT_EDITOR_PORT) || 4001;
|
const PORT = Number(process.env.CONTENT_EDITOR_PORT) || 4001;
|
||||||
const CONTENT_DIR = path.join(__dirname, 'proto', 'src', 'content');
|
const CONTENT_DIR = path.join(__dirname, 'proto', 'src', 'content');
|
||||||
const BACKUP_DIR = path.join(__dirname, '.content-backups');
|
const BACKUP_DIR = path.join(__dirname, '.content-backups');
|
||||||
const MAX_REQUEST_BODY_BYTES = 256 * 1024;
|
const MAX_REQUEST_BODY_BYTES = 256 * 1024;
|
||||||
const AUDIT_LOG_FILE = process.env.CONTENT_EDITOR_AUDIT_FILE || path.join(__dirname, '.content-editor-audit.jsonl');
|
const AUDIT_LOG_FILE = process.env.CONTENT_EDITOR_AUDIT_FILE || path.join(__dirname, '.content-editor-audit.jsonl');
|
||||||
|
const GUIDE_FILE = process.env.CONTENT_EDITOR_GUIDE_FILE || path.join(__dirname, 'docs', 'felhasznaloi-utmutato.md');
|
||||||
const RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000;
|
const RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000;
|
||||||
const AUTH_MAX_ATTEMPTS = 5;
|
const AUTH_MAX_ATTEMPTS = 5;
|
||||||
const PUBLISH_MAX_ATTEMPTS = 3;
|
const PUBLISH_MAX_ATTEMPTS = 3;
|
||||||
@@ -151,6 +153,7 @@ ${message ? `<div class="toast ${message.type === 'ok' ? 'ok' : 'err'}">${messag
|
|||||||
<button class="btn-publish" onclick="publish()" id="publishBtn">🚀 Publikálás & ${CMS_DEPLOY_ENV === 'staging' ? 'Staging deploy' : 'Élesítés'}</button>
|
<button class="btn-publish" onclick="publish()" id="publishBtn">🚀 Publikálás & ${CMS_DEPLOY_ENV === 'staging' ? 'Staging deploy' : 'Élesítés'}</button>
|
||||||
<span class="save-status" id="saveStatus"></span>
|
<span class="save-status" id="saveStatus"></span>
|
||||||
<a href="${CMS_DEPLOY_ENV === 'staging' ? 'https://stage.mozdit.hu' : 'http://localhost:3000'}" target="_blank" class="preview-link">🔗 Előnézet →</a>
|
<a href="${CMS_DEPLOY_ENV === 'staging' ? 'https://stage.mozdit.hu' : 'http://localhost:3000'}" target="_blank" class="preview-link">🔗 Előnézet →</a>
|
||||||
|
<a href="/guide" target="_blank" class="preview-link">❓ Súgó</a>
|
||||||
<button class="btn-logout" onclick="logout()">🚪 Kilépés</button>
|
<button class="btn-logout" onclick="logout()">🚪 Kilépés</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -458,8 +461,51 @@ render(DATA, document.getElementById('editor'));
|
|||||||
|
|
||||||
// Auto-dismiss toast
|
// Auto-dismiss toast
|
||||||
const toast = document.querySelector('.toast');
|
const toast = document.querySelector('.toast');
|
||||||
if (toast) setTimeout(() => toast.remove(), 3500);
|
if (toast) setTimeout(() => toast.remove(), 3500);
|
||||||
</script>
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
// User guide page — renders docs/felhasznaloi-utmutato.md with the shared dark theme.
|
||||||
|
const GUIDE_PAGE = (contentHtml) => `<!DOCTYPE html>
|
||||||
|
<html lang="hu">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>mozdIT — Felhasználói útmutató</title>
|
||||||
|
<style>
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f1117; color: #e2e8f0; line-height: 1.65; padding-bottom: 64px; }
|
||||||
|
header { background: linear-gradient(135deg,#1a1f2e,#252d40); border-bottom: 1px solid #2d3748; padding: 14px 32px; display: flex; align-items: center; gap: 12px; position: sticky; top: 0; z-index: 10; }
|
||||||
|
header h1 { font-size: 17px; font-weight: 700; background: linear-gradient(135deg,#60a5fa,#a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||||||
|
header a { color: #94a3b8; text-decoration: none; font-size: 14px; margin-left: auto; }
|
||||||
|
header a:hover { color: #e2e8f0; }
|
||||||
|
main { max-width: 760px; margin: 0 auto; padding: 32px 24px; }
|
||||||
|
h1 { font-size: 24px; margin: 16px 0 12px; color: #f1f5f9; }
|
||||||
|
h2 { font-size: 20px; margin: 28px 0 10px; color: #93c5fd; border-bottom: 1px solid #2d3748; padding-bottom: 6px; }
|
||||||
|
h3 { font-size: 16px; margin: 20px 0 8px; color: #c4b5fd; }
|
||||||
|
h4 { font-size: 14px; margin: 16px 0 6px; color: #c4b5fd; }
|
||||||
|
p { margin: 8px 0; }
|
||||||
|
ul, ol { margin: 8px 0 8px 22px; }
|
||||||
|
li { margin: 4px 0; }
|
||||||
|
a { color: #7dd3fc; }
|
||||||
|
code { background: #1e293b; border-radius: 4px; padding: 1px 6px; font-size: 0.9em; color: #fbbf24; }
|
||||||
|
pre { background: #1e293b; border: 1px solid #2d3748; border-radius: 8px; padding: 12px 16px; overflow-x: auto; margin: 12px 0; }
|
||||||
|
pre code { background: none; padding: 0; color: #e2e8f0; }
|
||||||
|
hr { border: none; border-top: 1px solid #2d3748; margin: 24px 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>mozdIT — Felhasználói útmutató</h1>
|
||||||
|
<a href="/">← Vissza a szerkesztőhöz</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
${contentHtml}
|
||||||
|
</main>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|
||||||
@@ -572,6 +618,19 @@ const server = http.createServer(async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /guide — user guide rendered from the maintained markdown in the repo.
|
||||||
|
if (req.method === 'GET' && u.pathname === '/guide') {
|
||||||
|
let contentHtml;
|
||||||
|
try {
|
||||||
|
contentHtml = renderMarkdown(fs.readFileSync(GUIDE_FILE, 'utf8'));
|
||||||
|
} catch (error) {
|
||||||
|
contentHtml = '<p>Az útmutató jelenleg nem elérhető. Kérlek, szólj a fejlesztőnek.</p>';
|
||||||
|
}
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||||
|
res.end(GUIDE_PAGE(contentHtml));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// POST /save — JSON body
|
// POST /save — JSON body
|
||||||
if (req.method === 'POST' && u.pathname === '/save') {
|
if (req.method === 'POST' && u.pathname === '/save') {
|
||||||
let body = '';
|
let body = '';
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# mozdIT — Felhasználói útmutató
|
||||||
|
|
||||||
|
Ez az útmutató a mozdIT weboldalt és a hozzá tartozó **Content Editor** (CMS) felületet írja le nem műszaki felhasználóknak.
|
||||||
|
|
||||||
|
A dokumentum a repó része, és **folyamatosan karbantartott**: minden funkcióváltozásnál a fejlesztő frissíti. A CMS ❓ Súgó menüpontja ezt a fájlt jeleníti meg.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. A weboldal
|
||||||
|
|
||||||
|
### Hol érhető el?
|
||||||
|
|
||||||
|
- **Staging (teszt) oldal**: [https://stage.mozdit.hu](https://stage.mozdit.hu) — itt ellenőrizhetők a friss változtatások éles környezetben, még a véglegesítés előtt.
|
||||||
|
- A staging oldal tetején **sárga figyelmeztető sáv** jelzi, hogy tesztkörnyezetet látsz.
|
||||||
|
|
||||||
|
### Oldalak
|
||||||
|
|
||||||
|
- **Kezdőlap** — `https://stage.mozdit.hu/`
|
||||||
|
- **Rólunk** — `/rolunk`
|
||||||
|
- **Szolgáltatások** — `/szolgaltatasok`
|
||||||
|
- **Kapcsolat** — `/kapcsolat` (űrlap, ami beérkező üzenetként tárolódik)
|
||||||
|
- **Adatvédelmi tájékoztató** — `/adatvedelem`
|
||||||
|
- **Felhasználási feltételek** — `/felhasznalasi-feltetelek`
|
||||||
|
|
||||||
|
### Hogyan változik a weboldal tartalma?
|
||||||
|
|
||||||
|
1. A szerkesztő a **Content Editorban** módosítja a szövegeket (2. fejezet).
|
||||||
|
2. **💾 Mentés** — a módosítás elmentődik, azonnali biztonsági mentéssel.
|
||||||
|
3. **🚀 Publikálás** — a változtatás bekerül a Git repóba, és automatikusan deployol a staging oldalra.
|
||||||
|
4. Az éles (production) weboldalra a tartalom csak ellenőrzött, szándékos deploy lépéssel kerül fel — a CMS-ből soha nem publisholódik automatikusan productionre.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Content Editor (CMS)
|
||||||
|
|
||||||
|
### Belépés és kilépés
|
||||||
|
|
||||||
|
- A CMS a kiadott címen érhető el (staging: `https://cms.stage.llmdev.mozdit.hu`).
|
||||||
|
- Belépés: a megadott **felhasználónév + jelszó** párossal (ezt az adminisztrátor adja).
|
||||||
|
- **🚪 Kilépés**: az alsó sáv gombja — anélkül jelentkezel ki, hogy be kellene zárnod a böngészőt.
|
||||||
|
|
||||||
|
### Felület áttekintés
|
||||||
|
|
||||||
|
- **Fájl fülek** (felül): oldalankénti tartalom — Kezdőlap, Rólunk, Szolgáltatások, Kapcsolat, jogi oldalak, közös szövegek.
|
||||||
|
- **Szerkesztőfelület**: a kiválasztott oldal összes szerkeszthető mezője.
|
||||||
|
- **Alsó sáv**: 💾 Mentés, 🚀 Publikálás, 🔗 Előnézet, ❓ Súgó, 🚪 Kilépés.
|
||||||
|
|
||||||
|
### Szöveg szerkesztése
|
||||||
|
|
||||||
|
- A mezők fölötti **útvonal** (pl. `hero.title`) jelzi, hol jelenik meg a szöveg az oldalon.
|
||||||
|
- Mezőtípusok:
|
||||||
|
- **Egysoros / több soros szövegmező** — általános szöveg; a hosszabb szöveg automatikusan nagyobb mezőben szerkeszthető.
|
||||||
|
- **Jelölőnégyzet** — be/ki (igen/nem) érték.
|
||||||
|
- **Számmező** — numerikus érték.
|
||||||
|
- A módosítás **nem kerül azonnal az oldalra** — ahhoz Mentés, majd Publikálás kell.
|
||||||
|
|
||||||
|
### Listák szerkesztése
|
||||||
|
|
||||||
|
- Lista elem (pl. egy jelszó, egy szolgáltatás tulajdonság): **❌ gombbal törölhető**.
|
||||||
|
- **➕ Új elem hozzáadása** gomb: új elem beszúrása a lista végére (üres, a meglévőkhöz hasonló űrlappal).
|
||||||
|
- Kártyás listáknál (pl. szolgáltatások) minden kártya külön törölhető a kártya alján lévő gombbal.
|
||||||
|
|
||||||
|
### 💾 Mentés
|
||||||
|
|
||||||
|
- A Mentés **ellenőrzi a tartalmat**: hiányzó vagy rossz típusú mező esetén hibaüzenetet kapsz, és a mentés nem történik meg — az oldal így nem tud elromlani.
|
||||||
|
- Minden sikeres mentés **biztonsági mentést** készít a szerveren (`.content-backups/`), és naplózza a műveletet.
|
||||||
|
- Ha a Mentés sikeres, a mentett állapotot **Előnézet** gombbal nézheted meg a staging oldalon.
|
||||||
|
|
||||||
|
### 🚀 Publikálás
|
||||||
|
|
||||||
|
- A Publikálás **commitolja és feltolja** a változtatásokat, majd elindítja a staging deployt.
|
||||||
|
- „No changes to commit" üzenet: nincs új változtatás — ez **nem hiba**.
|
||||||
|
- A publikálás korlátozva van (3 próbálkozás / 15 perc) a véletlen tömeges deploy elkerülésére.
|
||||||
|
- A deploy eltarthat 1-2 percig; az eredményt az Előnézet gombbal ellenőrizheted.
|
||||||
|
|
||||||
|
### Biztonság
|
||||||
|
|
||||||
|
- Több **sikertelen belépési kísérlet** után a rendszer átmenetileg letiltja a belépést a gépedről (kb. 15 percre).
|
||||||
|
- Minden mentés és publikálás **naplózva** van (audit log) a nyomonkövethetőség érdekében.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Karbantartás (fejlesztőknek)
|
||||||
|
|
||||||
|
- Forrás: `docs/felhasznaloi-utmutato.md` — a CMS a `/guide` útvonalon rendereli ki.
|
||||||
|
- **Szabály**: minden CMS- vagy honlapfunkció változásnál frissítsd ezt a fájlt ugyanabban a commitban.
|
||||||
|
- Az útmutató támogatott formátuma: címsorok, **félkövér**, `kód`, listák, linkek, elválasztó vonalak.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// WHY: the Content Editor runs on system Node without node_modules, so the user
|
||||||
|
// guide (docs/felhasznaloi-utmutato.md) is rendered by this small dependency-free
|
||||||
|
// markdown renderer instead of an external library.
|
||||||
|
// Supported subset: headings (#..####), bold, inline code, links, ul/ol lists,
|
||||||
|
// fenced code blocks, horizontal rules, paragraphs. HTML is escaped first.
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return String(value)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderInline(text) {
|
||||||
|
return escapeHtml(text)
|
||||||
|
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
||||||
|
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
||||||
|
.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMarkdown(markdown) {
|
||||||
|
const lines = String(markdown).split('\n');
|
||||||
|
const out = [];
|
||||||
|
let listTag = null; // 'ul' | 'ol'
|
||||||
|
let inCode = false;
|
||||||
|
|
||||||
|
const closeList = () => {
|
||||||
|
if (listTag) {
|
||||||
|
out.push(`</${listTag}>`);
|
||||||
|
listTag = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const raw of lines) {
|
||||||
|
const line = raw.trimEnd();
|
||||||
|
|
||||||
|
if (line.trim().startsWith('```')) {
|
||||||
|
closeList();
|
||||||
|
out.push(inCode ? '</code></pre>' : '<pre><code>');
|
||||||
|
inCode = !inCode;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inCode) {
|
||||||
|
out.push(escapeHtml(raw));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!line.trim()) {
|
||||||
|
closeList();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const heading = line.match(/^(#{1,4})\s+(.*)$/);
|
||||||
|
if (heading) {
|
||||||
|
closeList();
|
||||||
|
const level = heading[1].length;
|
||||||
|
out.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (/^(-{3,}|\*{3,})$/.test(line.trim())) {
|
||||||
|
closeList();
|
||||||
|
out.push('<hr>');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const unordered = line.match(/^\s*[-*]\s+(.*)$/);
|
||||||
|
if (unordered) {
|
||||||
|
if (listTag !== 'ul') {
|
||||||
|
closeList();
|
||||||
|
out.push('<ul>');
|
||||||
|
listTag = 'ul';
|
||||||
|
}
|
||||||
|
out.push(`<li>${renderInline(unordered[1])}</li>`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const ordered = line.match(/^\s*\d+\.\s+(.*)$/);
|
||||||
|
if (ordered) {
|
||||||
|
if (listTag !== 'ol') {
|
||||||
|
closeList();
|
||||||
|
out.push('<ol>');
|
||||||
|
listTag = 'ol';
|
||||||
|
}
|
||||||
|
out.push(`<li>${renderInline(ordered[1])}</li>`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeList();
|
||||||
|
out.push(`<p>${renderInline(line)}</p>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeList();
|
||||||
|
if (inCode) out.push('</code></pre>');
|
||||||
|
return out.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderMarkdown, renderInline, escapeHtml };
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the CMS user guide:
|
||||||
|
* 1. markdown renderer unit checks (headings, bold, code, lists, links, escaping)
|
||||||
|
* 2. /guide endpoint integration — auth-protected, serves the rendered guide
|
||||||
|
* 3. the main editor page contains the Súgó menu link
|
||||||
|
*/
|
||||||
|
const assert = require('assert/strict');
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
|
const { renderMarkdown } = require('../scripts/markdown-render');
|
||||||
|
|
||||||
|
// ── 1. Markdown renderer ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const rendered = renderMarkdown([
|
||||||
|
'# Cím',
|
||||||
|
'',
|
||||||
|
'Ez **félkövér** és `kód`, valamint [link](https://example.com).',
|
||||||
|
'',
|
||||||
|
'- első',
|
||||||
|
'- második',
|
||||||
|
'',
|
||||||
|
'1. lépés',
|
||||||
|
'2. lépés',
|
||||||
|
'',
|
||||||
|
'---',
|
||||||
|
'',
|
||||||
|
'<script>alert(1)</script>',
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
assert.match(rendered, /<h1>Cím<\/h1>/);
|
||||||
|
assert.match(rendered, /<strong>félkövér<\/strong>/);
|
||||||
|
assert.match(rendered, /<code>kód<\/code>/);
|
||||||
|
assert.match(rendered, /<a href="https:\/\/example\.com"[^>]*>link<\/a>/);
|
||||||
|
assert.match(rendered, /<ul>\s*<li>első<\/li>\s*<li>második<\/li>\s*<\/ul>/);
|
||||||
|
assert.match(rendered, /<ol>\s*<li>lépés<\/li>\s*<li>lépés<\/li>\s*<\/ol>/);
|
||||||
|
assert.match(rendered, /<hr>/);
|
||||||
|
// Raw HTML must be escaped, never executable
|
||||||
|
assert.doesNotMatch(rendered, /<script>alert/);
|
||||||
|
assert.match(rendered, /<script>/);
|
||||||
|
console.log('Markdown renderer unit tests: OK');
|
||||||
|
|
||||||
|
// ── 2. /guide endpoint + Súgó menu link (real server) ───────────────────────
|
||||||
|
|
||||||
|
const PORT = 4124;
|
||||||
|
const BASE = `http://127.0.0.1:${PORT}`;
|
||||||
|
const ROOT = path.join(__dirname, '..');
|
||||||
|
const AUDIT_FILE = path.join(os.tmpdir(), `content-editor-audit-guide-${process.pid}.jsonl`);
|
||||||
|
|
||||||
|
const child = spawn('node', ['content-editor.js'], {
|
||||||
|
cwd: ROOT,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
CONTENT_EDITOR_PORT: String(PORT),
|
||||||
|
CONTENT_EDITOR_AUDIT_FILE: AUDIT_FILE,
|
||||||
|
CMS_USER: 'guide-test-user',
|
||||||
|
CMS_PASS: 'guide-test-pass',
|
||||||
|
CMS_DEPLOY_ENV: 'staging',
|
||||||
|
},
|
||||||
|
stdio: 'ignore',
|
||||||
|
});
|
||||||
|
|
||||||
|
async function waitForServer(timeoutMs = 10000) {
|
||||||
|
const deadline = Date.now() + timeoutMs;
|
||||||
|
while (Date.now() < deadline) {
|
||||||
|
try {
|
||||||
|
await fetch(`${BASE}/logout`); // rate-limit-free readiness probe
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
await new Promise(r => setTimeout(r, 200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('server did not start');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await waitForServer();
|
||||||
|
|
||||||
|
const auth = 'Basic ' + Buffer.from('guide-test-user:guide-test-pass').toString('base64');
|
||||||
|
|
||||||
|
// /guide requires authentication
|
||||||
|
const unauth = await fetch(`${BASE}/guide`);
|
||||||
|
assert.equal(unauth.status, 401);
|
||||||
|
|
||||||
|
// /guide serves the rendered markdown as HTML
|
||||||
|
const guide = await fetch(`${BASE}/guide`, { headers: { Authorization: auth } });
|
||||||
|
assert.equal(guide.status, 200);
|
||||||
|
assert.match(guide.headers.get('content-type') || '', /text\/html/);
|
||||||
|
const guideHtml = await guide.text();
|
||||||
|
assert.match(guideHtml, /Felhasználói útmutató/);
|
||||||
|
assert.match(guideHtml, /<h2[^>]*>.*Content Editor/); // rendered from the markdown source
|
||||||
|
|
||||||
|
// The guide file must exist in the repo (maintenance contract)
|
||||||
|
assert.ok(fs.existsSync(path.join(ROOT, 'docs', 'felhasznaloi-utmutato.md')));
|
||||||
|
|
||||||
|
// The editor page exposes the Súgó menu entry
|
||||||
|
const editor = await fetch(`${BASE}/`, { headers: { Authorization: auth } });
|
||||||
|
const editorHtml = await editor.text();
|
||||||
|
assert.match(editorHtml, /href="\/guide"[^>]*>❓ Súgó/);
|
||||||
|
|
||||||
|
console.log('Content Editor guide endpoint test: OK');
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch(err => { console.error('❌', err.message); process.exitCode = 1; })
|
||||||
|
.finally(() => {
|
||||||
|
child.kill('SIGTERM');
|
||||||
|
try { fs.unlinkSync(AUDIT_FILE); } catch { /* already gone */ }
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user