feat(billing): refacto billing (#14243)
… prices for metered billing --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { findOrThrow } from '@/utils';
|
||||
|
||||
describe('findOrThrow', () => {
|
||||
it('returns the element when predicate matches', () => {
|
||||
const arr = [1, 2, 3, 4];
|
||||
const result = findOrThrow(arr, x => x === 3);
|
||||
expect(result).toBe(3);
|
||||
});
|
||||
|
||||
it('throws default error when no element matches', () => {
|
||||
const arr = [1, 2, 3];
|
||||
expect(() => findOrThrow(arr, x => x === 5)).toThrow(new Error('Element not found'));
|
||||
});
|
||||
|
||||
it('throws custom error when provided', () => {
|
||||
const arr = ['a', 'b', 'c'];
|
||||
const customError = new Error('Custom error!');
|
||||
expect(() => findOrThrow(arr, x => x === 'z', customError)).toThrow(customError);
|
||||
});
|
||||
|
||||
it('returns the first matching element when multiple match', () => {
|
||||
const arr = [2, 4, 6, 8];
|
||||
const result = findOrThrow(arr, x => x % 2 === 0);
|
||||
expect(result).toBe(2);
|
||||
});
|
||||
|
||||
it('works with object arrays', () => {
|
||||
const arr = [{id: 1}, {id: 2}, {id: 3}];
|
||||
const result = findOrThrow(arr, obj => obj.id === 2);
|
||||
expect(result).toEqual({id: 2});
|
||||
});
|
||||
|
||||
it('works with primitive arrays', () => {
|
||||
const arr = ['apple', 'banana', 'cherry'];
|
||||
const result = findOrThrow(arr, fruit => fruit.startsWith('b'));
|
||||
expect(result).toBe('banana');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { assertIsDefinedOrThrow } from '@/utils';
|
||||
|
||||
export const findOrThrow = <T>(
|
||||
array: T[],
|
||||
predicate: (value: T) => boolean,
|
||||
error: Error = new Error('Element not found'),
|
||||
): T => {
|
||||
const result = array.find(predicate);
|
||||
|
||||
assertIsDefinedOrThrow(result, error)
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -10,6 +10,7 @@
|
||||
export { filterOutByProperty } from './array/filterOutByProperty';
|
||||
export { findById } from './array/findById';
|
||||
export { findByProperty } from './array/findByProperty';
|
||||
export { findOrThrow } from './array/findOrThrow';
|
||||
export { sumByProperty } from './array/sumByProperty';
|
||||
export { assertUnreachable } from './assertUnreachable';
|
||||
export { deepMerge } from './deepMerge';
|
||||
@@ -46,6 +47,7 @@ export { isValidHostname } from './url/isValidHostname';
|
||||
export { isValidUrl } from './url/isValidUrl';
|
||||
export { lowercaseUrlOriginAndRemoveTrailingSlash } from './url/lowercaseUrlOriginAndRemoveTrailingSlash';
|
||||
export { uuidToBase36 } from './uuidToBase36';
|
||||
export { assertIsDefinedOrThrow } from './validation/assertIsDefinedOrThrow';
|
||||
export { isDefined } from './validation/isDefined';
|
||||
export { isLabelIdentifierFieldMetadataTypes } from './validation/isLabelIdentifierFieldMetadataTypes';
|
||||
export { isValidLocale } from './validation/isValidLocale';
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { assertIsDefinedOrThrow } from '@/utils';
|
||||
|
||||
describe('assertIsDefinedOrThrow', () => {
|
||||
it('does not throw when value is defined (number)', () => {
|
||||
expect(() => assertIsDefinedOrThrow(42)).not.toThrow();
|
||||
});
|
||||
|
||||
it('does not throw when value is defined (false)', () => {
|
||||
const v: boolean | undefined | null = false;
|
||||
expect(() => assertIsDefinedOrThrow(v)).not.toThrow();
|
||||
});
|
||||
|
||||
it('does not throw when value is defined (empty string)', () => {
|
||||
const v: string | undefined | null = '';
|
||||
expect(() => assertIsDefinedOrThrow(v)).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws the default error when value is undefined', () => {
|
||||
expect(() => assertIsDefinedOrThrow(undefined)).toThrow('Value not defined');
|
||||
});
|
||||
|
||||
it('throws the default error when value is null', () => {
|
||||
expect(() => assertIsDefinedOrThrow(null)).toThrow('Value not defined');
|
||||
});
|
||||
|
||||
it('throws the custom error instance when provided', () => {
|
||||
const err = new TypeError('Custom');
|
||||
expect(() => assertIsDefinedOrThrow(undefined, err)).toThrow(err);
|
||||
});
|
||||
|
||||
it('narrows the type after assertion (compile-time check)', () => {
|
||||
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
||||
function expectString(s: string) {
|
||||
expect(typeof s).toBe('string');
|
||||
}
|
||||
let maybe: string | undefined | null = 'ok';
|
||||
assertIsDefinedOrThrow(maybe);
|
||||
// After the assertion, TypeScript should treat `maybe` as `string`.
|
||||
expectString(maybe);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { isDefined } from '@/utils';
|
||||
|
||||
// https://github.com/microsoft/TypeScript/issues/34523
|
||||
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
||||
export function assertIsDefinedOrThrow<T>(
|
||||
value: T | undefined | null,
|
||||
exceptionToThrow: Error = new Error(
|
||||
'Value not defined',
|
||||
),
|
||||
): asserts value is T {
|
||||
if (!isDefined(value)) throw exceptionToThrow;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './isDefined';
|
||||
export * from './assertIsDefinedOrThrow';
|
||||
export * from './isValidLocale';
|
||||
export * from './isValidUuid';
|
||||
export * from './normalizeLocale';
|
||||
|
||||
Reference in New Issue
Block a user