Files
CashuMints.space/web/test/review-body.test.mjs
michilisandCursor b95aab2bcd Improve write-review flow and require Node 22.18 for native TS stripping.
Gate login, reveal the form after a rating, and wire inline Rate this mint; drop --experimental-strip-types.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-24 21:52:11 +02:00

78 lines
3.5 KiB
JavaScript

/**
* The review body renderer, against hostile relay content.
*
* `lib/review-body.ts` is deliberately import-free so this file can load it under
* plain node with type stripping: `pnpm test` (node --test).
* If these fail, an event someone can sign today can break or script the card.
*/
import test from 'node:test';
import assert from 'node:assert/strict';
import { escapeHtml, reviewBodyHtml } from '../src/lib/review-body.ts';
import { HOSTILE_REVIEW_CONTENT, LONG_URL, PASTED_NPUB } from './hostile-review.mjs';
/** The output with this module's own <a> elements removed: nothing else may be markup. */
function withoutOwnLinks(html) {
return html.replace(/<a class="rev-link" [^>]*>.*?<\/a>/gs, '');
}
test('the hostile fixture renders with no live markup', () => {
const html = reviewBodyHtml(HOSTILE_REVIEW_CONTENT);
const rest = withoutOwnLinks(html);
assert.ok(!rest.includes('<'), `unescaped markup survived: ${rest.slice(0, 200)}`);
assert.ok(html.includes('&lt;script&gt;'), 'script tag must render as text');
assert.ok(!/<img|<b>|<script/.test(html), 'no element from the body ever becomes real');
});
test('40 newlines collapse to a single blank line', () => {
const html = reviewBodyHtml(HOSTILE_REVIEW_CONTENT);
assert.ok(!/\n{3,}/.test(html), '3+ consecutive newlines must not survive');
});
test('the 500-char URL links out but shows at most 40 characters', () => {
const html = reviewBodyHtml(HOSTILE_REVIEW_CONTENT);
const match = /<a class="rev-link" href="([^"]+)" target="_blank" rel="nofollow ugc noopener">([^<]+)<\/a>/.exec(html);
assert.ok(match, 'the URL must become a link');
assert.equal(match[1], LONG_URL, 'the full URL goes in the href');
assert.ok(match[2].length <= 40, `shown text is ${match[2].length} chars, wanted <= 40`);
assert.ok(match[2].includes('…'), 'truncation is middle-out, marked with an ellipsis');
assert.ok(match[2].startsWith('https://very.long.exampl'), 'the head survives');
});
test('a pasted npub stays inert text', () => {
const html = reviewBodyHtml(HOSTILE_REVIEW_CONTENT);
assert.ok(html.includes(PASTED_NPUB), 'the npub passes through as text');
assert.ok(!html.includes(`href="${PASTED_NPUB}`), 'and is never a link');
});
test('emoji pass through untouched', () => {
const html = reviewBodyHtml(HOSTILE_REVIEW_CONTENT);
assert.ok(html.includes('\u{1F9C0}✅'));
});
test('nostr: URIs render as short plain text, never links', () => {
const html = reviewBodyHtml(`see nostr:${PASTED_NPUB} for who I am`);
assert.ok(!html.includes('<a'), 'a nostr URI must not become a link');
assert.ok(!html.includes('nostr:'), 'the scheme is dropped');
assert.ok(html.includes('npub180cvv07…yjh6w6'), 'the payload is shortened middle-out');
});
test('control characters and bidi overrides are stripped', () => {
const html = reviewBodyHtml('a\u0000bc \u202Eevil\u202C d\u200Be');
assert.equal(html, 'abc evil de', 'controls, overrides and zero-widths gone');
});
test('trailing sentence punctuation stays out of the href', () => {
const html = reviewBodyHtml('read this (https://example.com/page).');
assert.ok(html.includes('href="https://example.com/page"'), 'the parenthesis and dot are prose');
assert.ok(html.includes('</a>).'), 'and stay visible after the link');
});
test('short URLs are shown whole', () => {
const html = reviewBodyHtml('https://mint.example.com');
assert.ok(html.includes('>https://mint.example.com</a>'));
});
test('escapeHtml escapes all five characters', () => {
assert.equal(escapeHtml(`<a b="c" & 'd'>`), '&lt;a b=&quot;c&quot; &amp; &#39;d&#39;&gt;');
});