From 7ebfb2f572f4949c38113df496aa0e8ab20dd9ac Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 20:26:44 +0100 Subject: [PATCH 01/11] fix(metadata.test.ts): metadata upload unit tests Fixes the test file and controller to have passing tests and better error handling for the metadata upload functionality of the MetadataController --- package.json | 4 +- pnpm-lock.yaml | 94 ++++++++++++- src/controllers/MetadataController.ts | 73 +++++----- test/api/v1/metadata.test.ts | 195 +++++++++----------------- test/test-utils/mockMetadata.ts | 7 +- 5 files changed, 205 insertions(+), 168 deletions(-) diff --git a/package.json b/package.json index b5da9360..a09540b9 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,9 @@ "typedoc": "^0.26.5", "typescript": "5.5.3", "typescript-eslint": "^7.7.0", - "vitest": "^1.1.3" + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^1.1.3", + "vitest-mock-extended": "^2.0.2" }, "lint-staged": { "*.{mjx,cjs,js,jsx,ts,tsx}": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e740322..fbc7325b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -267,9 +267,15 @@ importers: typescript-eslint: specifier: ^7.7.0 version: 7.7.0(eslint@8.56.0)(typescript@5.5.3) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.5.3)(vite@5.0.11(@types/node@20.10.6)) vitest: specifier: ^1.1.3 version: 1.1.3(@types/node@20.10.6) + vitest-mock-extended: + specifier: ^2.0.2 + version: 2.0.2(typescript@5.5.3)(vitest@1.1.3(@types/node@20.10.6)) packages: @@ -314,6 +320,7 @@ packages: '@ardatan/relay-compiler@12.0.0': resolution: {integrity: sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==} + hasBin: true peerDependencies: graphql: '*' @@ -1018,6 +1025,7 @@ packages: '@graphql-codegen/cli@5.0.2': resolution: {integrity: sha512-MBIaFqDiLKuO4ojN6xxG9/xL9wmfD3ZjZ7RsPjwQnSHBCUXnEkdKvX+JVpx87Pq29Ycn8wTJUguXnTZ7Di0Mlw==} + hasBin: true peerDependencies: '@parcel/watcher': ^2.1.0 graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 @@ -1979,6 +1987,7 @@ packages: '@openzeppelin/hardhat-upgrades@3.2.1': resolution: {integrity: sha512-Zy5M3QhkzwGdpzQmk+xbWdYOGJWjoTvwbBKYLhctu9B91DoprlhDRaZUwCtunwTdynkTDGdVfGr0kIkvycyKjw==} + hasBin: true peerDependencies: '@nomicfoundation/hardhat-ethers': ^3.0.0 '@nomicfoundation/hardhat-verify': ^2.0.0 @@ -2319,6 +2328,7 @@ packages: '@snaplet/seed@0.97.20': resolution: {integrity: sha512-+lnqESgwP92O1266vsTyoRgrg4hMCUTybBUxDT1ICMBFcvdjgwcOaUt8Xjj81YvxYkZlu5+TTBIjyNQT4nP4jQ==} engines: {node: '>=18.5.0'} + hasBin: true peerDependencies: '@prisma/client': '>=5' '@snaplet/copycat': '>=2' @@ -2377,6 +2387,7 @@ packages: '@swc/cli@0.3.12': resolution: {integrity: sha512-h7bvxT+4+UDrLWJLFHt6V+vNAcUNii2G4aGSSotKz1ECEk4MyEh5CWxmeSscwuz5K3i+4DWTgm4+4EyMCQKn+g==} engines: {node: '>= 16.14.0'} + hasBin: true peerDependencies: '@swc/core': ^1.2.66 chokidar: ^3.5.1 @@ -4521,6 +4532,9 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -4533,6 +4547,7 @@ packages: gql.tada@1.8.3: resolution: {integrity: sha512-0H81I3M54jKTDHbnNWhXDf57Ie2d2raxnFCc93zdYjXHnrXe522jrio9AAFwqBlGx/xtaP3ILSSUw7J9H31LAA==} + hasBin: true peerDependencies: typescript: ^5.0.0 @@ -4614,6 +4629,7 @@ packages: hardhat@2.22.16: resolution: {integrity: sha512-d52yQZ09u0roL6GlgJSvtknsBtIuj9JrJ/U8VMzr/wue+gO5v2tQayvOX6llerlR57Zw2EOTQjLAt6RpHvjwHA==} + hasBin: true peerDependencies: ts-node: '*' typescript: '*' @@ -6841,6 +6857,14 @@ packages: resolution: {integrity: sha512-WZ/iAJrKDhdINv1WG6KZIGHrZDar6VfhftG1QJFpVbOYZMYJLJOvZOo1amictRXVdBXZIgBHKswMTXzElngprA==} engines: {node: '>=14.13.1'} + ts-essentials@10.0.4: + resolution: {integrity: sha512-lwYdz28+S4nicm+jFi6V58LaAIpxzhg9rLdgNC1VsdP/xiFBseGhF1M/shwCk6zMmwahBZdXcl34LVHrEang3A==} + peerDependencies: + typescript: '>=4.5.0' + peerDependenciesMeta: + typescript: + optional: true + ts-invariant@0.4.4: resolution: {integrity: sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==} @@ -6852,6 +6876,7 @@ packages: ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true peerDependencies: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' @@ -6863,6 +6888,16 @@ packages: '@swc/wasm': optional: true + tsconfck@3.1.4: + resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -6967,6 +7002,7 @@ packages: typedoc@0.26.5: resolution: {integrity: sha512-Vn9YKdjKtDZqSk+by7beZ+xzkkr8T8CYoiasqyt4TTRFy5+UHzL/mF/o4wGBjRF+rlWQHDb0t6xCpA3JNL5phg==} engines: {node: '>= 18'} + hasBin: true peerDependencies: typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x @@ -7065,6 +7101,7 @@ packages: update-browserslist-db@1.0.13: resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -7150,9 +7187,18 @@ packages: resolution: {integrity: sha512-BLSO72YAkIUuNrOx+8uznYICJfTEbvBAmWClY3hpath5+h1mbPS5OMn42lrTxXuyCazVyZoDkSRnju78GiVCqA==} engines: {node: ^18.0.0 || >=20.0.0} + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + vite@5.0.11: resolution: {integrity: sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==} engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true peerDependencies: '@types/node': ^18.0.0 || >=20.0.0 less: '*' @@ -7177,9 +7223,16 @@ packages: terser: optional: true + vitest-mock-extended@2.0.2: + resolution: {integrity: sha512-n3MBqVITKyclZ0n0y66hkT4UiiEYFQn9tteAnIxT0MPz1Z8nFcPUG3Cf0cZOyoPOj/cq6Ab1XFw2lM/qM5EDWQ==} + peerDependencies: + typescript: 3.x || 4.x || 5.x + vitest: '>=2.0.0' + vitest@1.1.3: resolution: {integrity: sha512-2l8om1NOkiA90/Y207PsEvJLYygddsOyr81wLQ20Ra8IlLKbyQncWsGZjnbkyG2KwwuTXLQjEPOJuxGMG8qJBQ==} engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 @@ -7569,7 +7622,7 @@ snapshots: '@babel/traverse': 7.23.9 '@babel/types': 7.23.9 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -7871,7 +7924,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.7 '@babel/types': 7.23.9 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -10975,7 +11028,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.5.3) '@typescript-eslint/utils': 7.7.0(eslint@8.56.0)(typescript@5.5.3) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6 eslint: 8.56.0 ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: @@ -10989,7 +11042,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.7.0 '@typescript-eslint/visitor-keys': 7.7.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 @@ -11377,7 +11430,7 @@ snapshots: agent-base@7.1.0: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -13236,6 +13289,8 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + globrex@0.1.2: {} + gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 @@ -13518,7 +13573,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -15117,7 +15172,7 @@ snapshots: require-in-the-middle@7.3.0: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6 module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -15771,6 +15826,10 @@ snapshots: ts-deepmerge@7.0.0: {} + ts-essentials@10.0.4(typescript@5.5.3): + optionalDependencies: + typescript: 5.5.3 + ts-invariant@0.4.4: dependencies: tslib: 1.14.1 @@ -15842,6 +15901,10 @@ snapshots: optionalDependencies: '@swc/core': 1.6.5 + tsconfck@3.1.4(typescript@5.5.3): + optionalDependencies: + typescript: 5.5.3 + tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 @@ -16138,6 +16201,17 @@ snapshots: - supports-color - terser + vite-tsconfig-paths@5.1.4(typescript@5.5.3)(vite@5.0.11(@types/node@20.10.6)): + dependencies: + debug: 4.3.6 + globrex: 0.1.2 + tsconfck: 3.1.4(typescript@5.5.3) + optionalDependencies: + vite: 5.0.11(@types/node@20.10.6) + transitivePeerDependencies: + - supports-color + - typescript + vite@5.0.11(@types/node@20.10.6): dependencies: esbuild: 0.19.11 @@ -16147,6 +16221,12 @@ snapshots: '@types/node': 20.10.6 fsevents: 2.3.3 + vitest-mock-extended@2.0.2(typescript@5.5.3)(vitest@1.1.3(@types/node@20.10.6)): + dependencies: + ts-essentials: 10.0.4(typescript@5.5.3) + typescript: 5.5.3 + vitest: 1.1.3(@types/node@20.10.6) + vitest@1.1.3(@types/node@20.10.6): dependencies: '@vitest/expect': 1.1.3 diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index c5ebd562..a4106112 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -40,46 +40,55 @@ export class MetadataController extends Controller { message: "Validation failed", errors: { metadata: "Invalid metadata." }, }) - public async storeMetadata( - @Body() requestBody: StoreMetadataRequest, - ): Promise { + public async storeMetadata(@Body() requestBody: StoreMetadataRequest) { const storage = await StorageService.init(); - const metadataValidationResult = validateMetadataAndClaimdata( - requestBody.metadata, - ); + const { metadata } = requestBody; - if (!metadataValidationResult.valid) { - this.setStatus(422); - return { - success: false, - message: "Validation failed", - errors: metadataValidationResult.errors, - }; - } - - if (requestBody.metadata.allowList) { - const allowListValidationResult = await validateRemoteAllowList( - requestBody.metadata.allowList, - ); - - if (!allowListValidationResult.valid) { + try { + const metadataValidationResult = validateMetadataAndClaimdata(metadata); + if (!metadataValidationResult.valid) { this.setStatus(422); return { success: false, - message: "Errors while validating allow list", - errors: allowListValidationResult.errors, + valid: false, + message: "Errors while validating metadata", + errors: metadataValidationResult.errors, }; } - } - const cid = await storage.uploadFile({ - file: jsonToBlob(metadataValidationResult.data), - }); - this.setStatus(201); - return { - success: true, - data: cid, - }; + // Validate allowlist separately if it exists + if (metadata.allowList) { + const allowListValidationResult = await validateRemoteAllowList( + metadata.allowList, + ); + if (!allowListValidationResult.valid) { + this.setStatus(422); + return { + success: false, + valid: false, + message: "Errors while validating allow list", + errors: allowListValidationResult.errors, + }; + } + } + + const cid = await storage.uploadFile({ + file: jsonToBlob(metadataValidationResult.data), + }); + this.setStatus(201); + + return { + success: true, + data: cid, + }; + } catch (e) { + this.setStatus(422); + return { + success: false, + message: "Error while storing metadata", + errors: { metadata: (e as Error).message }, + }; + } } /** diff --git a/test/api/v1/metadata.test.ts b/test/api/v1/metadata.test.ts index d482cf2d..707ff64d 100644 --- a/test/api/v1/metadata.test.ts +++ b/test/api/v1/metadata.test.ts @@ -1,153 +1,94 @@ -import { describe, it, afterEach, afterAll } from "vitest"; +import { describe, test, vi } from "vitest"; import { expect } from "chai"; -import { createMocks, RequestMethod } from "node-mocks-http"; -import { Request, Response } from "express"; - -import sinon from "sinon"; - -import { data } from "../../test-utils"; -import { Client } from "@web3-storage/w3up-client"; -import axios from "axios"; -import { metadataHandler } from "@/handlers/v1/web3up/metadata"; -import { AnyLink } from "@web3-storage/w3up-client/dist/src/types"; - -describe("W3Up Client metadata", async () => { - const { metadata, merkleTree, someData } = data; - - const storeBlobMock = sinon - .stub(Client.prototype, "uploadFile") - .resolves({ "/": metadata.cid } as unknown as AnyLink); //TODO better Link object creation - - const getAllowlistMock = sinon.stub(axios, "get"); - - const mockRequestResponse = (method: RequestMethod = "POST") => { - const { req, res }: { req: Request; res: Response } = createMocks({ - method, - }); - req.headers = { - "Content-Type": "application/json", - }; - req.body = metadata.data; - return { req, res }; +import { mock } from "vitest-mock-extended"; +import { StorageService } from "../../../src/services/StorageService.js"; +import { MetadataController } from "../../../src/controllers/MetadataController.js"; +import { + incorrectMetadata, + mockMetadata, +} from "../../test-utils/mockMetadata.js"; + +const mocks = vi.hoisted(() => { + return { + init: vi.fn(), }; +}); - afterEach(() => { - sinon.resetHistory(); - }); +vi.mock("../../../src/services/StorageService", async () => { + return { + StorageService: { init: mocks.init }, + }; +}); - afterAll(() => { - sinon.resetBehavior(); - }); +describe("Metadata upload at v1/metadata", async () => { + const controller = new MetadataController(); + const mockStorage = mock(); - it("POST valid metadata without allowList - 200", async () => { - const { req, res } = mockRequestResponse(); - await metadataHandler(req, res); + test("Stores a new metadata object and returns CID", async () => { + mocks.init.mockResolvedValue(mockStorage); - expect(res.statusCode).to.eq(200); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); + const response = await controller.storeMetadata({ metadata: mockMetadata }); + expect(response.success).to.be.true; + expect(response.data).to.not.be.undefined; + expect(response.data?.cid).to.eq("TEST_CID"); + }); - //TODO better typing and check on returned CID + test("Returns errors and message when metadata is invalid", async () => { + mocks.init.mockResolvedValue(mockStorage); - console.log(res); - // @ts-ignore - expect(res._getJSONData().message).to.eq("Data uploaded succesfully"); - // @ts-ignore - expect(res._getJSONData().cid).to.not.be.undefined; + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); + const response = await controller.storeMetadata({ + metadata: incorrectMetadata, + }); - expect(storeBlobMock.callCount).to.eq(1); - expect(getAllowlistMock.callCount).to.eq(0); + expect(response.success).to.be.false; + expect(response.data).to.be.undefined; + expect(response.message).to.eq("Errors while validating metadata"); + expect(response.errors).to.deep.eq({ + metadata: "Provided metadata is not a valid hypercert metadata object", + }); }); - it("POST valid metadata with allowList - 200", async () => { - const { req, res } = mockRequestResponse(); - req.body = { ...req.body, allowList: someData.cid }; - getAllowlistMock.resolves(Promise.resolve({ data: merkleTree.data })); + test("Handles errors during upload", async () => { + mocks.init.mockResolvedValue(mockStorage); - await metadataHandler(req, res); + const mockError = new Error("Error uploading data"); - expect(res.statusCode).to.eq(200); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); - - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Data uploaded succesfully"); - // @ts-ignore - expect(res._getJSONData().cid).to.not.be.undefined; - - expect(storeBlobMock.callCount).to.eq(1); - expect(getAllowlistMock.callCount).to.eq(1); + mockStorage.uploadFile.mockRejectedValue(mockError); + const response = await controller.storeMetadata({ metadata: mockMetadata }); + expect(response.success).to.be.false; + expect(response.data).to.be.undefined; + expect(response.errors).to.deep.eq({ + metadata: "Error uploading data", + }); }); +}); - it("GET metadata not allowed - 405", async () => { - const { req, res } = mockRequestResponse(); - req.method = "GET"; - await metadataHandler(req, res); +describe("Metadata validation at v1/metadata/validate", async () => { + const controller = new MetadataController(); - expect(res.statusCode).to.eq(405); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + test("Validates a metadata set and returns results", async () => { + const requestBody = mockMetadata; - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Not allowed"); + const response = await controller.validateMetadata(requestBody); - expect(storeBlobMock.callCount).to.eq(0); + expect(response.valid).to.be.true; + expect(response.success).to.be.true; + expect(response.message).to.be.eq("Metadata is valid hypercert metadata"); }); - it("POST incorrect metadata - 400", async () => { - const { req, res } = mockRequestResponse(); - req.body = data.someData.data; - await metadataHandler(req, res); + test("Returns errors and message when metadata is invalid", async () => { + const requestBody = incorrectMetadata; - expect(res.statusCode).to.eq(400); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + const response = await controller.validateMetadata(requestBody); - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq( - "Not a valid hypercert metadata object", + expect(response.success).to.be.true; + expect(response.message).to.eq( + "Errors while validating metadata and/or allow list", ); - }); - - it("POST correct metadata with incorrect allowlist - 400", async () => { - const { req, res } = mockRequestResponse(); - req.body = { ...req.body, allowList: someData.cid }; - getAllowlistMock.resolves(Promise.resolve({ data: "not a merkle tree" })); - await metadataHandler(req, res); - - expect(res.statusCode).to.eq(400); - expect(res.getHeaders()).to.deep.eq({ - "content-type": "application/json", + expect(response.errors).to.deep.eq({ + metadata: "Provided metadata is not a valid hypercert metadata object", }); - expect(res.statusMessage).to.eq("OK"); - - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq( - "Allowlist should be a valid openzeppelin merkle tree", - ); - - expect(storeBlobMock.callCount).to.eq(0); - expect(getAllowlistMock.callCount).to.eq(1); - }); - - it("POST upload metadata fails - 500", async () => { - const { req, res } = mockRequestResponse(); - storeBlobMock.rejects(); - await metadataHandler(req, res); - - expect(res.statusCode).to.eq(500); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); - - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Error uploading data"); - - expect(storeBlobMock.callCount).to.eq(1); - expect(getAllowlistMock.callCount).to.eq(0); }); }); diff --git a/test/test-utils/mockMetadata.ts b/test/test-utils/mockMetadata.ts index aa8b48eb..4430e4fd 100644 --- a/test/test-utils/mockMetadata.ts +++ b/test/test-utils/mockMetadata.ts @@ -64,4 +64,9 @@ const jsonContent = `{ } }`; -export const mockMetadata = JSON.parse(jsonContent); +const mockMetadata = JSON.parse(jsonContent); + +const incorrectMetadata = JSON.parse(jsonContent); +incorrectMetadata.hypercert = ""; + +export { mockMetadata, incorrectMetadata }; From 5054fee59edc558e9463d4f1130fd29403f62567 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 21:00:28 +0100 Subject: [PATCH 02/11] fix(metadata.test.ts): metadata validation handling and responses Restores the metadata validation endpoint testing. Additionally the full metadata controller is using try catches and we updated the response types. The response types have been made more simple and flexible, for one to support the cases of validation where the processing can be successful but the data is invalid. Lastly, responses where data is set as null were cleaned so simply not return data. --- src/controllers/HyperboardController.ts | 18 +- src/controllers/MetadataController.ts | 221 ++++++++++++++---------- src/types/api.ts | 55 +++--- test/api/v1/metadata.test.ts | 17 +- 4 files changed, 166 insertions(+), 145 deletions(-) diff --git a/src/controllers/HyperboardController.ts b/src/controllers/HyperboardController.ts index 75aa0e43..b92cfc35 100644 --- a/src/controllers/HyperboardController.ts +++ b/src/controllers/HyperboardController.ts @@ -14,7 +14,7 @@ import { import type { ApiResponse, HyperboardCreateRequest, - HyperboardCreateResponse, + HyperboardResponse, HyperboardUpdateRequest, } from "../types/api.js"; import { z } from "zod"; @@ -43,7 +43,7 @@ export class HyperboardController extends Controller { }) public async createHyperboard( @Body() requestBody: HyperboardCreateRequest, - ): Promise { + ): Promise { const inputSchema = z .object({ chainIds: z @@ -175,7 +175,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Invalid input", - data: null, errors: JSON.parse(parsedBody.error.toString()), }; } @@ -228,7 +227,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Invalid signature", - data: null, }; } @@ -262,7 +260,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error creating hyperboard", - data: null, }; } @@ -355,7 +352,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error updating collection", - data: null, }; } } @@ -430,7 +426,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error creating collection", - data: null, }; } } @@ -453,7 +448,7 @@ export class HyperboardController extends Controller { public async updateHyperboard( @Path() hyperboardId: string, @Body() requestBody: HyperboardUpdateRequest, - ): Promise> { + ): Promise { const inputSchema = z .object({ id: z.string().uuid(), @@ -590,7 +585,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Invalid input", - data: null, errors: JSON.parse(parsedBody.error.toString()), }; } @@ -603,7 +597,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Hyperboard not found", - data: null, }; } @@ -657,7 +650,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Invalid signature", - data: null, }; } @@ -671,7 +663,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Not authorized to update hyperboard", - data: null, }; } @@ -692,7 +683,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error updating hyperboard", - data: null, }; } @@ -794,7 +784,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error updating collection", - data: null, }; } } @@ -867,7 +856,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error creating collection", - data: null, }; } } diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index a4106112..6c045bc2 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -116,57 +116,67 @@ export class MetadataController extends Controller { @Body() requestBody: StoreMetadataWithAllowlistRequest, ): Promise { const storage = await StorageService.init(); - const metadataValidationResult = validateMetadataAndClaimdata( - requestBody.metadata, - ); + const { metadata } = requestBody; + const { allowList, totalUnits } = requestBody; - if (!metadataValidationResult.valid) { - this.setStatus(422); - return { - success: false, - message: "Validation failed", - errors: metadataValidationResult.errors, - }; - } + try { + const metadataValidationResult = validateMetadataAndClaimdata(metadata); - if (requestBody.metadata.allowList) { - this.setStatus(409); - return { - success: false, - message: "Allow list detected in metadata", - errors: { metadata: "Allowlist URI already present in metadata." }, - }; - } + if (!metadataValidationResult.valid) { + this.setStatus(422); + return { + success: false, + message: "Validation failed", + errors: metadataValidationResult.errors, + }; + } + + if (allowList) { + this.setStatus(409); + return { + success: false, + message: "Allow list detected in metadata", + errors: { metadata: "Allowlist URI already present in metadata." }, + }; + } + + const allowlistValidationResult = parseAndValidateMerkleTree({ + allowList, + totalUnits, + }); + + if (!allowlistValidationResult.valid) { + this.setStatus(422); + return { + success: false, + message: "Validation failed", + errors: allowlistValidationResult.errors, + }; + } - const allowlistValidationResult = parseAndValidateMerkleTree({ - allowList: requestBody.allowList, - totalUnits: requestBody?.totalUnits, - }); + const uploadResult = await storage.uploadFile({ + file: jsonToBlob(requestBody.allowList), + }); + const cid = await storage.uploadFile({ + file: jsonToBlob({ + ...metadataValidationResult.data, + allowList: `ipfs://${uploadResult.cid}`, + }), + }); - if (!allowlistValidationResult.valid) { + this.setStatus(201); + return { + success: true, + data: cid, + }; + } catch (e) { this.setStatus(422); return { success: false, - message: "Validation failed", - errors: allowlistValidationResult.errors, + message: "Error while storing metadata", + errors: { metadata: (e as Error).message }, }; } - - const uploadResult = await storage.uploadFile({ - file: jsonToBlob(requestBody.allowList), - }); - const cid = await storage.uploadFile({ - file: jsonToBlob({ - ...metadataValidationResult.data, - allowList: `ipfs://${uploadResult.cid}`, - }), - }); - - this.setStatus(201); - return { - success: true, - data: cid, - }; } /** @@ -185,39 +195,53 @@ export class MetadataController extends Controller { public async validateMetadata( @Body() requestBody: ValidateMetadataRequest, ): Promise { - const metadataValidationResult = validateMetadataAndClaimdata( - requestBody.metadata, - ); - - if (!metadataValidationResult.valid) { - this.setStatus(422); - return { - success: false, - message: "Errors while validating metadata or allow list", - errors: metadataValidationResult.errors, - }; - } + const { metadata } = requestBody; - if (requestBody.metadata.allowList) { - const allowListValidationResult = await validateRemoteAllowList( - requestBody.metadata.allowList, - ); + try { + const metadataValidationResult = validateMetadataAndClaimdata(metadata); - if (!allowListValidationResult.valid) { + if (!metadataValidationResult.valid) { this.setStatus(422); return { - success: false, - message: "Errors while validating allow list reference in metadata", - errors: allowListValidationResult.errors, + success: true, + valid: false, + message: "Errors while validating metadata", + errors: metadataValidationResult.errors, }; } - } - this.setStatus(200); - return { - success: true, - message: "Validation successful", - }; + if (metadata.allowList) { + const allowListValidationResult = await validateRemoteAllowList( + metadata.allowList, + ); + + if (!allowListValidationResult.valid) { + this.setStatus(422); + return { + success: true, + valid: false, + message: + "Errors while validating allow list referenced in metadata", + errors: allowListValidationResult.errors, + }; + } + } + + this.setStatus(200); + return { + success: true, + valid: true, + message: "Metadata is valid hypercert metadata", + }; + } catch (e) { + this.setStatus(422); + return { + success: false, + valid: false, + message: "Error while validating metadata", + errors: { metadata: (e as Error).message }, + }; + } } /** @@ -236,37 +260,50 @@ export class MetadataController extends Controller { public async validateMetadataWithAllowlist( @Body() requestBody: StoreMetadataWithAllowlistRequest, ): Promise { - const metadataValidationResult = validateMetadataAndClaimdata( - requestBody.metadata, - ); + const { metadata, allowList, totalUnits } = requestBody; - if (!metadataValidationResult.valid) { - this.setStatus(422); - return { - success: false, - message: "Validation failed", - errors: metadataValidationResult.errors, - }; - } + try { + const metadataValidationResult = validateMetadataAndClaimdata(metadata); + + if (!metadataValidationResult.valid) { + this.setStatus(422); + return { + success: true, + valid: false, + message: "Validation failed", + errors: metadataValidationResult.errors, + }; + } + + const allowlistValidationResult = parseAndValidateMerkleTree({ + allowList, + totalUnits, + }); - const allowlistValidationResult = parseAndValidateMerkleTree({ - allowList: requestBody.allowList, - totalUnits: requestBody?.totalUnits, - }); + if (!allowlistValidationResult.valid) { + this.setStatus(422); + return { + success: true, + valid: false, + message: "Validation failed", + errors: allowlistValidationResult.errors, + }; + } - if (!allowlistValidationResult.valid) { + this.setStatus(200); + return { + success: true, + valid: true, + message: "Metadata is valid hypercert metadata", + }; + } catch (e) { this.setStatus(422); return { success: false, - message: "Validation failed", - errors: allowlistValidationResult.errors, + valid: false, + message: "Error while validating metadata", + errors: { metadata: (e as Error).message }, }; } - - this.setStatus(200); - return { - success: true, - message: "Validation successful", - }; } } diff --git a/src/types/api.ts b/src/types/api.ts index 0fe8a2ae..9dc50dae 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -1,5 +1,23 @@ import type { HypercertMetadata } from "@hypercerts-org/sdk"; +// Base response type for all API responses +export interface ApiResponse { + success: boolean; // Whether the API call itself succeeded + message?: string; // Human readable message about the operation + errors?: Record; // Any errors that occurred +} + +// Response for operations that return data +export interface DataResponse extends ApiResponse { + data?: T; +} + +// Response specifically for validation operations +export interface ValidationResponse extends ApiResponse { + valid: boolean; // Whether the validated content is valid + data?: unknown; // Optional validated/transformed data +} + /** * Interface for storing metadata on IPFS. */ @@ -44,34 +62,18 @@ export interface ValidateMetadataWithAllowlistRequest extends ValidateMetadataRequest, ValidateAllowListRequest {} -/** - * Interface for a generic API response. - */ -export type ApiResponse = { - success: boolean; - data?: T; - message?: string; - errors?: Record | Error[]; -}; - /** * Interface for a storage response. */ -export type StorageResponse = ApiResponse<{ cid: string }>; - -/** - * Interface for a validation response. - */ -export type ValidationResult = { - valid: boolean; - data?: T; - errors?: Record; -}; +export interface StorageResponse extends DataResponse<{ cid: string }> {} /** * Interface for a validation response. */ -export type ValidationResponse = ApiResponse; +export interface ValidationResponse extends ApiResponse { + valid: boolean; // Whether the validated content is valid + data?: unknown; // Optional validated/transformed data +} /** * Interface for a user add or update request. @@ -84,14 +86,13 @@ export interface AddOrUpdateUserRequest { chain_id: number; } -export type AddOrUpdateUserResponse = ApiResponse<{ address: string } | null>; +export interface UserResponse extends DataResponse<{ address: string }> {} /** * Interface for a blueprint add or update request. */ -export type AddOrCreateBlueprintResponse = ApiResponse<{ - blueprint_id: number; -} | null>; +export interface BlueprintResponse + extends DataResponse<{ blueprint_id: number }> {} export type BlueprintDeleteRequest = { signature: string; @@ -102,9 +103,7 @@ export type BlueprintDeleteRequest = { /** * Response for a created hyperboard */ -export type HyperboardCreateResponse = ApiResponse<{ - id: string; -} | null>; +export interface HyperboardResponse extends DataResponse<{ id: string }> {} /** * Interface for creating a hyperboard diff --git a/test/api/v1/metadata.test.ts b/test/api/v1/metadata.test.ts index 707ff64d..147bcc54 100644 --- a/test/api/v1/metadata.test.ts +++ b/test/api/v1/metadata.test.ts @@ -69,24 +69,21 @@ describe("Metadata validation at v1/metadata/validate", async () => { const controller = new MetadataController(); test("Validates a metadata set and returns results", async () => { - const requestBody = mockMetadata; - - const response = await controller.validateMetadata(requestBody); + const response = await controller.validateMetadata({ + metadata: mockMetadata, + }); - expect(response.valid).to.be.true; expect(response.success).to.be.true; expect(response.message).to.be.eq("Metadata is valid hypercert metadata"); }); test("Returns errors and message when metadata is invalid", async () => { - const requestBody = incorrectMetadata; - - const response = await controller.validateMetadata(requestBody); + const response = await controller.validateMetadata({ + metadata: incorrectMetadata, + }); expect(response.success).to.be.true; - expect(response.message).to.eq( - "Errors while validating metadata and/or allow list", - ); + expect(response.message).to.eq("Errors while validating metadata"); expect(response.errors).to.deep.eq({ metadata: "Provided metadata is not a valid hypercert metadata object", }); From 5fb083019f5847d6002cdb2a1a5e2c8fc42d8566 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 21:34:29 +0100 Subject: [PATCH 03/11] fix(types): cleanup controller types Update all controllers to use the updated types structure. Rebuild the updated API routes and Swagger. --- src/__generated__/routes/routes.ts | 115 +++---- src/__generated__/swagger.json | 398 ++++++++++------------- src/controllers/AllowListController.ts | 47 ++- src/controllers/BlueprintController.ts | 16 +- src/controllers/HyperboardController.ts | 10 +- src/controllers/MarketplaceController.ts | 10 +- src/controllers/MetadataController.ts | 12 +- src/controllers/UserController.ts | 12 +- src/types/api.ts | 98 ++---- 9 files changed, 322 insertions(+), 396 deletions(-) diff --git a/src/__generated__/routes/routes.ts b/src/__generated__/routes/routes.ts index 0ea8b9ca..e10dd819 100644 --- a/src/__generated__/routes/routes.ts +++ b/src/__generated__/routes/routes.ts @@ -26,29 +26,25 @@ const models: TsoaRoute.Models = { "type": {"dataType":"nestedObjectLiteral","nestedProperties":{},"additionalProperties":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"array","array":{"dataType":"string"}}]},"validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "Error": { + "UserResponse": { "dataType": "refObject", "properties": { - "name": {"dataType":"string","required":true}, - "message": {"dataType":"string","required":true}, - "stack": {"dataType":"string"}, + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "data": {"dataType":"nestedObjectLiteral","nestedProperties":{"address":{"dataType":"string","required":true}}}, }, "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse__address-string_-or-null_": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"address":{"dataType":"string","required":true}}},{"dataType":"enum","enums":[null]}]},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AddOrUpdateUserResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse__address-string_-or-null_","validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"void"},"success":{"dataType":"boolean","required":true}},"validators":{}}, + "BaseResponse": { + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "AddOrUpdateUserRequest": { @@ -62,16 +58,6 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse__cid-string__": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"nestedObjectLiteral","nestedProperties":{"cid":{"dataType":"string","required":true}}},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "StorageResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse__cid-string__","validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "HypercertClaimdata": { "dataType": "refObject", "properties": { @@ -109,6 +95,17 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "StorageResponse": { + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "data": {"dataType":"nestedObjectLiteral","nestedProperties":{"cid":{"dataType":"string","required":true}}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "StoreMetadataWithAllowlistRequest": { "dataType": "refObject", "properties": { @@ -119,19 +116,16 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ValidationResult": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"ref":"Record_string.string-or-string-Array_"},"data":{"dataType":"void"},"valid":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse_ValidationResult_": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"ref":"ValidationResult"},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ValidationResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse_ValidationResult_","validators":{}}, + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "valid": {"dataType":"boolean","required":true}, + "data": {"dataType":"any"}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ValidateMetadataRequest": { @@ -199,14 +193,15 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse__id-string_-or-null_": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"id":{"dataType":"string","required":true}}},{"dataType":"enum","enums":[null]}]},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "HyperboardCreateResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse__id-string_-or-null_","validators":{}}, + "HyperboardResponse": { + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "data": {"dataType":"nestedObjectLiteral","nestedProperties":{"id":{"dataType":"string","required":true}}}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "HyperboardCreateRequest": { @@ -238,14 +233,15 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse__blueprint_id-number_-or-null_": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"blueprint_id":{"dataType":"double","required":true}}},{"dataType":"enum","enums":[null]}]},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AddOrCreateBlueprintResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse__blueprint_id-number_-or-null_","validators":{}}, + "BlueprintResponse": { + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "data": {"dataType":"nestedObjectLiteral","nestedProperties":{"blueprint_id":{"dataType":"double","required":true}}}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "BlueprintCreateRequest": { @@ -261,8 +257,13 @@ const models: TsoaRoute.Models = { }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "BlueprintDeleteRequest": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"admin_address":{"dataType":"string","required":true},"chain_id":{"dataType":"double","required":true},"signature":{"dataType":"string","required":true}},"validators":{}}, + "dataType": "refObject", + "properties": { + "signature": {"dataType":"string","required":true}, + "chain_id": {"dataType":"double","required":true}, + "admin_address": {"dataType":"string","required":true}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "BlueprintQueueMintRequest": { diff --git a/src/__generated__/swagger.json b/src/__generated__/swagger.json index 1167c4b1..13411125 100644 --- a/src/__generated__/swagger.json +++ b/src/__generated__/swagger.json @@ -25,42 +25,16 @@ "type": "object", "description": "Construct a type with a set of properties K of type T" }, - "Error": { + "UserResponse": { "properties": { - "name": { - "type": "string" + "success": { + "type": "boolean" }, "message": { "type": "string" }, - "stack": { - "type": "string" - } - }, - "required": [ - "name", - "message" - ], - "type": "object", - "additionalProperties": false - }, - "ApiResponse__address-string_-or-null_": { - "properties": { "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] - }, - "message": { - "type": "string" + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" }, "data": { "properties": { @@ -71,53 +45,34 @@ "required": [ "address" ], - "type": "object", - "nullable": true - }, - "success": { - "type": "boolean" + "type": "object" } }, "required": [ "success" ], "type": "object", - "description": "Interface for a generic API response." - }, - "AddOrUpdateUserResponse": { - "$ref": "#/components/schemas/ApiResponse__address-string_-or-null_" + "additionalProperties": false }, - "ApiResponse": { + "BaseResponse": { "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] + "success": { + "type": "boolean" }, "message": { "type": "string" }, - "data": {}, - "success": { - "type": "boolean" + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" } }, "required": [ "success" ], "type": "object", - "description": "Interface for a generic API response." + "additionalProperties": false }, "AddOrUpdateUserRequest": { - "description": "Interface for a user add or update request.", "properties": { "display_name": { "type": "string" @@ -142,49 +97,6 @@ "type": "object", "additionalProperties": false }, - "ApiResponse__cid-string__": { - "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] - }, - "message": { - "type": "string" - }, - "data": { - "properties": { - "cid": { - "type": "string" - } - }, - "required": [ - "cid" - ], - "type": "object" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "success" - ], - "type": "object", - "description": "Interface for a generic API response." - }, - "StorageResponse": { - "$ref": "#/components/schemas/ApiResponse__cid-string__", - "description": "Interface for a storage response." - }, "HypercertClaimdata": { "description": "Properties of an impact claim", "properties": { @@ -392,7 +304,6 @@ "additionalProperties": false }, "StoreMetadataRequest": { - "description": "Interface for storing metadata on IPFS.", "properties": { "metadata": { "$ref": "#/components/schemas/HypercertMetadata" @@ -404,8 +315,36 @@ "type": "object", "additionalProperties": false }, + "StorageResponse": { + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" + }, + "data": { + "properties": { + "cid": { + "type": "string" + } + }, + "required": [ + "cid" + ], + "type": "object" + } + }, + "required": [ + "success" + ], + "type": "object", + "additionalProperties": false + }, "StoreMetadataWithAllowlistRequest": { - "description": "Interface for storing metadata and allow list dump on IPFS.", "properties": { "metadata": { "$ref": "#/components/schemas/HypercertMetadata" @@ -424,59 +363,30 @@ "type": "object", "additionalProperties": false }, - "ValidationResult": { + "ValidationResponse": { "properties": { - "errors": { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - "data": {}, - "valid": { + "success": { "type": "boolean" - } - }, - "required": [ - "valid" - ], - "type": "object", - "description": "Interface for a validation response." - }, - "ApiResponse_ValidationResult_": { - "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] }, "message": { "type": "string" }, - "data": { - "$ref": "#/components/schemas/ValidationResult" + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" }, - "success": { + "valid": { "type": "boolean" - } + }, + "data": {} }, "required": [ - "success" + "success", + "valid" ], "type": "object", - "description": "Interface for a generic API response." - }, - "ValidationResponse": { - "$ref": "#/components/schemas/ApiResponse_ValidationResult_", - "description": "Interface for a validation response." + "additionalProperties": false }, "ValidateMetadataRequest": { - "description": "Interface for validating metadata.", "properties": { "metadata": { "$ref": "#/components/schemas/HypercertMetadata" @@ -672,24 +582,17 @@ "type": "object", "additionalProperties": false }, - "ApiResponse__id-string_-or-null_": { + "HyperboardResponse": { "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] + "success": { + "type": "boolean" }, "message": { "type": "string" }, + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" + }, "data": { "properties": { "id": { @@ -699,25 +602,16 @@ "required": [ "id" ], - "type": "object", - "nullable": true - }, - "success": { - "type": "boolean" + "type": "object" } }, "required": [ "success" ], "type": "object", - "description": "Interface for a generic API response." - }, - "HyperboardCreateResponse": { - "$ref": "#/components/schemas/ApiResponse__id-string_-or-null_", - "description": "Response for a created hyperboard" + "additionalProperties": false }, "HyperboardCreateRequest": { - "description": "Interface for creating a hyperboard", "properties": { "chainIds": { "items": { @@ -816,7 +710,6 @@ "additionalProperties": false }, "HyperboardUpdateRequest": { - "description": "Interface for updating a hyperboard", "properties": { "chainIds": { "items": { @@ -918,24 +811,17 @@ "type": "object", "additionalProperties": false }, - "ApiResponse__blueprint_id-number_-or-null_": { + "BlueprintResponse": { "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] + "success": { + "type": "boolean" }, "message": { "type": "string" }, + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" + }, "data": { "properties": { "blueprint_id": { @@ -946,22 +832,14 @@ "required": [ "blueprint_id" ], - "type": "object", - "nullable": true - }, - "success": { - "type": "boolean" + "type": "object" } }, "required": [ "success" ], "type": "object", - "description": "Interface for a generic API response." - }, - "AddOrCreateBlueprintResponse": { - "$ref": "#/components/schemas/ApiResponse__blueprint_id-number_-or-null_", - "description": "Interface for a blueprint add or update request." + "additionalProperties": false }, "BlueprintCreateRequest": { "properties": { @@ -992,23 +870,24 @@ }, "BlueprintDeleteRequest": { "properties": { - "admin_address": { + "signature": { "type": "string" }, "chain_id": { "type": "number", "format": "double" }, - "signature": { + "admin_address": { "type": "string" } }, "required": [ - "admin_address", + "signature", "chain_id", - "signature" + "admin_address" ], - "type": "object" + "type": "object", + "additionalProperties": false }, "BlueprintQueueMintRequest": { "properties": { @@ -1036,7 +915,6 @@ "additionalProperties": false }, "StoreAllowListRequest": { - "description": "Interface for storing an allow list dump on IPFS", "properties": { "allowList": { "type": "string" @@ -1052,7 +930,6 @@ "additionalProperties": false }, "ValidateAllowListRequest": { - "description": "Interface for validating an allow list dump.", "properties": { "allowList": { "type": "string" @@ -1088,7 +965,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddOrUpdateUserResponse" + "$ref": "#/components/schemas/UserResponse" } } } @@ -1098,7 +975,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1148,7 +1025,85 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StorageResponse" + "anyOf": [ + { + "properties": { + "data": {}, + "errors": {}, + "message": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "errors", + "message", + "valid", + "success" + ], + "type": "object" + }, + { + "properties": { + "errors": {}, + "message": {}, + "valid": {}, + "data": { + "properties": { + "cid": { + "type": "string" + } + }, + "required": [ + "cid" + ], + "type": "object" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "data", + "success" + ], + "type": "object" + }, + { + "properties": { + "data": {}, + "valid": {}, + "errors": { + "properties": { + "metadata": { + "type": "string" + } + }, + "required": [ + "metadata" + ], + "type": "object" + }, + "message": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "errors", + "message", + "success" + ], + "type": "object" + } + ] } } } @@ -1158,7 +1113,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1214,7 +1169,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1235,7 +1190,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1291,7 +1246,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1347,7 +1302,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1604,7 +1559,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1667,7 +1622,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1768,7 +1723,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1857,7 +1812,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1898,7 +1853,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HyperboardCreateResponse" + "$ref": "#/components/schemas/HyperboardResponse" } } } @@ -1908,7 +1863,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1949,7 +1904,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse__id-string_-or-null_" + "$ref": "#/components/schemas/HyperboardResponse" } } } @@ -1959,7 +1914,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2006,7 +1961,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" } } } @@ -2016,7 +1971,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2071,7 +2026,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddOrCreateBlueprintResponse" + "$ref": "#/components/schemas/BlueprintResponse" } } } @@ -2081,7 +2036,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BlueprintResponse" }, "examples": { "Example 1": { @@ -2229,7 +2184,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2282,7 +2237,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddOrCreateBlueprintResponse" + "$ref": "#/components/schemas/BlueprintResponse" } } } @@ -2292,7 +2247,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2355,7 +2310,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/StorageResponse" }, "examples": { "Example 1": { @@ -2409,12 +2364,13 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/ValidationResponse" }, "examples": { "Example 1": { "value": { "success": false, + "valid": false, "message": "Metadata validation failed", "errors": { "allowList": "Invalid allowList. Length is 0" diff --git a/src/controllers/AllowListController.ts b/src/controllers/AllowListController.ts index 4c4a578f..2de428c9 100644 --- a/src/controllers/AllowListController.ts +++ b/src/controllers/AllowListController.ts @@ -1,19 +1,25 @@ import { jsonToBlob } from "../utils/jsonToBlob.js"; -import { Body, Controller, Post, Response, Route, SuccessResponse, Tags } from "tsoa"; +import { + Body, + Controller, + Post, + Response, + Route, + SuccessResponse, + Tags, +} from "tsoa"; import { StorageService } from "../services/StorageService.js"; import { parseAndValidateMerkleTree } from "../utils/parseAndValidateMerkleTreeDump.js"; import type { - ApiResponse, StorageResponse, StoreAllowListRequest, ValidateAllowListRequest, - ValidationResponse + ValidationResponse, } from "../types/api.js"; @Route("v1/allowlists") @Tags("Allowlists") export class AllowListController extends Controller { - /** * Submits a new allowlist for validation and storage on IPFS. While we maintain a database of allowlists, the allowlist itself is stored on IPFS. * Try to keep a backup of the allowlist for recovery purposes. @@ -22,12 +28,14 @@ export class AllowListController extends Controller { */ @Post() @SuccessResponse(201, "Data uploaded successfully", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while validating allow list", - errors: { allowList: "Invalid allowList. Length is 0" } + errors: { allowList: "Invalid allowList. Length is 0" }, }) - public async storeAllowList(@Body() requestBody: StoreAllowListRequest): Promise { + public async storeAllowList( + @Body() requestBody: StoreAllowListRequest, + ): Promise { const storage = await StorageService.init(); const result = parseAndValidateMerkleTree(requestBody); @@ -37,16 +45,18 @@ export class AllowListController extends Controller { return { success: false, message: "Errors while validating allow list", - errors: result.errors + errors: result.errors, }; } - const cid = await storage.uploadFile({ file: jsonToBlob(requestBody.allowList) }); + const cid = await storage.uploadFile({ + file: jsonToBlob(requestBody.allowList), + }); this.setStatus(201); return { success: true, - data: cid + data: cid, }; } @@ -57,26 +67,31 @@ export class AllowListController extends Controller { */ @Post("/validate") @SuccessResponse(200, "Valid allowlist object", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, + valid: false, message: "Metadata validation failed", - errors: { allowList: "Invalid allowList. Length is 0" } + errors: { allowList: "Invalid allowList. Length is 0" }, }) - public async validateAllowList(@Body() requestBody: ValidateAllowListRequest): Promise { + public async validateAllowList( + @Body() requestBody: ValidateAllowListRequest, + ): Promise { const result = parseAndValidateMerkleTree(requestBody); if (!result.valid || !result.data) { this.setStatus(422); return { success: false, + valid: false, message: "Errors while validating allow list", - errors: result.errors + errors: result.errors, }; } this.setStatus(201); return { - success: true + success: true, + valid: true, }; } -} \ No newline at end of file +} diff --git a/src/controllers/BlueprintController.ts b/src/controllers/BlueprintController.ts index 7ec260a8..b2d43736 100644 --- a/src/controllers/BlueprintController.ts +++ b/src/controllers/BlueprintController.ts @@ -10,11 +10,11 @@ import { Tags, } from "tsoa"; import type { - AddOrCreateBlueprintResponse, - ApiResponse, + BlueprintResponse, BlueprintCreateRequest, BlueprintDeleteRequest, BlueprintQueueMintRequest, + BaseResponse, } from "../types/api.js"; import { z } from "zod"; import { SupabaseDataService } from "../services/SupabaseDataService.js"; @@ -29,14 +29,14 @@ import { waitForTxThenMintBlueprint } from "../utils/waitForTxThenMintBlueprint. export class BlueprintController extends Controller { @Post() @SuccessResponse(201, "Blueprint created successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { blueprint: "Invalid blueprint." }, }) public async createBlueprint( @Body() requestBody: BlueprintCreateRequest, - ): Promise { + ): Promise { const inputSchema = z.object({ form_values: z.object({ title: z @@ -131,7 +131,6 @@ export class BlueprintController extends Controller { return { success: false, message: "Invalid input", - data: null, errors: JSON.parse(parsedBody.error.toString()), }; } @@ -212,7 +211,7 @@ export class BlueprintController extends Controller { // Delete blueprint method @Delete("{blueprintId}") @SuccessResponse(200, "Blueprint deleted successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { blueprint: "Invalid blueprint." }, @@ -308,7 +307,7 @@ export class BlueprintController extends Controller { @Post("mint/{blueprintId}") @SuccessResponse(201, "Blueprint minted successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { blueprint: "Invalid blueprint." }, @@ -316,7 +315,7 @@ export class BlueprintController extends Controller { public async mintBlueprint( @Path() blueprintId: number, @Body() requestBody: BlueprintQueueMintRequest, - ): Promise { + ): Promise { const inputSchema = z.object({ signature: z.string(), chain_id: z.number(), @@ -331,7 +330,6 @@ export class BlueprintController extends Controller { return { success: false, message: "Invalid input", - data: null, errors: JSON.parse(parsedBody.error.toString()), }; } diff --git a/src/controllers/HyperboardController.ts b/src/controllers/HyperboardController.ts index b92cfc35..2d51f237 100644 --- a/src/controllers/HyperboardController.ts +++ b/src/controllers/HyperboardController.ts @@ -12,7 +12,7 @@ import { Patch, } from "tsoa"; import type { - ApiResponse, + BaseResponse, HyperboardCreateRequest, HyperboardResponse, HyperboardUpdateRequest, @@ -37,7 +37,7 @@ export class HyperboardController extends Controller { */ @Post() @SuccessResponse(201, "Data uploaded successfully", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while validating hyperboard", }) @@ -441,7 +441,7 @@ export class HyperboardController extends Controller { @Patch("{hyperboardId}") @SuccessResponse(204, "Hyperboard updated successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while updating hyperboard", }) @@ -871,7 +871,7 @@ export class HyperboardController extends Controller { @Delete("{hyperboardId}") @SuccessResponse(204, "Hyperboard deleted successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while deleting hyperboard", }) @@ -879,7 +879,7 @@ export class HyperboardController extends Controller { @Path() hyperboardId: string, @Query() adminAddress: string, @Query() signature: string, - ): Promise { + ): Promise { const inputSchema = z.object({ adminAddress: z.string(), signature: z.string(), diff --git a/src/controllers/MarketplaceController.ts b/src/controllers/MarketplaceController.ts index bcb15aba..e719ef61 100644 --- a/src/controllers/MarketplaceController.ts +++ b/src/controllers/MarketplaceController.ts @@ -15,7 +15,6 @@ import { Tags, } from "tsoa"; import { z } from "zod"; -import { ApiResponse } from "../types/api.js"; import { isAddress, verifyMessage } from "viem"; import { SupabaseDataService } from "../services/SupabaseDataService.js"; @@ -23,6 +22,7 @@ import { getFractionsById } from "../utils/getFractionsById.js"; import { getRpcUrl } from "../utils/getRpcUrl.js"; import { isParsableToBigInt } from "../utils/isParsableToBigInt.js"; import { getHypercertTokenId } from "../utils/tokenIds.js"; +import { BaseResponse } from "../types/api.js"; export interface CreateOrderRequest { signature: string; @@ -63,7 +63,7 @@ export class MarketplaceController extends Controller { */ @Post("/orders") @SuccessResponse(201, "Order created successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Order could not be created", }) @@ -263,7 +263,7 @@ export class MarketplaceController extends Controller { * Updates and returns the order nonce for a user on a specific chain. */ @Post("/order-nonce") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Order nonce could not be updated", }) @@ -360,7 +360,7 @@ export class MarketplaceController extends Controller { */ @Post("/orders/validate") @SuccessResponse(200, "Order validated successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Order could not be validated", }) @@ -411,7 +411,7 @@ export class MarketplaceController extends Controller { */ @Delete("/orders") @SuccessResponse(200, "Order deleted successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Order could not be deleted", }) diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index 6c045bc2..e6688648 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -10,7 +10,7 @@ import { } from "tsoa"; import { StorageService } from "../services/StorageService.js"; import type { - ApiResponse, + BaseResponse, StorageResponse, StoreMetadataRequest, StoreMetadataWithAllowlistRequest, @@ -35,7 +35,7 @@ export class MetadataController extends Controller { */ @Post() @SuccessResponse(201, "Data uploaded successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { metadata: "Invalid metadata." }, @@ -102,12 +102,12 @@ export class MetadataController extends Controller { */ @Post("/with-allowlist") @SuccessResponse(201, "Data uploaded successfully") - @Response(409, "Conflict", { + @Response(409, "Conflict", { success: false, message: "Allow list detected in metadata", errors: { metadata: "Allowlist URI already present in metadata." }, }) - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { metadata: "Invalid metadata." }, @@ -187,7 +187,7 @@ export class MetadataController extends Controller { */ @Post("/validate") @SuccessResponse(200, "Valid metadata", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { metadata: "Invalid metadata." }, @@ -252,7 +252,7 @@ export class MetadataController extends Controller { */ @Post("/with-allowlist/validate") @SuccessResponse(200, "Valid metadata", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { metadata: "Invalid metadata." }, diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index 424a231e..7d30536e 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -10,8 +10,8 @@ import { } from "tsoa"; import type { AddOrUpdateUserRequest, - AddOrUpdateUserResponse, - ApiResponse, + BaseResponse, + UserResponse, } from "../types/api.js"; import { z } from "zod"; import { SupabaseDataService } from "../services/SupabaseDataService.js"; @@ -25,14 +25,14 @@ export class UserController extends Controller { */ @Post(`{address}`) @SuccessResponse(201, "User updated successfully", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while validating user", }) public async addOrUpdateUser( @Path() address: string, @Body() requestBody: AddOrUpdateUserRequest, - ): Promise { + ): Promise { const inputSchema = z.object({ display_name: z.string().optional(), avatar: z.string().optional(), @@ -45,7 +45,6 @@ export class UserController extends Controller { return { success: false, message: "Invalid input", - data: null, errors: JSON.parse(parsedBody.error.toString()), }; } @@ -77,7 +76,6 @@ export class UserController extends Controller { return { success: false, message: "Invalid signature", - data: null, }; } @@ -98,7 +96,6 @@ export class UserController extends Controller { return { success: false, message: "Error adding or updating user", - data: null, }; } @@ -114,7 +111,6 @@ export class UserController extends Controller { return { success: false, message: "Error adding or updating user", - data: null, }; } } diff --git a/src/types/api.ts b/src/types/api.ts index 9dc50dae..0f2446fd 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -1,84 +1,54 @@ import type { HypercertMetadata } from "@hypercerts-org/sdk"; // Base response type for all API responses -export interface ApiResponse { +export interface BaseResponse { success: boolean; // Whether the API call itself succeeded message?: string; // Human readable message about the operation errors?: Record; // Any errors that occurred } // Response for operations that return data -export interface DataResponse extends ApiResponse { +export interface DataResponse extends BaseResponse { data?: T; } // Response specifically for validation operations -export interface ValidationResponse extends ApiResponse { +export interface ValidationResponse extends BaseResponse { valid: boolean; // Whether the validated content is valid data?: unknown; // Optional validated/transformed data } -/** - * Interface for storing metadata on IPFS. - */ +// Storage-related interfaces +export interface StorageResponse extends DataResponse<{ cid: string }> {} + export interface StoreMetadataRequest { metadata: HypercertMetadata; } -/** - * Interface for storing an allow list dump on IPFS - */ export interface StoreAllowListRequest { allowList: string; totalUnits?: string; } -/** - * Interface for storing metadata and allow list dump on IPFS. - */ export interface StoreMetadataWithAllowlistRequest extends StoreMetadataRequest, StoreAllowListRequest {} -/** - * Interface for validating metadata. - */ +// Validation-related interfaces export interface ValidateMetadataRequest { metadata: HypercertMetadata; } -/** - * Interface for validating an allow list dump. - */ export interface ValidateAllowListRequest { allowList: string; totalUnits?: string; } -/** - * Interface for validating metadata and allow list dump. - */ export interface ValidateMetadataWithAllowlistRequest extends ValidateMetadataRequest, ValidateAllowListRequest {} -/** - * Interface for a storage response. - */ -export interface StorageResponse extends DataResponse<{ cid: string }> {} - -/** - * Interface for a validation response. - */ -export interface ValidationResponse extends ApiResponse { - valid: boolean; // Whether the validated content is valid - data?: unknown; // Optional validated/transformed data -} - -/** - * Interface for a user add or update request. - */ - +// User-related interfaces export interface AddOrUpdateUserRequest { display_name: string; avatar: string; @@ -88,26 +58,32 @@ export interface AddOrUpdateUserRequest { export interface UserResponse extends DataResponse<{ address: string }> {} -/** - * Interface for a blueprint add or update request. - */ -export interface BlueprintResponse - extends DataResponse<{ blueprint_id: number }> {} +// Blueprint-related interfaces +export interface BlueprintCreateRequest { + form_values: unknown; + minter_address: string; + admin_address: string; + signature: string; + chain_id: number; +} + +export interface BlueprintQueueMintRequest { + signature: string; + chain_id: number; + minter_address: string; + tx_hash: string; +} -export type BlueprintDeleteRequest = { +export interface BlueprintDeleteRequest { signature: string; chain_id: number; admin_address: string; -}; +} -/** - * Response for a created hyperboard - */ -export interface HyperboardResponse extends DataResponse<{ id: string }> {} +export interface BlueprintResponse + extends DataResponse<{ blueprint_id: number }> {} -/** - * Interface for creating a hyperboard - */ +// Hyperboard-related interfaces export interface HyperboardCreateRequest { chainIds: number[]; title: string; @@ -130,24 +106,8 @@ export interface HyperboardCreateRequest { signature: string; } -/** - * Interface for updating a hyperboard - */ export interface HyperboardUpdateRequest extends HyperboardCreateRequest { id: string; } -export interface BlueprintCreateRequest { - form_values: unknown; - minter_address: string; - admin_address: string; - signature: string; - chain_id: number; -} - -export interface BlueprintQueueMintRequest { - signature: string; - chain_id: number; - minter_address: string; - tx_hash: string; -} +export interface HyperboardResponse extends DataResponse<{ id: string }> {} From 87878fe12765a12ba7f003c98d8c8b0847c6995d Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 21:52:18 +0100 Subject: [PATCH 04/11] fix(allowlist.test): allowlist upload unit tests Fixes the allowlist upload unit test flows. The current test suite was breaking on missing imports and the tested flow and types were updated to the latest implementation --- src/controllers/AllowListController.ts | 39 +++--- test/api/v1/allowlist.test.ts | 177 ++++++++++++++----------- test/test-utils/mockMerkleTree.ts | 6 +- 3 files changed, 130 insertions(+), 92 deletions(-) diff --git a/src/controllers/AllowListController.ts b/src/controllers/AllowListController.ts index 2de428c9..8c0c3b90 100644 --- a/src/controllers/AllowListController.ts +++ b/src/controllers/AllowListController.ts @@ -38,26 +38,35 @@ export class AllowListController extends Controller { ): Promise { const storage = await StorageService.init(); - const result = parseAndValidateMerkleTree(requestBody); + try { + const result = parseAndValidateMerkleTree(requestBody); - if (!result.valid || !result.data) { - this.setStatus(422); + if (!result.valid || !result.data) { + this.setStatus(422); + return { + success: false, + message: "Errors while validating allow list", + errors: result.errors, + }; + } + + const cid = await storage.uploadFile({ + file: jsonToBlob(requestBody.allowList), + }); + this.setStatus(201); + + return { + success: true, + data: cid, + }; + } catch (error) { + this.setStatus(500); return { success: false, - message: "Errors while validating allow list", - errors: result.errors, + message: "Error uploading data", + errors: { allowList: "Error uploading data" }, }; } - - const cid = await storage.uploadFile({ - file: jsonToBlob(requestBody.allowList), - }); - this.setStatus(201); - - return { - success: true, - data: cid, - }; } /** diff --git a/test/api/v1/allowlist.test.ts b/test/api/v1/allowlist.test.ts index 2a4be2cf..b465ea92 100644 --- a/test/api/v1/allowlist.test.ts +++ b/test/api/v1/allowlist.test.ts @@ -1,101 +1,126 @@ -import { describe, it, afterEach, afterAll } from "vitest"; +import { describe, test, vi } from "vitest"; import { expect } from "chai"; -import { createMocks, RequestMethod } from "node-mocks-http"; -import { Request, Response } from "express"; - -import sinon from "sinon"; +import { + incorrectMerkleTree, + mockMerkleTree, +} from "../../test-utils/mockMerkleTree.js"; +import { mock } from "vitest-mock-extended"; +import { AllowListController } from "../../../src/controllers/AllowListController.js"; +import { StorageService } from "../../../src/services/StorageService.js"; + +const mocks = vi.hoisted(() => { + return { + init: vi.fn(), + }; +}); -import { data } from "../../test-utils"; -import { Client } from "@web3-storage/w3up-client"; -import { allowlistHandler } from "@/handlers/v1/web3up/allowlist"; -import { Link } from "@web3-storage/access"; +vi.mock("../../../src/services/StorageService", async () => { + return { + StorageService: { init: mocks.init }, + }; +}); -describe("W3Up Client allowlist", async () => { - const { metadata, merkleTree, someData } = data; +describe("Allow list upload at v1/allowlists", async () => { + const controller = new AllowListController(); + const mockStorage = mock(); - const storeBlobMock = sinon - .stub(Client.prototype, "uploadFile") - .resolves({ "/": merkleTree.cid } as unknown as Link); //TODO better Link object creation + test("Stores a new allowlist and returns CID", async () => { + mocks.init.mockResolvedValue(mockStorage); - const mockRequestResponse = (method: RequestMethod = "POST") => { - const { req, res }: { req: Request; res: Response } = createMocks({ - method, - }); - req.headers = { - "Content-Type": "application/json", + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); + const requestBody = { + allowList: mockMerkleTree, + totalUnits: "100000000", }; - req.body = { allowList: merkleTree.data, totalUnits: 100n }; - return { req, res }; - }; - - afterEach(() => { - sinon.resetHistory(); - }); + const response = await controller.storeAllowList(requestBody); - afterAll(() => { - sinon.resetBehavior(); + expect(response.success).to.be.true; + expect(response.data).to.not.be.undefined; + expect(response.data?.cid).to.eq("TEST_CID"); }); - it("POST valid allowList - 200", async () => { - const { req, res } = mockRequestResponse(); - await allowlistHandler(req, res); + test("Returns errors and message when allowlist is invalid", async () => { + mocks.init.mockResolvedValue(mockStorage); - expect(res.statusCode).to.eq(200); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); - - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Data uploaded succesfully"); - // @ts-ignore - expect(res._getJSONData().cid).to.not.be.undefined; + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); + const requestBody = { + allowList: incorrectMerkleTree, + totalUnits: "100000000", + }; + const response = await controller.storeAllowList(requestBody); - expect(storeBlobMock.callCount).to.eq(1); + expect(response.success).to.be.false; + expect(response.data).to.be.undefined; + expect(response.message).to.eq("Errors while validating allow list"); + expect(response.errors).to.deep.eq({ + allowListData: "Data could not be parsed to OpenZeppelin MerkleTree", + }); }); - it("GET allowlist not allowed - 405", async () => { - const { req, res } = mockRequestResponse(); - req.method = "GET"; - await allowlistHandler(req, res); + test("Handles errors during upload", async () => { + mocks.init.mockResolvedValue(mockStorage); - expect(res.statusCode).to.eq(405); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + const mockError = new Error("Error uploading data"); + mockStorage.uploadFile.mockRejectedValue(mockError); - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Not allowed"); + const requestBody = { + allowList: mockMerkleTree, + totalUnits: "100000000", + }; + const response = await controller.storeAllowList(requestBody); - expect(storeBlobMock.callCount).to.eq(0); + expect(response.success).to.be.false; + expect(response.data).to.be.undefined; + expect(response.errors).to.deep.eq({ + allowList: "Error uploading data", + }); }); +}); - it("POST incorrect allowlist - 400", async () => { - const { req, res } = mockRequestResponse(); - req.body = { allowList: data.someData.data, totalUnits: 100n }; - await allowlistHandler(req, res); - - expect(res.statusCode).to.eq(400); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); +describe("Allow list validation at v1/allowlists/validate", async () => { + const controller = new AllowListController(); - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Not a valid merkle tree object"); + test("Validates correctness of allowlist and returns results", async () => { + const requestBody = { + allowList: mockMerkleTree, + totalUnits: "100", + }; + const response = await controller.validateAllowList(requestBody); + expect(response.valid).to.be.true; + expect(response.success).to.be.true; + expect(response.message).to.be.eq( + "Allowlist is a valid hypercerts allowlist object.", + ); }); - it("POST upload allowlist fails - 500", async () => { - const { req, res } = mockRequestResponse(); - storeBlobMock.rejects(); - await allowlistHandler(req, res); - - expect(res.statusCode).to.eq(500); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + test("Returns errors and message when allowlist is invalid", async () => { + const requestBody = { + allowList: incorrectMerkleTree, + totalUnits: "100", + }; + const response = await controller.validateAllowList(requestBody); - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Error uploading data"); + expect(response.success).to.be.true; + expect(response.valid).to.be.false; + expect(response.message).to.eq("Errors while validating allow list"); + expect(response.errors).to.deep.eq({ + allowListData: "Data could not be parsed to OpenZeppelin MerkleTree", + }); + }); - expect(storeBlobMock.callCount).to.eq(1); + test("Returns errors and message when total units doesn't match allow list", async () => { + const requestBody = { + allowList: mockMerkleTree, + totalUnits: "99", + }; + const response = await controller.validateAllowList(requestBody); + + expect(response.success).to.be.true; + expect(response.valid).to.be.false; + expect(response.message).to.eq("Errors while validating allow list"); + expect(response.errors).to.deep.eq({ + units: + "Total units in allowlist must match total units [expected: 99, got: 100]", + }); }); }); diff --git a/test/test-utils/mockMerkleTree.ts b/test/test-utils/mockMerkleTree.ts index 494f04d7..96137f00 100644 --- a/test/test-utils/mockMerkleTree.ts +++ b/test/test-utils/mockMerkleTree.ts @@ -1,4 +1,8 @@ const stringContent = - '{"format":"standard-v1","tree":["0xe582f894ed4599f64aed0c4f6ea1ed2d2f7bca47a11c984e63d823a38d4f43b6"],"values":[{"value":["0x59266D85D94666D037C1e32dAa8FaC9E95CdaFEf",100],"treeIndex":0}],"leafEncoding":["address","uint256"]}'; + '{"format":"standard-v1","tree":["0xe582f894ed4599f64aed0c4f6ea1ed2d2f7bca47a11c984e63d823a38d4f43b6"],"values":[{"value":["0x59266D85D94666D037C1e32dAa8FaC9E95CdaFEf",100000000],"treeIndex":0}],"leafEncoding":["address","uint256"]}'; export const mockMerkleTree = stringContent; +export const incorrectMerkleTree = stringContent.replace( + '"leafEncoding":["address","uint256"]', + '"leafEncoding":[null,null]', +); From e2406fbcfc8adadd29dcecdba19a15e520072fa7 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 22:03:23 +0100 Subject: [PATCH 05/11] fix(allowlist.test): allowlist controller unit tests wraps business logic in try-catches and updates response types where needed --- src/controllers/AllowListController.ts | 32 +++++++++++++++++--------- test/api/v1/allowlist.test.ts | 10 +++----- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/controllers/AllowListController.ts b/src/controllers/AllowListController.ts index 8c0c3b90..8d957d17 100644 --- a/src/controllers/AllowListController.ts +++ b/src/controllers/AllowListController.ts @@ -85,22 +85,32 @@ export class AllowListController extends Controller { public async validateAllowList( @Body() requestBody: ValidateAllowListRequest, ): Promise { - const result = parseAndValidateMerkleTree(requestBody); + try { + const result = parseAndValidateMerkleTree(requestBody); + + if (!result.valid || !result.data) { + this.setStatus(422); + return { + success: true, + valid: false, + message: "Errors while validating allow list", + errors: result.errors, + }; + } - if (!result.valid || !result.data) { - this.setStatus(422); + this.setStatus(201); + return { + success: true, + valid: true, + }; + } catch (error) { + this.setStatus(500); return { success: false, valid: false, - message: "Errors while validating allow list", - errors: result.errors, + message: "Error uploading data", + errors: { allowList: "Error uploading data" }, }; } - - this.setStatus(201); - return { - success: true, - valid: true, - }; } } diff --git a/test/api/v1/allowlist.test.ts b/test/api/v1/allowlist.test.ts index b465ea92..64c3dd7a 100644 --- a/test/api/v1/allowlist.test.ts +++ b/test/api/v1/allowlist.test.ts @@ -83,20 +83,17 @@ describe("Allow list validation at v1/allowlists/validate", async () => { test("Validates correctness of allowlist and returns results", async () => { const requestBody = { allowList: mockMerkleTree, - totalUnits: "100", + totalUnits: "100000000", }; const response = await controller.validateAllowList(requestBody); expect(response.valid).to.be.true; expect(response.success).to.be.true; - expect(response.message).to.be.eq( - "Allowlist is a valid hypercerts allowlist object.", - ); }); test("Returns errors and message when allowlist is invalid", async () => { const requestBody = { allowList: incorrectMerkleTree, - totalUnits: "100", + totalUnits: "100000000", }; const response = await controller.validateAllowList(requestBody); @@ -119,8 +116,7 @@ describe("Allow list validation at v1/allowlists/validate", async () => { expect(response.valid).to.be.false; expect(response.message).to.eq("Errors while validating allow list"); expect(response.errors).to.deep.eq({ - units: - "Total units in allowlist must match total units [expected: 99, got: 100]", + totalUnits: "Total units do not match the sum of units in the allowlist", }); }); }); From 8141388242c265da3f21ae5e0847e00c673ab1ef Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 22:13:57 +0100 Subject: [PATCH 06/11] chore(coverage): add coverage reporting and ci Adds vitest coverage reporting and CI action --- .github/workflows/ci-test-unit.yml | 54 +++ package.json | 4 +- pnpm-lock.yaml | 621 ++++++++++++++++++----------- vitest.config.ts | 26 +- 4 files changed, 480 insertions(+), 225 deletions(-) create mode 100644 .github/workflows/ci-test-unit.yml diff --git a/.github/workflows/ci-test-unit.yml b/.github/workflows/ci-test-unit.yml new file mode 100644 index 00000000..1e37afc2 --- /dev/null +++ b/.github/workflows/ci-test-unit.yml @@ -0,0 +1,54 @@ +name: "Unit tests" +on: + pull_request: + +jobs: + unit-test: + runs-on: ubuntu-latest + environment: test + + env: + PORT: 4000 + SUPABASE_CACHING_DB_URL: "https://test.supabase.com" + SUPABASE_CACHING_ANON_API_KEY: "test" + SUPABASE_DATA_DB_URL: "https://test.supabase.com" + SUPABASE_DATA_SERVICE_API_KEY: "test" + KEY: "test" + PROOF: "test" + + ALCHEMY_API_KEY: "test" + DRPC_API_KEY: "test" + INFURA_API_KEY: "test" + + INDEXER_ENVIRONMENT: "test" + + CACHING_DATABASE_URL: "https://test.supabase.com" + DATA_DATABASE_URL: "https://test.supabase.com" + + permissions: + # Required to checkout the code + contents: read + # Required to put a comment into the pull-request + pull-requests: write + + steps: + - uses: actions/checkout@v4 + - name: "Install Node" + uses: actions/setup-node@v4 + with: + node-version: "20.x" + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Install dependencies + run: pnpm install + + - name: "Run unit tests" + run: pnpm test:coverage + - name: "Report Coverage" + # Set if: always() to also generate the report if tests are failing + # Only works if you set `reportOnFailure: true` in your vite config as specified above + if: always() + uses: davelosert/vitest-coverage-report-action@v2 diff --git a/package.json b/package.json index a09540b9..f263f5ad 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "preintegration": "pnpm --dir ./lib/hypercerts-indexer i", "lint": "eslint", "test": "vitest", + "test:coverage": "vitest --coverage.enabled true", "prepare": "husky", "commitlint": "commitlint --config commitlintrc.ts --edit" }, @@ -94,6 +95,7 @@ "@types/pg": "^8.11.6", "@types/sinon": "^17.0.2", "@types/swagger-ui-express": "^4.1.6", + "@vitest/coverage-v8": "^2.1.8", "chai": "^5.0.0", "chai-assertions-count": "^1.0.2", "concurrently": "^8.2.2", @@ -116,7 +118,7 @@ "typescript": "5.5.3", "typescript-eslint": "^7.7.0", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^1.1.3", + "vitest": "^2.1.8", "vitest-mock-extended": "^2.0.2" }, "lint-staged": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbc7325b..d01da1e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -204,6 +204,9 @@ importers: '@types/swagger-ui-express': specifier: ^4.1.6 version: 4.1.6 + '@vitest/coverage-v8': + specifier: ^2.1.8 + version: 2.1.8(vitest@2.1.8(@types/node@20.10.6)) chai: specifier: ^5.0.0 version: 5.0.0 @@ -271,11 +274,11 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.5.3)(vite@5.0.11(@types/node@20.10.6)) vitest: - specifier: ^1.1.3 - version: 1.1.3(@types/node@20.10.6) + specifier: ^2.1.8 + version: 2.1.8(@types/node@20.10.6) vitest-mock-extended: specifier: ^2.0.2 - version: 2.0.2(typescript@5.5.3)(vitest@1.1.3(@types/node@20.10.6)) + version: 2.0.2(typescript@5.5.3)(vitest@2.1.8(@types/node@20.10.6)) packages: @@ -318,9 +321,12 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@ardatan/relay-compiler@12.0.0': resolution: {integrity: sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==} - hasBin: true peerDependencies: graphql: '*' @@ -431,10 +437,18 @@ packages: resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.22.20': resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.23.5': resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} @@ -455,6 +469,10 @@ packages: resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} engines: {node: '>=6.0.0'} + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + engines: {node: '>=6.0.0'} + '@babel/plugin-proposal-class-properties@7.18.6': resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} @@ -631,6 +649,13 @@ packages: resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -1025,7 +1050,6 @@ packages: '@graphql-codegen/cli@5.0.2': resolution: {integrity: sha512-MBIaFqDiLKuO4ojN6xxG9/xL9wmfD3ZjZ7RsPjwQnSHBCUXnEkdKvX+JVpx87Pq29Ycn8wTJUguXnTZ7Di0Mlw==} - hasBin: true peerDependencies: '@parcel/watcher': ^2.1.0 graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 @@ -1522,14 +1546,18 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} '@jridgewell/gen-mapping@0.3.3': resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -1538,12 +1566,22 @@ packages: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/trace-mapping@0.3.22': resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -1987,7 +2025,6 @@ packages: '@openzeppelin/hardhat-upgrades@3.2.1': resolution: {integrity: sha512-Zy5M3QhkzwGdpzQmk+xbWdYOGJWjoTvwbBKYLhctu9B91DoprlhDRaZUwCtunwTdynkTDGdVfGr0kIkvycyKjw==} - hasBin: true peerDependencies: '@nomicfoundation/hardhat-ethers': ^3.0.0 '@nomicfoundation/hardhat-verify': ^2.0.0 @@ -2293,9 +2330,6 @@ packages: '@shikijs/core@1.12.1': resolution: {integrity: sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==} - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -2328,7 +2362,6 @@ packages: '@snaplet/seed@0.97.20': resolution: {integrity: sha512-+lnqESgwP92O1266vsTyoRgrg4hMCUTybBUxDT1ICMBFcvdjgwcOaUt8Xjj81YvxYkZlu5+TTBIjyNQT4nP4jQ==} engines: {node: '>=18.5.0'} - hasBin: true peerDependencies: '@prisma/client': '>=5' '@snaplet/copycat': '>=2' @@ -2387,7 +2420,6 @@ packages: '@swc/cli@0.3.12': resolution: {integrity: sha512-h7bvxT+4+UDrLWJLFHt6V+vNAcUNii2G4aGSSotKz1ECEk4MyEh5CWxmeSscwuz5K3i+4DWTgm4+4EyMCQKn+g==} engines: {node: '>= 16.14.0'} - hasBin: true peerDependencies: '@swc/core': ^1.2.66 chokidar: ^3.5.1 @@ -2857,20 +2889,43 @@ packages: '@urql/core@5.0.4': resolution: {integrity: sha512-gl86J6B6gWXvvkx5omZ+CaGiPQ0chCUGM0jBsm0zTtkDQPRqufv0NSUN6sp2JhGGtTOB0NR6Pd+w7XAVGGyUOA==} - '@vitest/expect@1.1.3': - resolution: {integrity: sha512-MnJqsKc1Ko04lksF9XoRJza0bGGwTtqfbyrsYv5on4rcEkdo+QgUdITenBQBUltKzdxW7K3rWh+nXRULwsdaVg==} + '@vitest/coverage-v8@2.1.8': + resolution: {integrity: sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==} + peerDependencies: + '@vitest/browser': 2.1.8 + vitest: 2.1.8 + peerDependenciesMeta: + '@vitest/browser': + optional: true - '@vitest/runner@1.1.3': - resolution: {integrity: sha512-Va2XbWMnhSdDEh/OFxyUltgQuuDRxnarK1hW5QNN4URpQrqq6jtt8cfww/pQQ4i0LjoYxh/3bYWvDFlR9tU73g==} + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} - '@vitest/snapshot@1.1.3': - resolution: {integrity: sha512-U0r8pRXsLAdxSVAyGNcqOU2H3Z4Y2dAAGGelL50O0QRMdi1WWeYHdrH/QWpN1e8juWfVKsb8B+pyJwTC+4Gy9w==} + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} + + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} - '@vitest/spy@1.1.3': - resolution: {integrity: sha512-Ec0qWyGS5LhATFQtldvChPTAHv08yHIOZfiNcjwRQbFPHpkih0md9KAbs7TfeIfL7OFKoe7B/6ukBTqByubXkQ==} + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} - '@vitest/utils@1.1.3': - resolution: {integrity: sha512-Dyt3UMcdElTll2H75vhxfpZu03uFpXRCHxWnzcrFjZxT1kTbq8ALUYIeBgGolo1gldVdI0YSlQRacsqxTwNqwg==} + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} '@volar/language-core@2.4.0-alpha.18': resolution: {integrity: sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==} @@ -3106,10 +3161,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -3183,9 +3234,6 @@ packages: resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==} engines: {node: '>=12.0.0'} - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -3438,14 +3486,14 @@ packages: peerDependencies: chai: '*' - chai@4.4.0: - resolution: {integrity: sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==} - engines: {node: '>=4'} - chai@5.0.0: resolution: {integrity: sha512-HO5p0oEKd5M6HEcwOkNAThAE3j960vIZvVcc0t2tI06Dd0ATu69cEnMB2wOhC5/ZyQ6m67w3ePjU/HzXsSsdBA==} engines: {node: '>=12'} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -3470,13 +3518,14 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - check-error@2.0.0: resolution: {integrity: sha512-tjLAOBHKVxtPoHe/SA7kNOMvhCRdCJ3vETdeY0RuAc9popf+hyaSV6ZEg9hr4cpWF7jmo/JSWEnLDrnijS9Tog==} engines: {node: '>= 16'} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -3834,6 +3883,15 @@ packages: supports-color: optional: true + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -3860,10 +3918,6 @@ packages: babel-plugin-macros: optional: true - deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} - engines: {node: '>=6'} - deep-eql@5.0.1: resolution: {integrity: sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==} engines: {node: '>=6'} @@ -3935,10 +3989,6 @@ packages: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -4064,6 +4114,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -4215,6 +4268,10 @@ packages: resolution: {integrity: sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==} engines: {node: '>=18'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + express@4.19.2: resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} engines: {node: '>= 0.10.0'} @@ -4506,6 +4563,9 @@ packages: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} @@ -4547,7 +4607,6 @@ packages: gql.tada@1.8.3: resolution: {integrity: sha512-0H81I3M54jKTDHbnNWhXDf57Ie2d2raxnFCc93zdYjXHnrXe522jrio9AAFwqBlGx/xtaP3ILSSUw7J9H31LAA==} - hasBin: true peerDependencies: typescript: ^5.0.0 @@ -4629,7 +4688,6 @@ packages: hardhat@2.22.16: resolution: {integrity: sha512-d52yQZ09u0roL6GlgJSvtknsBtIuj9JrJ/U8VMzr/wue+gO5v2tQayvOX6llerlR57Zw2EOTQjLAt6RpHvjwHA==} - hasBin: true peerDependencies: ts-node: '*' typescript: '*' @@ -4689,6 +4747,9 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -5048,6 +5109,22 @@ packages: peerDependencies: ws: '*' + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + it-all@1.0.6: resolution: {integrity: sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==} @@ -5064,6 +5141,9 @@ packages: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + javascript-stringify@2.1.0: resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} @@ -5211,10 +5291,6 @@ packages: resolution: {integrity: sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==} engines: {node: '>=18.0.0'} - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - localforage@1.10.0: resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} @@ -5300,12 +5376,12 @@ packages: loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - loupe@3.0.2: resolution: {integrity: sha512-Tzlkbynv7dtqxTROe54Il+J4e/zG2iehtJGZUYpTv8WzlkW9qyEcE83UhGJCeuF3SCfzHuM5VWhBi47phV3+AQ==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lower-case-first@2.0.2: resolution: {integrity: sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==} @@ -5340,10 +5416,20 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.5: resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} engines: {node: '>=12'} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -5847,10 +5933,6 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -5875,6 +5957,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} @@ -5948,6 +6033,10 @@ packages: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} engines: {node: '>=16 || 14 >=14.17'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -5964,9 +6053,6 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - pathval@2.0.0: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} @@ -6121,10 +6207,6 @@ packages: resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} engines: {node: '>=14'} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -6215,9 +6297,6 @@ packages: rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} - react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - react-native-fetch-api@3.0.0: resolution: {integrity: sha512-g2rtqPjdroaboDKTsJCTlcmtw54E25OjyaunUP0anOZn4Fuo2IKs8BVfe02zVggA/UysbmfSnRJIqtNkAgggNA==} @@ -6560,10 +6639,6 @@ packages: resolution: {integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==} engines: {node: '>=0.10.0'} - source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} @@ -6607,8 +6682,8 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} @@ -6686,9 +6761,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@1.3.0: - resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} - strip-outer@2.0.0: resolution: {integrity: sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6776,6 +6848,10 @@ packages: resolution: {integrity: sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==} engines: {node: '>=12'} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-extensions@2.4.0: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} @@ -6792,19 +6868,26 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - tinybench@2.5.1: - resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} tinyglobby@0.2.10: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} - tinypool@0.8.1: - resolution: {integrity: sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==} + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyspy@2.2.0: - resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} title-case@3.0.3: @@ -6876,7 +6959,6 @@ packages: ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true peerDependencies: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' @@ -6891,7 +6973,6 @@ packages: tsconfck@3.1.4: resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} engines: {node: ^18 || >=20} - hasBin: true peerDependencies: typescript: ^5.0.0 peerDependenciesMeta: @@ -7002,7 +7083,6 @@ packages: typedoc@0.26.5: resolution: {integrity: sha512-Vn9YKdjKtDZqSk+by7beZ+xzkkr8T8CYoiasqyt4TTRFy5+UHzL/mF/o4wGBjRF+rlWQHDb0t6xCpA3JNL5phg==} engines: {node: '>= 18'} - hasBin: true peerDependencies: typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x @@ -7101,7 +7181,6 @@ packages: update-browserslist-db@1.0.13: resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -7183,9 +7262,10 @@ packages: typescript: optional: true - vite-node@1.1.3: - resolution: {integrity: sha512-BLSO72YAkIUuNrOx+8uznYICJfTEbvBAmWClY3hpath5+h1mbPS5OMn42lrTxXuyCazVyZoDkSRnju78GiVCqA==} + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} @@ -7229,15 +7309,15 @@ packages: typescript: 3.x || 4.x || 5.x vitest: '>=2.0.0' - vitest@1.1.3: - resolution: {integrity: sha512-2l8om1NOkiA90/Y207PsEvJLYygddsOyr81wLQ20Ra8IlLKbyQncWsGZjnbkyG2KwwuTXLQjEPOJuxGMG8qJBQ==} + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': ^1.0.0 - '@vitest/ui': ^1.0.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -7297,9 +7377,10 @@ packages: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} - why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} + hasBin: true widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} @@ -7551,6 +7632,11 @@ snapshots: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.9 + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + '@ardatan/relay-compiler@12.0.0(encoding@0.1.13)(graphql@16.8.1)': dependencies: '@babel/core': 7.23.9 @@ -7622,7 +7708,7 @@ snapshots: '@babel/traverse': 7.23.9 '@babel/types': 7.23.9 convert-source-map: 2.0.0 - debug: 4.3.6 + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -7718,8 +7804,12 @@ snapshots: '@babel/helper-string-parser@7.23.4': {} + '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-validator-identifier@7.22.20': {} + '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-option@7.23.5': {} '@babel/helpers@7.23.9': @@ -7744,6 +7834,10 @@ snapshots: dependencies: '@babel/types': 7.23.9 + '@babel/parser@7.26.3': + dependencies: + '@babel/types': 7.26.3 + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 @@ -7924,7 +8018,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.7 '@babel/types': 7.23.9 - debug: 4.3.6 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -7935,6 +8029,13 @@ snapshots: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 + '@babel/types@7.26.3': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@bcoe/v8-coverage@0.2.3': {} + '@colors/colors@1.5.0': optional: true @@ -9476,9 +9577,7 @@ snapshots: dependencies: minipass: 7.1.2 - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 + '@istanbuljs/schema@0.1.3': {} '@jridgewell/gen-mapping@0.3.3': dependencies: @@ -9486,17 +9585,32 @@ snapshots: '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.22 + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.1.2': {} + '@jridgewell/set-array@1.2.1': {} + '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/trace-mapping@0.3.22': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -9616,7 +9730,7 @@ snapshots: '@nomicfoundation/hardhat-ethers@3.0.6(ethers@6.12.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.3))(typescript@5.5.3))': dependencies: - debug: 4.3.6 + debug: 4.4.0 ethers: 6.12.2 hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.3))(typescript@5.5.3) lodash.isequal: 4.5.0 @@ -9658,7 +9772,7 @@ snapshots: '@ethersproject/address': 5.6.1 '@nomicfoundation/solidity-analyzer': 0.1.1 cbor: 9.0.2 - debug: 4.3.6 + debug: 4.4.0 ethers: 6.12.2 fs-extra: 10.1.0 immer: 10.0.2 @@ -10402,8 +10516,6 @@ snapshots: dependencies: '@types/hast': 3.0.4 - '@sinclair/typebox@0.27.8': {} - '@sindresorhus/is@4.6.0': {} '@sinonjs/commons@2.0.0': @@ -11145,34 +11257,63 @@ snapshots: transitivePeerDependencies: - graphql - '@vitest/expect@1.1.3': + '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@20.10.6))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.8.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.8(@types/node@20.10.6) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.8': dependencies: - '@vitest/spy': 1.1.3 - '@vitest/utils': 1.1.3 - chai: 4.4.0 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + tinyrainbow: 1.2.0 - '@vitest/runner@1.1.3': + '@vitest/mocker@2.1.8(vite@5.0.11(@types/node@20.10.6))': dependencies: - '@vitest/utils': 1.1.3 - p-limit: 5.0.0 - pathe: 1.1.1 + '@vitest/spy': 2.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 5.0.11(@types/node@20.10.6) - '@vitest/snapshot@1.1.3': + '@vitest/pretty-format@2.1.8': dependencies: - magic-string: 0.30.5 - pathe: 1.1.1 - pretty-format: 29.7.0 + tinyrainbow: 1.2.0 - '@vitest/spy@1.1.3': + '@vitest/runner@2.1.8': dependencies: - tinyspy: 2.2.0 + '@vitest/utils': 2.1.8 + pathe: 1.1.2 - '@vitest/utils@1.1.3': + '@vitest/snapshot@2.1.8': dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.17 + pathe: 1.1.2 + + '@vitest/spy@2.1.8': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 + tinyrainbow: 1.2.0 '@volar/language-core@2.4.0-alpha.18': dependencies: @@ -11424,7 +11565,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.6 + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -11501,8 +11642,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} any-signal@3.0.1: {} @@ -11583,8 +11722,6 @@ snapshots: pvutils: 1.1.3 tslib: 2.6.2 - assertion-error@1.1.0: {} - assertion-error@2.0.1: {} astral-regex@2.0.0: {} @@ -11935,16 +12072,6 @@ snapshots: dependencies: chai: 5.0.0 - chai@4.4.0: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.3 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 - chai@5.0.0: dependencies: assertion-error: 2.0.1 @@ -11953,6 +12080,14 @@ snapshots: loupe: 3.0.2 pathval: 2.0.0 + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.1 + loupe: 3.1.2 + pathval: 2.0.0 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -11998,12 +12133,10 @@ snapshots: chardet@0.7.0: {} - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 - check-error@2.0.0: {} + check-error@2.1.1: {} + chokidar@3.5.3: dependencies: anymatch: 3.1.3 @@ -12121,7 +12254,7 @@ snapshots: code-red@1.0.4: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@types/estree': 1.0.5 acorn: 8.11.3 estree-walker: 3.0.3 @@ -12378,6 +12511,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.4.0: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decamelize@4.0.0: {} @@ -12392,10 +12529,6 @@ snapshots: dedent@1.5.3: {} - deep-eql@4.1.3: - dependencies: - type-detect: 4.0.8 - deep-eql@5.0.1: {} deep-is@0.1.4: {} @@ -12444,8 +12577,6 @@ snapshots: detect-libc@2.0.3: {} - diff-sequences@29.6.3: {} - diff@4.0.2: {} diff@5.0.0: {} @@ -12595,6 +12726,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.6.0: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -12896,6 +13029,8 @@ snapshots: exit-hook@4.0.0: {} + expect-type@1.1.0: {} + express@4.19.2: dependencies: accepts: 1.3.8 @@ -13247,6 +13382,15 @@ snapshots: minipass: 7.0.4 path-scurry: 1.10.1 + glob@10.4.5: + dependencies: + foreground-child: 3.1.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@7.2.0: dependencies: fs.realpath: 1.0.0 @@ -13560,6 +13704,8 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + html-escaper@2.0.2: {} + http-cache-semantics@4.1.1: {} http-errors@2.0.0: @@ -13585,7 +13731,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.6 + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -13908,6 +14054,27 @@ snapshots: dependencies: ws: 8.18.0 + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + it-all@1.0.6: {} it-glob@1.0.2: @@ -13932,6 +14099,12 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + javascript-stringify@2.1.0: {} jiti@1.21.0: {} @@ -14086,11 +14259,6 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.0 - local-pkg@0.5.0: - dependencies: - mlly: 1.4.2 - pkg-types: 1.0.3 - localforage@1.10.0: dependencies: lie: 3.1.1 @@ -14170,14 +14338,12 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 - loupe@3.0.2: dependencies: get-func-name: 2.0.2 + loupe@3.1.2: {} + lower-case-first@2.0.2: dependencies: tslib: 2.6.2 @@ -14209,10 +14375,24 @@ snapshots: lunr@2.3.9: {} + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.5: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + source-map-js: 1.2.0 + + make-dir@4.0.0: + dependencies: + semver: 7.6.0 + make-error@1.3.6: {} map-cache@0.2.2: {} @@ -14371,7 +14551,7 @@ snapshots: mlly@1.4.2: dependencies: acorn: 8.11.3 - pathe: 1.1.1 + pathe: 1.1.2 pkg-types: 1.0.3 ufo: 1.3.2 @@ -14734,10 +14914,6 @@ snapshots: dependencies: yocto-queue: 1.0.0 - p-limit@5.0.0: - dependencies: - yocto-queue: 1.0.0 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -14761,6 +14937,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + pako@0.2.9: {} pako@1.0.11: {} @@ -14826,6 +15004,11 @@ snapshots: lru-cache: 10.2.0 minipass: 7.1.2 + path-scurry@1.11.1: + dependencies: + lru-cache: 10.2.0 + minipass: 7.1.2 + path-to-regexp@0.1.7: {} path-to-regexp@1.8.0: @@ -14838,8 +15021,6 @@ snapshots: pathe@1.1.2: {} - pathval@1.1.1: {} - pathval@2.0.0: {} pbkdf2@3.1.2: @@ -14950,8 +15131,8 @@ snapshots: postcss@8.4.33: dependencies: nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.0.2 + picocolors: 1.1.1 + source-map-js: 1.2.0 postgres-array@2.0.0: {} @@ -14986,12 +15167,6 @@ snapshots: prettier@3.3.2: {} - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.2.0 - process@0.11.10: {} promise@7.3.1: @@ -15107,8 +15282,6 @@ snapshots: defu: 6.1.4 destr: 2.0.3 - react-is@18.2.0: {} - react-native-fetch-api@3.0.0: dependencies: p-defer: 3.0.0 @@ -15172,7 +15345,7 @@ snapshots: require-in-the-middle@7.3.0: dependencies: - debug: 4.3.6 + debug: 4.4.0 module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -15509,8 +15682,6 @@ snapshots: dependencies: is-plain-obj: 1.1.0 - source-map-js@1.0.2: {} - source-map-js@1.2.0: {} source-map-support@0.5.21: @@ -15544,7 +15715,7 @@ snapshots: statuses@2.0.1: {} - std-env@3.7.0: {} + std-env@3.8.0: {} stdin-discarder@0.2.2: {} @@ -15621,10 +15792,6 @@ snapshots: strip-json-comments@3.1.1: {} - strip-literal@1.3.0: - dependencies: - acorn: 8.11.3 - strip-outer@2.0.0: {} strtok3@7.0.0: @@ -15671,9 +15838,9 @@ snapshots: svelte@4.2.18: dependencies: - '@ampproject/remapping': 2.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.22 + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 '@types/estree': 1.0.5 acorn: 8.11.3 aria-query: 5.3.0 @@ -15683,7 +15850,7 @@ snapshots: estree-walker: 3.0.3 is-reference: 3.0.2 locate-character: 3.0.0 - magic-string: 0.30.5 + magic-string: 0.30.17 periscopic: 3.1.0 swagger-ui-dist@5.17.9: {} @@ -15762,6 +15929,12 @@ snapshots: ansi-escapes: 5.0.0 supports-hyperlinks: 2.3.0 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-extensions@2.4.0: {} text-table@0.2.0: {} @@ -15774,16 +15947,20 @@ snapshots: tiny-inflate@1.0.3: {} - tinybench@2.5.1: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} tinyglobby@0.2.10: dependencies: fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 - tinypool@0.8.1: {} + tinypool@1.0.2: {} - tinyspy@2.2.0: {} + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} title-case@3.0.3: dependencies: @@ -16184,12 +16361,12 @@ snapshots: - utf-8-validate - zod - vite-node@1.1.3(@types/node@20.10.6): + vite-node@2.1.8(@types/node@20.10.6): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) - pathe: 1.1.1 - picocolors: 1.0.0 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 1.1.2 vite: 5.0.11(@types/node@20.10.6) transitivePeerDependencies: - '@types/node' @@ -16221,40 +16398,40 @@ snapshots: '@types/node': 20.10.6 fsevents: 2.3.3 - vitest-mock-extended@2.0.2(typescript@5.5.3)(vitest@1.1.3(@types/node@20.10.6)): + vitest-mock-extended@2.0.2(typescript@5.5.3)(vitest@2.1.8(@types/node@20.10.6)): dependencies: ts-essentials: 10.0.4(typescript@5.5.3) typescript: 5.5.3 - vitest: 1.1.3(@types/node@20.10.6) - - vitest@1.1.3(@types/node@20.10.6): - dependencies: - '@vitest/expect': 1.1.3 - '@vitest/runner': 1.1.3 - '@vitest/snapshot': 1.1.3 - '@vitest/spy': 1.1.3 - '@vitest/utils': 1.1.3 - acorn-walk: 8.3.1 - cac: 6.7.14 - chai: 4.4.0 - debug: 4.3.4(supports-color@8.1.1) - execa: 8.0.1 - local-pkg: 0.5.0 - magic-string: 0.30.5 - pathe: 1.1.1 - picocolors: 1.0.0 - std-env: 3.7.0 - strip-literal: 1.3.0 - tinybench: 2.5.1 - tinypool: 0.8.1 + vitest: 2.1.8(@types/node@20.10.6) + + vitest@2.1.8(@types/node@20.10.6): + dependencies: + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.0.11(@types/node@20.10.6)) + '@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.4.0 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 vite: 5.0.11(@types/node@20.10.6) - vite-node: 1.1.3(@types/node@20.10.6) - why-is-node-running: 2.2.2 + vite-node: 2.1.8(@types/node@20.10.6) + why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.10.6 transitivePeerDependencies: - less - lightningcss + - msw - sass - stylus - sugarss @@ -16326,7 +16503,7 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@2.2.2: + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 diff --git a/vitest.config.ts b/vitest.config.ts index 46147ebe..68171d6f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,10 +1,32 @@ import { resolve } from "node:path"; -import { defineConfig } from "vitest/config"; +import { configDefaults, defineConfig } from "vitest/config"; export default defineConfig({ test: { setupFiles: ["./test/setup-env.ts"], - exclude: ["./lib", "node_modules"], + exclude: [...configDefaults.exclude, "./lib/**/*"], + coverage: { + // you can include other reporters, but 'json-summary' is required, json is recommended + reporter: ["text", "json-summary", "json"], + // If you want a coverage reports even if your tests are failing, include the reportOnFailure option + reportOnFailure: true, + thresholds: { + lines: 15, + branches: 54, + functions: 49, + statements: 15, + }, + include: ["src/**/*.ts"], + exclude: [ + ...(configDefaults.coverage.exclude as string[]), + "**/*.types.ts", + "**/types.ts", + "src/__generated__/**/*", + "src/graphql/**/*", + "src/types/**/*", + "./lib/**/*", + ], + }, }, resolve: { alias: [{ find: "@", replacement: resolve(__dirname, "./src") }], From 5c46b420e26134bac1b9c910b22353843c08d58f Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 20:26:44 +0100 Subject: [PATCH 07/11] fix(metadata.test.ts): metadata upload unit tests Fixes the test file and controller to have passing tests and better error handling for the metadata upload functionality of the MetadataController --- package.json | 4 +- pnpm-lock.yaml | 225 ++++++++++++-------------- src/controllers/MetadataController.ts | 73 +++++---- test/api/v1/metadata.test.ts | 195 ++++++++-------------- test/test-utils/mockMetadata.ts | 7 +- 5 files changed, 225 insertions(+), 279 deletions(-) diff --git a/package.json b/package.json index b540c425..2bdcd52b 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,9 @@ "typedoc": "^0.26.5", "typescript": "5.5.3", "typescript-eslint": "^7.7.0", - "vitest": "^1.1.3" + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^1.1.3", + "vitest-mock-extended": "^2.0.2" }, "lint-staged": { "*.{mjx,cjs,js,jsx,ts,tsx}": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a385d0a7..2219b75a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -197,7 +197,7 @@ importers: version: 8.2.1 '@swc/cli': specifier: ^0.3.12 - version: 0.3.12(@swc/core@1.4.15)(chokidar@3.6.0) + version: 0.3.12(@swc/core@1.4.15)(chokidar@4.0.1) '@swc/core': specifier: ^1.4.15 version: 1.4.15 @@ -279,9 +279,15 @@ importers: typescript-eslint: specifier: ^7.7.0 version: 7.7.0(eslint@8.56.0)(typescript@5.5.3) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.5.3)(vite@5.0.11(@types/node@20.10.6)) vitest: specifier: ^1.1.3 version: 1.1.3(@types/node@20.10.6) + vitest-mock-extended: + specifier: ^2.0.2 + version: 2.0.2(typescript@5.5.3)(vitest@1.1.3(@types/node@20.10.6)) packages: @@ -326,7 +332,6 @@ packages: '@ardatan/relay-compiler@12.0.0': resolution: {integrity: sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==} - hasBin: true peerDependencies: graphql: '*' @@ -456,12 +461,10 @@ packages: '@babel/parser@7.23.9': resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} engines: {node: '>=6.0.0'} - hasBin: true '@babel/parser@7.24.7': resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} engines: {node: '>=6.0.0'} - hasBin: true '@babel/plugin-proposal-class-properties@7.18.6': resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} @@ -646,7 +649,6 @@ packages: '@commitlint/cli@19.4.1': resolution: {integrity: sha512-EerFVII3ZcnhXsDT9VePyIdCJoh3jEzygN1L37MjQXgPfGS6fJTWL/KHClVMod1d8w94lFC3l4Vh/y5ysVAz2A==} engines: {node: '>=v18'} - hasBin: true '@commitlint/config-conventional@19.4.1': resolution: {integrity: sha512-D5S5T7ilI5roybWGc8X35OBlRXLAwuTseH1ro0XgqkOWrhZU8yOwBOslrNmSDlTXhXLq8cnfhQyC42qaUCzlXA==} @@ -902,7 +904,6 @@ packages: '@ethereumjs/rlp@4.0.1': resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==} engines: {node: '>=14'} - hasBin: true '@ethereumjs/util@8.1.0': resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==} @@ -1035,7 +1036,6 @@ packages: '@graphql-codegen/cli@5.0.2': resolution: {integrity: sha512-MBIaFqDiLKuO4ojN6xxG9/xL9wmfD3ZjZ7RsPjwQnSHBCUXnEkdKvX+JVpx87Pq29Ycn8wTJUguXnTZ7Di0Mlw==} - hasBin: true peerDependencies: '@parcel/watcher': ^2.1.0 graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 @@ -1660,7 +1660,6 @@ packages: '@nomicfoundation/ethereumjs-rlp@5.0.4': resolution: {integrity: sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==} engines: {node: '>=18'} - hasBin: true '@nomicfoundation/ethereumjs-tx@5.0.4': resolution: {integrity: sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw==} @@ -1998,7 +1997,6 @@ packages: '@openzeppelin/hardhat-upgrades@3.2.1': resolution: {integrity: sha512-Zy5M3QhkzwGdpzQmk+xbWdYOGJWjoTvwbBKYLhctu9B91DoprlhDRaZUwCtunwTdynkTDGdVfGr0kIkvycyKjw==} - hasBin: true peerDependencies: '@nomicfoundation/hardhat-ethers': ^3.0.0 '@nomicfoundation/hardhat-verify': ^2.0.0 @@ -2016,7 +2014,6 @@ packages: '@openzeppelin/upgrades-core@1.35.1': resolution: {integrity: sha512-FzbOzMrrNtXpL+DoM6jtOGZOvUdp6F4KTsb0niqfrd2BxMecL9fUA0aupi09p1Of+9BELcYozX/Gt7tr91Z2TA==} - hasBin: true '@peculiar/asn1-schema@2.3.8': resolution: {integrity: sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==} @@ -2288,7 +2285,6 @@ packages: '@sentry/profiling-node@8.2.1': resolution: {integrity: sha512-oHjKXu8rROlaM1GZHQNyt8lG/58XSD6lUHgxx33IuqUTZjIzise8AB7fE1fzg4+JNFZITag6zzYqrQqPGSN3HQ==} engines: {node: '>=14.18'} - hasBin: true '@sentry/tracing@5.30.0': resolution: {integrity: sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==} @@ -2356,7 +2352,6 @@ packages: '@snaplet/seed@0.97.20': resolution: {integrity: sha512-+lnqESgwP92O1266vsTyoRgrg4hMCUTybBUxDT1ICMBFcvdjgwcOaUt8Xjj81YvxYkZlu5+TTBIjyNQT4nP4jQ==} engines: {node: '>=18.5.0'} - hasBin: true peerDependencies: '@prisma/client': '>=5' '@snaplet/copycat': '>=2' @@ -2415,7 +2410,6 @@ packages: '@swc/cli@0.3.12': resolution: {integrity: sha512-h7bvxT+4+UDrLWJLFHt6V+vNAcUNii2G4aGSSotKz1ECEk4MyEh5CWxmeSscwuz5K3i+4DWTgm4+4EyMCQKn+g==} engines: {node: '>= 16.14.0'} - hasBin: true peerDependencies: '@swc/core': ^1.2.66 chokidar: ^3.5.1 @@ -2612,7 +2606,6 @@ packages: '@tsoa/cli@6.2.1': resolution: {integrity: sha512-SS28cvL2uurau2PZbBO8Ks6O9LF497iMlnUfMr7hffbgxh81SftfG+qvddeniNw0ttSB593Mljvv+fPabEbrfQ==} engines: {node: '>=18.0.0', yarn: '>=1.9.4'} - hasBin: true '@tsoa/runtime@6.2.1': resolution: {integrity: sha512-YOA7ha6W6GQsSr3Pvb5omb5AwizvQd7GUu54Oi2TjNWYOzfczBROZonReMfKBiNULiZBDmEc5r1Hs+Kbbfjgyw==} @@ -2996,7 +2989,6 @@ packages: JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -3053,7 +3045,6 @@ packages: acorn@8.11.3: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} - hasBin: true actor@2.3.1: resolution: {integrity: sha512-ST/3wnvcP2tKDXnum7nLCLXm+/rsf8vPocXH2Fre6D8FQwNkGDd4JEitBlXj007VQJfiGYRQvXqwOBZVi+JtRg==} @@ -3373,7 +3364,6 @@ packages: browserslist@4.23.0: resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true bs58@4.0.1: resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} @@ -3467,7 +3457,6 @@ packages: cborg@4.0.6: resolution: {integrity: sha512-McNIJHMQKQv/WgSE1JqWfqS4kaeN5g9GRA5MqVCt1+66TGsywkpzBUywpZ/HWF3Ik8yudSR+ZPlq6TRBEZXQyA==} - hasBin: true chai-assertions-count@1.0.2: resolution: {integrity: sha512-TnhoI68Mh7GYsdrvQuxK+kKOTfEXQZjePP8lTvYhXGv8KOKY+GaOY3PemMq8mBAa0gqQRKsISdi7QFJ/Lxdt+g==} @@ -3684,7 +3673,6 @@ packages: concurrently@8.2.2: resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} engines: {node: ^14.13.0 || >=16.0.0} - hasBin: true conf@11.0.2: resolution: {integrity: sha512-jjyhlQ0ew/iwmtwsS2RaB6s8DBifcE2GYBEaw2SJDUY/slJJbNfY4GlDVzOs/ff8cM/Wua5CikqXgbFl5eu85A==} @@ -3719,7 +3707,6 @@ packages: conventional-commits-parser@5.0.0: resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} engines: {node: '>=16'} - hasBin: true convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -4028,7 +4015,6 @@ packages: ebnf@1.9.1: resolution: {integrity: sha512-uW2UKSsuty9ANJ3YByIQE4ANkD8nqUPO7r6Fwcc1ADKPe9FRdcPpMl3VEput4JSvKBJ4J86npIC2MLP0pYkCuw==} - hasBin: true ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -4121,7 +4107,6 @@ packages: esbuild@0.19.11: resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==} engines: {node: '>=12'} - hasBin: true escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -4153,7 +4138,6 @@ packages: eslint@8.56.0: resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} @@ -4199,7 +4183,6 @@ packages: ethereumjs-abi@0.6.8: resolution: {integrity: sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==} - deprecated: This library has been deprecated and usage is discouraged. ethereumjs-util@6.2.1: resolution: {integrity: sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==} @@ -4389,7 +4372,6 @@ packages: flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true flatted@3.2.9: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} @@ -4532,12 +4514,10 @@ packages: giget@1.2.3: resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} - hasBin: true git-raw-commits@4.0.0: resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} engines: {node: '>=16'} - hasBin: true glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -4550,11 +4530,9 @@ packages: glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} - hasBin: true glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} - deprecated: Glob versions prior to v9 are no longer supported glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -4579,6 +4557,9 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -4591,7 +4572,6 @@ packages: gql.tada@1.8.3: resolution: {integrity: sha512-0H81I3M54jKTDHbnNWhXDf57Ie2d2raxnFCc93zdYjXHnrXe522jrio9AAFwqBlGx/xtaP3ILSSUw7J9H31LAA==} - hasBin: true peerDependencies: typescript: ^5.0.0 @@ -4667,14 +4647,12 @@ packages: handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} - hasBin: true hardhat-deploy@0.11.45: resolution: {integrity: sha512-aC8UNaq3JcORnEUIwV945iJuvBwi65tjHVDU3v6mOcqik7WAzHVCJ7cwmkkipsHrWysrB5YvGF1q9S1vIph83w==} hardhat@2.22.17: resolution: {integrity: sha512-tDlI475ccz4d/dajnADUTRc1OJ3H8fpP9sWhXhBPpYsQOg8JHq5xrDimo53UhWPl7KJmAeDCm1bFG74xvpGRpg==} - hasBin: true peerDependencies: ts-node: '*' typescript: '*' @@ -4727,7 +4705,6 @@ packages: he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} @@ -4769,7 +4746,6 @@ packages: husky@9.1.5: resolution: {integrity: sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==} engines: {node: '>=18'} - hasBin: true iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} @@ -4914,7 +4890,6 @@ packages: is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} - hasBin: true is-electron@2.2.2: resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} @@ -5117,7 +5092,6 @@ packages: jiti@1.21.0: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} - hasBin: true jose@5.2.3: resolution: {integrity: sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==} @@ -5136,12 +5110,10 @@ packages: js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} - hasBin: true json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -5182,7 +5154,6 @@ packages: json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} - hasBin: true jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} @@ -5249,7 +5220,6 @@ packages: lint-staged@15.2.9: resolution: {integrity: sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==} engines: {node: '>=18.12.0'} - hasBin: true listr2@4.0.5: resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} @@ -5352,7 +5322,6 @@ packages: loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} @@ -5407,7 +5376,6 @@ packages: markdown-it@14.1.0: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} - hasBin: true match-all@1.2.6: resolution: {integrity: sha512-0EESkXiTkWzrQQntBu2uzKvLu6vVkUGz40nGPbSZuegcfE5UuSzNjLaIu76zJWuaT/2I3Z/8M06OlUOZLGwLlQ==} @@ -5493,7 +5461,6 @@ packages: mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} - hasBin: true mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} @@ -5577,22 +5544,18 @@ packages: mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} - hasBin: true mkdirp@2.1.6: resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} engines: {node: '>=10'} - hasBin: true mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} - hasBin: true mlly@1.4.2: resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} @@ -5606,7 +5569,6 @@ packages: mocha@10.2.0: resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==} engines: {node: '>= 14.0.0'} - hasBin: true module-details-from-path@1.0.3: resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} @@ -5661,12 +5623,10 @@ packages: nanoid@3.3.3: resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true native-fetch@3.0.0: resolution: {integrity: sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw==} @@ -5679,11 +5639,9 @@ packages: ndjson@2.0.0: resolution: {integrity: sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==} engines: {node: '>=10'} - hasBin: true nearley@2.20.1: resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} - hasBin: true negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} @@ -5742,7 +5700,6 @@ packages: node-gyp-build@4.7.1: resolution: {integrity: sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==} - hasBin: true node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -5757,7 +5714,6 @@ packages: nodemon@3.0.3: resolution: {integrity: sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==} engines: {node: '>=10'} - hasBin: true nofilter@3.1.0: resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} @@ -5765,7 +5721,6 @@ packages: nopt@1.0.10: resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} - hasBin: true normalize-path@2.1.1: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} @@ -5805,7 +5760,6 @@ packages: nypm@0.3.8: resolution: {integrity: sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==} engines: {node: ^14.16.0 || >=16.10.0} - hasBin: true object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -6117,7 +6071,6 @@ packages: pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} - hasBin: true pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} @@ -6194,7 +6147,6 @@ packages: prettier@3.3.2: resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} engines: {node: '>=14'} - hasBin: true pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} @@ -6379,7 +6331,6 @@ packages: resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true responselike@2.0.1: resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} @@ -6420,19 +6371,16 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true rimraf@5.0.5: resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} engines: {node: '>=14'} - hasBin: true ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} rlp@2.2.7: resolution: {integrity: sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==} - hasBin: true rollup-plugin-swc3@0.11.2: resolution: {integrity: sha512-o1ih9B806fV2wBSNk46T0cYfTF2eiiKmYXRpWw3K4j/Cp3tCAt10UCVsTqvUhGP58pcB3/GZcAVl5e7TCSKN6Q==} @@ -6449,7 +6397,6 @@ packages: rollup@4.12.0: resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} @@ -6498,26 +6445,21 @@ packages: semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} - hasBin: true semver@7.6.0: resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} engines: {node: '>=10'} - hasBin: true semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} - hasBin: true send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} @@ -6552,7 +6494,6 @@ packages: sha.js@2.4.11: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} - hasBin: true shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} @@ -6638,7 +6579,6 @@ packages: solc@0.8.26: resolution: {integrity: sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==} engines: {node: '>=10.0.0'} - hasBin: true solidity-ast@0.4.56: resolution: {integrity: sha512-HgmsA/Gfklm/M8GFbCX/J1qkVH0spXHgALCNZ8fA8x5X+MFdn/8CP2gr5OVyXjXw6RZTPC/Sxl2RUDQOXyNMeA==} @@ -6794,7 +6734,6 @@ packages: supabase@1.191.3: resolution: {integrity: sha512-5tIG7mPc5lZ9QRbkZssyHiOsx42qGFaVqclauXv+1fJAkZnfA28d0pzEDvfs33+w8YTReO5nNaWAgyzkWQQwfA==} engines: {npm: '>=8'} - hasBin: true supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} @@ -6924,14 +6863,12 @@ packages: touch@3.1.0: resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} - hasBin: true tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true treeify@1.1.0: resolution: {integrity: sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==} @@ -6951,6 +6888,14 @@ packages: resolution: {integrity: sha512-WZ/iAJrKDhdINv1WG6KZIGHrZDar6VfhftG1QJFpVbOYZMYJLJOvZOo1amictRXVdBXZIgBHKswMTXzElngprA==} engines: {node: '>=14.13.1'} + ts-essentials@10.0.4: + resolution: {integrity: sha512-lwYdz28+S4nicm+jFi6V58LaAIpxzhg9rLdgNC1VsdP/xiFBseGhF1M/shwCk6zMmwahBZdXcl34LVHrEang3A==} + peerDependencies: + typescript: '>=4.5.0' + peerDependenciesMeta: + typescript: + optional: true + ts-invariant@0.4.4: resolution: {integrity: sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==} @@ -6962,7 +6907,6 @@ packages: ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true peerDependencies: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' @@ -6974,6 +6918,15 @@ packages: '@swc/wasm': optional: true + tsconfck@3.1.4: + resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} + engines: {node: ^18 || >=20} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -6994,7 +6947,6 @@ packages: tsoa@6.2.1: resolution: {integrity: sha512-cK+Wmw99IdkVMuNPl8OM+SufIxvS1b5XY9mwjLrTJ4ytwiUkF1AOKvF6pX5k/xDnHXFLCrfHzbgaogj0JJO9EA==} engines: {node: '>=18.0.0', yarn: '>=1.9.4'} - hasBin: true tsort@0.0.1: resolution: {integrity: sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==} @@ -7002,7 +6954,6 @@ packages: tsx@4.7.1: resolution: {integrity: sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==} engines: {node: '>=18.0.0'} - hasBin: true tsyringe@4.8.0: resolution: {integrity: sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==} @@ -7080,7 +7031,6 @@ packages: typedoc@0.26.5: resolution: {integrity: sha512-Vn9YKdjKtDZqSk+by7beZ+xzkkr8T8CYoiasqyt4TTRFy5+UHzL/mF/o4wGBjRF+rlWQHDb0t6xCpA3JNL5phg==} engines: {node: '>= 18'} - hasBin: true peerDependencies: typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x @@ -7097,12 +7047,10 @@ packages: typescript@5.5.3: resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} engines: {node: '>=14.17'} - hasBin: true typescript@5.5.4: resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} - hasBin: true ua-parser-js@1.0.37: resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==} @@ -7119,7 +7067,6 @@ packages: uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} - hasBin: true uint8arraylist@2.4.8: resolution: {integrity: sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==} @@ -7182,7 +7129,6 @@ packages: update-browserslist-db@1.0.13: resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -7216,16 +7162,12 @@ packages: uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. - hasBin: true uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -7271,12 +7213,18 @@ packages: vite-node@1.1.3: resolution: {integrity: sha512-BLSO72YAkIUuNrOx+8uznYICJfTEbvBAmWClY3hpath5+h1mbPS5OMn42lrTxXuyCazVyZoDkSRnju78GiVCqA==} engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true vite@5.0.11: resolution: {integrity: sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==} engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true peerDependencies: '@types/node': ^18.0.0 || >=20.0.0 less: '*' @@ -7301,10 +7249,15 @@ packages: terser: optional: true + vitest-mock-extended@2.0.2: + resolution: {integrity: sha512-n3MBqVITKyclZ0n0y66hkT4UiiEYFQn9tteAnIxT0MPz1Z8nFcPUG3Cf0cZOyoPOj/cq6Ab1XFw2lM/qM5EDWQ==} + peerDependencies: + typescript: 3.x || 4.x || 5.x + vitest: '>=2.0.0' + vitest@1.1.3: resolution: {integrity: sha512-2l8om1NOkiA90/Y207PsEvJLYygddsOyr81wLQ20Ra8IlLKbyQncWsGZjnbkyG2KwwuTXLQjEPOJuxGMG8qJBQ==} engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 @@ -7364,17 +7317,14 @@ packages: which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} - hasBin: true why-is-node-running@2.2.2: resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} engines: {node: '>=8'} - hasBin: true widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} @@ -7530,12 +7480,10 @@ packages: yaml@2.4.1: resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==} engines: {node: '>= 14'} - hasBin: true yaml@2.5.0: resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} engines: {node: '>= 14'} - hasBin: true yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} @@ -7589,7 +7537,6 @@ packages: zksync-web3@0.14.4: resolution: {integrity: sha512-kYehMD/S6Uhe1g434UnaMN+sBr9nQm23Ywn0EUP5BfQCsbjcr3ORuS68PosZw8xUTu3pac7G6YMSnNHk+fwzvg==} - deprecated: This package has been deprecated in favor of zksync-ethers@5.0.0 peerDependencies: ethers: ^5.7.0 @@ -7700,7 +7647,7 @@ snapshots: '@babel/traverse': 7.23.9 '@babel/types': 7.23.9 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.6 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -8002,7 +7949,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.7 '@babel/types': 7.23.9 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.6 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -8252,7 +8199,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.0 @@ -8997,7 +8944,7 @@ snapshots: '@types/json-stable-stringify': 1.0.36 '@whatwg-node/fetch': 0.9.15 chalk: 4.1.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) dotenv: 16.3.1 graphql: 16.8.1 graphql-request: 6.1.0(encoding@0.1.13)(graphql@16.8.1) @@ -9319,7 +9266,7 @@ snapshots: '@humanwhocodes/config-array@0.11.13': dependencies: '@humanwhocodes/object-schema': 2.0.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -10575,7 +10522,7 @@ snapshots: c12: 1.10.0 change-case: 5.4.4 ci-info: 4.0.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) dedent: 1.5.3 deepmerge: 4.3.1 execa: 8.0.1 @@ -10655,7 +10602,7 @@ snapshots: - bufferutil - utf-8-validate - '@swc/cli@0.3.12(@swc/core@1.4.15)(chokidar@3.6.0)': + '@swc/cli@0.3.12(@swc/core@1.4.15)(chokidar@4.0.1)': dependencies: '@mole-inc/bin-wrapper': 8.0.1 '@swc/core': 1.4.15 @@ -10668,7 +10615,7 @@ snapshots: slash: 3.0.0 source-map: 0.7.4 optionalDependencies: - chokidar: 3.6.0 + chokidar: 4.0.1 '@swc/core-darwin-arm64@1.4.15': optional: true @@ -10788,7 +10735,7 @@ snapshots: fs-extra: 10.1.0 hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.3))(typescript@5.5.3) hardhat-deploy: 0.11.45 - tenderly: 0.9.1(ts-node@10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.3))(typescript@5.5.4) + tenderly: 0.9.1(ts-node@10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.4))(typescript@5.5.4) ts-node: 10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.4) tslog: 4.9.3 typescript: 5.5.4 @@ -10815,7 +10762,7 @@ snapshots: fs-extra: 10.1.0 hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.3))(typescript@5.5.3) hardhat-deploy: 0.11.45 - tenderly: 0.9.1(ts-node@10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.3))(typescript@5.5.4) + tenderly: 0.9.1(ts-node@10.9.2(@swc/core@1.6.5)(@types/node@20.10.6)(typescript@5.5.4))(typescript@5.5.4) ts-node: 10.9.2(@swc/core@1.6.5)(@types/node@20.10.6)(typescript@5.5.4) tslog: 4.9.3 typescript: 5.5.4 @@ -11115,7 +11062,7 @@ snapshots: '@typescript-eslint/type-utils': 7.7.0(eslint@8.56.0)(typescript@5.5.3) '@typescript-eslint/utils': 7.7.0(eslint@8.56.0)(typescript@5.5.3) '@typescript-eslint/visitor-keys': 7.7.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -11133,7 +11080,7 @@ snapshots: '@typescript-eslint/types': 7.7.0 '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.5.3) '@typescript-eslint/visitor-keys': 7.7.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 optionalDependencies: typescript: 5.5.3 @@ -11149,7 +11096,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.5.3) '@typescript-eslint/utils': 7.7.0(eslint@8.56.0)(typescript@5.5.3) - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.6 eslint: 8.56.0 ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: @@ -11163,7 +11110,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.7.0 '@typescript-eslint/visitor-keys': 7.7.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 @@ -11551,7 +11498,7 @@ snapshots: agent-base@7.1.0: dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -12792,7 +12739,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -13218,7 +13165,7 @@ snapshots: follow-redirects@1.15.4(debug@4.3.4): optionalDependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) follow-redirects@1.15.6(debug@4.3.6): optionalDependencies: @@ -13410,6 +13357,8 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + globrex@0.1.2: {} + gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 @@ -13692,7 +13641,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -13711,7 +13660,7 @@ snapshots: https-proxy-agent@7.0.4: dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -15295,7 +15244,7 @@ snapshots: require-in-the-middle@7.3.0: dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.6 module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -15852,7 +15801,7 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 - tenderly@0.9.1(ts-node@10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.3))(typescript@5.5.4): + tenderly@0.9.1(ts-node@10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.4))(typescript@5.5.4): dependencies: axios: 0.27.2 cli-table3: 0.6.5 @@ -15862,7 +15811,22 @@ snapshots: prompts: 2.4.2 tslog: 4.9.3 optionalDependencies: - ts-node: 10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.3) + ts-node: 10.9.2(@swc/core@1.4.15)(@types/node@20.10.6)(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - debug + + tenderly@0.9.1(ts-node@10.9.2(@swc/core@1.6.5)(@types/node@20.10.6)(typescript@5.5.4))(typescript@5.5.4): + dependencies: + axios: 0.27.2 + cli-table3: 0.6.5 + commander: 9.5.0 + js-yaml: 4.1.0 + open: 8.4.2 + prompts: 2.4.2 + tslog: 4.9.3 + optionalDependencies: + ts-node: 10.9.2(@swc/core@1.6.5)(@types/node@20.10.6)(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: - debug @@ -15936,6 +15900,10 @@ snapshots: ts-deepmerge@7.0.0: {} + ts-essentials@10.0.4(typescript@5.5.3): + optionalDependencies: + typescript: 5.5.3 + ts-invariant@0.4.4: dependencies: tslib: 1.14.1 @@ -16007,6 +15975,10 @@ snapshots: optionalDependencies: '@swc/core': 1.6.5 + tsconfck@3.1.4(typescript@5.5.3): + optionalDependencies: + typescript: 5.5.3 + tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 @@ -16289,7 +16261,7 @@ snapshots: vite-node@1.1.3(@types/node@20.10.6): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) pathe: 1.1.1 picocolors: 1.0.0 vite: 5.0.11(@types/node@20.10.6) @@ -16303,6 +16275,17 @@ snapshots: - supports-color - terser + vite-tsconfig-paths@5.1.4(typescript@5.5.3)(vite@5.0.11(@types/node@20.10.6)): + dependencies: + debug: 4.3.6 + globrex: 0.1.2 + tsconfck: 3.1.4(typescript@5.5.3) + optionalDependencies: + vite: 5.0.11(@types/node@20.10.6) + transitivePeerDependencies: + - supports-color + - typescript + vite@5.0.11(@types/node@20.10.6): dependencies: esbuild: 0.19.11 @@ -16312,6 +16295,12 @@ snapshots: '@types/node': 20.10.6 fsevents: 2.3.3 + vitest-mock-extended@2.0.2(typescript@5.5.3)(vitest@1.1.3(@types/node@20.10.6)): + dependencies: + ts-essentials: 10.0.4(typescript@5.5.3) + typescript: 5.5.3 + vitest: 1.1.3(@types/node@20.10.6) + vitest@1.1.3(@types/node@20.10.6): dependencies: '@vitest/expect': 1.1.3 @@ -16322,7 +16311,7 @@ snapshots: acorn-walk: 8.3.1 cac: 6.7.14 chai: 4.4.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.5 diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index c5ebd562..a4106112 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -40,46 +40,55 @@ export class MetadataController extends Controller { message: "Validation failed", errors: { metadata: "Invalid metadata." }, }) - public async storeMetadata( - @Body() requestBody: StoreMetadataRequest, - ): Promise { + public async storeMetadata(@Body() requestBody: StoreMetadataRequest) { const storage = await StorageService.init(); - const metadataValidationResult = validateMetadataAndClaimdata( - requestBody.metadata, - ); + const { metadata } = requestBody; - if (!metadataValidationResult.valid) { - this.setStatus(422); - return { - success: false, - message: "Validation failed", - errors: metadataValidationResult.errors, - }; - } - - if (requestBody.metadata.allowList) { - const allowListValidationResult = await validateRemoteAllowList( - requestBody.metadata.allowList, - ); - - if (!allowListValidationResult.valid) { + try { + const metadataValidationResult = validateMetadataAndClaimdata(metadata); + if (!metadataValidationResult.valid) { this.setStatus(422); return { success: false, - message: "Errors while validating allow list", - errors: allowListValidationResult.errors, + valid: false, + message: "Errors while validating metadata", + errors: metadataValidationResult.errors, }; } - } - const cid = await storage.uploadFile({ - file: jsonToBlob(metadataValidationResult.data), - }); - this.setStatus(201); - return { - success: true, - data: cid, - }; + // Validate allowlist separately if it exists + if (metadata.allowList) { + const allowListValidationResult = await validateRemoteAllowList( + metadata.allowList, + ); + if (!allowListValidationResult.valid) { + this.setStatus(422); + return { + success: false, + valid: false, + message: "Errors while validating allow list", + errors: allowListValidationResult.errors, + }; + } + } + + const cid = await storage.uploadFile({ + file: jsonToBlob(metadataValidationResult.data), + }); + this.setStatus(201); + + return { + success: true, + data: cid, + }; + } catch (e) { + this.setStatus(422); + return { + success: false, + message: "Error while storing metadata", + errors: { metadata: (e as Error).message }, + }; + } } /** diff --git a/test/api/v1/metadata.test.ts b/test/api/v1/metadata.test.ts index d482cf2d..707ff64d 100644 --- a/test/api/v1/metadata.test.ts +++ b/test/api/v1/metadata.test.ts @@ -1,153 +1,94 @@ -import { describe, it, afterEach, afterAll } from "vitest"; +import { describe, test, vi } from "vitest"; import { expect } from "chai"; -import { createMocks, RequestMethod } from "node-mocks-http"; -import { Request, Response } from "express"; - -import sinon from "sinon"; - -import { data } from "../../test-utils"; -import { Client } from "@web3-storage/w3up-client"; -import axios from "axios"; -import { metadataHandler } from "@/handlers/v1/web3up/metadata"; -import { AnyLink } from "@web3-storage/w3up-client/dist/src/types"; - -describe("W3Up Client metadata", async () => { - const { metadata, merkleTree, someData } = data; - - const storeBlobMock = sinon - .stub(Client.prototype, "uploadFile") - .resolves({ "/": metadata.cid } as unknown as AnyLink); //TODO better Link object creation - - const getAllowlistMock = sinon.stub(axios, "get"); - - const mockRequestResponse = (method: RequestMethod = "POST") => { - const { req, res }: { req: Request; res: Response } = createMocks({ - method, - }); - req.headers = { - "Content-Type": "application/json", - }; - req.body = metadata.data; - return { req, res }; +import { mock } from "vitest-mock-extended"; +import { StorageService } from "../../../src/services/StorageService.js"; +import { MetadataController } from "../../../src/controllers/MetadataController.js"; +import { + incorrectMetadata, + mockMetadata, +} from "../../test-utils/mockMetadata.js"; + +const mocks = vi.hoisted(() => { + return { + init: vi.fn(), }; +}); - afterEach(() => { - sinon.resetHistory(); - }); +vi.mock("../../../src/services/StorageService", async () => { + return { + StorageService: { init: mocks.init }, + }; +}); - afterAll(() => { - sinon.resetBehavior(); - }); +describe("Metadata upload at v1/metadata", async () => { + const controller = new MetadataController(); + const mockStorage = mock(); - it("POST valid metadata without allowList - 200", async () => { - const { req, res } = mockRequestResponse(); - await metadataHandler(req, res); + test("Stores a new metadata object and returns CID", async () => { + mocks.init.mockResolvedValue(mockStorage); - expect(res.statusCode).to.eq(200); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); + const response = await controller.storeMetadata({ metadata: mockMetadata }); + expect(response.success).to.be.true; + expect(response.data).to.not.be.undefined; + expect(response.data?.cid).to.eq("TEST_CID"); + }); - //TODO better typing and check on returned CID + test("Returns errors and message when metadata is invalid", async () => { + mocks.init.mockResolvedValue(mockStorage); - console.log(res); - // @ts-ignore - expect(res._getJSONData().message).to.eq("Data uploaded succesfully"); - // @ts-ignore - expect(res._getJSONData().cid).to.not.be.undefined; + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); + const response = await controller.storeMetadata({ + metadata: incorrectMetadata, + }); - expect(storeBlobMock.callCount).to.eq(1); - expect(getAllowlistMock.callCount).to.eq(0); + expect(response.success).to.be.false; + expect(response.data).to.be.undefined; + expect(response.message).to.eq("Errors while validating metadata"); + expect(response.errors).to.deep.eq({ + metadata: "Provided metadata is not a valid hypercert metadata object", + }); }); - it("POST valid metadata with allowList - 200", async () => { - const { req, res } = mockRequestResponse(); - req.body = { ...req.body, allowList: someData.cid }; - getAllowlistMock.resolves(Promise.resolve({ data: merkleTree.data })); + test("Handles errors during upload", async () => { + mocks.init.mockResolvedValue(mockStorage); - await metadataHandler(req, res); + const mockError = new Error("Error uploading data"); - expect(res.statusCode).to.eq(200); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); - - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Data uploaded succesfully"); - // @ts-ignore - expect(res._getJSONData().cid).to.not.be.undefined; - - expect(storeBlobMock.callCount).to.eq(1); - expect(getAllowlistMock.callCount).to.eq(1); + mockStorage.uploadFile.mockRejectedValue(mockError); + const response = await controller.storeMetadata({ metadata: mockMetadata }); + expect(response.success).to.be.false; + expect(response.data).to.be.undefined; + expect(response.errors).to.deep.eq({ + metadata: "Error uploading data", + }); }); +}); - it("GET metadata not allowed - 405", async () => { - const { req, res } = mockRequestResponse(); - req.method = "GET"; - await metadataHandler(req, res); +describe("Metadata validation at v1/metadata/validate", async () => { + const controller = new MetadataController(); - expect(res.statusCode).to.eq(405); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + test("Validates a metadata set and returns results", async () => { + const requestBody = mockMetadata; - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Not allowed"); + const response = await controller.validateMetadata(requestBody); - expect(storeBlobMock.callCount).to.eq(0); + expect(response.valid).to.be.true; + expect(response.success).to.be.true; + expect(response.message).to.be.eq("Metadata is valid hypercert metadata"); }); - it("POST incorrect metadata - 400", async () => { - const { req, res } = mockRequestResponse(); - req.body = data.someData.data; - await metadataHandler(req, res); + test("Returns errors and message when metadata is invalid", async () => { + const requestBody = incorrectMetadata; - expect(res.statusCode).to.eq(400); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + const response = await controller.validateMetadata(requestBody); - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq( - "Not a valid hypercert metadata object", + expect(response.success).to.be.true; + expect(response.message).to.eq( + "Errors while validating metadata and/or allow list", ); - }); - - it("POST correct metadata with incorrect allowlist - 400", async () => { - const { req, res } = mockRequestResponse(); - req.body = { ...req.body, allowList: someData.cid }; - getAllowlistMock.resolves(Promise.resolve({ data: "not a merkle tree" })); - await metadataHandler(req, res); - - expect(res.statusCode).to.eq(400); - expect(res.getHeaders()).to.deep.eq({ - "content-type": "application/json", + expect(response.errors).to.deep.eq({ + metadata: "Provided metadata is not a valid hypercert metadata object", }); - expect(res.statusMessage).to.eq("OK"); - - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq( - "Allowlist should be a valid openzeppelin merkle tree", - ); - - expect(storeBlobMock.callCount).to.eq(0); - expect(getAllowlistMock.callCount).to.eq(1); - }); - - it("POST upload metadata fails - 500", async () => { - const { req, res } = mockRequestResponse(); - storeBlobMock.rejects(); - await metadataHandler(req, res); - - expect(res.statusCode).to.eq(500); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); - - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Error uploading data"); - - expect(storeBlobMock.callCount).to.eq(1); - expect(getAllowlistMock.callCount).to.eq(0); }); }); diff --git a/test/test-utils/mockMetadata.ts b/test/test-utils/mockMetadata.ts index aa8b48eb..4430e4fd 100644 --- a/test/test-utils/mockMetadata.ts +++ b/test/test-utils/mockMetadata.ts @@ -64,4 +64,9 @@ const jsonContent = `{ } }`; -export const mockMetadata = JSON.parse(jsonContent); +const mockMetadata = JSON.parse(jsonContent); + +const incorrectMetadata = JSON.parse(jsonContent); +incorrectMetadata.hypercert = ""; + +export { mockMetadata, incorrectMetadata }; From 4f6bebaac90bb030a97f9a4caaabd54e71620ec8 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 21:00:28 +0100 Subject: [PATCH 08/11] fix(metadata.test.ts): metadata validation handling and responses Restores the metadata validation endpoint testing. Additionally the full metadata controller is using try catches and we updated the response types. The response types have been made more simple and flexible, for one to support the cases of validation where the processing can be successful but the data is invalid. Lastly, responses where data is set as null were cleaned so simply not return data. --- lib/hypercerts-indexer | 2 +- src/controllers/AllowListController.ts | 102 +++++++---- src/controllers/BlueprintController.ts | 8 +- src/controllers/HyperboardController.ts | 18 +- src/controllers/MetadataController.ts | 221 ++++++++++++++---------- src/controllers/UserController.ts | 6 +- src/types/api.ts | 54 +++--- test/api/v1/allowlist.test.ts | 171 ++++++++++-------- test/api/v1/metadata.test.ts | 17 +- test/test-utils/mockMerkleTree.ts | 6 +- 10 files changed, 340 insertions(+), 265 deletions(-) diff --git a/lib/hypercerts-indexer b/lib/hypercerts-indexer index cdeda3d7..b35f9af9 160000 --- a/lib/hypercerts-indexer +++ b/lib/hypercerts-indexer @@ -1 +1 @@ -Subproject commit cdeda3d77225fb8919eaa22ce716bad35ecc2e0a +Subproject commit b35f9af91cb4e843910134a00f7a19c38ebabde4 diff --git a/src/controllers/AllowListController.ts b/src/controllers/AllowListController.ts index 4c4a578f..8d957d17 100644 --- a/src/controllers/AllowListController.ts +++ b/src/controllers/AllowListController.ts @@ -1,19 +1,25 @@ import { jsonToBlob } from "../utils/jsonToBlob.js"; -import { Body, Controller, Post, Response, Route, SuccessResponse, Tags } from "tsoa"; +import { + Body, + Controller, + Post, + Response, + Route, + SuccessResponse, + Tags, +} from "tsoa"; import { StorageService } from "../services/StorageService.js"; import { parseAndValidateMerkleTree } from "../utils/parseAndValidateMerkleTreeDump.js"; import type { - ApiResponse, StorageResponse, StoreAllowListRequest, ValidateAllowListRequest, - ValidationResponse + ValidationResponse, } from "../types/api.js"; @Route("v1/allowlists") @Tags("Allowlists") export class AllowListController extends Controller { - /** * Submits a new allowlist for validation and storage on IPFS. While we maintain a database of allowlists, the allowlist itself is stored on IPFS. * Try to keep a backup of the allowlist for recovery purposes. @@ -22,32 +28,45 @@ export class AllowListController extends Controller { */ @Post() @SuccessResponse(201, "Data uploaded successfully", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while validating allow list", - errors: { allowList: "Invalid allowList. Length is 0" } + errors: { allowList: "Invalid allowList. Length is 0" }, }) - public async storeAllowList(@Body() requestBody: StoreAllowListRequest): Promise { + public async storeAllowList( + @Body() requestBody: StoreAllowListRequest, + ): Promise { const storage = await StorageService.init(); - const result = parseAndValidateMerkleTree(requestBody); + try { + const result = parseAndValidateMerkleTree(requestBody); + + if (!result.valid || !result.data) { + this.setStatus(422); + return { + success: false, + message: "Errors while validating allow list", + errors: result.errors, + }; + } + + const cid = await storage.uploadFile({ + file: jsonToBlob(requestBody.allowList), + }); + this.setStatus(201); - if (!result.valid || !result.data) { - this.setStatus(422); + return { + success: true, + data: cid, + }; + } catch (error) { + this.setStatus(500); return { success: false, - message: "Errors while validating allow list", - errors: result.errors + message: "Error uploading data", + errors: { allowList: "Error uploading data" }, }; } - - const cid = await storage.uploadFile({ file: jsonToBlob(requestBody.allowList) }); - this.setStatus(201); - - return { - success: true, - data: cid - }; } /** @@ -57,26 +76,41 @@ export class AllowListController extends Controller { */ @Post("/validate") @SuccessResponse(200, "Valid allowlist object", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, + valid: false, message: "Metadata validation failed", - errors: { allowList: "Invalid allowList. Length is 0" } + errors: { allowList: "Invalid allowList. Length is 0" }, }) - public async validateAllowList(@Body() requestBody: ValidateAllowListRequest): Promise { - const result = parseAndValidateMerkleTree(requestBody); + public async validateAllowList( + @Body() requestBody: ValidateAllowListRequest, + ): Promise { + try { + const result = parseAndValidateMerkleTree(requestBody); + + if (!result.valid || !result.data) { + this.setStatus(422); + return { + success: true, + valid: false, + message: "Errors while validating allow list", + errors: result.errors, + }; + } - if (!result.valid || !result.data) { - this.setStatus(422); + this.setStatus(201); + return { + success: true, + valid: true, + }; + } catch (error) { + this.setStatus(500); return { success: false, - message: "Errors while validating allow list", - errors: result.errors + valid: false, + message: "Error uploading data", + errors: { allowList: "Error uploading data" }, }; } - - this.setStatus(201); - return { - success: true - }; } -} \ No newline at end of file +} diff --git a/src/controllers/BlueprintController.ts b/src/controllers/BlueprintController.ts index 7ec260a8..7a4218a4 100644 --- a/src/controllers/BlueprintController.ts +++ b/src/controllers/BlueprintController.ts @@ -10,7 +10,7 @@ import { Tags, } from "tsoa"; import type { - AddOrCreateBlueprintResponse, + BlueprintResponse, ApiResponse, BlueprintCreateRequest, BlueprintDeleteRequest, @@ -36,7 +36,7 @@ export class BlueprintController extends Controller { }) public async createBlueprint( @Body() requestBody: BlueprintCreateRequest, - ): Promise { + ): Promise { const inputSchema = z.object({ form_values: z.object({ title: z @@ -131,7 +131,6 @@ export class BlueprintController extends Controller { return { success: false, message: "Invalid input", - data: null, errors: JSON.parse(parsedBody.error.toString()), }; } @@ -316,7 +315,7 @@ export class BlueprintController extends Controller { public async mintBlueprint( @Path() blueprintId: number, @Body() requestBody: BlueprintQueueMintRequest, - ): Promise { + ): Promise { const inputSchema = z.object({ signature: z.string(), chain_id: z.number(), @@ -331,7 +330,6 @@ export class BlueprintController extends Controller { return { success: false, message: "Invalid input", - data: null, errors: JSON.parse(parsedBody.error.toString()), }; } diff --git a/src/controllers/HyperboardController.ts b/src/controllers/HyperboardController.ts index 75aa0e43..b92cfc35 100644 --- a/src/controllers/HyperboardController.ts +++ b/src/controllers/HyperboardController.ts @@ -14,7 +14,7 @@ import { import type { ApiResponse, HyperboardCreateRequest, - HyperboardCreateResponse, + HyperboardResponse, HyperboardUpdateRequest, } from "../types/api.js"; import { z } from "zod"; @@ -43,7 +43,7 @@ export class HyperboardController extends Controller { }) public async createHyperboard( @Body() requestBody: HyperboardCreateRequest, - ): Promise { + ): Promise { const inputSchema = z .object({ chainIds: z @@ -175,7 +175,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Invalid input", - data: null, errors: JSON.parse(parsedBody.error.toString()), }; } @@ -228,7 +227,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Invalid signature", - data: null, }; } @@ -262,7 +260,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error creating hyperboard", - data: null, }; } @@ -355,7 +352,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error updating collection", - data: null, }; } } @@ -430,7 +426,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error creating collection", - data: null, }; } } @@ -453,7 +448,7 @@ export class HyperboardController extends Controller { public async updateHyperboard( @Path() hyperboardId: string, @Body() requestBody: HyperboardUpdateRequest, - ): Promise> { + ): Promise { const inputSchema = z .object({ id: z.string().uuid(), @@ -590,7 +585,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Invalid input", - data: null, errors: JSON.parse(parsedBody.error.toString()), }; } @@ -603,7 +597,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Hyperboard not found", - data: null, }; } @@ -657,7 +650,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Invalid signature", - data: null, }; } @@ -671,7 +663,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Not authorized to update hyperboard", - data: null, }; } @@ -692,7 +683,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error updating hyperboard", - data: null, }; } @@ -794,7 +784,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error updating collection", - data: null, }; } } @@ -867,7 +856,6 @@ export class HyperboardController extends Controller { return { success: false, message: "Error creating collection", - data: null, }; } } diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index a4106112..6c045bc2 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -116,57 +116,67 @@ export class MetadataController extends Controller { @Body() requestBody: StoreMetadataWithAllowlistRequest, ): Promise { const storage = await StorageService.init(); - const metadataValidationResult = validateMetadataAndClaimdata( - requestBody.metadata, - ); + const { metadata } = requestBody; + const { allowList, totalUnits } = requestBody; - if (!metadataValidationResult.valid) { - this.setStatus(422); - return { - success: false, - message: "Validation failed", - errors: metadataValidationResult.errors, - }; - } + try { + const metadataValidationResult = validateMetadataAndClaimdata(metadata); - if (requestBody.metadata.allowList) { - this.setStatus(409); - return { - success: false, - message: "Allow list detected in metadata", - errors: { metadata: "Allowlist URI already present in metadata." }, - }; - } + if (!metadataValidationResult.valid) { + this.setStatus(422); + return { + success: false, + message: "Validation failed", + errors: metadataValidationResult.errors, + }; + } + + if (allowList) { + this.setStatus(409); + return { + success: false, + message: "Allow list detected in metadata", + errors: { metadata: "Allowlist URI already present in metadata." }, + }; + } + + const allowlistValidationResult = parseAndValidateMerkleTree({ + allowList, + totalUnits, + }); + + if (!allowlistValidationResult.valid) { + this.setStatus(422); + return { + success: false, + message: "Validation failed", + errors: allowlistValidationResult.errors, + }; + } - const allowlistValidationResult = parseAndValidateMerkleTree({ - allowList: requestBody.allowList, - totalUnits: requestBody?.totalUnits, - }); + const uploadResult = await storage.uploadFile({ + file: jsonToBlob(requestBody.allowList), + }); + const cid = await storage.uploadFile({ + file: jsonToBlob({ + ...metadataValidationResult.data, + allowList: `ipfs://${uploadResult.cid}`, + }), + }); - if (!allowlistValidationResult.valid) { + this.setStatus(201); + return { + success: true, + data: cid, + }; + } catch (e) { this.setStatus(422); return { success: false, - message: "Validation failed", - errors: allowlistValidationResult.errors, + message: "Error while storing metadata", + errors: { metadata: (e as Error).message }, }; } - - const uploadResult = await storage.uploadFile({ - file: jsonToBlob(requestBody.allowList), - }); - const cid = await storage.uploadFile({ - file: jsonToBlob({ - ...metadataValidationResult.data, - allowList: `ipfs://${uploadResult.cid}`, - }), - }); - - this.setStatus(201); - return { - success: true, - data: cid, - }; } /** @@ -185,39 +195,53 @@ export class MetadataController extends Controller { public async validateMetadata( @Body() requestBody: ValidateMetadataRequest, ): Promise { - const metadataValidationResult = validateMetadataAndClaimdata( - requestBody.metadata, - ); - - if (!metadataValidationResult.valid) { - this.setStatus(422); - return { - success: false, - message: "Errors while validating metadata or allow list", - errors: metadataValidationResult.errors, - }; - } + const { metadata } = requestBody; - if (requestBody.metadata.allowList) { - const allowListValidationResult = await validateRemoteAllowList( - requestBody.metadata.allowList, - ); + try { + const metadataValidationResult = validateMetadataAndClaimdata(metadata); - if (!allowListValidationResult.valid) { + if (!metadataValidationResult.valid) { this.setStatus(422); return { - success: false, - message: "Errors while validating allow list reference in metadata", - errors: allowListValidationResult.errors, + success: true, + valid: false, + message: "Errors while validating metadata", + errors: metadataValidationResult.errors, }; } - } - this.setStatus(200); - return { - success: true, - message: "Validation successful", - }; + if (metadata.allowList) { + const allowListValidationResult = await validateRemoteAllowList( + metadata.allowList, + ); + + if (!allowListValidationResult.valid) { + this.setStatus(422); + return { + success: true, + valid: false, + message: + "Errors while validating allow list referenced in metadata", + errors: allowListValidationResult.errors, + }; + } + } + + this.setStatus(200); + return { + success: true, + valid: true, + message: "Metadata is valid hypercert metadata", + }; + } catch (e) { + this.setStatus(422); + return { + success: false, + valid: false, + message: "Error while validating metadata", + errors: { metadata: (e as Error).message }, + }; + } } /** @@ -236,37 +260,50 @@ export class MetadataController extends Controller { public async validateMetadataWithAllowlist( @Body() requestBody: StoreMetadataWithAllowlistRequest, ): Promise { - const metadataValidationResult = validateMetadataAndClaimdata( - requestBody.metadata, - ); + const { metadata, allowList, totalUnits } = requestBody; - if (!metadataValidationResult.valid) { - this.setStatus(422); - return { - success: false, - message: "Validation failed", - errors: metadataValidationResult.errors, - }; - } + try { + const metadataValidationResult = validateMetadataAndClaimdata(metadata); + + if (!metadataValidationResult.valid) { + this.setStatus(422); + return { + success: true, + valid: false, + message: "Validation failed", + errors: metadataValidationResult.errors, + }; + } + + const allowlistValidationResult = parseAndValidateMerkleTree({ + allowList, + totalUnits, + }); - const allowlistValidationResult = parseAndValidateMerkleTree({ - allowList: requestBody.allowList, - totalUnits: requestBody?.totalUnits, - }); + if (!allowlistValidationResult.valid) { + this.setStatus(422); + return { + success: true, + valid: false, + message: "Validation failed", + errors: allowlistValidationResult.errors, + }; + } - if (!allowlistValidationResult.valid) { + this.setStatus(200); + return { + success: true, + valid: true, + message: "Metadata is valid hypercert metadata", + }; + } catch (e) { this.setStatus(422); return { success: false, - message: "Validation failed", - errors: allowlistValidationResult.errors, + valid: false, + message: "Error while validating metadata", + errors: { metadata: (e as Error).message }, }; } - - this.setStatus(200); - return { - success: true, - message: "Validation successful", - }; } } diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index 0eede6f3..c76ee1b7 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -12,8 +12,8 @@ import { import type { AddOrUpdateUserRequest, - AddOrUpdateUserResponse, ApiResponse, + UserResponse, } from "../types/api.js"; import { UserUpsertError } from "../lib/users/errors.js"; import { USER_UPDATE_REQUEST_SCHEMA } from "../lib/users/schemas.js"; @@ -48,7 +48,7 @@ export class UserController extends Controller { public async addOrUpdateUser( @Path() address: string, @Body() requestBody: AddOrUpdateUserRequest, - ): Promise { + ): Promise { try { const parsedBody = parseInput(requestBody); const strategy = createStrategy(address, parsedBody); @@ -73,7 +73,6 @@ export class UserController extends Controller { return { success: false, message: error.message, - data: null, errors: error.errors, }; } @@ -86,7 +85,6 @@ export class UserController extends Controller { return { success: false, message: "Error adding or updating user", - data: null, }; } } diff --git a/src/types/api.ts b/src/types/api.ts index 74524fb8..b0557b5f 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -5,6 +5,23 @@ import type { HypercertMetadata } from "@hypercerts-org/sdk"; * Currently tsoa doesn't work with zod inferred types. * See https://github.com/lukeautry/tsoa/issues/1256. */ +// Base response type for all API responses +export interface ApiResponse { + success: boolean; // Whether the API call itself succeeded + message?: string; // Human readable message about the operation + errors?: Record; // Any errors that occurred +} + +// Response for operations that return data +export interface DataResponse extends ApiResponse { + data?: T; +} + +// Response specifically for validation operations +export interface ValidationResponse extends ApiResponse { + valid: boolean; // Whether the validated content is valid + data?: unknown; // Optional validated/transformed data +} /** * Interface for storing metadata on IPFS. @@ -50,34 +67,18 @@ export interface ValidateMetadataWithAllowlistRequest extends ValidateMetadataRequest, ValidateAllowListRequest {} -/** - * Interface for a generic API response. - */ -export type ApiResponse = { - success: boolean; - data?: T; - message?: string; - errors?: Record | Error[]; -}; - /** * Interface for a storage response. */ -export type StorageResponse = ApiResponse<{ cid: string }>; +export interface StorageResponse extends DataResponse<{ cid: string }> {} /** * Interface for a validation response. */ -export type ValidationResult = { - valid: boolean; - data?: T; - errors?: Record; -}; - -/** - * Interface for a validation response. - */ -export type ValidationResponse = ApiResponse; +export interface ValidationResponse extends ApiResponse { + valid: boolean; // Whether the validated content is valid + data?: unknown; // Optional validated/transformed data +} /** * Interface for a user add or update request. @@ -102,14 +103,13 @@ export type AddOrUpdateUserRequest = | EOAUserUpsertRequest | MultisigUserUpsertRequest; -export type AddOrUpdateUserResponse = ApiResponse<{ address: string } | null>; +export interface UserResponse extends DataResponse<{ address: string }> {} /** * Interface for a blueprint add or update request. */ -export type AddOrCreateBlueprintResponse = ApiResponse<{ - blueprint_id: number; -} | null>; +export interface BlueprintResponse + extends DataResponse<{ blueprint_id: number }> {} export type BlueprintDeleteRequest = { signature: string; @@ -120,9 +120,7 @@ export type BlueprintDeleteRequest = { /** * Response for a created hyperboard */ -export type HyperboardCreateResponse = ApiResponse<{ - id: string; -} | null>; +export interface HyperboardResponse extends DataResponse<{ id: string }> {} /** * Interface for creating a hyperboard diff --git a/test/api/v1/allowlist.test.ts b/test/api/v1/allowlist.test.ts index 2a4be2cf..64c3dd7a 100644 --- a/test/api/v1/allowlist.test.ts +++ b/test/api/v1/allowlist.test.ts @@ -1,101 +1,122 @@ -import { describe, it, afterEach, afterAll } from "vitest"; +import { describe, test, vi } from "vitest"; import { expect } from "chai"; -import { createMocks, RequestMethod } from "node-mocks-http"; -import { Request, Response } from "express"; - -import sinon from "sinon"; +import { + incorrectMerkleTree, + mockMerkleTree, +} from "../../test-utils/mockMerkleTree.js"; +import { mock } from "vitest-mock-extended"; +import { AllowListController } from "../../../src/controllers/AllowListController.js"; +import { StorageService } from "../../../src/services/StorageService.js"; + +const mocks = vi.hoisted(() => { + return { + init: vi.fn(), + }; +}); -import { data } from "../../test-utils"; -import { Client } from "@web3-storage/w3up-client"; -import { allowlistHandler } from "@/handlers/v1/web3up/allowlist"; -import { Link } from "@web3-storage/access"; +vi.mock("../../../src/services/StorageService", async () => { + return { + StorageService: { init: mocks.init }, + }; +}); -describe("W3Up Client allowlist", async () => { - const { metadata, merkleTree, someData } = data; +describe("Allow list upload at v1/allowlists", async () => { + const controller = new AllowListController(); + const mockStorage = mock(); - const storeBlobMock = sinon - .stub(Client.prototype, "uploadFile") - .resolves({ "/": merkleTree.cid } as unknown as Link); //TODO better Link object creation + test("Stores a new allowlist and returns CID", async () => { + mocks.init.mockResolvedValue(mockStorage); - const mockRequestResponse = (method: RequestMethod = "POST") => { - const { req, res }: { req: Request; res: Response } = createMocks({ - method, - }); - req.headers = { - "Content-Type": "application/json", + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); + const requestBody = { + allowList: mockMerkleTree, + totalUnits: "100000000", }; - req.body = { allowList: merkleTree.data, totalUnits: 100n }; - return { req, res }; - }; - - afterEach(() => { - sinon.resetHistory(); - }); + const response = await controller.storeAllowList(requestBody); - afterAll(() => { - sinon.resetBehavior(); + expect(response.success).to.be.true; + expect(response.data).to.not.be.undefined; + expect(response.data?.cid).to.eq("TEST_CID"); }); - it("POST valid allowList - 200", async () => { - const { req, res } = mockRequestResponse(); - await allowlistHandler(req, res); + test("Returns errors and message when allowlist is invalid", async () => { + mocks.init.mockResolvedValue(mockStorage); - expect(res.statusCode).to.eq(200); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); - - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Data uploaded succesfully"); - // @ts-ignore - expect(res._getJSONData().cid).to.not.be.undefined; + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); + const requestBody = { + allowList: incorrectMerkleTree, + totalUnits: "100000000", + }; + const response = await controller.storeAllowList(requestBody); - expect(storeBlobMock.callCount).to.eq(1); + expect(response.success).to.be.false; + expect(response.data).to.be.undefined; + expect(response.message).to.eq("Errors while validating allow list"); + expect(response.errors).to.deep.eq({ + allowListData: "Data could not be parsed to OpenZeppelin MerkleTree", + }); }); - it("GET allowlist not allowed - 405", async () => { - const { req, res } = mockRequestResponse(); - req.method = "GET"; - await allowlistHandler(req, res); + test("Handles errors during upload", async () => { + mocks.init.mockResolvedValue(mockStorage); - expect(res.statusCode).to.eq(405); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + const mockError = new Error("Error uploading data"); + mockStorage.uploadFile.mockRejectedValue(mockError); - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Not allowed"); + const requestBody = { + allowList: mockMerkleTree, + totalUnits: "100000000", + }; + const response = await controller.storeAllowList(requestBody); - expect(storeBlobMock.callCount).to.eq(0); + expect(response.success).to.be.false; + expect(response.data).to.be.undefined; + expect(response.errors).to.deep.eq({ + allowList: "Error uploading data", + }); }); +}); - it("POST incorrect allowlist - 400", async () => { - const { req, res } = mockRequestResponse(); - req.body = { allowList: data.someData.data, totalUnits: 100n }; - await allowlistHandler(req, res); - - expect(res.statusCode).to.eq(400); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); +describe("Allow list validation at v1/allowlists/validate", async () => { + const controller = new AllowListController(); - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Not a valid merkle tree object"); + test("Validates correctness of allowlist and returns results", async () => { + const requestBody = { + allowList: mockMerkleTree, + totalUnits: "100000000", + }; + const response = await controller.validateAllowList(requestBody); + expect(response.valid).to.be.true; + expect(response.success).to.be.true; }); - it("POST upload allowlist fails - 500", async () => { - const { req, res } = mockRequestResponse(); - storeBlobMock.rejects(); - await allowlistHandler(req, res); + test("Returns errors and message when allowlist is invalid", async () => { + const requestBody = { + allowList: incorrectMerkleTree, + totalUnits: "100000000", + }; + const response = await controller.validateAllowList(requestBody); - expect(res.statusCode).to.eq(500); - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); - expect(res.statusMessage).to.eq("OK"); + expect(response.success).to.be.true; + expect(response.valid).to.be.false; + expect(response.message).to.eq("Errors while validating allow list"); + expect(response.errors).to.deep.eq({ + allowListData: "Data could not be parsed to OpenZeppelin MerkleTree", + }); + }); - //TODO better typing and check on returned CID - // @ts-ignore - expect(res._getJSONData().message).to.eq("Error uploading data"); + test("Returns errors and message when total units doesn't match allow list", async () => { + const requestBody = { + allowList: mockMerkleTree, + totalUnits: "99", + }; + const response = await controller.validateAllowList(requestBody); - expect(storeBlobMock.callCount).to.eq(1); + expect(response.success).to.be.true; + expect(response.valid).to.be.false; + expect(response.message).to.eq("Errors while validating allow list"); + expect(response.errors).to.deep.eq({ + totalUnits: "Total units do not match the sum of units in the allowlist", + }); }); }); diff --git a/test/api/v1/metadata.test.ts b/test/api/v1/metadata.test.ts index 707ff64d..147bcc54 100644 --- a/test/api/v1/metadata.test.ts +++ b/test/api/v1/metadata.test.ts @@ -69,24 +69,21 @@ describe("Metadata validation at v1/metadata/validate", async () => { const controller = new MetadataController(); test("Validates a metadata set and returns results", async () => { - const requestBody = mockMetadata; - - const response = await controller.validateMetadata(requestBody); + const response = await controller.validateMetadata({ + metadata: mockMetadata, + }); - expect(response.valid).to.be.true; expect(response.success).to.be.true; expect(response.message).to.be.eq("Metadata is valid hypercert metadata"); }); test("Returns errors and message when metadata is invalid", async () => { - const requestBody = incorrectMetadata; - - const response = await controller.validateMetadata(requestBody); + const response = await controller.validateMetadata({ + metadata: incorrectMetadata, + }); expect(response.success).to.be.true; - expect(response.message).to.eq( - "Errors while validating metadata and/or allow list", - ); + expect(response.message).to.eq("Errors while validating metadata"); expect(response.errors).to.deep.eq({ metadata: "Provided metadata is not a valid hypercert metadata object", }); diff --git a/test/test-utils/mockMerkleTree.ts b/test/test-utils/mockMerkleTree.ts index 494f04d7..96137f00 100644 --- a/test/test-utils/mockMerkleTree.ts +++ b/test/test-utils/mockMerkleTree.ts @@ -1,4 +1,8 @@ const stringContent = - '{"format":"standard-v1","tree":["0xe582f894ed4599f64aed0c4f6ea1ed2d2f7bca47a11c984e63d823a38d4f43b6"],"values":[{"value":["0x59266D85D94666D037C1e32dAa8FaC9E95CdaFEf",100],"treeIndex":0}],"leafEncoding":["address","uint256"]}'; + '{"format":"standard-v1","tree":["0xe582f894ed4599f64aed0c4f6ea1ed2d2f7bca47a11c984e63d823a38d4f43b6"],"values":[{"value":["0x59266D85D94666D037C1e32dAa8FaC9E95CdaFEf",100000000],"treeIndex":0}],"leafEncoding":["address","uint256"]}'; export const mockMerkleTree = stringContent; +export const incorrectMerkleTree = stringContent.replace( + '"leafEncoding":["address","uint256"]', + '"leafEncoding":[null,null]', +); From e7066e1177b357a79bed60ac09f5c566d30cca5e Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 21:34:29 +0100 Subject: [PATCH 09/11] fix(types): cleanup controller types Update all controllers to use the updated types structure. Rebuild the updated API routes and Swagger. --- src/__generated__/routes/routes.ts | 115 +++---- src/__generated__/swagger.json | 397 ++++++++++------------- src/controllers/AllowListController.ts | 12 +- src/controllers/BlueprintController.ts | 8 +- src/controllers/HyperboardController.ts | 10 +- src/controllers/MarketplaceController.ts | 10 +- src/controllers/MetadataController.ts | 12 +- src/controllers/UserController.ts | 4 +- src/types/api.ts | 97 ++---- 9 files changed, 292 insertions(+), 373 deletions(-) diff --git a/src/__generated__/routes/routes.ts b/src/__generated__/routes/routes.ts index 0ada2c3f..386b8a3f 100644 --- a/src/__generated__/routes/routes.ts +++ b/src/__generated__/routes/routes.ts @@ -28,29 +28,25 @@ const models: TsoaRoute.Models = { "type": {"dataType":"nestedObjectLiteral","nestedProperties":{},"additionalProperties":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"array","array":{"dataType":"string"}}]},"validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "Error": { + "UserResponse": { "dataType": "refObject", "properties": { - "name": {"dataType":"string","required":true}, - "message": {"dataType":"string","required":true}, - "stack": {"dataType":"string"}, + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "data": {"dataType":"nestedObjectLiteral","nestedProperties":{"address":{"dataType":"string","required":true}}}, }, "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse__address-string_-or-null_": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"address":{"dataType":"string","required":true}}},{"dataType":"enum","enums":[null]}]},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AddOrUpdateUserResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse__address-string_-or-null_","validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"void"},"success":{"dataType":"boolean","required":true}},"validators":{}}, + "BaseResponse": { + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "EOAUserUpsertRequest": { @@ -90,16 +86,6 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse__cid-string__": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"nestedObjectLiteral","nestedProperties":{"cid":{"dataType":"string","required":true}}},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "StorageResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse__cid-string__","validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "HypercertClaimdata": { "dataType": "refObject", "properties": { @@ -137,6 +123,17 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "StorageResponse": { + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "data": {"dataType":"nestedObjectLiteral","nestedProperties":{"cid":{"dataType":"string","required":true}}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "StoreMetadataWithAllowlistRequest": { "dataType": "refObject", "properties": { @@ -147,19 +144,16 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ValidationResult": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"ref":"Record_string.string-or-string-Array_"},"data":{"dataType":"void"},"valid":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse_ValidationResult_": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"ref":"ValidationResult"},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ValidationResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse_ValidationResult_","validators":{}}, + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "valid": {"dataType":"boolean","required":true}, + "data": {"dataType":"any"}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ValidateMetadataRequest": { @@ -227,14 +221,15 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse__id-string_-or-null_": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"id":{"dataType":"string","required":true}}},{"dataType":"enum","enums":[null]}]},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "HyperboardCreateResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse__id-string_-or-null_","validators":{}}, + "HyperboardResponse": { + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "data": {"dataType":"nestedObjectLiteral","nestedProperties":{"id":{"dataType":"string","required":true}}}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "HyperboardCreateRequest": { @@ -266,14 +261,15 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResponse__blueprint_id-number_-or-null_": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"errors":{"dataType":"union","subSchemas":[{"ref":"Record_string.string-or-string-Array_"},{"dataType":"array","array":{"dataType":"refObject","ref":"Error"}}]},"message":{"dataType":"string"},"data":{"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"blueprint_id":{"dataType":"double","required":true}}},{"dataType":"enum","enums":[null]}]},"success":{"dataType":"boolean","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AddOrCreateBlueprintResponse": { - "dataType": "refAlias", - "type": {"ref":"ApiResponse__blueprint_id-number_-or-null_","validators":{}}, + "BlueprintResponse": { + "dataType": "refObject", + "properties": { + "success": {"dataType":"boolean","required":true}, + "message": {"dataType":"string"}, + "errors": {"ref":"Record_string.string-or-string-Array_"}, + "data": {"dataType":"nestedObjectLiteral","nestedProperties":{"blueprint_id":{"dataType":"double","required":true}}}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "BlueprintCreateRequest": { @@ -289,8 +285,13 @@ const models: TsoaRoute.Models = { }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "BlueprintDeleteRequest": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"admin_address":{"dataType":"string","required":true},"chain_id":{"dataType":"double","required":true},"signature":{"dataType":"string","required":true}},"validators":{}}, + "dataType": "refObject", + "properties": { + "signature": {"dataType":"string","required":true}, + "chain_id": {"dataType":"double","required":true}, + "admin_address": {"dataType":"string","required":true}, + }, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "BlueprintQueueMintRequest": { diff --git a/src/__generated__/swagger.json b/src/__generated__/swagger.json index b1c00e43..f7a901c1 100644 --- a/src/__generated__/swagger.json +++ b/src/__generated__/swagger.json @@ -25,42 +25,16 @@ "type": "object", "description": "Construct a type with a set of properties K of type T" }, - "Error": { + "UserResponse": { "properties": { - "name": { - "type": "string" + "success": { + "type": "boolean" }, "message": { "type": "string" }, - "stack": { - "type": "string" - } - }, - "required": [ - "name", - "message" - ], - "type": "object", - "additionalProperties": false - }, - "ApiResponse__address-string_-or-null_": { - "properties": { "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] - }, - "message": { - "type": "string" + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" }, "data": { "properties": { @@ -71,50 +45,32 @@ "required": [ "address" ], - "type": "object", - "nullable": true - }, - "success": { - "type": "boolean" + "type": "object" } }, "required": [ "success" ], "type": "object", - "description": "Interface for a generic API response." - }, - "AddOrUpdateUserResponse": { - "$ref": "#/components/schemas/ApiResponse__address-string_-or-null_" + "additionalProperties": false }, - "ApiResponse": { + "BaseResponse": { "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] + "success": { + "type": "boolean" }, "message": { "type": "string" }, - "data": {}, - "success": { - "type": "boolean" + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" } }, "required": [ "success" ], "type": "object", - "description": "Interface for a generic API response." + "additionalProperties": false }, "EOAUserUpsertRequest": { "properties": { @@ -203,49 +159,6 @@ "type": "object", "additionalProperties": false }, - "ApiResponse__cid-string__": { - "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] - }, - "message": { - "type": "string" - }, - "data": { - "properties": { - "cid": { - "type": "string" - } - }, - "required": [ - "cid" - ], - "type": "object" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "success" - ], - "type": "object", - "description": "Interface for a generic API response." - }, - "StorageResponse": { - "$ref": "#/components/schemas/ApiResponse__cid-string__", - "description": "Interface for a storage response." - }, "HypercertClaimdata": { "description": "Properties of an impact claim", "properties": { @@ -453,7 +366,6 @@ "additionalProperties": false }, "StoreMetadataRequest": { - "description": "Interface for storing metadata on IPFS.", "properties": { "metadata": { "$ref": "#/components/schemas/HypercertMetadata" @@ -465,8 +377,36 @@ "type": "object", "additionalProperties": false }, + "StorageResponse": { + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" + }, + "data": { + "properties": { + "cid": { + "type": "string" + } + }, + "required": [ + "cid" + ], + "type": "object" + } + }, + "required": [ + "success" + ], + "type": "object", + "additionalProperties": false + }, "StoreMetadataWithAllowlistRequest": { - "description": "Interface for storing metadata and allow list dump on IPFS.", "properties": { "metadata": { "$ref": "#/components/schemas/HypercertMetadata" @@ -485,59 +425,30 @@ "type": "object", "additionalProperties": false }, - "ValidationResult": { + "ValidationResponse": { "properties": { - "errors": { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - "data": {}, - "valid": { + "success": { "type": "boolean" - } - }, - "required": [ - "valid" - ], - "type": "object", - "description": "Interface for a validation response." - }, - "ApiResponse_ValidationResult_": { - "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] }, "message": { "type": "string" }, - "data": { - "$ref": "#/components/schemas/ValidationResult" + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" }, - "success": { + "valid": { "type": "boolean" - } + }, + "data": {} }, "required": [ - "success" + "success", + "valid" ], "type": "object", - "description": "Interface for a generic API response." - }, - "ValidationResponse": { - "$ref": "#/components/schemas/ApiResponse_ValidationResult_", - "description": "Interface for a validation response." + "additionalProperties": false }, "ValidateMetadataRequest": { - "description": "Interface for validating metadata.", "properties": { "metadata": { "$ref": "#/components/schemas/HypercertMetadata" @@ -733,24 +644,17 @@ "type": "object", "additionalProperties": false }, - "ApiResponse__id-string_-or-null_": { + "HyperboardResponse": { "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] + "success": { + "type": "boolean" }, "message": { "type": "string" }, + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" + }, "data": { "properties": { "id": { @@ -760,25 +664,16 @@ "required": [ "id" ], - "type": "object", - "nullable": true - }, - "success": { - "type": "boolean" + "type": "object" } }, "required": [ "success" ], "type": "object", - "description": "Interface for a generic API response." - }, - "HyperboardCreateResponse": { - "$ref": "#/components/schemas/ApiResponse__id-string_-or-null_", - "description": "Response for a created hyperboard" + "additionalProperties": false }, "HyperboardCreateRequest": { - "description": "Interface for creating a hyperboard", "properties": { "chainIds": { "items": { @@ -877,7 +772,6 @@ "additionalProperties": false }, "HyperboardUpdateRequest": { - "description": "Interface for updating a hyperboard", "properties": { "chainIds": { "items": { @@ -979,24 +873,17 @@ "type": "object", "additionalProperties": false }, - "ApiResponse__blueprint_id-number_-or-null_": { + "BlueprintResponse": { "properties": { - "errors": { - "anyOf": [ - { - "$ref": "#/components/schemas/Record_string.string-or-string-Array_" - }, - { - "items": { - "$ref": "#/components/schemas/Error" - }, - "type": "array" - } - ] + "success": { + "type": "boolean" }, "message": { "type": "string" }, + "errors": { + "$ref": "#/components/schemas/Record_string.string-or-string-Array_" + }, "data": { "properties": { "blueprint_id": { @@ -1007,22 +894,14 @@ "required": [ "blueprint_id" ], - "type": "object", - "nullable": true - }, - "success": { - "type": "boolean" + "type": "object" } }, "required": [ "success" ], "type": "object", - "description": "Interface for a generic API response." - }, - "AddOrCreateBlueprintResponse": { - "$ref": "#/components/schemas/ApiResponse__blueprint_id-number_-or-null_", - "description": "Interface for a blueprint add or update request." + "additionalProperties": false }, "BlueprintCreateRequest": { "properties": { @@ -1053,23 +932,24 @@ }, "BlueprintDeleteRequest": { "properties": { - "admin_address": { + "signature": { "type": "string" }, "chain_id": { "type": "number", "format": "double" }, - "signature": { + "admin_address": { "type": "string" } }, "required": [ - "admin_address", + "signature", "chain_id", - "signature" + "admin_address" ], - "type": "object" + "type": "object", + "additionalProperties": false }, "BlueprintQueueMintRequest": { "properties": { @@ -1097,7 +977,6 @@ "additionalProperties": false }, "StoreAllowListRequest": { - "description": "Interface for storing an allow list dump on IPFS", "properties": { "allowList": { "type": "string" @@ -1113,7 +992,6 @@ "additionalProperties": false }, "ValidateAllowListRequest": { - "description": "Interface for validating an allow list dump.", "properties": { "allowList": { "type": "string" @@ -1149,7 +1027,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddOrUpdateUserResponse" + "$ref": "#/components/schemas/UserResponse" } } } @@ -1159,7 +1037,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1328,7 +1206,85 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StorageResponse" + "anyOf": [ + { + "properties": { + "data": {}, + "errors": {}, + "message": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "errors", + "message", + "valid", + "success" + ], + "type": "object" + }, + { + "properties": { + "errors": {}, + "message": {}, + "valid": {}, + "data": { + "properties": { + "cid": { + "type": "string" + } + }, + "required": [ + "cid" + ], + "type": "object" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "data", + "success" + ], + "type": "object" + }, + { + "properties": { + "data": {}, + "valid": {}, + "errors": { + "properties": { + "metadata": { + "type": "string" + } + }, + "required": [ + "metadata" + ], + "type": "object" + }, + "message": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "errors", + "message", + "success" + ], + "type": "object" + } + ] } } } @@ -1338,7 +1294,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1394,7 +1350,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1415,7 +1371,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1471,7 +1427,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1527,7 +1483,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1784,7 +1740,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1847,7 +1803,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -1948,7 +1904,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2037,7 +1993,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2078,7 +2034,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HyperboardCreateResponse" + "$ref": "#/components/schemas/HyperboardResponse" } } } @@ -2088,7 +2044,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2129,7 +2085,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse__id-string_-or-null_" + "$ref": "#/components/schemas/HyperboardResponse" } } } @@ -2139,7 +2095,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2186,7 +2142,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" } } } @@ -2196,7 +2152,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2251,7 +2207,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddOrCreateBlueprintResponse" + "$ref": "#/components/schemas/BlueprintResponse" } } } @@ -2261,7 +2217,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BlueprintResponse" }, "examples": { "Example 1": { @@ -2409,7 +2365,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2462,7 +2418,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddOrCreateBlueprintResponse" + "$ref": "#/components/schemas/BlueprintResponse" } } } @@ -2472,7 +2428,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/BaseResponse" }, "examples": { "Example 1": { @@ -2535,7 +2491,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/StorageResponse" }, "examples": { "Example 1": { @@ -2589,12 +2545,13 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiResponse" + "$ref": "#/components/schemas/ValidationResponse" }, "examples": { "Example 1": { "value": { "success": false, + "valid": false, "message": "Metadata validation failed", "errors": { "allowList": "Invalid allowList. Length is 0" diff --git a/src/controllers/AllowListController.ts b/src/controllers/AllowListController.ts index 8d957d17..fd08548f 100644 --- a/src/controllers/AllowListController.ts +++ b/src/controllers/AllowListController.ts @@ -59,12 +59,12 @@ export class AllowListController extends Controller { success: true, data: cid, }; - } catch (error) { + } catch (e) { this.setStatus(500); return { success: false, - message: "Error uploading data", - errors: { allowList: "Error uploading data" }, + message: "Errors while validating allow list", + errors: { allowList: (e as Error).message }, }; } } @@ -103,13 +103,13 @@ export class AllowListController extends Controller { success: true, valid: true, }; - } catch (error) { + } catch (e) { this.setStatus(500); return { success: false, valid: false, - message: "Error uploading data", - errors: { allowList: "Error uploading data" }, + message: "Errors while validating allow list", + errors: { allowList: (e as Error).message }, }; } } diff --git a/src/controllers/BlueprintController.ts b/src/controllers/BlueprintController.ts index 7a4218a4..b2d43736 100644 --- a/src/controllers/BlueprintController.ts +++ b/src/controllers/BlueprintController.ts @@ -11,10 +11,10 @@ import { } from "tsoa"; import type { BlueprintResponse, - ApiResponse, BlueprintCreateRequest, BlueprintDeleteRequest, BlueprintQueueMintRequest, + BaseResponse, } from "../types/api.js"; import { z } from "zod"; import { SupabaseDataService } from "../services/SupabaseDataService.js"; @@ -29,7 +29,7 @@ import { waitForTxThenMintBlueprint } from "../utils/waitForTxThenMintBlueprint. export class BlueprintController extends Controller { @Post() @SuccessResponse(201, "Blueprint created successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { blueprint: "Invalid blueprint." }, @@ -211,7 +211,7 @@ export class BlueprintController extends Controller { // Delete blueprint method @Delete("{blueprintId}") @SuccessResponse(200, "Blueprint deleted successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { blueprint: "Invalid blueprint." }, @@ -307,7 +307,7 @@ export class BlueprintController extends Controller { @Post("mint/{blueprintId}") @SuccessResponse(201, "Blueprint minted successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { blueprint: "Invalid blueprint." }, diff --git a/src/controllers/HyperboardController.ts b/src/controllers/HyperboardController.ts index b92cfc35..2d51f237 100644 --- a/src/controllers/HyperboardController.ts +++ b/src/controllers/HyperboardController.ts @@ -12,7 +12,7 @@ import { Patch, } from "tsoa"; import type { - ApiResponse, + BaseResponse, HyperboardCreateRequest, HyperboardResponse, HyperboardUpdateRequest, @@ -37,7 +37,7 @@ export class HyperboardController extends Controller { */ @Post() @SuccessResponse(201, "Data uploaded successfully", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while validating hyperboard", }) @@ -441,7 +441,7 @@ export class HyperboardController extends Controller { @Patch("{hyperboardId}") @SuccessResponse(204, "Hyperboard updated successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while updating hyperboard", }) @@ -871,7 +871,7 @@ export class HyperboardController extends Controller { @Delete("{hyperboardId}") @SuccessResponse(204, "Hyperboard deleted successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while deleting hyperboard", }) @@ -879,7 +879,7 @@ export class HyperboardController extends Controller { @Path() hyperboardId: string, @Query() adminAddress: string, @Query() signature: string, - ): Promise { + ): Promise { const inputSchema = z.object({ adminAddress: z.string(), signature: z.string(), diff --git a/src/controllers/MarketplaceController.ts b/src/controllers/MarketplaceController.ts index bcb15aba..e719ef61 100644 --- a/src/controllers/MarketplaceController.ts +++ b/src/controllers/MarketplaceController.ts @@ -15,7 +15,6 @@ import { Tags, } from "tsoa"; import { z } from "zod"; -import { ApiResponse } from "../types/api.js"; import { isAddress, verifyMessage } from "viem"; import { SupabaseDataService } from "../services/SupabaseDataService.js"; @@ -23,6 +22,7 @@ import { getFractionsById } from "../utils/getFractionsById.js"; import { getRpcUrl } from "../utils/getRpcUrl.js"; import { isParsableToBigInt } from "../utils/isParsableToBigInt.js"; import { getHypercertTokenId } from "../utils/tokenIds.js"; +import { BaseResponse } from "../types/api.js"; export interface CreateOrderRequest { signature: string; @@ -63,7 +63,7 @@ export class MarketplaceController extends Controller { */ @Post("/orders") @SuccessResponse(201, "Order created successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Order could not be created", }) @@ -263,7 +263,7 @@ export class MarketplaceController extends Controller { * Updates and returns the order nonce for a user on a specific chain. */ @Post("/order-nonce") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Order nonce could not be updated", }) @@ -360,7 +360,7 @@ export class MarketplaceController extends Controller { */ @Post("/orders/validate") @SuccessResponse(200, "Order validated successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Order could not be validated", }) @@ -411,7 +411,7 @@ export class MarketplaceController extends Controller { */ @Delete("/orders") @SuccessResponse(200, "Order deleted successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Order could not be deleted", }) diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index 6c045bc2..e6688648 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -10,7 +10,7 @@ import { } from "tsoa"; import { StorageService } from "../services/StorageService.js"; import type { - ApiResponse, + BaseResponse, StorageResponse, StoreMetadataRequest, StoreMetadataWithAllowlistRequest, @@ -35,7 +35,7 @@ export class MetadataController extends Controller { */ @Post() @SuccessResponse(201, "Data uploaded successfully") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { metadata: "Invalid metadata." }, @@ -102,12 +102,12 @@ export class MetadataController extends Controller { */ @Post("/with-allowlist") @SuccessResponse(201, "Data uploaded successfully") - @Response(409, "Conflict", { + @Response(409, "Conflict", { success: false, message: "Allow list detected in metadata", errors: { metadata: "Allowlist URI already present in metadata." }, }) - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { metadata: "Invalid metadata." }, @@ -187,7 +187,7 @@ export class MetadataController extends Controller { */ @Post("/validate") @SuccessResponse(200, "Valid metadata", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { metadata: "Invalid metadata." }, @@ -252,7 +252,7 @@ export class MetadataController extends Controller { */ @Post("/with-allowlist/validate") @SuccessResponse(200, "Valid metadata", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Validation failed", errors: { metadata: "Invalid metadata." }, diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index c76ee1b7..42935437 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -12,7 +12,7 @@ import { import type { AddOrUpdateUserRequest, - ApiResponse, + BaseResponse, UserResponse, } from "../types/api.js"; import { UserUpsertError } from "../lib/users/errors.js"; @@ -41,7 +41,7 @@ export class UserController extends Controller { */ @Post(`{address}`) @SuccessResponse(201, "User updated successfully", "application/json") - @Response(422, "Unprocessable content", { + @Response(422, "Unprocessable content", { success: false, message: "Errors while validating user", }) diff --git a/src/types/api.ts b/src/types/api.ts index b0557b5f..9d3fa565 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -6,83 +6,54 @@ import type { HypercertMetadata } from "@hypercerts-org/sdk"; * See https://github.com/lukeautry/tsoa/issues/1256. */ // Base response type for all API responses -export interface ApiResponse { +export interface BaseResponse { success: boolean; // Whether the API call itself succeeded message?: string; // Human readable message about the operation errors?: Record; // Any errors that occurred } // Response for operations that return data -export interface DataResponse extends ApiResponse { +export interface DataResponse extends BaseResponse { data?: T; } // Response specifically for validation operations -export interface ValidationResponse extends ApiResponse { +export interface ValidationResponse extends BaseResponse { valid: boolean; // Whether the validated content is valid data?: unknown; // Optional validated/transformed data } -/** - * Interface for storing metadata on IPFS. - */ +// Storage-related interfaces +export interface StorageResponse extends DataResponse<{ cid: string }> {} + export interface StoreMetadataRequest { metadata: HypercertMetadata; } -/** - * Interface for storing an allow list dump on IPFS - */ export interface StoreAllowListRequest { allowList: string; totalUnits?: string; } -/** - * Interface for storing metadata and allow list dump on IPFS. - */ export interface StoreMetadataWithAllowlistRequest extends StoreMetadataRequest, StoreAllowListRequest {} -/** - * Interface for validating metadata. - */ +// Validation-related interfaces export interface ValidateMetadataRequest { metadata: HypercertMetadata; } -/** - * Interface for validating an allow list dump. - */ export interface ValidateAllowListRequest { allowList: string; totalUnits?: string; } -/** - * Interface for validating metadata and allow list dump. - */ export interface ValidateMetadataWithAllowlistRequest extends ValidateMetadataRequest, ValidateAllowListRequest {} -/** - * Interface for a storage response. - */ -export interface StorageResponse extends DataResponse<{ cid: string }> {} - -/** - * Interface for a validation response. - */ -export interface ValidationResponse extends ApiResponse { - valid: boolean; // Whether the validated content is valid - data?: unknown; // Optional validated/transformed data -} - -/** - * Interface for a user add or update request. - */ +// User-related interfaces interface BaseUserUpsertRequest { chain_id: number; } @@ -105,26 +76,32 @@ export type AddOrUpdateUserRequest = export interface UserResponse extends DataResponse<{ address: string }> {} -/** - * Interface for a blueprint add or update request. - */ -export interface BlueprintResponse - extends DataResponse<{ blueprint_id: number }> {} +// Blueprint-related interfaces +export interface BlueprintCreateRequest { + form_values: unknown; + minter_address: string; + admin_address: string; + signature: string; + chain_id: number; +} + +export interface BlueprintQueueMintRequest { + signature: string; + chain_id: number; + minter_address: string; + tx_hash: string; +} -export type BlueprintDeleteRequest = { +export interface BlueprintDeleteRequest { signature: string; chain_id: number; admin_address: string; -}; +} -/** - * Response for a created hyperboard - */ -export interface HyperboardResponse extends DataResponse<{ id: string }> {} +export interface BlueprintResponse + extends DataResponse<{ blueprint_id: number }> {} -/** - * Interface for creating a hyperboard - */ +// Hyperboard-related interfaces export interface HyperboardCreateRequest { chainIds: number[]; title: string; @@ -147,24 +124,8 @@ export interface HyperboardCreateRequest { signature: string; } -/** - * Interface for updating a hyperboard - */ export interface HyperboardUpdateRequest extends HyperboardCreateRequest { id: string; } -export interface BlueprintCreateRequest { - form_values: unknown; - minter_address: string; - admin_address: string; - signature: string; - chain_id: number; -} - -export interface BlueprintQueueMintRequest { - signature: string; - chain_id: number; - minter_address: string; - tx_hash: string; -} +export interface HyperboardResponse extends DataResponse<{ id: string }> {} From 046fa24398dbbbdfce56002af110f1e35a8f302f Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 22:13:57 +0100 Subject: [PATCH 10/11] chore(coverage): add coverage reporting and ci Adds vitest coverage reporting and CI action --- .github/workflows/ci-test-unit.yml | 54 +++ package.json | 4 +- pnpm-lock.yaml | 591 +++++++++++++++++++---------- vitest.config.ts | 26 +- 4 files changed, 473 insertions(+), 202 deletions(-) create mode 100644 .github/workflows/ci-test-unit.yml diff --git a/.github/workflows/ci-test-unit.yml b/.github/workflows/ci-test-unit.yml new file mode 100644 index 00000000..1e37afc2 --- /dev/null +++ b/.github/workflows/ci-test-unit.yml @@ -0,0 +1,54 @@ +name: "Unit tests" +on: + pull_request: + +jobs: + unit-test: + runs-on: ubuntu-latest + environment: test + + env: + PORT: 4000 + SUPABASE_CACHING_DB_URL: "https://test.supabase.com" + SUPABASE_CACHING_ANON_API_KEY: "test" + SUPABASE_DATA_DB_URL: "https://test.supabase.com" + SUPABASE_DATA_SERVICE_API_KEY: "test" + KEY: "test" + PROOF: "test" + + ALCHEMY_API_KEY: "test" + DRPC_API_KEY: "test" + INFURA_API_KEY: "test" + + INDEXER_ENVIRONMENT: "test" + + CACHING_DATABASE_URL: "https://test.supabase.com" + DATA_DATABASE_URL: "https://test.supabase.com" + + permissions: + # Required to checkout the code + contents: read + # Required to put a comment into the pull-request + pull-requests: write + + steps: + - uses: actions/checkout@v4 + - name: "Install Node" + uses: actions/setup-node@v4 + with: + node-version: "20.x" + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Install dependencies + run: pnpm install + + - name: "Run unit tests" + run: pnpm test:coverage + - name: "Report Coverage" + # Set if: always() to also generate the report if tests are failing + # Only works if you set `reportOnFailure: true` in your vite config as specified above + if: always() + uses: davelosert/vitest-coverage-report-action@v2 diff --git a/package.json b/package.json index 2bdcd52b..36245d81 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "preintegration": "pnpm --dir ./lib/hypercerts-indexer i", "lint": "eslint", "test": "vitest", + "test:coverage": "vitest --coverage.enabled true", "prepare": "husky", "commitlint": "commitlint --config commitlintrc.ts --edit" }, @@ -98,6 +99,7 @@ "@types/pg": "^8.11.6", "@types/sinon": "^17.0.2", "@types/swagger-ui-express": "^4.1.6", + "@vitest/coverage-v8": "^2.1.8", "chai": "^5.0.0", "chai-assertions-count": "^1.0.2", "concurrently": "^8.2.2", @@ -120,7 +122,7 @@ "typescript": "5.5.3", "typescript-eslint": "^7.7.0", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^1.1.3", + "vitest": "^2.1.8", "vitest-mock-extended": "^2.0.2" }, "lint-staged": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2219b75a..ea001eb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -216,6 +216,9 @@ importers: '@types/swagger-ui-express': specifier: ^4.1.6 version: 4.1.6 + '@vitest/coverage-v8': + specifier: ^2.1.8 + version: 2.1.8(vitest@2.1.8(@types/node@20.10.6)) chai: specifier: ^5.0.0 version: 5.0.0 @@ -283,11 +286,11 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.5.3)(vite@5.0.11(@types/node@20.10.6)) vitest: - specifier: ^1.1.3 - version: 1.1.3(@types/node@20.10.6) + specifier: ^2.1.8 + version: 2.1.8(@types/node@20.10.6) vitest-mock-extended: specifier: ^2.0.2 - version: 2.0.2(typescript@5.5.3)(vitest@1.1.3(@types/node@20.10.6)) + version: 2.0.2(typescript@5.5.3)(vitest@2.1.8(@types/node@20.10.6)) packages: @@ -330,6 +333,10 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@ardatan/relay-compiler@12.0.0': resolution: {integrity: sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==} peerDependencies: @@ -442,10 +449,18 @@ packages: resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.22.20': resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.23.5': resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} @@ -466,6 +481,11 @@ packages: resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} engines: {node: '>=6.0.0'} + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-proposal-class-properties@7.18.6': resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} @@ -642,6 +662,13 @@ packages: resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -1532,14 +1559,18 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} '@jridgewell/gen-mapping@0.3.3': resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -1548,12 +1579,22 @@ packages: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/trace-mapping@0.3.22': resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -2317,9 +2358,6 @@ packages: '@shikijs/core@1.12.1': resolution: {integrity: sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==} - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -2882,20 +2920,43 @@ packages: '@urql/core@5.0.4': resolution: {integrity: sha512-gl86J6B6gWXvvkx5omZ+CaGiPQ0chCUGM0jBsm0zTtkDQPRqufv0NSUN6sp2JhGGtTOB0NR6Pd+w7XAVGGyUOA==} - '@vitest/expect@1.1.3': - resolution: {integrity: sha512-MnJqsKc1Ko04lksF9XoRJza0bGGwTtqfbyrsYv5on4rcEkdo+QgUdITenBQBUltKzdxW7K3rWh+nXRULwsdaVg==} + '@vitest/coverage-v8@2.1.8': + resolution: {integrity: sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==} + peerDependencies: + '@vitest/browser': 2.1.8 + vitest: 2.1.8 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} - '@vitest/runner@1.1.3': - resolution: {integrity: sha512-Va2XbWMnhSdDEh/OFxyUltgQuuDRxnarK1hW5QNN4URpQrqq6jtt8cfww/pQQ4i0LjoYxh/3bYWvDFlR9tU73g==} + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true - '@vitest/snapshot@1.1.3': - resolution: {integrity: sha512-U0r8pRXsLAdxSVAyGNcqOU2H3Z4Y2dAAGGelL50O0QRMdi1WWeYHdrH/QWpN1e8juWfVKsb8B+pyJwTC+4Gy9w==} + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} - '@vitest/spy@1.1.3': - resolution: {integrity: sha512-Ec0qWyGS5LhATFQtldvChPTAHv08yHIOZfiNcjwRQbFPHpkih0md9KAbs7TfeIfL7OFKoe7B/6ukBTqByubXkQ==} + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} - '@vitest/utils@1.1.3': - resolution: {integrity: sha512-Dyt3UMcdElTll2H75vhxfpZu03uFpXRCHxWnzcrFjZxT1kTbq8ALUYIeBgGolo1gldVdI0YSlQRacsqxTwNqwg==} + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} + + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} + + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} '@volar/language-core@2.4.0-alpha.18': resolution: {integrity: sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==} @@ -3131,10 +3192,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -3208,9 +3265,6 @@ packages: resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==} engines: {node: '>=12.0.0'} - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -3463,14 +3517,14 @@ packages: peerDependencies: chai: '*' - chai@4.4.0: - resolution: {integrity: sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==} - engines: {node: '>=4'} - chai@5.0.0: resolution: {integrity: sha512-HO5p0oEKd5M6HEcwOkNAThAE3j960vIZvVcc0t2tI06Dd0ATu69cEnMB2wOhC5/ZyQ6m67w3ePjU/HzXsSsdBA==} engines: {node: '>=12'} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -3495,13 +3549,14 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - check-error@2.0.0: resolution: {integrity: sha512-tjLAOBHKVxtPoHe/SA7kNOMvhCRdCJ3vETdeY0RuAc9popf+hyaSV6ZEg9hr4cpWF7jmo/JSWEnLDrnijS9Tog==} engines: {node: '>= 16'} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -3859,6 +3914,15 @@ packages: supports-color: optional: true + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -3885,10 +3949,6 @@ packages: babel-plugin-macros: optional: true - deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} - engines: {node: '>=6'} - deep-eql@5.0.1: resolution: {integrity: sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==} engines: {node: '>=6'} @@ -3960,10 +4020,6 @@ packages: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -4089,6 +4145,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -4240,6 +4299,10 @@ packages: resolution: {integrity: sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==} engines: {node: '>=18'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + express@4.19.2: resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} engines: {node: '>= 0.10.0'} @@ -4531,11 +4594,16 @@ packages: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} @@ -4712,6 +4780,9 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -5071,6 +5142,22 @@ packages: peerDependencies: ws: '*' + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + it-all@1.0.6: resolution: {integrity: sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==} @@ -5087,6 +5174,9 @@ packages: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + javascript-stringify@2.1.0: resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} @@ -5234,10 +5324,6 @@ packages: resolution: {integrity: sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==} engines: {node: '>=18.0.0'} - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - localforage@1.10.0: resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} @@ -5323,12 +5409,12 @@ packages: loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - loupe@3.0.2: resolution: {integrity: sha512-Tzlkbynv7dtqxTROe54Il+J4e/zG2iehtJGZUYpTv8WzlkW9qyEcE83UhGJCeuF3SCfzHuM5VWhBi47phV3+AQ==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lower-case-first@2.0.2: resolution: {integrity: sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==} @@ -5363,10 +5449,20 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.5: resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} engines: {node: '>=12'} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -5874,10 +5970,6 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -5902,6 +5994,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} @@ -5975,6 +6070,10 @@ packages: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} engines: {node: '>=16 || 14 >=14.17'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -5991,9 +6090,6 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - pathval@2.0.0: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} @@ -6148,10 +6244,6 @@ packages: resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} engines: {node: '>=14'} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -6242,9 +6334,6 @@ packages: rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} - react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - react-native-fetch-api@3.0.0: resolution: {integrity: sha512-g2rtqPjdroaboDKTsJCTlcmtw54E25OjyaunUP0anOZn4Fuo2IKs8BVfe02zVggA/UysbmfSnRJIqtNkAgggNA==} @@ -6591,10 +6680,6 @@ packages: resolution: {integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==} engines: {node: '>=0.10.0'} - source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} @@ -6638,8 +6723,8 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} @@ -6717,9 +6802,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@1.3.0: - resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} - strip-outer@2.0.0: resolution: {integrity: sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6807,6 +6889,10 @@ packages: resolution: {integrity: sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==} engines: {node: '>=12'} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-extensions@2.4.0: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} @@ -6823,19 +6909,26 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - tinybench@2.5.1: - resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} tinyglobby@0.2.10: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} - tinypool@0.8.1: - resolution: {integrity: sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==} + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyspy@2.2.0: - resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} title-case@3.0.3: @@ -7210,9 +7303,10 @@ packages: typescript: optional: true - vite-node@1.1.3: - resolution: {integrity: sha512-BLSO72YAkIUuNrOx+8uznYICJfTEbvBAmWClY3hpath5+h1mbPS5OMn42lrTxXuyCazVyZoDkSRnju78GiVCqA==} + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} @@ -7225,6 +7319,7 @@ packages: vite@5.0.11: resolution: {integrity: sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==} engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true peerDependencies: '@types/node': ^18.0.0 || >=20.0.0 less: '*' @@ -7255,14 +7350,15 @@ packages: typescript: 3.x || 4.x || 5.x vitest: '>=2.0.0' - vitest@1.1.3: - resolution: {integrity: sha512-2l8om1NOkiA90/Y207PsEvJLYygddsOyr81wLQ20Ra8IlLKbyQncWsGZjnbkyG2KwwuTXLQjEPOJuxGMG8qJBQ==} + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': ^1.0.0 - '@vitest/ui': ^1.0.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -7322,9 +7418,10 @@ packages: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} - why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} + hasBin: true widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} @@ -7574,7 +7671,12 @@ snapshots: '@ampproject/remapping@2.2.1': dependencies: '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.9 + '@jridgewell/trace-mapping': 0.3.22 + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 '@ardatan/relay-compiler@12.0.0(encoding@0.1.13)(graphql@16.8.1)': dependencies: @@ -7743,8 +7845,12 @@ snapshots: '@babel/helper-string-parser@7.23.4': {} + '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-validator-identifier@7.22.20': {} + '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-option@7.23.5': {} '@babel/helpers@7.23.9': @@ -7769,6 +7875,10 @@ snapshots: dependencies: '@babel/types': 7.23.9 + '@babel/parser@7.26.3': + dependencies: + '@babel/types': 7.26.3 + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 @@ -7960,6 +8070,13 @@ snapshots: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 + '@babel/types@7.26.3': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@bcoe/v8-coverage@0.2.3': {} + '@colors/colors@1.5.0': optional: true @@ -9501,9 +9618,7 @@ snapshots: dependencies: minipass: 7.1.2 - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 + '@istanbuljs/schema@0.1.3': {} '@jridgewell/gen-mapping@0.3.3': dependencies: @@ -9511,17 +9626,32 @@ snapshots: '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.22 + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.1.2': {} + '@jridgewell/set-array@1.2.1': {} + '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/trace-mapping@0.3.22': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -10468,8 +10598,6 @@ snapshots: dependencies: '@types/hast': 3.0.4 - '@sinclair/typebox@0.27.8': {} - '@sindresorhus/is@4.6.0': {} '@sinonjs/commons@2.0.0': @@ -11213,34 +11341,63 @@ snapshots: transitivePeerDependencies: - graphql - '@vitest/expect@1.1.3': + '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@20.10.6))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.8.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.8(@types/node@20.10.6) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.8': dependencies: - '@vitest/spy': 1.1.3 - '@vitest/utils': 1.1.3 - chai: 4.4.0 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + tinyrainbow: 1.2.0 - '@vitest/runner@1.1.3': + '@vitest/mocker@2.1.8(vite@5.0.11(@types/node@20.10.6))': dependencies: - '@vitest/utils': 1.1.3 - p-limit: 5.0.0 - pathe: 1.1.1 + '@vitest/spy': 2.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 5.0.11(@types/node@20.10.6) - '@vitest/snapshot@1.1.3': + '@vitest/pretty-format@2.1.8': dependencies: - magic-string: 0.30.5 - pathe: 1.1.1 - pretty-format: 29.7.0 + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.8': + dependencies: + '@vitest/utils': 2.1.8 + pathe: 1.1.2 - '@vitest/spy@1.1.3': + '@vitest/snapshot@2.1.8': dependencies: - tinyspy: 2.2.0 + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.17 + pathe: 1.1.2 - '@vitest/utils@1.1.3': + '@vitest/spy@2.1.8': dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + tinyspy: 3.0.2 + + '@vitest/utils@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 + tinyrainbow: 1.2.0 '@volar/language-core@2.4.0-alpha.18': dependencies: @@ -11569,8 +11726,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} any-signal@3.0.1: {} @@ -11651,8 +11806,6 @@ snapshots: pvutils: 1.1.3 tslib: 2.6.2 - assertion-error@1.1.0: {} - assertion-error@2.0.1: {} astral-regex@2.0.0: {} @@ -12003,16 +12156,6 @@ snapshots: dependencies: chai: 5.0.0 - chai@4.4.0: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.3 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 - chai@5.0.0: dependencies: assertion-error: 2.0.1 @@ -12021,6 +12164,14 @@ snapshots: loupe: 3.0.2 pathval: 2.0.0 + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.1 + loupe: 3.1.2 + pathval: 2.0.0 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -12066,12 +12217,10 @@ snapshots: chardet@0.7.0: {} - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 - check-error@2.0.0: {} + check-error@2.1.1: {} + chokidar@3.5.3: dependencies: anymatch: 3.1.3 @@ -12446,6 +12595,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.4.0: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decamelize@4.0.0: {} @@ -12460,10 +12613,6 @@ snapshots: dedent@1.5.3: {} - deep-eql@4.1.3: - dependencies: - type-detect: 4.0.8 - deep-eql@5.0.1: {} deep-is@0.1.4: {} @@ -12512,8 +12661,6 @@ snapshots: detect-libc@2.0.3: {} - diff-sequences@29.6.3: {} - diff@4.0.2: {} diff@5.0.0: {} @@ -12663,6 +12810,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.6.0: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -12964,6 +13113,8 @@ snapshots: exit-hook@4.0.0: {} + expect-type@1.1.0: {} + express@4.19.2: dependencies: accepts: 1.3.8 @@ -13315,6 +13466,15 @@ snapshots: minipass: 7.0.4 path-scurry: 1.10.1 + glob@10.4.5: + dependencies: + foreground-child: 3.1.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@7.2.0: dependencies: fs.realpath: 1.0.0 @@ -13628,6 +13788,8 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + html-escaper@2.0.2: {} + http-cache-semantics@4.1.1: {} http-errors@2.0.0: @@ -13976,6 +14138,27 @@ snapshots: dependencies: ws: 8.18.0 + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + it-all@1.0.6: {} it-glob@1.0.2: @@ -14000,6 +14183,12 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + javascript-stringify@2.1.0: {} jiti@1.21.0: {} @@ -14154,11 +14343,6 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.0 - local-pkg@0.5.0: - dependencies: - mlly: 1.4.2 - pkg-types: 1.0.3 - localforage@1.10.0: dependencies: lie: 3.1.1 @@ -14238,14 +14422,12 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 - loupe@3.0.2: dependencies: get-func-name: 2.0.2 + loupe@3.1.2: {} + lower-case-first@2.0.2: dependencies: tslib: 2.6.2 @@ -14277,10 +14459,24 @@ snapshots: lunr@2.3.9: {} + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.5: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + source-map-js: 1.2.0 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + make-error@1.3.6: {} map-cache@0.2.2: {} @@ -14806,10 +15002,6 @@ snapshots: dependencies: yocto-queue: 1.0.0 - p-limit@5.0.0: - dependencies: - yocto-queue: 1.0.0 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -14833,6 +15025,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + pako@0.2.9: {} pako@1.0.11: {} @@ -14898,6 +15092,11 @@ snapshots: lru-cache: 10.2.0 minipass: 7.1.2 + path-scurry@1.11.1: + dependencies: + lru-cache: 10.2.0 + minipass: 7.1.2 + path-to-regexp@0.1.7: {} path-to-regexp@1.8.0: @@ -14910,8 +15109,6 @@ snapshots: pathe@1.1.2: {} - pathval@1.1.1: {} - pathval@2.0.0: {} pbkdf2@3.1.2: @@ -15022,8 +15219,8 @@ snapshots: postcss@8.4.33: dependencies: nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.0.2 + picocolors: 1.1.1 + source-map-js: 1.2.0 postgres-array@2.0.0: {} @@ -15058,12 +15255,6 @@ snapshots: prettier@3.3.2: {} - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.2.0 - process@0.11.10: {} promise@7.3.1: @@ -15179,8 +15370,6 @@ snapshots: defu: 6.1.4 destr: 2.0.3 - react-is@18.2.0: {} - react-native-fetch-api@3.0.0: dependencies: p-defer: 3.0.0 @@ -15583,8 +15772,6 @@ snapshots: dependencies: is-plain-obj: 1.1.0 - source-map-js@1.0.2: {} - source-map-js@1.2.0: {} source-map-support@0.5.21: @@ -15618,7 +15805,7 @@ snapshots: statuses@2.0.1: {} - std-env@3.7.0: {} + std-env@3.8.0: {} stdin-discarder@0.2.2: {} @@ -15695,10 +15882,6 @@ snapshots: strip-json-comments@3.1.1: {} - strip-literal@1.3.0: - dependencies: - acorn: 8.11.3 - strip-outer@2.0.0: {} strtok3@7.0.0: @@ -15836,6 +16019,12 @@ snapshots: ansi-escapes: 5.0.0 supports-hyperlinks: 2.3.0 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-extensions@2.4.0: {} text-table@0.2.0: {} @@ -15848,16 +16037,20 @@ snapshots: tiny-inflate@1.0.3: {} - tinybench@2.5.1: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} tinyglobby@0.2.10: dependencies: fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 - tinypool@0.8.1: {} + tinypool@1.0.2: {} + + tinyrainbow@1.2.0: {} - tinyspy@2.2.0: {} + tinyspy@3.0.2: {} title-case@3.0.3: dependencies: @@ -16258,12 +16451,12 @@ snapshots: - utf-8-validate - zod - vite-node@1.1.3(@types/node@20.10.6): + vite-node@2.1.8(@types/node@20.10.6): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) - pathe: 1.1.1 - picocolors: 1.0.0 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 1.1.2 vite: 5.0.11(@types/node@20.10.6) transitivePeerDependencies: - '@types/node' @@ -16295,40 +16488,40 @@ snapshots: '@types/node': 20.10.6 fsevents: 2.3.3 - vitest-mock-extended@2.0.2(typescript@5.5.3)(vitest@1.1.3(@types/node@20.10.6)): + vitest-mock-extended@2.0.2(typescript@5.5.3)(vitest@2.1.8(@types/node@20.10.6)): dependencies: ts-essentials: 10.0.4(typescript@5.5.3) typescript: 5.5.3 - vitest: 1.1.3(@types/node@20.10.6) - - vitest@1.1.3(@types/node@20.10.6): - dependencies: - '@vitest/expect': 1.1.3 - '@vitest/runner': 1.1.3 - '@vitest/snapshot': 1.1.3 - '@vitest/spy': 1.1.3 - '@vitest/utils': 1.1.3 - acorn-walk: 8.3.1 - cac: 6.7.14 - chai: 4.4.0 - debug: 4.3.4(supports-color@8.1.1) - execa: 8.0.1 - local-pkg: 0.5.0 - magic-string: 0.30.5 - pathe: 1.1.1 - picocolors: 1.0.0 - std-env: 3.7.0 - strip-literal: 1.3.0 - tinybench: 2.5.1 - tinypool: 0.8.1 + vitest: 2.1.8(@types/node@20.10.6) + + vitest@2.1.8(@types/node@20.10.6): + dependencies: + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.0.11(@types/node@20.10.6)) + '@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.4.0 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 vite: 5.0.11(@types/node@20.10.6) - vite-node: 1.1.3(@types/node@20.10.6) - why-is-node-running: 2.2.2 + vite-node: 2.1.8(@types/node@20.10.6) + why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.10.6 transitivePeerDependencies: - less - lightningcss + - msw - sass - stylus - sugarss @@ -16400,7 +16593,7 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@2.2.2: + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 diff --git a/vitest.config.ts b/vitest.config.ts index 46147ebe..68171d6f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,10 +1,32 @@ import { resolve } from "node:path"; -import { defineConfig } from "vitest/config"; +import { configDefaults, defineConfig } from "vitest/config"; export default defineConfig({ test: { setupFiles: ["./test/setup-env.ts"], - exclude: ["./lib", "node_modules"], + exclude: [...configDefaults.exclude, "./lib/**/*"], + coverage: { + // you can include other reporters, but 'json-summary' is required, json is recommended + reporter: ["text", "json-summary", "json"], + // If you want a coverage reports even if your tests are failing, include the reportOnFailure option + reportOnFailure: true, + thresholds: { + lines: 15, + branches: 54, + functions: 49, + statements: 15, + }, + include: ["src/**/*.ts"], + exclude: [ + ...(configDefaults.coverage.exclude as string[]), + "**/*.types.ts", + "**/types.ts", + "src/__generated__/**/*", + "src/graphql/**/*", + "src/types/**/*", + "./lib/**/*", + ], + }, }, resolve: { alias: [{ find: "@", replacement: resolve(__dirname, "./src") }], From 579779394a399d91a9abc6ae560219bfcd93fa9e Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 2 Jan 2025 22:49:59 +0100 Subject: [PATCH 11/11] fix(gha): alchemy api key in gh ci test env updates gha test script to use env var --- .github/workflows/ci-test-unit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-test-unit.yml b/.github/workflows/ci-test-unit.yml index 1e37afc2..de65211f 100644 --- a/.github/workflows/ci-test-unit.yml +++ b/.github/workflows/ci-test-unit.yml @@ -16,7 +16,7 @@ jobs: KEY: "test" PROOF: "test" - ALCHEMY_API_KEY: "test" + ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} DRPC_API_KEY: "test" INFURA_API_KEY: "test"