Skip to content

Commit

Permalink
increasing code coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
prayanshchh committed Nov 10, 2024
1 parent 9737ca1 commit fa3a3ab
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 4 deletions.
39 changes: 39 additions & 0 deletions tests/resolvers/Mutation/login.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { createTestEventWithRegistrants } from "../../helpers/eventsWithRegistra
import type { TestUserType } from "../../helpers/userAndOrg";
import { decryptEmail, encryptEmail } from "../../../src/utilities/encryption";
import { hashEmail } from "../../../src/utilities/hashEmail";
import { ValidationError } from "../../../src/libraries/errors";

let testUser: TestUserType;
let MONGOOSE_INSTANCE: typeof mongoose;
Expand Down Expand Up @@ -86,6 +87,44 @@ describe("resolvers -> Mutation -> login", () => {
vi.resetModules();
});

it("throws ValidationError if email is not found in args.data", async () => {
const { requestContext } = await import("../../../src/libraries");

// Spy on the translate function to capture error messages
const spy = vi
.spyOn(requestContext, "translate")
.mockImplementationOnce((message) => `Translated ${message}`);

try {
const email = `nonexistentuser${nanoid().toLowerCase()}@gmail.com`;
const hashedEmail = hashEmail(email);
const newUser = await User.create({
email: encryptEmail(email),
hashedEmail: hashedEmail,
password: "password",
firstName: "John",
lastName: "Doe",
});

const args: MutationLoginArgs = {
data: {
email: null,
password: "password",
},
};

const { login: loginResolver } = await import(
"../../../src/resolvers/Mutation/login"
);

await loginResolver?.({}, args, {});
} catch (error) {
expect(error).instanceOf(ValidationError);
if (error instanceof ValidationError) {
expect(error.message).toBe("Email is required");
}
}
});
it("throws NotFoundError if the user is not found after creating AppUserProfile", async () => {
const { requestContext } = await import("../../../src/libraries");

Expand Down
25 changes: 24 additions & 1 deletion tests/resolvers/Mutation/signUp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,30 @@ describe("resolvers -> Mutation -> signUp", () => {
vi.restoreAllMocks();
});

it(`throws ConflictError message if a user already with email === args.data.email already exists`, async () => {
it("throws error if email has invalid format", async () => {
try {
const args: MutationSignUpArgs = {
data: {
email: "",
firstName: "firstName",
lastName: "lastName",
password: "password",
appLanguageCode: "en",
selectedOrganization: testOrganization?._id,
},
};

const { signUp: signUpResolver } = await import(
"../../../src/resolvers/Mutation/signUp"
);

await signUpResolver?.({}, args, {});
} catch (error: unknown) {
expect((error as Error).message).toEqual("Invalid email format");
}
});

it(`throws ConflictError message if a user already with email === args.data.email already exists`, async () => {
let email = "";
if (testUser?.email) {
email = decryptEmail(testUser.email).decrypted;
Expand Down
47 changes: 46 additions & 1 deletion tests/utilities/encryptionModule.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { describe, it, expect } from "vitest";
import { describe, it, expect, afterEach } from "vitest";
import {
decryptEmail,
encryptEmail,
generateRandomIV,
} from "../../src/utilities/encryption";

describe("encryptionModule", () => {
const validEncryptedData =
"11898325fe8807edeb99d37f0b168eaa:3991cd4d1a6372ed70492e23d499b066:4f209bb501460537fa9345ca16361023a19f9b2eff1860e8dadc80f29705d469cbe46edc4913e77d3418814b8eb7";
const originalKey = process.env.ENCRYPTION_KEY;

afterEach(() => {
process.env.ENCRYPTION_KEY = originalKey;
});
describe("generateRandomIV", () => {
it("should generate a random salt of the specified length", () => {
const salt = generateRandomIV();
Expand Down Expand Up @@ -41,6 +48,12 @@ describe("encryptionModule", () => {
expect(decrypted).toEqual(email);
});

it("throws an error for invalid email format", () => {
expect(() => encryptEmail("a".repeat(10000))).toThrow(
"Invalid email format",
);
});

it("throws an error for empty email input", () => {
expect(() => encryptEmail("")).toThrow("Empty or invalid email input.");
});
Expand Down Expand Up @@ -82,4 +95,36 @@ describe("encryptionModule", () => {
expect(encrypted).toMatch(/^[0-9a-f]+:[0-9a-f]+:[0-9a-f]+$/i);
});
});

it("should throw an error if encrypted data format is invalid (missing iv, authTag, or encryptedHex)", () => {
expect(() => decryptEmail("a".repeat(10000))).toThrow(
"Invalid encrypted data format. Expected format 'iv:authTag:encryptedData'.",
);
});

it("should throw an error if encryption key length is not 64 characters", () => {
process.env.ENCRYPTION_KEY = "a".repeat(32); // 32 characters instead of 64
expect(() => decryptEmail(validEncryptedData)).toThrow(
"Encryption key must be a valid 256-bit hexadecimal string (64 characters).",
);
});

it("should throw an error if encryption key contains non-hexadecimal characters", () => {
process.env.ENCRYPTION_KEY = "z".repeat(64); // 'z' is not a valid hex character
expect(() => decryptEmail(validEncryptedData)).toThrow(
"Encryption key must be a valid 256-bit hexadecimal string (64 characters).",
);
});

it("should not throw an error for a valid 64-character hexadecimal encryption key", () => {
expect(() => decryptEmail(validEncryptedData)).not.toThrow();
});

it("should throw an error for a invalid encrypted data", () => {
const invalidEncryptedData =
"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6:1234567890abcdef1234567890abcdef:abcd1234abcd1234";
expect(() => decryptEmail(invalidEncryptedData)).toThrow(
"Decryption failed: invalid data or authentication tag.",
);
});
});
75 changes: 73 additions & 2 deletions tests/utilities/hashingModule.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { hashEmail } from "../../src/utilities/hashEmail";
import { describe, it, expect, vi } from "vitest";
import { compareHashedEmails, hashEmail } from "../../src/utilities/hashEmail";
import { setHashPepper } from "../../setup";

describe("hashingModule", () => {
Expand Down Expand Up @@ -50,5 +50,76 @@ describe("hashingModule", () => {
process.env.HASH_PEPPER = originalPepper;
}
});
it("should throw an error for an invalid email format", () => {
const invalidEmails = [
"plainaddress",
"missing@domain",
"@missinglocal.com",
"[email protected]",
];

invalidEmails.forEach((email) => {
expect(() => hashEmail(email)).toThrow("Invalid email format");
});
});

it("should throw an error if HASH_PEPPER is missing", () => {
const originalPepper = process.env.HASH_PEPPER;
delete process.env.HASH_PEPPER;

expect(() => hashEmail("[email protected]")).toThrow(
"Missing HASH_PEPPER environment variable required for secure email hashing",
);

process.env.HASH_PEPPER = originalPepper;
});

it("should throw an error if HASH_PEPPER is shorter than 32 characters", () => {
const originalPepper = process.env.HASH_PEPPER;
process.env.HASH_PEPPER = "short_pepper";

expect(() => hashEmail("[email protected]")).toThrow(
"HASH_PEPPER must be at least 32 characters long",
);

process.env.HASH_PEPPER = originalPepper;
});
});

describe("compareHashedEmails function error handling", () => {
it("should return false for invalid hashed email formats", () => {
const validHash = "a".repeat(64);
const invalidHashes = [
"short",
"invalid_characters_!@#",
"",
null,
undefined,
];

invalidHashes.forEach((invalidHash) => {
expect(
compareHashedEmails(invalidHash as unknown as string, validHash),
).toBe(false);
expect(
compareHashedEmails(validHash, invalidHash as unknown as string),
).toBe(false);
});
});

it("should log an error and return false if crypto.timingSafeEqual fails due to invalid hex encoding", () => {
const invalidHash = "z".repeat(64); // deliberately invalid hex
let result;
try {
result = compareHashedEmails(invalidHash, invalidHash);
} catch (error) {
expect(result).toBe(false);
if (error instanceof Error) {
expect(error.message).toBe(
"Failed to compare hashes, likely due to invalid hex encoding",
);
}
}
});
});
});

0 comments on commit fa3a3ab

Please sign in to comment.