diff --git a/.github/workflows/build-master.yml b/.github/workflows/build-master.yml index 41ac59ba59..df3e1adc62 100644 --- a/.github/workflows/build-master.yml +++ b/.github/workflows/build-master.yml @@ -195,13 +195,45 @@ jobs: name: Server Test runs-on: ubuntu-latest environment: development + services: + postgres: + image: postgres + env: + POSTGRES_PASSWORD: affine + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: ./.github/actions/setup-node + - name: Initialize database + run: | + psql -h localhost -U postgres -c "CREATE DATABASE affine;" + psql -h localhost -U postgres -c "CREATE USER affine WITH PASSWORD 'affine';" + psql -h localhost -U postgres -c "ALTER USER affine WITH SUPERUSER;" + env: + PGPASSWORD: affine + - name: Generate prisma client + run: | + yarn exec prisma generate + yarn exec prisma db push + working-directory: apps/server + env: + DATABASE_URL: postgresql://affine:affine@localhost:5432/affine + - name: Run init-db script + run: yarn exec ts-node-esm ./scripts/init-db.ts + env: + DATABASE_URL: postgresql://affine:affine@localhost:5432/affine - name: Run server tests run: yarn test:coverage working-directory: apps/server + env: + DATABASE_URL: postgresql://affine:affine@localhost:5432/affine - name: Upload server test coverage results uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9860e6044f..488d427d85 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,13 +86,46 @@ jobs: name: Server Test runs-on: ubuntu-latest environment: development + services: + postgres: + image: postgres + env: + POSTGRES_PASSWORD: affine + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: ./.github/actions/setup-node + - name: Initialize database + run: | + psql -h localhost -U postgres -c "CREATE DATABASE affine;" + psql -h localhost -U postgres -c "CREATE USER affine WITH PASSWORD 'affine';" + psql -h localhost -U postgres -c "ALTER USER affine WITH SUPERUSER;" + env: + PGPASSWORD: affine + - name: Generate prisma client + run: | + yarn exec prisma generate + yarn exec prisma db push + working-directory: apps/server + env: + DATABASE_URL: postgresql://affine:affine@localhost:5432/affine + - name: Run init-db script + run: yarn exec ts-node-esm ./scripts/init-db.ts + working-directory: apps/server + env: + DATABASE_URL: postgresql://affine:affine@localhost:5432/affine - name: Run server tests run: yarn test:coverage working-directory: apps/server + env: + DATABASE_URL: postgresql://affine:affine@localhost:5432/affine - name: Upload server test coverage results uses: codecov/codecov-action@v3 with: diff --git a/apps/server/migrations/20230418212743_init/migration.sql b/apps/server/migrations/20230418212743_init/migration.sql new file mode 100644 index 0000000000..da95ce145a --- /dev/null +++ b/apps/server/migrations/20230418212743_init/migration.sql @@ -0,0 +1,67 @@ +-- CreateTable +CREATE TABLE "google_users" ( + "id" VARCHAR NOT NULL, + "user_id" VARCHAR NOT NULL, + "google_id" VARCHAR NOT NULL, + + CONSTRAINT "google_users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "permissions" ( + "id" VARCHAR NOT NULL, + "workspace_id" VARCHAR NOT NULL, + "user_id" VARCHAR, + "user_email" TEXT, + "type" SMALLINT NOT NULL, + "accepted" BOOLEAN NOT NULL DEFAULT false, + "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "permissions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "seaql_migrations" ( + "version" VARCHAR NOT NULL, + "applied_at" BIGINT NOT NULL, + + CONSTRAINT "seaql_migrations_pkey" PRIMARY KEY ("version") +); + +-- CreateTable +CREATE TABLE "users" ( + "id" VARCHAR NOT NULL, + "name" VARCHAR NOT NULL, + "email" VARCHAR NOT NULL, + "avatar_url" VARCHAR, + "token_nonce" SMALLINT DEFAULT 0, + "password" VARCHAR, + "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "workspaces" ( + "id" VARCHAR NOT NULL, + "public" BOOLEAN NOT NULL, + "type" SMALLINT NOT NULL, + "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "workspaces_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "google_users_google_id_key" ON "google_users"("google_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); + +-- AddForeignKey +ALTER TABLE "google_users" ADD CONSTRAINT "google_users_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "permissions" ADD CONSTRAINT "permissions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "permissions" ADD CONSTRAINT "permissions_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/server/migrations/migration_lock.toml b/apps/server/migrations/migration_lock.toml new file mode 100644 index 0000000000..fbffa92c2b --- /dev/null +++ b/apps/server/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/apps/server/package.json b/apps/server/package.json index 1674d45b43..8ef8ed70ac 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -4,10 +4,13 @@ "version": "0.5.3", "description": "Affine Node.js server", "type": "module", + "bin": { + "run-test": "./scripts/run-test.ts" + }, "scripts": { "dev": "nodemon ./src/index.ts", - "test": "NODE_ENV=test node --loader ts-node/esm.mjs --es-module-specifier-resolution node --test ./src/tests/*", - "test:coverage": "NODE_ENV=test c8 node --loader ts-node/esm.mjs --es-module-specifier-resolution node --experimental-test-coverage ./src/tests/*" + "test": "ts-node-esm ./scripts/run-test.ts all", + "test:coverage": "c8 ts-node-esm ./scripts/run-test.ts all" }, "dependencies": { "@apollo/server": "^4.6.0", @@ -29,8 +32,10 @@ "@nestjs/testing": "^9.4.0", "@types/lodash-es": "^4.14.194", "@types/node": "^18.15.11", + "@types/supertest": "^2.0.12", "c8": "^7.13.0", "nodemon": "^2.0.22", + "supertest": "^6.3.3", "ts-node": "^10.9.1", "typescript": "^5.0.4", "vitest": "^0.30.1" @@ -64,6 +69,7 @@ ], "report-dir": ".coverage", "exclude": [ + "scripts", "node_modules", "**/*.spec.ts" ] diff --git a/apps/server/scripts/init-db.ts b/apps/server/scripts/init-db.ts new file mode 100644 index 0000000000..bc3bf73f68 --- /dev/null +++ b/apps/server/scripts/init-db.ts @@ -0,0 +1,24 @@ +import { randomUUID } from 'node:crypto'; + +import userA from '@affine-test/fixtures/userA.json' assert { type: 'json' }; +import { PrismaClient } from '@prisma/client'; +const prisma = new PrismaClient(); + +async function main() { + await prisma.users.create({ + data: { + id: randomUUID(), + ...userA, + }, + }); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async e => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/apps/server/scripts/run-test.ts b/apps/server/scripts/run-test.ts new file mode 100755 index 0000000000..ec5aed1d02 --- /dev/null +++ b/apps/server/scripts/run-test.ts @@ -0,0 +1,56 @@ +#!/usr/bin/env ts-node-esm +import { resolve } from 'node:path'; + +import * as p from '@clack/prompts'; +import { spawn } from 'child_process'; +import { readdir } from 'fs/promises'; +import * as process from 'process'; +import { fileURLToPath } from 'url'; + +import pkg from '../package.json' assert { type: 'json' }; +const root = fileURLToPath(new URL('..', import.meta.url)); +const testDir = resolve(root, 'src', 'tests'); +const files = await readdir(testDir); + +const args = [...pkg.nodemonConfig.nodeArgs, '--test']; + +const env = { + PATH: process.env.PATH, + NODE_ENV: 'test', + DATABASE_URL: process.env.DATABASE_URL, +}; + +if (process.argv[2] === 'all') { + const cp = spawn('node', [...args, resolve(testDir, '*')], { + cwd: root, + env, + stdio: 'inherit', + shell: true, + }); + cp.on('exit', code => { + process.exit(code ?? 0); + }); +} else { + const result = await p.group({ + file: () => + p.select({ + message: 'Select a file to run', + options: files.map(file => ({ + label: file, + value: file as any, + })), + }), + }); + + const target = resolve(testDir, result.file); + + const cp = spawn('node', [...args, target], { + cwd: root, + env, + stdio: 'inherit', + shell: true, + }); + cp.on('exit', code => { + process.exit(code ?? 0); + }); +} diff --git a/apps/server/src/config/default.ts b/apps/server/src/config/default.ts index 87ae62c770..1e186a775b 100644 --- a/apps/server/src/config/default.ts +++ b/apps/server/src/config/default.ts @@ -19,7 +19,7 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => ({ }, https: false, host: 'localhost', - port: 3000, + port: 3010, path: '', get origin() { return this.dev diff --git a/apps/server/src/modules/index.ts b/apps/server/src/modules/index.ts index 0a3f3dab8e..cd5cde7cee 100644 --- a/apps/server/src/modules/index.ts +++ b/apps/server/src/modules/index.ts @@ -1,3 +1,4 @@ +import { UsersModule } from './users'; import { WorkspaceModule } from './workspaces'; -export const BusinessModules = [WorkspaceModule]; +export const BusinessModules = [WorkspaceModule, UsersModule]; diff --git a/apps/server/src/modules/users/index.ts b/apps/server/src/modules/users/index.ts new file mode 100644 index 0000000000..efe1072088 --- /dev/null +++ b/apps/server/src/modules/users/index.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; + +import { UserResolver } from './resolver'; + +@Module({ + providers: [UserResolver], +}) +export class UsersModule {} diff --git a/apps/server/src/modules/users/resolver.ts b/apps/server/src/modules/users/resolver.ts new file mode 100644 index 0000000000..c92114acdb --- /dev/null +++ b/apps/server/src/modules/users/resolver.ts @@ -0,0 +1,37 @@ +import { Args, Field, ID, ObjectType, Query, Resolver } from '@nestjs/graphql'; +import type { users } from '@prisma/client'; + +import { PrismaService } from '../../prisma/service'; + +@ObjectType() +export class User implements users { + @Field(() => ID) + id!: string; + @Field({ description: 'User name' }) + name!: string; + @Field({ description: 'User email' }) + email!: string; + @Field({ description: 'User password', nullable: true }) + password!: string; + @Field({ description: 'User avatar url', nullable: true }) + avatar_url!: string; + @Field({ description: 'User token nonce', nullable: true }) + token_nonce!: number; + @Field({ description: 'User created date', nullable: true }) + created_at!: Date; +} + +@Resolver(() => User) +export class UserResolver { + constructor(private readonly prisma: PrismaService) {} + + @Query(() => User, { + name: 'user', + description: 'Get user by email', + }) + async user(@Args('email') email: string) { + return this.prisma.users.findUnique({ + where: { email }, + }); + } +} diff --git a/apps/server/src/modules/workspaces/resolver.ts b/apps/server/src/modules/workspaces/resolver.ts index 290b695e01..12f7590a1f 100644 --- a/apps/server/src/modules/workspaces/resolver.ts +++ b/apps/server/src/modules/workspaces/resolver.ts @@ -1,6 +1,10 @@ +import { randomUUID } from 'node:crypto'; + import { Args, Field, + ID, + Mutation, ObjectType, Query, registerEnumType, @@ -17,11 +21,20 @@ export enum WorkspaceType { registerEnumType(WorkspaceType, { name: 'WorkspaceType', + description: 'Workspace type', + valuesMap: { + Normal: { + description: 'Normal workspace', + }, + Private: { + description: 'Private workspace', + }, + }, }); @ObjectType() export class Workspace implements workspaces { - @Field() + @Field(() => ID) id!: string; @Field({ description: 'is Public workspace' }) public!: boolean; @@ -53,4 +66,20 @@ export class WorkspaceResolver { where: { id }, }); } + + // create workspace + @Mutation(() => Workspace, { + name: 'createWorkspace', + description: 'Create workspace', + }) + async createWorkspace() { + return this.prisma.workspaces.create({ + data: { + id: randomUUID(), + type: WorkspaceType.Private, + public: false, + created_at: new Date(), + }, + }); + } } diff --git a/apps/server/src/tests/app.e2e.ts b/apps/server/src/tests/app.e2e.ts new file mode 100644 index 0000000000..965cb4e5c7 --- /dev/null +++ b/apps/server/src/tests/app.e2e.ts @@ -0,0 +1,100 @@ +import { equal, ok } from 'node:assert'; +import { afterEach, beforeEach, describe, test } from 'node:test'; + +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import request from 'supertest'; + +import { AppModule } from '../app'; +import { getDefaultAFFiNEConfig } from '../config/default'; + +const gql = '/graphql'; + +globalThis.AFFiNE = getDefaultAFFiNEConfig(); + +// please run `ts-node-esm ./scripts/init-db.ts` before running this test +describe('AppModule', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + app = module.createNestApplication(); + await app.init(); + }); + + afterEach(async () => { + await app.close(); + }); + + test('should init app', async () => { + ok(typeof app === 'object'); + await request(app.getHttpServer()) + .post(gql) + .send({ + query: ` + query { + error + } + `, + }) + .expect(400); + await request(app.getHttpServer()) + .post(gql) + .send({ + query: ` + mutation { + createWorkspace { + id + type + public + created_at + } + } + `, + }) + .expect(200) + .expect(res => { + ok( + typeof res.body.data.createWorkspace === 'object', + 'res.body.data.createWorkspace is not an object' + ); + ok( + typeof res.body.data.createWorkspace.id === 'string', + 'res.body.data.createWorkspace.id is not a string' + ); + ok( + typeof res.body.data.createWorkspace.type === 'string', + 'res.body.data.createWorkspace.type is not a string' + ); + ok( + typeof res.body.data.createWorkspace.public === 'boolean', + 'res.body.data.createWorkspace.public is not a boolean' + ); + ok( + typeof res.body.data.createWorkspace.created_at === 'string', + 'res.body.data.createWorkspace.created_at is not a string' + ); + }); + }); + + test('should find default user', async () => { + await request(app.getHttpServer()) + .post(gql) + .send({ + query: ` + query { + user(email: "alex.yang@example.org") { + email + avatar_url + } + } + `, + }) + .expect(200) + .expect(res => { + equal(res.body.data.user.email, 'alex.yang@example.org'); + }); + }); +}); diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index c788020f1e..e9677bf5d3 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -15,6 +15,11 @@ }, "include": ["src", "package.json"], "exclude": ["dist", "node_modules"], + "references": [ + { + "path": "./tsconfig.node.json" + } + ], "ts-node": { "esm": true, "experimentalSpecifierResolution": "node" diff --git a/apps/server/tsconfig.node.json b/apps/server/tsconfig.node.json new file mode 100644 index 0000000000..b9e486d423 --- /dev/null +++ b/apps/server/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["./scripts", "package.json"] +} diff --git a/yarn.lock b/yarn.lock index b27f9bbe63..380641023f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -214,6 +214,7 @@ __metadata: "@prisma/client": ^4.12.0 "@types/lodash-es": ^4.14.194 "@types/node": ^18.15.11 + "@types/supertest": ^2.0.12 c8: ^7.13.0 dotenv: ^16.0.3 graphql: ^16.6.0 @@ -223,9 +224,12 @@ __metadata: prisma: ^4.12.0 reflect-metadata: ^0.1.13 rxjs: ^7.8.0 + supertest: ^6.3.3 ts-node: ^10.9.1 typescript: ^5.0.4 vitest: ^0.30.1 + bin: + run-test: ./scripts/run-test.ts languageName: unknown linkType: soft @@ -7545,6 +7549,13 @@ __metadata: languageName: node linkType: hard +"@types/cookiejar@npm:*": + version: 2.1.2 + resolution: "@types/cookiejar@npm:2.1.2" + checksum: f6e1903454007f86edd6c3520cbb4d553e1d4e17eaf1f77f6f75e3270f48cc828d74397a113a36942f5fe52f9fa71067bcfa738f53ad468fcca0bc52cb1cbd28 + languageName: node + linkType: hard + "@types/debug@npm:^4.1.7": version: 4.1.7 resolution: "@types/debug@npm:4.1.7" @@ -8046,6 +8057,25 @@ __metadata: languageName: node linkType: hard +"@types/superagent@npm:*": + version: 4.1.16 + resolution: "@types/superagent@npm:4.1.16" + dependencies: + "@types/cookiejar": "*" + "@types/node": "*" + checksum: 187d1d32fdafd20b27e81728c46283160d3296ad904d56e0780769cf524105c94cc64bf5bafa170400cf5f1063d30826427de42ff0894d15b54df6d0fa31be4e + languageName: node + linkType: hard + +"@types/supertest@npm:^2.0.12": + version: 2.0.12 + resolution: "@types/supertest@npm:2.0.12" + dependencies: + "@types/superagent": "*" + checksum: f0e2b44f86bec2f708d6a3d0cb209055b487922040773049b0f8c6b557af52d4b5fa904e17dfaa4ce6e610172206bbec7b62420d158fa57b6ffc2de37b1730d3 + languageName: node + linkType: hard + "@types/testing-library__jest-dom@npm:^5.9.1": version: 5.14.5 resolution: "@types/testing-library__jest-dom@npm:5.14.5" @@ -9201,6 +9231,13 @@ __metadata: languageName: node linkType: hard +"asap@npm:^2.0.0": + version: 2.0.6 + resolution: "asap@npm:2.0.6" + checksum: b296c92c4b969e973260e47523207cd5769abd27c245a68c26dc7a0fe8053c55bb04360237cb51cab1df52be939da77150ace99ad331fb7fb13b3423ed73ff3d + languageName: node + linkType: hard + "asar@npm:^3.0.0": version: 3.2.0 resolution: "asar@npm:3.2.0" @@ -10579,6 +10616,13 @@ __metadata: languageName: node linkType: hard +"component-emitter@npm:^1.3.0": + version: 1.3.0 + resolution: "component-emitter@npm:1.3.0" + checksum: b3c46de38ffd35c57d1c02488355be9f218e582aec72d72d1b8bbec95a3ac1b38c96cd6e03ff015577e68f550fbb361a3bfdbd9bb248be9390b7b3745691be6b + languageName: node + linkType: hard + "compressible@npm:~2.0.16": version: 2.0.18 resolution: "compressible@npm:2.0.18" @@ -10767,6 +10811,13 @@ __metadata: languageName: node linkType: hard +"cookiejar@npm:^2.1.4": + version: 2.1.4 + resolution: "cookiejar@npm:2.1.4" + checksum: c4442111963077dc0e5672359956d6556a195d31cbb35b528356ce5f184922b99ac48245ac05ed86cf993f7df157c56da10ab3efdadfed79778a0d9b1b092d5b + languageName: node + linkType: hard + "copy-anything@npm:^3.0.2": version: 3.0.3 resolution: "copy-anything@npm:3.0.3" @@ -11338,6 +11389,16 @@ __metadata: languageName: node linkType: hard +"dezalgo@npm:^1.0.4": + version: 1.0.4 + resolution: "dezalgo@npm:1.0.4" + dependencies: + asap: ^2.0.0 + wrappy: 1 + checksum: 895389c6aead740d2ab5da4d3466d20fa30f738010a4d3f4dcccc9fc645ca31c9d10b7e1804ae489b1eb02c7986f9f1f34ba132d409b043082a86d9a4e745624 + languageName: node + linkType: hard + "diff-sequences@npm:^28.1.1": version: 28.1.1 resolution: "diff-sequences@npm:28.1.1" @@ -12902,7 +12963,7 @@ __metadata: languageName: node linkType: hard -"fast-safe-stringify@npm:2.1.1": +"fast-safe-stringify@npm:2.1.1, fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" checksum: a851cbddc451745662f8f00ddb622d6766f9bd97642dabfd9a405fb0d646d69fc0b9a1243cbf67f5f18a39f40f6fa821737651ff1bceeba06c9992ca2dc5bd3d @@ -13330,6 +13391,18 @@ __metadata: languageName: node linkType: hard +"formidable@npm:^2.1.2": + version: 2.1.2 + resolution: "formidable@npm:2.1.2" + dependencies: + dezalgo: ^1.0.4 + hexoid: ^1.0.0 + once: ^1.4.0 + qs: ^6.11.0 + checksum: 81c8e5d89f5eb873e992893468f0de22c01678ca3d315db62be0560f9de1c77d4faefc9b1f4575098eb2263b3c81ba1024833a9fc3206297ddbac88a4f69b7a8 + languageName: node + linkType: hard + "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -14275,6 +14348,13 @@ __metadata: languageName: node linkType: hard +"hexoid@npm:^1.0.0": + version: 1.0.0 + resolution: "hexoid@npm:1.0.0" + checksum: 27a148ca76a2358287f40445870116baaff4a0ed0acc99900bf167f0f708ffd82e044ff55e9949c71963852b580fc024146d3ac6d5d76b508b78d927fa48ae2d + languageName: node + linkType: hard + "highlight.js@npm:^10.4.1, highlight.js@npm:~10.7.0": version: 10.7.3 resolution: "highlight.js@npm:10.7.3" @@ -17229,7 +17309,7 @@ __metadata: languageName: node linkType: hard -"methods@npm:~1.1.2": +"methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" checksum: 0917ff4041fa8e2f2fda5425a955fe16ca411591fbd123c0d722fcf02b73971ed6f764d85f0a6f547ce49ee0221ce2c19a5fa692157931cecb422984f1dcd13a @@ -17287,7 +17367,7 @@ __metadata: languageName: node linkType: hard -"mime@npm:^2.0.3": +"mime@npm:2.6.0, mime@npm:^2.0.3": version: 2.6.0 resolution: "mime@npm:2.6.0" bin: @@ -19205,7 +19285,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.10.0": +"qs@npm:^6.10.0, qs@npm:^6.11.0": version: 6.11.1 resolution: "qs@npm:6.11.1" dependencies: @@ -20424,6 +20504,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.8": + version: 7.5.0 + resolution: "semver@npm:7.5.0" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 2d266937756689a76f124ffb4c1ea3e1bbb2b263219f90ada8a11aebebe1280b13bb76cca2ca96bdee3dbc554cbc0b24752eb895b2a51577aa644427e9229f2b + languageName: node + linkType: hard + "semver@npm:~7.0.0": version: 7.0.0 resolution: "semver@npm:7.0.0" @@ -21387,6 +21478,24 @@ __metadata: languageName: node linkType: hard +"superagent@npm:^8.0.5": + version: 8.0.9 + resolution: "superagent@npm:8.0.9" + dependencies: + component-emitter: ^1.3.0 + cookiejar: ^2.1.4 + debug: ^4.3.4 + fast-safe-stringify: ^2.1.1 + form-data: ^4.0.0 + formidable: ^2.1.2 + methods: ^1.1.2 + mime: 2.6.0 + qs: ^6.11.0 + semver: ^7.3.8 + checksum: 5d00cdc7ceb5570663da80604965750e6b1b8d7d7442b7791e285c62bcd8d578a8ead0242a2426432b59a255fb42eb3a196d636157538a1392e7b6c5f1624810 + languageName: node + linkType: hard + "superjson@npm:^1.12.2": version: 1.12.2 resolution: "superjson@npm:1.12.2" @@ -21396,6 +21505,16 @@ __metadata: languageName: node linkType: hard +"supertest@npm:^6.3.3": + version: 6.3.3 + resolution: "supertest@npm:6.3.3" + dependencies: + methods: ^1.1.2 + superagent: ^8.0.5 + checksum: 38239e517f7ba62b7a139a79c5c48d55f8d67b5ff4b6e51d5b07732ca8bbc4a28ffa1b10916fbb403dd013a054dbf028edc5850057d9a43aecbff439d494673e + languageName: node + linkType: hard + "supports-color@npm:^5.3.0, supports-color@npm:^5.5.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0"