myResume2/test/resume.test.js
Blade34242 45a3cddf06
All checks were successful
Node CI / lint (push) Successful in 1m29s
Refresh resume design, Node lifecycle, PDF export and SEO
2026-11-07 07:38:03 +07:00

112 lines
5.8 KiB
JavaScript

const { test } = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const fs = require('node:fs/promises');
const os = require('node:os');
const express = require('express');
const createResumeRouter = require('../routes/home');
const { buildSeo, siteOrigin } = require('../utils/seo');
async function serve(t, options) {
const app = express();
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '../views'));
app.use(createResumeRouter(options));
const server = app.listen(0, '127.0.0.1');
await new Promise((resolve, reject) => { server.once('listening', resolve); server.once('error', reject); });
t.after(() => new Promise((resolve) => server.close(resolve)));
return `http://127.0.0.1:${server.address().port}`;
}
for (const example of ['cvExample1', 'cvExample2']) {
test(`renders ${example}, metadata and crawler endpoints`, async (t) => {
const resumeFile = path.join(__dirname, '../public/examples', example, 'me.json');
const data = JSON.parse(await fs.readFile(resumeFile, 'utf8'));
const base = await serve(t, { resumeFile, siteUrl: 'https://resume.example.com' });
const response = await fetch(base);
assert.equal(response.status, 200);
const html = await response.text();
assert.match(html, /id="experience"/);
assert.match(html, /id="skills"/);
assert.match(html, /id="education"/);
assert.match(html, /media="print"/);
const schema = JSON.parse(html.match(/<script type="application\/ld\+json">(.*?)<\/script>/s)[1]);
assert.equal(schema.mainEntity.name, data.name);
assert.equal(schema.url, 'https://resume.example.com/');
const robots = await (await fetch(base + '/robots.txt')).text();
assert.match(robots, /Sitemap: https:\/\/resume.example.com\/sitemap.xml/);
assert.match(await (await fetch(base + '/sitemap.xml')).text(), /<loc>https:\/\/resume.example.com\/<\/loc>/);
const summary = await (await fetch(base + '/llms.txt')).text();
assert.ok(summary.includes(data.name));
assert.ok(summary.includes(data.workexperiences[0].desc));
const preview = await fetch(base + '/?pdf-preview');
assert.equal(preview.headers.get('x-robots-tag'), 'noindex, nofollow');
});
}
test('missing canonical does not fabricate a production hostname', async (t) => {
const resumeFile = path.join(__dirname, '../public/examples/cvExample1/me.json');
const base = await serve(t, { resumeFile, siteUrl: '' });
assert.equal((await fetch(base + '/sitemap.xml')).status, 503);
assert.doesNotMatch(await (await fetch(base)).text(), /rel="canonical"/);
assert.doesNotMatch(await (await fetch(base + '/robots.txt')).text(), /Sitemap:/);
});
test('structured data safely handles closing script tags without inventing profile identities', async () => {
const data = JSON.parse(await fs.readFile(path.join(__dirname, '../public/examples/cvExample1/me.json'), 'utf8'));
data.name = '</script><script>alert(1)</script>';
const seo = buildSeo(data, 'https://resume.example.com');
assert.ok(!seo.schema.includes('</script>'));
assert.equal(JSON.parse(seo.schema).mainEntity.name, data.name);
assert.equal(JSON.parse(seo.schema).mainEntity.sameAs, undefined);
assert.throws(() => siteOrigin('javascript:alert(1)'));
assert.throws(() => siteOrigin('https://resume.example.com/subpath'));
});
test('invalid resume returns a clear client error', async (t) => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'resume-test-'));
t.after(() => fs.rm(directory, { recursive: true, force: true }));
const resumeFile = path.join(directory, 'me.json');
await fs.writeFile(resumeFile, '{}');
const base = await serve(t, { resumeFile, siteUrl: '' });
assert.equal((await fetch(base)).status, 400);
});
test('me.json updates all public URLs live, overrides environment and recovers from invalid edits', async (t) => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'resume-config-'));
t.after(() => fs.rm(directory, { recursive: true, force: true }));
const resumeFile = path.join(directory, 'me.json');
const data = JSON.parse(await fs.readFile(path.join(__dirname, '../public/examples/cvExample1/me.json'), 'utf8'));
await fs.writeFile(resumeFile, JSON.stringify(data));
const writeConfig = (config) => fs.writeFile(resumeFile, JSON.stringify({ ...data, ...config }));
const base = await serve(t, { resumeFile, siteUrl: 'https://fallback.example.com' });
const checkOrigin = async (origin) => {
const html = await (await fetch(base)).text();
assert.ok(html.includes(`rel="canonical" href="${origin}/"`));
const schema = JSON.parse(html.match(/<script type="application\/ld\+json">(.*?)<\/script>/s)[1]);
assert.equal(schema.url, origin + '/');
for (const endpoint of ['/robots.txt', '/sitemap.xml', '/llms.txt']) {
const response = await fetch(base + endpoint);
assert.equal(response.status, 200);
assert.ok((await response.text()).includes(origin + '/'), endpoint);
}
};
await checkOrigin('https://fallback.example.com');
await writeConfig({ SITE_URL: 'https://first.example.com' });
await checkOrigin('https://first.example.com');
await writeConfig({ SITE_URL: 'https://second.example.com/' });
await checkOrigin('https://second.example.com');
for (const invalid of [123, null, 'javascript:alert(1)']) {
await writeConfig({ SITE_URL: invalid });
for (const endpoint of ['/', '/robots.txt', '/sitemap.xml', '/llms.txt']) {
assert.equal((await fetch(base + endpoint)).status, 503, endpoint);
}
}
await fs.writeFile(resumeFile, '{');
assert.equal((await fetch(base)).status, 503);
await writeConfig({});
await checkOrigin('https://fallback.example.com');
await writeConfig({ SITE_URL: '' });
assert.doesNotMatch(await (await fetch(base)).text(), /rel="canonical"/);
assert.equal((await fetch(base + '/sitemap.xml')).status, 503);
});