@linkforty/core 1.25.3 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +43 -0
  2. package/dist/index.d.ts +5 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/database.d.ts.map +1 -1
  7. package/dist/lib/database.js +19 -0
  8. package/dist/lib/database.js.map +1 -1
  9. package/dist/lib/launchpad.d.ts +153 -0
  10. package/dist/lib/launchpad.d.ts.map +1 -0
  11. package/dist/lib/launchpad.js +237 -0
  12. package/dist/lib/launchpad.js.map +1 -0
  13. package/dist/lib/launchpad.test.d.ts +2 -0
  14. package/dist/lib/launchpad.test.d.ts.map +1 -0
  15. package/dist/lib/launchpad.test.js +200 -0
  16. package/dist/lib/launchpad.test.js.map +1 -0
  17. package/dist/lib/link-safety.d.ts +96 -7
  18. package/dist/lib/link-safety.d.ts.map +1 -1
  19. package/dist/lib/link-safety.js +108 -8
  20. package/dist/lib/link-safety.js.map +1 -1
  21. package/dist/lib/link-safety.test.js +95 -1
  22. package/dist/lib/link-safety.test.js.map +1 -1
  23. package/dist/routes/links.d.ts.map +1 -1
  24. package/dist/routes/links.js +10 -4
  25. package/dist/routes/links.js.map +1 -1
  26. package/dist/routes/redirect.d.ts +19 -0
  27. package/dist/routes/redirect.d.ts.map +1 -1
  28. package/dist/routes/redirect.js +92 -19
  29. package/dist/routes/redirect.js.map +1 -1
  30. package/dist/routes/redirect.launchpad.test.d.ts +2 -0
  31. package/dist/routes/redirect.launchpad.test.d.ts.map +1 -0
  32. package/dist/routes/redirect.launchpad.test.js +255 -0
  33. package/dist/routes/redirect.launchpad.test.js.map +1 -0
  34. package/dist/routes/redirect.no-web-destination.test.js +32 -11
  35. package/dist/routes/redirect.no-web-destination.test.js.map +1 -1
  36. package/dist/routes/redirect.safety.test.js +116 -13
  37. package/dist/routes/redirect.safety.test.js.map +1 -1
  38. package/dist/routes/sdk-cache-bypass.test.js +10 -2
  39. package/dist/routes/sdk-cache-bypass.test.js.map +1 -1
  40. package/dist/types/index.d.ts +2 -0
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/llms.txt +16 -0
  43. package/package.json +1 -1
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Route-level tests for the launchpad page: when the redirect serves it, what it
3
+ * carries, and — most importantly — when it does not.
4
+ *
5
+ * Follows redirect.no-web-destination.test.ts: the real `redirectRoutes` plugin
6
+ * through `fastify.inject()`, only the data layer mocked.
7
+ */
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
9
+ import Fastify from 'fastify';
10
+ import { redirectRoutes } from './redirect.js';
11
+ const query = vi.fn();
12
+ vi.mock('../lib/database.js', () => ({
13
+ db: {
14
+ query: (...args) => query(...args),
15
+ },
16
+ }));
17
+ const DESKTOP_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36';
18
+ const IPHONE_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1';
19
+ function linkRow(overrides = {}) {
20
+ return {
21
+ id: '00000000-0000-0000-0000-0000000000aa',
22
+ short_code: 'abc123',
23
+ title: null,
24
+ description: null,
25
+ og_title: null,
26
+ og_description: null,
27
+ og_image_url: null,
28
+ original_url: null,
29
+ web_fallback_url: null,
30
+ ios_app_store_url: 'https://apps.apple.com/app/id1',
31
+ android_app_store_url: 'https://play.google.com/store/apps/details?id=demo',
32
+ ios_universal_link: null,
33
+ android_app_link: null,
34
+ app_scheme: null,
35
+ deep_link_path: null,
36
+ launchpad_mode: 'inherit',
37
+ is_active: true,
38
+ warn_at: null,
39
+ owner_suspended_at: null,
40
+ expires_at: null,
41
+ targeting_rules: null,
42
+ template_settings: null,
43
+ org_settings: null,
44
+ utm_parameters: null,
45
+ deep_link_parameters: null,
46
+ append_click_id: false,
47
+ ...overrides,
48
+ };
49
+ }
50
+ function mockDb(row) {
51
+ query.mockReset();
52
+ query.mockImplementation(async (sql) => {
53
+ if (/information_schema\.columns/i.test(sql)) {
54
+ return { rows: [{ '?column?': 1 }], rowCount: 1 };
55
+ }
56
+ if (/^\s*SELECT\s+l\.\*/i.test(sql) || /FROM links l/i.test(sql)) {
57
+ return { rows: row ? [row] : [], rowCount: row ? 1 : 0 };
58
+ }
59
+ return { rows: [], rowCount: 0 };
60
+ });
61
+ }
62
+ const get = (app, ua, url = '/abc123') => app.inject({ method: 'GET', url, headers: { 'user-agent': ua, host: 'go.example' } });
63
+ async function build(options = {}) {
64
+ const app = Fastify();
65
+ await app.register(redirectRoutes, options);
66
+ await app.ready();
67
+ return app;
68
+ }
69
+ let app;
70
+ beforeEach(async () => {
71
+ app = await build();
72
+ });
73
+ afterEach(async () => {
74
+ await new Promise((r) => setImmediate(r));
75
+ await app.close();
76
+ });
77
+ describe('launchpad page — when it is served', () => {
78
+ it('replaces the plain page for a desktop visitor to a link with no web destination', async () => {
79
+ mockDb(linkRow({ og_title: 'Ride Alert', og_description: 'Live updates for your route' }));
80
+ const res = await get(app, DESKTOP_UA);
81
+ expect(res.statusCode).toBe(200);
82
+ expect(res.headers['content-type']).toMatch(/text\/html/);
83
+ expect(res.headers['x-robots-tag']).toBe('noindex, nofollow');
84
+ expect(res.headers['cache-control']).toBe('no-store');
85
+ expect(res.headers['content-security-policy']).toMatch(/^default-src 'none'; .*script-src 'nonce-[A-Za-z0-9+/=]+'/);
86
+ expect(res.body).toContain('<h1>Ride Alert</h1>');
87
+ expect(res.body).toContain('Live updates for your route');
88
+ expect(res.body).toContain('Download on the App Store');
89
+ expect(res.body).toContain('Get it on Google Play');
90
+ expect(res.body).toContain('/api/links/00000000-0000-0000-0000-0000000000aa/qr?format=svg');
91
+ expect(res.body).toContain('<meta property="og:url" content="http://go.example/abc123">');
92
+ expect(res.body).not.toContain('This link opens in an app');
93
+ });
94
+ it('never attempts a scheme open on desktop, and ships no script when nothing needs one', async () => {
95
+ mockDb(linkRow({ app_scheme: 'demo', deep_link_path: '/p/1' }));
96
+ const res = await get(app, DESKTOP_UA);
97
+ expect(res.statusCode).toBe(200);
98
+ expect(res.body).not.toContain('Open in app');
99
+ expect(res.body).not.toContain('<script');
100
+ });
101
+ it('keeps the 302 for a link with a destination when nobody opted in', async () => {
102
+ mockDb(linkRow({ web_fallback_url: 'https://example.com/page' }));
103
+ const res = await get(app, DESKTOP_UA);
104
+ expect(res.statusCode).toBe(302);
105
+ expect(res.headers.location).toBe('https://example.com/page');
106
+ });
107
+ it("serves the page in front of a destination when the workspace chose 'always'", async () => {
108
+ mockDb(linkRow({ web_fallback_url: 'https://example.com/page', org_settings: { launchpad: { desktop: 'always' } } }));
109
+ const res = await get(app, DESKTOP_UA);
110
+ expect(res.statusCode).toBe(200);
111
+ expect(res.body).toContain('class="lp"');
112
+ });
113
+ it("a link set to 'off' wins over a workspace set to 'always'", async () => {
114
+ mockDb(linkRow({
115
+ web_fallback_url: 'https://example.com/page',
116
+ launchpad_mode: 'off',
117
+ org_settings: { launchpad: { desktop: 'always' } },
118
+ }));
119
+ const res = await get(app, DESKTOP_UA);
120
+ expect(res.statusCode).toBe(302);
121
+ });
122
+ it("a link set to 'on' wins over the workspace default", async () => {
123
+ mockDb(linkRow({ web_fallback_url: 'https://example.com/page', launchpad_mode: 'on' }));
124
+ const res = await get(app, DESKTOP_UA);
125
+ expect(res.statusCode).toBe(200);
126
+ expect(res.body).toContain('class="lp"');
127
+ });
128
+ it("a workspace set to 'off' gets the plain page for a link with no destination", async () => {
129
+ mockDb(linkRow({ org_settings: { launchpad: { desktop: 'off' } } }));
130
+ const res = await get(app, DESKTOP_UA);
131
+ expect(res.statusCode).toBe(200);
132
+ expect(res.body).toContain('This link opens in an app');
133
+ expect(res.body).not.toContain('class="lp"');
134
+ });
135
+ it('treats a cached row from before the column existed as inherit', async () => {
136
+ const row = linkRow();
137
+ delete row.launchpad_mode;
138
+ mockDb(row);
139
+ const res = await get(app, DESKTOP_UA);
140
+ expect(res.statusCode).toBe(200);
141
+ expect(res.body).toContain('class="lp"');
142
+ });
143
+ it('uses the template slug in og:url for a templated short link', async () => {
144
+ mockDb(linkRow());
145
+ const res = await get(app, DESKTOP_UA, '/promo/abc123');
146
+ expect(res.body).toContain('<meta property="og:url" content="http://go.example/promo/abc123">');
147
+ });
148
+ });
149
+ describe('launchpad page — what does not change', () => {
150
+ it('leaves an iPhone visitor on the store redirect', async () => {
151
+ mockDb(linkRow());
152
+ const res = await get(app, IPHONE_UA);
153
+ expect(res.statusCode).toBe(302);
154
+ expect(res.headers.location).toBe('https://apps.apple.com/app/id1');
155
+ });
156
+ it("leaves an iPhone visitor on the store redirect even under 'always'", async () => {
157
+ mockDb(linkRow({ org_settings: { launchpad: { desktop: 'always' } } }));
158
+ const res = await get(app, IPHONE_UA);
159
+ expect(res.statusCode).toBe(302);
160
+ });
161
+ it('leaves an iPhone visitor on the scheme interstitial when the link has a scheme', async () => {
162
+ mockDb(linkRow({ app_scheme: 'demo', deep_link_path: '/p/1' }));
163
+ const res = await get(app, IPHONE_UA);
164
+ expect(res.statusCode).toBe(200);
165
+ expect(res.body).toContain('Opening');
166
+ expect(res.body).not.toContain('class="lp"');
167
+ });
168
+ it('still 404s an unknown short code', async () => {
169
+ mockDb(null);
170
+ expect((await get(app, DESKTOP_UA)).statusCode).toBe(404);
171
+ });
172
+ it('still records the click', async () => {
173
+ mockDb(linkRow());
174
+ await get(app, DESKTOP_UA);
175
+ await new Promise((r) => setImmediate(r));
176
+ expect(query.mock.calls.some(([sql]) => /INSERT INTO click_events/i.test(String(sql)))).toBe(true);
177
+ });
178
+ });
179
+ describe('launchpad page — content and escaping', () => {
180
+ it('escapes a title and an image URL carrying HTML', async () => {
181
+ mockDb(linkRow({ og_title: `<script>alert("xss")</script>`, og_image_url: 'https://cdn.example/a.png?x="y"' }));
182
+ const res = await get(app, DESKTOP_UA);
183
+ expect(res.body).not.toContain('<script>alert');
184
+ expect(res.body).toContain('&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;');
185
+ expect(res.body).toContain('content="https://cdn.example/a.png?x=&quot;y&quot;"');
186
+ expect(res.body).toContain('src="https://cdn.example/a.png?x=&quot;y&quot;"');
187
+ });
188
+ it('applies the workspace theme', async () => {
189
+ mockDb(linkRow({
190
+ org_settings: { launchpad: { appName: 'Demo App', appIconUrl: 'https://cdn.example/icon.png', accentColor: '#0f766e' } },
191
+ }));
192
+ const res = await get(app, DESKTOP_UA);
193
+ expect(res.body).toContain('<span>Demo App</span>');
194
+ expect(res.body).toContain('<img src="https://cdn.example/icon.png" alt="" width="48" height="48">');
195
+ expect(res.body).toContain('--lp-accent: #0f766e;');
196
+ });
197
+ });
198
+ describe('launchpad page — host hooks', () => {
199
+ it('inserts heroHtml from resolveContent and keeps the rest of the frame', async () => {
200
+ await app.close();
201
+ app = await build({
202
+ launchpad: {
203
+ resolveContent: async (link, settings) => ({
204
+ title: `Resolved ${link.short_code}`,
205
+ description: 'from the host',
206
+ imageUrl: null,
207
+ heroHtml: `<div class="host-hero">${settings.templateId ?? 'default'}</div>`,
208
+ theme: {},
209
+ }),
210
+ },
211
+ });
212
+ mockDb(linkRow({ org_settings: { launchpad: { templateId: 'hero' } } }));
213
+ const res = await get(app, DESKTOP_UA);
214
+ expect(res.statusCode).toBe(200);
215
+ expect(res.body).toContain('<div class="host-hero">hero</div>');
216
+ expect(res.body).toContain('<title>Resolved abc123</title>');
217
+ expect(res.body).toContain('Download on the App Store');
218
+ });
219
+ it('falls back to the default content when resolveContent throws or returns null', async () => {
220
+ await app.close();
221
+ let calls = 0;
222
+ app = await build({
223
+ launchpad: {
224
+ resolveContent: async () => {
225
+ calls++;
226
+ if (calls === 1)
227
+ throw new Error('boom');
228
+ return null;
229
+ },
230
+ },
231
+ });
232
+ mockDb(linkRow({ og_title: 'Default title' }));
233
+ const thrown = await get(app, DESKTOP_UA);
234
+ expect(thrown.statusCode).toBe(200);
235
+ expect(thrown.body).toContain('<h1>Default title</h1>');
236
+ const nulled = await get(app, DESKTOP_UA);
237
+ expect(nulled.statusCode).toBe(200);
238
+ expect(nulled.body).toContain('<h1>Default title</h1>');
239
+ expect(calls).toBe(2);
240
+ });
241
+ it('emits beacons only when a beacon URL is configured, and admits its origin in the CSP', async () => {
242
+ mockDb(linkRow());
243
+ const silent = await get(app, DESKTOP_UA);
244
+ expect(silent.body).not.toContain('sendBeacon');
245
+ await app.close();
246
+ app = await build({ launchpad: { beaconUrl: 'https://events.example/v1/launchpad' } });
247
+ mockDb(linkRow());
248
+ const res = await get(app, DESKTOP_UA);
249
+ expect(res.body.match(/navigator\.sendBeacon\(/g)).toHaveLength(1);
250
+ expect(res.body).toContain('data-lp-cta="cta_ios"');
251
+ expect(res.body).toContain('data-lp-cta="cta_android"');
252
+ expect(res.headers['content-security-policy']).toContain("connect-src 'self' https://events.example");
253
+ });
254
+ });
255
+ //# sourceMappingURL=redirect.launchpad.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect.launchpad.test.js","sourceRoot":"","sources":["../../src/routes/redirect.launchpad.test.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,13 @@
1
1
  /**
2
- * Route-level tests for the page shown when a link has no web destination.
2
+ * Route-level tests for the plain page shown when a link has no web destination
3
+ * and the launchpad page is switched off.
3
4
  *
4
5
  * A link belonging to an app-only product has nothing to put in a web fallback,
5
6
  * so a desktop visitor used to receive `{"error":"No destination URL configured
6
- * for this link"}` as a raw 404 body — rendered on the customer's own branded
7
- * short-link domain.
7
+ * for this link"}` as a raw 404 body — rendered on the link owner's own branded
8
+ * short-link domain. The launchpad page (redirect.launchpad.test.ts) is now the
9
+ * default for that case; the plain page remains for workspaces that opt out, so
10
+ * every test here pins `launchpad.desktop = 'off'`.
8
11
  *
9
12
  * The generator is module-private, so these drive the real `redirectRoutes`
10
13
  * plugin through `fastify.inject()` with only the data layer mocked, following
@@ -51,6 +54,10 @@ function linkRow(overrides = {}) {
51
54
  ...overrides,
52
55
  };
53
56
  }
57
+ /** The same link, in a workspace that has switched the launchpad page off. */
58
+ function plainPageRow(overrides = {}) {
59
+ return linkRow({ org_settings: { launchpad: { desktop: 'off' } }, ...overrides });
60
+ }
54
61
  function mockDb(row) {
55
62
  query.mockReset();
56
63
  query.mockImplementation(async (sql) => {
@@ -77,7 +84,7 @@ afterEach(async () => {
77
84
  });
78
85
  describe('no web destination', () => {
79
86
  it('serves an HTML page to a desktop visitor instead of a JSON 404', async () => {
80
- mockDb(linkRow());
87
+ mockDb(plainPageRow());
81
88
  const res = await get(app, DESKTOP_UA);
82
89
  expect(res.statusCode).toBe(200);
83
90
  expect(res.headers['content-type']).toMatch(/text\/html/);
@@ -86,13 +93,13 @@ describe('no web destination', () => {
86
93
  expect(res.body).toContain('This link opens in an app');
87
94
  });
88
95
  it('uses the link title, preferring og_title', async () => {
89
- mockDb(linkRow({ title: 'Plain title', og_title: 'OG title' }));
96
+ mockDb(plainPageRow({ title: 'Plain title', og_title: 'OG title' }));
90
97
  const res = await get(app, DESKTOP_UA);
91
98
  expect(res.body).toContain('OG title');
92
99
  expect(res.body).not.toContain('Plain title');
93
100
  });
94
101
  it('falls back to title when og_title is unset', async () => {
95
- mockDb(linkRow({ title: 'Plain title' }));
102
+ mockDb(plainPageRow({ title: 'Plain title' }));
96
103
  expect((await get(app, DESKTOP_UA)).body).toContain('Plain title');
97
104
  });
98
105
  /**
@@ -100,7 +107,7 @@ describe('no web destination', () => {
100
107
  * the test that matters most in this file.
101
108
  */
102
109
  it('escapes a title containing HTML', async () => {
103
- mockDb(linkRow({ og_title: `<script>alert("xss")</script> & 'quotes'` }));
110
+ mockDb(plainPageRow({ og_title: `<script>alert("xss")</script> & 'quotes'` }));
104
111
  const res = await get(app, DESKTOP_UA);
105
112
  expect(res.body).not.toContain('<script>');
106
113
  expect(res.body).toContain('&lt;script&gt;');
@@ -109,19 +116,33 @@ describe('no web destination', () => {
109
116
  expect(res.body).toContain('&#39;');
110
117
  });
111
118
  it('shows store buttons only for the platforms the link carries', async () => {
112
- mockDb(linkRow({ ios_app_store_url: 'https://apps.apple.com/app/id1' }));
119
+ mockDb(plainPageRow({ ios_app_store_url: 'https://apps.apple.com/app/id1' }));
113
120
  const onlyIos = await get(app, DESKTOP_UA);
114
121
  expect(onlyIos.body).toContain('Download for iOS');
115
122
  expect(onlyIos.body).not.toContain('Download for Android');
116
- mockDb(linkRow());
123
+ mockDb(plainPageRow());
117
124
  const neither = await get(app, DESKTOP_UA);
118
125
  expect(neither.body).not.toContain('Download for');
119
126
  });
120
127
  it('escapes a store URL into the href', async () => {
121
- mockDb(linkRow({ ios_app_store_url: 'https://apps.apple.com/app?a=1&b=2' }));
128
+ mockDb(plainPageRow({ ios_app_store_url: 'https://apps.apple.com/app?a=1&b=2' }));
122
129
  const res = await get(app, DESKTOP_UA);
123
130
  expect(res.body).toContain('https://apps.apple.com/app?a=1&amp;b=2');
124
131
  });
132
+ it('is the fallback when the link itself opts out of the launchpad page', async () => {
133
+ mockDb(linkRow({ launchpad_mode: 'off' }));
134
+ const res = await get(app, DESKTOP_UA);
135
+ expect(res.statusCode).toBe(200);
136
+ expect(res.body).toContain('This link opens in an app');
137
+ expect(res.body).not.toContain('class="lp"');
138
+ });
139
+ it('is not served by default — the launchpad page is', async () => {
140
+ mockDb(linkRow());
141
+ const res = await get(app, DESKTOP_UA);
142
+ expect(res.statusCode).toBe(200);
143
+ expect(res.body).toContain('class="lp"');
144
+ expect(res.body).not.toContain('This link opens in an app');
145
+ });
125
146
  it('resolves through the template and workspace chain before showing the page', async () => {
126
147
  mockDb(linkRow({ template_settings: { defaultWebFallbackUrl: 'https://from-template.example' } }));
127
148
  const res = await get(app, DESKTOP_UA);
@@ -146,7 +167,7 @@ describe('no web destination', () => {
146
167
  * opposite case — a genuine visit to a working link.
147
168
  */
148
169
  it('still records the click', async () => {
149
- mockDb(linkRow());
170
+ mockDb(plainPageRow());
150
171
  await get(app, DESKTOP_UA);
151
172
  await new Promise((r) => setImmediate(r));
152
173
  expect(query.mock.calls.some(([sql]) => /INSERT INTO click_events/i.test(String(sql)))).toBe(true);
@@ -1 +1 @@
1
- {"version":3,"file":"redirect.no-web-destination.test.js","sourceRoot":"","sources":["../../src/routes/redirect.no-web-destination.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"redirect.no-web-destination.test.js","sourceRoot":"","sources":["../../src/routes/redirect.no-web-destination.test.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,7 @@ function linkRow(overrides = {}) {
26
26
  deep_link_path: null,
27
27
  is_active: true,
28
28
  warn_at: null,
29
+ disabled_at: null,
29
30
  owner_suspended_at: null,
30
31
  expires_at: null,
31
32
  targeting_rules: null,
@@ -81,23 +82,109 @@ describe('redirect safety gate', () => {
81
82
  expect(res.statusCode).toBe(302);
82
83
  expect(res.headers.location).toBe('https://example.com/landing');
83
84
  });
84
- it('404s an inactive link', async () => {
85
+ /**
86
+ * An inactive link with no `disabled_at` is expired or was switched off by whoever
87
+ * made it. Nobody decided anything about abuse, so nothing is explained.
88
+ *
89
+ * In practice the WHERE clause excludes these before they reach the gate; this
90
+ * covers the row arriving from cache, where it can still be seen.
91
+ */
92
+ it('404s an inactive link that was not disabled by a decision', async () => {
85
93
  mockDb(linkRow({ is_active: false }));
86
94
  const res = await app.inject({ method: 'GET', url: '/abc123' });
87
95
  expect(res.statusCode).toBe(404);
96
+ expect(res.body).not.toContain('This link is no longer available');
88
97
  });
89
- it('404s when the owner is restricted, even though the link itself is fine', async () => {
90
- mockDb(linkRow({ owner_suspended_at: '2026-08-10T00:00:00Z' }));
91
- const res = await app.inject({ method: 'GET', url: '/abc123' });
92
- expect(res.statusCode).toBe(404);
93
- });
94
- it('gives a restricted owner the SAME response as an unknown code, leaking nothing', async () => {
95
- mockDb(linkRow({ owner_suspended_at: '2026-08-10T00:00:00Z' }));
96
- const restricted = await app.inject({ method: 'GET', url: '/abc123' });
98
+ /**
99
+ * The narrowed form of the old "leaks nothing" test.
100
+ *
101
+ * That test asserted every blocked link was byte-identical to an unknown code.
102
+ * Deliberately no longer true — an abuse decision now earns an explanation, see
103
+ * link-safety.ts. What must still hold is that a link which is merely *off* gives
104
+ * nothing away, because its owner did nothing wrong and its visitor was not
105
+ * targeted. This is the assertion that fails if the notice is ever widened to
106
+ * plain `is_active`.
107
+ */
108
+ it('gives a merely inactive link the SAME response as an unknown code', async () => {
109
+ mockDb(linkRow({ is_active: false }));
110
+ const inactive = await app.inject({ method: 'GET', url: '/abc123' });
97
111
  mockDb(null);
98
112
  const unknown = await app.inject({ method: 'GET', url: '/nosuchcode' });
99
- expect(restricted.statusCode).toBe(unknown.statusCode);
100
- expect(restricted.body).toBe(unknown.body);
113
+ expect(inactive.statusCode).toBe(unknown.statusCode);
114
+ expect(inactive.body).toBe(unknown.body);
115
+ });
116
+ describe('a link blocked by an abuse decision', () => {
117
+ const SUSPENDED = { owner_suspended_at: '2026-08-10T00:00:00Z' };
118
+ const DISABLED = { is_active: false, disabled_at: '2026-08-10T00:00:00Z' };
119
+ it.each([
120
+ ['the owner is restricted', SUSPENDED],
121
+ ['the link itself was disabled', DISABLED],
122
+ ])('serves the notice with 410 Gone when %s', async (_label, row) => {
123
+ mockDb(linkRow(row));
124
+ const res = await app.inject({ method: 'GET', url: '/abc123' });
125
+ expect(res.statusCode).toBe(410);
126
+ expect(res.headers['content-type']).toMatch(/text\/html/);
127
+ expect(res.body).toContain('This link is no longer available');
128
+ });
129
+ it('never redirects — there is nowhere safe to send anyone', async () => {
130
+ mockDb(linkRow(SUSPENDED));
131
+ const res = await app.inject({ method: 'GET', url: '/abc123' });
132
+ expect(res.statusCode).not.toBe(302);
133
+ expect(res.headers.location).toBeUndefined();
134
+ });
135
+ /** The warning page offers a way through because it is only a suspicion. This is not. */
136
+ it('offers no way to continue', async () => {
137
+ mockDb(linkRow(SUSPENDED));
138
+ const res = await app.inject({ method: 'GET', url: '/abc123' });
139
+ expect(res.body).not.toContain('Continue anyway');
140
+ expect(res.body).not.toMatch(/<a[^>]+href="https?:/i);
141
+ });
142
+ /**
143
+ * The assertion that matters most. Naming the destination puts the hostile URL
144
+ * back in front of the one person already proven to click it, and the workspace
145
+ * and owner are somebody else's details.
146
+ */
147
+ it('names no destination, workspace, or account holder', async () => {
148
+ mockDb(linkRow({
149
+ ...SUSPENDED,
150
+ original_url: 'https://phish.example/steal',
151
+ web_fallback_url: 'https://phish.example/fallback',
152
+ deep_link_path: '/secret-path',
153
+ }));
154
+ const res = await app.inject({ method: 'GET', url: '/abc123' });
155
+ expect(res.body).not.toContain('phish.example');
156
+ expect(res.body).not.toContain('secret-path');
157
+ });
158
+ /**
159
+ * The page must not tell a visitor why the link went away. It is a claim about
160
+ * a user's content on a domain we serve, and it would be false the first time a
161
+ * link is disabled in error.
162
+ */
163
+ it('does not say why the link was removed', async () => {
164
+ mockDb(linkRow(SUSPENDED));
165
+ const res = await app.inject({ method: 'GET', url: '/abc123' });
166
+ for (const word of ['phishing', 'fraud', 'scam', 'abuse', 'reported', 'suspended', 'restricted']) {
167
+ expect(res.body.toLowerCase(), `must not mention "${word}"`).not.toContain(word);
168
+ }
169
+ });
170
+ it('asks not to be indexed or cached', async () => {
171
+ mockDb(linkRow(SUSPENDED));
172
+ const res = await app.inject({ method: 'GET', url: '/abc123' });
173
+ expect(res.headers['x-robots-tag']).toMatch(/noindex/);
174
+ expect(res.headers['cache-control']).toMatch(/no-store/);
175
+ });
176
+ it('records NO click — nobody reached the destination', async () => {
177
+ mockDb(linkRow(SUSPENDED));
178
+ await app.inject({ method: 'GET', url: '/abc123' });
179
+ expect(await clickWasRecorded()).toBe(false);
180
+ });
181
+ /** An unknown code must stay a plain 404; only a real, withdrawn code is 410. */
182
+ it('still 404s an unknown short code', async () => {
183
+ mockDb(null);
184
+ const res = await app.inject({ method: 'GET', url: '/nosuchcode' });
185
+ expect(res.statusCode).toBe(404);
186
+ expect(res.body).not.toContain('This link is no longer available');
187
+ });
101
188
  });
102
189
  describe('a link flagged to warn', () => {
103
190
  it('serves an interstitial instead of redirecting', async () => {
@@ -134,10 +221,26 @@ describe('redirect safety gate', () => {
134
221
  const res = await app.inject({ method: 'GET', url: '/abc123' });
135
222
  expect(res.body).toContain('https://example.net/x');
136
223
  });
137
- it('is outranked by owner restriction', async () => {
224
+ /**
225
+ * Precedence is unchanged — a block still outranks a warn. What changed is the
226
+ * response it produces. The property under test is that the visitor never gets
227
+ * the interstitial's way through, since a restricted owner's link must be
228
+ * unreachable even when it was only flagged to warn.
229
+ */
230
+ it('is outranked by owner restriction, and offers no way through', async () => {
138
231
  mockDb(linkRow({ warn_at: '2026-08-10T00:00:00Z', owner_suspended_at: '2026-08-10T00:00:00Z' }));
139
232
  const res = await app.inject({ method: 'GET', url: '/abc123' });
140
- expect(res.statusCode).toBe(404);
233
+ expect(res.statusCode).toBe(410);
234
+ expect(res.body).toContain('This link is no longer available');
235
+ expect(res.body).not.toContain('Check this link before continuing');
236
+ expect(res.body).not.toContain('Continue anyway');
237
+ });
238
+ /** Same precedence, for a link disabled directly rather than via its owner. */
239
+ it('is outranked by an explicit disable', async () => {
240
+ mockDb(linkRow({ warn_at: '2026-08-10T00:00:00Z', is_active: false, disabled_at: '2026-08-10T00:00:00Z' }));
241
+ const res = await app.inject({ method: 'GET', url: '/abc123' });
242
+ expect(res.statusCode).toBe(410);
243
+ expect(res.body).not.toContain('Continue anyway');
141
244
  });
142
245
  });
143
246
  describe('a database without organizations.suspended_at', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"redirect.safety.test.js","sourceRoot":"","sources":["../../src/routes/redirect.safety.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"redirect.safety.test.js","sourceRoot":"","sources":["../../src/routes/redirect.safety.test.ts"],"names":[],"mappings":"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"}