ledgerone_backend/test/auth.service.spec.ts

145 lines
4.9 KiB
TypeScript

import { NotFoundException } from "@nestjs/common";
import { AuthService } from "../src/auth/auth.service";
const model = (order: string[]) => ({
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
updateMany: jest.fn(),
upsert: jest.fn(),
delete: jest.fn(async () => {
order.push("user.delete");
}),
deleteMany: jest.fn(async () => undefined),
});
const createService = () => {
const order: string[] = [];
const tx = {
account: model(order),
transactionRaw: model(order),
transactionDerived: model(order),
rule: model(order),
ruleExecution: model(order),
exportLog: model(order),
auditLog: model(order),
googleConnection: model(order),
emailVerificationToken: model(order),
passwordResetToken: model(order),
refreshToken: model(order),
session: model(order),
socialAccount: model(order),
subscription: model(order),
abuseEvent: model(order),
taxReturn: model(order),
taxDocument: model(order),
user: model(order),
};
tx.taxDocument.deleteMany.mockImplementation(async () => {
order.push("taxDocument.deleteMany");
});
tx.ruleExecution.deleteMany.mockImplementation(async () => {
order.push("ruleExecution.deleteMany");
});
tx.transactionDerived.deleteMany.mockImplementation(async () => {
order.push("transactionDerived.deleteMany");
});
tx.transactionRaw.deleteMany.mockImplementation(async () => {
order.push("transactionRaw.deleteMany");
});
tx.account.deleteMany.mockImplementation(async () => {
order.push("account.deleteMany");
});
tx.rule.deleteMany.mockImplementation(async () => {
order.push("rule.deleteMany");
});
const prisma = {
user: { findUnique: jest.fn() },
$transaction: jest.fn((callback: (client: typeof tx) => Promise<void>) => callback(tx)),
};
const service = new AuthService(
prisma as any,
{ sign: jest.fn(), verify: jest.fn() } as any,
{ sendVerificationEmail: jest.fn(), sendPasswordResetEmail: jest.fn() } as any,
{ decrypt: jest.fn() } as any,
);
return { service, prisma, tx, order };
};
describe("AuthService account deletion", () => {
it("deletes dependent personal data before deleting the user", async () => {
const { service, prisma, tx, order } = createService();
prisma.user.findUnique.mockResolvedValue({ id: "user_1" });
tx.account.findMany.mockResolvedValue([{ id: "acct_1" }]);
tx.transactionRaw.findMany.mockResolvedValue([{ id: "tx_1" }]);
tx.rule.findMany.mockResolvedValue([{ id: "rule_1" }]);
tx.taxReturn.findMany.mockResolvedValue([{ id: "return_1" }]);
await expect(service.deleteAccount("user_1")).resolves.toEqual({
message: "Account and associated personal data deleted.",
});
expect(tx.ruleExecution.deleteMany).toHaveBeenCalledWith({
where: {
OR: [
{ ruleId: { in: ["rule_1"] } },
{ transactionId: { in: ["tx_1"] } },
],
},
});
expect(tx.user.delete).toHaveBeenCalledWith({ where: { id: "user_1" } });
expect(order.slice(0, 6)).toEqual([
"taxDocument.deleteMany",
"ruleExecution.deleteMany",
"transactionDerived.deleteMany",
"transactionRaw.deleteMany",
"account.deleteMany",
"rule.deleteMany",
]);
expect(order[order.length - 1]).toBe("user.delete");
});
it("throws when the user no longer exists", async () => {
const { service, prisma } = createService();
prisma.user.findUnique.mockResolvedValue(null);
await expect(service.deleteAccount("missing_user")).rejects.toBeInstanceOf(NotFoundException);
expect(prisma.$transaction).not.toHaveBeenCalled();
});
});
describe("AuthService session-bound tokens", () => {
it("creates a server-side session and binds tokens to it", async () => {
const { service, prisma } = createService();
(prisma as any).session = {
create: jest.fn().mockResolvedValue({ id: "session_1" }),
};
(prisma as any).refreshToken = {
create: jest.fn().mockResolvedValue({ id: "refresh_1" }),
};
const jwt = (service as any).jwtService;
jwt.sign.mockReturnValue("access_token");
const result = await service.issueTokensForUser("user_1", {
ipAddress: "127.0.0.1",
userAgent: "jest",
});
expect(result).toEqual({ accessToken: "access_token", refreshToken: expect.any(String), sessionNonce: expect.any(String) });
expect((prisma as any).session.create).toHaveBeenCalledWith({
data: expect.objectContaining({
userId: "user_1",
ipHash: expect.any(String),
userAgentHash: expect.any(String),
nonceHash: expect.stringMatching(/^[a-f0-9]{64}$/),
}),
});
expect(jwt.sign).toHaveBeenCalledWith({ sub: "user_1", sid: "session_1" });
expect((prisma as any).refreshToken.create).toHaveBeenCalledWith({
data: expect.objectContaining({ userId: "user_1", sessionId: "session_1" }),
});
});
});