|
1 | | -import { describe, it, afterEach, afterAll } from "vitest"; |
| 1 | +import { describe, test, vi } from "vitest"; |
2 | 2 | import { expect } from "chai"; |
3 | | -import { createMocks, RequestMethod } from "node-mocks-http"; |
4 | | -import { Request, Response } from "express"; |
5 | | - |
6 | | -import sinon from "sinon"; |
| 3 | +import { |
| 4 | + incorrectMerkleTree, |
| 5 | + mockMerkleTree, |
| 6 | +} from "../../test-utils/mockMerkleTree.js"; |
| 7 | +import { mock } from "vitest-mock-extended"; |
| 8 | +import { AllowListController } from "../../../src/controllers/AllowListController.js"; |
| 9 | +import { StorageService } from "../../../src/services/StorageService.js"; |
| 10 | + |
| 11 | +const mocks = vi.hoisted(() => { |
| 12 | + return { |
| 13 | + init: vi.fn(), |
| 14 | + }; |
| 15 | +}); |
7 | 16 |
|
8 | | -import { data } from "../../test-utils"; |
9 | | -import { Client } from "@web3-storage/w3up-client"; |
10 | | -import { allowlistHandler } from "@/handlers/v1/web3up/allowlist"; |
11 | | -import { Link } from "@web3-storage/access"; |
| 17 | +vi.mock("../../../src/services/StorageService", async () => { |
| 18 | + return { |
| 19 | + StorageService: { init: mocks.init }, |
| 20 | + }; |
| 21 | +}); |
12 | 22 |
|
13 | | -describe("W3Up Client allowlist", async () => { |
14 | | - const { metadata, merkleTree, someData } = data; |
| 23 | +describe("Allow list upload at v1/allowlists", async () => { |
| 24 | + const controller = new AllowListController(); |
| 25 | + const mockStorage = mock<StorageService>(); |
15 | 26 |
|
16 | | - const storeBlobMock = sinon |
17 | | - .stub(Client.prototype, "uploadFile") |
18 | | - .resolves({ "/": merkleTree.cid } as unknown as Link); //TODO better Link object creation |
| 27 | + test("Stores a new allowlist and returns CID", async () => { |
| 28 | + mocks.init.mockResolvedValue(mockStorage); |
19 | 29 |
|
20 | | - const mockRequestResponse = (method: RequestMethod = "POST") => { |
21 | | - const { req, res }: { req: Request; res: Response } = createMocks({ |
22 | | - method, |
23 | | - }); |
24 | | - req.headers = { |
25 | | - "Content-Type": "application/json", |
| 30 | + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); |
| 31 | + const requestBody = { |
| 32 | + allowList: mockMerkleTree, |
| 33 | + totalUnits: "100000000", |
26 | 34 | }; |
27 | | - req.body = { allowList: merkleTree.data, totalUnits: 100n }; |
28 | | - return { req, res }; |
29 | | - }; |
30 | | - |
31 | | - afterEach(() => { |
32 | | - sinon.resetHistory(); |
33 | | - }); |
| 35 | + const response = await controller.storeAllowList(requestBody); |
34 | 36 |
|
35 | | - afterAll(() => { |
36 | | - sinon.resetBehavior(); |
| 37 | + expect(response.success).to.be.true; |
| 38 | + expect(response.data).to.not.be.undefined; |
| 39 | + expect(response.data?.cid).to.eq("TEST_CID"); |
37 | 40 | }); |
38 | 41 |
|
39 | | - it("POST valid allowList - 200", async () => { |
40 | | - const { req, res } = mockRequestResponse(); |
41 | | - await allowlistHandler(req, res); |
| 42 | + test("Returns errors and message when allowlist is invalid", async () => { |
| 43 | + mocks.init.mockResolvedValue(mockStorage); |
42 | 44 |
|
43 | | - expect(res.statusCode).to.eq(200); |
44 | | - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); |
45 | | - expect(res.statusMessage).to.eq("OK"); |
46 | | - |
47 | | - //TODO better typing and check on returned CID |
48 | | - // @ts-ignore |
49 | | - expect(res._getJSONData().message).to.eq("Data uploaded succesfully"); |
50 | | - // @ts-ignore |
51 | | - expect(res._getJSONData().cid).to.not.be.undefined; |
| 45 | + mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" }); |
| 46 | + const requestBody = { |
| 47 | + allowList: incorrectMerkleTree, |
| 48 | + totalUnits: "100000000", |
| 49 | + }; |
| 50 | + const response = await controller.storeAllowList(requestBody); |
52 | 51 |
|
53 | | - expect(storeBlobMock.callCount).to.eq(1); |
| 52 | + expect(response.success).to.be.false; |
| 53 | + expect(response.data).to.be.undefined; |
| 54 | + expect(response.message).to.eq("Errors while validating allow list"); |
| 55 | + expect(response.errors).to.deep.eq({ |
| 56 | + allowListData: "Data could not be parsed to OpenZeppelin MerkleTree", |
| 57 | + }); |
54 | 58 | }); |
55 | 59 |
|
56 | | - it("GET allowlist not allowed - 405", async () => { |
57 | | - const { req, res } = mockRequestResponse(); |
58 | | - req.method = "GET"; |
59 | | - await allowlistHandler(req, res); |
| 60 | + test("Handles errors during upload", async () => { |
| 61 | + mocks.init.mockResolvedValue(mockStorage); |
60 | 62 |
|
61 | | - expect(res.statusCode).to.eq(405); |
62 | | - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); |
63 | | - expect(res.statusMessage).to.eq("OK"); |
| 63 | + const mockError = new Error("Error uploading data"); |
| 64 | + mockStorage.uploadFile.mockRejectedValue(mockError); |
64 | 65 |
|
65 | | - //TODO better typing and check on returned CID |
66 | | - // @ts-ignore |
67 | | - expect(res._getJSONData().message).to.eq("Not allowed"); |
| 66 | + const requestBody = { |
| 67 | + allowList: mockMerkleTree, |
| 68 | + totalUnits: "100000000", |
| 69 | + }; |
| 70 | + const response = await controller.storeAllowList(requestBody); |
68 | 71 |
|
69 | | - expect(storeBlobMock.callCount).to.eq(0); |
| 72 | + expect(response.success).to.be.false; |
| 73 | + expect(response.data).to.be.undefined; |
| 74 | + expect(response.errors).to.deep.eq({ |
| 75 | + allowList: "Error uploading data", |
| 76 | + }); |
70 | 77 | }); |
| 78 | +}); |
71 | 79 |
|
72 | | - it("POST incorrect allowlist - 400", async () => { |
73 | | - const { req, res } = mockRequestResponse(); |
74 | | - req.body = { allowList: data.someData.data, totalUnits: 100n }; |
75 | | - await allowlistHandler(req, res); |
76 | | - |
77 | | - expect(res.statusCode).to.eq(400); |
78 | | - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); |
79 | | - expect(res.statusMessage).to.eq("OK"); |
| 80 | +describe("Allow list validation at v1/allowlists/validate", async () => { |
| 81 | + const controller = new AllowListController(); |
80 | 82 |
|
81 | | - //TODO better typing and check on returned CID |
82 | | - // @ts-ignore |
83 | | - expect(res._getJSONData().message).to.eq("Not a valid merkle tree object"); |
| 83 | + test("Validates correctness of allowlist and returns results", async () => { |
| 84 | + const requestBody = { |
| 85 | + allowList: mockMerkleTree, |
| 86 | + totalUnits: "100", |
| 87 | + }; |
| 88 | + const response = await controller.validateAllowList(requestBody); |
| 89 | + expect(response.valid).to.be.true; |
| 90 | + expect(response.success).to.be.true; |
| 91 | + expect(response.message).to.be.eq( |
| 92 | + "Allowlist is a valid hypercerts allowlist object.", |
| 93 | + ); |
84 | 94 | }); |
85 | 95 |
|
86 | | - it("POST upload allowlist fails - 500", async () => { |
87 | | - const { req, res } = mockRequestResponse(); |
88 | | - storeBlobMock.rejects(); |
89 | | - await allowlistHandler(req, res); |
90 | | - |
91 | | - expect(res.statusCode).to.eq(500); |
92 | | - expect(res.getHeaders()).to.deep.eq({ "content-type": "application/json" }); |
93 | | - expect(res.statusMessage).to.eq("OK"); |
| 96 | + test("Returns errors and message when allowlist is invalid", async () => { |
| 97 | + const requestBody = { |
| 98 | + allowList: incorrectMerkleTree, |
| 99 | + totalUnits: "100", |
| 100 | + }; |
| 101 | + const response = await controller.validateAllowList(requestBody); |
94 | 102 |
|
95 | | - //TODO better typing and check on returned CID |
96 | | - // @ts-ignore |
97 | | - expect(res._getJSONData().message).to.eq("Error uploading data"); |
| 103 | + expect(response.success).to.be.true; |
| 104 | + expect(response.valid).to.be.false; |
| 105 | + expect(response.message).to.eq("Errors while validating allow list"); |
| 106 | + expect(response.errors).to.deep.eq({ |
| 107 | + allowListData: "Data could not be parsed to OpenZeppelin MerkleTree", |
| 108 | + }); |
| 109 | + }); |
98 | 110 |
|
99 | | - expect(storeBlobMock.callCount).to.eq(1); |
| 111 | + test("Returns errors and message when total units doesn't match allow list", async () => { |
| 112 | + const requestBody = { |
| 113 | + allowList: mockMerkleTree, |
| 114 | + totalUnits: "99", |
| 115 | + }; |
| 116 | + const response = await controller.validateAllowList(requestBody); |
| 117 | + |
| 118 | + expect(response.success).to.be.true; |
| 119 | + expect(response.valid).to.be.false; |
| 120 | + expect(response.message).to.eq("Errors while validating allow list"); |
| 121 | + expect(response.errors).to.deep.eq({ |
| 122 | + units: |
| 123 | + "Total units in allowlist must match total units [expected: 99, got: 100]", |
| 124 | + }); |
100 | 125 | }); |
101 | 126 | }); |
0 commit comments