init
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user