59 lines
3.5 KiB
JavaScript
59 lines
3.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { DOMParser } from 'linkedom';
|
|
|
|
const base = (process.env.VERIFY_URL || 'http://127.0.0.1:8080').replace(/\/$/, '');
|
|
const get = async (route) => {
|
|
const response = await fetch(base + route, { redirect: 'manual', signal: AbortSignal.timeout(15000) });
|
|
return { response, text: await response.text() };
|
|
};
|
|
const xml = await get('/sitemap.xml');
|
|
assert.equal(xml.response.status, 200);
|
|
const sitemap = new DOMParser().parseFromString(xml.text, 'text/xml');
|
|
const urls = [...sitemap.querySelectorAll('loc')].map((el) => el.textContent);
|
|
assert.equal(new Set(urls).size, urls.length);
|
|
const origin = new URL(urls[0]).origin;
|
|
const paths = urls.map((url) => new URL(url).pathname);
|
|
const selected = new Set(['/', '/services/', '/company/staff/', '/price/', '/contacts/', '/legaldocs/', '/checkup/', '/sales/']);
|
|
for (const prefix of ['/company/staff/', '/price/', '/checkup/', '/legaldocs/']) {
|
|
const group = paths.filter((p) => p.startsWith(prefix) && p !== prefix);
|
|
if (group[0]) selected.add(group[0]);
|
|
if (prefix === '/company/staff/') {
|
|
const doctor = group.find((p) => p.split('/').filter(Boolean).length === 4);
|
|
if (doctor) selected.add(doctor);
|
|
}
|
|
}
|
|
for (const path of paths) selected.add(path);
|
|
let hits = 0;
|
|
for (const path of selected) {
|
|
const { response, text } = await get(path);
|
|
assert.equal(response.status, 200, path);
|
|
const doc = new DOMParser().parseFromString(text, 'text/html');
|
|
for (const selector of ['title', 'h1', 'meta[name="description"]', 'link[rel="canonical"]', '#page-data', 'script[type="application/ld+json"]']) {
|
|
assert.equal(doc.querySelectorAll(selector).length, 1, `${path}: ${selector}`);
|
|
}
|
|
assert.equal(doc.querySelector('link[rel="canonical"]').getAttribute('href'), origin + path, path);
|
|
assert.ok(doc.querySelector('meta[name="description"]').getAttribute('content').trim(), `${path}: empty description`);
|
|
assert.ok(doc.querySelector('#root').textContent.length > 500, `${path}: empty server HTML`);
|
|
const initial = JSON.parse(doc.querySelector('#page-data').textContent);
|
|
assert.ok(initial.prerendered);
|
|
assert.ok(!Object.keys(initial).some((k) => /admin|lead|session/i.test(k)));
|
|
const schema = JSON.parse(doc.querySelector('script[type="application/ld+json"]').textContent);
|
|
assert.ok(schema['@graph'].some((n) => n['@type'] === 'BreadcrumbList'));
|
|
assert.equal(schema['@graph'][0].url, origin + path);
|
|
const second = await get(path);
|
|
assert.equal(second.response.headers.get('x-html-cache'), 'hit', `${path}: cache miss on identical data`);
|
|
hits++;
|
|
}
|
|
const query = await get('/company/staff/?q=test');
|
|
assert.equal(query.response.status, 200);
|
|
assert.ok(query.response.headers.get('x-robots-tag').includes('noindex'));
|
|
const queryDoc = new DOMParser().parseFromString(query.text, 'text/html');
|
|
assert.equal(queryDoc.querySelector('link[rel="canonical"]').getAttribute('href'), origin + '/company/staff/');
|
|
for (const alias of ['/company/docs/', '/services/estetic/rinoplastika']) {
|
|
assert.equal((await get(alias)).response.status, 301, alias);
|
|
}
|
|
for (const missing of ['/services/not-a-service/', '/company/staff/not-a-specialty/', '/company/staff/not-a-specialty/not-a-doctor/', '/price/not-a-price/', '/checkup/not-a-checkup/', '/legaldocs/not-a-document/', '/not-a-page/']) {
|
|
assert.equal((await get(missing)).response.status, 404, missing);
|
|
}
|
|
console.log(`Live SEO verified: ${selected.size} server-rendered pages, ${urls.length} sitemap URLs, ${hits} cache hits; query noindex, aliases and real 404s.`);
|