Spaces:
Sleeping
Sleeping
assafvayner HF Staff
fix(app): 503 on storage failures, validated currency, lock release on shutdown, per-IP login limit, unit-only npm test
e830ad4 Download tests/unit/server/actions.test.ts from assafvayner/splitwise: direct link, hf CLI and curl.
- Browser
- Download file 2.34 kB
-
https://huggingface.co/spaces/assafvayner/splitwise/resolve/main/tests/unit/server/actions.test.ts
- Command line
-
hf download hf://spaces/assafvayner/splitwise/tests/unit/server/actions.test.ts
-
curl -L -o actions.test.ts https://huggingface.co/spaces/assafvayner/splitwise/resolve/main/tests/unit/server/actions.test.ts
2.34 kB
| 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); | |
| }); | |
| }); | |