// ============================================================
// Contract test for the Notes reader sanitizer. Confirms that
// DOMPurify (loaded via jsdom here) strips the payloads we care
// about — script tags, inline event handlers, iframes, object
// tags — while preserving the formatting tags we DO allow.
//
// Why this test exists:
// The original Notes module used a homegrown walker-based
// sanitizer. When the body round-trips through localStorage or
// a compromised row, a bypass in the sanitizer means session
// theft. We swapped to DOMPurify; this test holds the contract
// so a regression is loud.
// ============================================================

const { test } = require('node:test');
const assert = require('node:assert/strict');
const { JSDOM } = require('jsdom');
const createDOMPurify = require('dompurify');

// Match the allowlist used in public/js/notes/utils.js `sanitizeHtml`.
const ALLOWED_TAGS = ['p','br','strong','em','b','i','u','s','h2','h3','h4',
  'ul','ol','li','a','blockquote','code','pre','hr'];
const ALLOWED_ATTR = ['href','target','rel'];

function makeSanitizer() {
  const window = new JSDOM('').window;
  const DOMPurify = createDOMPurify(window);
  return function sanitize(html) {
    return DOMPurify.sanitize(html, {
      ALLOWED_TAGS, ALLOWED_ATTR,
      FORBID_ATTR: ['style','onerror','onload','onclick','onmouseover'],
      ALLOW_DATA_ATTR: false,
    });
  };
}

test('strips <script> tags completely', () => {
  const sanitize = makeSanitizer();
  const out = sanitize('<p>ok</p><script>alert(1)</script>');
  assert.ok(!out.includes('<script'));
  assert.ok(!out.includes('alert'));
  assert.match(out, /<p>ok<\/p>/);
});

test('strips inline event handlers on otherwise-allowed tags', () => {
  const sanitize = makeSanitizer();
  const out = sanitize('<a href="https://x" onclick="steal()">link</a>');
  assert.ok(!out.includes('onclick'));
  assert.ok(!out.includes('steal'));
  assert.match(out, /href="https:\/\/x"/);
});

test('drops <img onerror="...">  — img is not in the allowlist', () => {
  const sanitize = makeSanitizer();
  const out = sanitize('<img src=x onerror="alert(1)">');
  assert.ok(!out.includes('<img'));
  assert.ok(!out.includes('onerror'));
});

test('drops <iframe> and <object> (framing/plugin injection)', () => {
  const sanitize = makeSanitizer();
  assert.ok(!sanitize('<iframe src="evil"></iframe>').includes('iframe'));
  assert.ok(!sanitize('<object data="evil"></object>').includes('object'));
});

test('drops style attributes — CSS injection / expression() on older browsers', () => {
  const sanitize = makeSanitizer();
  const out = sanitize('<p style="background:url(evil)">text</p>');
  assert.ok(!out.includes('style'));
  assert.match(out, /<p>text<\/p>/);
});

test('preserves every tag in the allowlist', () => {
  const sanitize = makeSanitizer();
  for (const tag of ALLOWED_TAGS) {
    const html = `<${tag}>x</${tag}>`;
    const out = sanitize(html);
    // <br> and <hr> are self-closing; DOMPurify may emit them either way.
    if (tag === 'br' || tag === 'hr') {
      assert.match(out, new RegExp(`<${tag}`), `expected <${tag}> preserved`);
    } else {
      assert.match(out, new RegExp(`<${tag}>x</${tag}>`), `expected <${tag}>x</${tag}> preserved`);
    }
  }
});

test('preserves mailto + https links; drops javascript: URIs', () => {
  const sanitize = makeSanitizer();
  assert.match(sanitize('<a href="https://ok">x</a>'),  /href="https:\/\/ok"/);
  assert.match(sanitize('<a href="mailto:a@b.c">x</a>'), /href="mailto:a@b\.c"/);
  const jsAttempt = sanitize('<a href="javascript:alert(1)">x</a>');
  assert.ok(!jsAttempt.includes('javascript:'), 'javascript: URI should be stripped');
});

test('empty + null + non-string input does not throw', () => {
  const sanitize = makeSanitizer();
  assert.equal(sanitize(''), '');
  assert.equal(sanitize(null), '');
  assert.equal(sanitize(undefined), '');
});

test('nested tags: <script> inside <p> still stripped', () => {
  const sanitize = makeSanitizer();
  const out = sanitize('<p>safe<script>evil()</script>still safe</p>');
  assert.ok(!out.includes('<script'));
  assert.ok(!out.includes('evil()'));
  // Text content before + after the stripped tag should survive
  assert.match(out, /safe/);
  assert.match(out, /still safe/);
});
