diff --git a/src/api/services/ShlinkApiClient.ts b/src/api/services/ShlinkApiClient.ts index 2f1c3de4..66d36332 100644 --- a/src/api/services/ShlinkApiClient.ts +++ b/src/api/services/ShlinkApiClient.ts @@ -20,7 +20,7 @@ import { import { orderToString } from '../../utils/helpers/ordering'; import { isRegularNotFound, parseApiError } from '../utils'; import { stringifyQuery } from '../../utils/helpers/query'; -import { JsonFetch } from '../../utils/types'; +import { HttpClient } from '../../common/services/HttpClient'; const buildShlinkBaseUrl = (url: string, version: 2 | 3) => `${url}/rest/v${version}`; const rejectNilProps = reject(isNil); @@ -34,7 +34,7 @@ export class ShlinkApiClient { private apiVersion: 2 | 3; public constructor( - private readonly fetch: JsonFetch, + private readonly httpClient: HttpClient, private readonly baseUrl: string, private readonly apiKey: string, ) { @@ -119,11 +119,14 @@ export class ShlinkApiClient { const normalizedQuery = stringifyQuery(rejectNilProps(query)); const stringifiedQuery = isEmpty(normalizedQuery) ? '' : `?${normalizedQuery}`; - return this.fetch(`${buildShlinkBaseUrl(this.baseUrl, this.apiVersion)}${url}${stringifiedQuery}`, { - method, - body: body && JSON.stringify(body), - headers: { 'X-Api-Key': this.apiKey }, - }).catch((e: unknown) => { + return this.httpClient.fetchJson( + `${buildShlinkBaseUrl(this.baseUrl, this.apiVersion)}${url}${stringifiedQuery}`, + { + method, + body: body && JSON.stringify(body), + headers: { 'X-Api-Key': this.apiKey }, + }, + ).catch((e: unknown) => { if (!isRegularNotFound(parseApiError(e))) { throw e; } diff --git a/src/api/services/ShlinkApiClientBuilder.ts b/src/api/services/ShlinkApiClientBuilder.ts index 25196ac6..ef74356a 100644 --- a/src/api/services/ShlinkApiClientBuilder.ts +++ b/src/api/services/ShlinkApiClientBuilder.ts @@ -1,7 +1,7 @@ import { hasServerData, ServerWithId } from '../../servers/data'; import { GetState } from '../../container/types'; import { ShlinkApiClient } from './ShlinkApiClient'; -import { JsonFetch } from '../../utils/types'; +import { HttpClient } from '../../common/services/HttpClient'; const apiClients: Record = {}; @@ -16,14 +16,14 @@ const getSelectedServerFromState = (getState: GetState): ServerWithId => { return selectedServer; }; -export const buildShlinkApiClient = (fetch: JsonFetch) => (getStateOrSelectedServer: GetState | ServerWithId) => { +export const buildShlinkApiClient = (httpClient: HttpClient) => (getStateOrSelectedServer: GetState | ServerWithId) => { const { url, apiKey } = isGetState(getStateOrSelectedServer) ? getSelectedServerFromState(getStateOrSelectedServer) : getStateOrSelectedServer; const clientKey = `${url}_${apiKey}`; if (!apiClients[clientKey]) { - apiClients[clientKey] = new ShlinkApiClient(fetch, url, apiKey); + apiClients[clientKey] = new ShlinkApiClient(httpClient, url, apiKey); } return apiClients[clientKey]; diff --git a/src/api/services/provideServices.ts b/src/api/services/provideServices.ts index 07697256..c8bcd854 100644 --- a/src/api/services/provideServices.ts +++ b/src/api/services/provideServices.ts @@ -2,7 +2,7 @@ import Bottle from 'bottlejs'; import { buildShlinkApiClient } from './ShlinkApiClientBuilder'; const provideServices = (bottle: Bottle) => { - bottle.serviceFactory('buildShlinkApiClient', buildShlinkApiClient, 'jsonFetch'); + bottle.serviceFactory('buildShlinkApiClient', buildShlinkApiClient, 'HttpClient'); }; export default provideServices; diff --git a/src/common/services/HttpClient.ts b/src/common/services/HttpClient.ts new file mode 100644 index 00000000..bd9b47f9 --- /dev/null +++ b/src/common/services/HttpClient.ts @@ -0,0 +1,21 @@ +import { Fetch } from '../../utils/types'; + +export class HttpClient { + constructor(private readonly fetch: Fetch) {} + + public fetchJson(url: string, options?: RequestInit): Promise { + return this.fetch(url, options).then(async (resp) => { + const parsed = await resp.json(); + + if (!resp.ok) { + throw parsed; + } + + return parsed as T; + }); + } + + public fetchBlob(url: string): Promise { + return this.fetch(url).then((resp) => resp.blob()); + } +} diff --git a/src/common/services/ImageDownloader.ts b/src/common/services/ImageDownloader.ts index 8371268e..39314d7e 100644 --- a/src/common/services/ImageDownloader.ts +++ b/src/common/services/ImageDownloader.ts @@ -1,11 +1,11 @@ -import { Fetch } from '../../utils/types'; import { saveUrl } from '../../utils/helpers/files'; +import { HttpClient } from './HttpClient'; export class ImageDownloader { - public constructor(private readonly fetch: Fetch, private readonly window: Window) {} + public constructor(private readonly httpClient: HttpClient, private readonly window: Window) {} public async saveImage(imgUrl: string, filename: string): Promise { - const data = await this.fetch(imgUrl).then((resp) => resp.blob()); + const data = await this.httpClient.fetchBlob(imgUrl); const url = URL.createObjectURL(data); saveUrl(this.window, url, filename); diff --git a/src/common/services/provideServices.ts b/src/common/services/provideServices.ts index fdebe589..c6b2d3ef 100644 --- a/src/common/services/provideServices.ts +++ b/src/common/services/provideServices.ts @@ -11,16 +11,16 @@ import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServ import { sidebarNotPresent, sidebarPresent } from '../reducers/sidebar'; import { ImageDownloader } from './ImageDownloader'; import { ReportExporter } from './ReportExporter'; -import { jsonFetch } from '../../utils/helpers/fetch'; +import { HttpClient } from './HttpClient'; const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Services bottle.constant('window', (global as any).window); bottle.constant('console', global.console); bottle.constant('fetch', (global as any).fetch.bind(global)); - bottle.serviceFactory('jsonFetch', jsonFetch, 'fetch'); - bottle.service('ImageDownloader', ImageDownloader, 'fetch', 'window'); + bottle.service('HttpClient', HttpClient, 'fetch'); + bottle.service('ImageDownloader', ImageDownloader, 'HttpClient', 'window'); bottle.service('ReportExporter', ReportExporter, 'window', 'jsonToCsv'); // Components diff --git a/src/servers/reducers/remoteServers.ts b/src/servers/reducers/remoteServers.ts index 98868d6f..8776441d 100644 --- a/src/servers/reducers/remoteServers.ts +++ b/src/servers/reducers/remoteServers.ts @@ -2,14 +2,14 @@ import pack from '../../../package.json'; import { hasServerData, ServerData } from '../data'; import { createServers } from './servers'; import { createAsyncThunk } from '../../utils/helpers/redux'; -import { JsonFetch } from '../../utils/types'; +import { HttpClient } from '../../common/services/HttpClient'; const responseToServersList = (data: any): ServerData[] => (Array.isArray(data) ? data.filter(hasServerData) : []); -export const fetchServers = (fetch: JsonFetch) => createAsyncThunk( +export const fetchServers = (httpClient: HttpClient) => createAsyncThunk( 'shlink/remoteServers/fetchServers', async (_: void, { dispatch }): Promise => { - const resp = await fetch(`${pack.homepage}/servers.json`); + const resp = await httpClient.fetchJson(`${pack.homepage}/servers.json`); const result = responseToServersList(resp); dispatch(createServers(result)); diff --git a/src/servers/services/provideServices.ts b/src/servers/services/provideServices.ts index 73235dc5..92aaca58 100644 --- a/src/servers/services/provideServices.ts +++ b/src/servers/services/provideServices.ts @@ -80,7 +80,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('deleteServer', () => deleteServer); bottle.serviceFactory('editServer', () => editServer); bottle.serviceFactory('setAutoConnect', () => setAutoConnect); - bottle.serviceFactory('fetchServers', fetchServers, 'jsonFetch'); + bottle.serviceFactory('fetchServers', fetchServers, 'HttpClient'); bottle.serviceFactory('resetSelectedServer', () => resetSelectedServer); diff --git a/src/utils/helpers/fetch.ts b/src/utils/helpers/fetch.ts deleted file mode 100644 index 98f5fdb3..00000000 --- a/src/utils/helpers/fetch.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const jsonFetch = (fetch: typeof window.fetch) => (url: string, options?: RequestInit) => fetch(url, options) - .then(async (resp) => { - const parsed = await resp.json(); - - if (!resp.ok) { - throw parsed; // eslint-disable-line @typescript-eslint/no-throw-literal - } - - return parsed as T; - }); diff --git a/test/api/services/ShlinkApiClient.test.ts b/test/api/services/ShlinkApiClient.test.ts index 5d1db797..17c6ac49 100644 --- a/test/api/services/ShlinkApiClient.test.ts +++ b/test/api/services/ShlinkApiClient.test.ts @@ -3,26 +3,27 @@ import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient'; import { OptionalString } from '../../../src/utils/utils'; import { ShlinkDomain, ShlinkVisits, ShlinkVisitsOverview } from '../../../src/api/types'; import { ShortUrl, ShortUrlsOrder } from '../../../src/short-urls/data'; -import { JsonFetch } from '../../../src/utils/types'; import { ErrorTypeV2, ErrorTypeV3 } from '../../../src/api/types/errors'; +import { HttpClient } from '../../../src/common/services/HttpClient'; describe('ShlinkApiClient', () => { - const buildFetch = (data: any) => jest.fn().mockResolvedValue(data); - const buildRejectedFetch = (error: any) => jest.fn().mockRejectedValueOnce(error); - const buildApiClient = (fetch: JsonFetch) => new ShlinkApiClient(fetch, '', ''); + const fetchJson = jest.fn().mockResolvedValue({}); + const httpClient = Mock.of({ fetchJson }); + const buildApiClient = () => new ShlinkApiClient(httpClient, '', ''); const shortCodesWithDomainCombinations: [string, OptionalString][] = [ ['abc123', null], ['abc123', undefined], ['abc123', 'example.com'], ]; + beforeEach(jest.clearAllMocks); + describe('listShortUrls', () => { const expectedList = ['foo', 'bar']; it('properly returns short URLs list', async () => { - const { listShortUrls } = buildApiClient(buildFetch({ - shortUrls: expectedList, - })); + fetchJson.mockResolvedValue({ shortUrls: expectedList }); + const { listShortUrls } = buildApiClient(); const actualList = await listShortUrls(); @@ -34,12 +35,15 @@ describe('ShlinkApiClient', () => { [{ field: 'longUrl', dir: 'ASC' } as ShortUrlsOrder, '?orderBy=longUrl-ASC'], [{ field: 'longUrl', dir: undefined } as ShortUrlsOrder, ''], ])('parses orderBy in params', async (orderBy, expectedOrderBy) => { - const fetch = buildFetch({ data: expectedList }); - const { listShortUrls } = buildApiClient(fetch); + fetchJson.mockResolvedValue({ data: expectedList }); + const { listShortUrls } = buildApiClient(); await listShortUrls({ orderBy }); - expect(fetch).toHaveBeenCalledWith(expect.stringContaining(`/short-urls${expectedOrderBy}`), expect.anything()); + expect(fetchJson).toHaveBeenCalledWith( + expect.stringContaining(`/short-urls${expectedOrderBy}`), + expect.anything(), + ); }); }); @@ -49,19 +53,20 @@ describe('ShlinkApiClient', () => { }; it('returns create short URL', async () => { - const { createShortUrl } = buildApiClient(buildFetch(shortUrl)); + fetchJson.mockResolvedValue(shortUrl); + const { createShortUrl } = buildApiClient(); const result = await createShortUrl({ longUrl: '' }); expect(result).toEqual(shortUrl); }); it('removes all empty options', async () => { - const fetch = buildFetch({ data: shortUrl }); - const { createShortUrl } = buildApiClient(fetch); + fetchJson.mockResolvedValue({ data: shortUrl }); + const { createShortUrl } = buildApiClient(); await createShortUrl({ longUrl: 'bar', customSlug: undefined, maxVisits: null }); - expect(fetch).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ + expect(fetchJson).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ body: JSON.stringify({ longUrl: 'bar' }), })); }); @@ -70,36 +75,37 @@ describe('ShlinkApiClient', () => { describe('getShortUrlVisits', () => { it('properly returns short URL visits', async () => { const expectedVisits = ['foo', 'bar']; - const fetch = buildFetch({ + fetchJson.mockResolvedValue({ visits: { data: expectedVisits, }, }); - const { getShortUrlVisits } = buildApiClient(fetch); + const { getShortUrlVisits } = buildApiClient(); const actualVisits = await getShortUrlVisits('abc123', {}); expect({ data: expectedVisits }).toEqual(actualVisits); - expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/short-urls/abc123/visits'), expect.objectContaining({ - method: 'GET', - })); + expect(fetchJson).toHaveBeenCalledWith( + expect.stringContaining('/short-urls/abc123/visits'), + expect.objectContaining({ method: 'GET' }), + ); }); }); describe('getTagVisits', () => { it('properly returns tag visits', async () => { const expectedVisits = ['foo', 'bar']; - const fetch = buildFetch({ + fetchJson.mockResolvedValue({ visits: { data: expectedVisits, }, }); - const { getTagVisits } = buildApiClient(fetch); + const { getTagVisits } = buildApiClient(); const actualVisits = await getTagVisits('foo', {}); expect({ data: expectedVisits }).toEqual(actualVisits); - expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/tags/foo/visits'), expect.objectContaining({ + expect(fetchJson).toHaveBeenCalledWith(expect.stringContaining('/tags/foo/visits'), expect.objectContaining({ method: 'GET', })); }); @@ -108,33 +114,34 @@ describe('ShlinkApiClient', () => { describe('getDomainVisits', () => { it('properly returns domain visits', async () => { const expectedVisits = ['foo', 'bar']; - const fetch = buildFetch({ + fetchJson.mockResolvedValue({ visits: { data: expectedVisits, }, }); - const { getDomainVisits } = buildApiClient(fetch); + const { getDomainVisits } = buildApiClient(); const actualVisits = await getDomainVisits('foo.com', {}); expect({ data: expectedVisits }).toEqual(actualVisits); - expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/domains/foo.com/visits'), expect.objectContaining({ - method: 'GET', - })); + expect(fetchJson).toHaveBeenCalledWith( + expect.stringContaining('/domains/foo.com/visits'), + expect.objectContaining({ method: 'GET' }), + ); }); }); describe('getShortUrl', () => { it.each(shortCodesWithDomainCombinations)('properly returns short URL', async (shortCode, domain) => { const expectedShortUrl = { foo: 'bar' }; - const fetch = buildFetch(expectedShortUrl); - const { getShortUrl } = buildApiClient(fetch); + fetchJson.mockResolvedValue(expectedShortUrl); + const { getShortUrl } = buildApiClient(); const expectedQuery = domain ? `?domain=${domain}` : ''; const result = await getShortUrl(shortCode, domain); expect(expectedShortUrl).toEqual(result); - expect(fetch).toHaveBeenCalledWith( + expect(fetchJson).toHaveBeenCalledWith( expect.stringContaining(`/short-urls/${shortCode}${expectedQuery}`), expect.objectContaining({ method: 'GET' }), ); @@ -148,14 +155,14 @@ describe('ShlinkApiClient', () => { validSince: '2025-01-01T10:00:00+01:00', }; const expectedResp = Mock.of(); - const fetch = buildFetch(expectedResp); - const { updateShortUrl } = buildApiClient(fetch); + fetchJson.mockResolvedValue(expectedResp); + const { updateShortUrl } = buildApiClient(); const expectedQuery = domain ? `?domain=${domain}` : ''; const result = await updateShortUrl(shortCode, domain, meta); expect(expectedResp).toEqual(result); - expect(fetch).toHaveBeenCalledWith( + expect(fetchJson).toHaveBeenCalledWith( expect.stringContaining(`/short-urls/${shortCode}${expectedQuery}`), expect.objectContaining({ method: 'PATCH' }), ); @@ -165,29 +172,31 @@ describe('ShlinkApiClient', () => { describe('listTags', () => { it('properly returns list of tags', async () => { const expectedTags = ['foo', 'bar']; - const fetch = buildFetch({ + fetchJson.mockResolvedValue({ tags: { data: expectedTags, }, }); - const { listTags } = buildApiClient(fetch); + const { listTags } = buildApiClient(); const result = await listTags(); expect({ tags: expectedTags }).toEqual(result); - expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/tags'), expect.objectContaining({ method: 'GET' })); + expect(fetchJson).toHaveBeenCalledWith( + expect.stringContaining('/tags'), + expect.objectContaining({ method: 'GET' }), + ); }); }); describe('deleteTags', () => { it('properly deletes provided tags', async () => { const tags = ['foo', 'bar']; - const fetch = buildFetch({}); - const { deleteTags } = buildApiClient(fetch); + const { deleteTags } = buildApiClient(); await deleteTags(tags); - expect(fetch).toHaveBeenCalledWith( + expect(fetchJson).toHaveBeenCalledWith( expect.stringContaining(`/tags?${tags.map((tag) => `tags%5B%5D=${tag}`).join('&')}`), expect.objectContaining({ method: 'DELETE' }), ); @@ -198,12 +207,11 @@ describe('ShlinkApiClient', () => { it('properly edits provided tag', async () => { const oldName = 'foo'; const newName = 'bar'; - const fetch = buildFetch({}); - const { editTag } = buildApiClient(fetch); + const { editTag } = buildApiClient(); await editTag(oldName, newName); - expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/tags'), expect.objectContaining({ + expect(fetchJson).toHaveBeenCalledWith(expect.stringContaining('/tags'), expect.objectContaining({ method: 'PUT', body: JSON.stringify({ oldName, newName }), })); @@ -212,13 +220,12 @@ describe('ShlinkApiClient', () => { describe('deleteShortUrl', () => { it.each(shortCodesWithDomainCombinations)('properly deletes provided short URL', async (shortCode, domain) => { - const fetch = buildFetch({}); - const { deleteShortUrl } = buildApiClient(fetch); + const { deleteShortUrl } = buildApiClient(); const expectedQuery = domain ? `?domain=${domain}` : ''; await deleteShortUrl(shortCode, domain); - expect(fetch).toHaveBeenCalledWith( + expect(fetchJson).toHaveBeenCalledWith( expect.stringContaining(`/short-urls/${shortCode}${expectedQuery}`), expect.objectContaining({ method: 'DELETE' }), ); @@ -231,12 +238,12 @@ describe('ShlinkApiClient', () => { status: 'pass', version: '1.19.0', }; - const fetch = buildFetch(expectedData); - const { health } = buildApiClient(fetch); + fetchJson.mockResolvedValue(expectedData); + const { health } = buildApiClient(); const result = await health(); - expect(fetch).toHaveBeenCalled(); + expect(fetchJson).toHaveBeenCalled(); expect(result).toEqual(expectedData); }); }); @@ -247,12 +254,12 @@ describe('ShlinkApiClient', () => { token: 'abc.123.def', mercureHubUrl: 'http://example.com/.well-known/mercure', }; - const fetch = buildFetch(expectedData); - const { mercureInfo } = buildApiClient(fetch); + fetchJson.mockResolvedValue(expectedData); + const { mercureInfo } = buildApiClient(); const result = await mercureInfo(); - expect(fetch).toHaveBeenCalled(); + expect(fetchJson).toHaveBeenCalled(); expect(result).toEqual(expectedData); }); }); @@ -260,12 +267,12 @@ describe('ShlinkApiClient', () => { describe('listDomains', () => { it('returns domains', async () => { const expectedData = { data: [Mock.all(), Mock.all()] }; - const fetch = buildFetch({ domains: expectedData }); - const { listDomains } = buildApiClient(fetch); + fetchJson.mockResolvedValue({ domains: expectedData }); + const { listDomains } = buildApiClient(); const result = await listDomains(); - expect(fetch).toHaveBeenCalled(); + expect(fetchJson).toHaveBeenCalled(); expect(result).toEqual(expectedData); }); }); @@ -273,36 +280,36 @@ describe('ShlinkApiClient', () => { describe('getVisitsOverview', () => { it('returns visits overview', async () => { const expectedData = Mock.all(); - const fetch = buildFetch({ visits: expectedData }); - const { getVisitsOverview } = buildApiClient(fetch); + fetchJson.mockResolvedValue({ visits: expectedData }); + const { getVisitsOverview } = buildApiClient(); const result = await getVisitsOverview(); - expect(fetch).toHaveBeenCalled(); + expect(fetchJson).toHaveBeenCalled(); expect(result).toEqual(expectedData); }); }); describe('getOrphanVisits', () => { it('returns orphan visits', async () => { - const fetch = buildFetch({ visits: Mock.of({ data: [] }) }); - const { getOrphanVisits } = buildApiClient(fetch); + fetchJson.mockResolvedValue({ visits: Mock.of({ data: [] }) }); + const { getOrphanVisits } = buildApiClient(); const result = await getOrphanVisits(); - expect(fetch).toHaveBeenCalled(); + expect(fetchJson).toHaveBeenCalled(); expect(result).toEqual({ data: [] }); }); }); describe('getNonOrphanVisits', () => { it('returns non-orphan visits', async () => { - const fetch = buildFetch({ visits: Mock.of({ data: [] }) }); - const { getNonOrphanVisits } = buildApiClient(fetch); + fetchJson.mockResolvedValue({ visits: Mock.of({ data: [] }) }); + const { getNonOrphanVisits } = buildApiClient(); const result = await getNonOrphanVisits(); - expect(fetch).toHaveBeenCalled(); + expect(fetchJson).toHaveBeenCalled(); expect(result).toEqual({ data: [] }); }); }); @@ -310,12 +317,12 @@ describe('ShlinkApiClient', () => { describe('editDomainRedirects', () => { it('returns the redirects', async () => { const resp = { baseUrlRedirect: null, regular404Redirect: 'foo', invalidShortUrlRedirect: 'bar' }; - const fetch = buildFetch(resp); - const { editDomainRedirects } = buildApiClient(fetch); + fetchJson.mockResolvedValue(resp); + const { editDomainRedirects } = buildApiClient(); const result = await editDomainRedirects({ domain: 'foo' }); - expect(fetch).toHaveBeenCalled(); + expect(fetchJson).toHaveBeenCalled(); expect(result).toEqual(resp); }); @@ -324,16 +331,16 @@ describe('ShlinkApiClient', () => { [ErrorTypeV2.NOT_FOUND], [ErrorTypeV3.NOT_FOUND], ])('retries request if API version is not supported', async (type) => { - const fetch = buildRejectedFetch({ type, detail: 'detail', title: 'title', status: 404 }).mockImplementation( - buildFetch({}), - ); - const { editDomainRedirects } = buildApiClient(fetch); + fetchJson + .mockRejectedValueOnce({ type, detail: 'detail', title: 'title', status: 404 }) + .mockResolvedValue({}); + const { editDomainRedirects } = buildApiClient(); await editDomainRedirects({ domain: 'foo' }); - expect(fetch).toHaveBeenCalledTimes(2); - expect(fetch).toHaveBeenNthCalledWith(1, expect.stringContaining('/v3/'), expect.anything()); - expect(fetch).toHaveBeenNthCalledWith(2, expect.stringContaining('/v2/'), expect.anything()); + expect(fetchJson).toHaveBeenCalledTimes(2); + expect(fetchJson).toHaveBeenNthCalledWith(1, expect.stringContaining('/v3/'), expect.anything()); + expect(fetchJson).toHaveBeenNthCalledWith(2, expect.stringContaining('/v2/'), expect.anything()); }); }); }); diff --git a/test/api/services/ShlinkApiClientBuilder.test.ts b/test/api/services/ShlinkApiClientBuilder.test.ts index 1b08d385..18c29ebf 100644 --- a/test/api/services/ShlinkApiClientBuilder.test.ts +++ b/test/api/services/ShlinkApiClientBuilder.test.ts @@ -2,13 +2,13 @@ import { Mock } from 'ts-mockery'; import { buildShlinkApiClient } from '../../../src/api/services/ShlinkApiClientBuilder'; import { ReachableServer, SelectedServer } from '../../../src/servers/data'; import { ShlinkState } from '../../../src/container/types'; +import { HttpClient } from '../../../src/common/services/HttpClient'; describe('ShlinkApiClientBuilder', () => { - const fetch = jest.fn(); const server = (data: Partial) => Mock.of(data); const createBuilder = () => { - const builder = buildShlinkApiClient(fetch); + const builder = buildShlinkApiClient(Mock.of()); return (selectedServer: SelectedServer) => builder(() => Mock.of({ selectedServer })); }; @@ -42,7 +42,7 @@ describe('ShlinkApiClientBuilder', () => { it('does not fetch from state when provided param is already selected server', () => { const url = 'url'; const apiKey = 'apiKey'; - const apiClient = buildShlinkApiClient(fetch)(server({ url, apiKey })); + const apiClient = buildShlinkApiClient(Mock.of())(server({ url, apiKey })); expect(apiClient['baseUrl']).toEqual(url); // eslint-disable-line @typescript-eslint/dot-notation expect(apiClient['apiKey']).toEqual(apiKey); // eslint-disable-line @typescript-eslint/dot-notation diff --git a/test/common/services/ImageDownloader.test.ts b/test/common/services/ImageDownloader.test.ts index 2b537f20..e33c4ecf 100644 --- a/test/common/services/ImageDownloader.test.ts +++ b/test/common/services/ImageDownloader.test.ts @@ -1,22 +1,25 @@ +import { Mock } from 'ts-mockery'; import { ImageDownloader } from '../../../src/common/services/ImageDownloader'; +import { HttpClient } from '../../../src/common/services/HttpClient'; import { windowMock } from '../../__mocks__/Window.mock'; describe('ImageDownloader', () => { - const fetch = jest.fn(); + const fetchBlob = jest.fn(); + const httpClient = Mock.of({ fetchBlob }); let imageDownloader: ImageDownloader; beforeEach(() => { jest.clearAllMocks(); (global as any).URL = { createObjectURL: () => '' }; - imageDownloader = new ImageDownloader(fetch, windowMock); + imageDownloader = new ImageDownloader(httpClient, windowMock); }); it('calls URL with response type blob', async () => { - fetch.mockResolvedValue({ blob: () => new Blob() }); + fetchBlob.mockResolvedValue(new Blob()); await imageDownloader.saveImage('/foo/bar.png', 'my-image.png'); - expect(fetch).toHaveBeenCalledWith('/foo/bar.png'); + expect(fetchBlob).toHaveBeenCalledWith('/foo/bar.png'); }); }); diff --git a/test/servers/reducers/remoteServers.test.ts b/test/servers/reducers/remoteServers.test.ts index efc44907..2c717696 100644 --- a/test/servers/reducers/remoteServers.test.ts +++ b/test/servers/reducers/remoteServers.test.ts @@ -1,12 +1,15 @@ +import { Mock } from 'ts-mockery'; import { fetchServers } from '../../../src/servers/reducers/remoteServers'; import { createServers } from '../../../src/servers/reducers/servers'; +import { HttpClient } from '../../../src/common/services/HttpClient'; describe('remoteServersReducer', () => { afterEach(jest.clearAllMocks); describe('fetchServers', () => { const dispatch = jest.fn(); - const fetch = jest.fn(); + const fetchJson = jest.fn(); + const httpClient = Mock.of({ fetchJson }); it.each([ [ @@ -76,8 +79,8 @@ describe('remoteServersReducer', () => { ['', {}], [{}, {}], ])('tries to fetch servers from remote', async (mockedValue, expectedNewServers) => { - fetch.mockResolvedValue(mockedValue); - const doFetchServers = fetchServers(fetch); + fetchJson.mockResolvedValue(mockedValue); + const doFetchServers = fetchServers(httpClient); await doFetchServers()(dispatch, jest.fn(), {}); @@ -88,7 +91,7 @@ describe('remoteServersReducer', () => { expect(dispatch).toHaveBeenNthCalledWith(3, expect.objectContaining({ type: doFetchServers.fulfilled.toString(), })); - expect(fetch).toHaveBeenCalledTimes(1); + expect(fetchJson).toHaveBeenCalledTimes(1); }); }); });