Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: wt 1781 create availability service #961

Merged
merged 10 commits into from
Oct 11, 2023
68 changes: 68 additions & 0 deletions packages/checkout/sdk/src/availability/availability.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import axios from 'axios';
import { availabilityService } from './availability';
import { CheckoutError, CheckoutErrorType } from '../errors';

jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('availabilityService', () => {
afterEach(() => {
jest.clearAllMocks();
});

describe('checkDexAvailability', () => {
it('should return true when status is 204', async () => {
const mockResponse = {
status: 204,
};
mockedAxios.post.mockResolvedValueOnce(mockResponse);
const response = await availabilityService(false, false).checkDexAvailability();

expect(mockedAxios.post).toHaveBeenCalledTimes(1);
expect(response).toEqual(true);
});

it('should return false when status is 403', async () => {
const mockResponse = {
status: 403,
};
mockedAxios.post.mockResolvedValueOnce(mockResponse);
const response = await availabilityService(false, false).checkDexAvailability();

expect(mockedAxios.post).toHaveBeenCalledTimes(1);
expect(response).toEqual(false);
});

it('should throw error when status is neither 204 or 403', async () => {
const mockResponse = {
status: 500,
statusText: 'error message',
};
mockedAxios.post.mockResolvedValueOnce(mockResponse);

await expect(availabilityService(false, false).checkDexAvailability())
.rejects
.toThrow(
new CheckoutError(
'Error fetching from api: 500 error message',
CheckoutErrorType.API_ERROR,
),
);
});

it('should throw error when error fetching availability', async () => {
mockedAxios.post.mockRejectedValue({
message: 'error message',
});

await expect(availabilityService(false, false).checkDexAvailability())
.rejects
.toThrow(
new CheckoutError(
'Error fetching from api: error message',
CheckoutErrorType.API_ERROR,
),
);
});
});
});
45 changes: 45 additions & 0 deletions packages/checkout/sdk/src/availability/availability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Environment } from '@imtbl/config';
import axios from 'axios';
import { ENV_DEVELOPMENT, IMMUTABLE_API_BASE_URL } from '../types';
import { CheckoutError, CheckoutErrorType } from '../errors';

export type AvailabilityService = {
checkDexAvailability: () => Promise<boolean>
};

export const availabilityService = (
isDevelopment: boolean,
isProduction: boolean,
) => {
const postEndpoint = () => {
if (isDevelopment) return IMMUTABLE_API_BASE_URL[ENV_DEVELOPMENT];
if (isProduction) return IMMUTABLE_API_BASE_URL[Environment.PRODUCTION];

return IMMUTABLE_API_BASE_URL[Environment.SANDBOX];
};

const checkDexAvailability = async (): Promise<boolean> => {
let response;

try {
response = await axios.post(`${postEndpoint()}/v1/availability/checkout/swap`);
} catch (error: any) {
throw new CheckoutError(`Error fetching from api: ${error.message}`, CheckoutErrorType.API_ERROR);
}

if (response.status === 403) {
return false;
}
if (response.status === 204) {
return true;
}
throw new CheckoutError(
`Error fetching from api: ${response.status} ${response.statusText}`,
CheckoutErrorType.API_ERROR,
);
};

return {
checkDexAvailability,
};
};
1 change: 1 addition & 0 deletions packages/checkout/sdk/src/availability/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './availability';
22 changes: 19 additions & 3 deletions packages/checkout/sdk/src/config/remoteConfigFetcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import axios from 'axios';
import { Environment } from '@imtbl/config';
import { CHECKOUT_API_BASE_URL, ChainId, ENV_DEVELOPMENT } from '../types';
import { RemoteConfigFetcher } from './remoteConfigFetcher';
import { CheckoutError, CheckoutErrorType } from '../errors';

jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
Expand Down Expand Up @@ -117,7 +118,12 @@ describe('RemoteConfig', () => {
isProduction: env !== ENV_DEVELOPMENT && env === Environment.PRODUCTION,
});

await expect(fetcher.getConfig()).rejects.toThrowError(new Error('Error fetching from api: error message'));
await expect(fetcher.getConfig()).rejects.toThrow(
new CheckoutError(
'Error fetching from api: error message',
CheckoutErrorType.API_ERROR,
),
);
});
});

Expand Down Expand Up @@ -192,7 +198,12 @@ describe('RemoteConfig', () => {

await expect(fetcher.getTokensConfig(ChainId.SEPOLIA))
.rejects
.toThrow(new Error('Error fetching from api: 500 error message'));
.toThrow(
new CheckoutError(
'Error fetching from api: 500 error message',
CheckoutErrorType.API_ERROR,
),
);
});

it(`should throw error when error fetching [${env}]`, async () => {
Expand All @@ -207,7 +218,12 @@ describe('RemoteConfig', () => {

await expect(fetcher.getTokensConfig(ChainId.SEPOLIA))
.rejects
.toThrow(new Error('Error fetching from api: error message'));
.toThrow(
new CheckoutError(
'Error fetching from api: error message',
CheckoutErrorType.API_ERROR,
),
);
});
});
});
Expand Down
6 changes: 4 additions & 2 deletions packages/checkout/sdk/src/config/remoteConfigFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
RemoteConfiguration,
ChainTokensConfig,
} from '../types';
import { CheckoutError, CheckoutErrorType } from '../errors';

export type RemoteConfigParams = {
isDevelopment: boolean;
Expand All @@ -34,12 +35,13 @@ export class RemoteConfigFetcher {
try {
response = await axios.get(url);
} catch (error: any) {
throw new Error(`Error fetching from api: ${error.message}`);
throw new CheckoutError(`Error fetching from api: ${error.message}`, CheckoutErrorType.API_ERROR);
}

if (response.status !== 200) {
throw new Error(
throw new CheckoutError(
`Error fetching from api: ${response.status} ${response.statusText}`,
CheckoutErrorType.API_ERROR,
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/checkout/sdk/src/errors/checkoutError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export enum CheckoutErrorType {
BRIDGE_GAS_ESTIMATE_ERROR = 'BRIDGE_GAS_ESTIMATE_ERROR',
ORDER_FEE_ERROR = 'ORDER_FEE_ERROR',
ITEM_REQUIREMENTS_ERROR = 'ITEM_REQUIREMENTS_ERROR',
API_ERROR = 'API_ERROR',
}

/**
Expand Down
12 changes: 12 additions & 0 deletions packages/checkout/sdk/src/types/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ const ZKEVM_NATIVE_TOKEN = {
address: IMX_ADDRESS_ZKEVM,
};

/**
* Base URL for the Immutable API based on the environment.
* @property {string} DEVELOPMENT - The base URL for the development environment.
* @property {string} SANDBOX - The base URL for the sandbox environment.
* @property {string} PRODUCTION - The base URL for the production environment.
*/
export const IMMUTABLE_API_BASE_URL = {
[ENV_DEVELOPMENT]: 'https://api.dev.immutable.com',
[Environment.SANDBOX]: 'https://api.sandbox.immutable.com',
[Environment.PRODUCTION]: 'https://api.immutable.com',
};

/**
* Base URL for the checkout API based on the environment.
* @property {string} DEVELOPMENT - The base URL for the development environment.
Expand Down