From 9985ab103f23474a19d555e85a4570a912435da5 Mon Sep 17 00:00:00 2001 From: CocoIsBuggy Date: Wed, 11 Dec 2024 08:53:27 +0200 Subject: [PATCH 1/6] Install vitest and uninstall jest (no yarn lockfile) --- jest.config.cjs | 19 ---- package.json | 4 +- src/__tests__/fixtures/gql.fixtures.ts | 128 +++++++++++++++++++++++ src/__tests__/fixtures/mongo.fixtures.ts | 120 +++++++++++++++++++++ tsconfig.json | 2 +- vite.config.ts | 8 ++ 6 files changed, 258 insertions(+), 23 deletions(-) delete mode 100644 jest.config.cjs create mode 100644 src/__tests__/fixtures/gql.fixtures.ts create mode 100644 src/__tests__/fixtures/mongo.fixtures.ts create mode 100644 vite.config.ts diff --git a/jest.config.cjs b/jest.config.cjs deleted file mode 100644 index 29335854..00000000 --- a/jest.config.cjs +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - testTimeout: 2 * 60 * 1000, - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1' - }, - extensionsToTreatAsEsm: ['.ts'], - transform: { - '^.+\\.(mt|t|cj|j)s$': [ - 'ts-jest', - { - useESM: true - } - ] - }, - testEnvironment: 'node', - testMatch: [ - '/**/__tests__/*.ts' - ] -} diff --git a/package.json b/package.json index eb9421e0..bd24fe2b 100644 --- a/package.json +++ b/package.json @@ -8,20 +8,18 @@ "license": "AGPL-3.0-or-later", "devDependencies": { "@types/auth0": "^3.3.2", - "@types/jest": "^29.4.0", "@types/node": "^18.13.0", "@types/supertest": "^2.0.12", "@types/underscore": "^1.11.4", "cross-env": "^7.0.3", "husky": "^8.0.1", - "jest": "^29.7.0", "jest-extended": "^4.0.2", "mongodb-memory-server": "^10.1.2", "nock": "^13.3.0", "supertest": "^6.3.3", - "ts-jest": "^29.2.5", "ts-standard": "^12.0.0", "typescript": "4.9.5", + "vitest": "^2.1.8", "wait-for-expect": "^3.0.2" }, "dependencies": { diff --git a/src/__tests__/fixtures/gql.fixtures.ts b/src/__tests__/fixtures/gql.fixtures.ts new file mode 100644 index 00000000..ca9d80be --- /dev/null +++ b/src/__tests__/fixtures/gql.fixtures.ts @@ -0,0 +1,128 @@ +import { ApolloServer, BaseContext } from '@apollo/server' +import express, { Application } from 'express' +import { dbTest } from './mongo.fixtures' +import { QueryAPIProps } from '../../utils/testUtils' +import request from 'supertest' +import jwt from 'jsonwebtoken' +import { expressMiddleware } from '@apollo/server/dist/esm/express4' +import bodyParser from 'body-parser' +import { applyMiddleware } from 'graphql-middleware' +import { localDevBypassAuthContext } from '../../auth/local-dev/middleware' +import localDevBypassAuthPermissions from '../../auth/local-dev/permissions' +import { graphqlSchema } from '../../graphql/resolvers' +import BulkImportDataSource from '../../model/BulkImportDataSource' +import ChangeLogDataSource from '../../model/ChangeLogDataSource' +import MutableMediaDataSource from '../../model/MutableMediaDataSource' +import TickDataSource from '../../model/TickDataSource' +import UserDataSource from '../../model/UserDataSource' +import cors from 'cors' +import MutableOrganizationDataSource from '../../model/MutableOrganizationDataSource' +import { muuidToString } from '../../utils/helpers' +import muuid, { MUUID } from 'uuid-mongodb' +import { AreaType } from '../../db/AreaTypes' + + + +interface ServerTestFixtures { + ctx: { + server: ApolloServer + app: Application + }, + query: (opts: QueryAPIProps) => Promise, + user: MUUID, + userUuid: string, + + usa: AreaType, + ca: AreaType, + wa: AreaType, + or: AreaType +} + + +export const serverTest = dbTest.extend({ + ctx: async ({ task, db, climbs, areas }, use) => { + const schema = applyMiddleware( + graphqlSchema, + (localDevBypassAuthPermissions).generate(graphqlSchema) + ) + const dataSources = ({ + climbs, + areas, + bulkImport: BulkImportDataSource.getInstance(), + organizations: MutableOrganizationDataSource.getInstance(), + ticks: TickDataSource.getInstance(), + history: ChangeLogDataSource.getInstance(), + media: MutableMediaDataSource.getInstance(), + users: UserDataSource.getInstance() + }) + + const app = express() + + const server = new ApolloServer({ + schema, + introspection: false, + plugins: [] + }) + // server must be started before applying middleware + await server.start() + + const context = localDevBypassAuthContext + + app.use('/', + bodyParser.json({ limit: '10mb' }), + cors(), + express.json(), + expressMiddleware(server, { + context: async ({ req }) => ({ dataSources, ...await context({ req }) }) + }) + ) + + + await use({ + app, server + }) + await server.stop() + }, + + query: async ({ctx}, use) => { + await use( + async ({ + query, + operationName, + variables, + userUuid = '', + roles = [], + endpoint = '/' + }: QueryAPIProps) => { + // Avoid needing to pass in actual signed tokens. + const jwtSpy = vi.spyOn(jwt, 'verify') + jwtSpy.mockImplementation(() => { + return { + // Roles defined at https://manage.auth0.com/dashboard/us/dev-fmjy7n5n/roles + 'https://tacos.openbeta.io/roles': roles, + 'https://tacos.openbeta.io/uuid': userUuid + } + }) + + const queryObj = { query, operationName, variables } + let req = request(ctx.app) + .post(endpoint) + .send(queryObj) + + if (userUuid != null) { + req = req.set('Authorization', 'Bearer placeholder-jwt-see-SpyOn') + } + + return await req + } + ) + }, + + user: async ({ task }, use) => await use(muuid.mode('relaxed').from(task.id)), + userUuid: async ({ user }, use) => await use(muuidToString(user)), + + usa: async ({ areas }, use) => await use(await areas.addCountry('usa')), + ca: async ({ user, usa, areas }, use) => await use(await areas.addArea(user, 'CA', usa.metadata.area_id)), + wa: async ({ user, usa, areas }, use) => await use(await areas.addArea(user, 'WA', usa.metadata.area_id)), + or: async ({ user, usa, areas }, use) => await use(await areas.addArea(user, 'OR', usa.metadata.area_id)) +}) diff --git a/src/__tests__/fixtures/mongo.fixtures.ts b/src/__tests__/fixtures/mongo.fixtures.ts new file mode 100644 index 00000000..97a598d7 --- /dev/null +++ b/src/__tests__/fixtures/mongo.fixtures.ts @@ -0,0 +1,120 @@ +import { MongoMemoryReplSet } from 'mongodb-memory-server' +import { ChangeStream, MongoClient } from 'mongodb' +import mongoose, { Model, Mongoose } from 'mongoose' +import { checkVar, defaultPostConnect } from '../../db' +import { testStreamListener } from '../../db/edit/streamListener' +import { Mock } from 'vitest' +import { ClimbSchema } from '../../db/ClimbSchema' +import { AreaSchema } from '../../db/AreaSchema' +import { ClimbType } from '../../db/ClimbTypes' +import { AreaType } from '../../db/AreaTypes' +import MutableAreaDataSource from '../../model/MutableAreaDataSource' +import MutableClimbDataSource from '../../model/MutableClimbDataSource' +import { MediaObject } from '../../db/MediaObjectTypes' +import { MediaObjectSchema } from '../../db/MediaObjectSchema' + +/** + * In-memory Mongo replset used for testing. + * More portable than requiring user to set up Mongo in a background Docker process. + * Need a replset to faciliate transactions. + */ +let mongod: MongoMemoryReplSet +const onChange: Mock = vi.fn() +let stream: ChangeStream + +beforeAll(async () => { + mongod = await MongoMemoryReplSet.create({ + // Stream listener listens on DB denoted by 'MONGO_DBNAME' env var. + replSet: { count: 1, storageEngine: 'wiredTiger', dbName: checkVar('MONGO_DBNAME') } + }) + stream = await defaultPostConnect(async () => await testStreamListener(onChange)) +}) + +afterAll(async () => { + await stream?.close() + await mongoose.connection.dropDatabase() + await mongoose.connection.close() + await mongod.stop() +}) + +interface DbTestContext { + uri: string + db: Mongoose + client: MongoClient + insertDirectly: (collection: string, documents: any[]) => Promise + climbModel: Model + areaModel: Model + mediaModel: Model + + areas: MutableAreaDataSource + climbs: MutableClimbDataSource +} + +export const dbTest = test.extend({ + uri: async ({ task }, use) => await use(await mongod.getUri(task.id)), + client: async ({ task, uri }, use) => { + const client = new MongoClient(uri) + await use(client) + await client.close() + }, + db: async ({ task, uri, client }, use) => { + const mongooseInstance = await mongoose.connect(uri, { + autoIndex: false // Create indices using defaultPostConnect instead. + }) + + mongoose.set('debug', false) // Set to 'true' to enable verbose mode + await use(mongooseInstance) + + // Clear the collections this instance created + await client.db(task.id).dropDatabase() + }, + insertDirectly: async ({ task, uri }, use) => { + /** + * Bypass Mongoose to insert data directly into Mongo. + * Useful for inserting data that is incompatible with Mongoose schemas for migration testing. + * @param collection Name of collection for documents to be inserted into. + * @param docs Documents to be inserted into collection. + */ + const insertDirectly = async (collection: string, documents: any[]): Promise => { + const client = new MongoClient(uri) + + try { + const database = client.db(task.id) + const mCollection = database.collection(collection) + const result = await mCollection.insertMany(documents) + + console.log(`${result.insertedCount} documents were inserted directly into MongoDB`) + } finally { + await client.close() + } + } + + await use(insertDirectly) + }, + + climbModel: async ({ task, db }, use) => { + const climbModel = db.model('climbs', ClimbSchema) + await climbModel.createIndexes() + await use(climbModel) + }, + + areaModel: async ({ db }, use) => { + const model = db.model('areas', AreaSchema) + await model.createIndexes() + await use(model) + }, + + mediaModel: async ({ db }, use) => { + const model = db.model('media_objects', MediaObjectSchema) + await model.createIndexes() + await use(model) + }, + + areas: async ({ climbModel, areaModel, mediaModel, client }, use) => { + await use(new MutableAreaDataSource({ climbModel, areaModel, mediaModel, modelOrCollection: client.db().collection('areas') })) + }, + + climbs: async ({ climbModel, areaModel, client }, use) => { + await use(new MutableClimbDataSource({ climbModel, areaModel, modelOrCollection: client.db().collection('climbs') })) + } +}) diff --git a/tsconfig.json b/tsconfig.json index 3b503295..c191f2e5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "types": [ "node", - "jest", + "vitest/globals", ], "allowJs": true, "skipLibCheck": true, diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 00000000..4147feba --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' +export default defineConfig({ + test: { + include: ['src/**/*.test.ts', 'src/**/__tests__/**/*'], + globals: true, + environment: 'node' + } +}) From e64abd5e1a5e34e0cb8ebb4de61bf61e9282c817 Mon Sep 17 00:00:00 2001 From: CocoIsBuggy Date: Wed, 11 Dec 2024 10:13:09 +0200 Subject: [PATCH 2/6] write out all fail() invocations I believe the fail() pattern came from jasmine down to jest in the first instance, though I am not sure. In any event it leads to some strange testing behavior in which you begin to re-invent the framework from the inside out. For this reason it seems the vite team has decided not to make a pass at including it, since it is technically breaking containment even in the context of jest testing. I have done my best to take the time it needs to ascertain which of these fails() should be asserts, and which should be using the standard expectations api. --- src/__tests__/gradeUtils.ts | 13 +++-- .../export/json/async-file.processor.test.ts | 2 +- src/model/__tests__/AreaHistoryDataSource.ts | 17 ++---- src/model/__tests__/MediaDataSource.ts | 17 +++--- src/model/__tests__/MutableClimbDataSource.ts | 56 +++++++++---------- .../MutableOrganizationDataSource.ts | 5 +- src/model/__tests__/ticks.ts | 40 +++++-------- src/model/__tests__/updateAreas.ts | 23 ++++---- 8 files changed, 74 insertions(+), 99 deletions(-) diff --git a/src/__tests__/gradeUtils.ts b/src/__tests__/gradeUtils.ts index 94c8fffe..7682e83d 100644 --- a/src/__tests__/gradeUtils.ts +++ b/src/__tests__/gradeUtils.ts @@ -42,7 +42,7 @@ describe('Test grade utilities', () => { it('creates grade object correctly in US context', () => { const context = gradeContextToGradeScales.US - if (context == null) fail('Bad grade context. Should not happen.') + assert(context != null, 'Bad grade context, should not happen') let actual = createGradeObject('5.9', sanitizeDisciplines({ sport: true }), context) expect(actual).toEqual({ @@ -88,9 +88,9 @@ describe('Test grade utilities', () => { expect(actual).toBeUndefined() }) - it.failing('can alpine ice grades to climbs with discipline ice', () => { + it.fails('can alpine ice grades to climbs with discipline ice', () => { const context = gradeContextToGradeScales.US - if (context == null) fail('Bad grade context. Should not happen.') + assert(context != null, 'Bad grade context, should not happen') const actual = createGradeObject('AI2', sanitizeDisciplines({ ice: true }), context) expect(actual).toEqual({ @@ -100,7 +100,7 @@ describe('Test grade utilities', () => { it('creates grade object correctly in AU context', () => { const context = gradeContextToGradeScales.AU - if (context == null) fail('Bad grade context. Should not happen.') + assert(context != null, 'Bad grade context, should not happen') let actual = createGradeObject('5', sanitizeDisciplines({ sport: true }), context) expect(actual).toEqual({ @@ -139,7 +139,7 @@ describe('Test grade utilities', () => { it('creates grade object correctly in FR context', () => { const context = gradeContextToGradeScales.FR - if (context == null) fail('Bad grade context. Should not happen.') + assert(context != null, 'Bad grade context, should not happen') let actual = createGradeObject('5a', sanitizeDisciplines({ sport: true }), context) expect(actual).toEqual({ @@ -178,7 +178,8 @@ describe('Test grade utilities', () => { it('creates grade object correctly in BRZ context', () => { const context = gradeContextToGradeScales.BRZ - if (context == null) { fail('Bad grade context. Should not happen.') } + assert(context != null, 'Bad grade context, should not happen') + let actual = createGradeObject('V', sanitizeDisciplines({ sport: true }), context) expect(actual).toEqual({ brazilian_crux: 'V' diff --git a/src/db/export/json/async-file.processor.test.ts b/src/db/export/json/async-file.processor.test.ts index c02ddf19..a1a719e0 100644 --- a/src/db/export/json/async-file.processor.test.ts +++ b/src/db/export/json/async-file.processor.test.ts @@ -4,7 +4,7 @@ import path from 'path' interface TestType { name: string, path?: string[] } describe('file processor', () => { - const writer = jest.fn(async (_data, _path) => await Promise.resolve()) + const writer = vi.fn(async (_data, _path) => await Promise.resolve()) const testData: TestType[] = [{ name: 'test', path: ['one', 'two'] }, { name: 'test2' }] const testPath = 'testPath' diff --git a/src/model/__tests__/AreaHistoryDataSource.ts b/src/model/__tests__/AreaHistoryDataSource.ts index 424490c4..5c16295e 100644 --- a/src/model/__tests__/AreaHistoryDataSource.ts +++ b/src/model/__tests__/AreaHistoryDataSource.ts @@ -122,8 +122,7 @@ describe('Area history', () => { it('should record an Areas.deleteArea() call', async () => { const greece = await areas.addCountry('grc') const leonidio = await areas.addArea(testUser, 'Leonidio', greece.metadata.area_id) - - if (leonidio == null) fail() + assert(leonidio != null) await areas.deleteArea(testUser, leonidio.metadata.area_id) @@ -141,21 +140,13 @@ describe('Area history', () => { const spain = await areas.addCountry('esp') const margalef = await areas.addArea(testUser, 'margalef', spain.metadata.area_id) - if (margalef == null) fail() + assert(margalef != null) const newChild = await areas.addArea(testUser, 'One', margalef.metadata.area_id) - if (newChild == null) fail() - - let deleted = false - try { - await areas.deleteArea(testUser, margalef.metadata.area_id) - fail('Shouldn\'t allow deletion when the area still has subareas') - } catch (e) { - deleted = true - } + assert(newChild != null) - expect(deleted).toBeTruthy() + await expect(async () => await areas.deleteArea(testUser, margalef.metadata.area_id)).rejects.toThrow() await waitForExpect(() => expect(onChange).toHaveBeenCalledTimes(5)) const history = await ChangeLogDataSource.getInstance().getAreaChangeSets(spain.metadata.area_id) diff --git a/src/model/__tests__/MediaDataSource.ts b/src/model/__tests__/MediaDataSource.ts index 0a30a164..13a638b0 100644 --- a/src/model/__tests__/MediaDataSource.ts +++ b/src/model/__tests__/MediaDataSource.ts @@ -61,17 +61,18 @@ describe('MediaDataSource', () => { await areas.addCountry('USA') areaForTagging1 = await areas.addArea(muuid.v4(), 'Yosemite NP', null, 'USA') areaForTagging2 = await areas.addArea(muuid.v4(), 'Lake Tahoe', null, 'USA') - if (areaForTagging1 == null || areaForTagging2 == null) fail('Fail to pre-seed test areas') + + assert(areaForTagging1 != null, 'Fail to pre-seed test areas') + assert(areaForTagging2 != null, 'Fail to pre-seed test areas') const rs = await climbs.addOrUpdateClimbs(muuid.v4(), areaForTagging1.metadata.area_id, [newSportClimb1]) - if (rs == null) fail('Fail to pre-seed test climbs') + assert(rs != null, 'Fail to pre-seed test areas') climbIdForTagging = muuid.from(rs[0]) const rs2 = await media.addMediaObjects([TEST_MEDIA]) testMediaObject = rs2[0] - if (testMediaObject == null) { - fail('Fail to create test media') - } + + assert(testMediaObject != null, 'fail to create test media') areaTag1 = { mediaId: testMediaObject._id, @@ -116,7 +117,7 @@ describe('MediaDataSource', () => { }) it('should tag & remove an area tag', async () => { - if (areaForTagging1 == null) fail('Pre-seeded test area not found') + assert(areaForTagging1 != null, 'Pre-seeded test area not found') // verify the number tags before test let mediaObjects = await media.getOneUserMedia(TEST_MEDIA.userUuid, 10) @@ -227,9 +228,7 @@ describe('MediaDataSource', () => { const expectedMedia = await media.addMediaObjects(newMediaListInput) - if (expectedMedia == null) { - fail('Seeding test media fail') - } + assert(expectedMedia != null, 'seeding test media fail') // reverse because getOneUserMediaPagination() returns most recent first expectedMedia.reverse() diff --git a/src/model/__tests__/MutableClimbDataSource.ts b/src/model/__tests__/MutableClimbDataSource.ts index f78fbb5a..e25cd449 100644 --- a/src/model/__tests__/MutableClimbDataSource.ts +++ b/src/model/__tests__/MutableClimbDataSource.ts @@ -171,7 +171,7 @@ describe('Climb CRUD', () => { await areas.addCountry('usa') const newDestination = await areas.addArea(testUser, 'California', null, 'usa') - if (newDestination == null) fail('Expect new area to be created') + expect(newDestination).toBeTruthy() const routesArea = await areas.addArea(testUser, 'Sport & Trad', newDestination.metadata.area_id) @@ -208,7 +208,7 @@ describe('Climb CRUD', () => { await areas.addCountry('esp') const newDestination = await areas.addArea(testUser, 'Valencia', null, 'esp') - if (newDestination == null) fail('Expect new area to be created') + expect(newDestination).toBeTruthy() const boulderingArea = await areas.addArea(testUser, 'Bouldering only', newDestination.metadata.area_id) @@ -223,13 +223,13 @@ describe('Climb CRUD', () => { const newClimb = await climbs.findOneClimbByMUUID(muid.from(newIDs[0])) - if (newClimb == null) fail('Expecting new boulder problem to be added, but didn\'t find one') + assert(newClimb != null) expect(newClimb.name).toBe(newBoulderProblem1.name) }) it('can delete new boulder problems', async () => { const newBoulderingArea = await areas.addArea(testUser, 'Bouldering area 1', null, 'fr') - if (newBoulderingArea == null) fail('Expect new area to be created') + expect(newBoulderingArea).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( testUser, @@ -270,7 +270,7 @@ describe('Climb CRUD', () => { // expect one to remain rs = await climbs.findOneClimbByMUUID(muid.from(newIDs[1])) - if (rs == null) fail('Expect climb 2 to exist') + assert(rs != null) expect(rs._id.toUUID().toString()).toEqual(newIDs[1]) const areaRs = await areas.findOneAreaByUUID(newBoulderingArea.metadata.area_id) @@ -281,7 +281,7 @@ describe('Climb CRUD', () => { it('handles mixed grades and disciplines correctly', async () => { await areas.addCountry('can') const newBoulderingArea = await areas.addArea(testUser, 'Bouldering area 1', null, 'can') - if (newBoulderingArea == null) fail('Expect new area to be created') + expect(newBoulderingArea).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( testUser, @@ -304,7 +304,8 @@ describe('Climb CRUD', () => { { // A roped climbing area const newClimbingArea = await areas.addArea(testUser, 'Climbing area 1', null, 'aus') - if (newClimbingArea == null) fail('Expect new area to be created') + expect(newClimbingArea).toBeTruthy() + const newclimbs = [ { ...newSportClimb1, grade: '17' }, // good sport grade @@ -351,7 +352,7 @@ describe('Climb CRUD', () => { { // A bouldering area const newBoulderingArea = await areas.addArea(testUser, 'Bouldering area 1', null, 'aus') - if (newBoulderingArea == null) fail('Expect new area to be created') + expect(newBoulderingArea).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( testUser, @@ -379,7 +380,7 @@ describe('Climb CRUD', () => { { // A roped climbing area const newClimbingArea = await areas.addArea(testUser, 'Climbing area in Brazil', null, 'bra') - if (newClimbingArea == null) fail('Expect new area to be created in Brazil') + expect(newClimbingArea).toBeTruthy() const newclimbs = [ { ...newSportClimb1, grade: 'VIsup' }, // good sport grade @@ -426,7 +427,7 @@ describe('Climb CRUD', () => { { // A bouldering area const newBoulderingArea = await areas.addArea(testUser, 'Bouldering area 1', null, 'bra') - if (newBoulderingArea == null) fail('Expect new area to be created') + expect(newBoulderingArea).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( testUser, @@ -453,7 +454,7 @@ describe('Climb CRUD', () => { // A roped climbing area const newClimbingArea = await areas.addArea(testUser, 'Climbing area 1', null, 'deu') - if (newClimbingArea == null) fail('Expect new area to be created') + expect(newClimbingArea).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( testUser, @@ -481,7 +482,7 @@ describe('Climb CRUD', () => { it('can update boulder problems', async () => { const newDestination = await areas.addArea(testUser, 'Bouldering area A100', null, 'fr') - if (newDestination == null) fail('Expect new area to be created') + expect(newDestination).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( testUser, @@ -540,7 +541,7 @@ describe('Climb CRUD', () => { it('can update climb length, boltsCount & fa', async () => { const newDestination = await areas.addArea(testUser, 'Sport area Z100', null, 'fr') - if (newDestination == null) fail('Expect new area to be created') + expect(newDestination).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( testUser, @@ -576,7 +577,7 @@ describe('Climb CRUD', () => { await areas.addCountry('aut') const newDestination = await areas.addArea(testUser, 'Some Location with Multi-Pitch Climbs', null, 'aut') - if (newDestination == null) fail('Expect new area to be created') + expect(newDestination).toBeTruthy() const routesArea = await areas.addArea(testUser, 'Sport & Trad Multi-Pitches', newDestination.metadata.area_id) @@ -602,22 +603,20 @@ describe('Climb CRUD', () => { }, pitches: newClimbWithPitches.pitches }) - // Validate each pitch - if (climb?.pitches != null) { - climb.pitches.forEach((pitch) => { + + assert(climb?.pitches != null) + + climb.pitches.forEach((pitch) => { expect(pitch).toHaveProperty('_id') expect(pitch).toHaveProperty('parentId') expect(pitch).toHaveProperty('pitchNumber') }) - } else { - fail('Pitches are missing either of required attributes id, parentId, pitchNumber') - } }) it('can update multi-pitch problems', async () => { const newDestination = await areas.addArea(testUser, 'Some Multi-Pitch Area to be Updated', null, 'deu') - if (newDestination == null) fail('Expect new area to be created') + expect(newDestination).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( testUser, @@ -628,11 +627,9 @@ describe('Climb CRUD', () => { // Fetch the original climb const original = await climbs.findOneClimbByMUUID(muid.from(newIDs[0])) - // Check if 'original' is not null before accessing its properties - if ((original == null) || (original.pitches == null) || original.pitches.length < 2) { - fail('Original climb is null or does not have at least two pitches (as defined in the test case)') - return - } + assert(original !== null) + assert(original.pitches !== undefined) + expect(original.pitches.length).toBeGreaterThan(2) // Store original pitch IDs and parent IDs const originalPitch1ID = original.pitches[0]._id.toUUID().toString() @@ -700,12 +697,11 @@ describe('Climb CRUD', () => { } // Check that the createdBy and updatedBy fields are not undefined before accessing their properties - if ((updatedClimb.createdBy != null) && (updatedClimb.updatedBy != null)) { + assert(updatedClimb.createdBy != undefined) + assert(updatedClimb.updatedBy != undefined) + expect(updatedClimb.createdBy.toUUID().toString()).toEqual(testUser.toString()) expect(updatedClimb.updatedBy.toUUID().toString()).toEqual(testUser.toString()) - } else { - fail('createdBy or updatedBy is undefined') - } } }) }) diff --git a/src/model/__tests__/MutableOrganizationDataSource.ts b/src/model/__tests__/MutableOrganizationDataSource.ts index 651198b5..4546fd09 100644 --- a/src/model/__tests__/MutableOrganizationDataSource.ts +++ b/src/model/__tests__/MutableOrganizationDataSource.ts @@ -112,9 +112,8 @@ describe('Organization', () => { const updatedOrg = await organizations.updateOrganization(testUser, newOrg.orgId, document) expect(updatedOrg).toBeDefined() - if (updatedOrg == null) { - fail('should not reach here.') - } + assert(updatedOrg != null) + expect(updatedOrg.associatedAreaIds.map(muuidToString).sort()) .toStrictEqual(document?.associatedAreaIds?.map(muuidToString).sort()) expect(updatedOrg.excludedAreaIds.map(muuidToString).sort()) diff --git a/src/model/__tests__/ticks.ts b/src/model/__tests__/ticks.ts index 6140f927..0f52897b 100644 --- a/src/model/__tests__/ticks.ts +++ b/src/model/__tests__/ticks.ts @@ -84,14 +84,12 @@ describe('Ticks', () => { it('should update a tick and return the proper information', async () => { const tick = await ticks.addTick(toTest) - if (tick == null) { - fail('Tick should not be null') - } + expect(tick).not.toBeNull() + const newTick = await ticks.editTick({ _id: tick._id }, tickUpdate) - if (newTick == null) { - fail('The new tick should not be null') - } + expect(newTick).not.toBeNull() + expect(newTick?._id).toEqual(tick._id) expect(newTick?.notes).toEqual(tickUpdate.notes) expect(newTick?.attemptType).toEqual(tickUpdate.attemptType) @@ -101,9 +99,7 @@ describe('Ticks', () => { it('should remove a tick', async () => { const tick = await ticks.addTick(toTest) - if (tick == null) { - fail('Tick should not be null') - } + expect(tick).not.toBeNull() await ticks.deleteTick(tick._id) const newTick = await tickModel.findOne({ _id: tick._id }) @@ -115,10 +111,8 @@ describe('Ticks', () => { it('should add an array of ticks', async () => { const newTicks = await ticks.importTicks(testImport) - if (newTicks == null) { - fail(`Should add ${testImport.length} new ticks`) - } - expect(newTicks?.length).toEqual(testImport.length) + expect(newTicks).not.toBeNull() + expect(newTicks).toHaveLength(testImport.length) const tick1 = await tickModel.findOne({ _id: newTicks[0]._id }) expect(tick1?._id).toEqual(newTicks[0]._id) @@ -139,9 +133,7 @@ describe('Ticks', () => { await users.createOrUpdateUserProfile(userId, userProfileInput) const tick = await ticks.addTick(toTest) - if (tick == null) { - fail('Should add a new tick') - } + expect(tick).not.toBeNull() const newTicks = await ticks.ticksByUser({ userId }) @@ -153,9 +145,9 @@ describe('Ticks', () => { const tick = await ticks.addTick(toTest) const tick2 = await ticks.addTick(toTest2) - if (tick == null || tick2 == null) { - fail('Should add a new tick') - } + expect(tick).not.toBeNull() + expect(tick2).not.toBeNull() + const userClimbTicks = await ticks.ticksByUserIdAndClimb(climbId, userId.toUUID().toString()) expect(userClimbTicks.length).toEqual(1) }) @@ -163,9 +155,8 @@ describe('Ticks', () => { it('should delete all ticks with the specified userId', async () => { const newTicks = await ticks.importTicks(testImport) - if (newTicks == null) { - fail('Should add 3 new ticks') - } + expect(newTicks).not.toBeNull() + expect(newTicks).toHaveLength(3) await ticks.deleteAllTicks(userId.toUUID().toString()) const newTick = await tickModel.findOne({ userId }) @@ -176,9 +167,8 @@ describe('Ticks', () => { const MPTick = await ticks.addTick(toTest) const OBTick = await ticks.addTick(tickUpdate) - if (MPTick == null || OBTick == null) { - fail('Should add two new ticks') - } + expect(MPTick).not.toBeNull() + expect(OBTick).not.toBeNull() await ticks.deleteImportedTicks(userId.toUUID().toString()) const newTick = await tickModel.findOne({ _id: OBTick._id }) diff --git a/src/model/__tests__/updateAreas.ts b/src/model/__tests__/updateAreas.ts index 4d5a894b..90d022e0 100644 --- a/src/model/__tests__/updateAreas.ts +++ b/src/model/__tests__/updateAreas.ts @@ -47,10 +47,9 @@ describe('Areas', () => { const canada = await areas.addCountry('can') // Add 1st area to the country const bc = await areas.addArea(testUser, 'British Columbia', canada.metadata.area_id) + assert(bc != null) + assert(canada != null) - if (bc == null || canada == null) { - fail() - } expect(canada.metadata.lnglat).not.toMatchObject(geometry('Point', [0, 0])) expect(bc.area_name).toEqual('British Columbia') @@ -123,9 +122,8 @@ describe('Areas', () => { await areas.addCountry('au') const a1 = await areas.addArea(testUser, 'One', null, 'au') - if (a1 == null) { - fail() - } + assert(a1 != null) + // for testing area desccription is sanitized const iframeStr = '' const doc1: AreaEditableFieldsType = { @@ -154,7 +152,8 @@ describe('Areas', () => { it('should not update country name and code', async () => { const country = await areas.addCountry('lao') - if (country == null) fail() + assert(country != null) + await expect(areas.updateArea(testUser, country.metadata.area_id, { areaName: 'Foo' })).rejects.toThrowError() // eslint-disable-next-line @@ -169,9 +168,9 @@ describe('Areas', () => { const or = await areas.addArea(testUser, 'OR', usa.metadata.area_id) const wa = await areas.addArea(testUser, 'WA', usa.metadata.area_id) - if (ca == null || or == null || wa == null) { - fail('Child area is null') - } + assert(ca != null, 'child area is null') + assert(or != null, 'child area is null') + assert(wa != null, 'child area is null') // eslint-disable-next-line await new Promise(res => setTimeout(res, 3000)) @@ -205,11 +204,11 @@ describe('Areas', () => { const gr = await areas.addCountry('grc') const kali = await areas.addArea(testUser, 'Kalymnos', gr.metadata.area_id) - if (kali == null) fail() + assert(kali != null) const arhi = await areas.addArea(testUser, 'Arhi', kali.metadata.area_id) - if (arhi == null) fail() + assert(arhi != null) // Try to delete 'Arhi' (expecting exception) await expect(areas.deleteArea(testUser, kali.metadata.area_id)).rejects.toThrow('subareas not empty') From d2a9503874fe50b050c0a05dee08153096d51aaa Mon Sep 17 00:00:00 2001 From: CocoIsBuggy Date: Wed, 11 Dec 2024 10:49:22 +0200 Subject: [PATCH 3/6] Tests now all appear to be passing I have maintained the file-sequential execution style (which I will next attempt to take away.) tests currently run through in ~99 seconds or so. This is much longer than it needs to be, and so this is where the vitest optimizations can really have an opportunity to shine. --- .vscode/launch.json | 35 --------- package.json | 3 +- src/__tests__/areas.ts | 3 - src/__tests__/fixtures/gql.fixtures.ts | 69 +++++++---------- src/__tests__/fixtures/mongo.fixtures.ts | 77 +++++++------------ src/__tests__/history.ts | 3 - src/__tests__/ticks.ts | 3 - .../export/json/async-file.processor.test.ts | 9 ++- src/db/utils/__tests__/Aggregate.test.ts | 3 +- src/model/__tests__/AreaHistoryDataSource.ts | 7 +- src/model/__tests__/MediaDataSource.ts | 8 +- src/model/__tests__/MutableClimbDataSource.ts | 23 +++--- .../MutableOrganizationDataSource.ts | 2 +- src/model/__tests__/UserDataSource.ts | 5 +- src/model/__tests__/updateAreas.ts | 2 +- src/server.ts | 6 +- src/utils/testUtils.ts | 5 +- vite.config.ts | 6 +- 18 files changed, 97 insertions(+), 172 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 3d769e4e..42b1b203 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -76,40 +76,5 @@ ], "console": "integratedTerminal" }, - { - "name": "Debug Jest Tests", - "type": "node", - "request": "launch", - "env": { - "NODE_OPTIONS": "--experimental-vm-modules" - }, - "runtimeArgs": [ - "--inspect-brk", - "${workspaceRoot}/node_modules/.bin/jest", - "--runInBand", - "history" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - }, - { - "type": "node", - "name": "vscode-jest-tests.v2", - "request": "launch", - "env": { - "NODE_OPTIONS": "--experimental-vm-modules" - }, - "args": [ - "${workspaceRoot}/node_modules/.bin/jest", - "--runInBand", - "--watchAll=false", - "--testNamePattern", - "${jest.testNamePattern}", - "--runTestsByPath", - "${jest.testFile}" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - } ] } \ No newline at end of file diff --git a/package.json b/package.json index bd24fe2b..a95c9f00 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "@types/underscore": "^1.11.4", "cross-env": "^7.0.3", "husky": "^8.0.1", - "jest-extended": "^4.0.2", "mongodb-memory-server": "^10.1.2", "nock": "^13.3.0", "supertest": "^6.3.3", @@ -71,7 +70,7 @@ "scripts": { "lint": "yarn ts-standard", "fix": "yarn ts-standard --fix", - "test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest --runInBand", + "test": "vitest run --no-file-parallelism", "build": "tsc -p tsconfig.json", "build-release": "tsc -p tsconfig.release.json", "clean": "tsc -b --clean && rm -rf build/*", diff --git a/src/__tests__/areas.ts b/src/__tests__/areas.ts index 758b1090..8ac46bd5 100644 --- a/src/__tests__/areas.ts +++ b/src/__tests__/areas.ts @@ -1,6 +1,5 @@ import { ApolloServer } from '@apollo/server' import muuid from 'uuid-mongodb' -import { jest } from '@jest/globals' import MutableAreaDataSource from '../model/MutableAreaDataSource.js' import MutableOrganizationDataSource from '../model/MutableOrganizationDataSource.js' import { AreaType } from '../db/AreaTypes.js' @@ -10,8 +9,6 @@ import { muuidToString } from '../utils/helpers.js' import { InMemoryDB } from '../utils/inMemoryDB.js' import express from 'express' -jest.setTimeout(60000) - describe('areas API', () => { let server: ApolloServer let user: muuid.MUUID diff --git a/src/__tests__/fixtures/gql.fixtures.ts b/src/__tests__/fixtures/gql.fixtures.ts index ca9d80be..3a90fd05 100644 --- a/src/__tests__/fixtures/gql.fixtures.ts +++ b/src/__tests__/fixtures/gql.fixtures.ts @@ -10,37 +10,29 @@ import { applyMiddleware } from 'graphql-middleware' import { localDevBypassAuthContext } from '../../auth/local-dev/middleware' import localDevBypassAuthPermissions from '../../auth/local-dev/permissions' import { graphqlSchema } from '../../graphql/resolvers' -import BulkImportDataSource from '../../model/BulkImportDataSource' -import ChangeLogDataSource from '../../model/ChangeLogDataSource' -import MutableMediaDataSource from '../../model/MutableMediaDataSource' -import TickDataSource from '../../model/TickDataSource' -import UserDataSource from '../../model/UserDataSource' import cors from 'cors' -import MutableOrganizationDataSource from '../../model/MutableOrganizationDataSource' import { muuidToString } from '../../utils/helpers' import muuid, { MUUID } from 'uuid-mongodb' -import { AreaType } from '../../db/AreaTypes' - - interface ServerTestFixtures { ctx: { server: ApolloServer app: Application - }, - query: (opts: QueryAPIProps) => Promise, - user: MUUID, - userUuid: string, - - usa: AreaType, - ca: AreaType, - wa: AreaType, - or: AreaType + } + query: (opts: QueryAPIProps) => Promise + user: MUUID + userUuid: string } - export const serverTest = dbTest.extend({ - ctx: async ({ task, db, climbs, areas }, use) => { + ctx: async ({ + task, climbs, areas, bulkImport, + organizations, + ticks, + history, + media, + users + }, use) => { const schema = applyMiddleware( graphqlSchema, (localDevBypassAuthPermissions).generate(graphqlSchema) @@ -48,16 +40,16 @@ export const serverTest = dbTest.extend({ const dataSources = ({ climbs, areas, - bulkImport: BulkImportDataSource.getInstance(), - organizations: MutableOrganizationDataSource.getInstance(), - ticks: TickDataSource.getInstance(), - history: ChangeLogDataSource.getInstance(), - media: MutableMediaDataSource.getInstance(), - users: UserDataSource.getInstance() + bulkImport, + organizations, + ticks, + history, + media, + users }) - + const app = express() - + const server = new ApolloServer({ schema, introspection: false, @@ -65,9 +57,9 @@ export const serverTest = dbTest.extend({ }) // server must be started before applying middleware await server.start() - + const context = localDevBypassAuthContext - + app.use('/', bodyParser.json({ limit: '10mb' }), cors(), @@ -77,14 +69,14 @@ export const serverTest = dbTest.extend({ }) ) - await use({ app, server }) + await server.stop() }, - query: async ({ctx}, use) => { + query: async ({ ctx }, use) => { await use( async ({ query, @@ -103,26 +95,21 @@ export const serverTest = dbTest.extend({ 'https://tacos.openbeta.io/uuid': userUuid } }) - + const queryObj = { query, operationName, variables } let req = request(ctx.app) .post(endpoint) .send(queryObj) - + if (userUuid != null) { req = req.set('Authorization', 'Bearer placeholder-jwt-see-SpyOn') } - + return await req } ) }, user: async ({ task }, use) => await use(muuid.mode('relaxed').from(task.id)), - userUuid: async ({ user }, use) => await use(muuidToString(user)), - - usa: async ({ areas }, use) => await use(await areas.addCountry('usa')), - ca: async ({ user, usa, areas }, use) => await use(await areas.addArea(user, 'CA', usa.metadata.area_id)), - wa: async ({ user, usa, areas }, use) => await use(await areas.addArea(user, 'WA', usa.metadata.area_id)), - or: async ({ user, usa, areas }, use) => await use(await areas.addArea(user, 'OR', usa.metadata.area_id)) + userUuid: async ({ user }, use) => await use(muuidToString(user)) }) diff --git a/src/__tests__/fixtures/mongo.fixtures.ts b/src/__tests__/fixtures/mongo.fixtures.ts index 97a598d7..87bae34f 100644 --- a/src/__tests__/fixtures/mongo.fixtures.ts +++ b/src/__tests__/fixtures/mongo.fixtures.ts @@ -1,17 +1,17 @@ import { MongoMemoryReplSet } from 'mongodb-memory-server' import { ChangeStream, MongoClient } from 'mongodb' -import mongoose, { Model, Mongoose } from 'mongoose' +import mongoose from 'mongoose' import { checkVar, defaultPostConnect } from '../../db' import { testStreamListener } from '../../db/edit/streamListener' import { Mock } from 'vitest' -import { ClimbSchema } from '../../db/ClimbSchema' -import { AreaSchema } from '../../db/AreaSchema' -import { ClimbType } from '../../db/ClimbTypes' -import { AreaType } from '../../db/AreaTypes' import MutableAreaDataSource from '../../model/MutableAreaDataSource' import MutableClimbDataSource from '../../model/MutableClimbDataSource' -import { MediaObject } from '../../db/MediaObjectTypes' -import { MediaObjectSchema } from '../../db/MediaObjectSchema' +import BulkImportDataSource from '../../model/BulkImportDataSource' +import ChangeLogDataSource from '../../model/ChangeLogDataSource' +import MutableMediaDataSource from '../../model/MutableMediaDataSource' +import MutableOrganizationDataSource from '../../model/MutableOrganizationDataSource' +import TickDataSource from '../../model/TickDataSource' +import UserDataSource from '../../model/UserDataSource' /** * In-memory Mongo replset used for testing. @@ -21,12 +21,18 @@ import { MediaObjectSchema } from '../../db/MediaObjectSchema' let mongod: MongoMemoryReplSet const onChange: Mock = vi.fn() let stream: ChangeStream +let uri: string beforeAll(async () => { mongod = await MongoMemoryReplSet.create({ // Stream listener listens on DB denoted by 'MONGO_DBNAME' env var. replSet: { count: 1, storageEngine: 'wiredTiger', dbName: checkVar('MONGO_DBNAME') } }) + + uri = await mongod.getUri(checkVar('MONGO_DBNAME')) + await mongoose.connect(uri, { autoIndex: false }) + mongoose.set('debug', false) // Set to 'true' to enable verbose mode + stream = await defaultPostConnect(async () => await testStreamListener(onChange)) }) @@ -39,35 +45,27 @@ afterAll(async () => { interface DbTestContext { uri: string - db: Mongoose client: MongoClient insertDirectly: (collection: string, documents: any[]) => Promise - climbModel: Model - areaModel: Model - mediaModel: Model areas: MutableAreaDataSource climbs: MutableClimbDataSource + bulkImport: BulkImportDataSource + organizations: MutableOrganizationDataSource + ticks: TickDataSource + history: ChangeLogDataSource + media: MutableMediaDataSource + users: UserDataSource } export const dbTest = test.extend({ - uri: async ({ task }, use) => await use(await mongod.getUri(task.id)), - client: async ({ task, uri }, use) => { + uri: async ({ }, use) => await use(uri), + client: async ({ uri }, use) => { const client = new MongoClient(uri) await use(client) await client.close() }, - db: async ({ task, uri, client }, use) => { - const mongooseInstance = await mongoose.connect(uri, { - autoIndex: false // Create indices using defaultPostConnect instead. - }) - - mongoose.set('debug', false) // Set to 'true' to enable verbose mode - await use(mongooseInstance) - // Clear the collections this instance created - await client.db(task.id).dropDatabase() - }, insertDirectly: async ({ task, uri }, use) => { /** * Bypass Mongoose to insert data directly into Mongo. @@ -92,29 +90,12 @@ export const dbTest = test.extend({ await use(insertDirectly) }, - climbModel: async ({ task, db }, use) => { - const climbModel = db.model('climbs', ClimbSchema) - await climbModel.createIndexes() - await use(climbModel) - }, - - areaModel: async ({ db }, use) => { - const model = db.model('areas', AreaSchema) - await model.createIndexes() - await use(model) - }, - - mediaModel: async ({ db }, use) => { - const model = db.model('media_objects', MediaObjectSchema) - await model.createIndexes() - await use(model) - }, - - areas: async ({ climbModel, areaModel, mediaModel, client }, use) => { - await use(new MutableAreaDataSource({ climbModel, areaModel, mediaModel, modelOrCollection: client.db().collection('areas') })) - }, - - climbs: async ({ climbModel, areaModel, client }, use) => { - await use(new MutableClimbDataSource({ climbModel, areaModel, modelOrCollection: client.db().collection('climbs') })) - } + areas: async ({ }, use) => await use(MutableAreaDataSource.getInstance()), + climbs: async ({ }, use) => await use(MutableClimbDataSource.getInstance()), + bulkImport: async ({ }, use) => await use(BulkImportDataSource.getInstance()), + organizations: async ({ }, use) => await use(MutableOrganizationDataSource.getInstance()), + ticks: async ({ }, use) => await use(TickDataSource.getInstance()), + history: async ({ }, use) => await use(ChangeLogDataSource.getInstance()), + media: async ({ }, use) => await use(MutableMediaDataSource.getInstance()), + users: async ({ }, use) => await use(UserDataSource.getInstance()) }) diff --git a/src/__tests__/history.ts b/src/__tests__/history.ts index 0c02f392..0590fd0c 100644 --- a/src/__tests__/history.ts +++ b/src/__tests__/history.ts @@ -1,6 +1,5 @@ import { ApolloServer } from '@apollo/server' import muuid from 'uuid-mongodb' -import { jest } from '@jest/globals' import MutableAreaDataSource from '../model/MutableAreaDataSource.js' import MutableOrganizationDataSource from '../model/MutableOrganizationDataSource.js' import MutableClimbDataSource from '../model/MutableClimbDataSource.js' @@ -11,8 +10,6 @@ import { queryAPI, setUpServer } from '../utils/testUtils.js' import { InMemoryDB } from '../utils/inMemoryDB.js' import express from 'express' -jest.setTimeout(60000) - describe('history API', () => { let server: ApolloServer let user: muuid.MUUID diff --git a/src/__tests__/ticks.ts b/src/__tests__/ticks.ts index 33ab8f20..32c5df61 100644 --- a/src/__tests__/ticks.ts +++ b/src/__tests__/ticks.ts @@ -1,6 +1,5 @@ import { ApolloServer } from '@apollo/server' import muuid from 'uuid-mongodb' -import { jest } from '@jest/globals' import { queryAPI, setUpServer } from '../utils/testUtils.js' import { muuidToString } from '../utils/helpers.js' import { TickInput } from '../db/TickTypes.js' @@ -10,8 +9,6 @@ import { UpdateProfileGQLInput } from '../db/UserTypes.js' import { InMemoryDB } from '../utils/inMemoryDB.js' import express from 'express' -jest.setTimeout(110000) - describe('ticks API', () => { let server: ApolloServer let user: muuid.MUUID diff --git a/src/db/export/json/async-file.processor.test.ts b/src/db/export/json/async-file.processor.test.ts index a1a719e0..071fd50f 100644 --- a/src/db/export/json/async-file.processor.test.ts +++ b/src/db/export/json/async-file.processor.test.ts @@ -21,11 +21,13 @@ describe('file processor', () => { }) } - function withFailedWriteOn (failingData: { name: string }) { + function withFailedWriteOn (failingData: { name: string }): Writer { return async (data, path) => { + console.log(data, failingData) if (data === JSON.stringify(failingData)) { return await Promise.reject('error') } + return await writer(data, path) } } @@ -42,7 +44,10 @@ describe('file processor', () => { it('should continue batch processing on error', async () => { const processor = createProcessor(withFailedWriteOn(testData[0])) - await expect(processor(testData, 0)).rejects.toContain('Failed to write 1/2 files') + // First, check that our failed writer fires as expected + await expect(() => withFailedWriteOn(testData[0])(JSON.stringify(testData[0]), 'path')).rejects.toContain('error') + // now in the context of a strem, we should expect 1 out of two possible files to fail + await expect(async () => await processor(testData, 0)).rejects.toThrow('Failed to write 1/2 files') assertWriterCalledFor(testData[1]) }) diff --git a/src/db/utils/__tests__/Aggregate.test.ts b/src/db/utils/__tests__/Aggregate.test.ts index f4ea7418..693b7bad 100644 --- a/src/db/utils/__tests__/Aggregate.test.ts +++ b/src/db/utils/__tests__/Aggregate.test.ts @@ -1,4 +1,3 @@ -import { jest } from '@jest/globals' import { logger } from '../../../logger' import { aggregateCragStats, merge } from '../Aggregate' import { AggregateType } from '../../AreaTypes' @@ -40,7 +39,7 @@ describe('Aggregate merge', () => { describe('Aggregate Crag Stats', () => { it('Provides crag stat aggregates in US grade context', () => { - jest.spyOn(logger, 'warn').mockImplementation(() => {}) + vi.spyOn(logger, 'warn').mockImplementation(() => {}) const crag = { gradeContext: 'US', climbs: [ diff --git a/src/model/__tests__/AreaHistoryDataSource.ts b/src/model/__tests__/AreaHistoryDataSource.ts index 5c16295e..6795efb0 100644 --- a/src/model/__tests__/AreaHistoryDataSource.ts +++ b/src/model/__tests__/AreaHistoryDataSource.ts @@ -1,19 +1,18 @@ import muuid from 'uuid-mongodb' - import MutableAreaDataSource from '../MutableAreaDataSource.js' import ChangeLogDataSource from '../ChangeLogDataSource.js' import { OperationType } from '../../db/AreaTypes.js' import inMemoryDB from '../../utils/inMemoryDB.js' import waitForExpect from 'wait-for-expect' -import jest from 'jest-mock' +import { Mock } from 'vitest' describe('Area history', () => { let areas: MutableAreaDataSource - let onChange: jest.Mock + let onChange: Mock const testUser = muuid.v4() beforeAll(async () => { - onChange = jest.fn() + onChange = vi.fn() await inMemoryDB.connect(onChange) await ChangeLogDataSource.getInstance()._testRemoveAll() diff --git a/src/model/__tests__/MediaDataSource.ts b/src/model/__tests__/MediaDataSource.ts index 13a638b0..64bf0cca 100644 --- a/src/model/__tests__/MediaDataSource.ts +++ b/src/model/__tests__/MediaDataSource.ts @@ -180,9 +180,7 @@ describe('MediaDataSource', () => { } await media.addMediaObjects([mediaObj]) - const rs2 = await expect(media.addMediaObjects([mediaObj])).rejects.toThrowError(/duplicate key error collection/i) - - expect(rs2).toBeUndefined() + await expect(async () => await media.addMediaObjects([mediaObj])).rejects.toThrowError('duplicate key error collection') }) it('should delete media', async () => { @@ -196,7 +194,7 @@ describe('MediaDataSource', () => { const rs2 = await media.deleteMediaObject(rs[0]._id) expect(rs2).toBe(true) - await expect(media.deleteMediaObject(rs[0]._id)).rejects.toThrowError(/not found/i) + await expect(async () => await media.deleteMediaObject(rs[0]._id)).rejects.toThrowError('not found') }) it('should not delete media with non-empty tags', async () => { @@ -207,7 +205,7 @@ describe('MediaDataSource', () => { } ]) - await expect(media.deleteMediaObject(rs[0]._id)).rejects.toThrowError(/Cannot delete media object with non-empty tags./i) + await expect(async () => await media.deleteMediaObject(rs[0]._id)).rejects.toThrowError('Cannot delete media object with non-empty tags.') }) it('should return paginated media results', async () => { diff --git a/src/model/__tests__/MutableClimbDataSource.ts b/src/model/__tests__/MutableClimbDataSource.ts index e25cd449..c8f39b98 100644 --- a/src/model/__tests__/MutableClimbDataSource.ts +++ b/src/model/__tests__/MutableClimbDataSource.ts @@ -171,7 +171,7 @@ describe('Climb CRUD', () => { await areas.addCountry('usa') const newDestination = await areas.addArea(testUser, 'California', null, 'usa') - expect(newDestination).toBeTruthy() + expect(newDestination).toBeTruthy() const routesArea = await areas.addArea(testUser, 'Sport & Trad', newDestination.metadata.area_id) @@ -208,7 +208,7 @@ describe('Climb CRUD', () => { await areas.addCountry('esp') const newDestination = await areas.addArea(testUser, 'Valencia', null, 'esp') - expect(newDestination).toBeTruthy() + expect(newDestination).toBeTruthy() const boulderingArea = await areas.addArea(testUser, 'Bouldering only', newDestination.metadata.area_id) @@ -306,7 +306,6 @@ describe('Climb CRUD', () => { const newClimbingArea = await areas.addArea(testUser, 'Climbing area 1', null, 'aus') expect(newClimbingArea).toBeTruthy() - const newclimbs = [ { ...newSportClimb1, grade: '17' }, // good sport grade { ...newSportClimb2, grade: '29/30', disciplines: { trad: true } }, // good trad and slash grade @@ -605,18 +604,18 @@ describe('Climb CRUD', () => { }) assert(climb?.pitches != null) - + climb.pitches.forEach((pitch) => { - expect(pitch).toHaveProperty('_id') - expect(pitch).toHaveProperty('parentId') - expect(pitch).toHaveProperty('pitchNumber') - }) + expect(pitch).toHaveProperty('_id') + expect(pitch).toHaveProperty('parentId') + expect(pitch).toHaveProperty('pitchNumber') + }) }) it('can update multi-pitch problems', async () => { const newDestination = await areas.addArea(testUser, 'Some Multi-Pitch Area to be Updated', null, 'deu') - expect(newDestination).toBeTruthy() + expect(newDestination).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( testUser, @@ -629,7 +628,7 @@ describe('Climb CRUD', () => { assert(original !== null) assert(original.pitches !== undefined) - expect(original.pitches.length).toBeGreaterThan(2) + expect(original.pitches.length).not.toBeLessThan(2) // Store original pitch IDs and parent IDs const originalPitch1ID = original.pitches[0]._id.toUUID().toString() @@ -700,8 +699,8 @@ describe('Climb CRUD', () => { assert(updatedClimb.createdBy != undefined) assert(updatedClimb.updatedBy != undefined) - expect(updatedClimb.createdBy.toUUID().toString()).toEqual(testUser.toString()) - expect(updatedClimb.updatedBy.toUUID().toString()).toEqual(testUser.toString()) + expect(updatedClimb.createdBy.toUUID().toString()).toEqual(testUser.toString()) + expect(updatedClimb.updatedBy.toUUID().toString()).toEqual(testUser.toString()) } }) }) diff --git a/src/model/__tests__/MutableOrganizationDataSource.ts b/src/model/__tests__/MutableOrganizationDataSource.ts index 4546fd09..766ede95 100644 --- a/src/model/__tests__/MutableOrganizationDataSource.ts +++ b/src/model/__tests__/MutableOrganizationDataSource.ts @@ -113,7 +113,7 @@ describe('Organization', () => { expect(updatedOrg).toBeDefined() assert(updatedOrg != null) - + expect(updatedOrg.associatedAreaIds.map(muuidToString).sort()) .toStrictEqual(document?.associatedAreaIds?.map(muuidToString).sort()) expect(updatedOrg.excludedAreaIds.map(muuidToString).sort()) diff --git a/src/model/__tests__/UserDataSource.ts b/src/model/__tests__/UserDataSource.ts index 1a63bef9..584ca71a 100644 --- a/src/model/__tests__/UserDataSource.ts +++ b/src/model/__tests__/UserDataSource.ts @@ -1,6 +1,5 @@ import mongoose from 'mongoose' import muuid from 'uuid-mongodb' -import { jest } from '@jest/globals' import { getUserModel } from '../../db/index.js' import UserDataSource from '../UserDataSource.js' @@ -27,7 +26,7 @@ describe('UserDataSource', () => { }) afterEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() }) it('should create a new user with just username', async () => { @@ -143,7 +142,7 @@ describe('UserDataSource', () => { await users.createOrUpdateUserProfile(updater, input) - jest + vi .spyOn(UserDataSource, 'calculateLastUpdatedInDays') .mockImplementation(() => 14) diff --git a/src/model/__tests__/updateAreas.ts b/src/model/__tests__/updateAreas.ts index 90d022e0..131bfe5f 100644 --- a/src/model/__tests__/updateAreas.ts +++ b/src/model/__tests__/updateAreas.ts @@ -152,7 +152,7 @@ describe('Areas', () => { it('should not update country name and code', async () => { const country = await areas.addCountry('lao') - assert(country != null) + assert(country != null) await expect(areas.updateArea(testUser, country.metadata.area_id, { areaName: 'Foo' })).rejects.toThrowError() diff --git a/src/server.ts b/src/server.ts index 6eab9995..ea3e66b8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -25,7 +25,7 @@ import BulkImportDataSource from './model/BulkImportDataSource.js' /** * Create a GraphQL server */ -export async function createServer (): Promise<{ app: express.Application, server: ApolloServer }> { +export async function createServer (serve = true): Promise<{ app: express.Application, server: ApolloServer }> { const schema = applyMiddleware( graphqlSchema, (process.env.LOCAL_DEV_BYPASS_AUTH === 'true' ? localDevBypassAuthPermissions : permissions).generate(graphqlSchema) @@ -66,6 +66,8 @@ export async function createServer (): Promise<{ app: express.Application, serve }) ) - await new Promise(resolve => httpServer.listen({ port: 4000 }, resolve)) + if (serve) { + await new Promise(resolve => httpServer.listen({ port: 4000 }, resolve)) + } return { app, server } } diff --git a/src/utils/testUtils.ts b/src/utils/testUtils.ts index 6006cccf..5efc5632 100644 --- a/src/utils/testUtils.ts +++ b/src/utils/testUtils.ts @@ -1,5 +1,4 @@ import jwt from 'jsonwebtoken' -import { jest } from '@jest/globals' import request from 'supertest' import { ApolloServer } from '@apollo/server' import express from 'express' @@ -37,7 +36,7 @@ export const queryAPI = async ({ port = PORT }: QueryAPIProps): Promise => { // Avoid needing to pass in actual signed tokens. - const jwtSpy = jest.spyOn(jwt, 'verify') + const jwtSpy = vi.spyOn(jwt, 'verify') jwtSpy.mockImplementation(() => { return { // Roles defined at https://manage.auth0.com/dashboard/us/dev-fmjy7n5n/roles @@ -70,7 +69,7 @@ export interface SetUpServerReturnType { export const setUpServer = async (): Promise => { await inMemoryDB.connect() - const { app, server } = await createServer() + const { app, server } = await createServer(false) return { app, server, inMemoryDB } } diff --git a/vite.config.ts b/vite.config.ts index 4147feba..986aee6d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,10 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - include: ['src/**/*.test.ts', 'src/**/__tests__/**/*'], + include: ['src/**/*.test.ts', 'src/**/__tests__/**/*.ts'], + exclude: ['**/*/fixtures'], globals: true, - environment: 'node' + environment: 'node', } }) + From d7b432a8b81b96f77b45525e1afb5d52dd7fe783 Mon Sep 17 00:00:00 2001 From: CocoisBuggy Date: Sat, 14 Dec 2024 09:00:47 +0200 Subject: [PATCH 4/6] Passing most tests stable with vitest fixtures The test speed is up roughly 10x but my expectation is that it should be able to come lower than that. The beauty of the way test containment is implemented here means that there is not much need for isolation - which is good for simulating our use case because we make use of a lot of singletons (though thankfully not TOO much shared state) It was all relatively trivial with the exception of histories. I am intensely anxious with the implementation that has been used for document history, as it obscures control flow and does not look like it will scale - especially if we were ever to want horizontal deployment. All in all the tests are faster, and can tighten up development feedback loops BUT the un-strict nature of our existing test runners means that there are some inconsistencies in execution that I haven't yet uncovered --- .gitignore | 3 + .vscode/launch.json | 12 + package.json | 5 +- src/__tests__/areas.ts | 121 +- src/__tests__/bulkImport.test.ts | 97 +- src/__tests__/fixtures/data.fixtures.ts | 186 ++ src/__tests__/fixtures/gql.fixtures.ts | 104 +- src/__tests__/fixtures/mongo.fixtures.ts | 64 +- src/__tests__/history.ts | 89 +- src/__tests__/organizations.ts | 225 +- src/__tests__/ticks.ts | 137 +- src/model/MutableAreaDataSource.ts | 1 + src/model/__tests__/AreaHistoryDataSource.ts | 186 +- src/model/__tests__/AreaUtils.ts | 2 +- src/model/__tests__/BulkDataSource.test.ts | 147 +- src/model/__tests__/ChangeLogDS.ts | 28 +- src/model/__tests__/MediaDataSource.ts | 178 +- .../__tests__/MutableAreaDataSource.test.ts | 112 +- src/model/__tests__/MutableClimbDataSource.ts | 178 +- .../MutableOrganizationDataSource.ts | 160 +- src/model/__tests__/UserDataSource.ts | 35 +- src/model/__tests__/ticks.ts | 201 +- src/model/__tests__/updateAreas.ts | 235 +- src/server.ts | 6 +- src/utils/inMemoryDB.ts | 84 - src/utils/testUtils.ts | 75 - vite.config.ts | 9 +- yarn.lock | 2237 ++++------------- 28 files changed, 1641 insertions(+), 3276 deletions(-) create mode 100644 src/__tests__/fixtures/data.fixtures.ts delete mode 100644 src/utils/inMemoryDB.ts diff --git a/.gitignore b/.gitignore index e54f7667..be0585d7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ yarn-error.log* lerna-debug.log* .DS_Store +# test profiling data +profiling + # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 42b1b203..fa9c1e18 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -76,5 +76,17 @@ ], "console": "integratedTerminal" }, + { + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "type": "node", + "request": "launch", + "name": "Debug Tests", + "autoAttachChildProcesses": true, + "skipFiles": ["/**", "**/node_modules/**"], + "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", + "args": ["run"], + "smartStep": true, + "console": "integratedTerminal" + } ] } \ No newline at end of file diff --git a/package.json b/package.json index a95c9f00..4cca9b9f 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,7 @@ "supertest": "^6.3.3", "ts-standard": "^12.0.0", "typescript": "4.9.5", - "vitest": "^2.1.8", - "wait-for-expect": "^3.0.2" + "vitest": "^2.1.8" }, "dependencies": { "@apollo/server": "^4.11.2", @@ -70,7 +69,7 @@ "scripts": { "lint": "yarn ts-standard", "fix": "yarn ts-standard --fix", - "test": "vitest run --no-file-parallelism", + "test": "vitest run --silent", "build": "tsc -p tsconfig.json", "build-release": "tsc -p tsconfig.release.json", "clean": "tsc -b --clean && rm -rf build/*", diff --git a/src/__tests__/areas.ts b/src/__tests__/areas.ts index 8ac46bd5..07446881 100644 --- a/src/__tests__/areas.ts +++ b/src/__tests__/areas.ts @@ -1,50 +1,35 @@ -import { ApolloServer } from '@apollo/server' -import muuid from 'uuid-mongodb' -import MutableAreaDataSource from '../model/MutableAreaDataSource.js' -import MutableOrganizationDataSource from '../model/MutableOrganizationDataSource.js' import { AreaType } from '../db/AreaTypes.js' import { OrganizationEditableFieldsType, OrganizationType, OrgType } from '../db/OrganizationTypes.js' -import { queryAPI, setUpServer } from '../utils/testUtils.js' import { muuidToString } from '../utils/helpers.js' -import { InMemoryDB } from '../utils/inMemoryDB.js' -import express from 'express' +import { gqlTest } from './fixtures/gql.fixtures.js' +interface LocalContext { + includedChild: AreaType + excludedArea: AreaType + alphaFields: OrganizationEditableFieldsType + alphaOrg: OrganizationType +} -describe('areas API', () => { - let server: ApolloServer - let user: muuid.MUUID - let userUuid: string - let app: express.Application - let inMemoryDB: InMemoryDB - - // Mongoose models for mocking pre-existing state. - let areas: MutableAreaDataSource - let organizations: MutableOrganizationDataSource - let usa: AreaType - let ca: AreaType - let wa: AreaType - - beforeAll(async () => { - ({ server, inMemoryDB, app } = await setUpServer()) - // Auth0 serializes uuids in "relaxed" mode, resulting in this hex string format - // "59f1d95a-627d-4b8c-91b9-389c7424cb54" instead of base64 "WfHZWmJ9S4yRuTicdCTLVA==". - user = muuid.mode('relaxed').v4() - userUuid = muuidToString(user) - }) - - beforeEach(async () => { - await inMemoryDB.clear() - areas = MutableAreaDataSource.getInstance() - organizations = MutableOrganizationDataSource.getInstance() - usa = await areas.addCountry('usa') - ca = await areas.addArea(user, 'CA', usa.metadata.area_id) - wa = await areas.addArea(user, 'WA', usa.metadata.area_id) - }) +const it = gqlTest.extend({ + includedChild: async ({ addArea, area }, use) => await use(await addArea(undefined, { parent: area })), + excludedArea: async ({ addArea, area }, use) => await use(await addArea(undefined, { parent: area })), + alphaFields: async ({ excludedArea, task, area }, use) => await use({ + displayName: task.id, + associatedAreaIds: [area.metadata.area_id], + excludedAreaIds: [excludedArea.metadata.area_id] + }), + alphaOrg: async ({ organizations, user, alphaFields }, use) => { + const org = await organizations.addOrganization(user, OrgType.localClimbingOrganization, alphaFields) + .then((res: OrganizationType | null) => { + if (res === null) throw new Error('Failure mocking organization.') + return res + }) - afterAll(async () => { - await server.stop() - await inMemoryDB.close() - }) + await use(org) + await organizations.deleteFromCacheById(org._id) + } +}) +describe('areas API', () => { describe('queries', () => { const areaQuery = ` query area($input: ID) { @@ -56,50 +41,50 @@ describe('areas API', () => { } } ` - let alphaFields: OrganizationEditableFieldsType - let alphaOrg: OrganizationType - - beforeEach(async () => { - alphaFields = { - displayName: 'USA without CA Org', - associatedAreaIds: [usa.metadata.area_id], - excludedAreaIds: [ca.metadata.area_id] - } - alphaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, alphaFields) - .then((res: OrganizationType | null) => { - if (res === null) throw new Error('Failure mocking organization.') - return res - }) - }) - it('retrieves an area omitting organizations that exclude it', async () => { - const response = await queryAPI({ + it('retrieves an area omitting organizations that exclude it', async ({ query, userUuid, excludedArea }) => { + const response = await query({ query: areaQuery, operationName: 'area', - variables: { input: ca.metadata.area_id }, - userUuid, - app + variables: { input: muuidToString(excludedArea.metadata.area_id) }, + userUuid }) + expect(response.statusCode).toBe(200) const areaResult = response.body.data.area - expect(areaResult.uuid).toBe(muuidToString(ca.metadata.area_id)) + expect(areaResult).toBeTruthy() + expect(areaResult.uuid).toBe(muuidToString(excludedArea.metadata.area_id)) // Even though alphaOrg associates with ca's parent, usa, it excludes // ca and so should not be listed. expect(areaResult.organizations).toHaveLength(0) }) - it.each([userUuid, undefined])('retrieves an area and lists associated organizations', async (userId) => { - const response = await queryAPI({ + it('retrieves an area and lists associated organizations', async ({ query, userUuid, includedChild, alphaOrg }) => { + const response = await query({ + query: areaQuery, + operationName: 'area', + variables: { input: muuidToString(includedChild.metadata.area_id) }, + userUuid + }) + + expect(response.statusCode).toBe(200) + const areaResult = response.body.data.area + expect(areaResult.uuid).toBe(muuidToString(includedChild.metadata.area_id)) + expect(areaResult.organizations).toHaveLength(1) + expect(areaResult.organizations[0].orgId).toBe(muuidToString(alphaOrg.orgId)) + }) + + it('retrieves an area and lists associated organizations, even with no auth context', async ({ query, includedChild, alphaOrg }) => { + const response = await query({ query: areaQuery, operationName: 'area', - variables: { input: wa.metadata.area_id }, - userUuid: userId, - app + variables: { input: muuidToString(includedChild.metadata.area_id) } }) expect(response.statusCode).toBe(200) const areaResult = response.body.data.area - expect(areaResult.uuid).toBe(muuidToString(wa.metadata.area_id)) + expect(areaResult.uuid).toBe(muuidToString(includedChild.metadata.area_id)) + console.log(areaResult) expect(areaResult.organizations).toHaveLength(1) expect(areaResult.organizations[0].orgId).toBe(muuidToString(alphaOrg.orgId)) }) diff --git a/src/__tests__/bulkImport.test.ts b/src/__tests__/bulkImport.test.ts index be8a54c3..d17a313d 100644 --- a/src/__tests__/bulkImport.test.ts +++ b/src/__tests__/bulkImport.test.ts @@ -1,17 +1,27 @@ -import {ApolloServer} from "@apollo/server"; import muuid from "uuid-mongodb"; -import express from "express"; -import {InMemoryDB} from "../utils/inMemoryDB.js"; -import {queryAPI, setUpServer} from "../utils/testUtils.js"; -import {muuidToString} from "../utils/helpers.js"; import exampleImportData from './import-example.json' assert {type: 'json'}; -import {AreaType} from "../db/AreaTypes.js"; import {BulkImportResultType} from "../db/BulkImportTypes.js"; -import MutableClimbDataSource from "../model/MutableClimbDataSource.js"; -import BulkImportDataSource from "../model/BulkImportDataSource.js"; +import { gqlTest } from "./fixtures/gql.fixtures.js"; +import { muuidToString } from "../utils/helpers"; + +interface LocalContext { + importData: typeof exampleImportData +} + +const it = gqlTest.extend({ + importData: async ({ country }, use) => await use( + { areas: exampleImportData.areas.map(x => { + if (x.countryCode) { + return { ...x, countryCode: country.shortCode} + } + + return { ...x } + }) as typeof exampleImportData['areas'] + }) +}) describe('bulkImportAreas', () => { - const query = ` + const bulkQuery = ` mutation bulkImportAreas($input: BulkImportInput!) { bulkImportAreas(input: $input) { addedAreas { @@ -33,85 +43,50 @@ describe('bulkImportAreas', () => { } ` - let server: ApolloServer - let user: muuid.MUUID - let userUuid: string - let app: express.Application - let inMemoryDB: InMemoryDB - let testArea: AreaType - - let bulkImport: BulkImportDataSource - let climbs: MutableClimbDataSource - - beforeAll(async () => { - ({server, inMemoryDB, app} = await setUpServer()) - // Auth0 serializes uuids in "relaxed" mode, resulting in this hex string format - // "59f1d95a-627d-4b8c-91b9-389c7424cb54" instead of base64 "WfHZWmJ9S4yRuTicdCTLVA==". - user = muuid.mode('relaxed').v4() - userUuid = muuidToString(user) - bulkImport = BulkImportDataSource.getInstance() - climbs = MutableClimbDataSource.getInstance() - }) - - beforeEach(async () => { - await inMemoryDB.clear() - await bulkImport.addCountry('usa') - testArea = await bulkImport.addArea(user, "Test Area", null, "us") - }) - - afterAll(async () => { - await server.stop() - await inMemoryDB.close() - }) - - it('should return 403 if no user', async () => { - const res = await queryAPI({ - app, - query, + it('should return 403 if no user', async ( { query, importData }) => { + const res = await query({ + query: bulkQuery, operationName: 'bulkImportAreas', - variables: {input: exampleImportData} + variables: {input: importData} }) expect(res.statusCode).toBe(200) expect(res.body.errors[0].message).toBe('Not Authorised!') }) - it('should return 403 if user is not an editor', async () => { - const res = await queryAPI({ - app, + it('should return 403 if user is not an editor', async ({ query, userUuid, importData }) => { + const res = await query({ userUuid, - query, + query: bulkQuery, operationName: 'bulkImportAreas', - variables: {input: exampleImportData} + variables: {input: importData} }) expect(res.statusCode).toBe(200) expect(res.body.errors[0].message).toBe('Not Authorised!') }) - it('should return 200 if user is an editor', async () => { - const res = await queryAPI({ - app, + it('should return 200 if user is an editor', async ({ query, importData, userUuid}) => { + const res = await query({ userUuid, roles: ['editor'], - query, + query: bulkQuery, operationName: 'bulkImportAreas', - variables: {input: exampleImportData} + variables: {input: importData} }) expect(res.status).toBe(200) }) - it('should import data', async () => { - const res = await queryAPI({ - app, + it('should import data', async ({ query, userUuid, area, bulkImport, climbs, importData }) => { + const res = await query({ userUuid, roles: ['editor'], - query, + query: bulkQuery, operationName: 'bulkImportAreas', variables: { input: { areas: [ - ...exampleImportData.areas, + ...importData.areas, { - uuid: testArea.metadata.area_id, + uuid: muuidToString(area.metadata.area_id), areaName: "Updated Test Area", } ] diff --git a/src/__tests__/fixtures/data.fixtures.ts b/src/__tests__/fixtures/data.fixtures.ts new file mode 100644 index 00000000..5425500d --- /dev/null +++ b/src/__tests__/fixtures/data.fixtures.ts @@ -0,0 +1,186 @@ +import { ClimbChangeInputType, ClimbType, DisciplineType } from '../../db/ClimbTypes' +import { AreaType } from '../../db/AreaTypes' +import { dbTest } from './mongo.fixtures' +import muuid, { MUUID } from 'uuid-mongodb' +import { muuidToString } from '../../utils/helpers' +import isoCountries, { Alpha3Code } from 'i18n-iso-countries' +import { UserPublicProfile } from '../../db/UserTypes' +import { createGradeObject, gradeContextToGradeScales } from '../../GradeUtils' +import { getScale, GradeScalesTypes } from '@openbeta/sandbag' +import CountriesLngLat from '../../data/countries-with-lnglat.json' + +interface DbTestContext { + user: MUUID + userUuid: string + profile: UserPublicProfile + + addArea: (name?: string, extra?: Partial<{ leaf: boolean, boulder: boolean, parent: MUUID | AreaType }>) => Promise + countryCode: Alpha3Code + country: AreaType + area: AreaType + + addClimb: (props?: Partial) => Promise + climb: ClimbType + + gradeSystemFor: (climb: { type: DisciplineType }) => GradeScalesTypes + + /** + * Given the country that has been supplied to this test context, what would be a + * valid grade to generate for a given climb object + */ + randomGrade: (climb: ClimbType | { type: DisciplineType }) => string +} + +const availableCountries: Alpha3Code[] = Object.keys(isoCountries.getAlpha3Codes()).filter(country => CountriesLngLat[country]) as Alpha3Code[] + +beforeAll(() => { + // We set a default grade contexts for all countries + for (const country of availableCountries) { + if (gradeContextToGradeScales[country] !== undefined) continue + gradeContextToGradeScales[country] = gradeContextToGradeScales.US + } +}) + +export const dataFixtures = dbTest.extend({ + user: async ({ task }, use) => await use(muuid.v4()), + userUuid: async ({ user }, use) => await use(muuidToString(user)), + profile: async ({ task, user, users, userUuid }, use) => { + await users.createOrUpdateUserProfile( + user, + { + userUuid, + username: task.id, + email: 'cat@example.com' + } + ) + + const profile = await users.getUserPublicProfileByUuid(user) + assert(profile != null) + await use(profile) + + await users.deleteFromCacheByFields({ username: task.id }) + }, + + countryCode: async ({ task }, use) => { + const countryCode = availableCountries.pop() + assert(countryCode !== undefined) + await use(countryCode) + }, + + country: async ({ areas, countryCode }, use) => { + const country = await areas.addCountry(countryCode) + assert(country.shortCode) + gradeContextToGradeScales[country.shortCode] = gradeContextToGradeScales.US + await use(country) + + await areas.areaModel.deleteMany({ 'embeddedRelations.ancestors._id': country._id }) + await areas.areaModel.deleteOne({ _id: country._id }) + // once we have cleared out this country and its children, we can happily add this + // country code back into the stack + availableCountries.push(countryCode) + }, + + addArea: async ({ task, country, user, areas }, use) => { + async function addArea (name?: string, extra?: Partial<{ leaf: boolean, boulder: boolean, parent: MUUID | AreaType }>): Promise { + function isArea (x: any): x is AreaType { + return typeof x.metadata?.area_id !== 'undefined' + } + + if (name === undefined || name === 'test') { + name = task.id + process.uptime().toString() + } + + let parent: MUUID | undefined + if ((extra?.parent) != null) { + if (isArea(extra.parent)) { + parent = extra.parent.metadata?.area_id + } else { + parent = extra.parent + } + } + + return await areas.addArea( + user, + name, + parent ?? country.metadata.area_id, + undefined, + undefined, + extra?.leaf, + extra?.boulder + ) + } + + await use(addArea) + + await areas.areaModel.deleteMany({ area_name: { $regex: `^${task.id}` } }) + }, + area: async ({ task, addArea }, use) => { + await use(await addArea()) + }, + + addClimb: async ({ climbs, area, task, user }, use) => { + async function addClimb (data?: Partial): Promise { + const [id] = await climbs.addOrUpdateClimbs(user, area.metadata.area_id, [{ + name: task.id + process.uptime().toString(), + disciplines: { + sport: true + }, + description: 'A good warm up problem', + location: 'Start from the left arete', + protection: '2 bolts', + boltsCount: 2, + ...(data ?? {}) + }]) + + const climb = await climbs.findOneClimbByMUUID(muuid.from(id)) + assert(climb != null) + return climb + } + + await use(addClimb) + + await climbs.climbModel.deleteMany({ + name: { $regex: `^${task.id}` } + }) + }, + climb: async ({ addClimb }, use) => { + await use(await addClimb()) + }, + + gradeSystemFor: async ({ country }, use) => { + const ctx = gradeContextToGradeScales[country.gradeContext] + assert(ctx !== undefined) + + const generate = (climb: { type: DisciplineType }): GradeScalesTypes => { + const system: GradeScalesTypes = gradeContextToGradeScales[country.gradeContext]?.[Object.keys(climb.type).filter(type => climb.type[type])[0]] + assert(system) + return system + } + + await use(generate) + }, + + randomGrade: async ({ country, gradeSystemFor }, use) => { + const ctx = gradeContextToGradeScales[country.gradeContext] + assert(ctx !== undefined) + + const generate = (climb: ClimbType): string => { + const system = gradeSystemFor(climb) + + const scale = getScale(system) + assert(scale, `no support for system ${system}`) + const grade = scale.getGrade(Math.floor(Math.random() * 100)) + assert(grade) + + console.log({ grade, type: climb.type, scale }) + const record = createGradeObject(grade, climb.type, ctx) + assert(record !== undefined) + + const first = record[Object.keys(record)[0]] + assert(first) + return first + } + + await use(generate) + } +}) diff --git a/src/__tests__/fixtures/gql.fixtures.ts b/src/__tests__/fixtures/gql.fixtures.ts index 3a90fd05..f9d0fcbf 100644 --- a/src/__tests__/fixtures/gql.fixtures.ts +++ b/src/__tests__/fixtures/gql.fixtures.ts @@ -1,18 +1,17 @@ import { ApolloServer, BaseContext } from '@apollo/server' import express, { Application } from 'express' -import { dbTest } from './mongo.fixtures' -import { QueryAPIProps } from '../../utils/testUtils' import request from 'supertest' import jwt from 'jsonwebtoken' -import { expressMiddleware } from '@apollo/server/dist/esm/express4' +import { expressMiddleware } from '@apollo/server/express4' import bodyParser from 'body-parser' import { applyMiddleware } from 'graphql-middleware' -import { localDevBypassAuthContext } from '../../auth/local-dev/middleware' -import localDevBypassAuthPermissions from '../../auth/local-dev/permissions' import { graphqlSchema } from '../../graphql/resolvers' import cors from 'cors' -import { muuidToString } from '../../utils/helpers' -import muuid, { MUUID } from 'uuid-mongodb' +import { dataFixtures } from './data.fixtures' +import { createContext, permissions } from '../../auth' + +let server: ApolloServer +let app: Application interface ServerTestFixtures { ctx: { @@ -20,60 +19,72 @@ interface ServerTestFixtures { app: Application } query: (opts: QueryAPIProps) => Promise - user: MUUID - userUuid: string } -export const serverTest = dbTest.extend({ +export interface QueryAPIProps { + query?: string + operationName?: string + variables?: any + userUuid?: string + roles?: string[] + port?: number + endpoint?: string + app?: express.Application + body?: any +} + +export const gqlTest = dataFixtures.extend({ ctx: async ({ - task, climbs, areas, bulkImport, + climbs, areas, bulkImport, organizations, ticks, history, media, users }, use) => { - const schema = applyMiddleware( - graphqlSchema, - (localDevBypassAuthPermissions).generate(graphqlSchema) - ) - const dataSources = ({ - climbs, - areas, - bulkImport, - organizations, - ticks, - history, - media, - users - }) - - const app = express() + if (app === undefined) { + app = express() + } - const server = new ApolloServer({ - schema, - introspection: false, - plugins: [] - }) - // server must be started before applying middleware - await server.start() + if (server === undefined) { + const schema = applyMiddleware( + graphqlSchema, + permissions.generate(graphqlSchema) + ) - const context = localDevBypassAuthContext + const dataSources = ({ + climbs, + areas, + bulkImport, + organizations, + ticks, + history, + media, + users + }) - app.use('/', - bodyParser.json({ limit: '10mb' }), - cors(), - express.json(), - expressMiddleware(server, { - context: async ({ req }) => ({ dataSources, ...await context({ req }) }) + server = new ApolloServer({ + schema, + introspection: false, + plugins: [] }) - ) + + // server must be started before applying middleware + await server.start() + + app.use('/', + bodyParser.json({ limit: '10mb' }), + cors(), + express.json(), + expressMiddleware(server, { + context: async ({ req }) => ({ dataSources, ...await createContext({ req }) }) + }) + ) + } await use({ app, server }) - - await server.stop() }, query: async ({ ctx }, use) => { @@ -108,8 +119,5 @@ export const serverTest = dbTest.extend({ return await req } ) - }, - - user: async ({ task }, use) => await use(muuid.mode('relaxed').from(task.id)), - userUuid: async ({ user }, use) => await use(muuidToString(user)) + } }) diff --git a/src/__tests__/fixtures/mongo.fixtures.ts b/src/__tests__/fixtures/mongo.fixtures.ts index 87bae34f..de7dd8e3 100644 --- a/src/__tests__/fixtures/mongo.fixtures.ts +++ b/src/__tests__/fixtures/mongo.fixtures.ts @@ -1,9 +1,10 @@ +/* eslint-disable no-empty-pattern */ +// To explain the rule for this file: Object destructuring is REQUIRED for vitest fixtures because +// of how they utilize autoloading. import { MongoMemoryReplSet } from 'mongodb-memory-server' import { ChangeStream, MongoClient } from 'mongodb' import mongoose from 'mongoose' import { checkVar, defaultPostConnect } from '../../db' -import { testStreamListener } from '../../db/edit/streamListener' -import { Mock } from 'vitest' import MutableAreaDataSource from '../../model/MutableAreaDataSource' import MutableClimbDataSource from '../../model/MutableClimbDataSource' import BulkImportDataSource from '../../model/BulkImportDataSource' @@ -12,6 +13,8 @@ import MutableMediaDataSource from '../../model/MutableMediaDataSource' import MutableOrganizationDataSource from '../../model/MutableOrganizationDataSource' import TickDataSource from '../../model/TickDataSource' import UserDataSource from '../../model/UserDataSource' +import { MUUID } from 'uuid-mongodb' +import { BaseChangeRecordType, ChangeLogType } from '../../db/ChangeLogType' /** * In-memory Mongo replset used for testing. @@ -19,9 +22,8 @@ import UserDataSource from '../../model/UserDataSource' * Need a replset to faciliate transactions. */ let mongod: MongoMemoryReplSet -const onChange: Mock = vi.fn() -let stream: ChangeStream let uri: string +let stream: ChangeStream beforeAll(async () => { mongod = await MongoMemoryReplSet.create({ @@ -32,15 +34,7 @@ beforeAll(async () => { uri = await mongod.getUri(checkVar('MONGO_DBNAME')) await mongoose.connect(uri, { autoIndex: false }) mongoose.set('debug', false) // Set to 'true' to enable verbose mode - - stream = await defaultPostConnect(async () => await testStreamListener(onChange)) -}) - -afterAll(async () => { - await stream?.close() - await mongoose.connection.dropDatabase() - await mongoose.connection.close() - await mongod.stop() + stream = await defaultPostConnect() }) interface DbTestContext { @@ -56,6 +50,9 @@ interface DbTestContext { history: ChangeLogDataSource media: MutableMediaDataSource users: UserDataSource + changeLog: ChangeLogDataSource + + waitForChanges: (props: WaitProps) => Promise } export const dbTest = test.extend({ @@ -97,5 +94,44 @@ export const dbTest = test.extend({ ticks: async ({ }, use) => await use(TickDataSource.getInstance()), history: async ({ }, use) => await use(ChangeLogDataSource.getInstance()), media: async ({ }, use) => await use(MutableMediaDataSource.getInstance()), - users: async ({ }, use) => await use(UserDataSource.getInstance()) + users: async ({ }, use) => await use(UserDataSource.getInstance()), + changeLog: async ({ }, use) => await use(ChangeLogDataSource.getInstance()), + + waitForChanges: async ({ changeLog, task }, use) => { + const changeStream = changeLog.changeLogModel.collection.watch() + + async function wait (props: WaitProps): Promise { + return await new Promise((resolve) => { + const listener = changeStream.on('change', (doc) => { + let changes: BaseChangeRecordType[] + + if (doc.operationType === 'insert') { + changes = doc.fullDocument.changes + } else if (doc.operationType === 'update') { + assert(doc.updateDescription.updatedFields?.changes) + changes = doc.updateDescription.updatedFields?.changes + } else { + // we may not know what to do here + return + } + + if (changes[0] === undefined) return + + if ((props.count === undefined && changes.length === 1) || changes.length === props.count) { + resolve() + listener.close()?.catch(console.warn) + } + }) + }) + } + + await use(wait) + await changeStream.close() + } }) + +interface WaitProps { + count?: number + // operation?: AreaOperationType | ClimbEditOperationType + document: { _id: mongoose.Types.ObjectId | MUUID } +} diff --git a/src/__tests__/history.ts b/src/__tests__/history.ts index 0590fd0c..7dbd9196 100644 --- a/src/__tests__/history.ts +++ b/src/__tests__/history.ts @@ -1,47 +1,10 @@ -import { ApolloServer } from '@apollo/server' -import muuid from 'uuid-mongodb' -import MutableAreaDataSource from '../model/MutableAreaDataSource.js' -import MutableOrganizationDataSource from '../model/MutableOrganizationDataSource.js' -import MutableClimbDataSource from '../model/MutableClimbDataSource.js' -import { AreaType } from '../db/AreaTypes.js' -import { OrganizationType, OrgType } from '../db/OrganizationTypes.js' +import mongoose from 'mongoose' +import { OrgType } from '../db/OrganizationTypes.js' import { muuidToString } from '../utils/helpers.js' -import { queryAPI, setUpServer } from '../utils/testUtils.js' -import { InMemoryDB } from '../utils/inMemoryDB.js' -import express from 'express' +import { gqlTest as it } from './fixtures/gql.fixtures.js' +import muuid from 'uuid-mongodb' describe('history API', () => { - let server: ApolloServer - let user: muuid.MUUID - let userUuid: string - let app: express.Application - let inMemoryDB: InMemoryDB - - // Mongoose models for mocking pre-existing state. - let areas: MutableAreaDataSource - let organizations: MutableOrganizationDataSource - let climbs: MutableClimbDataSource - - beforeAll(async () => { - ({ server, inMemoryDB, app } = await setUpServer()) - // Auth0 serializes uuids in "relaxed" mode, resulting in this hex string format - // "59f1d95a-627d-4b8c-91b9-389c7424cb54" instead of base64 "WfHZWmJ9S4yRuTicdCTLVA==". - user = muuid.mode('relaxed').v4() - userUuid = muuidToString(user) - }) - - beforeEach(async () => { - await inMemoryDB.clear() - areas = MutableAreaDataSource.getInstance() - organizations = MutableOrganizationDataSource.getInstance() - climbs = MutableClimbDataSource.getInstance() - }) - - afterAll(async () => { - await server.stop() - await inMemoryDB.close() - }) - describe('queries', () => { const FRAGMENT_CHANGE_HISTORY = ` fragment ChangeHistoryFields on History { @@ -86,39 +49,43 @@ describe('history API', () => { } ` - let usa: AreaType - let ca: AreaType - let alphaOrg: OrganizationType - let climbIds: string[] - - it('queries recent change history successfully', async () => { + it('queries recent change history successfully', async ({ user, userUuid, query, climbs, organizations, area, country }) => { // Make changes to be tracked. - usa = await areas.addCountry('usa') - ca = await areas.addArea(user, 'CA', usa.metadata.area_id) const alphaFields = { displayName: 'Alpha OpenBeta Club', - associatedAreaIds: [usa.metadata.area_id], + associatedAreaIds: [country.metadata.area_id], email: 'admin@alphaopenbeta.com' } - alphaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, alphaFields) - climbIds = await climbs.addOrUpdateClimbs(user, ca.metadata.area_id, [{ name: 'Alpha Climb' }]) + + const alphaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, alphaFields) + const climbIds = await climbs.addOrUpdateClimbs(user, area.metadata.area_id, [{ name: 'Alpha Climb' }]) // Query for changes and ensure they are tracked. - const resp = await queryAPI({ + const resp = await query({ query: QUERY_RECENT_CHANGE_HISTORY, variables: { filter: {} }, - userUuid, - app + userUuid }) + expect(resp.statusCode).toBe(200) const histories = resp.body.data.getChangeHistory - expect(histories.length).toBe(3) - // Latest change first - // Note: addCountry is not captured by history. - const [climbChange, orgChange, areaChange] = histories + await new Promise((resolve) => setTimeout(resolve, 500)) + const climb = await climbs.findOneClimbByMUUID(muuid.from(climbIds[0])) + + assert(climb) + assert(climb?._change?.historyId) + assert(area._change?.historyId) + assert(alphaOrg._change?.historyId) + + const areaChange = histories.find(item => area._change?.historyId.equals(new mongoose.Types.ObjectId(item.id))) + const orgChange = histories.find(item => alphaOrg._change?.historyId.equals(new mongoose.Types.ObjectId(item.id))) + const climbChange = histories.find(item => climb?._change?.historyId.equals(new mongoose.Types.ObjectId(item.id))) + + assert(climbChange) + assert(orgChange) + assert(areaChange) - expect(climbChange.operation).toBe('updateClimb') expect(climbChange.editedBy).toBe(userUuid) /** @@ -132,7 +99,7 @@ describe('history API', () => { const insertChange = climbChange.changes.filter(c => c.dbOp === 'insert')[0] const updateChange = climbChange.changes.filter(c => c.dbOp === 'update')[0] expect(insertChange.fullDocument.uuid).toBe(climbIds[0]) - expect(updateChange.fullDocument.uuid).toBe(muuidToString(ca.metadata.area_id)) + expect(updateChange.fullDocument.uuid).toBe(muuidToString(area.metadata.area_id)) expect(orgChange.operation).toBe('addOrganization') expect(orgChange.editedBy).toBe(userUuid) diff --git a/src/__tests__/organizations.ts b/src/__tests__/organizations.ts index f3dd931d..d6b8eb8c 100644 --- a/src/__tests__/organizations.ts +++ b/src/__tests__/organizations.ts @@ -1,58 +1,53 @@ -import { ApolloServer } from '@apollo/server' -import muuid from 'uuid-mongodb' -import MutableAreaDataSource from '../model/MutableAreaDataSource.js' -import MutableOrganizationDataSource from '../model/MutableOrganizationDataSource.js' -import { AreaType } from '../db/AreaTypes.js' import { OperationType, OrganizationEditableFieldsType, OrganizationType, OrgType } from '../db/OrganizationTypes.js' import ChangeLogDataSource from '../model/ChangeLogDataSource.js' -import { queryAPI, setUpServer } from '../utils/testUtils.js' import { muuidToString } from '../utils/helpers.js' import { validate as validateMuuid } from 'uuid' -import { InMemoryDB } from '../utils/inMemoryDB.js' -import express from 'express' - -describe('organizations API', () => { - let server: ApolloServer - let user: muuid.MUUID - let userUuid: string - let app: express.Application - let inMemoryDB: InMemoryDB - - // Mongoose models for mocking pre-existing state. - let areas: MutableAreaDataSource - let organizations: MutableOrganizationDataSource - let usa: AreaType - let ca: AreaType - let wa: AreaType - - beforeAll(async () => { - ({ server, inMemoryDB, app } = await setUpServer()) - // Auth0 serializes uuids in "relaxed" mode, resulting in this hex string format - // "59f1d95a-627d-4b8c-91b9-389c7424cb54" instead of base64 "WfHZWmJ9S4yRuTicdCTLVA==". - user = muuid.mode('relaxed').v4() - userUuid = muuidToString(user) - }) +import { gqlTest } from './fixtures/gql.fixtures' +import { AreaType } from '../db/AreaTypes.js' - beforeEach(async () => { - await inMemoryDB.clear() - areas = MutableAreaDataSource.getInstance() - organizations = MutableOrganizationDataSource.getInstance() - usa = await areas.addCountry('usa') - ca = await areas.addArea(user, 'CA', usa.metadata.area_id) - wa = await areas.addArea(user, 'WA', usa.metadata.area_id) - }) +interface LocalContext { + ca: AreaType + wa: AreaType + orgData: OrganizationEditableFieldsType[] + orgs: OrganizationType[] +} - afterAll(async () => { - await server?.stop() - await inMemoryDB?.close() - }) +const it = gqlTest.extend({ + ca: async ({ addArea }, use) => await use(await addArea()), + wa: async ({ addArea }, use) => await use(await addArea()), + orgData: async ({ wa, ca, task }, use) => { + await use([ + { + displayName: `${task.id} Alpha OpenBeta Club`, + associatedAreaIds: [ca.metadata.area_id, wa.metadata.area_id], + email: 'admin@alphaopenbeta.com', + facebookLink: 'https://www.facebook.com/alphaopenbeta', + instagramLink: 'https://www.instagram.com/alphaopenbeta', + hardwareReportLink: 'https://alphaopenbeta.com/reporthardware' + }, + { + displayName: `${task.id} Delta OpenBeta Club`, + email: 'admin@deltaopenbeta.com' + }, + { + displayName: `${task.id}Delta Gamma OpenBeta Club`, + description: 'We are an offshoot of the delta club.\nSee our website for more details.', + excludedAreaIds: [wa.metadata.area_id] + } + ]) + }, + orgs: async ({ organizations, user, orgData }, use) => { + await use(await Promise.all(orgData.map(async fields => await organizations.addOrganization(user, OrgType.localClimbingOrganization, fields)))) + } +}) +describe('organizations API', () => { describe('mutations', () => { const createQuery = ` mutation addOrganization($input: AddOrganizationInput!) { organization: addOrganization(input: $input) { orgId - orgType + orgType displayName associatedAreaIds excludedAreaIds @@ -81,14 +76,14 @@ describe('organizations API', () => { } ` - it('creates and updates an organization', async () => { - const createResponse = await queryAPI({ + it('creates and updates an organization', async ({ query, userUuid, country, addArea }) => { + const areaToExclude = await addArea() + const createResponse = await query({ query: createQuery, operationName: 'addOrganization', variables: { input: { displayName: 'Friends of Openbeta', orgType: 'LOCAL_CLIMBING_ORGANIZATION' } }, userUuid, - roles: ['user_admin'], - app + roles: ['user_admin'] }) expect(createResponse.statusCode).toBe(200) @@ -102,14 +97,14 @@ describe('organizations API', () => { expect(createResponse.body.data.organization.createdBy).toBe(userUuid) expect(createResponse.body.data.organization.updatedBy).toBe(userUuid) - const updateResponse = await queryAPI({ + const updateResponse = await query({ query: updateQuery, operationName: 'updateOrganization', variables: { input: { orgId, - associatedAreaIds: [muuidToString(usa.metadata.area_id)], - excludedAreaIds: [muuidToString(wa.metadata.area_id)], + associatedAreaIds: [muuidToString(country.metadata.area_id)], + excludedAreaIds: [muuidToString(areaToExclude.metadata.area_id)], displayName: 'Allies of Openbeta', website: 'https://alliesofopenbeta.com', email: 'admin@alliesofopenbeta.com', @@ -121,15 +116,14 @@ describe('organizations API', () => { } }, userUuid, - roles: ['user_admin'], - app + roles: ['user_admin'] }) expect(updateResponse.statusCode).toBe(200) expect(updateResponse.body.errors).toBeUndefined() const orgResult = updateResponse.body.data.organization expect(orgResult.orgId).toBe(orgId) - expect(orgResult.associatedAreaIds).toEqual([muuidToString(usa.metadata.area_id)]) - expect(orgResult.excludedAreaIds).toEqual([muuidToString(wa.metadata.area_id)]) + expect(orgResult.associatedAreaIds).toEqual([muuidToString(country.metadata.area_id)]) + expect(orgResult.excludedAreaIds).toEqual([muuidToString(areaToExclude.metadata.area_id)]) expect(orgResult.displayName).toBe('Allies of Openbeta') expect(orgResult.content.website).toBe('https://alliesofopenbeta.com') expect(orgResult.content.email).toBe('admin@alliesofopenbeta.com') @@ -161,14 +155,13 @@ describe('organizations API', () => { expect(createRecord[0].fullDocument.displayName).toBe('Friends of Openbeta') }) - it('throws an error if a non-user_admin tries to add an organization', async () => { - const response = await queryAPI({ + it('throws an error if a non-user_admin tries to add an organization', async ({ query, userUuid }) => { + const response = await query({ query: createQuery, operationName: 'addOrganization', variables: { input: { displayName: 'Friends of Openbeta', orgType: 'LOCAL_CLIMBING_ORGANIZATION' } }, userUuid, - roles: ['editor'], - app + roles: ['editor'] }) expect(response.statusCode).toBe(200) expect(response.body.data.organization).toBeNull() @@ -205,140 +198,92 @@ describe('organizations API', () => { } } ` - let alphaFields: OrganizationEditableFieldsType - let deltaFields: OrganizationEditableFieldsType - let gammaFields: OrganizationEditableFieldsType - let alphaOrg: OrganizationType - let deltaOrg: OrganizationType - let gammaOrg: OrganizationType - - beforeEach(async () => { - alphaFields = { - displayName: 'Alpha OpenBeta Club', - associatedAreaIds: [ca.metadata.area_id, wa.metadata.area_id], - email: 'admin@alphaopenbeta.com', - facebookLink: 'https://www.facebook.com/alphaopenbeta', - instagramLink: 'https://www.instagram.com/alphaopenbeta', - hardwareReportLink: 'https://alphaopenbeta.com/reporthardware' - } - alphaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, alphaFields) - .then((res: OrganizationType | null) => { - if (res === null) throw new Error('Failure mocking organization.') - return res - }) - deltaFields = { - displayName: 'Delta OpenBeta Club', - email: 'admin@deltaopenbeta.com' - } - deltaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, deltaFields) - .then((res: OrganizationType | null) => { - if (res === null) throw new Error('Failure mocking organization.') - return res - }) - - gammaFields = { - displayName: 'Delta Gamma OpenBeta Club', - description: 'We are an offshoot of the delta club.\nSee our website for more details.', - excludedAreaIds: [wa.metadata.area_id] - } - gammaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, gammaFields) - .then((res: OrganizationType | null) => { - if (res === null) throw new Error('Failure mocking organization.') - return res - }) - }) - - it('retrieves an organization with an MUUID', async () => { - const response = await queryAPI({ + it('retrieves an organization with an MUUID', async ({ query, userUuid, orgs, orgData, ca, wa }) => { + const response = await query({ query: organizationQuery, operationName: 'organization', - variables: { input: muuidToString(alphaOrg.orgId) }, - userUuid, - app + variables: { input: muuidToString(orgs[0].orgId) }, + userUuid }) expect(response.statusCode).toBe(200) const orgResult = response.body.data.organization - expect(orgResult.orgId).toBe(muuidToString(alphaOrg.orgId)) - expect(orgResult.displayName).toBe(alphaFields.displayName) + expect(orgResult.orgId).toBe(muuidToString(orgs[0].orgId)) + expect(orgResult.displayName).toBe(orgs[0].displayName) expect(orgResult.associatedAreaIds.sort()).toEqual([muuidToString(ca.metadata.area_id), muuidToString(wa.metadata.area_id)].sort()) - expect(orgResult.content.email).toBe(alphaFields.email) - expect(orgResult.content.instagramLink).toBe(alphaFields.instagramLink) - expect(orgResult.content.facebookLink).toBe(alphaFields.facebookLink) - expect(orgResult.content.hardwareReportLink).toBe(alphaFields.hardwareReportLink) + expect(orgResult.content.email).toBe(orgData[0].email) + expect(orgResult.content.instagramLink).toBe(orgData[0].instagramLink) + expect(orgResult.content.facebookLink).toBe(orgData[0].facebookLink) + expect(orgResult.content.hardwareReportLink).toBe(orgData[0].hardwareReportLink) }) - it('retrieves organizations using an exactMatch displayName filter', async () => { - const response = await queryAPI({ + it('retrieves organizations using an exactMatch displayName filter', async ({ query, userUuid, orgs }) => { + const response = await query({ query: organizationsQuery, operationName: 'organizations', - variables: { filter: { displayName: { match: 'Delta OpenBeta Club', exactMatch: true } } }, - userUuid, - app + variables: { filter: { displayName: { match: orgs[1].displayName, exactMatch: true } } }, + userUuid }) expect(response.statusCode).toBe(200) const dataResult = response.body.data.organizations expect(dataResult.length).toBe(1) - expect(dataResult[0].orgId).toBe(muuidToString(deltaOrg.orgId)) + expect(dataResult[0].orgId).toBe(muuidToString(orgs[1].orgId)) }) - it('retrieves organizations using a non-exactMatch displayName filter', async () => { - const response = await queryAPI({ + it('retrieves organizations using a non-exactMatch displayName filter', async ({ query, userUuid, orgs, task }) => { + const response = await query({ query: organizationsQuery, operationName: 'organizations', - variables: { filter: { displayName: { match: 'delta', exactMatch: false } } }, - userUuid, - app + variables: { filter: { displayName: { match: task.id, exactMatch: false } } }, + userUuid }) expect(response.statusCode).toBe(200) const dataResult = response.body.data.organizations - expect(dataResult.length).toBe(2) - expect(dataResult.map(o => o.orgId).sort()).toEqual([muuidToString(deltaOrg.orgId), muuidToString(gammaOrg.orgId)].sort()) + expect(dataResult.map(o => o.orgId).sort()) + .toEqual([muuidToString(orgs[0].orgId), muuidToString(orgs[1].orgId), muuidToString(orgs[2].orgId)].sort()) }) - it('limits organizations returned', async () => { - const response = await queryAPI({ + it('limits organizations returned', async ({ userUuid, query }) => { + const response = await query({ query: organizationsQuery, operationName: 'organizations', variables: { limit: 1 }, - userUuid, - app + userUuid }) expect(response.statusCode).toBe(200) const dataResult = response.body.data.organizations expect(dataResult.length).toBe(1) // Three matching orgs, but only return one. }) - it('retrieves organizations using an associatedAreaIds filter', async () => { - const response = await queryAPI({ + it('retrieves organizations using an associatedAreaIds filter', async ({ userUuid, query, ca, orgs, wa }) => { + const response = await query({ query: organizationsQuery, operationName: 'organizations', variables: { filter: { associatedAreaIds: { includes: [muuidToString(ca.metadata.area_id)] } } }, - userUuid, - app + userUuid }) + // Graphql should convert `includes` from a string[] to MUUID[] expect(response.statusCode).toBe(200) const dataResult = response.body.data.organizations expect(dataResult.length).toBe(1) - expect(dataResult[0].orgId).toBe(muuidToString(alphaOrg.orgId)) + expect(dataResult[0].orgId).toBe(muuidToString(orgs[0].orgId)) }) - it('excludes organizations using an excludedAreaIds filter', async () => { - const response = await queryAPI({ + it('excludes organizations using an excludedAreaIds filter', async ({ userUuid, query, wa, orgs }) => { + const response = await query({ query: organizationsQuery, operationName: 'organizations', variables: { filter: { excludedAreaIds: { excludes: [muuidToString(wa.metadata.area_id)] } } }, - userUuid, - app + userUuid }) expect(response.statusCode).toBe(200) const dataResult = response.body.data.organizations - expect(dataResult.length).toBe(2) - expect(dataResult.map((o: OrganizationType) => o.orgId).includes(muuidToString(gammaOrg.orgId))).toBeFalsy() + // We want the org that explicitly excludes wa to be absent from this array. + expect(dataResult.map((o: OrganizationType) => o.orgId).includes(muuidToString(orgs[2].orgId))).toBeFalsy() }) }) }) diff --git a/src/__tests__/ticks.ts b/src/__tests__/ticks.ts index 32c5df61..31d0bc5a 100644 --- a/src/__tests__/ticks.ts +++ b/src/__tests__/ticks.ts @@ -1,32 +1,16 @@ -import { ApolloServer } from '@apollo/server' -import muuid from 'uuid-mongodb' -import { queryAPI, setUpServer } from '../utils/testUtils.js' -import { muuidToString } from '../utils/helpers.js' -import { TickInput } from '../db/TickTypes.js' -import TickDataSource from '../model/TickDataSource.js' -import UserDataSource from '../model/UserDataSource.js' +import { TickInput, TickType } from '../db/TickTypes.js' import { UpdateProfileGQLInput } from '../db/UserTypes.js' -import { InMemoryDB } from '../utils/inMemoryDB.js' -import express from 'express' - -describe('ticks API', () => { - let server: ApolloServer - let user: muuid.MUUID - let userUuid: string - let app: express.Application - let inMemoryDB: InMemoryDB - - // Mongoose models for mocking pre-existing state. - let ticks: TickDataSource - let users: UserDataSource - let tickOne: TickInput +import { muuidToString } from '../utils/helpers.js' +import { gqlTest } from './fixtures/gql.fixtures.js' - beforeAll(async () => { - ({ server, inMemoryDB, app } = await setUpServer()) - user = muuid.v4() - userUuid = muuidToString(user) +interface LocalContext { + singleTickData: TickInput + tick: TickType +} - tickOne = { +const it = gqlTest.extend({ + singleTickData: async ({ userUuid }, use) => { + await use({ name: 'Route One', notes: 'Nice slab', climbId: 'c76d2083-6b8f-524a-8fb8-76e1dc79833f', @@ -36,20 +20,14 @@ describe('ticks API', () => { dateClimbed: new Date('2016-07-20T17:30:15+05:30'), grade: '5.8', source: 'MP' - } - }) - - beforeEach(async () => { - ticks = TickDataSource.getInstance() - users = UserDataSource.getInstance() - await inMemoryDB.clear() - }) - - afterAll(async () => { - await server.stop() - await inMemoryDB.close() - }) + }) + }, + tick: async ({ ticks, singleTickData }, use) => { + await use(await ticks.addTick(singleTickData)) + } +}) +describe('ticks API', () => { describe('queries', () => { const userQuery = ` query userTicks($userId: MUUID, $username: String) { @@ -82,58 +60,41 @@ describe('ticks API', () => { } ` - it('queries by userId', async () => { - const userProfileInput: UpdateProfileGQLInput = { - userUuid, - username: 'cat.dog', - email: 'cat@example.com' - } - await users.createOrUpdateUserProfile(user, userProfileInput) - await ticks.addTick(tickOne) - const response = await queryAPI({ + it('queries by userId', async ({ userUuid, profile, tick, query }) => { + const response = await query({ query: userQuery, - variables: { userId: userUuid }, - userUuid, - app + variables: { userId: muuidToString(profile._id) }, + userUuid }) + expect(response.statusCode).toBe(200) const res = response.body.data.userTicks expect(res).toHaveLength(1) - expect(res[0].name).toBe(tickOne.name) + expect(res[0].name).toBe(tick.name) }) - it('queries by username', async () => { - const userProfileInput: UpdateProfileGQLInput = { - userUuid, - username: 'cat.dog', - email: 'cat@example.com' - } - await users.createOrUpdateUserProfile(user, userProfileInput) - await ticks.addTick(tickOne) - const response = await queryAPI({ + it('queries by username', async ({ userUuid, profile, tick, query }) => { + const response = await query({ query: userQuery, - variables: { username: 'cat.dog' }, - userUuid, - app + variables: { username: profile.username }, + userUuid }) expect(response.statusCode).toBe(200) const res = response.body.data.userTicks expect(res).toHaveLength(1) - expect(res[0].name).toBe(tickOne.name) + expect(res[0].name).toBe(tick.name) }) - it('queries by userId and climbId', async () => { - await ticks.addTick(tickOne) - const response = await queryAPI({ + it('queries by userId and climbId', async ({ tick, query, userUuid }) => { + const response = await query({ query: userTickByClimbQuery, - variables: { userId: userUuid, climbId: tickOne.climbId }, - userUuid, - app + variables: { userId: userUuid, climbId: tick.climbId }, + userUuid }) expect(response.statusCode).toBe(200) const res = response.body.data.userTicksByClimbId expect(res).toHaveLength(1) - expect(res[0].name).toBe(tickOne.name) + expect(res[0].name).toBe(tick.name) }) }) @@ -170,29 +131,28 @@ describe('ticks API', () => { } } ` - it('creates and updates a tick', async () => { - const createResponse = await queryAPI({ + it('creates and updates a tick', async ({ query, userUuid, singleTickData }) => { + const createResponse = await query({ query: createQuery, - variables: { input: tickOne }, + variables: { input: singleTickData }, userUuid, - roles: ['user_admin'], - app + roles: ['user_admin'] }) expect(createResponse.statusCode).toBe(200) const createTickRes = createResponse.body.data.tick - expect(createTickRes.name).toBe(tickOne.name) - expect(createTickRes.notes).toBe(tickOne.notes) - expect(createTickRes.climbId).toBe(tickOne.climbId) - expect(createTickRes.userId).toBe(tickOne.userId) - expect(createTickRes.style).toBe(tickOne.style) - expect(createTickRes.attemptType).toBe(tickOne.attemptType) - expect(createTickRes.dateClimbed).toBe(new Date(tickOne.dateClimbed).getTime()) - expect(createTickRes.grade).toBe(tickOne.grade) - expect(createTickRes.source).toBe(tickOne.source) + expect(createTickRes.name).toBe(singleTickData.name) + expect(createTickRes.notes).toBe(singleTickData.notes) + expect(createTickRes.climbId).toBe(singleTickData.climbId) + expect(createTickRes.userId).toBe(singleTickData.userId) + expect(createTickRes.style).toBe(singleTickData.style) + expect(createTickRes.attemptType).toBe(singleTickData.attemptType) + expect(createTickRes.dateClimbed).toBe(new Date(singleTickData.dateClimbed).getTime()) + expect(createTickRes.grade).toBe(singleTickData.grade) + expect(createTickRes.source).toBe(singleTickData.source) expect(createTickRes._id).toBeTruthy() - const updateResponse = await queryAPI({ + const updateResponse = await query({ query: updateQuery, variables: { input: { @@ -208,8 +168,7 @@ describe('ticks API', () => { } }, userUuid, - roles: [], // ['user_admin'], - app + roles: [] }) expect(updateResponse.statusCode).toBe(200) diff --git a/src/model/MutableAreaDataSource.ts b/src/model/MutableAreaDataSource.ts index 4eba2c64..f5377e22 100644 --- a/src/model/MutableAreaDataSource.ts +++ b/src/model/MutableAreaDataSource.ts @@ -147,6 +147,7 @@ export default class MutableAreaDataSource extends AreaDataSource { } else { // account for a few new/unofficial countries without lat,lng in the lookup table logger.warn(`Missing lnglat for ${countryName}`) + throw `Missing lnglat for ${countryName}` } await this.validateUniqueAreaName(countryName, null) diff --git a/src/model/__tests__/AreaHistoryDataSource.ts b/src/model/__tests__/AreaHistoryDataSource.ts index 6795efb0..f289f44a 100644 --- a/src/model/__tests__/AreaHistoryDataSource.ts +++ b/src/model/__tests__/AreaHistoryDataSource.ts @@ -1,159 +1,131 @@ import muuid from 'uuid-mongodb' -import MutableAreaDataSource from '../MutableAreaDataSource.js' -import ChangeLogDataSource from '../ChangeLogDataSource.js' -import { OperationType } from '../../db/AreaTypes.js' -import inMemoryDB from '../../utils/inMemoryDB.js' -import waitForExpect from 'wait-for-expect' -import { Mock } from 'vitest' +import { dataFixtures as it } from '../../__tests__/fixtures/data.fixtures.js' +import { AreaType } from '../../db/AreaTypes.js' +import { BaseChangeRecordType } from '../../db/ChangeLogType.js' describe('Area history', () => { - let areas: MutableAreaDataSource - let onChange: Mock - const testUser = muuid.v4() + it('should create history changes for an area when children get added to it', async ({ changeLog, area, addArea, country, waitForChanges }) => { + const historySettled = waitForChanges({ document: area, count: 2 }) + await addArea('nevada', { parent: area }) + await addArea('oregon', { parent: area }) + await historySettled - beforeAll(async () => { - onChange = vi.fn() - await inMemoryDB.connect(onChange) - await ChangeLogDataSource.getInstance()._testRemoveAll() - - areas = MutableAreaDataSource.getInstance() + expect(await changeLog.getAreaChangeSets(area.metadata.area_id)).toHaveLength(2) }) - afterAll(async () => { - try { - await inMemoryDB.close() - } catch (e) { - console.log('closing mongoose', e) - } - }) + it('should properly seperate unrelated histories', async ({ changeLog, area, addArea, waitForChanges }) => { + const mainAreaHistory = waitForChanges({ document: area, count: 2 }) + await Promise.all([ + addArea(undefined, { parent: area }), + addArea(undefined, { parent: area }) + ]) + await mainAreaHistory - beforeEach(async () => { - await ChangeLogDataSource.getInstance()._testRemoveAll() - onChange.mockClear() + const randomHistory = await changeLog.getAreaChangeSets(muuid.v4()) + expect(randomHistory).toHaveLength(0) }) - it('should create history records for new subareas', async () => { - const usa = await areas.addCountry('usa') - const newArea = await areas.findOneAreaByUUID(usa.metadata.area_id) - expect(newArea.area_name).toEqual(usa.area_name) + it('should return change sets in most recent order', async ({ changeLog, area, addArea, areas, waitForChanges, user }) => { + const mainAreaHistory = waitForChanges({ document: area, count: 2 }) + const child = await addArea(undefined, { parent: area }) + await areas.deleteArea(user, child.metadata.area_id) - const or = await areas.addArea(testUser, 'oregon', usa.metadata.area_id) - const nv = await areas.addArea(testUser, 'nevada', usa.metadata.area_id) + await mainAreaHistory - expect(nv?._id).toBeTruthy() - expect(or?._id).toBeTruthy() + const changeSets = await changeLog.getAreaChangeSets(area.metadata.area_id) - await waitForExpect(() => expect(onChange).toHaveBeenCalledTimes(5)) - const areaHistory = await ChangeLogDataSource.getInstance().getAreaChangeSets() - - expect(areaHistory).toHaveLength(2) // verify changes in most recent order - expect(areaHistory[0].operation).toEqual(OperationType.addArea) - expect(areaHistory[1].operation).toEqual(OperationType.addArea) + assert(area._change?.historyId) + assert(changeSets[1].changes[0].fullDocument._change?.historyId) + expect(changeSets[0].changes[0].fullDocument._change?.prevHistoryId?.equals(changeSets[1].changes[0].fullDocument._change?.historyId)) + }) - // Verify NV history - const nvAreaHistory = areaHistory[0].changes - expect(nvAreaHistory).toHaveLength(2) + it('should create history records for new subareas', async ({ changeLog, area, addArea, country, waitForChanges }) => { + const mainAreaHistory = waitForChanges({ document: area, count: 2 }) + const nv = await addArea('nevada', { parent: area }) + await addArea('oregon', { parent: area }) - // history is shown most recent first - expect(nvAreaHistory[0].dbOp).toEqual('insert') // insert new area - expect(nvAreaHistory[0].fullDocument.area_name).toEqual(nv?.area_name) // area added to the right parent? + await mainAreaHistory + + const initialHistory = await changeLog.getAreaChangeSets(area.metadata.area_id) + const nvAreaHistory: Array> = initialHistory[1].changes // verify change history linking - expect(nvAreaHistory[0].fullDocument._change?.historyId).toEqual(areaHistory[0]._id) // should point to current change + expect(nvAreaHistory[0].fullDocument._change?.historyId.equals(initialHistory[0]._id)) // should point to current change expect(nvAreaHistory[0].fullDocument._change?.prevHistoryId).not.toBeDefined() // new document -> no previous history expect(nvAreaHistory[1].dbOp).toEqual('update') // add area to country.children[] - expect(nvAreaHistory[1].fullDocument.area_name).toEqual(usa?.area_name) + expect(nvAreaHistory[1].fullDocument.area_name).toEqual(area?.area_name) + // coco: What? I don't see where this is supposed to happen I am confused expect(nvAreaHistory[1].fullDocument.children).toHaveLength(2) expect(nvAreaHistory[1].fullDocument.children[1]).toEqual(nv?._id) // area added to parent.children[]? // verify change history linking // 2nd change record: parent (country) - expect(nvAreaHistory[1].fullDocument._change?.historyId).toEqual(areaHistory[0]._id) // should point to current change - expect(nvAreaHistory[1].fullDocument._change?.prevHistoryId).toEqual(areaHistory[1]._id) // should point to previous Add new area - - // Verify OR history - const orAreaHistory = areaHistory[1].changes - expect(orAreaHistory).toHaveLength(2) - - const randomHistory = await ChangeLogDataSource.getInstance().getAreaChangeSets(muuid.v4()) - expect(randomHistory).toHaveLength(0) + expect(nvAreaHistory[1].fullDocument._change?.historyId.equals(initialHistory[0]._id)) // should point to current change + expect(nvAreaHistory[1].fullDocument._change?.prevHistoryId?.equals(initialHistory[1]._id))// should point to previous Add new area - // Verify USA history - const usaHistory = await ChangeLogDataSource.getInstance().getAreaChangeSets(usa.metadata.area_id) - expect(usaHistory).toHaveLength(2) - expect(usaHistory[0].operation).toEqual('addArea') - expect(usaHistory[1].operation).toEqual('addArea') + // Verify parent history + const countryHistory2 = await changeLog.getAreaChangeSets(area.metadata.area_id) + expect(countryHistory2).toHaveLength(2) + expect(countryHistory2[0].operation).toEqual('addArea') + expect(countryHistory2[1].operation).toEqual('addArea') // Verify USA history links - expect(usaHistory[0].changes[0]) + expect(countryHistory2[0].changes[0]) }) - it('should record multiple Areas.setDestination() calls ', async () => { - const canada = await areas.addCountry('can') - const squamish = await areas.addArea(testUser, 'squamish', canada.metadata.area_id) + it('should record multiple Areas.setDestination() calls ', async ({ user, areas, changeLog, country, area }) => { + const areaUuid = area.metadata.area_id + await expect(areas.setDestinationFlag(user, muuid.v4(), true)).rejects.toThrow() // non-existent area id. Trx won't be recorded - expect(squamish?._id).toBeTruthy() + await areas.setDestinationFlag(user, areaUuid, true) + await areas.setDestinationFlag(user, areaUuid, false) - if (squamish != null) { - const areaUuid = squamish.metadata.area_id - await expect(areas.setDestinationFlag(testUser, muuid.v4(), true)).rejects.toThrow() // non-existent area id. Trx won't be recorded + await new Promise((resolve) => setTimeout(resolve, 200)) + const changset = await changeLog.getAreaChangeSets(areaUuid) - await areas.setDestinationFlag(testUser, areaUuid, true) - await areas.setDestinationFlag(testUser, areaUuid, false) + expect(changset).toHaveLength(3) + expect(changset[0].operation).toEqual('updateDestination') + expect(changset[1].operation).toEqual('updateDestination') + expect(changset[2].operation).toEqual('addArea') - await waitForExpect(() => expect(onChange).toHaveBeenCalledTimes(5)) - const changset = await ChangeLogDataSource.getInstance().getAreaChangeSets(areaUuid) - - expect(changset).toHaveLength(3) - expect(changset[0].operation).toEqual('updateDestination') - expect(changset[1].operation).toEqual('updateDestination') - expect(changset[2].operation).toEqual('addArea') - - expect(changset[0].changes[0].fullDocument.metadata.isDestination).toStrictEqual(false) - expect(changset[1].changes[0].fullDocument.metadata.isDestination).toStrictEqual(true) - expect(changset[2].changes[0].fullDocument.metadata.isDestination).toStrictEqual(false) // default - } + expect(changset[0].changes[0].fullDocument.metadata.isDestination).toStrictEqual(false) + expect(changset[1].changes[0].fullDocument.metadata.isDestination).toStrictEqual(true) + expect(changset[2].changes[0].fullDocument.metadata.isDestination).toStrictEqual(false) // default }) - it('should record an Areas.deleteArea() call', async () => { - const greece = await areas.addCountry('grc') - const leonidio = await areas.addArea(testUser, 'Leonidio', greece.metadata.area_id) - assert(leonidio != null) + it('should record an Areas.deleteArea() call', async ({ user, areas, changeLog, area, waitForChanges }) => { + await areas.deleteArea(user, area.metadata.area_id) + await waitForChanges({ document: area, count: 1 }) - await areas.deleteArea(testUser, leonidio.metadata.area_id) - - await waitForExpect(() => expect(onChange).toHaveBeenCalledTimes(5)) - const history = await ChangeLogDataSource.getInstance().getAreaChangeSets(leonidio.metadata.area_id) + const history = await changeLog.getAreaChangeSets(area.metadata.area_id) expect(history).toHaveLength(2) expect(history[0].operation).toEqual('deleteArea') expect(history[1].operation).toEqual('addArea') - expect(history[0].changes[0].fullDocument._id).toEqual(leonidio._id) + expect(history[0].changes[0].fullDocument._id).toEqual(area._id) }) - it('should not record a failed Areas.deleteArea() call', async () => { - const spain = await areas.addCountry('esp') - const margalef = await areas.addArea(testUser, 'margalef', spain.metadata.area_id) - - assert(margalef != null) + it('should not record a failed Areas.deleteArea() call', async ({ user, area, areas, addArea, changeLog, waitForChanges }) => { + const process = waitForChanges({ document: area, count: 2 }) + const child = await addArea(undefined, { parent: area }) + // by giving this child its own child, we can create a vioalation condition if someone were + // to try and delete + await addArea(undefined, { parent: child }) - const newChild = await areas.addArea(testUser, 'One', margalef.metadata.area_id) + await expect(async () => await areas.deleteArea(user, child.metadata.area_id)).rejects.toThrow() + await process - assert(newChild != null) - - await expect(async () => await areas.deleteArea(testUser, margalef.metadata.area_id)).rejects.toThrow() - - await waitForExpect(() => expect(onChange).toHaveBeenCalledTimes(5)) - const history = await ChangeLogDataSource.getInstance().getAreaChangeSets(spain.metadata.area_id) + const history = await changeLog.getAreaChangeSets(area.metadata.area_id) // should only have 2 entries: - // 1. Add country - // 2. Add child to country - expect(history).toHaveLength(1) + // 1. Add child + // 2. Add child to that child + expect(history).toHaveLength(2) expect(history[0].operation).toEqual('addArea') + expect(history[1].operation).toEqual('addArea') }) }) diff --git a/src/model/__tests__/AreaUtils.ts b/src/model/__tests__/AreaUtils.ts index 0f30849b..306c87eb 100644 --- a/src/model/__tests__/AreaUtils.ts +++ b/src/model/__tests__/AreaUtils.ts @@ -1,4 +1,4 @@ -describe('Test area utilities', () => { +describe.todo('Test area utilities', () => { test.todo('The name comparison code unit') test.todo('The name-uniqueness system with other side-effects stripped out') }) diff --git a/src/model/__tests__/BulkDataSource.test.ts b/src/model/__tests__/BulkDataSource.test.ts index c9e8744f..a323cb30 100644 --- a/src/model/__tests__/BulkDataSource.test.ts +++ b/src/model/__tests__/BulkDataSource.test.ts @@ -1,87 +1,59 @@ -import {ChangeStream} from 'mongodb'; -import muuid from 'uuid-mongodb'; -import ChangeLogDataSource from '../ChangeLogDataSource.js'; -import MutableClimbDataSource from '../MutableClimbDataSource.js'; import {AreaType} from '../../db/AreaTypes.js'; import {ClimbType} from '../../db/ClimbTypes.js'; -import streamListener from '../../db/edit/streamListener.js'; -import inMemoryDB from "../../utils/inMemoryDB.js"; import {isFulfilled} from "../../utils/testUtils.js"; -import BulkImportDataSource from "../BulkImportDataSource.js"; import {BulkImportAreaInputType, BulkImportResultType} from "../../db/BulkImportTypes.js"; +import { dataFixtures } from '../../__tests__/fixtures/data.fixtures.js'; + +interface LocalContext { + assertBulkImport: (...input: BulkImportAreaInputType[]) => Promise +} + +const it = dataFixtures.extend({ + assertBulkImport: async ({ climbs, user, bulkImport }, use) => { + const assertBulkImport = async (...input: BulkImportAreaInputType[]): Promise => { + const result = await bulkImport.bulkImport({ + user: user, + input: {areas: input}, + climbs + }); -describe('bulk import e2e', () => { - let bulkImport: BulkImportDataSource; - let climbs: MutableClimbDataSource; - let stream: ChangeStream; - const testUser = muuid.v4(); - - const assertBulkImport = async (...input: BulkImportAreaInputType[]): Promise => { - const result = await bulkImport.bulkImport({ - user: testUser, - input: {areas: input}, - climbs - }); - - const addedAreas = await Promise.allSettled( - result.addedAreas.map((area) => - bulkImport.findOneAreaByUUID(area.metadata.area_id) - ) - ); - const updatedAreas = await Promise.allSettled( - result.updatedAreas.map((area) => - bulkImport.findOneAreaByUUID(area.metadata.area_id) - ) - ); - const addedOrUpdatedClimbs = await Promise.allSettled( - result.addedOrUpdatedClimbs.map((climb) => climbs.findOneClimbByMUUID(climb._id)) - ); - - return { - addedAreas: addedAreas.filter(isFulfilled).map((p) => p.value), - updatedAreas: updatedAreas.filter(isFulfilled).map((p) => p.value), - addedOrUpdatedClimbs: addedOrUpdatedClimbs.filter(isFulfilled).map((p) => p.value as ClimbType), + const addedAreas = await Promise.allSettled( + result.addedAreas.map((area) => + bulkImport.findOneAreaByUUID(area.metadata.area_id) + ) + ); + const updatedAreas = await Promise.allSettled( + result.updatedAreas.map((area) => + bulkImport.findOneAreaByUUID(area.metadata.area_id) + ) + ); + const addedOrUpdatedClimbs = await Promise.allSettled( + result.addedOrUpdatedClimbs.map((climb) => climbs.findOneClimbByMUUID(climb._id)) + ); + + return { + addedAreas: addedAreas.filter(isFulfilled).map((p) => p.value), + updatedAreas: updatedAreas.filter(isFulfilled).map((p) => p.value), + addedOrUpdatedClimbs: addedOrUpdatedClimbs.filter(isFulfilled).map((p) => p.value as ClimbType), + }; }; - }; - - beforeAll(async () => { - await inMemoryDB.connect() - stream = await streamListener(); - }); - - afterAll(async () => { - try { - await stream.close(); - await inMemoryDB.close() - } catch (e) { - console.log('error closing mongoose', e); - } - }); - - beforeEach(async () => { - bulkImport = BulkImportDataSource.getInstance(); - climbs = MutableClimbDataSource.getInstance(); - - await bulkImport.addCountry('us'); - }); - - afterEach(async () => { - await ChangeLogDataSource.getInstance()._testRemoveAll(); - await inMemoryDB.clear() - }); + await use(assertBulkImport) + } +}) +describe('bulk import e2e', () => { describe('adding new areas and climbs', () => { - it('should commit a new minimal area to the database', async () => { + it('should commit a new minimal area to the database', async ({ assertBulkImport, country }) => { await expect( assertBulkImport({ areaName: 'Minimal Area', - countryCode: 'us', + countryCode: country.shortCode, }) ).resolves.toMatchObject({ addedAreas: [ { area_name: 'Minimal Area', - gradeContext: 'US', + gradeContext: country.gradeContext, metadata: { leaf: false, isBoulder: false, @@ -91,12 +63,12 @@ describe('bulk import e2e', () => { }); }); - it('should rollback when one of the areas fails to import', async () => { + it('should rollback when one of the areas fails to import', async ({ assertBulkImport, country }) => { await expect( assertBulkImport( { areaName: 'Test Area', - countryCode: 'us', + countryCode: country.shortCode, }, { areaName: 'Test Area 2', @@ -105,11 +77,11 @@ describe('bulk import e2e', () => { ).rejects.toThrowError("Must provide parent Id or country code"); }); - it('should import nested areas with children', async () => { + it('should import nested areas with children', async ({ assertBulkImport, country }) => { await expect( assertBulkImport({ areaName: 'Parent Area', - countryCode: 'us', + countryCode: country.shortCode, children: [ { areaName: 'Child Area 2', @@ -118,17 +90,17 @@ describe('bulk import e2e', () => { }) ).resolves.toMatchObject({ addedAreas: [ - {area_name: 'Parent Area', gradeContext: 'US'}, - {area_name: 'Child Area 2', gradeContext: 'US'}, + {area_name: 'Parent Area', gradeContext: country.gradeContext}, + {area_name: 'Child Area 2', gradeContext: country.gradeContext}, ] as Partial[], }); }); - it('should import nested areas with children and grandchildren', async () => { + it('should import nested areas with children and grandchildren', async ({ assertBulkImport, country }) => { await expect( assertBulkImport({ areaName: 'Test Area', - countryCode: 'us', + countryCode: country.shortCode, children: [ { areaName: 'Test Area 2', @@ -144,12 +116,12 @@ describe('bulk import e2e', () => { addedAreas: [ { area_name: 'Test Area', - pathTokens: ['United States of America', 'Test Area'], + pathTokens: [country.area_name, 'Test Area'], }, { area_name: 'Test Area 2', pathTokens: [ - 'United States of America', + country.area_name, 'Test Area', 'Test Area 2', ], @@ -157,7 +129,7 @@ describe('bulk import e2e', () => { { area_name: 'Test Area 3', pathTokens: [ - 'United States of America', + country.area_name, 'Test Area', 'Test Area 2', 'Test Area 3', @@ -167,11 +139,11 @@ describe('bulk import e2e', () => { }); }); - it('should import leaf areas with climbs', async () => { + it('should import leaf areas with climbs', async ({ assertBulkImport, country }) => { await expect( assertBulkImport({ areaName: 'Test Area', - countryCode: 'us', + countryCode: country.shortCode, climbs: [ { name: 'Test Climb', @@ -184,7 +156,7 @@ describe('bulk import e2e', () => { addedAreas: [ { area_name: 'Test Area', - gradeContext: 'US', + gradeContext: country.gradeContext, metadata: { leaf: true, isBoulder: false, @@ -210,16 +182,7 @@ describe('bulk import e2e', () => { }); describe('updating existing areas', () => { - let area: AreaType; - beforeEach(async () => { - const result = await assertBulkImport({ - areaName: 'Existing Area', - countryCode: 'us', - }); - area = result.addedAreas[0] as AreaType; - }); - - it('should update an existing area', async () => { + it('should update an existing area', async ({ assertBulkImport, area }) => { await expect( assertBulkImport({ uuid: area.metadata.area_id, diff --git a/src/model/__tests__/ChangeLogDS.ts b/src/model/__tests__/ChangeLogDS.ts index e722867e..5b6055a0 100644 --- a/src/model/__tests__/ChangeLogDS.ts +++ b/src/model/__tests__/ChangeLogDS.ts @@ -1,33 +1,11 @@ import muuid from 'uuid-mongodb' -import { getAreaModel, getChangeLogModel } from '../../db/index.js' -import ChangeLogDataSource from '../ChangeLogDataSource.js' +import { getChangeLogModel } from '../../db/index.js' import { OpType } from '../../db/ChangeLogType.js' import { OperationType } from '../../db/AreaTypes.js' - -import { logger } from '../../logger.js' -import inMemoryDB from '../../utils/inMemoryDB.js' +import { dbTest as it } from '../../__tests__/fixtures/mongo.fixtures.js' describe('Area history', () => { - let changeLog: ChangeLogDataSource - - beforeAll(async () => { - await inMemoryDB.connect() - - try { - await getAreaModel().collection.drop() - await getChangeLogModel().collection.drop() - } catch (e) { - logger.info('Expected exception') - } - - changeLog = ChangeLogDataSource.getInstance() - }) - - afterAll(async () => { - await inMemoryDB.close() - }) - - it('should create a change record', async () => { + it('should create a change record', async ({ changeLog }) => { const userId = muuid.v4() const op: OpType = OperationType.addCountry diff --git a/src/model/__tests__/MediaDataSource.ts b/src/model/__tests__/MediaDataSource.ts index 64bf0cca..ff578a31 100644 --- a/src/model/__tests__/MediaDataSource.ts +++ b/src/model/__tests__/MediaDataSource.ts @@ -1,10 +1,5 @@ import mongoose from 'mongoose' -import muuid, { MUUID } from 'uuid-mongodb' -import MutableMediaDataSource from '../MutableMediaDataSource.js' -import AreaDataSource from '../MutableAreaDataSource.js' -import ClimbDataSource from '../MutableClimbDataSource.js' - -import { createIndexes } from '../../db/index.js' +import muuid from 'uuid-mongodb' import { AreaType } from '../../db/AreaTypes.js' import { AddTagEntityInput, @@ -14,116 +9,75 @@ import { UserMedia, UserMediaQueryInput } from '../../db/MediaObjectTypes.js' -import { newSportClimb1 } from './MutableClimbDataSource.js' -import inMemoryDB from '../../utils/inMemoryDB.js' - -const TEST_MEDIA: MediaObjectGQLInput = { - userUuid: 'a2eb6353-65d1-445f-912c-53c6301404bd', - mediaUrl: '/u/a2eb6353-65d1-445f-912c-53c6301404bd/photo1.jpg', - width: 800, - height: 600, - format: 'jpeg', - size: 45000 -} - -describe('MediaDataSource', () => { - let media: MutableMediaDataSource - let areas: AreaDataSource - let climbs: ClimbDataSource - - let areaForTagging1: AreaType - let areaForTagging2: AreaType - let climbIdForTagging: MUUID - - let areaTag1: AddTagEntityInput - let areaTag2: AddTagEntityInput - let climbTag: AddTagEntityInput - - let testMediaObject: MediaObject - - beforeAll(async () => { - await inMemoryDB.connect() - - areas = AreaDataSource.getInstance() - climbs = ClimbDataSource.getInstance() - media = MutableMediaDataSource.getInstance() - - try { - await areas.areaModel.collection.drop() - await climbs.climbModel.collection.drop() - await media.mediaObjectModel.collection.drop() - } catch (e) { - console.log('Cleaning up db before test') - } - - await createIndexes() - - await areas.addCountry('USA') - areaForTagging1 = await areas.addArea(muuid.v4(), 'Yosemite NP', null, 'USA') - areaForTagging2 = await areas.addArea(muuid.v4(), 'Lake Tahoe', null, 'USA') - - assert(areaForTagging1 != null, 'Fail to pre-seed test areas') - assert(areaForTagging2 != null, 'Fail to pre-seed test areas') - - const rs = await climbs.addOrUpdateClimbs(muuid.v4(), areaForTagging1.metadata.area_id, [newSportClimb1]) - assert(rs != null, 'Fail to pre-seed test areas') - climbIdForTagging = muuid.from(rs[0]) +import { gqlTest } from '../../__tests__/fixtures/gql.fixtures.js' - const rs2 = await media.addMediaObjects([TEST_MEDIA]) - testMediaObject = rs2[0] +interface LocalContext { + mediaInput: MediaObjectGQLInput + mediaObject: MediaObject + otherArea: AreaType - assert(testMediaObject != null, 'fail to create test media') - - areaTag1 = { - mediaId: testMediaObject._id, - entityType: 1, - entityUuid: areaForTagging1.metadata.area_id - } - - areaTag2 = { - mediaId: testMediaObject._id, - entityType: 1, - entityUuid: areaForTagging2.metadata.area_id, - topoData: { name: 'AA', value: '1234' } - } - - climbTag = { - mediaId: testMediaObject._id, - entityType: 0, - entityUuid: climbIdForTagging - } - }) + areaTag1: AddTagEntityInput + areaTag2: AddTagEntityInput + climbTag: AddTagEntityInput +} - afterAll(async () => { - await inMemoryDB.close() +const it = gqlTest.extend({ + mediaInput: async ({ task, userUuid }, use) => await use({ + userUuid, + mediaUrl: `/u/${userUuid}/${task.id}.jpg`, + width: 800, + height: 600, + format: 'jpeg', + size: 45000 + }), + otherArea: async ({ addArea }, use) => await use(await addArea()), + mediaObject: async ({ media, mediaInput }, use) => await use((await media.addMediaObjects([mediaInput]))[0]), + + areaTag1: async ({ area, mediaObject }, use) => await use({ + mediaId: mediaObject._id, + entityType: 1, + entityUuid: area.metadata.area_id + }), + + areaTag2: async ({ otherArea, mediaObject }, use) => await use({ + mediaId: mediaObject._id, + entityType: 1, + entityUuid: otherArea.metadata.area_id, + topoData: { name: 'AA', value: '1234' } + }), + + climbTag: async ({ climb, mediaObject }, use) => await use({ + mediaId: mediaObject._id, + entityType: 0, + entityUuid: climb._id }) +}) - it('should not tag a nonexistent area', async () => { +describe('MediaDataSource', () => { + it('should not tag a nonexistent area', async ({ media, mediaObject }) => { const badAreaTag: AddTagEntityInput = { - mediaId: testMediaObject._id, + mediaId: mediaObject._id, entityType: 1, entityUuid: muuid.v4() // some random area } await expect(media.upsertEntityTag(badAreaTag)).rejects.toThrow(/area .* not found/i) }) - it('should not tag a nonexistent *climb*', async () => { + it('should not tag a nonexistent *climb*', async ({ media, mediaObject }) => { const badClimbTag: AddTagEntityInput = { - mediaId: testMediaObject._id, + mediaId: mediaObject._id, entityType: 0, entityUuid: muuid.v4() // some random climb } await expect(media.upsertEntityTag(badClimbTag)).rejects.toThrow(/climb .* not found/i) }) - it('should tag & remove an area tag', async () => { - assert(areaForTagging1 != null, 'Pre-seeded test area not found') - + it('should tag & remove an area tag', async ({ media, mediaInput, areaTag1, climbTag, area, climb }) => { // verify the number tags before test - let mediaObjects = await media.getOneUserMedia(TEST_MEDIA.userUuid, 10) + let mediaObjects = await media.getOneUserMedia(mediaInput.userUuid, 10) expect(mediaObjects[0].entityTags).toHaveLength(0) - // add 1st tag + // add 1climbNamest tag await media.upsertEntityTag(areaTag1) // add 2nd tag @@ -132,14 +86,14 @@ describe('MediaDataSource', () => { expect(tag).toMatchObject>({ targetId: climbTag.entityUuid, type: climbTag.entityType, - areaName: areaForTagging1.area_name, - ancestors: areaForTagging1.ancestors, - climbName: newSportClimb1.name, - lnglat: areaForTagging1.metadata.lnglat + areaName: area.area_name, + ancestors: area.ancestors, + climbName: climb.name, + lnglat: area.metadata.lnglat }) // verify the number tags - mediaObjects = await media.getOneUserMedia(TEST_MEDIA.userUuid, 10) + mediaObjects = await media.getOneUserMedia(mediaInput.userUuid, 10) expect(mediaObjects[0].entityTags).toHaveLength(2) // remove tag @@ -147,14 +101,14 @@ describe('MediaDataSource', () => { expect(res).toBe(true) // verify the number tags - mediaObjects = await media.getOneUserMedia(TEST_MEDIA.userUuid, 10) + mediaObjects = await media.getOneUserMedia(mediaInput.userUuid, 10) expect(mediaObjects[0].entityTags).toHaveLength(1) }) - it('should handle delete tag errors gracefully', async () => { + it('should handle delete tag errors gracefully', async ({ media, mediaObject }) => { // with invalid id format await expect(media.removeEntityTag({ - mediaId: testMediaObject._id, + mediaId: mediaObject._id, // @ts-expect-error tagId: 'abc' // bad ObjectId format })).rejects.toThrowError(/Cast to ObjectId failed/i) @@ -166,16 +120,16 @@ describe('MediaDataSource', () => { })).rejects.toThrowError(/not found/i) }) - it('should not add a duplicate tag', async () => { + it('should not add a duplicate tag', async ({ areaTag2, media }) => { const updating = { ...areaTag2, topoData: { name: 'ZZ' } } const newTag = await media.upsertEntityTag(updating) expect(newTag.targetId).toEqual(areaTag2.entityUuid) expect(newTag.topoData).toEqual(updating.topoData) }) - it('should not add media with the same url', async () => { + it('should not add media with the same url', async ({ mediaInput, media }) => { const mediaObj = { - ...TEST_MEDIA, + ...mediaInput, mediaUrl: 'photoAAA.jpg' } await media.addMediaObjects([mediaObj]) @@ -183,9 +137,9 @@ describe('MediaDataSource', () => { await expect(async () => await media.addMediaObjects([mediaObj])).rejects.toThrowError('duplicate key error collection') }) - it('should delete media', async () => { + it('should delete media', async ({ mediaInput, media }) => { const rs = await media.addMediaObjects([{ - ...TEST_MEDIA, + ...mediaInput, mediaUrl: 'u/a0ca9ebb-aa3b-4bb0-8ddd-7c8b2ed228a5/photo100.jpg' }]) @@ -197,21 +151,21 @@ describe('MediaDataSource', () => { await expect(async () => await media.deleteMediaObject(rs[0]._id)).rejects.toThrowError('not found') }) - it('should not delete media with non-empty tags', async () => { + it('should not delete media with non-empty tags', async ({ mediaInput, media, climb }) => { const rs = await media.addMediaObjects([{ - ...TEST_MEDIA, + ...mediaInput, mediaUrl: 'photo101.jpg', - entityTag: { entityType: 0, entityId: climbIdForTagging.toUUID().toString() } + entityTag: { entityType: 0, entityId: climb._id.toUUID().toString() } } ]) await expect(async () => await media.deleteMediaObject(rs[0]._id)).rejects.toThrowError('Cannot delete media object with non-empty tags.') }) - it('should return paginated media results', async () => { + it('should return paginated media results', async ({ mediaInput, media }) => { const ITEMS_PER_PAGE = 3 const MEDIA_TEMPLATE: MediaObjectGQLInput = { - ...TEST_MEDIA, + ...mediaInput, userUuid: 'a0ca9ebb-aa3b-4bb0-8ddd-7c8b2ed228a5' } diff --git a/src/model/__tests__/MutableAreaDataSource.test.ts b/src/model/__tests__/MutableAreaDataSource.test.ts index 3d48c035..f4acf1e6 100644 --- a/src/model/__tests__/MutableAreaDataSource.test.ts +++ b/src/model/__tests__/MutableAreaDataSource.test.ts @@ -1,77 +1,27 @@ import { GraphQLError } from "graphql" -import { getAreaModel, createIndexes } from "../../db" -import inMemoryDB from "../../utils/inMemoryDB" -import MutableAreaDataSource from "../MutableAreaDataSource" -import muid, { MUUID } from 'uuid-mongodb' +import muid from 'uuid-mongodb' import { AreaType, OperationType } from "../../db/AreaTypes" import { ChangeRecordMetadataType } from "../../db/ChangeLogType" - +import { dataFixtures as test } from "../../__tests__/fixtures/data.fixtures" describe("Test area mutations", () => { - let areas: MutableAreaDataSource - let rootCountry: AreaType - let areaCounter = 0 - const testUser = muid.v4() - - async function addArea(name?: string, extra?: Partial<{ leaf: boolean, boulder: boolean, parent: MUUID | AreaType}>) { - function isArea(x: any): x is AreaType { - return typeof x.metadata?.area_id !== 'undefined' - } - - areaCounter += 1 - if (name === undefined || name === 'test') { - name = process.uptime().toString() + '-' + areaCounter.toString() - } - - let parent: MUUID | undefined = undefined - if (extra?.parent) { - if (isArea(extra.parent)) { - parent = extra.parent.metadata?.area_id - } else { - parent = extra.parent - } - } - - return areas.addArea( - testUser, - name, - parent ?? rootCountry.metadata.area_id, - undefined, - undefined, - extra?.leaf, - extra?.boulder - ) - } - - beforeAll(async () => { - await inMemoryDB.connect() - await getAreaModel().collection.drop() - await createIndexes() - - areas = MutableAreaDataSource.getInstance() - // We need a root country, and it is beyond the scope of these tests - rootCountry = await areas.addCountry("USA") - }) - - afterAll(inMemoryDB.close) - describe("Add area param cases", () => { - test("Add a simple area with no specifications using a parent UUID", () => areas - .addArea(testUser, 'Texas2', rootCountry.metadata.area_id) + test("Add a simple area with no specifications using a parent UUID", ({ areas, user, country }) => areas + .addArea(user, 'Texas2', country.metadata.area_id) .then(area => { expect(area?._change).toMatchObject({ - user: testUser, + user: user, operation: OperationType.addArea, } satisfies Partial) })) test("Add an area with an unknown UUID parent should fail", - async () => await expect(() => areas.addArea(testUser, 'Texas', muid.v4())).rejects.toThrow()) + async ({ areas, user, country }) => await expect(() => areas.addArea(user, 'Texas', muid.v4())).rejects.toThrow()) - test("Add a simple area with no specifications using a country code", () => areas.addArea(testUser, 'Texas part 2', null, 'USA') - .then(texas => areas.addArea(testUser, 'Texas Child', texas.metadata.area_id))) + test("Add a simple area with no specifications using a country code", ({ areas, user, country }) => areas.addArea(user, 'Texas part 2', null, country.shortCode) + .then(texas => areas.addArea(user, 'Texas Child', texas.metadata.area_id))) - test("Add a simple area, then specify a new child one level deep", () => addArea('California') + test("Add a simple area, then specify a new child one level deep", ({ areas, addArea }) => addArea('California') .then(async parent => { let child = await addArea('Child', { parent }) expect(child).toMatchObject({ area_name: 'Child' }) @@ -79,14 +29,14 @@ describe("Test area mutations", () => { expect(parentCheck?.children ?? []).toContainEqual(child._id) })) - test("Add a leaf area", () => addArea('Somewhere').then(parent => addArea('Child', { leaf: true, parent })) + test("Add a leaf area", ({ areas, addArea }) => addArea('Somewhere').then(parent => addArea('Child', { leaf: true, parent })) .then(async leaf => { expect(leaf).toMatchObject({ metadata: { leaf: true }}) let area = await areas.areaModel.findById(leaf._id) expect(area).toMatchObject({ metadata: { leaf: true }}) })) - test("Add a leaf area that is a boulder", () => addArea('Maine') + test("Add a leaf area that is a boulder", ({ addArea }) => addArea('Maine') .then(parent => addArea('Child', {leaf: true, boulder: true, parent} )) .then(area => { expect(area).toMatchObject({ @@ -97,7 +47,7 @@ describe("Test area mutations", () => { } satisfies Partial & { metadata: Partial}>) })) - test("Add a NON-leaf area that is a boulder", () => addArea('Wisconcin') + test("Add a NON-leaf area that is a boulder", ({ addArea }) => addArea('Wisconcin') .then(texas => addArea('Child', { leaf: false, boulder: true })) .then(area => { expect(area).toMatchObject({ @@ -110,19 +60,19 @@ describe("Test area mutations", () => { } satisfies Partial & { metadata: Partial}>) })) - test("Adding a child to a leaf area should cause it to become a normal area", () => addArea() + test("Adding a child to a leaf area should cause it to become a normal area", ({ addArea }) => addArea() .then(parent => Promise.all(new Array(5).map(() => addArea('test', { leaf: true, parent } )))) .then(([leaf]) => leaf) .then(leaf => addArea('test', { parent: leaf })) .then(leaf => expect(leaf).toMatchObject({ metadata: { leaf: false }}))) - test("area names should be unique in their parent context", () => addArea('test').then(async parent => { + test("area names should be unique in their parent context", ({ areas, user, country, addArea }) => addArea('test').then(async parent => { await addArea('Big ol boulder', { parent }) await expect(() => addArea('Big ol boulder', { parent })).rejects.toThrow(GraphQLError) })) }) - test("Delete Area", () => addArea("test").then(area => areas.deleteArea(testUser, area.metadata.area_id)).then(async deleted => { + test("Delete Area", ({ areas, user, addArea }) => addArea("test").then(area => areas.deleteArea(user, area.metadata.area_id)).then(async deleted => { expect(deleted).toBeDefined() // TODO: this test fails based on the data returned, which appears to omit the _deleting field. let d = await areas.areaModel.findById(deleted?._id) @@ -132,58 +82,58 @@ describe("Test area mutations", () => { expect(d?._deleting).toBeDefined() })) - test("Delete Area that is already deleted should throw", () => addArea("test") - .then(area => areas.deleteArea(testUser, area.metadata.area_id)) + test("Delete Area that is already deleted should throw", ({ areas, user, addArea }) => addArea("test") + .then(area => areas.deleteArea(user, area.metadata.area_id)) .then(async area => { expect(area).not.toBeNull() - await expect(() => areas.deleteArea(testUser, area!.metadata.area_id)).rejects.toThrow() + await expect(() => areas.deleteArea(user, area!.metadata.area_id)).rejects.toThrow() })) describe("Area update cases", () => { - test("Updating an area should superficially pass", () => addArea('test').then(area => areas.updateArea(testUser, area.metadata.area_id, { areaName: `New Name! ${process.uptime()}`}))) - test("Updating an area should produce a change entry in the changelog", () => addArea('test') - .then(area => areas.updateArea(testUser, area.metadata.area_id, { areaName: process.uptime().toString() })) + test("Updating an area should superficially pass", ({ areas, user, addArea }) => addArea('test').then(area => areas.updateArea(user, area.metadata.area_id, { areaName: `New Name! ${process.uptime()}`}))) + test("Updating an area should produce a change entry in the changelog", ({ areas, user, country, addArea }) => addArea('test') + .then(area => areas.updateArea(user, area.metadata.area_id, { areaName: process.uptime().toString() })) .then(area => { expect(area?._change).toMatchObject({ - user: testUser, + user: user, operation: OperationType.updateArea, } satisfies Partial) })) - test("Area name uniqueness in its current parent context", () => addArea('test').then(async parent => { + test("Area name uniqueness in its current parent context", ({ areas, user, addArea }) => addArea('test').then(async parent => { let [area, newArea, divorcedArea] = await Promise.all([ addArea('original', { parent }), addArea('wannabe', { parent }), - addArea(undefined, { parent: rootCountry }), + addArea(), ]) await Promise.all([ // Case where an area gets changed to what it already is, which should not throw an error - areas.updateArea(testUser, area.metadata.area_id, { areaName: area.area_name }), + areas.updateArea(user, area.metadata.area_id, { areaName: area.area_name }), // name-uniqueness should not be global, so this shouldn't throw - areas.updateArea(testUser, divorcedArea.metadata.area_id, { areaName: area.area_name }), + areas.updateArea(user, divorcedArea.metadata.area_id, { areaName: area.area_name }), // if we update one of the areas to have a name for which another area already exists, we should expect this to throw. - expect(() => areas.updateArea(testUser, newArea.metadata.area_id, { areaName: area.area_name })).rejects.toThrow(GraphQLError), + expect(() => areas.updateArea(user, newArea.metadata.area_id, { areaName: area.area_name })).rejects.toThrow(GraphQLError), ]) })) }) - test("Area name uniqueness should not create a UUID shadow via deletion", () => addArea('test').then(async parent => { + test("Area name uniqueness should not create a UUID shadow via deletion", ({ areas, user, country, addArea }) => addArea('test').then(async parent => { let name = 'Big ol boulder' let big = await addArea(name, { boulder: true, parent }) - await areas.deleteArea(testUser, big.metadata.area_id) + await areas.deleteArea(user, big.metadata.area_id) await addArea(name, { boulder: true, parent }) })) - test("Area name uniqueness should not create a UUID shadow via edit of name", () => addArea('test').then(async parent => { + test("Area name uniqueness should not create a UUID shadow via edit of name", ({ areas, user, country, addArea }) => addArea('test').then(async parent => { let nameShadow = 'Big ol boulder 2' let big = await addArea(nameShadow, { boulder: true, parent }) // We change the name of the original owner of the nameshadow, and then try to add a // name claming the original name in this area structure context - await areas.updateArea(testUser, big.metadata.area_id, { areaName: "Still big ol bolder"}) + await areas.updateArea(user, big.metadata.area_id, { areaName: "Still big ol bolder"}) await addArea(nameShadow, { boulder: true, parent }) })) }) \ No newline at end of file diff --git a/src/model/__tests__/MutableClimbDataSource.ts b/src/model/__tests__/MutableClimbDataSource.ts index c8f39b98..87d7eb81 100644 --- a/src/model/__tests__/MutableClimbDataSource.ts +++ b/src/model/__tests__/MutableClimbDataSource.ts @@ -1,18 +1,9 @@ import muid from 'uuid-mongodb' -import { ChangeStream } from 'mongodb' - -import MutableClimbDataSource from '../MutableClimbDataSource.js' -import MutableAreaDataSource from '../MutableAreaDataSource.js' - -import { createIndexes, getAreaModel, getClimbModel } from '../../db/index.js' -import { logger } from '../../logger.js' import { ClimbChangeInputType, ClimbType } from '../../db/ClimbTypes.js' import { sanitizeDisciplines } from '../../GradeUtils.js' -import streamListener from '../../db/edit/streamListener.js' -import ChangeLogDataSource from '../ChangeLogDataSource.js' -import inMemoryDB from '../../utils/inMemoryDB.js' +import { dataFixtures as it } from '../../__tests__/fixtures/data.fixtures' -export const newSportClimb1: ClimbChangeInputType = { +const newSportClimb1: ClimbChangeInputType = { name: 'Cool route 1', disciplines: { sport: true @@ -24,11 +15,6 @@ export const newSportClimb1: ClimbChangeInputType = { } describe('Climb CRUD', () => { - let climbs: MutableClimbDataSource - let areas: MutableAreaDataSource - let stream: ChangeStream - const testUser = muid.v4() - const newClimbsToAdd: ClimbChangeInputType[] = [ { name: 'Sport 1', @@ -139,44 +125,16 @@ describe('Climb CRUD', () => { ] } - beforeAll(async () => { - await inMemoryDB.connect() - stream = await streamListener() - - try { - await getAreaModel().collection.drop() - await getClimbModel().collection.drop() - } catch (e) { - logger.info('Expected exception') - } - - await createIndexes() - - climbs = MutableClimbDataSource.getInstance() - areas = MutableAreaDataSource.getInstance() - await ChangeLogDataSource.getInstance()._testRemoveAll() - await areas.addCountry('fr') - }) - - afterAll(async () => { - try { - await stream.close() - await inMemoryDB.close() - } catch (e) { - console.log('closing mongoose', e) - } - }) - - it('can add new climbs', async () => { + it('can add new climbs', async ({ areas, climbs, user }) => { await areas.addCountry('usa') - const newDestination = await areas.addArea(testUser, 'California', null, 'usa') + const newDestination = await areas.addArea(user, 'California', null, 'usa') expect(newDestination).toBeTruthy() - const routesArea = await areas.addArea(testUser, 'Sport & Trad', newDestination.metadata.area_id) + const routesArea = await areas.addArea(user, 'Sport & Trad', newDestination.metadata.area_id) const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, routesArea.metadata.area_id, newClimbsToAdd) @@ -197,25 +155,25 @@ describe('Climb CRUD', () => { // California contains subareas. Should fail. await expect( - climbs.addOrUpdateClimbs(testUser, newDestination.metadata.area_id, [newBoulderProblem1]) + climbs.addOrUpdateClimbs(user, newDestination.metadata.area_id, [newBoulderProblem1]) ).rejects.toThrowError(/You can only add climbs to a crag/) // Route-only area should accept new boulder problems - await climbs.addOrUpdateClimbs(testUser, routesArea.metadata.area_id, [newBoulderProblem1]) + await climbs.addOrUpdateClimbs(user, routesArea.metadata.area_id, [newBoulderProblem1]) }) - it('can add new boulder problems', async () => { + it('can add new boulder problems', async ({ areas, climbs, user }) => { await areas.addCountry('esp') - const newDestination = await areas.addArea(testUser, 'Valencia', null, 'esp') + const newDestination = await areas.addArea(user, 'Valencia', null, 'esp') expect(newDestination).toBeTruthy() - const boulderingArea = await areas.addArea(testUser, 'Bouldering only', newDestination.metadata.area_id) + const boulderingArea = await areas.addArea(user, 'Bouldering only', newDestination.metadata.area_id) expect(boulderingArea.metadata.isBoulder).toBeFalsy() const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, boulderingArea.metadata.area_id, [newBoulderProblem1, newBoulderProblem2]) @@ -227,12 +185,10 @@ describe('Climb CRUD', () => { expect(newClimb.name).toBe(newBoulderProblem1.name) }) - it('can delete new boulder problems', async () => { - const newBoulderingArea = await areas.addArea(testUser, 'Bouldering area 1', null, 'fr') - expect(newBoulderingArea).toBeTruthy() - + it('can delete new boulder problems', async ({ areas, climbs, user, addArea }) => { + const newBoulderingArea = await addArea('Bouldering area 1') const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, newBoulderingArea.metadata.area_id, [newBoulderProblem1, newBoulderProblem2]) @@ -240,20 +196,20 @@ describe('Climb CRUD', () => { // delete a random (non-existing) climb const count0 = await climbs.deleteClimbs( - testUser, + user, newBoulderingArea.metadata.area_id, [muid.v4()]) expect(count0).toEqual(0) // try delete a correct climb and a non-existent one const count1 = await climbs.deleteClimbs( - testUser, + user, newBoulderingArea.metadata.area_id, [muid.from(newIDs[0]), muid.v4()]) // immediately delete a previously deleted climb. Should be a no op. const count2 = await climbs.deleteClimbs( - testUser, + user, newBoulderingArea.metadata.area_id, [muid.from(newIDs[0]), muid.v4()]) @@ -278,13 +234,13 @@ describe('Climb CRUD', () => { expect((areaRs.climbs[0] as ClimbType)._id.toUUID().toString()).toEqual(newIDs[1]) }) - it('handles mixed grades and disciplines correctly', async () => { + it('handles mixed grades and disciplines correctly', async ({ areas, climbs, user }) => { await areas.addCountry('can') - const newBoulderingArea = await areas.addArea(testUser, 'Bouldering area 1', null, 'can') + const newBoulderingArea = await areas.addArea(user, 'Bouldering area 1', null, 'can') expect(newBoulderingArea).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, newBoulderingArea.metadata.area_id, [{ ...newBoulderProblem1, grade: 'V3' }, // good grade { ...newBoulderProblem2, grade: '5.9' }]) // invalid grade (YDS grade for a boulder problem) @@ -298,12 +254,12 @@ describe('Climb CRUD', () => { expect(climb2?.grades).toEqual(undefined) }) - it('handles Australian grade context correctly', async () => { + it('handles Australian grade context correctly', async ({ areas, climbs, user }) => { await areas.addCountry('aus') { // A roped climbing area - const newClimbingArea = await areas.addArea(testUser, 'Climbing area 1', null, 'aus') + const newClimbingArea = await areas.addArea(user, 'Climbing area 1', null, 'aus') expect(newClimbingArea).toBeTruthy() const newclimbs = [ @@ -315,7 +271,7 @@ describe('Climb CRUD', () => { ] const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, newClimbingArea.metadata.area_id, newclimbs ) @@ -350,11 +306,11 @@ describe('Climb CRUD', () => { { // A bouldering area - const newBoulderingArea = await areas.addArea(testUser, 'Bouldering area 1', null, 'aus') + const newBoulderingArea = await areas.addArea(user, 'Bouldering area 1', null, 'aus') expect(newBoulderingArea).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, newBoulderingArea.metadata.area_id, [{ ...newBoulderProblem1, grade: 'V3' }, // good grade { ...newBoulderProblem2, grade: '23' }, // bad boulder grade @@ -373,12 +329,12 @@ describe('Climb CRUD', () => { } }) - it('handles Brazilian grade context correctly', async () => { + it('handles Brazilian grade context correctly', async ({ areas, climbs, user }) => { await areas.addCountry('bra') { // A roped climbing area - const newClimbingArea = await areas.addArea(testUser, 'Climbing area in Brazil', null, 'bra') + const newClimbingArea = await areas.addArea(user, 'Climbing area in Brazil', null, 'bra') expect(newClimbingArea).toBeTruthy() const newclimbs = [ @@ -390,7 +346,7 @@ describe('Climb CRUD', () => { ] const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, newClimbingArea.metadata.area_id, newclimbs ) @@ -425,11 +381,11 @@ describe('Climb CRUD', () => { { // A bouldering area - const newBoulderingArea = await areas.addArea(testUser, 'Bouldering area 1', null, 'bra') + const newBoulderingArea = await areas.addArea(user, 'Bouldering area 1', null, 'bra') expect(newBoulderingArea).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, newBoulderingArea.metadata.area_id, [{ ...newBoulderProblem1, grade: 'V3' }, // good grade { ...newBoulderProblem2, grade: '23' }, // bad boulder grade @@ -448,15 +404,15 @@ describe('Climb CRUD', () => { } }) - it('handles UIAA grades correctly', async () => { + it('handles UIAA grades correctly', async ({ areas, climbs, user }) => { await areas.addCountry('deu') // Assuming Germany since UIAA is dominant grading system // A roped climbing area - const newClimbingArea = await areas.addArea(testUser, 'Climbing area 1', null, 'deu') + const newClimbingArea = await areas.addArea(user, 'Climbing area 1', null, 'deu') expect(newClimbingArea).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, newClimbingArea.metadata.area_id, [{ ...newSportClimb1, grade: '6+' }, // good UIAA grade { ...newSportClimb2, grade: '7-' }, // good UIAA grade @@ -478,31 +434,28 @@ describe('Climb CRUD', () => { expect(climb4?.grades).toEqual(undefined) }) - it('can update boulder problems', async () => { - const newDestination = await areas.addArea(testUser, 'Bouldering area A100', null, 'fr') - - expect(newDestination).toBeTruthy() - + it('can update boulder problems', async ({ climbs, user, area, randomGrade, gradeSystemFor }) => { const newIDs = await climbs.addOrUpdateClimbs( - testUser, - newDestination.metadata.area_id, + user, + area.metadata.area_id, [newBoulderProblem1, newBoulderProblem2]) const actual0 = await climbs.findOneClimbByMUUID(muid.from(newIDs[0])) + assert(actual0 != null) expect(actual0).toMatchObject({ name: newBoulderProblem1.name, type: sanitizeDisciplines(newBoulderProblem1.disciplines) }) - expect(actual0?.createdBy?.toUUID().toString()).toEqual(testUser.toString()) + expect(actual0?.createdBy?.toUUID().toString()).toEqual(user.toString()) expect(actual0?.updatedBy).toBeUndefined() const changes: ClimbChangeInputType[] = [ { id: newIDs[0], name: 'new name A100', - grade: '6b', + grade: randomGrade(actual0), disciplines: sanitizeDisciplines({ bouldering: true }) }, { @@ -512,17 +465,15 @@ describe('Climb CRUD', () => { ] const otherUser = muid.v4() - - const updated = await climbs.addOrUpdateClimbs(otherUser, newDestination.metadata.area_id, changes) + const updated = await climbs.addOrUpdateClimbs(otherUser, area.metadata.area_id, changes) expect(updated).toHaveLength(2) - const actual1 = await climbs.findOneClimbByMUUID(muid.from(newIDs[0])) - - expect(actual1).toMatchObject({ + const climbInDatabase = await climbs.findOneClimbByMUUID(muid.from(newIDs[0])) + expect(climbInDatabase).toMatchObject({ name: changes[0].name, grades: { - font: changes[0].grade + [gradeSystemFor(actual0)]: changes[0].grade }, // Make sure update doesn't touch other fields type: sanitizeDisciplines(changes[0].disciplines), @@ -533,17 +484,15 @@ describe('Climb CRUD', () => { } }) - expect(actual1?.createdBy?.toUUID().toString()).toEqual(testUser.toUUID().toString()) - expect(actual1?.updatedBy?.toUUID().toString()).toEqual(otherUser.toUUID().toString()) + expect(climbInDatabase?.createdBy?.toUUID().toString()).toEqual(user.toUUID().toString()) + expect(climbInDatabase?.updatedBy?.toUUID().toString()).toEqual(otherUser.toUUID().toString()) }) - it('can update climb length, boltsCount & fa', async () => { - const newDestination = await areas.addArea(testUser, 'Sport area Z100', null, 'fr') - - expect(newDestination).toBeTruthy() + it('can update climb length, boltsCount & fa', async ({ areas, climbs, user, addArea }) => { + const newDestination = await addArea('Sport area Z100') const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, newDestination.metadata.area_id, newClimbsToAdd ) @@ -555,9 +504,10 @@ describe('Climb CRUD', () => { boltsCount: 5 } - await climbs.addOrUpdateClimbs(testUser, + await climbs.addOrUpdateClimbs(user, newDestination.metadata.area_id, - [change]) + [change] + ) const actual = await climbs.findOneClimbByMUUID(muid.from(newIDs[0])) @@ -572,17 +522,17 @@ describe('Climb CRUD', () => { }) }) - it('can add multi-pitch climbs', async () => { + it('can add multi-pitch climbs', async ({ areas, climbs, user }) => { await areas.addCountry('aut') - const newDestination = await areas.addArea(testUser, 'Some Location with Multi-Pitch Climbs', null, 'aut') + const newDestination = await areas.addArea(user, 'Some Location with Multi-Pitch Climbs', null, 'aut') expect(newDestination).toBeTruthy() - const routesArea = await areas.addArea(testUser, 'Sport & Trad Multi-Pitches', newDestination.metadata.area_id) + const routesArea = await areas.addArea(user, 'Sport & Trad Multi-Pitches', newDestination.metadata.area_id) // create new climb with individual pitches const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, routesArea.metadata.area_id, [newClimbWithPitches] ) @@ -612,13 +562,13 @@ describe('Climb CRUD', () => { }) }) - it('can update multi-pitch problems', async () => { - const newDestination = await areas.addArea(testUser, 'Some Multi-Pitch Area to be Updated', null, 'deu') + it('can update multi-pitch problems', async ({ areas, climbs, user }) => { + const newDestination = await areas.addArea(user, 'Some Multi-Pitch Area to be Updated', null, 'deu') expect(newDestination).toBeTruthy() const newIDs = await climbs.addOrUpdateClimbs( - testUser, + user, newDestination.metadata.area_id, [newClimbWithPitches] ) @@ -667,7 +617,7 @@ describe('Climb CRUD', () => { ] // update climb - await climbs.addOrUpdateClimbs(testUser, newDestination.metadata.area_id, changes) + await climbs.addOrUpdateClimbs(user, newDestination.metadata.area_id, changes) // Fetch the updated climb const updatedClimb = await climbs.findOneClimbByMUUID(muid.from(newIDs[0])) @@ -696,11 +646,11 @@ describe('Climb CRUD', () => { } // Check that the createdBy and updatedBy fields are not undefined before accessing their properties - assert(updatedClimb.createdBy != undefined) - assert(updatedClimb.updatedBy != undefined) + assert(updatedClimb.createdBy !== undefined) + assert(updatedClimb.updatedBy !== undefined) - expect(updatedClimb.createdBy.toUUID().toString()).toEqual(testUser.toString()) - expect(updatedClimb.updatedBy.toUUID().toString()).toEqual(testUser.toString()) + expect(updatedClimb.createdBy.toUUID().toString()).toEqual(user.toString()) + expect(updatedClimb.updatedBy.toUUID().toString()).toEqual(user.toString()) } }) }) diff --git a/src/model/__tests__/MutableOrganizationDataSource.ts b/src/model/__tests__/MutableOrganizationDataSource.ts index 766ede95..0c371886 100644 --- a/src/model/__tests__/MutableOrganizationDataSource.ts +++ b/src/model/__tests__/MutableOrganizationDataSource.ts @@ -1,68 +1,52 @@ import muuid from 'uuid-mongodb' -import MutableOrganizationDataSource from '../MutableOrganizationDataSource.js' -import MutableAreaDataSource from '../MutableAreaDataSource.js' -import { createIndexes, getAreaModel, getOrganizationModel } from '../../db/index.js' -import { OrganizationEditableFieldsType, OrgType } from '../../db/OrganizationTypes.js' +import { OrganizationEditableFieldsType, OrganizationType, OrgType } from '../../db/OrganizationTypes.js' import { AreaType } from '../../db/AreaTypes.js' import { muuidToString } from '../../utils/helpers.js' -import inMemoryDB from '../../utils/inMemoryDB.js' +import { dataFixtures } from '../../__tests__/fixtures/data.fixtures.js' -describe('Organization', () => { - let organizations: MutableOrganizationDataSource - let areas: MutableAreaDataSource - let usa: AreaType - let ca: AreaType - let wa: AreaType - let fullOrg: OrganizationEditableFieldsType - let emptyOrg: OrganizationEditableFieldsType - const testUser = muuid.v4() +interface LocalContext { + excludedArea: AreaType + orgData: OrganizationEditableFieldsType + organization: OrganizationType + emptyOrg: OrganizationEditableFieldsType +} - beforeAll(async () => { - await inMemoryDB.connect() - try { // Use the same fixed areas for testing so no need to drop and re-create on each test. - await getAreaModel().collection.drop() - } catch (e) { - console.log('Cleaning up area model before test', e) - } - organizations = MutableOrganizationDataSource.getInstance() - areas = MutableAreaDataSource.getInstance() - usa = await areas.addCountry('usa') - ca = await areas.addArea(testUser, 'CA', usa.metadata.area_id) - wa = await areas.addArea(testUser, 'WA', usa.metadata.area_id) - fullOrg = { - associatedAreaIds: [usa.metadata.area_id], - excludedAreaIds: [ca.metadata.area_id, wa.metadata.area_id], - displayName: 'Friends of Openbeta', - website: 'https://www.friendsofopenbeta.com', - email: 'admin@friendsofopenbeta.com', - donationLink: 'https://www.friendsofopenbeta.com/donate', - instagramLink: 'https://www.instagram.com/friendsofopenbeta', - facebookLink: 'https://www.facebook.com/friendsofopenbeta', - hardwareReportLink: 'https://www.friendsofopenbeta.com/reporthardware', - description: 'We are friends of openbeta.\nWe are a 503(B) corporation.' - } - emptyOrg = { - displayName: 'Foes of Openbeta' - } - }) +const it = dataFixtures.extend({ + excludedArea: async ({ addArea }, use) => { await use(await addArea()) }, - beforeEach(async () => { - try { - await getOrganizationModel().collection.drop() - } catch (e) { - console.log('Cleaning up organization model before test', e) - } - await createIndexes() - }) + orgData: async ({ country, excludedArea, area, task }, use) => { + await use({ + associatedAreaIds: [country.metadata.area_id], + excludedAreaIds: [excludedArea.metadata.area_id, area.metadata.area_id], + displayName: task.name, + website: `https://www.${task.id}.com`, + email: `admin@${task.id}.com`, + donationLink: `https://www.${task.id}.com/donate`, + instagramLink: `https://www.instagram.com/${task.id}`, + facebookLink: `https://www.facebook.com/${task.id}`, + hardwareReportLink: `https://www.${task.id}.com/reporthardware`, + description: `We are ${task.id}.\nWe are a 503(B) corporation.` + }) + }, - afterAll(async () => { - await inMemoryDB.close() - }) + organization: async ({ organizations, orgData, user }, use) => { + const org = await organizations.addOrganization(user, OrgType.localClimbingOrganization, orgData) + await use(org) + await organizations.deleteFromCacheById(org._id) + }, + + emptyOrg: async ({ task }, use) => { + await use({ + displayName: `Foes of ${task.id}` + }) + } +}) - it('should successfully create a document when passed valid input', async () => { - const newOrg = await organizations.addOrganization(testUser, OrgType.localClimbingOrganization, fullOrg) - const document = { ...fullOrg } +describe('Organization', () => { + it('should successfully create a document when passed valid input', async ({ organizations, orgData, user, country }) => { + const newOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, orgData) + const document = { ...orgData } expect(newOrg.displayName).toBe(document.displayName) expect(newOrg.content?.website).toBe(document.website) expect(newOrg.content?.email).toBe(document.email) @@ -71,7 +55,7 @@ describe('Organization', () => { expect(newOrg.content?.facebookLink).toBe(document.facebookLink) expect(newOrg.content?.hardwareReportLink).toBe(document.hardwareReportLink) expect(newOrg.content?.description).toBe(document.description) - expect(newOrg.associatedAreaIds.map(muuidToString)).toEqual([muuidToString(usa.metadata.area_id)]) + expect(newOrg.associatedAreaIds.map(muuidToString)).toEqual([muuidToString(country.metadata.area_id)]) expect(newOrg._change?.operation).toBe('addOrganization') expect(newOrg._change?.seq).toBe(0) @@ -79,37 +63,51 @@ describe('Organization', () => { expect(orgIdSearchRes._id).toEqual(newOrg._id) }) - it('should retrieve documents based on displayName', async () => { - const newOrg = await organizations.addOrganization(testUser, OrgType.localClimbingOrganization, fullOrg) - // Match should be case-insensitive. - const displayNameSearchCursor = await organizations.findOrganizationsByFilter({ - displayName: { - match: 'openbeta', - exactMatch: false - } + describe('should retrieve documents based on displayName', () => { + it('Should be case insensitive', async ({ organization, organizations }) => { + // Match should be case-insensitive. + const displayNameSearchCursor = await organizations.findOrganizationsByFilter({ + displayName: { + match: organization.displayName.toLocaleUpperCase(), + exactMatch: false + } + }) + const displayNameSearchRes = await displayNameSearchCursor.toArray() + expect(displayNameSearchRes).toHaveLength(1) + expect(displayNameSearchRes[0]._id).toEqual(organization._id) + }) + + it('Should match against a partial string', async ({ organization, organizations }) => { + // Match should be case-insensitive. + const displayNameSearchCursor = await organizations.findOrganizationsByFilter({ + displayName: { + match: organization.displayName.toLocaleUpperCase().slice(10, 20), + exactMatch: false + } + }) + const displayNameSearchRes = await displayNameSearchCursor.toArray() + expect(displayNameSearchRes).toHaveLength(1) + expect(displayNameSearchRes[0]._id).toEqual(organization._id) }) - const displayNameSearchRes = await displayNameSearchCursor.toArray() - expect(displayNameSearchRes).toHaveLength(1) - expect(displayNameSearchRes[0]._id).toEqual(newOrg._id) }) - it('should retrieve documents based on associatedAreaIds', async () => { - const newOrg = await organizations.addOrganization(testUser, OrgType.localClimbingOrganization, fullOrg) + it('should retrieve documents based on associatedAreaIds', async ({ organizations, orgData, user, excludedArea, area }) => { + const newOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, orgData) const document = { - associatedAreaIds: [ca.metadata.area_id, wa.metadata.area_id] + associatedAreaIds: [excludedArea.metadata.area_id, area.metadata.area_id] } - await organizations.updateOrganization(testUser, newOrg.orgId, document) - const areaIdSearchCursor = await organizations.findOrganizationsByFilter({ associatedAreaIds: { includes: [ca.metadata.area_id] } }) + await organizations.updateOrganization(user, newOrg.orgId, document) + const areaIdSearchCursor = await organizations.findOrganizationsByFilter({ associatedAreaIds: { includes: [excludedArea.metadata.area_id] } }) const areaIdSearchRes = await areaIdSearchCursor.toArray() expect(areaIdSearchRes).toHaveLength(1) expect(areaIdSearchRes[0]._id).toEqual(newOrg._id) }) describe('update', () => { - it('should succeed on valid input', async () => { - const newOrg = await organizations.addOrganization(testUser, OrgType.localClimbingOrganization, emptyOrg) - const document = { ...fullOrg } - const updatedOrg = await organizations.updateOrganization(testUser, newOrg.orgId, document) + it('should succeed on valid input', async ({ organizations, emptyOrg, user, orgData }) => { + const newOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, emptyOrg) + const document = { ...orgData } + const updatedOrg = await organizations.updateOrganization(user, newOrg.orgId, document) expect(updatedOrg).toBeDefined() assert(updatedOrg != null) @@ -131,13 +129,13 @@ describe('Organization', () => { expect(updatedOrg.updatedAt?.getTime()).toBeGreaterThan(updatedOrg.createdAt.getTime()) }) - it('should throw when an invalid area is supplied', async () => { - const newOrg = await organizations.addOrganization(testUser, OrgType.localClimbingOrganization, emptyOrg) + it('should throw when an invalid area is supplied', async ({ orgData, emptyOrg, user, organizations }) => { + const newOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, emptyOrg) const document = { - ...fullOrg, + ...orgData, associatedAreaIds: [muuid.v4()] } - await expect(organizations.updateOrganization(testUser, newOrg.orgId, document)) + await expect(organizations.updateOrganization(user, newOrg.orgId, document)) .rejects .toThrow(/Organization update error. Reason: Associated areas not found: /) }) diff --git a/src/model/__tests__/UserDataSource.ts b/src/model/__tests__/UserDataSource.ts index 584ca71a..b5e5e722 100644 --- a/src/model/__tests__/UserDataSource.ts +++ b/src/model/__tests__/UserDataSource.ts @@ -1,35 +1,14 @@ -import mongoose from 'mongoose' import muuid from 'uuid-mongodb' - -import { getUserModel } from '../../db/index.js' import UserDataSource from '../UserDataSource.js' import { UpdateProfileGQLInput } from '../../db/UserTypes.js' -import inMemoryDB from '../../utils/inMemoryDB.js' +import { dataFixtures as it } from '../../__tests__/fixtures/data.fixtures.js' describe('UserDataSource', () => { - let users: UserDataSource - - beforeAll(async () => { - await inMemoryDB.connect() - const userModel = getUserModel() - try { - await userModel.collection.drop() - } catch (e) { - console.log('Cleaning up db before test') - } - await userModel.ensureIndexes() - users = new UserDataSource({ modelOrCollection: mongoose.connection.db.collection('users') }) - }) - - afterAll(async () => { - await inMemoryDB.close() - }) - afterEach(() => { vi.clearAllMocks() }) - it('should create a new user with just username', async () => { + it('should create a new user with just username', async ({ users }) => { const userUuid = muuid.v4() const updater = muuid.v4() const input: UpdateProfileGQLInput = { @@ -52,7 +31,7 @@ describe('UserDataSource', () => { expect(u?.updatedAt.getTime()).toBeLessThan(Date.now()) }) - it('should create a new user from username and other updatable fields', async () => { + it('should create a new user from username and other updatable fields', async ({ users }) => { const updater = muuid.v4() const userUuid = muuid.v4() const username = 'new-test-profile' @@ -99,7 +78,7 @@ describe('UserDataSource', () => { }) }) - it('should require an email when creating new profile', async () => { + it('should require an email when creating new profile', async ({ users }) => { const updater = muuid.v4() const userUuid = muuid.v4() const input: UpdateProfileGQLInput = { @@ -112,7 +91,7 @@ describe('UserDataSource', () => { ).rejects.toThrowError(/Email is required/i) }) - it('should enforce a waiting period for username update', async () => { + it('should enforce a waiting period for username update', async ({ users }) => { const updater = muuid.v4() const userUuid = muuid.v4() const input: UpdateProfileGQLInput = { @@ -131,7 +110,7 @@ describe('UserDataSource', () => { ).rejects.toThrowError(/frequent update/i) }) - it('should allow username update after the waiting period', async () => { + it('should allow username update after the waiting period', async ({ users }) => { const updater = muuid.v4() const userUuid = muuid.v4() const input: UpdateProfileGQLInput = { @@ -158,7 +137,7 @@ describe('UserDataSource', () => { expect(updatedUser?.username).toEqual(newInput.username) }) - it('should reject invalid website url', async () => { + it('should reject invalid website url', async ({ users }) => { const updater = muuid.v4() const userUuid = muuid.v4() const input: UpdateProfileGQLInput = { diff --git a/src/model/__tests__/ticks.ts b/src/model/__tests__/ticks.ts index 0f52897b..d79f7598 100644 --- a/src/model/__tests__/ticks.ts +++ b/src/model/__tests__/ticks.ts @@ -1,177 +1,130 @@ import { produce } from 'immer' -import TickDataSource from '../TickDataSource.js' -import { getTickModel, getUserModel } from '../../db/index.js' -import { TickInput } from '../../db/TickTypes.js' +import { TickInput, TickType } from '../../db/TickTypes.js' +import { dataFixtures } from '../../__tests__/fixtures/data.fixtures.js' +import { muuidToString } from '../../utils/helpers.js' import muuid from 'uuid-mongodb' -import UserDataSource from '../UserDataSource.js' -import { UpdateProfileGQLInput } from '../../db/UserTypes.js' -import inMemoryDB from '../../utils/inMemoryDB.js' - -const userId = muuid.v4() - -const toTest: TickInput = { - name: 'Small Dog', - notes: 'Sandbagged', - climbId: 'c76d2083-6b8f-524a-8fb8-76e1dc79833f', - userId: userId.toUUID().toString(), - style: 'Lead', - attemptType: 'Onsight', - dateClimbed: new Date('2012-12-12'), - grade: '5.7', - source: 'MP' -} -const toTest2: TickInput = { - name: 'Sloppy Peaches', - notes: 'v sloppy', - climbId: 'b767d949-0daf-5af3-b1f1-626de8c84b2a', - userId: userId.toUUID().toString(), - style: 'Lead', - attemptType: 'Flash', - dateClimbed: new Date('2012-10-15'), - grade: '5.10', - source: 'MP' +interface LocalContext { + tickImportData: TickInput[] + tickData: TickInput + tickUpdateData: TickInput + tick: TickType } -const tickUpdate: TickInput = produce(toTest, draft => { - draft.notes = 'Not sandbagged' - draft.attemptType = 'Fell/Hung' - draft.source = 'OB' +const it = dataFixtures.extend({ + tickImportData: async ({ task, userUuid, climb }, use) => await use( + Array.from({ length: 20 }).map((_, idx) => ( + { + name: `${task.id}-${idx}`, + notes: 'Sandbagged', + climbId: muuidToString(climb._id), + userId: userUuid, + style: 'Lead', + attemptType: 'Onsight', + dateClimbed: new Date(), + grade: '5.7', + source: 'MP' + } + ))), + tickData: async ({ task, userUuid }, use) => await use({ + name: 'Small Dog', + notes: 'Sandbagged', + climbId: 'c76d2083-6b8f-524a-8fb8-76e1dc79833f', + userId: userUuid, + style: 'Lead', + attemptType: 'Onsight', + dateClimbed: new Date('2012-12-12'), + grade: '5.7', + source: 'MP' + }), + tickUpdateData: async ({ tickData }, use) => await use(produce(tickData, draft => { + draft.notes = 'Not sandbagged' + draft.attemptType = 'Fell/Hung' + draft.source = 'OB' + })), + tick: async ({ ticks, tickData }, use) => await use(await ticks.addTick(tickData)) }) -const testImport: TickInput[] = [ - toTest, toTest2, tickUpdate -] - describe('Ticks', () => { - let ticks: TickDataSource - const tickModel = getTickModel() - - let users: UserDataSource - - beforeAll(async () => { - console.log('#BeforeAll Ticks') - await inMemoryDB.connect() - - try { - await getTickModel().collection.drop() - await getUserModel().collection.drop() - } catch (e) { - console.log('Cleaning db') - } - - ticks = TickDataSource.getInstance() - users = UserDataSource.getInstance() - }) - - afterAll(async () => { - await inMemoryDB.close() - }) - - afterEach(async () => { - await getTickModel().collection.drop() - await tickModel.ensureIndexes() - }) - // test adding tick - it('should create a new tick for the associated climb', async () => { - const tick = await ticks.addTick(toTest) - const newTick = await tickModel.findOne({ userId: toTest.userId }) + it('should create a new tick for the associated climb', async ({ ticks, tickData }) => { + const tick = await ticks.addTick(tickData) + const newTick = await ticks.tickModel.findOne({ userId: tickData.userId }) expect(newTick?._id).toEqual(tick._id) }) // test updating tick - it('should update a tick and return the proper information', async () => { - const tick = await ticks.addTick(toTest) - - expect(tick).not.toBeNull() - - const newTick = await ticks.editTick({ _id: tick._id }, tickUpdate) + it('should update a tick and return the proper information', async ({ ticks, tick, tickUpdateData }) => { + const newTick = await ticks.editTick({ _id: tick._id }, tickUpdateData) expect(newTick).not.toBeNull() expect(newTick?._id).toEqual(tick._id) - expect(newTick?.notes).toEqual(tickUpdate.notes) - expect(newTick?.attemptType).toEqual(tickUpdate.attemptType) + expect(newTick?.notes).toEqual(tickUpdateData.notes) + expect(newTick?.attemptType).toEqual(tickUpdateData.attemptType) }) // test removing tick - it('should remove a tick', async () => { - const tick = await ticks.addTick(toTest) - - expect(tick).not.toBeNull() + it('should remove a tick', async ({ ticks, tick }) => { await ticks.deleteTick(tick._id) - - const newTick = await tickModel.findOne({ _id: tick._id }) - + const newTick = await ticks.tickModel.findOne({ _id: tick._id }) expect(newTick).toBeNull() }) // test importing ticks - it('should add an array of ticks', async () => { - const newTicks = await ticks.importTicks(testImport) + it('should add an array of ticks', async ({ ticks, tickImportData }) => { + const newTicks = await ticks.importTicks(tickImportData) expect(newTicks).not.toBeNull() - expect(newTicks).toHaveLength(testImport.length) + expect(newTicks).toHaveLength(tickImportData.length) - const tick1 = await tickModel.findOne({ _id: newTicks[0]._id }) + const tick1 = await ticks.tickModel.findOne({ _id: newTicks[0]._id }) expect(tick1?._id).toEqual(newTicks[0]._id) - const tick2 = await tickModel.findOne({ _id: newTicks[1]._id }) + const tick2 = await ticks.tickModel.findOne({ _id: newTicks[1]._id }) expect(tick2?._id).toEqual(newTicks[1]._id) - const tick3 = await tickModel.findOne({ _id: newTicks[2]._id }) + const tick3 = await ticks.tickModel.findOne({ _id: newTicks[2]._id }) expect(tick3?._id).toEqual(newTicks[2]._id) }) - it('should grab all ticks by userId', async () => { - const userProfileInput: UpdateProfileGQLInput = { - userUuid: userId.toUUID().toString(), - username: 'cat.dog', - email: 'cat@example.com' - } - await users.createOrUpdateUserProfile(userId, userProfileInput) - const tick = await ticks.addTick(toTest) - - expect(tick).not.toBeNull() - - const newTicks = await ticks.ticksByUser({ userId }) - - expect(newTicks.length).toEqual(1) + it('should grab all ticks by userId', async ({ ticks, tick, userUuid, profile }) => { + const newTicks = await ticks.ticksByUser({ userId: profile._id }) + newTicks.forEach(tick => expect(tick.userId).toEqual(userUuid)) + expect(newTicks[0]._id.equals(tick._id)) }) - it('should grab all ticks by userId and climbId', async () => { - const climbId = 'c76d2083-6b8f-524a-8fb8-76e1dc79833f' - const tick = await ticks.addTick(toTest) - const tick2 = await ticks.addTick(toTest2) - - expect(tick).not.toBeNull() - expect(tick2).not.toBeNull() + it('should grab all ticks by userId and climbId', async ({ ticks, tickImportData, climb, userUuid }) => { + await Promise.all([ + ticks.addTick(tickImportData[0]), + ticks.addTick(tickImportData[1]), + ticks.addTick({ ...tickImportData[1], userId: muuidToString(muuid.v4()) }) + ]) - const userClimbTicks = await ticks.ticksByUserIdAndClimb(climbId, userId.toUUID().toString()) - expect(userClimbTicks.length).toEqual(1) + const userClimbTicks = await ticks.ticksByUserIdAndClimb(muuidToString(climb._id), userUuid) + expect(userClimbTicks).toHaveLength(2) }) - it('should delete all ticks with the specified userId', async () => { - const newTicks = await ticks.importTicks(testImport) + it('should delete all ticks with the specified userId', async ({ ticks, tickImportData, userUuid }) => { + const newTicks = await ticks.importTicks(tickImportData) expect(newTicks).not.toBeNull() - expect(newTicks).toHaveLength(3) + expect(newTicks).toHaveLength(tickImportData.length) - await ticks.deleteAllTicks(userId.toUUID().toString()) - const newTick = await tickModel.findOne({ userId }) + await ticks.deleteAllTicks(userUuid) + const newTick = await ticks.tickModel.findOne({ userId: userUuid }) expect(newTick).toBeNull() }) - it('should only delete MP imports', async () => { - const MPTick = await ticks.addTick(toTest) - const OBTick = await ticks.addTick(tickUpdate) + it('should only delete MP imports', async ({ ticks, tickData, tickUpdateData, userUuid }) => { + const MPTick = await ticks.addTick(tickData) + const OBTick = await ticks.addTick(tickUpdateData) expect(MPTick).not.toBeNull() expect(OBTick).not.toBeNull() - await ticks.deleteImportedTicks(userId.toUUID().toString()) - const newTick = await tickModel.findOne({ _id: OBTick._id }) + await ticks.deleteImportedTicks(userUuid) + const newTick = await ticks.tickModel.findOne({ _id: OBTick._id }) expect(newTick?._id).toEqual(OBTick._id) expect(newTick?.notes).toEqual('Not sandbagged') }) diff --git a/src/model/__tests__/updateAreas.ts b/src/model/__tests__/updateAreas.ts index 131bfe5f..c9b9ba15 100644 --- a/src/model/__tests__/updateAreas.ts +++ b/src/model/__tests__/updateAreas.ts @@ -1,129 +1,103 @@ import muuid from 'uuid-mongodb' import { geometry } from '@turf/helpers' - -import MutableAreaDataSource from '../MutableAreaDataSource.js' -import MutableClimbDataSource from '../MutableClimbDataSource.js' -import { createIndexes, getAreaModel, getClimbModel } from '../../db/index.js' +import countries from 'i18n-iso-countries' import { AreaEditableFieldsType, UpdateSortingOrderType } from '../../db/AreaTypes.js' -import inMemoryDB from '../../utils/inMemoryDB.js' +import { dataFixtures as it } from '../../__tests__/fixtures/data.fixtures' +import { GradeContexts, gradeContextToGradeScales } from '../../GradeUtils.js' describe('Areas', () => { - let areas: MutableAreaDataSource - let climbs: MutableClimbDataSource - const testUser = muuid.v4() - - beforeAll(async () => { - await inMemoryDB.connect() - - try { - await getAreaModel().collection.drop() - await getClimbModel().collection.drop() - } catch (e) { - console.log('Cleaning up db before test', e) - } - await createIndexes() - areas = MutableAreaDataSource.getInstance() - climbs = MutableClimbDataSource.getInstance() - }) - - afterAll(async () => { - await inMemoryDB.close() + it('should create a country by Alpha-3 country code', async ({ areas, countryCode }) => { + const country = await areas.addCountry(countryCode.toLocaleLowerCase()) + const newArea = await areas.findOneAreaByUUID(country.metadata.area_id) + expect(newArea.area_name).toEqual(countries.getName(countryCode, 'en')) + expect(newArea.shortCode).toEqual(countryCode) }) - it('should create a country by Alpha-3 country code', async () => { - const spain = await areas.addCountry('esP') - const newArea = await areas.findOneAreaByUUID(spain.metadata.area_id) - expect(newArea.area_name).toEqual('Spain') - expect(newArea.shortCode).toEqual('ESP') + it('should create a country by Alpha-2 country code', async ({ areas, countryCode }) => { + const alpha2 = countries.alpha3ToAlpha2(countryCode) + assert(alpha2) + const country = await areas.addCountry(alpha2) + expect(country.area_name).toEqual(countries.getName(countryCode, 'en')) + // should be expanded to the long country code + expect(country.shortCode).toEqual(countryCode) }) - it('should create a country by Alpha-2 country code', async () => { - const country = await areas.addCountry('ch') - expect(country.area_name).toEqual('Switzerland') - expect(country.shortCode).toEqual('CHE') - }) - - it('should create a country and 2 subareas', async () => { - const canada = await areas.addCountry('can') + it('should create a country and 2 subareas', async ({ areas, user, countryCode }) => { + const country = await areas.addCountry(countryCode) // Add 1st area to the country - const bc = await areas.addArea(testUser, 'British Columbia', canada.metadata.area_id) - assert(bc != null) - assert(canada != null) + const district = await areas.addArea(user, 'British Columbia', country.metadata.area_id) + assert(district != null) + assert(country != null) - expect(canada.metadata.lnglat).not.toMatchObject(geometry('Point', [0, 0])) - expect(bc.area_name).toEqual('British Columbia') + expect(country.metadata.lnglat).not.toMatchObject(geometry('Point', [0, 0])) + expect(district.area_name).toEqual('British Columbia') - expect(bc.metadata.lnglat).toEqual(canada.metadata.lnglat) + expect(district.metadata.lnglat).toEqual(country.metadata.lnglat) - let canadaInDb = await areas.findOneAreaByUUID(canada.metadata.area_id) + let countryInDB = await areas.findOneAreaByUUID(country.metadata.area_id) - expect(canadaInDb.children.length).toEqual(1) - expect(canadaInDb.children[0]).toEqual(bc?._id) + expect(countryInDB.children.length).toEqual(1) + expect(countryInDB.children[0]).toEqual(district?._id) // Add another area to the country - const theBug = await areas.addArea(testUser, 'The Bugaboos', canada.metadata.area_id) + const province = await areas.addArea(user, 'The Bugaboos', country.metadata.area_id) - canadaInDb = await areas.findOneAreaByUUID(canada.metadata.area_id) - expect(canadaInDb.children.length).toEqual(2) - expect(canadaInDb.children[1]).toEqual(theBug?._id) + countryInDB = await areas.findOneAreaByUUID(country.metadata.area_id) + expect(countryInDB.children.length).toEqual(2) + expect(countryInDB.children[1]).toEqual(province?._id) // Verify paths and ancestors - if (theBug != null) { // make TS happy - expect(theBug.ancestors) - .toEqual(`${canada.metadata.area_id.toUUID().toString()},${theBug?.metadata.area_id.toUUID().toString()}`) - expect(theBug.pathTokens) - .toEqual([canada.area_name, theBug.area_name]) + if (province != null) { // make TS happy + expect(province.ancestors) + .toEqual(`${country.metadata.area_id.toUUID().toString()},${province?.metadata.area_id.toUUID().toString()}`) + expect(province.pathTokens) + .toEqual([country.area_name, province.area_name]) } }) - it('should allow adding child areas to empty leaf area', async () => { - let parent = await areas.addArea(testUser, 'My house', null, 'can') - await areas.updateArea(testUser, parent.metadata.area_id, { isLeaf: true, isBoulder: true }) + it('should allow adding child areas to empty leaf area', async ({ areas, user, climbs, country, area }) => { + await areas.updateArea(user, area.metadata.area_id, { isLeaf: true, isBoulder: true }) - const newClimb = await climbs.addOrUpdateClimbs(testUser, parent.metadata.area_id, [{ name: 'Big Mac' }]) + gradeContextToGradeScales[country.gradeContext] = gradeContextToGradeScales.US + const newClimb = await climbs.addOrUpdateClimbs(user, area.metadata.area_id, [{ name: 'Big Mac' }]) // Try to add a new area when there's already a climb - await expect(areas.addArea(testUser, 'Kitchen', parent.metadata.area_id)).rejects.toThrow(/Adding new areas to a leaf or boulder area is not allowed/) + await expect(areas.addArea(user, 'Kitchen', area.metadata.area_id)).rejects.toThrow('Adding new areas to a leaf or boulder area is not allowed') // Now remove the climb to see if we can add the area + await climbs.deleteClimbs(user, area.metadata.area_id, [muuid.from(newClimb[0])]) + await areas.addArea(user, 'Kitchen', area.metadata.area_id) - await climbs.deleteClimbs(testUser, parent.metadata.area_id, [muuid.from(newClimb[0])]) - await areas.addArea(testUser, 'Kitchen', parent.metadata.area_id) + // Reload the parent area + area = await areas.findOneAreaByUUID(area.metadata.area_id) - // Reload the parent - parent = await areas.findOneAreaByUUID(parent.metadata.area_id) - expect(parent.climbs).toHaveLength(0) - expect(parent.children).toHaveLength(1) + expect(area.climbs).toHaveLength(0) + expect(area.children).toHaveLength(1) // make sure leaf and boulder flag are cleared - expect(parent.metadata.leaf).toBeFalsy() - expect(parent.metadata.isBoulder).toBeFalsy() + expect(area.metadata.leaf).toBeFalsy() + expect(area.metadata.isBoulder).toBeFalsy() }) - it('should create an area using only country code (without parent id)', async () => { - const country = await areas.addCountry('za') - const area = await areas.addArea(testUser, 'Table mountain', null, 'zaf') + it('should create an area using only country code (without parent id)', async ({ areas, user, countryCode }) => { + const country = await areas.addCountry(countryCode) + const area = await areas.addArea(user, 'Table mountain', null, countryCode) const countryInDb = await areas.findOneAreaByUUID(country.metadata.area_id) expect(countryInDb.children.length).toEqual(1) expect(countryInDb.children[0]).toEqual(area?._id) }) - it('should set crag/boulder attribute when adding new areas', async () => { - let parent = await areas.addArea(testUser, 'Boulder A', null, 'can', undefined, false, true) + it('should set crag/boulder attribute when adding new areas', async ({ areas, user, country }) => { + let parent = await areas.addArea(user, 'Boulder A', country.metadata.area_id, undefined, undefined, false, true) expect(parent.metadata.isBoulder).toBe(true) expect(parent.metadata.leaf).toBe(true) - parent = await areas.addArea(testUser, 'Sport A', null, 'can', undefined, true, undefined) + parent = await areas.addArea(user, 'Sport A', country.metadata.area_id, undefined, undefined, true, undefined) expect(parent.metadata.isBoulder).toBe(false) expect(parent.metadata.leaf).toBe(true) }) - it('should update multiple fields', async () => { - await areas.addCountry('au') - const a1 = await areas.addArea(testUser, 'One', null, 'au') - - assert(a1 != null) - + it('should update multiple fields', async ({ areas, user, area }) => { // for testing area desccription is sanitized const iframeStr = '' const doc1: AreaEditableFieldsType = { @@ -132,7 +106,7 @@ describe('Areas', () => { description: `This is a cool area with some malicious code.${iframeStr}`, isDestination: true } - let a1Updated = await areas.updateArea(testUser, a1?.metadata.area_id, doc1) + let a1Updated = await areas.updateArea(user, area?.metadata.area_id, doc1) expect(a1Updated?.area_name).toEqual(doc1.areaName) expect(a1Updated?.shortCode).toEqual(doc1.shortCode) @@ -145,37 +119,28 @@ describe('Areas', () => { lat: 46.433333, lng: 11.85 } - a1Updated = await areas.updateArea(testUser, a1?.metadata.area_id, doc2) + a1Updated = await areas.updateArea(user, area?.metadata.area_id, doc2) expect(a1Updated?.metadata.lnglat).toEqual(geometry('Point', [doc2.lng, doc2.lat])) expect(a1Updated?.metadata.isDestination).toEqual(doc2.isDestination) }) - it('should not update country name and code', async () => { - const country = await areas.addCountry('lao') - assert(country != null) - - await expect(areas.updateArea(testUser, country.metadata.area_id, { areaName: 'Foo' })).rejects.toThrowError() - - // eslint-disable-next-line - await new Promise(res => setTimeout(res, 2000)) - - await expect(areas.updateArea(testUser, country.metadata.area_id, { shortCode: 'Foo' })).rejects.toThrowError() + it('should not update country name and code', async ({ areas, user, country }) => { + await expect(areas.updateArea(user, country.metadata.area_id, { areaName: 'Foo' })).rejects.toThrowError() }) - it('should delete a subarea', async () => { - const usa = await areas.addCountry('usa') - const ca = await areas.addArea(testUser, 'CA', usa.metadata.area_id) - const or = await areas.addArea(testUser, 'OR', usa.metadata.area_id) - const wa = await areas.addArea(testUser, 'WA', usa.metadata.area_id) + it('should delete a subarea', async ({ areas, user, country }) => { + const ca = await areas.addArea(user, 'CA', country.metadata.area_id) + const or = await areas.addArea(user, 'OR', country.metadata.area_id) + const wa = await areas.addArea(user, 'WA', country.metadata.area_id) assert(ca != null, 'child area is null') assert(or != null, 'child area is null') assert(wa != null, 'child area is null') - // eslint-disable-next-line - await new Promise(res => setTimeout(res, 3000)) + // + // await new Promise(res => setTimeout(res, 3000)) - let usaInDB = await areas.findOneAreaByUUID(usa.metadata.area_id) + let usaInDB = await areas.findOneAreaByUUID(country.metadata.area_id) // verify number of child areas in parent expect(usaInDB.children as any[]).toHaveLength(3) @@ -186,9 +151,9 @@ describe('Areas', () => { wa._id ]) - await areas.deleteArea(testUser, ca.metadata.area_id) + await areas.deleteArea(user, ca.metadata.area_id) - usaInDB = await areas.findOneAreaByUUID(usa.metadata.area_id) + usaInDB = await areas.findOneAreaByUUID(country.metadata.area_id) // verify child area IDs (one less than before) expect(usaInDB.children as any[]).toHaveLength(2) @@ -200,60 +165,60 @@ describe('Areas', () => { await expect(areas.findOneAreaByUUID(ca.metadata.area_id)).rejects.toThrow(/Area.*not found/) }) - it('should not delete a subarea containing children', async () => { - const gr = await areas.addCountry('grc') - const kali = await areas.addArea(testUser, 'Kalymnos', gr.metadata.area_id) + it('should not delete a subarea containing children', async ({ areas, user, countryCode }) => { + const country = await areas.addCountry(countryCode) + const province = await areas.addArea(user, 'Kalymnos', country.metadata.area_id) - assert(kali != null) + assert(province != null) - const arhi = await areas.addArea(testUser, 'Arhi', kali.metadata.area_id) + const arhi = await areas.addArea(user, 'Arhi', province.metadata.area_id) assert(arhi != null) // Try to delete 'Arhi' (expecting exception) - await expect(areas.deleteArea(testUser, kali.metadata.area_id)).rejects.toThrow('subareas not empty') + await expect(areas.deleteArea(user, province.metadata.area_id)).rejects.toThrow('subareas not empty') const arhiInDb = await areas.findOneAreaByUUID(arhi.metadata.area_id) expect(arhiInDb._id).toEqual(arhi._id) }) - it('should not create duplicate countries', async () => { - await areas.addCountry('ita') + it('should not create duplicate countries', async ({ areas, user, countryCode }) => { + await areas.addCountry(countryCode) // eslint-disable-next-line await new Promise(res => setTimeout(res, 2000)) - await expect(areas.addCountry('ita')).rejects.toThrowError('This name already exists for some other area in this parent') + await expect(areas.addCountry(countryCode)).rejects.toThrowError('This name already exists for some other area in this parent') }) - it('should not create duplicate sub-areas', async () => { - const fr = await areas.addCountry('fra') - await areas.addArea(testUser, 'Verdon Gorge', fr.metadata.area_id) - await expect(areas.addArea(testUser, 'Verdon Gorge', fr.metadata.area_id)) + it('should not create duplicate sub-areas', async ({ areas, user, countryCode }) => { + const country = await areas.addCountry(countryCode) + await areas.addArea(user, 'Verdon Gorge', country.metadata.area_id) + await expect(areas.addArea(user, 'Verdon Gorge', country.metadata.area_id)) .rejects.toThrowError('This name already exists for some other area in this parent') }) - it('should fail when adding without a parent country', async () => { - await expect(areas.addArea(testUser, 'Peak District ', null, 'GB')) + it('should fail when adding without a parent country', async ({ areas, user, climbs }) => { + await expect(areas.addArea(user, 'Peak District ', null, 'GB')) .rejects.toThrowError() }) - it('should fail when adding with a non-existent parent id', async () => { + it('should fail when adding with a non-existent parent id', async ({ areas, user, climbs }) => { const notInDb = muuid.from('abf6cb8b-8461-45c3-b46b-5997444be867') - await expect(areas.addArea(testUser, 'Land\'s End ', notInDb)) + await expect(areas.addArea(user, 'Land\'s End ', notInDb)) .rejects.toThrowError() }) - it('should fail when adding with null parents', async () => { - await expect(areas.addArea(testUser, 'Land\'s End ', null, '1q1')) + it('should fail when adding with null parents', async ({ areas, user, climbs }) => { + await expect(areas.addArea(user, 'Land\'s End ', null, '1q1')) .rejects.toThrowError() }) - it('should update areas sorting order', async () => { + it('should update areas sorting order', async ({ areas, user, climbs }) => { // Setup await areas.addCountry('MX') - const a1 = await areas.addArea(testUser, 'A1', null, 'MX') - const a2 = await areas.addArea(testUser, 'A2', null, 'MX') + const a1 = await areas.addArea(user, 'A1', null, 'MX') + const a2 = await areas.addArea(user, 'A2', null, 'MX') const change1: UpdateSortingOrderType = { areaId: a1.metadata.area_id.toUUID().toString(), @@ -265,7 +230,7 @@ describe('Areas', () => { } // Update - await areas.updateSortingOrder(testUser, [change1, change2]) + await areas.updateSortingOrder(user, [change1, change2]) // Verify const a1Actual = await areas.findOneAreaByUUID(a1.metadata.area_id) @@ -287,15 +252,15 @@ describe('Areas', () => { })) }) - it('should update self and childrens pathTokens', async () => { + it('should update self and childrens pathTokens', async ({ areas, user, climbs }) => { await areas.addCountry('JP') - const a1 = await areas.addArea(testUser, 'Parent', null, 'JP') - const b1 = await areas.addArea(testUser, 'B1', a1.metadata.area_id) - const b2 = await areas.addArea(testUser, 'B2', a1.metadata.area_id) - const c1 = await areas.addArea(testUser, 'C1', b1.metadata.area_id) - const c2 = await areas.addArea(testUser, 'C2', b1.metadata.area_id) - const c3 = await areas.addArea(testUser, 'C3', b2.metadata.area_id) - const e1 = await areas.addArea(testUser, 'E1', c3.metadata.area_id) + const a1 = await areas.addArea(user, 'Parent', null, 'JP') + const b1 = await areas.addArea(user, 'B1', a1.metadata.area_id) + const b2 = await areas.addArea(user, 'B2', a1.metadata.area_id) + const c1 = await areas.addArea(user, 'C1', b1.metadata.area_id) + const c2 = await areas.addArea(user, 'C2', b1.metadata.area_id) + const c3 = await areas.addArea(user, 'C3', b2.metadata.area_id) + const e1 = await areas.addArea(user, 'E1', c3.metadata.area_id) let a1Actual = await areas.findOneAreaByUUID(a1.metadata.area_id) expect(a1Actual).toEqual( @@ -344,7 +309,7 @@ describe('Areas', () => { const doc1: AreaEditableFieldsType = { areaName: 'Test Name' } - await areas.updateArea(testUser, a1?.metadata.area_id, doc1) + await areas.updateArea(user, a1?.metadata.area_id, doc1) // Verify a1Actual = await areas.findOneAreaByUUID(a1.metadata.area_id) diff --git a/src/server.ts b/src/server.ts index ea3e66b8..6eab9995 100644 --- a/src/server.ts +++ b/src/server.ts @@ -25,7 +25,7 @@ import BulkImportDataSource from './model/BulkImportDataSource.js' /** * Create a GraphQL server */ -export async function createServer (serve = true): Promise<{ app: express.Application, server: ApolloServer }> { +export async function createServer (): Promise<{ app: express.Application, server: ApolloServer }> { const schema = applyMiddleware( graphqlSchema, (process.env.LOCAL_DEV_BYPASS_AUTH === 'true' ? localDevBypassAuthPermissions : permissions).generate(graphqlSchema) @@ -66,8 +66,6 @@ export async function createServer (serve = true): Promise<{ app: express.Applic }) ) - if (serve) { - await new Promise(resolve => httpServer.listen({ port: 4000 }, resolve)) - } + await new Promise(resolve => httpServer.listen({ port: 4000 }, resolve)) return { app, server } } diff --git a/src/utils/inMemoryDB.ts b/src/utils/inMemoryDB.ts deleted file mode 100644 index f30e8eef..00000000 --- a/src/utils/inMemoryDB.ts +++ /dev/null @@ -1,84 +0,0 @@ -import mongoose, { ConnectOptions } from 'mongoose' -import { ChangeStream, ChangeStreamDocument, MongoClient } from 'mongodb' -import { MongoMemoryReplSet } from 'mongodb-memory-server' -import { checkVar, defaultPostConnect } from '../db/index.js' -import { logger } from '../logger.js' -import { testStreamListener } from '../db/edit/streamListener' - -/** - * In-memory Mongo replset used for testing. - * More portable than requiring user to set up Mongo in a background Docker process. - * Need a replset to faciliate transactions. - */ -let mongod: MongoMemoryReplSet -let stream: ChangeStream | undefined - -/** - * Connect to the in-memory database. - */ -export const connect = async (onChange?: (change: ChangeStreamDocument) => void): Promise => { - mongod = await MongoMemoryReplSet.create({ - // Stream listener listens on DB denoted by 'MONGO_DBNAME' env var. - replSet: { count: 1, storageEngine: 'wiredTiger', dbName: checkVar('MONGO_DBNAME') } - }) - const uri = await mongod.getUri(checkVar('MONGO_DBNAME')) - logger.info(`Connecting to in-memory database ${uri}`) - const mongooseOpts: ConnectOptions = { - autoIndex: false // Create indices using defaultPostConnect instead. - } - - await mongoose.connect(uri, mongooseOpts) - mongoose.set('debug', false) // Set to 'true' to enable verbose mode - stream = await defaultPostConnect(async () => await testStreamListener(onChange)) -} - -/** - * Drop database, close the connection and stop mongod. - */ -export const close = async (): Promise => { - await stream?.close() - await mongoose.connection.dropDatabase() - await mongoose.connection.close() - await mongod.stop() -} - -/** - * Remove all the data for all db collections. - */ -export const clear = async (): Promise => { - const collections = mongoose.connection.collections - - for (const key in collections) { - const collection = collections[key] - await collection.deleteMany({}) - } -} - -/** - * Bypass Mongoose to insert data directly into Mongo. - * Useful for inserting data that is incompatible with Mongoose schemas for migration testing. - * @param collection Name of collection for documents to be inserted into. - * @param docs Documents to be inserted into collection. - */ -const insertDirectly = async (collection: string, documents: any[]): Promise => { - const uri = await mongod.getUri(checkVar('MONGO_DBNAME')) - const client = new MongoClient(uri) - try { - const database = client.db(checkVar('MONGO_DBNAME')) - const mCollection = database.collection(collection) - const result = await mCollection.insertMany(documents) - - console.log(`${result.insertedCount} documents were inserted directly into MongoDB`) - } finally { - void client.close() - } -} - -export interface InMemoryDB { - connect: () => Promise - close: () => Promise - clear: () => Promise - insertDirectly: (collection: string, documents: any[]) => Promise -} - -export default { connect, close, clear, insertDirectly, stream } diff --git a/src/utils/testUtils.ts b/src/utils/testUtils.ts index 5efc5632..e49da079 100644 --- a/src/utils/testUtils.ts +++ b/src/utils/testUtils.ts @@ -1,78 +1,3 @@ -import jwt from 'jsonwebtoken' -import request from 'supertest' -import { ApolloServer } from '@apollo/server' -import express from 'express' - -import type { InMemoryDB } from './inMemoryDB.js' -import inMemoryDB from './inMemoryDB.js' -import { createServer } from '../server.js' - -const PORT = 4000 - -export interface QueryAPIProps { - query?: string - operationName?: string - variables?: any - userUuid?: string - roles?: string[] - port?: number - endpoint?: string - app?: express.Application - body?: any -} - -/* - * Helper function for querying the locally-served API. It mocks JWT verification - * so we can pretend to have an role we want when calling the API. - */ -export const queryAPI = async ({ - query, - operationName, - variables, - userUuid = '', - roles = [], - app, - endpoint = '/', - port = PORT -}: QueryAPIProps): Promise => { - // Avoid needing to pass in actual signed tokens. - const jwtSpy = vi.spyOn(jwt, 'verify') - jwtSpy.mockImplementation(() => { - return { - // Roles defined at https://manage.auth0.com/dashboard/us/dev-fmjy7n5n/roles - 'https://tacos.openbeta.io/roles': roles, - 'https://tacos.openbeta.io/uuid': userUuid - } - }) - - const queryObj = { query, operationName, variables } - let req = request(app ?? `http://localhost:${port}`) - .post(endpoint) - .send(queryObj) - - if (userUuid != null) { - req = req.set('Authorization', 'Bearer placeholder-jwt-see-SpyOn') - } - - return await req -} - -export interface SetUpServerReturnType { - server: ApolloServer - app: express.Application - inMemoryDB: InMemoryDB -} - -/* - * Starts Apollo server and has Mongo inMemory replset connect to it. -*/ -export const setUpServer = async (): Promise => { - await inMemoryDB.connect() - - const { app, server } = await createServer(false) - return { app, server, inMemoryDB } -} - export const isFulfilled = ( p: PromiseSettledResult ): p is PromiseFulfilledResult => p.status === 'fulfilled' diff --git a/vite.config.ts b/vite.config.ts index 986aee6d..89255528 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,13 @@ export default defineConfig({ exclude: ['**/*/fixtures'], globals: true, environment: 'node', - } + + pool: 'threads', + poolOptions: { + threads: { + isolate: false, + }, + } + }, }) diff --git a/yarn.lock b/yarn.lock index c19a69ad..538f494e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,14 +2,6 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - "@apollo/cache-control-types@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz#5da62cf64c3b4419dabfef4536b57a40c8ff0b47" @@ -168,234 +160,6 @@ resolved "https://registry.yarnpkg.com/@apollo/utils.withrequired/-/utils.withrequired-2.0.1.tgz#e72bc512582a6f26af150439f7eb7473b46ba874" integrity sha512-YBDiuAX9i1lLc6GeTy1m7DGLFn/gMnvXqlalOIMjM7DeOgIacEjjfwPqb0M1CQ2v11HhR15d1NmxJoRCfrNqcA== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== - dependencies: - "@babel/helper-validator-identifier" "^7.25.9" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/compat-data@^7.25.9": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" - integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" - integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.26.0" - "@babel/generator" "^7.26.0" - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helpers" "^7.26.0" - "@babel/parser" "^7.26.0" - "@babel/template" "^7.25.9" - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.26.0" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.25.9", "@babel/generator@^7.26.0", "@babel/generator@^7.7.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" - integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== - dependencies: - "@babel/parser" "^7.26.2" - "@babel/types" "^7.26.0" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" - integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== - dependencies: - "@babel/compat-data" "^7.25.9" - "@babel/helper-validator-option" "^7.25.9" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-module-imports@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" - integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-transforms@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" - integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" - integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== - -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" - integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== - -"@babel/helper-validator-identifier@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" - integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== - -"@babel/helper-validator-option@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" - integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== - -"@babel/helpers@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" - integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== - dependencies: - "@babel/template" "^7.25.9" - "@babel/types" "^7.26.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" - integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== - dependencies: - "@babel/types" "^7.26.0" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" - integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" - integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" - integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.2", "@babel/runtime@^7.21.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" @@ -403,40 +167,120 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.25.9", "@babel/template@^7.3.3": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" - integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== - dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/parser" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/traverse@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" - integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== - dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/generator" "^7.25.9" - "@babel/parser" "^7.25.9" - "@babel/template" "^7.25.9" - "@babel/types" "^7.25.9" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.3.3": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" - integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== "@eslint-community/eslint-utils@^4.2.0": version "4.4.1" @@ -621,246 +465,11 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== - dependencies: - "@jest/console" "^29.7.0" - "@jest/reporters" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.7.0" - jest-config "^29.7.0" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-resolve-dependencies "^29.7.0" - jest-runner "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - jest-watcher "^29.7.0" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== - dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" - -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^6.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - jest-worker "^29.7.0" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== - dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== - dependencies: - "@jest/test-result" "^29.7.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - slash "^3.0.0" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@mongodb-js/saslprep@^1.1.0", "@mongodb-js/saslprep@^1.1.9": version "1.1.9" resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz#e974bab8eca9faa88677d4ea4da8d09a52069004" @@ -957,30 +566,106 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@rollup/rollup-android-arm-eabi@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz#7f4c4d8cd5ccab6e95d6750dbe00321c1f30791e" + integrity sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ== + +"@rollup/rollup-android-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz#17ea71695fb1518c2c324badbe431a0bd1879f2d" + integrity sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA== + +"@rollup/rollup-darwin-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz#dac0f0d0cfa73e7d5225ae6d303c13c8979e7999" + integrity sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ== + +"@rollup/rollup-darwin-x64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz#8f63baa1d31784904a380d2e293fa1ddf53dd4a2" + integrity sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ== + +"@rollup/rollup-freebsd-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz#30ed247e0df6e8858cdc6ae4090e12dbeb8ce946" + integrity sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA== + +"@rollup/rollup-freebsd-x64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz#57846f382fddbb508412ae07855b8a04c8f56282" + integrity sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz#378ca666c9dae5e6f94d1d351e7497c176e9b6df" + integrity sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA== + +"@rollup/rollup-linux-arm-musleabihf@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz#a692eff3bab330d5c33a5d5813a090c15374cddb" + integrity sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg== + +"@rollup/rollup-linux-arm64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz#6b1719b76088da5ac1ae1feccf48c5926b9e3db9" + integrity sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA== + +"@rollup/rollup-linux-arm64-musl@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz#865baf5b6f5ff67acb32e5a359508828e8dc5788" + integrity sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A== + +"@rollup/rollup-linux-loongarch64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz#23c6609ba0f7fa7a7f2038b6b6a08555a5055a87" + integrity sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz#652ef0d9334a9f25b9daf85731242801cb0fc41c" + integrity sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A== + +"@rollup/rollup-linux-riscv64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz#1eb6651839ee6ebca64d6cc64febbd299e95e6bd" + integrity sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA== + +"@rollup/rollup-linux-s390x-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz#015c52293afb3ff2a293cf0936b1d43975c1e9cd" + integrity sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg== + +"@rollup/rollup-linux-x64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz#b83001b5abed2bcb5e2dbeec6a7e69b194235c1e" + integrity sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw== + +"@rollup/rollup-linux-x64-musl@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz#6cc7c84cd4563737f8593e66f33b57d8e228805b" + integrity sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g== + +"@rollup/rollup-win32-arm64-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz#631ffeee094d71279fcd1fe8072bdcf25311bc11" + integrity sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A== + +"@rollup/rollup-win32-ia32-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz#06d1d60d5b9f718e8a6c4a43f82e3f9e3254587f" + integrity sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA== + +"@rollup/rollup-win32-x64-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz#4dff5c4259ebe6c5b4a8f2c5bc3829b7a8447ff0" + integrity sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA== + "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sinonjs/commons@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" - integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -1058,39 +743,6 @@ resolved "https://registry.yarnpkg.com/@types/auth0/-/auth0-3.3.10.tgz#6c483d7ebe6e3eb1fb44d5c27aecbe49ad0b200d" integrity sha512-9tS0Y2igWxw+Dx5uCHkIUCu6tG0oRkwpE322dOJPwZMLXQMx49n/gDmUz7YJSe1iVjrWW+ffVYmlPShVIEwjkg== -"@types/babel__core@^7.1.14": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.8" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" - integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.6" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" - integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== - dependencies: - "@babel/types" "^7.20.7" - "@types/body-parser@*": version "1.19.5" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" @@ -1111,6 +763,11 @@ resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== +"@types/estree@1.0.6", "@types/estree@^1.0.0": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + "@types/express-serve-static-core@^4.17.30", "@types/express-serve-static-core@^4.17.33": version "4.19.6" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" @@ -1131,45 +788,11 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/graceful-fs@^4.1.3": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - "@types/http-errors@*": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" - integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== - -"@types/istanbul-lib-report@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" - integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" - integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^29.4.0": - version "29.5.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" - integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -1268,11 +891,6 @@ "@types/node" "*" "@types/send" "*" -"@types/stack-utils@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" - integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== - "@types/superagent@*": version "8.1.9" resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.9.tgz#28bfe4658e469838ed0bf66d898354bcab21f49f" @@ -1320,18 +938,6 @@ "@types/node" "*" "@types/webidl-conversions" "*" -"@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== - -"@types/yargs@^17.0.8": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== - dependencies: - "@types/yargs-parser" "*" - "@types/yup@0.29.13": version "0.29.13" resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.13.tgz#21b137ba60841307a3c8a1050d3bf4e63ad561e9" @@ -1426,6 +1032,65 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@vitest/expect@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.8.tgz#13fad0e8d5a0bf0feb675dcf1d1f1a36a1773bc1" + integrity sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw== + dependencies: + "@vitest/spy" "2.1.8" + "@vitest/utils" "2.1.8" + chai "^5.1.2" + tinyrainbow "^1.2.0" + +"@vitest/mocker@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.8.tgz#51dec42ac244e949d20009249e033e274e323f73" + integrity sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA== + dependencies: + "@vitest/spy" "2.1.8" + estree-walker "^3.0.3" + magic-string "^0.30.12" + +"@vitest/pretty-format@2.1.8", "@vitest/pretty-format@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.8.tgz#88f47726e5d0cf4ba873d50c135b02e4395e2bca" + integrity sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.8.tgz#b0e2dd29ca49c25e9323ea2a45a5125d8729759f" + integrity sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg== + dependencies: + "@vitest/utils" "2.1.8" + pathe "^1.1.2" + +"@vitest/snapshot@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.8.tgz#d5dc204f4b95dc8b5e468b455dfc99000047d2de" + integrity sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg== + dependencies: + "@vitest/pretty-format" "2.1.8" + magic-string "^0.30.12" + pathe "^1.1.2" + +"@vitest/spy@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.8.tgz#bc41af3e1e6a41ae3b67e51f09724136b88fa447" + integrity sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.8.tgz#f8ef85525f3362ebd37fd25d268745108d6ae388" + integrity sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA== + dependencies: + "@vitest/pretty-format" "2.1.8" + loupe "^3.1.2" + tinyrainbow "^1.2.0" + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -1475,13 +1140,6 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -1499,24 +1157,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -anymatch@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - apollo-datasource-mongodb@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/apollo-datasource-mongodb/-/apollo-datasource-mongodb-0.6.0.tgz#643e3311cff1861a11cc967eff7259d0a84342e3" @@ -1526,13 +1171,6 @@ apollo-datasource-mongodb@^0.6.0: bson "^5.4.0" dataloader "^1.4.0" -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1647,6 +1285,11 @@ asap@^2.0.0: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + async-mutex@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482" @@ -1661,11 +1304,6 @@ async-retry@^1.2.1, async-retry@^1.3.3: dependencies: retry "0.13.1" -async@^3.2.3: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1711,69 +1349,6 @@ b4a@^1.6.4: resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" integrity sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg== -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - dependencies: - "@jest/transform" "^29.7.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" - integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -1878,30 +1453,6 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0: - version "4.24.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" - integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== - dependencies: - caniuse-lite "^1.0.30001669" - electron-to-chromium "^1.5.41" - node-releases "^2.0.18" - update-browserslist-db "^1.1.1" - -bs-logger@^0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - bson@^5.4.0, bson@^5.5.0: version "5.5.1" resolved "https://registry.yarnpkg.com/bson/-/bson-5.5.1.tgz#f5849d405711a7f23acdda9a442375df858e6833" @@ -1922,11 +1473,6 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -1947,6 +1493,11 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -1971,22 +1522,23 @@ camel-case@^1.1.1: sentence-case "^1.1.1" upper-case "^1.1.1" -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0, camelcase@^6.3.0: +camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001669: - version "1.0.30001684" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz#0eca437bab7d5f03452ff0ef9de8299be6b08e16" - integrity sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ== +chai@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d" + integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" -chalk@^4.0.0, chalk@^4.0.2: +chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2016,45 +1568,16 @@ change-case@^2.3.0: upper-case "^1.1.1" upper-case-first "^1.1.0" -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -cjs-module-lexer@^1.0.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" - integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -2152,11 +1675,6 @@ content-type@~1.0.4, content-type@~1.0.5: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -2180,19 +1698,6 @@ cors@^2.8.5: object-assign "^4" vary "^1" -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-config "^29.7.0" - jest-util "^29.7.0" - prompts "^2.0.1" - cross-env@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" @@ -2200,7 +1705,7 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -2281,10 +1786,10 @@ decompress-response@^6.0.0: dependencies: mimic-response "^3.1.0" -dedent@^1.0.0: - version "1.5.3" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" - integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== deep-extend@^0.6.0: version "0.6.0" @@ -2344,11 +1849,6 @@ detect-libc@^2.0.0, detect-libc@^2.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - dezalgo@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" @@ -2362,11 +1862,6 @@ diacritics@1.3.0: resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1" integrity sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA== -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -2465,23 +1960,6 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -ejs@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" - integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== - dependencies: - jake "^10.8.5" - -electron-to-chromium@^1.5.41: - version "1.5.66" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.66.tgz#1e9b4bc7638ac02d3551eea1dbaeb0101ec5823f" - integrity sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -2613,6 +2091,11 @@ es-iterator-helpers@^1.1.0: iterator.prototype "^1.1.3" safe-array-concat "^1.1.2" +es-module-lexer@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -2645,21 +2128,40 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.5" is-symbol "^1.0.4" -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -2873,11 +2375,6 @@ espree@^9.6.0, espree@^9.6.1: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - esquery@^1.4.2: version "1.6.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" @@ -2902,6 +2399,13 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -2917,41 +2421,15 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -expect@^29.0.0, expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" +expect-type@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75" + integrity sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA== express@^4.18.2, express@^4.21.1: version "4.21.1" @@ -3021,7 +2499,7 @@ fast-json-parse@^1.0.3: resolved "https://registry.yarnpkg.com/fast-json-parse/-/fast-json-parse-1.0.3.tgz#43e5c61ee4efa9265633046b770fb682a7577c4d" integrity sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw== -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3060,13 +2538,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3074,13 +2545,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -filelist@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== - dependencies: - minimatch "^5.0.1" - fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -3117,7 +2581,7 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -3228,7 +2692,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2: +fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -3271,16 +2735,6 @@ gcp-metadata@^5.3.0: gaxios "^5.0.0" json-bigint "^1.0.0" -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" @@ -3292,21 +2746,11 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - get-stdin@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - get-symbol-description@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" @@ -3347,7 +2791,7 @@ glob@^10.2.2: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.1.3, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3359,11 +2803,6 @@ glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - globals@^13.19.0: version "13.24.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" @@ -3420,7 +2859,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.15, graceful-fs@^4.2.9: +graceful-fs@^4.1.15: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -3518,12 +2957,7 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: hexoid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" - integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== htmlparser2@^8.0.0: version "8.0.2" @@ -3571,11 +3005,6 @@ https-proxy-agent@^7.0.5: agent-base "^7.0.2" debug "4" -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - husky@^8.0.1: version "8.0.3" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" @@ -3618,14 +3047,6 @@ import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3754,11 +3175,6 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - is-generator-function@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" @@ -3895,59 +3311,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-instrument@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" - integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== - dependencies: - "@babel/core" "^7.23.9" - "@babel/parser" "^7.23.9" - "@istanbuljs/schema" "^0.1.3" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - iterator.prototype@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.3.tgz#016c2abe0be3bbdb8319852884f60908ac62bf9c" @@ -3968,382 +3331,6 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jake@^10.8.5: - version "10.9.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" - integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== - dependencies: - async "^3.2.3" - chalk "^4.0.2" - filelist "^1.0.4" - minimatch "^3.1.2" - -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== - dependencies: - execa "^5.0.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.0" - jest-each "^29.7.0" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - pretty-format "^29.7.0" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== - dependencies: - "@jest/core" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - chalk "^4.0.0" - create-jest "^29.7.0" - exit "^0.1.2" - import-local "^3.0.2" - jest-config "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - yargs "^17.3.1" - -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.7.0" - "@jest/types" "^29.6.3" - babel-jest "^29.7.0" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^29.7.0" - jest-environment-node "^29.7.0" - jest-get-type "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-runner "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^29.0.0, jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.7.0" - pretty-format "^29.7.0" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -jest-extended@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/jest-extended/-/jest-extended-4.0.2.tgz#d23b52e687cedf66694e6b2d77f65e211e99e021" - integrity sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog== - dependencies: - jest-diff "^29.0.0" - jest-get-type "^29.0.0" - -jest-get-type@^29.0.0, jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - dependencies: - "@jest/types" "^29.6.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" - -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-pnp-resolver "^1.2.2" - jest-util "^29.7.0" - jest-validate "^29.7.0" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== - dependencies: - "@jest/console" "^29.7.0" - "@jest/environment" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.7.0" - jest-environment-node "^29.7.0" - jest-haste-map "^29.7.0" - jest-leak-detector "^29.7.0" - jest-message-util "^29.7.0" - jest-resolve "^29.7.0" - jest-runtime "^29.7.0" - jest-util "^29.7.0" - jest-watcher "^29.7.0" - jest-worker "^29.7.0" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/globals" "^29.7.0" - "@jest/source-map" "^29.6.3" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.7.0" - graceful-fs "^4.2.9" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - natural-compare "^1.4.0" - pretty-format "^29.7.0" - semver "^7.5.3" - -jest-util@^29.0.0, jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.7.0" - -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== - dependencies: - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.7.0" - string-length "^4.0.1" - -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== - dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" - import-local "^3.0.2" - jest-cli "^29.7.0" - jose@^2.0.6: version "2.0.7" resolved "https://registry.yarnpkg.com/jose/-/jose-2.0.7.tgz#3aabbaec70bff313c108b9406498a163737b16ba" @@ -4356,19 +3343,11 @@ jose@^4.14.6: resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.9.tgz#9b68eda29e9a0614c042fa29387196c7dd800100" integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA== -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: +"js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -4381,11 +3360,6 @@ jsbn@1.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== -jsesc@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" - integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== - json-bigint@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" @@ -4403,11 +3377,6 @@ json-parse-better-errors@^1.0.1: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -4430,11 +3399,6 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -4547,16 +3511,6 @@ keyv@^4.5.3: dependencies: json-buffer "3.0.1" -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -4570,11 +3524,6 @@ limiter@^1.1.5: resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - load-json-file@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-5.3.0.tgz#4d3c1e01fa1c03ea78a60ac7af932c9ce53403f3" @@ -4665,11 +3614,6 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -4712,6 +3656,11 @@ loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +loupe@^3.1.0, loupe@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" + integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== + lower-case-first@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1" @@ -4736,13 +3685,6 @@ lru-cache@^10.0.0, lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - lru-cache@^7.10.1, lru-cache@^7.14.1: version "7.18.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" @@ -4756,6 +3698,13 @@ lru-memoizer@^2.1.4, lru-memoizer@^2.2.0: lodash.clonedeep "^4.5.0" lru-cache "6.0.0" +magic-string@^0.30.12: + version "0.30.15" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.15.tgz#d5474a2c4c5f35f041349edaba8a5cb02733ed3c" + integrity sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -4763,25 +3712,6 @@ make-dir@^3.0.2: dependencies: semver "^6.0.0" -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -make-error@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4797,11 +3727,6 @@ merge-descriptors@1.0.3: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -4852,30 +3777,18 @@ mime@^3.0.0: resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - minimatch@^9.0.4: version "9.0.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" @@ -5087,28 +4000,6 @@ node-forge@^1.3.1: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" - integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -5195,13 +4086,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -5221,7 +4105,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.1, p-limit@^3.0.2, p-limit@^3.1.0: +p-limit@^3.0.1, p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -5295,16 +4179,6 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - parse-srcset@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" @@ -5350,7 +4224,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-key@^3.0.0, path-key@^3.1.0: +path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -5378,17 +4252,27 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== -picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: +picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: +picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -5459,11 +4343,6 @@ pino@^9.5.0: sonic-boom "^4.0.1" thread-stream "^3.0.0" -pirates@^4.0.4: - version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== - pkg-conf@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-3.1.0.tgz#d9f9c75ea1bae0e77938cde045b276dac7cc69ae" @@ -5480,7 +4359,7 @@ pkg-conf@^4.0.0: find-up "^6.0.0" load-json-file "^7.0.0" -pkg-dir@^4.1.0, pkg-dir@^4.2.0: +pkg-dir@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -5497,7 +4376,7 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== -postcss@^8.3.11: +postcss@^8.3.11, postcss@^8.4.43: version "8.4.49" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== @@ -5529,15 +4408,6 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -pretty-format@^29.0.0, pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - process-warning@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" @@ -5548,14 +4418,6 @@ process-warning@^4.0.0: resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-4.0.0.tgz#581e3a7a1fb456c5f4fd239f76bce75897682d5a" integrity sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw== -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -5615,11 +4477,6 @@ punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -pure-rand@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" - integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== - qs@6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" @@ -5691,11 +4548,6 @@ react-is@^16.13.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^18.0.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - "readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -5743,34 +4595,12 @@ regexpp@^3.0.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" - integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== - -resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.4: +resolve@^1.22.1, resolve@^1.22.4: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -5828,6 +4658,34 @@ robust-predicates@^2.0.4: resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-2.0.4.tgz#0a2367a93abd99676d075981707f29cfb402248b" integrity sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg== +rollup@^4.20.0: + version "4.28.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.28.1.tgz#7718ba34d62b449dfc49adbfd2f312b4fe0df4de" + integrity sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.28.1" + "@rollup/rollup-android-arm64" "4.28.1" + "@rollup/rollup-darwin-arm64" "4.28.1" + "@rollup/rollup-darwin-x64" "4.28.1" + "@rollup/rollup-freebsd-arm64" "4.28.1" + "@rollup/rollup-freebsd-x64" "4.28.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.28.1" + "@rollup/rollup-linux-arm-musleabihf" "4.28.1" + "@rollup/rollup-linux-arm64-gnu" "4.28.1" + "@rollup/rollup-linux-arm64-musl" "4.28.1" + "@rollup/rollup-linux-loongarch64-gnu" "4.28.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.28.1" + "@rollup/rollup-linux-riscv64-gnu" "4.28.1" + "@rollup/rollup-linux-s390x-gnu" "4.28.1" + "@rollup/rollup-linux-x64-gnu" "4.28.1" + "@rollup/rollup-linux-x64-musl" "4.28.1" + "@rollup/rollup-win32-arm64-msvc" "4.28.1" + "@rollup/rollup-win32-ia32-msvc" "4.28.1" + "@rollup/rollup-win32-x64-msvc" "4.28.1" + fsevents "~2.3.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -5886,12 +4744,12 @@ semver@^5.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: +semver@^6.0.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.0.0, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3: +semver@^7.0.0, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.4, semver@^7.6.3: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -6008,10 +4866,10 @@ sift@16.0.1: resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.1.tgz#e9c2ccc72191585008cf3e36fc447b2d2633a053" integrity sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ== -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== signal-exit@^4.0.1: version "4.1.0" @@ -6039,11 +4897,6 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -6089,19 +4942,6 @@ source-map-js@^1.2.1: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - sparse-bitfield@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" @@ -6126,17 +4966,10 @@ sprintf-js@^1.1.3: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== standard-engine@^15.0.0: version "15.1.0" @@ -6153,6 +4986,11 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +std-env@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" + integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== + stream-events@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" @@ -6176,14 +5014,6 @@ streamx@^2.15.0, streamx@^2.20.0: optionalDependencies: bare-events "^2.2.0" -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -6193,7 +5023,7 @@ string-length@^4.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6298,16 +5128,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -6376,13 +5196,6 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -6448,15 +5261,6 @@ teeny-request@^8.0.0: stream-events "^1.0.5" uuid "^9.0.0" -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - text-decoder@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.1.tgz#e173f5121d97bfa3ff8723429ad5ba92e1ead67e" @@ -6487,11 +5291,36 @@ tiny-case@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98" + integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ== + +tinypool@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2" + integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA== + tinyqueue@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA== +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + title-case@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/title-case/-/title-case-1.1.2.tgz#fae4a6ae546bfa22d083a0eea910a40d12ed4f5a" @@ -6500,11 +5329,6 @@ title-case@^1.1.0: sentence-case "^1.1.1" upper-case "^1.0.3" -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -6541,21 +5365,6 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -ts-jest@^29.2.5: - version "29.2.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" - integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== - dependencies: - bs-logger "^0.2.6" - ejs "^3.1.10" - fast-json-stable-stringify "^2.1.0" - jest-util "^29.0.0" - json5 "^2.2.3" - lodash.memoize "^4.1.2" - make-error "^1.3.6" - semver "^7.6.3" - yargs-parser "^21.1.1" - ts-standard@^12.0.0: version "12.0.2" resolved "https://registry.yarnpkg.com/ts-standard/-/ts-standard-12.0.2.tgz#883db655106f9bde374348fc81c89e8a30414e1b" @@ -6620,21 +5429,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - type-fest@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" @@ -6741,14 +5540,6 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" - integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.0" - upper-case-first@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115" @@ -6800,15 +5591,6 @@ uuid@^9.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== -v8-to-istanbul@^9.0.1: - version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" - integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - value-or-promise@1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.11.tgz#3e90299af31dd014fe843fe309cefa7c1d94b140" @@ -6824,17 +5606,53 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -wait-for-expect@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.2.tgz#d2f14b2f7b778c9b82144109c8fa89ceaadaa463" - integrity sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag== - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== +vite-node@2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.8.tgz#9495ca17652f6f7f95ca7c4b568a235e0c8dbac5" + integrity sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg== dependencies: - makeerror "1.0.12" + cac "^6.7.14" + debug "^4.3.7" + es-module-lexer "^1.5.4" + pathe "^1.1.2" + vite "^5.0.0" + +vite@^5.0.0: + version "5.4.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5" + integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.8.tgz#2e6a00bc24833574d535c96d6602fb64163092fa" + integrity sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ== + dependencies: + "@vitest/expect" "2.1.8" + "@vitest/mocker" "2.1.8" + "@vitest/pretty-format" "^2.1.8" + "@vitest/runner" "2.1.8" + "@vitest/snapshot" "2.1.8" + "@vitest/spy" "2.1.8" + "@vitest/utils" "2.1.8" + chai "^5.1.2" + debug "^4.3.7" + expect-type "^1.1.0" + magic-string "^0.30.12" + pathe "^1.1.2" + std-env "^3.8.0" + tinybench "^2.9.0" + tinyexec "^0.3.1" + tinypool "^1.0.1" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.1.8" + why-is-node-running "^2.3.0" webidl-conversions@^3.0.0: version "3.0.1" @@ -6933,6 +5751,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" @@ -6947,15 +5773,6 @@ word-wrap@^1.2.5: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -6970,52 +5787,16 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.3.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - yauzl@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-3.2.0.tgz#7b6cb548f09a48a6177ea0be8ece48deb7da45c0" From 691db38b6d2b822dcbbf6290f84a776f1d4d0e88 Mon Sep 17 00:00:00 2001 From: CocoisBuggy Date: Mon, 3 Mar 2025 13:14:23 +0200 Subject: [PATCH 5/6] Fix TS-standard lint error the vite config is also expressed in TS, so it should not be exempt from linting --- tsconfig.json | 1 + vite.config.ts | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index c191f2e5..e8439192 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,6 @@ }, "include": [ "src/**/*.ts", + "vite.config.ts" ] } diff --git a/vite.config.ts b/vite.config.ts index 89255528..e6888958 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,9 +9,8 @@ export default defineConfig({ pool: 'threads', poolOptions: { threads: { - isolate: false, - }, + isolate: false + } } - }, + } }) - From 56fd48eddaa3de3d2cba95448ee00e170b1796bf Mon Sep 17 00:00:00 2001 From: CocoisBuggy Date: Mon, 3 Mar 2025 13:22:02 +0200 Subject: [PATCH 6/6] Allow logger to recieve a LOG_LEVEL env var testing is quiiiiite noisy in testing --- package.json | 2 +- src/__tests__/areas.ts | 1 - src/__tests__/fixtures/data.fixtures.ts | 1 - src/__tests__/fixtures/mongo.fixtures.ts | 3 ++- src/db/export/json/async-file.processor.test.ts | 3 ++- src/db/import/usa/__tests__/Tree.test.ts | 3 ++- src/db/index.ts | 2 +- src/logger.ts | 2 +- 8 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 4cca9b9f..5a58e223 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "scripts": { "lint": "yarn ts-standard", "fix": "yarn ts-standard --fix", - "test": "vitest run --silent", + "test": "LOG_LEVEL=error vitest run --silent", "build": "tsc -p tsconfig.json", "build-release": "tsc -p tsconfig.release.json", "clean": "tsc -b --clean && rm -rf build/*", diff --git a/src/__tests__/areas.ts b/src/__tests__/areas.ts index 07446881..496903d3 100644 --- a/src/__tests__/areas.ts +++ b/src/__tests__/areas.ts @@ -84,7 +84,6 @@ describe('areas API', () => { expect(response.statusCode).toBe(200) const areaResult = response.body.data.area expect(areaResult.uuid).toBe(muuidToString(includedChild.metadata.area_id)) - console.log(areaResult) expect(areaResult.organizations).toHaveLength(1) expect(areaResult.organizations[0].orgId).toBe(muuidToString(alphaOrg.orgId)) }) diff --git a/src/__tests__/fixtures/data.fixtures.ts b/src/__tests__/fixtures/data.fixtures.ts index 4278b46c..03e5555c 100644 --- a/src/__tests__/fixtures/data.fixtures.ts +++ b/src/__tests__/fixtures/data.fixtures.ts @@ -211,7 +211,6 @@ export const dataFixtures = dbTest.extend({ const grade = scale.getGrade(Math.floor(Math.random() * 100)) assert(grade) - console.log({ grade, type: climb.type, scale }) const record = createGradeObject(grade, climb.type, ctx) assert(record !== undefined) diff --git a/src/__tests__/fixtures/mongo.fixtures.ts b/src/__tests__/fixtures/mongo.fixtures.ts index d1b704bb..0a126401 100644 --- a/src/__tests__/fixtures/mongo.fixtures.ts +++ b/src/__tests__/fixtures/mongo.fixtures.ts @@ -15,6 +15,7 @@ import TickDataSource from '../../model/TickDataSource' import UserDataSource from '../../model/UserDataSource' import { MUUID } from 'uuid-mongodb' import { BaseChangeRecordType, ChangeLogType } from '../../db/ChangeLogType' +import { logger } from '../../logger' /** * In-memory Mongo replset used for testing. @@ -82,7 +83,7 @@ export const dbTest = test.extend({ const mCollection = database.collection(collection) const result = await mCollection.insertMany(documents) - console.log(`${result.insertedCount} documents were inserted directly into MongoDB`) + logger.debug(`${result.insertedCount} documents were inserted directly into MongoDB`) } finally { await client.close() } diff --git a/src/db/export/json/async-file.processor.test.ts b/src/db/export/json/async-file.processor.test.ts index 071fd50f..8d9491ef 100644 --- a/src/db/export/json/async-file.processor.test.ts +++ b/src/db/export/json/async-file.processor.test.ts @@ -1,3 +1,4 @@ +import { logger } from '../../../logger' import { asyncFileProcessor, Writer } from './async-file.processor' import path from 'path' @@ -23,7 +24,7 @@ describe('file processor', () => { function withFailedWriteOn (failingData: { name: string }): Writer { return async (data, path) => { - console.log(data, failingData) + logger.info(data, failingData) if (data === JSON.stringify(failingData)) { return await Promise.reject('error') } diff --git a/src/db/import/usa/__tests__/Tree.test.ts b/src/db/import/usa/__tests__/Tree.test.ts index ebb61a16..2640de90 100644 --- a/src/db/import/usa/__tests__/Tree.test.ts +++ b/src/db/import/usa/__tests__/Tree.test.ts @@ -1,3 +1,4 @@ +import { logger } from '../../../../logger' import { Tree, createRootNode } from '../AreaTree' const path1 = 'Oregon|Central Oregon|Paulina Peak|Vigilantes de Obsidiana|Roca Rhodales' @@ -45,7 +46,7 @@ describe('Area Tree data structure', () => { const leaf = tree.atPath(path1) if (leaf !== undefined) { const ancestors = leaf.getAncestors() - console.log(ancestors) + logger.debug(ancestors) expect(ancestors.length).toEqual(path1.split('|').length + 1) // all element of path1 + 1 for US root expect(ancestors[0]).toEqual(countryRoot?.uuid) const stateRoot = tree.atPath('Oregon') diff --git a/src/db/index.ts b/src/db/index.ts index e31ae385..c5af1a40 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -81,7 +81,7 @@ export const gracefulExit = async (exitCode: number = 0): Promise => { } export const defaultPostConnect = async (changeStreamListener = streamListener): Promise => { - console.log('Kudos!') + logger.debug('defaultPostConnect- Kudos!') await createIndexes() return await changeStreamListener() } diff --git a/src/logger.ts b/src/logger.ts index e5694869..1fcd820a 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -13,5 +13,5 @@ const setupLogFlare = (apiKey?: string, sourceToken?: string): any | undefined = export const logger = pino({ name: 'openbeta-graphql', - level: 'info' + level: process.env.LOG_LEVEL ?? 'info' }, setupLogFlare(process.env.LOGFLARE_API_KEY, process.env.LOGFLARE_SOURCE_TOKEN))