diff --git a/api/routes/auth-token.ts b/api/routes/auth-token.ts index e79ea82..6c51d39 100644 --- a/api/routes/auth-token.ts +++ b/api/routes/auth-token.ts @@ -1,7 +1,6 @@ import * as Sentry from '@sentry/node'; import { ServerResponse, UserMembershipData } from 'bungie-api-ts/user'; import asyncHandler from 'express-async-handler'; -import superagent from 'superagent'; import util from 'util'; import { AuthTokenRequest, AuthTokenResponse } from '../shapes/auth.js'; @@ -30,12 +29,34 @@ export const authTokenHandler = asyncHandler(async (req, res) => { // make request to bungie try { - const bungieResponse = await superagent - .get('https://www.bungie.net/Platform/User/GetMembershipsForCurrentUser/') - .set('X-API-Key', apiApp.bungieApiKey) - .set('Authorization', `Bearer ${bungieAccessToken}`); + const bungieResponse = await fetch( + 'https://www.bungie.net/Platform/User/GetMembershipsForCurrentUser/', + { + headers: { + 'X-API-Key': apiApp.bungieApiKey, + Authorization: `Bearer ${bungieAccessToken}`, + }, + }, + ); + + if (!bungieResponse.ok) { + // TODO: try/catch + const errorBody = await bungieResponse.json(); + if (errorBody.ErrorStatus == 'WebAuthRequired') { + metrics.increment('authToken.webAuthRequired.count'); + res.status(401).send({ + error: 'WebAuthRequired', + message: `Bungie.net token is not valid`, + }); + return; + } else { + throw new Error( + `Error from Bungie.net while verifying token: ${errorBody.ErrorStatus}: ${errorBody.Message}`, + ); + } + } - const responseData = bungieResponse.body as ServerResponse; + const responseData = (await bungieResponse.json()) as ServerResponse; const serverMembershipId = responseData.Response.bungieNetUser.membershipId; if (serverMembershipId === membershipId) { @@ -96,18 +117,8 @@ export const authTokenHandler = asyncHandler(async (req, res) => { }); } } catch (e) { - if (e.response && e.response.body.ErrorStatus == 'WebAuthRequired') { - metrics.increment('authToken.webAuthRequired.count'); - res.status(401).send({ - error: 'WebAuthRequired', - message: `Bungie.net token is not valid`, - }); - } else { - Sentry.captureException(e); - console.error('Error issuing auth token', e); - throw new Error( - `Error from Bungie.net while verifying token: ${e.response?.body.ErrorStatus}: ${e.response?.body.Message}`, - ); - } + Sentry.captureException(e); + console.error('Error issuing auth token', e); + throw e; } }); diff --git a/api/routes/donate.ts b/api/routes/donate.ts index daa14e6..2a5ec07 100644 --- a/api/routes/donate.ts +++ b/api/routes/donate.ts @@ -1,18 +1,17 @@ import asyncHandler from 'express-async-handler'; -import superagent from 'superagent'; const donationUrl = 'https://bungiefoundation.donordrive.com/api/1.3/participants/22881'; // Temporary proxy for donor drive API export const donateHandler = asyncHandler(async (_req, res) => { try { - const response = await superagent.get(donationUrl); - if (response.statusCode >= 400) { - throw new Error(`Got status code ${response.statusCode}`); + const response = await fetch(donationUrl); + if (response.status >= 400) { + throw new Error(`Got status code ${response.status}`); } // Cache successful responses for 15 minutes in CF res.set('Cache-Control', 'max-age=900'); - res.send(response.body); + res.send(await response.text()); } catch (e) { res.set('Cache-Control', 'max-age=60'); res.status(500); diff --git a/api/server.test.ts b/api/server.test.ts index cc7961e..e9a530c 100644 --- a/api/server.test.ts +++ b/api/server.test.ts @@ -1,30 +1,32 @@ import { readFile } from 'fs'; import { sign } from 'jsonwebtoken'; import _ from 'lodash'; -import supertest from 'supertest'; +import { makeFetch } from 'supertest-fetch'; import { promisify } from 'util'; import { v4 as uuid } from 'uuid'; import { refreshApps } from './apps/index.js'; import { closeDbPool } from './db/index.js'; import { app } from './server.js'; +import { ApiApp } from './shapes/app.js'; import { ExportResponse } from './shapes/export.js'; -import { GlobalSettings } from './shapes/global-settings.js'; -import { LoadoutShareRequest } from './shapes/loadout-share.js'; +import { PlatformInfoResponse } from './shapes/global-settings.js'; +import { ImportResponse } from './shapes/import.js'; +import { LoadoutShareRequest, LoadoutShareResponse } from './shapes/loadout-share.js'; import { Loadout, LoadoutItem } from './shapes/loadouts.js'; -import { ProfileResponse, ProfileUpdateRequest } from './shapes/profile.js'; +import { ProfileResponse, ProfileUpdateRequest, ProfileUpdateResponse } from './shapes/profile.js'; import { SearchType } from './shapes/search.js'; import { defaultSettings } from './shapes/settings.js'; -const request = supertest(app); +const fetch = makeFetch(app); const bungieMembershipId = 1234; const platformMembershipId = '4611686018433092312'; -let testApiKey; -let testUserToken; +let testApiKey: string; +let testUserToken: string; beforeAll(async () => { const appResponse = await createApp(); - testApiKey = appResponse.body.app.dimApiKey; + testApiKey = appResponse.dimApiKey; expect(testApiKey).toBeDefined(); await refreshApps(); @@ -39,27 +41,30 @@ afterAll(() => closeDbPool()); it('returns basic info from GET /', async () => { // Sends GET Request to / endpoint - const response = await request.get('/'); + const response = await fetch('/'); expect(response.status).toBe(200); }); describe('platform_info', () => { it('returns global info from GET /platform_info', async () => { - const response = await request.get('/platform_info').expect('Content-Type', /json/).expect(200); + const response = (await fetch('/platform_info') + .expect('Content-Type', /json/) + .expect(200) + .json()) as PlatformInfoResponse; - const platformInfo = response.body.settings as GlobalSettings; + const platformInfo = response.settings; expect(platformInfo.dimApiEnabled).toBe(true); }); it('can return info from an unknown flavor', async () => { - const response = await request - .get('/platform_info?flavor=foo') + const response = (await fetch('/platform_info?flavor=foo') .expect('Content-Type', /json/) - .expect(200); + .expect(200) + .json()) as PlatformInfoResponse; - const platformInfo = response.body.settings as GlobalSettings; + const platformInfo = response.settings; expect(platformInfo.dimApiEnabled).toBe(true); }); @@ -70,16 +75,14 @@ it('can create new apps idempotently', async () => { const response = await createApp(); // Same API Key - expect(response.body.app.dimApiKey).toEqual(testApiKey); + expect(response.dimApiKey).toEqual(testApiKey); }); describe('import/export', () => { it('can import and export data', async () => { await importData(); - const response = await getRequestAuthed('/export').expect(200); - - const exportResponse = response.body as ExportResponse; + const exportResponse = (await getRequestAuthed('/export').expect(200).json()) as ExportResponse; expect(exportResponse.settings.itemSortOrderCustom).toEqual([ 'sunset', @@ -104,11 +107,11 @@ describe('profile', () => { beforeEach(importData); it('can retrieve all profile data', async () => { - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=settings,loadouts,tags,triumphs&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.settings!.itemSortOrderCustom).toEqual([ 'sunset', @@ -126,9 +129,9 @@ describe('profile', () => { }); it('can retrieve only settings, without needing a platform membership ID', async () => { - const response = await getRequestAuthed('/profile?components=settings').expect(200); - - const profileResponse = response.body as ProfileResponse; + const profileResponse = (await getRequestAuthed('/profile?components=settings') + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.settings!.itemSortOrderCustom).toEqual([ 'sunset', @@ -146,11 +149,11 @@ describe('profile', () => { }); it('can retrieve only loadouts', async () => { - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=loadouts&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.settings).toBeUndefined(); expect(profileResponse.loadouts!.length).toBe(19); @@ -160,7 +163,7 @@ describe('profile', () => { it('can delete all data with /delete_all_data', async () => { const response = await postRequestAuthed('/delete_all_data').expect(200); - expect(response.body.deleted).toEqual({ + expect((await response.json()).deleted).toEqual({ itemHashTags: 71, loadouts: 37, searches: 205, @@ -170,9 +173,7 @@ describe('profile', () => { }); // Now re-export and make sure it's all gone - const exported = await getRequestAuthed('/export').expect(200); - - const exportResponse = exported.body as ExportResponse; + const exportResponse = (await getRequestAuthed('/export').expect(200).json()) as ExportResponse; expect(_.size(exportResponse.settings)).toBe(0); expect(exportResponse.loadouts.length).toBe(0); @@ -184,9 +185,9 @@ describe('settings', () => { beforeEach(() => postRequestAuthed('/delete_all_data').expect(200)); it('returns default settings', async () => { - const response = await getRequestAuthed('/profile?components=settings').expect(200); - - const profileResponse = response.body as ProfileResponse; + const profileResponse = (await getRequestAuthed('/profile?components=settings') + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.settings).toEqual(defaultSettings); }); @@ -203,12 +204,12 @@ describe('settings', () => { ], }; - await postRequestAuthed('/profile').send(request).expect(200); + await postRequestAuthed('/profile', request).expect(200); // Read settings back - const response = await getRequestAuthed('/profile?components=settings').expect(200); - - const profileResponse = response.body as ProfileResponse; + const profileResponse = (await getRequestAuthed('/profile?components=settings') + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.settings?.showNewItems).toBe(true); }); @@ -252,16 +253,18 @@ describe('loadouts', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Read loadouts back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=loadouts&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.loadouts?.length).toBe(1); const resultLoadout = profileResponse.loadouts![0]; @@ -286,9 +289,11 @@ describe('loadouts', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Change name const request2: ProfileUpdateRequest = { @@ -302,16 +307,18 @@ describe('loadouts', () => { ], }; - const updateResult2 = await postRequestAuthed('/profile').send(request2).expect(200); + const updateResult2 = (await postRequestAuthed('/profile', request2) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult2.body.results[0].status).toBe('Success'); + expect(updateResult2.results[0].status).toBe('Success'); // Read loadouts back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=loadouts&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.loadouts?.length).toBe(1); expect(profileResponse.loadouts![0].name).toBe('Updated Name'); @@ -329,9 +336,11 @@ describe('loadouts', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Delete the loadout const request2: ProfileUpdateRequest = { @@ -345,16 +354,18 @@ describe('loadouts', () => { ], }; - const updateResult2 = await postRequestAuthed('/profile').send(request2).expect(200); + const updateResult2 = (await postRequestAuthed('/profile', request2) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult2.body.results[0].status).toBe('Success'); + expect(updateResult2.results[0].status).toBe('Success'); // Read loadouts back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=loadouts&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.loadouts?.length).toBe(0); }); @@ -378,16 +389,18 @@ describe('tags', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=tags&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.tags?.length).toBe(1); const resultTag = profileResponse.tags![0]; @@ -412,9 +425,11 @@ describe('tags', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Change tag and notes const request2: ProfileUpdateRequest = { @@ -432,16 +447,18 @@ describe('tags', () => { ], }; - const updateResult2 = await postRequestAuthed('/profile').send(request2).expect(200); + const updateResult2 = (await postRequestAuthed('/profile', request2) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult2.body.results[0].status).toBe('Success'); + expect(updateResult2.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=tags&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.tags?.length).toBe(1); const resultTag = profileResponse.tags![0]; @@ -466,16 +483,18 @@ describe('tags', () => { ], }; - const updateResult3 = await postRequestAuthed('/profile').send(request3).expect(200); + const updateResult3 = (await postRequestAuthed('/profile', request3) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult3.body.results[0].status).toBe('Success'); + expect(updateResult3.results[0].status).toBe('Success'); // Read tags back after deleting the tag - const response2 = await getRequestAuthed( + const profileResponse2 = (await getRequestAuthed( `/profile?components=tags&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse2 = response2.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse2.tags?.length).toBe(1); const resultTag2 = profileResponse2.tags![0]; @@ -501,9 +520,11 @@ describe('tags', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // delete tag and notes const request2: ProfileUpdateRequest = { @@ -521,16 +542,18 @@ describe('tags', () => { ], }; - const updateResult2 = await postRequestAuthed('/profile').send(request2).expect(200); + const updateResult2 = (await postRequestAuthed('/profile', request2) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult2.body.results[0].status).toBe('Success'); + expect(updateResult2.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=tags&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.tags?.length).toBe(0); }); @@ -559,10 +582,12 @@ describe('tags', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); - expect(updateResult.body.results[1].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); + expect(updateResult.results[1].status).toBe('Success'); // cleanup tags by id const request2: ProfileUpdateRequest = { @@ -576,16 +601,18 @@ describe('tags', () => { ], }; - const updateResult2 = await postRequestAuthed('/profile').send(request2).expect(200); + const updateResult2 = (await postRequestAuthed('/profile', request2) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult2.body.results[0].status).toBe('Success'); + expect(updateResult2.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=tags&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.tags?.length).toBe(0); }); @@ -607,16 +634,18 @@ describe('item hash tags', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=hashtags&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.itemHashTags?.length).toBe(1); const resultTag = profileResponse.itemHashTags![0]; @@ -639,9 +668,11 @@ describe('item hash tags', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Change tag and notes const request2: ProfileUpdateRequest = { @@ -657,16 +688,18 @@ describe('item hash tags', () => { ], }; - const updateResult2 = await postRequestAuthed('/profile').send(request2).expect(200); + const updateResult2 = (await postRequestAuthed('/profile', request2) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult2.body.results[0].status).toBe('Success'); + expect(updateResult2.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=hashtags&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.itemHashTags?.length).toBe(1); const resultTag = profileResponse.itemHashTags![0]; @@ -689,16 +722,18 @@ describe('item hash tags', () => { ], }; - const updateResult3 = await postRequestAuthed('/profile').send(request3).expect(200); + const updateResult3 = (await postRequestAuthed('/profile', request3) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult3.body.results[0].status).toBe('Success'); + expect(updateResult3.results[0].status).toBe('Success'); // Read tags back after deleting the tag - const response2 = await getRequestAuthed( + const profileResponse2 = (await getRequestAuthed( `/profile?components=hashtags&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse2 = response2.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse2.itemHashTags?.length).toBe(1); const resultTag2 = profileResponse2.itemHashTags![0]; @@ -722,9 +757,11 @@ describe('item hash tags', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // delete tag and notes const request2: ProfileUpdateRequest = { @@ -742,16 +779,18 @@ describe('item hash tags', () => { ], }; - const updateResult2 = await postRequestAuthed('/profile').send(request2).expect(200); + const updateResult2 = (await postRequestAuthed('/profile', request2) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult2.body.results[0].status).toBe('Success'); + expect(updateResult2.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=tags&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.tags?.length).toBe(0); }); @@ -775,16 +814,18 @@ describe('triumphs', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=triumphs&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.triumphs?.length).toBe(1); expect(profileResponse.triumphs!).toEqual([1234]); @@ -805,9 +846,11 @@ describe('triumphs', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); const request2: ProfileUpdateRequest = { platformMembershipId, @@ -823,18 +866,20 @@ describe('triumphs', () => { ], }; - const updateResult2 = await postRequestAuthed('/profile').send(request2).expect(200); + const updateResult2 = (await postRequestAuthed('/profile', request2) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); - expect(updateResult2.body.results[0].status).toBe('Success'); + expect(updateResult2.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=triumphs&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.triumphs?.length).toBe(0); }); @@ -854,9 +899,11 @@ describe('triumphs', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); const request2: ProfileUpdateRequest = { platformMembershipId, @@ -872,18 +919,18 @@ describe('triumphs', () => { ], }; - const updateResult2 = await postRequestAuthed('/profile').send(request2).expect(200); + const updateResult2 = (await postRequestAuthed('/profile', request2) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); - - expect(updateResult2.body.results[0].status).toBe('Success'); + expect(updateResult2.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=triumphs&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.triumphs?.length).toBe(1); expect(profileResponse.triumphs!).toEqual([1234]); @@ -907,16 +954,18 @@ describe('searches', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=searches&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.searches?.filter((s) => s.usageCount > 0)?.length).toBe(1); expect(profileResponse.searches![0].query).toBe('tag:favorite'); @@ -945,16 +994,18 @@ describe('searches', () => { ], }; - const updateResult = await postRequestAuthed('/profile').send(request).expect(200); + const updateResult = (await postRequestAuthed('/profile', request) + .expect(200) + .json()) as ProfileUpdateResponse; - expect(updateResult.body.results[0].status).toBe('Success'); + expect(updateResult.results[0].status).toBe('Success'); // Read tags back - const response = await getRequestAuthed( + const profileResponse = (await getRequestAuthed( `/profile?components=searches&platformMembershipId=${platformMembershipId}`, - ).expect(200); - - const profileResponse = response.body as ProfileResponse; + ) + .expect(200) + .json()) as ProfileResponse; expect(profileResponse.searches?.filter((s) => s.usageCount > 0)?.length).toBe(1); expect(profileResponse.searches![0].query).toBe('tag:favorite'); @@ -970,49 +1021,60 @@ describe('loadouts', () => { loadout, }; - const updateResult = await postRequestAuthed('/loadout_share').send(request).expect(200); + const updateResult = (await postRequestAuthed('/loadout_share', request) + .expect(200) + .json()) as LoadoutShareResponse; - console.log(updateResult.body.shareUrl); - expect(updateResult.body.shareUrl).toMatch(/https:\/\/dim.gg\/[a-z0-9]{7}\/Test-Loadout/); + console.log(updateResult.shareUrl); + expect(updateResult.shareUrl).toMatch(/https:\/\/dim.gg\/[a-z0-9]{7}\/Test-Loadout/); }); }); async function createApp() { - const response = await request - .post('/new_app') - .send({ + const response = (await fetch('/new_app', { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ id: 'test-app', bungieApiKey: 'test-api-key', origin: 'https://localhost:8080', - }) + }), + }) .expect('Content-Type', /json/) - .expect(200); + .expect(200) + .json()) as { app: ApiApp }; - expect(response.body.app.dimApiKey).toBeDefined(); + expect(response.app.dimApiKey).toBeDefined(); - return response; + return response.app; } async function importData() { const file = JSON.parse((await promisify(readFile)('./dim-data.json')).toString()); - await postRequestAuthed('/import').send(file).expect(200); + (await postRequestAuthed('/import', file).expect(200).json()) as ImportResponse; return file; } function getRequestAuthed(url: string) { - return request - .get(url) - .set('X-API-Key', testApiKey) - .set('Authorization', `Bearer ${testUserToken}`) - .expect('Content-Type', /json/); + return fetch(url, { + headers: { + 'X-API-Key': testApiKey, + Authorization: `Bearer ${testUserToken}`, + }, + }).expect('Content-Type', /json/); } -function postRequestAuthed(url: string) { - return request - .post(url) - .set('X-API-Key', testApiKey) - .set('Authorization', `Bearer ${testUserToken}`) - .expect('Content-Type', /json/); +function postRequestAuthed(url: string, body?: any) { + return fetch(url, { + method: 'POST', + headers: { + 'X-API-Key': testApiKey, + Authorization: `Bearer ${testUserToken}`, + }, + body: body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined, + }).expect('Content-Type', /json/); } diff --git a/package.json b/package.json index 0605a25..1313548 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "docker:push": "docker push destinyitemmanager/dim-api:latest", "lint": "eslint --fix api --ext .js,.ts", "lint-check": "eslint api --ext .js,.ts", - "test": "jest --verbose --coverage api --forceExit --detectOpenHandles", + "test": "jest --verbose --coverage api --forceExit", "test:watch": "jest --watch", "dim-api-types:build": "./build-dim-api-types.sh" }, @@ -37,8 +37,6 @@ "@types/lodash": "^4.14.149", "@types/morgan": "^1.7.37", "@types/pg": "^8.6.0", - "@types/superagent": "^4.1.3", - "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", "@types/vhost": "^3.0.4", "@typescript-eslint/eslint-plugin": "^6.7.2", @@ -51,7 +49,7 @@ "prettier": "^3.0.3", "prettier-plugin-organize-imports": "^3.0.0", "rollup": "^3.29.2", - "supertest": "^6.0.1", + "supertest-fetch": "^2.0.0", "ts-jest": "^29.0.3", "ts-node": "^10.0.0", "ts-node-dev": "^2.0.0", @@ -76,7 +74,6 @@ "morgan": "^1.9.1", "pg": "^8.0.0", "slugify": "^1.6.5", - "superagent": "^8.0.6", "uuid": "^9.0.0", "vhost": "^3.0.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 187ae10..3133208 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,9 +59,6 @@ dependencies: slugify: specifier: ^1.6.5 version: 1.6.6 - superagent: - specifier: ^8.0.6 - version: 8.1.2 uuid: specifier: ^9.0.0 version: 9.0.1 @@ -115,12 +112,6 @@ devDependencies: '@types/pg': specifier: ^8.6.0 version: 8.10.5 - '@types/superagent': - specifier: ^4.1.3 - version: 4.1.19 - '@types/supertest': - specifier: ^2.0.8 - version: 2.0.14 '@types/uuid': specifier: ^9.0.0 version: 9.0.5 @@ -157,9 +148,9 @@ devDependencies: rollup: specifier: ^3.29.2 version: 3.29.4 - supertest: - specifier: ^6.0.1 - version: 6.3.3 + supertest-fetch: + specifier: ^2.0.0 + version: 2.0.0 ts-jest: specifier: ^29.0.3 version: 29.1.1(@babel/core@7.23.0)(jest@29.7.0)(typescript@5.2.2) @@ -1962,10 +1953,6 @@ packages: '@types/node': 20.8.4 dev: true - /@types/cookiejar@2.1.2: - resolution: {integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==} - dev: true - /@types/cors@2.8.14: resolution: {integrity: sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==} dependencies: @@ -2110,19 +2097,6 @@ packages: resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} dev: true - /@types/superagent@4.1.19: - resolution: {integrity: sha512-McM1mlc7PBZpCaw0fw/36uFqo0YeA6m8JqoyE4OfqXsZCIg0hPP2xdE6FM7r6fdprDZHlJwDpydUj1R++93hCA==} - dependencies: - '@types/cookiejar': 2.1.2 - '@types/node': 20.8.4 - dev: true - - /@types/supertest@2.0.14: - resolution: {integrity: sha512-Q900DeeHNFF3ZYYepf/EyJfZDA2JrnWLaSQ0YNV7+2GTo8IlJzauEnDGhya+hauncpBYTYGpVHwGdssJeAQ7eA==} - dependencies: - '@types/superagent': 4.1.19 - dev: true - /@types/uuid@9.0.5: resolution: {integrity: sha512-xfHdwa1FMJ082prjSJpoEI57GZITiQz10r3vEJCHa2khEFQjKy91aWKz6+zybzssCvXUwE1LQWgWVwZ4nYUvHQ==} dev: true @@ -2379,9 +2353,6 @@ packages: engines: {node: '>=8'} dev: true - /asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - /asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} dependencies: @@ -2402,9 +2373,6 @@ packages: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: false - /asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - /babel-jest@29.7.0(@babel/core@7.23.0): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2641,6 +2609,7 @@ packages: dependencies: function-bind: 1.1.1 get-intrinsic: 1.2.1 + dev: false /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -2756,15 +2725,6 @@ packages: engines: {node: '>=0.1.90'} dev: true - /combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - dependencies: - delayed-stream: 1.0.0 - - /component-emitter@1.3.0: - resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} - /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2793,9 +2753,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /cookiejar@2.1.4: - resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} - /core-js-compat@3.33.0: resolution: {integrity: sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==} dependencies: @@ -2950,10 +2907,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -2969,12 +2922,6 @@ packages: engines: {node: '>=8'} dev: true - /dezalgo@1.0.4: - resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} - dependencies: - asap: 2.0.6 - wrappy: 1.0.2 - /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3319,9 +3266,6 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - /fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: @@ -3411,22 +3355,6 @@ packages: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} dev: true - /form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - - /formidable@2.1.2: - resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} - dependencies: - dezalgo: 1.0.4 - hexoid: 1.0.0 - once: 1.4.0 - qs: 6.11.2 - /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -3451,6 +3379,7 @@ packages: /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: false /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -3469,6 +3398,7 @@ packages: has: 1.0.4 has-proto: 1.0.1 has-symbols: 1.0.3 + dev: false /get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} @@ -3549,19 +3479,17 @@ packages: /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} + dev: false /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + dev: false /has@1.0.4: resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} engines: {node: '>= 0.4.0'} - /hexoid@1.0.0: - resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} - engines: {node: '>=8'} - /hi-base32@0.5.1: resolution: {integrity: sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==} dev: false @@ -4445,6 +4373,7 @@ packages: /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} + dev: false /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} @@ -4457,12 +4386,14 @@ packages: /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + dev: false /mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 + dev: false /mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} @@ -4470,11 +4401,6 @@ packages: hasBin: true dev: false - /mime@2.6.0: - resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} - engines: {node: '>=4.0.0'} - hasBin: true - /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -4604,6 +4530,7 @@ packages: /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: false /obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} @@ -4632,6 +4559,7 @@ packages: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 + dev: true /onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} @@ -4973,12 +4901,6 @@ packages: side-channel: 1.0.4 dev: false - /qs@6.11.2: - resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} - engines: {node: '>=0.6'} - dependencies: - side-channel: 1.0.4 - /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -5231,6 +5153,7 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.1 object-inspect: 1.12.3 + dev: false /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -5359,31 +5282,9 @@ packages: engines: {node: '>=8'} dev: true - /superagent@8.1.2: - resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} - engines: {node: '>=6.4.0 <13 || >=14'} - dependencies: - component-emitter: 1.3.0 - cookiejar: 2.1.4 - debug: 4.3.4 - fast-safe-stringify: 2.1.1 - form-data: 4.0.0 - formidable: 2.1.2 - methods: 1.1.2 - mime: 2.6.0 - qs: 6.11.2 - semver: 7.5.4 - transitivePeerDependencies: - - supports-color - - /supertest@6.3.3: - resolution: {integrity: sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==} - engines: {node: '>=6.4.0'} - dependencies: - methods: 1.1.2 - superagent: 8.1.2 - transitivePeerDependencies: - - supports-color + /supertest-fetch@2.0.0: + resolution: {integrity: sha512-Mx2ZszLJkrBMFt7fmyML12y+u2yHAN5FF94yOzZXHzrTtsS59fjdbqh9G4OAPDM14jnkDkcLk2AgCVGJZcVoUg==} + engines: {node: '>=18.0.0'} dev: true /supports-color@5.5.0: @@ -5775,6 +5676,7 @@ packages: /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true /write-file-atomic@4.0.2: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}