Files
alarm-client/__tests__/joinError.test.ts
T
2026-10-18 22:42:00 +01:00

82 lines
2.5 KiB
TypeScript

/**
* Error plumbing: the service answers `{"error":{"code":…,"message":…}}`, and
* the join path has to tell the user something true without naming a mechanism
* it cannot observe. These tests pin both halves.
*/
import { ApiError, configureApi, post } from '../src/services/apiClient';
import { joinErrorMessage } from '../src/services/alarmApi';
const g = globalThis as unknown as { fetch: typeof fetch };
const realFetch = g.fetch;
function stubFetch(body: string, status = 400): void {
g.fetch = (async () => ({
ok: false,
status,
text: async () => body,
})) as unknown as typeof fetch;
}
beforeEach(() => {
configureApi({
baseUrl: 'http://alarm.test',
mode: 'direct',
serviceRef: null,
token: null,
refreshToken: null,
onUnauthorized: () => {},
onSessionUpdate: () => {},
});
});
afterEach(() => {
g.fetch = realFetch;
});
describe('apiClient error unwrapping', () => {
it('extracts the service error code and the clean message', async () => {
stubFetch(
JSON.stringify({
error: { code: 'ERR_ALERT_TOKEN_INVALID', message: 'invalid subscribe token' },
}),
);
await expect(post('joinAlert', {})).rejects.toMatchObject({
name: 'ApiError',
status: 400,
code: 'ERR_ALERT_TOKEN_INVALID',
message: 'invalid subscribe token',
});
});
it('keeps a non-JSON body as the message and reports no code', async () => {
stubFetch('Bad Gateway', 502);
await expect(post('joinAlert', {})).rejects.toMatchObject({
status: 502,
code: null,
message: 'Bad Gateway',
});
});
});
describe('joinErrorMessage', () => {
it('renders a token problem as a remedy, never as "expired"', () => {
// JoinAlert returns this one code for a rotated token AND for a malformed
// one, so the client cannot say which happened.
const err = new ApiError(400, 'invalid subscribe token', 'ERR_ALERT_TOKEN_INVALID');
const copy = joinErrorMessage(err);
expect(copy).toBe('This code is no longer valid — ask the owner for a new one.');
expect(copy).not.toMatch(/expired/i);
});
it('passes through any other server message', () => {
expect(joinErrorMessage(new ApiError(404, 'alert not found', 'ERR_NOT_FOUND'))).toBe(
'alert not found',
);
});
it('falls back for errors that are not ApiError instances', () => {
expect(joinErrorMessage(new Error('boom'))).toBe('boom');
expect(joinErrorMessage(undefined)).toBe('Could not subscribe this device.');
});
});