// Auto-linking bare URLs — a plain http(s) URL in a post's federated copy becomes a // clickable link; already-linked URLs and attribute values are left alone; trailing // sentence punctuation stays outside the link. In-memory SQLite. Run: npm test import { test } from 'node:test'; import assert from 'node:assert/strict'; process.env.DATABASE_PATH = ':memory:'; process.env.PUBLIC_BASE_URL = 'https://test.example'; const dbMod = await import('../src/config/database.js'); const db = dbMod.default; dbMod.initializeDatabase(); const AP = (await import('../src/services/ActivityPubService.js')).default; const BASE = 'https://test.example'; db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u1', 'u1', 'u1@test', 'x', 'god'); db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,?)').run('s1', 'demo', 'Demo', 'u1', 1); const site = db.prepare('SELECT * FROM sites WHERE id = ?').get('s1'); site.primary_slug = 'demo'; const note = (content, extra = {}) => AP.buildNote(BASE, site, { id: 'u' + Math.abs(content.length * 7919 % 100000), slug: 'x', title: '', content, tags: '[]', created_at: '2026-01-01T00:00:00Z', ...extra, }); test('a bare URL becomes a link; trailing punctuation stays outside', () => { const n = note('

check https://example.com/x?a=1&b=2, ok

'); assert.match(n.content, /https:\/\/example\.com\/x\?a=1&b=2<\/a>,/); }); test('a URL with a fragment does not get hashtag-ified', () => { const n = note('

see https://example.com/page#section

'); assert.match(n.content, /
]*>#section/.test(n.content), 'no hashtag link out of the fragment'); }); test('an already-linked URL is not wrapped twice', () => { const n = note('

https://example.com/

'); assert.equal((n.content.match(/ { const n = note('

and https://example.org

'); // is stripped from federated content (travels as attachment) — no anchor made for its src. assert.ok(!n.content.includes('cdn.example.com'), 'img src not present in content'); assert.match(n.content, /