Skip to content

Commit

Permalink
Merge pull request #232 from sinamics/api_tests
Browse files Browse the repository at this point in the history
Added more tests for the public api
  • Loading branch information
sinamics authored Dec 10, 2023
2 parents 7677ccf + d9ae206 commit 1e51d28
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 22 deletions.
39 changes: 21 additions & 18 deletions jest.api.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,44 @@ import type { JestConfigWithTsJest } from "ts-jest";
import nextJest from "next/jest.js";

const nextConfig = {
dir: "./", // Path to your Next.js app
dir: "./", // Path to your Next.js app
};

const baseConfig: JestConfigWithTsJest = {
clearMocks: true,
coverageProvider: "v8",
preset: "ts-jest/presets/js-with-ts",
transform: {
"^.+\\.mjs$": "ts-jest",
},
clearMocks: true,
coverageProvider: "v8",
preset: "ts-jest/presets/js-with-ts",
transform: {
"^.+\\.mjs$": "ts-jest",
},
};

const filesConfig = {
setupFiles: ["dotenv/config"],
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
setupFiles: ["dotenv/config"],
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
};

const moduleConfig = {
moduleNameMapper: {
"^~/(.*)$": "<rootDir>/src/$1",
},
moduleNameMapper: {
"^~/(.*)$": "<rootDir>/src/$1",
},
};

const testConfig = {
testMatch: ["**/server/api/__tests__/**/*.test.ts"],
testMatch: [
"**/server/api/__tests__/**/*.test.ts",
"**/pages/api/__tests__/**/*.test.ts",
],
};

const jestConfig: JestConfigWithTsJest = {
...baseConfig,
...filesConfig,
...moduleConfig,
...testConfig,
...baseConfig,
...filesConfig,
...moduleConfig,
...testConfig,
};

const createJestConfig = nextJest(nextConfig);

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export default createJestConfig(jestConfig as any);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
}
}
}
52 changes: 52 additions & 0 deletions src/pages/api/__tests__/v1/network/network.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import apiNetworkHandler from "~/pages/api/v1/network";
import { NextApiRequest, NextApiResponse } from "next";

describe("/api/createNetwork", () => {
it("should respond 405 to unsupported methods", async () => {
const req = { method: "PUT" } as NextApiRequest;
const res = {
status: jest.fn().mockReturnThis(),
end: jest.fn(),
json: jest.fn().mockReturnThis(),
setHeader: jest.fn(), // Mock `setHeader` rate limiter uses it
} as unknown as NextApiResponse;

await apiNetworkHandler(req, res);

expect(res.status).toHaveBeenCalledWith(405);
});

it("should respond 401 when invalid API key for POST", async () => {
const req = {
method: "POST",
headers: { "x-ztnet-auth": "invalidApiKey" },
} as unknown as NextApiRequest;
const res = {
status: jest.fn().mockReturnThis(),
end: jest.fn(),
json: jest.fn().mockReturnThis(),
setHeader: jest.fn(), // Mock `setHeader` rate limiter uses it
} as unknown as NextApiResponse;

await apiNetworkHandler(req, res);

expect(res.status).toHaveBeenCalledWith(401);
});

it("should respond 401 when invalid API key for GET", async () => {
const req = {
method: "GET",
headers: { "x-ztnet-auth": "invalidApiKey" },
} as unknown as NextApiRequest;
const res = {
status: jest.fn().mockReturnThis(),
end: jest.fn(),
json: jest.fn().mockReturnThis(),
setHeader: jest.fn(), // Mock `setHeader` rate limiter uses it
} as unknown as NextApiResponse;

await apiNetworkHandler(req, res);

expect(res.status).toHaveBeenCalledWith(401);
});
});
193 changes: 193 additions & 0 deletions src/pages/api/__tests__/v1/network/networkById.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { TRPCError } from "@trpc/server";
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
import { NextApiRequest, NextApiResponse } from "next";
import apiNetworkByIdHandler from "~/pages/api/v1/network/[id]";
import { prisma } from "~/server/db";
import * as encryptionModule from "~/utils/encryption";
import * as ztController from "~/utils/ztApi";
// import rateLimit from "~/utils/rateLimit";

// Mock the ztController module
jest.mock("~/utils/encryption", () => {
const originalModule = jest.requireActual("~/utils/encryption");
return {
...originalModule,
decryptAndVerifyToken: jest.fn(),
};
});

// Mock the ztController module
jest.mock("~/utils/ztApi", () => {
const originalModule = jest.requireActual("~/utils/ztApi");
return {
...originalModule,
local_network_detail: jest.fn(),
};
});

// Mock the rateLimit module
jest.mock("~/utils/rateLimit", () => ({
__esModule: true, // Ensure correct handling of ES module
default: jest.fn(() => ({
check: jest.fn().mockResolvedValue(null), // Mock implementation of the check method
})),
}));

// Add these tests to your describe block
it("should respond 200 when network is found", async () => {
// Mock the decryption to return a valid user ID
(encryptionModule.decryptAndVerifyToken as jest.Mock).mockResolvedValue({
userId: "userId",
});

// Mock the database to return a network
prisma.network.findUnique = jest.fn().mockResolvedValue({
nwid: "test_nw_id",
nwname: "credent_second",
authorId: 1,
});

// Mock the ztController to return a network detail
(ztController.local_network_detail as jest.Mock).mockResolvedValue({
network: { id: "networkId", name: "networkName" },
});

const req = {
method: "GET",
headers: { "x-ztnet-auth": "validApiKey" },
query: { id: "networkId" },
} as unknown as NextApiRequest;

const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
end: jest.fn(),
setHeader: jest.fn(), // Mock `setHeader` rate limiter uses it
} as unknown as NextApiResponse;

await apiNetworkByIdHandler(req, res);

expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({ id: "networkId", name: "networkName" });
});

it("should respond 401 when network is not found", async () => {
// Mock the decryption to return a valid user ID
(encryptionModule.decryptAndVerifyToken as jest.Mock).mockResolvedValue({
userId: "userId",
});

// Mock the database to return a network
prisma.network.findUnique = jest.fn().mockResolvedValue(null);

const req = {
method: "GET",
headers: { "x-ztnet-auth": "validApiKey" },
query: { id: "networkId" },
} as unknown as NextApiRequest;

const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
end: jest.fn(),
setHeader: jest.fn(), // Mock `setHeader` rate limiter uses it
} as unknown as NextApiResponse;

await apiNetworkByIdHandler(req, res);

expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({ error: "Network not found or access denied." });
});

it("should respond with an error when ztController throws an error", async () => {
// Mock the decryption to return a valid user ID
(encryptionModule.decryptAndVerifyToken as jest.Mock).mockResolvedValue({
userId: "ztnetUserId",
});

// Mock the database to return a network
prisma.network.findUnique = jest.fn().mockResolvedValue({
nwid: "networkId",
name: "networkName",
authorId: 1,
});

// Mock the ztController to throw an error
const error = new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Internal server error",
});

(ztController.local_network_detail as jest.Mock).mockRejectedValue(error);

const req = {
method: "GET",
headers: { "x-ztnet-auth": "validApiKey" },
query: { id: "networkId" },
} as unknown as NextApiRequest;

const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
end: jest.fn(),
setHeader: jest.fn(), // Mock `setHeader` rate limiter uses it
} as unknown as NextApiResponse;

await apiNetworkByIdHandler(req, res);

const httpCode = getHTTPStatusCodeFromError(error);
expect(res.status).toHaveBeenCalledWith(httpCode);
expect(res.json).toHaveBeenCalledWith({ error: error.message });
});

it("should respond 401 when decryptAndVerifyToken fails", async () => {
// Mock the decryption to fail
(encryptionModule.decryptAndVerifyToken as jest.Mock).mockRejectedValue(
new Error("Invalid token"),
);

const req = {
method: "GET",
headers: { "x-ztnet-auth": "invalidApiKey" },
query: { id: "networkId" },
} as unknown as NextApiRequest;

const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
end: jest.fn(),
setHeader: jest.fn(), // Mock `setHeader` if rate limiter uses it
} as unknown as NextApiResponse;

await apiNetworkByIdHandler(req, res);

expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith(
expect.objectContaining({ error: expect.any(String) }),
);
});

// it("should respond with 429 when rate limit is exceeded", async () => {
// // Apply the mock directly to the rateLimit().check for this specific test
// rateLimit().check = jest.fn().mockImplementation(() => {
// throw new Error();
// });

// const req = {
// method: "GET",
// headers: { "x-ztnet-auth": "validApiKey" },
// query: { id: "networkId" },
// } as unknown as NextApiRequest;

// const res = {
// status: jest.fn().mockReturnThis(),
// json: jest.fn(),
// end: jest.fn(),
// setHeader: jest.fn(),
// } as unknown as NextApiResponse;

// await apiNetworkByIdHandler(req, res);

// expect(res.status).toHaveBeenCalledWith(429);
// expect(res.json).toHaveBeenCalledWith({ error: "Rate limit exceeded" });
// });
2 changes: 1 addition & 1 deletion src/pages/api/v1/network/[id]/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const limiter = rateLimit({

const REQUEST_PR_MINUTE = 50;

export default async function createNetworkHandler(
export default async function apiNetworkByIdHandler(
req: NextApiRequest,
res: NextApiResponse,
) {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/v1/network/[id]/member/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const limiter = rateLimit({

const REQUEST_PR_MINUTE = 50;

export default async function createNetworkHandler(
export default async function apiNetworkMembersHandler(
req: NextApiRequest,
res: NextApiResponse,
) {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/v1/network/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const limiter = rateLimit({

const REQUEST_PR_MINUTE = 50;

export default async function createNetworkHandler(
export default async function apiNetworkHandler(
req: NextApiRequest,
res: NextApiResponse,
) {
Expand Down

0 comments on commit 1e51d28

Please sign in to comment.