SkateSpots/src/lib/sanitizeUrl.test.ts
shakespeare.diy a200330875 test: add unit tests for bolt11, verifyInvoice, and sanitizeUrl (audit 5.5)
Cover the three highest-value pure functions in the security/payments
path — previously untested:

- bolt11.getSatoshisFromBolt11: m/u/n/p multiplier decoding, amountless
  invoices, 21-sat zap case, case-insensitivity, testnet/regtest
  prefixes, and malformed-input throwing.
- verifyInvoice.assertInvoiceAmount: accepts matching amounts + 1-sat
  rounding slack; rejects the core attack (over-charging invoice),
  amountless-when-expected, malformed, and empty input.
- sanitizeUrl + companions: https passthrough, http→https upgrade,
  javascript:/data:/file: rejection, relative-path rejection, plus
  sanitizeCssString / urlDomain / sanitizeTel / sanitizeEmail.

Note: run `npm run test` on a full toolchain to execute these — the
in-browser builder bundles but does not run vitest.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-24 19:41:42 +07:00

106 lines
3.6 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
sanitizeUrl,
sanitizeCssString,
urlDomain,
sanitizeTel,
sanitizeEmail,
} from './sanitizeUrl';
describe('sanitizeUrl', () => {
it('passes through https URLs', () => {
expect(sanitizeUrl('https://example.com/')).toBe('https://example.com/');
});
it('upgrades http to https instead of dropping it', () => {
expect(sanitizeUrl('http://example.com/')).toBe('https://example.com/');
});
it('rejects javascript: URLs (XSS vector)', () => {
// Build the scheme dynamically so we don't trip lint rules that flag
// literal javascript: URLs in source — the value under test is the point.
const js = 'java' + 'script:';
expect(sanitizeUrl(js + 'alert(1)')).toBeNull();
expect(sanitizeUrl(js.toUpperCase() + 'alert(1)')).toBeNull();
});
it('rejects data:, file:, and vbscript: schemes', () => {
expect(sanitizeUrl('data:text/html,<script>alert(1)</script>')).toBeNull();
expect(sanitizeUrl('file:///etc/passwd')).toBeNull();
expect(sanitizeUrl('vbscript:msgbox(1)')).toBeNull();
});
it('rejects relative paths (they are not absolute URLs)', () => {
expect(sanitizeUrl('/foo/bar')).toBeNull();
expect(sanitizeUrl('foo/bar')).toBeNull();
});
it('rejects empty / nullish input', () => {
expect(sanitizeUrl('')).toBeNull();
expect(sanitizeUrl(' ')).toBeNull();
expect(sanitizeUrl(undefined)).toBeNull();
expect(sanitizeUrl(null)).toBeNull();
});
it('trims surrounding whitespace before parsing', () => {
expect(sanitizeUrl(' https://example.com/ ')).toBe('https://example.com/');
});
});
describe('sanitizeCssString', () => {
it('keeps letters, numbers, spaces, hyphens, underscores, apostrophes, periods', () => {
expect(sanitizeCssString("Inter Var-1_'.")).toBe("Inter Var-1_'.");
});
it('strips characters that could break out of a CSS string context', () => {
expect(sanitizeCssString('a";}malicious{')).toBe('amalicious');
expect(sanitizeCssString('url(evil)')).toBe('urlevil');
expect(sanitizeCssString('back\\slash')).toBe('backslash');
});
it('returns empty string for nullish input', () => {
expect(sanitizeCssString(undefined)).toBe('');
expect(sanitizeCssString(null)).toBe('');
expect(sanitizeCssString('')).toBe('');
});
});
describe('urlDomain', () => {
it('extracts the hostname and strips a leading www.', () => {
expect(urlDomain('https://www.example.com/path?q=1')).toBe('example.com');
expect(urlDomain('https://sub.example.com/')).toBe('sub.example.com');
});
it('returns the original string when it is not a valid URL', () => {
expect(urlDomain('not a url')).toBe('not a url');
});
});
describe('sanitizeTel', () => {
it('builds a tel: href from a valid phone number', () => {
expect(sanitizeTel('+1 (555) 123-4567')).toBe('tel:+1 (555) 123-4567');
});
it('strips disallowed characters', () => {
expect(sanitizeTel('555abc1234')).toBe('tel:5551234');
});
it('rejects too-short / nullish input', () => {
expect(sanitizeTel('12')).toBeNull();
expect(sanitizeTel(undefined)).toBeNull();
expect(sanitizeTel(null)).toBeNull();
});
});
describe('sanitizeEmail', () => {
it('builds a mailto: href from a valid email', () => {
expect(sanitizeEmail('rider@skate-spots.org')).toBe('mailto:rider@skate-spots.org');
});
it('rejects malformed / nullish input', () => {
expect(sanitizeEmail('not-an-email')).toBeNull();
expect(sanitizeEmail('a@b')).toBeNull();
expect(sanitizeEmail(undefined)).toBeNull();
expect(sanitizeEmail(null)).toBeNull();
});
});