mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
97 lines
4.0 KiB
TypeScript
97 lines
4.0 KiB
TypeScript
// @vitest-environment node
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { clearAllHooks, uiHooks, pluginErrorTracker } from '../plugin-hooks';
|
|
import { interceptWithResult, acceptLinkRewrite, resolveExternalLink } from '../plugin-hook-result';
|
|
|
|
|
|
beforeEach(() => {
|
|
clearAllHooks();
|
|
pluginErrorTracker.resetAll();
|
|
});
|
|
|
|
describe('interceptWithResult', () => {
|
|
it('lets the action through unchanged without handlers', async () => {
|
|
const ctx = { href: 'https://a.example/' };
|
|
expect(await interceptWithResult(uiHooks.onBeforeExternalLink, ctx, acceptLinkRewrite))
|
|
.toEqual({ allowed: true, value: ctx });
|
|
});
|
|
|
|
it('stops on false and does not ask later handlers', async () => {
|
|
const later = vi.fn();
|
|
uiHooks.onBeforeExternalLink.register('a', () => false);
|
|
uiHooks.onBeforeExternalLink.register('b', later);
|
|
const out = await interceptWithResult(uiHooks.onBeforeExternalLink, { href: 'https://a.example/' }, acceptLinkRewrite);
|
|
expect(out).toEqual({ allowed: false });
|
|
expect(later).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('takes a returned { href } and hands it to the next handler', async () => {
|
|
const second = vi.fn(() => undefined);
|
|
uiHooks.onBeforeExternalLink.register('a', () => ({ href: 'https://proxy.example/?u=1' }));
|
|
uiHooks.onBeforeExternalLink.register('b', second);
|
|
const out = await interceptWithResult(uiHooks.onBeforeExternalLink, { href: 'https://a.example/', emailId: 'e1' }, acceptLinkRewrite);
|
|
expect(out).toEqual({ allowed: true, value: { href: 'https://proxy.example/?u=1', emailId: 'e1' } });
|
|
expect(second).toHaveBeenCalledWith({ href: 'https://proxy.example/?u=1', emailId: 'e1' });
|
|
});
|
|
|
|
it('calls every handler once', async () => {
|
|
const handler = vi.fn(() => ({ href: 'https://b.example/' }));
|
|
uiHooks.onBeforeExternalLink.register('a', handler);
|
|
await interceptWithResult(uiHooks.onBeforeExternalLink, { href: 'https://a.example/' }, acceptLinkRewrite);
|
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('skips a handler that throws (fails open)', async () => {
|
|
uiHooks.onBeforeExternalLink.register('a', () => { throw new Error('boom'); });
|
|
const out = await interceptWithResult(uiHooks.onBeforeExternalLink, { href: 'https://a.example/' }, acceptLinkRewrite);
|
|
expect(out).toEqual({ allowed: true, value: { href: 'https://a.example/' } });
|
|
});
|
|
});
|
|
|
|
describe('acceptLinkRewrite', () => {
|
|
const cur = { href: 'https://a.example/' };
|
|
it.each([
|
|
'javascript:alert(1)',
|
|
'data:text/html,hi',
|
|
'/settings',
|
|
'mailto:x@example.com',
|
|
'https://',
|
|
])('ignores %s', (href) => {
|
|
expect(acceptLinkRewrite({ href }, cur)).toBeUndefined();
|
|
});
|
|
|
|
it('ignores anything but an object with a string href', () => {
|
|
expect(acceptLinkRewrite('https://b.example/', cur)).toBeUndefined();
|
|
expect(acceptLinkRewrite({ href: 42 }, cur)).toBeUndefined();
|
|
});
|
|
|
|
it('takes an http(s) address', () => {
|
|
expect(acceptLinkRewrite({ href: ' HTTP://b.example/x ' }, cur)).toEqual({ href: 'HTTP://b.example/x' });
|
|
});
|
|
});
|
|
|
|
describe('resolveExternalLink (a link clicked in a message)', () => {
|
|
const ctx = { href: 'https://a.example/', emailId: 'e1' };
|
|
|
|
it('opens the original address without plugins', async () => {
|
|
expect(await resolveExternalLink(ctx)).toBe('https://a.example/');
|
|
});
|
|
|
|
it('opens the address a plugin returns', async () => {
|
|
const handler = vi.fn(() => ({ href: 'https://proxy.example/?u=a' }));
|
|
uiHooks.onBeforeExternalLink.register('p', handler);
|
|
expect(await resolveExternalLink(ctx)).toBe('https://proxy.example/?u=a');
|
|
expect(handler).toHaveBeenCalledWith(ctx);
|
|
});
|
|
|
|
it('opens nothing when a plugin returns false', async () => {
|
|
uiHooks.onBeforeExternalLink.register('p', () => false);
|
|
expect(await resolveExternalLink(ctx)).toBeNull();
|
|
});
|
|
|
|
it('keeps the original when the rewrite is not a web address', async () => {
|
|
uiHooks.onBeforeExternalLink.register('p', () => ({ href: 'javascript:alert(1)' }));
|
|
expect(await resolveExternalLink(ctx)).toBe('https://a.example/');
|
|
});
|
|
});
|