File size: 2,340 Bytes
e830ad4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import type { RequestEvent } from '@sveltejs/kit';
import { fail, isActionFailure, isHttpError, isRedirect, redirect } from '@sveltejs/kit';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { guarded } from '../../../src/lib/server/actions';
import { ReadOnlyError } from '../../../src/lib/server/ledger/errors';

/** The guard never touches the event, so a bare object is enough. */
const event = { request: new Request('http://localhost/'), locals: {} } as unknown as RequestEvent;

describe('guarded', () => {
	beforeEach(() => {
		vi.spyOn(console, 'error').mockImplementation(() => {});
	});

	it('turns an unexpected throw into a 503 with a retry message', async () => {
		const actions = guarded({
			go: async (_e: RequestEvent) => {
				throw new Error('boom');
			}
		});
		const e = await actions.go(event).then(
			() => null,
			(err: unknown) => err
		);
		expect(isHttpError(e)).toBe(true);
		expect(isHttpError(e) && e.status).toBe(503);
		expect(isHttpError(e) && e.body.message).toBe(
			'Could not save — the storage backend is unavailable. Please try again.'
		);
		expect(console.error).toHaveBeenCalledWith('[action] storage failure', expect.any(Error));
	});

	it('maps ReadOnlyError to a 503 that says the app is read-only', async () => {
		const actions = guarded({
			go: async (_e: RequestEvent) => {
				throw new ReadOnlyError();
			}
		});
		const e = await actions.go(event).then(
			() => null,
			(err: unknown) => err
		);
		expect(isHttpError(e) && e.status).toBe(503);
		expect(isHttpError(e) && e.body.message).toBe(
			'The app is temporarily read-only; try again in a couple of minutes.'
		);
	});

	it('rethrows redirects unchanged', async () => {
		const actions = guarded({
			go: async (_e: RequestEvent) => {
				redirect(303, '/');
			}
		});
		const e = await actions.go(event).then(
			() => null,
			(err: unknown) => err
		);
		expect(isRedirect(e)).toBe(true);
		expect(isRedirect(e) && e.status).toBe(303);
		expect(isRedirect(e) && e.location).toBe('/');
	});

	it('returns action failures unchanged', async () => {
		const failure = fail(400, { error: 'nope' });
		const actions = guarded({ go: async (_e: RequestEvent) => failure });
		const result = await actions.go(event);
		expect(result).toBe(failure);
		expect(isActionFailure(result)).toBe(true);
	});
});