chore: add testing infra to apps/web (#4563)

Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
Co-authored-by: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com>
This commit is contained in:
Matti Nannt
2025-01-09 14:00:16 +01:00
committed by GitHub
parent d197c91995
commit 5970ff917f
5 changed files with 118 additions and 26 deletions

View File

@@ -0,0 +1,88 @@
import { Authenticator } from "@otplib/core";
import type { AuthenticatorOptions } from "@otplib/core/authenticator";
import { createDigest, createRandomBytes } from "@otplib/plugin-crypto";
import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two";
import { describe, expect, it, vi } from "vitest";
import { totpAuthenticatorCheck } from "./totp";
vi.mock("@otplib/core");
vi.mock("@otplib/plugin-crypto");
vi.mock("@otplib/plugin-thirty-two");
describe("totpAuthenticatorCheck", () => {
const token = "123456";
const secret = "JBSWY3DPEHPK3PXP";
const opts: Partial<AuthenticatorOptions> = { window: [1, 0] };
it("should check a TOTP token with a base32-encoded secret", () => {
const checkMock = vi.fn().mockReturnValue(true);
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: checkMock,
}));
const result = totpAuthenticatorCheck(token, secret, opts);
expect(Authenticator).toHaveBeenCalledWith({
createDigest,
createRandomBytes,
keyDecoder,
keyEncoder,
window: [1, 0],
});
expect(checkMock).toHaveBeenCalledWith(token, secret);
expect(result).toBe(true);
});
it("should use default window if none is provided", () => {
const checkMock = vi.fn().mockReturnValue(true);
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: checkMock,
}));
const result = totpAuthenticatorCheck(token, secret);
expect(Authenticator).toHaveBeenCalledWith({
createDigest,
createRandomBytes,
keyDecoder,
keyEncoder,
window: [1, 0],
});
expect(checkMock).toHaveBeenCalledWith(token, secret);
expect(result).toBe(true);
});
it("should throw an error for invalid token format", () => {
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: () => {
throw new Error("Invalid token format");
},
}));
expect(() => {
totpAuthenticatorCheck("invalidToken", secret);
}).toThrow("Invalid token format");
});
it("should throw an error for invalid secret format", () => {
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: () => {
throw new Error("Invalid secret format");
},
}));
expect(() => {
totpAuthenticatorCheck(token, "invalidSecret");
}).toThrow("Invalid secret format");
});
it("should return false if token verification fails", () => {
const checkMock = vi.fn().mockReturnValue(false);
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: checkMock,
}));
const result = totpAuthenticatorCheck(token, secret);
expect(result).toBe(false);
});
});

View File

@@ -1,6 +1,5 @@
import { Authenticator, TOTP } from "@otplib/core";
import { Authenticator } from "@otplib/core";
import type { AuthenticatorOptions } from "@otplib/core/authenticator";
import type { TOTPOptions } from "@otplib/core/totp";
import { createDigest, createRandomBytes } from "@otplib/plugin-crypto";
import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two";
@@ -28,21 +27,3 @@ export const totpAuthenticatorCheck = (
});
return authenticator.check(token, secret);
};
/**
* Checks the validity of a TOTP token using a raw secret.
*
* @param token - The token.
* @param secret - The raw hex-encoded shared secret.
* @param opts - The TOTPOptions object.
* @param opts.window - The amount of past and future tokens considered valid. Either a single value or array of `[past, future]`. Default: `[1, 0]`
*/
export const totpRawCheck = (token: string, secret: string, opts: Partial<TOTPOptions> = {}) => {
const { window = [1, 0], ...rest } = opts;
const authenticator = new TOTP({
createDigest,
window,
...rest,
});
return authenticator.check(token, secret);
};

View File

@@ -9,7 +9,8 @@
"build": "next build",
"build:dev": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"test": "dotenv -e ../../.env -- vitest run"
},
"dependencies": {
"@ai-sdk/azure": "1.0.10",
@@ -91,6 +92,7 @@
"next-auth": "4.24.11",
"next-intl": "3.26.1",
"next-safe-action": "7.10.2",
"nodemailer": "6.9.16",
"optional": "0.1.4",
"otplib": "12.0.1",
"papaparse": "5.4.1",
@@ -127,6 +129,8 @@
"@types/nodemailer": "6.4.17",
"@types/papaparse": "5.3.15",
"@types/qrcode": "1.5.5",
"nodemailer": "6.9.16"
"vite": "6.0.3",
"vitest": "2.1.8",
"vitest-mock-extended": "2.0.2"
}
}

10
apps/web/vite.config.mts Normal file
View File

@@ -0,0 +1,10 @@
import tsconfigPaths from "vite-tsconfig-paths";
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
exclude: ["playwright/**", "node_modules/**"],
setupFiles: ["../../packages/lib/vitestSetup.ts"],
},
plugins: [tsconfigPaths()],
});

17
pnpm-lock.yaml generated
View File

@@ -583,6 +583,9 @@ importers:
next-safe-action:
specifier: 7.10.2
version: 7.10.2(next@15.1.0(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@3.24.1)
nodemailer:
specifier: 6.9.16
version: 6.9.16
optional:
specifier: 0.1.4
version: 0.1.4
@@ -686,9 +689,15 @@ importers:
'@types/qrcode':
specifier: 1.5.5
version: 1.5.5
nodemailer:
specifier: 6.9.16
version: 6.9.16
vite:
specifier: 6.0.3
version: 6.0.3(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.27.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
vitest:
specifier: 2.1.8
version: 2.1.8(@types/node@22.10.2)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.37.0)
vitest-mock-extended:
specifier: 2.0.2
version: 2.0.2(typescript@5.7.2)(vitest@2.1.8(@types/node@22.10.2)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.37.0))
packages/api:
devDependencies:
@@ -28503,7 +28512,7 @@ snapshots:
'@vitest/spy': 2.1.8
'@vitest/utils': 2.1.8
chai: 5.1.2
debug: 4.3.7
debug: 4.4.0
expect-type: 1.1.0
magic-string: 0.30.14
pathe: 1.1.2