Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sinamics committed Sep 1, 2024
1 parent 12f9aff commit 8acd8e4
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 40 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"format": "biome format src",
"format:fix": "biome format src --write",
"start": "node .next/standalone/server.js",
"test:dev": "jest --config jest.pages.config.ts && jest --config jest.api.config.ts",
"test": "jest --config jest.pages.config.ts && jest --config jest.api.config.ts --ci --coverage",
"test:dev": "jest --detectOpenHandles --verbose --config jest.pages.config.ts && jest --config jest.api.config.ts",
"test": "jest --config jest.pages.config.ts && jest --verbose --config jest.api.config.ts --ci --coverage",
"studio": "prisma studio"
},
"dependencies": {
Expand Down Expand Up @@ -109,4 +109,4 @@
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
}
}
}
15 changes: 13 additions & 2 deletions src/pages/api/__tests__/v1/application/statistic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ import { NextApiRequest, NextApiResponse } from "next";
describe("/api/stats", () => {
it("should allow only GET method", async () => {
const methods = ["DELETE", "POST", "PUT", "PATCH", "OPTIONS", "HEAD"];
const req = {} as NextApiRequest;
const req = {
method: "GET",
headers: {
"x-ztnet-auth": "validApiKey",
},
query: {},
body: {},
} 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
setHeader: jest.fn(),
} as unknown as NextApiResponse;

for (const method of methods) {
Expand All @@ -29,7 +37,10 @@ describe("/api/stats", () => {
const req = {
method: "GET",
headers: { "x-ztnet-auth": "invalidApiKey" },
query: {},
body: {},
} as unknown as NextApiRequest;

const res = {
status: jest.fn().mockReturnThis(),
end: jest.fn(),
Expand Down
8 changes: 5 additions & 3 deletions src/pages/api/__tests__/v1/network/network.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { NextApiRequest, NextApiResponse } from "next";

describe("/api/createNetwork", () => {
it("should respond 405 to unsupported methods", async () => {
const req = { method: "PUT" } as NextApiRequest;
const req = { method: "PUT", query: {} } as NextApiRequest;
const res = {
status: jest.fn().mockReturnThis(),
end: jest.fn(),
Expand All @@ -20,12 +20,13 @@ describe("/api/createNetwork", () => {
const req = {
method: "POST",
headers: { "x-ztnet-auth": "invalidApiKey" },
query: {},
} 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
setHeader: jest.fn(),
} as unknown as NextApiResponse;

await apiNetworkHandler(req, res);
Expand All @@ -37,12 +38,13 @@ describe("/api/createNetwork", () => {
const req = {
method: "GET",
headers: { "x-ztnet-auth": "invalidApiKey" },
query: {},
} 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
setHeader: jest.fn(),
} as unknown as NextApiResponse;

await apiNetworkHandler(req, res);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe("Update Network Members", () => {
method: "POST",
headers: { "x-ztnet-auth": "validApiKey" },
query: { id: "networkId", memberId: "memberId" },
body: { name: "New Name", authorized: "true" },
body: { name: "New Name", authorized: true },
} as unknown as NextApiRequest;

// Mock the database to return a network
Expand Down Expand Up @@ -114,7 +114,7 @@ describe("Update Network Members", () => {
method: "POST",
headers: { "x-ztnet-auth": "validApiKey" },
query: { id: "networkId", memberId: "memberId" },
body: { name: "New Name", authorized: "true" },
body: { name: "New Name", authorized: true },
} as unknown as NextApiRequest;

const res = createMockRes();
Expand Down Expand Up @@ -160,7 +160,7 @@ describe("Update Network Members", () => {
method: "POST",
headers: { "x-ztnet-auth": "invalidApiKey" },
query: { id: "networkId", memberId: "memberId" },
body: { name: "New Name", authorized: "true" },
body: { name: "New Name", authorized: true },
} as unknown as NextApiRequest;

const res = createMockRes();
Expand Down
1 change: 1 addition & 0 deletions src/pages/api/__tests__/v1/org/org.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ describe("organization api validation", () => {
.mockResolvedValue({ id: "newUserId", name: "Ztnet", email: "post@ztnet.network" });

mockRequest.headers["x-ztnet-auth"] = "not valid token";
mockRequest.query = {};

await GET_userOrganization(
mockRequest as NextApiRequest,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/api/__tests__/v1/org/orgid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ describe("organization api validation", () => {
const validToken = encrypt(validTokenData, generateInstanceSecret(API_TOKEN_SECRET));
mockRequest.headers["x-ztnet-auth"] = validToken;

// add organizationId to the request
mockRequest.query = undefined;
// add empty query
mockRequest.query = {};
await apiNetworkHandler(
mockRequest as NextApiRequest,
mockResponse as NextApiResponse,
Expand Down
26 changes: 22 additions & 4 deletions src/pages/api/__tests__/v1/user/user.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextApiRequest, NextApiResponse } from "next";
import createUserHandler, { POST_createUser } from "~/pages/api/v1/user";
import createUserHandler from "~/pages/api/v1/user";
import { prisma } from "~/server/db";
import { appRouter } from "~/server/api/root";
import { API_TOKEN_SECRET, encrypt, generateInstanceSecret } from "~/utils/encryption";
Expand All @@ -18,7 +18,12 @@ jest.mock("~/server/api/root", () => ({
})),
},
}));

jest.mock("~/utils/rateLimit", () => ({
__esModule: true,
default: () => ({
check: jest.fn().mockResolvedValue(true),
}),
}));
jest.mock("~/server/api/trpc");

jest.mock("~/server/db", () => ({
Expand Down Expand Up @@ -126,9 +131,19 @@ describe("createUserHandler", () => {
}),
}));

mockRequest.method = "POST";
mockRequest.headers["x-ztnet-auth"] = "not defined";
mockRequest.body = {
email: "ztnet@example.com",
password: "password123",
name: "Ztnet",
};

await createUserHandler(
mockRequest as NextApiRequest,
mockResponse as NextApiResponse,
);

await POST_createUser(mockRequest as NextApiRequest, mockResponse as NextApiResponse);
expect(mockResponse.status).toHaveBeenCalledWith(200);

// Check if the response is as expected
Expand Down Expand Up @@ -166,6 +181,7 @@ describe("createUserHandler", () => {
method: "POST",
headers: { "x-ztnet-auth": tokenWithIdHash },
body: { email: "test@example.com", password: "password123", name: "Test User" },
query: {},
} as unknown as NextApiRequest;

const res = {
Expand Down Expand Up @@ -208,7 +224,9 @@ describe("createUserHandler", () => {

it("should allow only POST method", async () => {
const methods = ["GET", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
const req = {} as NextApiRequest;
const req = {
query: {},
} as NextApiRequest;
const res = createMockRes();

for (const method of methods) {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/v1/user/_schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { z } from "zod";
import { passwordSchema } from "~/server/api/routers/authRouter";
import { passwordSchema } from "~/server/api/routers/_schema";

// Input validation schema
export const createUserSchema = z.object({
Expand Down
2 changes: 0 additions & 2 deletions src/pages/api/v1/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ export const POST_createUser = async (req: NextApiRequest, res: NextApiResponse)

// Input validation
const validatedInput = createUserSchema.parse(req.body);

// get data from the post request
const { email, password, name, expiresAt, generateApiToken } = validatedInput;

Expand All @@ -81,7 +80,6 @@ export const POST_createUser = async (req: NextApiRequest, res: NextApiResponse)
return res.status(400).json({ message: "Invalid expiresAt date" });
}
}

/**
*
* Create a transaction to make sure the user and API token are created together
Expand Down
21 changes: 21 additions & 0 deletions src/server/api/routers/_schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from "zod";

// This regular expression (regex) is used to validate a password based on the following criteria:
// - The password must be at least 6 characters long.
// - The password must contain at least two of the following three character types:
// - Lowercase letters (a-z)
// - Uppercase letters (A-Z)
// - Digits (0-9)
export const mediumPassword = new RegExp(
"^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})",
);

// create a zod password schema
export const passwordSchema = (errorMessage: string) =>
z
.string()
.max(40, { message: "Password must not exceed 40 characters" })
.refine((val) => mediumPassword.test(val), {
message: errorMessage,
})
.optional();
21 changes: 1 addition & 20 deletions src/server/api/routers/authRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,7 @@ import { validateOrganizationToken } from "../services/organizationAuthService";
import rateLimit from "~/utils/rateLimit";
import { ErrorCode } from "~/utils/errorCode";
import { MailTemplateKey } from "~/utils/enums";

// This regular expression (regex) is used to validate a password based on the following criteria:
// - The password must be at least 6 characters long.
// - The password must contain at least two of the following three character types:
// - Lowercase letters (a-z)
// - Uppercase letters (A-Z)
// - Digits (0-9)
const mediumPassword = new RegExp(
"^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})",
);
import { mediumPassword, passwordSchema } from "./_schema";

// allow 15 requests per 10 minutes
const limiter = rateLimit({
Expand All @@ -44,16 +35,6 @@ const limiter = rateLimit({
const GENERAL_REQUEST_LIMIT = 60;
const SHORT_REQUEST_LIMIT = 5;

// create a zod password schema
export const passwordSchema = (errorMessage: string) =>
z
.string()
.max(40, { message: "Password must not exceed 40 characters" })
.refine((val) => mediumPassword.test(val), {
message: errorMessage,
})
.optional();

export const authRouter = createTRPCRouter({
register: publicProcedure
.input(
Expand Down

0 comments on commit 8acd8e4

Please sign in to comment.