-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fixup! 🥅(frontend) improve add member form error handling
- Loading branch information
1 parent
7464b90
commit db23941
Showing
4 changed files
with
302 additions
and
38 deletions.
There are no files selected for viewing
157 changes: 157 additions & 0 deletions
157
src/frontend/apps/desk/src/api/__tests__/parseAPIErrorV2.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import { APIError } from '@/api'; | ||
|
||
import { | ||
parseAPIError, | ||
parseAPIErrorCause, | ||
parseServerAPIError, | ||
} from '../parseAPIErrorV2'; | ||
|
||
describe('parseAPIError', () => { | ||
const handleErrorMock = jest.fn(); | ||
const handleServerErrorMock = jest.fn(); | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should handle specific API error and return no unhandled causes', () => { | ||
const error = new APIError('client error', { | ||
cause: ['Mail domain with this name already exists.'], | ||
status: 400, | ||
}); | ||
|
||
const result = parseAPIError({ | ||
error, | ||
errorParams: [ | ||
[ | ||
['Mail domain with this name already exists.'], | ||
'This domain already exists.', | ||
handleErrorMock, | ||
], | ||
], | ||
serverErrorParams: ['Server error', handleServerErrorMock], | ||
}); | ||
|
||
expect(handleErrorMock).toHaveBeenCalled(); | ||
expect(result).toEqual(['This domain already exists.']); | ||
}); | ||
|
||
it('should return unhandled causes when no match is found', () => { | ||
const error = new APIError('client error', { | ||
cause: ['Unhandled error'], | ||
status: 400, | ||
}); | ||
|
||
const result = parseAPIError({ | ||
error, | ||
errorParams: [ | ||
[ | ||
['Mail domain with this name already exists.'], | ||
'This domain already exists.', | ||
handleErrorMock, | ||
], | ||
], | ||
serverErrorParams: ['Server error', handleServerErrorMock], | ||
}); | ||
|
||
expect(handleErrorMock).not.toHaveBeenCalled(); | ||
expect(result).toEqual(['Unhandled error']); | ||
}); | ||
|
||
it('should handle server errors correctly and prepend server error message', () => { | ||
const error = new APIError('server error', { status: 500 }); | ||
|
||
const result = parseAPIError({ | ||
error, | ||
errorParams: undefined, | ||
serverErrorParams: ['Server error occurred', handleServerErrorMock], | ||
}); | ||
|
||
expect(handleServerErrorMock).toHaveBeenCalled(); | ||
expect(result).toEqual(['Server error occurred']); | ||
}); | ||
|
||
it('should handle absence of errors gracefully', () => { | ||
const result = parseAPIError({ | ||
error: null, | ||
errorParams: [ | ||
[ | ||
['Mail domain with this name already exists.'], | ||
'This domain already exists.', | ||
handleErrorMock, | ||
], | ||
], | ||
serverErrorParams: ['Server error', handleServerErrorMock], | ||
}); | ||
|
||
expect(result).toBeUndefined(); | ||
}); | ||
}); | ||
|
||
describe('parseAPIErrorCause', () => { | ||
it('should handle specific errors and call handleError', () => { | ||
const handleErrorMock = jest.fn(); | ||
const causes = ['Mail domain with this name already exists.']; | ||
|
||
const errorParams: [string[], string, () => void][] = [ | ||
[ | ||
['Mail domain with this name already exists.'], | ||
'This domain already exists.', | ||
handleErrorMock, | ||
], | ||
]; | ||
|
||
const result = parseAPIErrorCause( | ||
new APIError('client error', { cause: causes, status: 400 }), | ||
errorParams, | ||
); | ||
|
||
expect(handleErrorMock).toHaveBeenCalled(); | ||
expect(result).toEqual(['This domain already exists.']); | ||
}); | ||
|
||
it('should handle multiple causes and return unhandled causes', () => { | ||
const handleErrorMock = jest.fn(); | ||
const causes = [ | ||
'Mail domain with this name already exists.', | ||
'Unhandled error', | ||
]; | ||
|
||
const errorParams: [string[], string, () => void][] = [ | ||
[ | ||
['Mail domain with this name already exists.'], | ||
'This domain already exists.', | ||
handleErrorMock, | ||
], | ||
]; | ||
|
||
const result = parseAPIErrorCause( | ||
new APIError('client error', { cause: causes, status: 400 }), | ||
errorParams, | ||
); | ||
|
||
expect(handleErrorMock).toHaveBeenCalled(); | ||
expect(result).toEqual(['This domain already exists.', 'Unhandled error']); | ||
}); | ||
}); | ||
|
||
describe('parseServerAPIError', () => { | ||
const handleServerErrorMock = jest.fn(); | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should return the server error message and handle callback', () => { | ||
const result = parseServerAPIError(['Server error', handleServerErrorMock]); | ||
|
||
expect(result).toEqual('Server error'); | ||
expect(handleServerErrorMock).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should return only the server error message when no callback is provided', () => { | ||
const result = parseServerAPIError(['Server error', undefined]); | ||
|
||
expect(result).toEqual('Server error'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { APIError } from '@/api/index'; | ||
|
||
type ErrorCallback = () => void; | ||
|
||
// Type for the error tuple format [causes, message, handleError] | ||
type ErrorTuple = [string[], string, ErrorCallback | undefined]; | ||
|
||
// Server error tuple [defaultMessage, handleError] | ||
type ServerErrorTuple = [string, ErrorCallback | undefined]; | ||
|
||
/** | ||
* @function parseAPIError | ||
* @description function to centralize APIError handling to treat discovered errors | ||
* and error type 500 with default behavior using a simplified tuple structure. | ||
* @param error - APIError object | ||
* @param errorParams - Array of tuples: each contains an array of causes, a message, and an optional callback function. | ||
* @param serverErrorParams - A tuple for server error handling: [defaultMessage, handleError] | ||
* @returns Array of error messages or undefined | ||
*/ | ||
export const parseAPIError = ({ | ||
error, | ||
errorParams, | ||
serverErrorParams, | ||
}: { | ||
error: APIError | null; | ||
errorParams?: ErrorTuple[]; | ||
serverErrorParams?: ServerErrorTuple; | ||
}): string[] | undefined => { | ||
if (!error) { | ||
return; | ||
} | ||
|
||
// Parse known error causes using the tuple structure | ||
const errorCauses = | ||
error.cause?.length && errorParams | ||
? parseAPIErrorCause(error, errorParams) | ||
: undefined; | ||
|
||
// Check if it's a server error (500) and handle that case | ||
const serverErrorCause = | ||
(error?.status === 500 || !error?.status) && serverErrorParams | ||
? parseServerAPIError(serverErrorParams) | ||
: undefined; | ||
|
||
// Combine the causes and return | ||
const causes: string[] = errorCauses ? [...errorCauses] : []; | ||
if (serverErrorCause) { | ||
causes.unshift(serverErrorCause); | ||
} | ||
|
||
return causes.length ? causes : undefined; | ||
}; | ||
|
||
/** | ||
* @function parseAPIErrorCause | ||
* @description Processes known API error causes using the tuple structure. | ||
* @param error - APIError object | ||
* @param errorParams - Array of tuples: each contains an array of causes, a message, and an optional callback function. | ||
* @returns Array of error messages | ||
*/ | ||
export const parseAPIErrorCause = ( | ||
error: APIError, | ||
errorParams: ErrorTuple[], | ||
): string[] | undefined => { | ||
if (!error.cause) { | ||
return; | ||
} | ||
|
||
return error.cause.reduce((causes: string[], cause: string) => { | ||
// Find the matching error tuple | ||
const matchedError = errorParams.find(([errorCauses]) => | ||
errorCauses.some((knownCause) => new RegExp(knownCause, 'i').test(cause)), | ||
); | ||
|
||
if (matchedError) { | ||
const [, message, handleError] = matchedError; | ||
causes.push(message); | ||
|
||
if (handleError) { | ||
handleError(); | ||
} | ||
} else { | ||
// If no match is found, add the original cause | ||
causes.push(cause); | ||
} | ||
|
||
return causes; | ||
}, []); | ||
}; | ||
|
||
/** | ||
* @function parseServerAPIError | ||
* @description Handles server errors (500) and adds the default message. | ||
* @param serverErrorParams - Tuple [defaultMessage, handleError] | ||
* @returns Server error message | ||
*/ | ||
export const parseServerAPIError = ([ | ||
defaultMessage, | ||
handleError, | ||
]: ServerErrorTuple): string => { | ||
if (handleError) { | ||
handleError(); | ||
} | ||
|
||
return defaultMessage; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters