Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,19 @@ npm run db:seed

---

## πŸ”Œ Supabase Integration

This project uses Supabase for external data access and future integrations.

Update your `.env` file with:

```bash
SUPABASE_URL=...
SUPABASE_ANON_KEY=...
```
Comment on lines +239 to +248
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Verification agent

🧩 Analysis chain

Supabase env vars: add service role key note and align .env.example.

Document the server-only SUPABASE_SERVICE_ROLE_KEY and warn against committing secrets. Also ensure .env.example includes these vars.

Apply:

 ## πŸ”Œ Supabase Integration

 This project uses Supabase for external data access and future integrations.

 Update your `.env` file with:
 
 ```bash
 SUPABASE_URL=...
 SUPABASE_ANON_KEY=...
+# Server-only key for admin operations (never expose publicly)
+SUPABASE_SERVICE_ROLE_KEY=...

Run to verify placeholders exist in .env.example and code refs are consistent:

---

🏁 Script executed:

```shell
#!/bin/bash
rg -nP 'SUPABASE_(URL|ANON_KEY|SERVICE_ROLE_KEY)' -S .env.example || true
rg -nP 'process\.env\.SUPABASE_(URL|ANON_KEY|SERVICE_ROLE_KEY)' -S | sed -E 's/^(.{0,120}).*/\1/'

Length of output: 400


Add SUPABASE_SERVICE_ROLE_KEY to README and .env.example

  • README.md (lines 239–248): append
    SUPABASE_SERVICE_ROLE_KEY=… # Server-only key for admin operations (never expose publicly)
    to the env snippet.
  • .env.example (after line 56): add
    SUPABASE_SERVICE_ROLE_KEY=your-service-role-key.
🧰 Tools
πŸͺ› LanguageTool

[grammar] ~239-~239: There might be a mistake here.
Context: ...eed ``` --- ## πŸ”Œ Supabase Integration This project uses Supabase for external ...

(QB_NEW_EN)

πŸ€– Prompt for AI Agents
In README.md around lines 239 to 248 and .env.example after line 56, the
instructions and example are missing the Supabase service role key; append to
the README env snippet a new line: SUPABASE_SERVICE_ROLE_KEY=… with a comment
that it's a server-only key (never expose publicly), and add
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key to .env.example after line 56 so
the example includes the server-only key placeholder.


---

## πŸ“ Module Overview

### Core Modules
Expand Down
23 changes: 18 additions & 5 deletions src/modules/auth/__tests__/controllers/AuthController.int.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { Request, Response } from "express";
// Integration test for AuthController
import AuthController from "../../presentation/controllers/Auth.controller";
import { AuthModuleFactory } from "../../factories/auth.factory";

describe("AuthController Integration", () => {
it("should have a register method", () => {
expect(typeof AuthController.register).toBe("function");
let authUseCases: ReturnType<typeof AuthModuleFactory.createAuthController>;

beforeEach(() => {
authUseCases = AuthModuleFactory.createAuthController();
});

it("should create auth use cases with all required dependencies", () => {
expect(authUseCases.sendVerificationEmailUseCase).toBeDefined();
expect(authUseCases.resendVerificationEmailUseCase).toBeDefined();
expect(authUseCases.verifyEmailUseCase).toBeDefined();
expect(authUseCases.validateWalletFormatUseCase).toBeDefined();
expect(authUseCases.verifyWalletUseCase).toBeDefined();
});

test("should return error if required fields are missing on register", async () => {
Expand All @@ -13,7 +22,11 @@ describe("AuthController Integration", () => {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
} as Partial<Response>;
await AuthController.register(req as Request, res as Response);

// Import the controller functions directly since they use the factory
const { register } = await import("../../presentation/controllers/Auth.controller");
await register(req as Request, res as Response);

expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith(
expect.objectContaining({ message: expect.any(String) })
Expand Down
85 changes: 85 additions & 0 deletions src/modules/auth/__tests__/factories/auth.factory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { AuthModuleFactory } from "../../factories/auth.factory";
import { PrismaUserRepository } from "../../../user/repositories/PrismaUserRepository";
import { SendVerificationEmailUseCase } from "../../use-cases/send-verification-email.usecase";
import { ResendVerificationEmailUseCase } from "../../use-cases/resend-verification-email.usecase";
import { VerifyEmailUseCase } from "../../use-cases/verify-email.usecase";
import { ValidateWalletFormatUseCase } from "../../use-cases/wallet-format-validation.usecase";
import { VerifyWalletUseCase } from "../../use-cases/verify-wallet.usecase";

// Mock the dependencies
jest.mock("../../../user/repositories/PrismaUserRepository");
jest.mock("../../use-cases/send-verification-email.usecase");
jest.mock("../../use-cases/resend-verification-email.usecase");
jest.mock("../../use-cases/verify-email.usecase");
jest.mock("../../use-cases/wallet-format-validation.usecase");
jest.mock("../../use-cases/verify-wallet.usecase");

describe("AuthModuleFactory", () => {
let mockUserRepository: jest.Mocked<PrismaUserRepository>;
let mockSendVerificationEmailUseCase: jest.Mocked<SendVerificationEmailUseCase>;
let mockResendVerificationEmailUseCase: jest.Mocked<ResendVerificationEmailUseCase>;
let mockVerifyEmailUseCase: jest.Mocked<VerifyEmailUseCase>;
let mockValidateWalletFormatUseCase: jest.Mocked<ValidateWalletFormatUseCase>;
let mockVerifyWalletUseCase: jest.Mocked<VerifyWalletUseCase>;

beforeEach(() => {
jest.clearAllMocks();

// Create mock instances
mockUserRepository = new PrismaUserRepository() as jest.Mocked<PrismaUserRepository>;
mockSendVerificationEmailUseCase = new SendVerificationEmailUseCase(mockUserRepository) as jest.Mocked<SendVerificationEmailUseCase>;
mockResendVerificationEmailUseCase = new ResendVerificationEmailUseCase(mockUserRepository) as jest.Mocked<ResendVerificationEmailUseCase>;
mockVerifyEmailUseCase = new VerifyEmailUseCase(mockUserRepository) as jest.Mocked<VerifyEmailUseCase>;
mockValidateWalletFormatUseCase = new ValidateWalletFormatUseCase() as jest.Mocked<ValidateWalletFormatUseCase>;
mockVerifyWalletUseCase = new VerifyWalletUseCase() as jest.Mocked<VerifyWalletUseCase>;

// Mock the constructors
(PrismaUserRepository as jest.Mock).mockImplementation(() => mockUserRepository);
(SendVerificationEmailUseCase as jest.Mock).mockImplementation(() => mockSendVerificationEmailUseCase);
(ResendVerificationEmailUseCase as jest.Mock).mockImplementation(() => mockResendVerificationEmailUseCase);
(VerifyEmailUseCase as jest.Mock).mockImplementation(() => mockVerifyEmailUseCase);
(ValidateWalletFormatUseCase as jest.Mock).mockImplementation(() => mockValidateWalletFormatUseCase);
(VerifyWalletUseCase as jest.Mock).mockImplementation(() => mockVerifyWalletUseCase);
});
Comment on lines +25 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not instantiate mocked classes in setup; it corrupts constructor call counts.

Creating mocks via new before wiring assertions makes toHaveBeenCalledTimes(1) fail and couples tests to Jest internals. Use stub objects + mockReturnValue instead.

   beforeEach(() => {
     jest.clearAllMocks();

-    // Create mock instances
-    mockUserRepository = new PrismaUserRepository() as jest.Mocked<PrismaUserRepository>;
-    mockSendVerificationEmailUseCase = new SendVerificationEmailUseCase(mockUserRepository) as jest.Mocked<SendVerificationEmailUseCase>;
-    mockResendVerificationEmailUseCase = new ResendVerificationEmailUseCase(mockUserRepository) as jest.Mocked<ResendVerificationEmailUseCase>;
-    mockVerifyEmailUseCase = new VerifyEmailUseCase(mockUserRepository) as jest.Mocked<VerifyEmailUseCase>;
-    mockValidateWalletFormatUseCase = new ValidateWalletFormatUseCase() as jest.Mocked<ValidateWalletFormatUseCase>;
-    mockVerifyWalletUseCase = new VerifyWalletUseCase() as jest.Mocked<VerifyWalletUseCase>;
+    // Create stub instances (no constructor calls)
+    mockUserRepository = {} as unknown as jest.Mocked<PrismaUserRepository>;
+    mockSendVerificationEmailUseCase = {} as unknown as jest.Mocked<SendVerificationEmailUseCase>;
+    mockResendVerificationEmailUseCase = {} as unknown as jest.Mocked<ResendVerificationEmailUseCase>;
+    mockVerifyEmailUseCase = {} as unknown as jest.Mocked<VerifyEmailUseCase>;
+    mockValidateWalletFormatUseCase = {} as unknown as jest.Mocked<ValidateWalletFormatUseCase>;
+    mockVerifyWalletUseCase = {} as unknown as jest.Mocked<VerifyWalletUseCase>;

     // Mock the constructors
-    (PrismaUserRepository as jest.Mock).mockImplementation(() => mockUserRepository);
-    (SendVerificationEmailUseCase as jest.Mock).mockImplementation(() => mockSendVerificationEmailUseCase);
-    (ResendVerificationEmailUseCase as jest.Mock).mockImplementation(() => mockResendVerificationEmailUseCase);
-    (VerifyEmailUseCase as jest.Mock).mockImplementation(() => mockVerifyEmailUseCase);
-    (ValidateWalletFormatUseCase as jest.Mock).mockImplementation(() => mockValidateWalletFormatUseCase);
-    (VerifyWalletUseCase as jest.Mock).mockImplementation(() => mockVerifyWalletUseCase);
+    (PrismaUserRepository as jest.Mock).mockReturnValue(mockUserRepository);
+    (SendVerificationEmailUseCase as jest.Mock).mockReturnValue(mockSendVerificationEmailUseCase);
+    (ResendVerificationEmailUseCase as jest.Mock).mockReturnValue(mockResendVerificationEmailUseCase);
+    (VerifyEmailUseCase as jest.Mock).mockReturnValue(mockVerifyEmailUseCase);
+    (ValidateWalletFormatUseCase as jest.Mock).mockReturnValue(mockValidateWalletFormatUseCase);
+    (VerifyWalletUseCase as jest.Mock).mockReturnValue(mockVerifyWalletUseCase);
   });
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeEach(() => {
jest.clearAllMocks();
// Create mock instances
mockUserRepository = new PrismaUserRepository() as jest.Mocked<PrismaUserRepository>;
mockSendVerificationEmailUseCase = new SendVerificationEmailUseCase(mockUserRepository) as jest.Mocked<SendVerificationEmailUseCase>;
mockResendVerificationEmailUseCase = new ResendVerificationEmailUseCase(mockUserRepository) as jest.Mocked<ResendVerificationEmailUseCase>;
mockVerifyEmailUseCase = new VerifyEmailUseCase(mockUserRepository) as jest.Mocked<VerifyEmailUseCase>;
mockValidateWalletFormatUseCase = new ValidateWalletFormatUseCase() as jest.Mocked<ValidateWalletFormatUseCase>;
mockVerifyWalletUseCase = new VerifyWalletUseCase() as jest.Mocked<VerifyWalletUseCase>;
// Mock the constructors
(PrismaUserRepository as jest.Mock).mockImplementation(() => mockUserRepository);
(SendVerificationEmailUseCase as jest.Mock).mockImplementation(() => mockSendVerificationEmailUseCase);
(ResendVerificationEmailUseCase as jest.Mock).mockImplementation(() => mockResendVerificationEmailUseCase);
(VerifyEmailUseCase as jest.Mock).mockImplementation(() => mockVerifyEmailUseCase);
(ValidateWalletFormatUseCase as jest.Mock).mockImplementation(() => mockValidateWalletFormatUseCase);
(VerifyWalletUseCase as jest.Mock).mockImplementation(() => mockVerifyWalletUseCase);
});
beforeEach(() => {
jest.clearAllMocks();
// Create stub instances (no constructor calls)
mockUserRepository = {} as unknown as jest.Mocked<PrismaUserRepository>;
mockSendVerificationEmailUseCase = {} as unknown as jest.Mocked<SendVerificationEmailUseCase>;
mockResendVerificationEmailUseCase = {} as unknown as jest.Mocked<ResendVerificationEmailUseCase>;
mockVerifyEmailUseCase = {} as unknown as jest.Mocked<VerifyEmailUseCase>;
mockValidateWalletFormatUseCase = {} as unknown as jest.Mocked<ValidateWalletFormatUseCase>;
mockVerifyWalletUseCase = {} as unknown as jest.Mocked<VerifyWalletUseCase>;
// Mock the constructors
(PrismaUserRepository as jest.Mock).mockReturnValue(mockUserRepository);
(SendVerificationEmailUseCase as jest.Mock).mockReturnValue(mockSendVerificationEmailUseCase);
(ResendVerificationEmailUseCase as jest.Mock).mockReturnValue(mockResendVerificationEmailUseCase);
(VerifyEmailUseCase as jest.Mock).mockReturnValue(mockVerifyEmailUseCase);
(ValidateWalletFormatUseCase as jest.Mock).mockReturnValue(mockValidateWalletFormatUseCase);
(VerifyWalletUseCase as jest.Mock).mockReturnValue(mockVerifyWalletUseCase);
});
πŸ€– Prompt for AI Agents
In src/modules/auth/__tests__/factories/auth.factory.test.ts around lines 25 to
43, the beforeEach currently instantiates mocked classes (new
PrismaUserRepository(), new SendVerificationEmailUseCase(), etc.) which corrupts
constructor call counts; replace those "new" instantiations with lightweight
stub objects that implement the minimal shape needed for the tests (or typed as
Partial/ jest.Mocked) and then wire the constructor mocks using
mockReturnValue(mockStub) instead of mockImplementation(() => mockInstance).
Specifically: remove all `new ...` calls, create plain stub objects for
mockUserRepository and each use case, and change `(X as
jest.Mock).mockImplementation(() => mockX)` to `(X as
jest.Mock).mockReturnValue(mockXStub)` so constructors are not invoked and
call-count assertions remain accurate.


describe("createAuthController", () => {
it("should create all required use cases with proper dependencies", () => {
const authUseCases = AuthModuleFactory.createAuthController();

// Verify all use cases are created
expect(authUseCases.sendVerificationEmailUseCase).toBeDefined();
expect(authUseCases.resendVerificationEmailUseCase).toBeDefined();
expect(authUseCases.verifyEmailUseCase).toBeDefined();
expect(authUseCases.validateWalletFormatUseCase).toBeDefined();
expect(authUseCases.verifyWalletUseCase).toBeDefined();

// Verify constructors were called with correct dependencies
expect(PrismaUserRepository).toHaveBeenCalledTimes(1);
expect(SendVerificationEmailUseCase).toHaveBeenCalledWith(mockUserRepository);
expect(ResendVerificationEmailUseCase).toHaveBeenCalledWith(mockUserRepository);
expect(VerifyEmailUseCase).toHaveBeenCalledWith(mockUserRepository);
expect(ValidateWalletFormatUseCase).toHaveBeenCalledTimes(1);
expect(VerifyWalletUseCase).toHaveBeenCalledTimes(1);
});

it("should return different instances on each call", () => {
const firstInstance = AuthModuleFactory.createAuthController();
const secondInstance = AuthModuleFactory.createAuthController();

expect(firstInstance).not.toBe(secondInstance);
expect(firstInstance.sendVerificationEmailUseCase).not.toBe(secondInstance.sendVerificationEmailUseCase);
});

it("should create use cases with the same user repository instance", () => {
const authUseCases = AuthModuleFactory.createAuthController();

// All use cases that depend on user repository should use the same instance
expect(authUseCases.sendVerificationEmailUseCase).toBeDefined();
expect(authUseCases.resendVerificationEmailUseCase).toBeDefined();
expect(authUseCases.verifyEmailUseCase).toBeDefined();

// Verify the user repository was created only once
expect(PrismaUserRepository).toHaveBeenCalledTimes(1);
});
});
});
25 changes: 25 additions & 0 deletions src/modules/auth/factories/auth.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { PrismaUserRepository } from "../../user/repositories/PrismaUserRepository";
import { SendVerificationEmailUseCase } from "../use-cases/send-verification-email.usecase";
import { ResendVerificationEmailUseCase } from "../use-cases/resend-verification-email.usecase";
import { VerifyEmailUseCase } from "../use-cases/verify-email.usecase";
import { ValidateWalletFormatUseCase } from "../use-cases/wallet-format-validation.usecase";
import { VerifyWalletUseCase } from "../use-cases/verify-wallet.usecase";

export class AuthModuleFactory {
static createAuthController() {
const userRepository = new PrismaUserRepository();
const sendVerificationEmailUseCase = new SendVerificationEmailUseCase(userRepository);
const resendVerificationEmailUseCase = new ResendVerificationEmailUseCase(userRepository);
const verifyEmailUseCase = new VerifyEmailUseCase(userRepository);
const validateWalletFormatUseCase = new ValidateWalletFormatUseCase();
const verifyWalletUseCase = new VerifyWalletUseCase();

return {
sendVerificationEmailUseCase,
resendVerificationEmailUseCase,
verifyEmailUseCase,
validateWalletFormatUseCase,
verifyWalletUseCase,
};
}
}
26 changes: 9 additions & 17 deletions src/modules/auth/presentation/controllers/Auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,15 @@ import {
} from "../../dto/wallet-validation.dto";

// Use cases
import { PrismaUserRepository } from "../../../user/repositories/PrismaUserRepository";
import { SendVerificationEmailUseCase } from "../../use-cases/send-verification-email.usecase";
import { ResendVerificationEmailUseCase } from "../../use-cases/resend-verification-email.usecase";
import { VerifyEmailUseCase } from "../../use-cases/verify-email.usecase";
import { ValidateWalletFormatUseCase } from "../../use-cases/wallet-format-validation.usecase";
import { VerifyWalletUseCase } from "../../use-cases/verify-wallet.usecase";

const userRepository = new PrismaUserRepository();
const sendVerificationEmailUseCase = new SendVerificationEmailUseCase(
userRepository
);
const resendVerificationEmailUseCase = new ResendVerificationEmailUseCase(
userRepository
);
const verifyEmailUseCase = new VerifyEmailUseCase(userRepository);
const validateWalletFormatUseCase = new ValidateWalletFormatUseCase();
const verifyWalletUseCase = new VerifyWalletUseCase();
import { AuthModuleFactory } from "../../factories/auth.factory";

const {
sendVerificationEmailUseCase,
resendVerificationEmailUseCase,
verifyEmailUseCase,
validateWalletFormatUseCase,
verifyWalletUseCase,
} = AuthModuleFactory.createAuthController();

// DTO validator
async function validateOr400<T>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { CertificateModuleFactory } from "../../factories/certificate.factory";
import { CertificateService } from "../../application/services/CertificateService";
import { ICertificateService } from "../../domain/interfaces/ICertificateService";

// Mock the CertificateService
jest.mock("../../application/services/CertificateService");

describe("CertificateModuleFactory", () => {
let mockCertificateService: jest.Mocked<CertificateService>;

beforeEach(() => {
jest.clearAllMocks();
mockCertificateService = new CertificateService() as jest.Mocked<CertificateService>;
(CertificateService as jest.Mock).mockImplementation(() => mockCertificateService);
});
Comment on lines +11 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Test mutates constructor call counts before assertions

Instantiating new CertificateService() in beforeEach increments the constructor count, breaking toHaveBeenCalledTimes expectations.

Apply this diff to avoid pre-instantiation and still control the returned mock:

   beforeEach(() => {
-    jest.clearAllMocks();
-    mockCertificateService = new CertificateService() as jest.Mocked<CertificateService>;
-    (CertificateService as jest.Mock).mockImplementation(() => mockCertificateService);
+    jest.clearAllMocks();
+    // Return a fresh mock on each factory call without invoking the constructor early
+    (CertificateService as unknown as jest.Mock).mockImplementation(() => {
+      return Object.assign(Object.create(CertificateService.prototype), {
+        createCertificate: jest.fn(),
+        getCertificateUrl: jest.fn(),
+        getCertificateBuffer: jest.fn(),
+        logDownload: jest.fn(),
+      }) as CertificateService;
+    });
   });
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeEach(() => {
jest.clearAllMocks();
mockCertificateService = new CertificateService() as jest.Mocked<CertificateService>;
(CertificateService as jest.Mock).mockImplementation(() => mockCertificateService);
});
beforeEach(() => {
jest.clearAllMocks();
// Return a fresh mock on each factory call without invoking the constructor early
(CertificateService as unknown as jest.Mock).mockImplementation(() => {
return Object.assign(Object.create(CertificateService.prototype), {
createCertificate: jest.fn(),
getCertificateUrl: jest.fn(),
getCertificateBuffer: jest.fn(),
logDownload: jest.fn(),
}) as CertificateService;
});
});
πŸ€– Prompt for AI Agents
In src/modules/certificate/__tests__/factories/certificate.factory.test.ts
around lines 11-15, the beforeEach currently calls new CertificateService(),
which increments the mocked constructor call count and breaks
toHaveBeenCalledTimes assertions; remove the instantiation and instead create a
mocked object (e.g. const mockCertificateService = {} as
jest.Mocked<CertificateService> or build it via jest.createMockFromModule /
manual jest.fn() fields) and then wire it up with (CertificateService as
jest.Mock).mockImplementation(() => mockCertificateService) (or
mockImplementationOnce where appropriate) so the test controls the returned mock
without incrementing the constructor call count.


describe("createCertificateService", () => {
it("should create a certificate service instance", () => {
const certificateService = CertificateModuleFactory.createCertificateService();

expect(certificateService).toBeDefined();
expect(CertificateService).toHaveBeenCalledTimes(1);
});

it("should return an instance that implements ICertificateService", () => {
const certificateService = CertificateModuleFactory.createCertificateService();

expect(certificateService).toBeInstanceOf(CertificateService);
expect(typeof certificateService.createCertificate).toBe("function");
expect(typeof certificateService.getCertificateUrl).toBe("function");
expect(typeof certificateService.getCertificateBuffer).toBe("function");
expect(typeof certificateService.logDownload).toBe("function");
});

it("should return different instances on each call", () => {
const firstInstance = CertificateModuleFactory.createCertificateService();
const secondInstance = CertificateModuleFactory.createCertificateService();

expect(firstInstance).not.toBe(secondInstance);
expect(CertificateService).toHaveBeenCalledTimes(2);
});
});
});
8 changes: 8 additions & 0 deletions src/modules/certificate/factories/certificate.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { CertificateService } from "../application/services/CertificateService";
import { ICertificateService } from "../domain/interfaces/ICertificateService";

export class CertificateModuleFactory {
static createCertificateService(): ICertificateService {
return new CertificateService();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { container } from "../../../../shared/infrastructure/container";
import { prisma } from "../../../../config/prisma";
import { ICertificateService } from "../../domain/interfaces/ICertificateService";
import { CertificateModuleFactory } from "../../factories/certificate.factory";
import { AuthenticatedRequest } from "../../../../types/auth.types";
import { Response } from "express";

const certificateService: ICertificateService = container.certificateService;
const certificateService: ICertificateService = CertificateModuleFactory.createCertificateService();

export const downloadCertificate = async (
req: AuthenticatedRequest,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { MessagingModuleFactory } from "../../factories/messaging.factory";
import { MessagingService } from "../../application/services/MessagingService";
import { MessagingController } from "../../controllers/MessagingController";
import { MessagePrismaRepository } from "../../repositories/implementations/message-prisma.repository";
import { PrismaClient } from "@prisma/client";

// Mock the dependencies
jest.mock("../../application/services/MessagingService");
jest.mock("../../controllers/MessagingController");
jest.mock("../../repositories/implementations/message-prisma.repository");
jest.mock("@prisma/client");

describe("MessagingModuleFactory", () => {
let mockPrisma: jest.Mocked<PrismaClient>;
let mockMessageRepository: jest.Mocked<MessagePrismaRepository>;
let mockMessagingService: jest.Mocked<MessagingService>;
let mockMessagingController: jest.Mocked<MessagingController>;

beforeEach(() => {
jest.clearAllMocks();

// Create mock instances
mockPrisma = new PrismaClient() as jest.Mocked<PrismaClient>;
mockMessageRepository = new MessagePrismaRepository(mockPrisma) as jest.Mocked<MessagePrismaRepository>;
mockMessagingService = new MessagingService(mockMessageRepository, mockPrisma) as jest.Mocked<MessagingService>;
mockMessagingController = new MessagingController(mockMessagingService) as jest.Mocked<MessagingController>;

// Mock the constructors
(PrismaClient as jest.Mock).mockImplementation(() => mockPrisma);
(MessagePrismaRepository as jest.Mock).mockImplementation(() => mockMessageRepository);
(MessagingService as jest.Mock).mockImplementation(() => mockMessagingService);
(MessagingController as jest.Mock).mockImplementation(() => mockMessagingController);
});
Comment on lines +22 to +33
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Pre-instantiating mocks inflates constructor call counts

Creating instances with new before setting mockImplementation causes toHaveBeenCalledTimes failures.

Apply this diff to return stubs without early constructor calls and preserve instanceof:

-    // Create mock instances
-    mockPrisma = new PrismaClient() as jest.Mocked<PrismaClient>;
-    mockMessageRepository = new MessagePrismaRepository(mockPrisma) as jest.Mocked<MessagePrismaRepository>;
-    mockMessagingService = new MessagingService(mockMessageRepository, mockPrisma) as jest.Mocked<MessagingService>;
-    mockMessagingController = new MessagingController(mockMessagingService) as jest.Mocked<MessagingController>;
-
-    // Mock the constructors
-    (PrismaClient as jest.Mock).mockImplementation(() => mockPrisma);
-    (MessagePrismaRepository as jest.Mock).mockImplementation(() => mockMessageRepository);
-    (MessagingService as jest.Mock).mockImplementation(() => mockMessagingService);
-    (MessagingController as jest.Mock).mockImplementation(() => mockMessagingController);
+    // Provide stub instances without calling constructors
+    mockPrisma = {} as unknown as PrismaClient;
+    mockMessageRepository = {} as unknown as MessagePrismaRepository;
+    mockMessagingService = {
+      sendMessage: jest.fn(),
+      getConversation: jest.fn(),
+      markMessageAsRead: jest.fn(),
+    } as unknown as MessagingService;
+    const makeController = () => {
+      const ctrl = Object.create(MessagingController.prototype) as MessagingController;
+      (ctrl as any).sendMessage = jest.fn();
+      (ctrl as any).getConversation = jest.fn();
+      (ctrl as any).markAsRead = jest.fn();
+      return ctrl as jest.Mocked<MessagingController>;
+    };
+
+    // Mock the constructors to return the stubs
+    (PrismaClient as unknown as jest.Mock).mockImplementation(() => mockPrisma);
+    (MessagePrismaRepository as unknown as jest.Mock).mockImplementation(() => mockMessageRepository);
+    (MessagingService as unknown as jest.Mock).mockImplementation(() => mockMessagingService);
+    (MessagingController as unknown as jest.Mock).mockImplementation(() => makeController());
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Create mock instances
mockPrisma = new PrismaClient() as jest.Mocked<PrismaClient>;
mockMessageRepository = new MessagePrismaRepository(mockPrisma) as jest.Mocked<MessagePrismaRepository>;
mockMessagingService = new MessagingService(mockMessageRepository, mockPrisma) as jest.Mocked<MessagingService>;
mockMessagingController = new MessagingController(mockMessagingService) as jest.Mocked<MessagingController>;
// Mock the constructors
(PrismaClient as jest.Mock).mockImplementation(() => mockPrisma);
(MessagePrismaRepository as jest.Mock).mockImplementation(() => mockMessageRepository);
(MessagingService as jest.Mock).mockImplementation(() => mockMessagingService);
(MessagingController as jest.Mock).mockImplementation(() => mockMessagingController);
});
// Provide stub instances without calling constructors
mockPrisma = {} as unknown as PrismaClient;
mockMessageRepository = {} as unknown as MessagePrismaRepository;
mockMessagingService = {
sendMessage: jest.fn(),
getConversation: jest.fn(),
markMessageAsRead: jest.fn(),
} as unknown as MessagingService;
const makeController = () => {
const ctrl = Object.create(MessagingController.prototype) as MessagingController;
(ctrl as any).sendMessage = jest.fn();
(ctrl as any).getConversation = jest.fn();
(ctrl as any).markAsRead = jest.fn();
return ctrl as jest.Mocked<MessagingController>;
};
// Mock the constructors to return the stubs
(PrismaClient as unknown as jest.Mock).mockImplementation(() => mockPrisma);
(MessagePrismaRepository as unknown as jest.Mock).mockImplementation(() => mockMessageRepository);
(MessagingService as unknown as jest.Mock).mockImplementation(() => mockMessagingService);
(MessagingController as unknown as jest.Mock).mockImplementation(() => makeController());
πŸ€– Prompt for AI Agents
In src/modules/messaging/__tests__/factories/messaging.factory.test.ts around
lines 22 to 33, the test currently pre-instantiates real objects with `new`
before calling `mockImplementation`, which increments constructor call counts
and breaks `toHaveBeenCalledTimes` assertions; instead remove the `new` calls
and create plain stub instances (e.g., via Object.create(Class.prototype) or an
empty object with its prototype set to the real class prototype) for
PrismaClient, MessagePrismaRepository, MessagingService and MessagingController,
then set each `(Class as jest.Mock).mockImplementation(() => stubInstance)` so
constructors are not invoked but `instanceof` still holds; finally ensure mocks
are cleaned up/reset in afterEach if not already done.


describe("createMessagingController", () => {
it("should create a messaging controller with all required dependencies", () => {
const messagingController = MessagingModuleFactory.createMessagingController();

expect(messagingController).toBeDefined();
expect(messagingController).toBeInstanceOf(MessagingController);

// Verify constructors were called with correct dependencies
expect(PrismaClient).toHaveBeenCalledTimes(1);
expect(MessagePrismaRepository).toHaveBeenCalledWith(mockPrisma);
expect(MessagingService).toHaveBeenCalledWith(mockMessageRepository, mockPrisma);
expect(MessagingController).toHaveBeenCalledWith(mockMessagingService);
});

it("should return different instances on each call", () => {
const firstInstance = MessagingModuleFactory.createMessagingController();
const secondInstance = MessagingModuleFactory.createMessagingController();

expect(firstInstance).not.toBe(secondInstance);
expect(MessagingController).toHaveBeenCalledTimes(2);
});

it("should create dependencies with proper wiring", () => {
const messagingController = MessagingModuleFactory.createMessagingController();

// Verify the controller has the expected methods
expect(typeof messagingController.sendMessage).toBe("function");
expect(typeof messagingController.getConversation).toBe("function");
expect(typeof messagingController.markAsRead).toBe("function");
});
});
});
13 changes: 13 additions & 0 deletions src/modules/messaging/factories/messaging.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MessagingService } from "../application/services/MessagingService";
import { MessagingController } from "../controllers/MessagingController";
import { MessagePrismaRepository } from "../repositories/implementations/message-prisma.repository";
import { PrismaClient } from "@prisma/client";

export class MessagingModuleFactory {
static createMessagingController(): MessagingController {
const prisma = new PrismaClient();
const messageRepository = new MessagePrismaRepository(prisma);
const messagingService = new MessagingService(messageRepository, prisma);
return new MessagingController(messagingService);
}
Comment on lines +8 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Accept PrismaClient from caller; don’t instantiate here.

Creating Prisma inside the factory impedes lifecycle control and can leak connections. Accept an injected instance instead.

-export class MessagingModuleFactory {
-  static createMessagingController(): MessagingController {
-    const prisma = new PrismaClient();
-    const messageRepository = new MessagePrismaRepository(prisma);
-    const messagingService = new MessagingService(messageRepository, prisma);
-    return new MessagingController(messagingService);
-  }
-}
+export class MessagingModuleFactory {
+  static createMessagingController(prisma: PrismaClient): MessagingController {
+    const messageRepository = new MessagePrismaRepository(prisma);
+    const messagingService = new MessagingService(messageRepository, prisma);
+    return new MessagingController(messagingService);
+  }
+}

Add graceful shutdown (app-level):

// e.g., index.ts
process.on("SIGINT", async () => { await prisma.$disconnect(); process.exit(0); });
process.on("SIGTERM", async () => { await prisma.$disconnect(); process.exit(0); });
πŸ€– Prompt for AI Agents
In src/modules/messaging/factories/messaging.factory.ts around lines 8 to 12,
the factory currently instantiates a new PrismaClient inside the factory which
can leak connections and prevents proper lifecycle control; change the factory
to accept a PrismaClient instance from the caller (add a prisma: PrismaClient
parameter to the factory function), remove the new PrismaClient() call, pass the
injected prisma into MessagePrismaRepository and MessagingService, and return
the controller; do not add shutdown handlers here β€” ensure graceful shutdown
(calling prisma.$disconnect()) is implemented at the application entrypoint
(e.g., index.ts) on SIGINT/SIGTERM.

}
10 changes: 2 additions & 8 deletions src/modules/messaging/routes/messaging.routes.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { Router } from "express";
import { MessagingController } from "../controllers/MessagingController";
import { MessagingService } from "../application/services/MessagingService";
import { MessagePrismaRepository } from "../repositories/implementations/message-prisma.repository";
import { MessagingModuleFactory } from "../factories/messaging.factory";
import { authMiddleware } from "../../../middleware/authMiddleware";
import { PrismaClient } from "@prisma/client";

const router = Router();
const prisma = new PrismaClient();

const messageRepository = new MessagePrismaRepository(prisma);
const messagingService = new MessagingService(messageRepository, prisma);
const messagingController = new MessagingController(messagingService);
const messagingController = MessagingModuleFactory.createMessagingController();

Comment on lines +2 to 8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Avoid new PrismaClient per factory; inject app-scoped Prisma.

Factory currently creates its own Prisma client, risking excess connections and no shutdown disposal. Import the shared prisma and pass it to the factory.

-import { MessagingModuleFactory } from "../factories/messaging.factory";
+import { MessagingModuleFactory } from "../factories/messaging.factory";
+import { prisma } from "../../../config/prisma";
 
-const messagingController = MessagingModuleFactory.createMessagingController();
+const messagingController = MessagingModuleFactory.createMessagingController(prisma);

And adjust the factory (see factory file comment) to accept a PrismaClient.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { MessagingModuleFactory } from "../factories/messaging.factory";
import { authMiddleware } from "../../../middleware/authMiddleware";
import { PrismaClient } from "@prisma/client";
const router = Router();
const prisma = new PrismaClient();
const messageRepository = new MessagePrismaRepository(prisma);
const messagingService = new MessagingService(messageRepository, prisma);
const messagingController = new MessagingController(messagingService);
const messagingController = MessagingModuleFactory.createMessagingController();
import { MessagingModuleFactory } from "../factories/messaging.factory";
import { prisma } from "../../../config/prisma";
import { authMiddleware } from "../../../middleware/authMiddleware";
const router = Router();
const messagingController = MessagingModuleFactory.createMessagingController(prisma);
πŸ€– Prompt for AI Agents
In src/modules/messaging/routes/messaging.routes.ts around lines 2 to 8, the
route file imports the MessagingModuleFactory which currently creates its own
PrismaClient; instead import the application-scoped Prisma instance (e.g., from
your shared prisma export) and pass that PrismaClient into the factory when
calling createMessagingController so the factory uses the injected client;
update the factory signature to accept a PrismaClient parameter and construct
controllers/services using that instance (do not instantiate a new PrismaClient
here).

router.use(authMiddleware);

Expand Down
19 changes: 19 additions & 0 deletions src/modules/wallet/factories/wallet.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { WalletService } from "../application/services/WalletService";
import { HorizonWalletRepository } from "../repositories/HorizonWalletRepository";
import { VerifyWalletUseCase } from "../use-cases/VerifyWalletUseCase";
import { ValidateWalletFormatUseCase } from "../use-cases/ValidateWalletFormatUseCase";

export class WalletModuleFactory {
static createWalletService(): WalletService {
const walletRepository = new HorizonWalletRepository();
const verifyWalletUseCase = new VerifyWalletUseCase(walletRepository);
const validateWalletFormatUseCase = new ValidateWalletFormatUseCase();

const walletService = new WalletService();
(walletService as any).walletRepository = walletRepository;
(walletService as any).verifyWalletUseCase = verifyWalletUseCase;
(walletService as any).validateWalletFormatUseCase = validateWalletFormatUseCase;
Comment on lines +12 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

⚠️ Potential issue

Remove any-cast field injection; use typed constructor injection.

Assigning private deps via (walletService as any) is unsafe and brittle. Inject dependencies via the WalletService constructor.

-    const walletService = new WalletService();
-    (walletService as any).walletRepository = walletRepository;
-    (walletService as any).verifyWalletUseCase = verifyWalletUseCase;
-    (walletService as any).validateWalletFormatUseCase = validateWalletFormatUseCase;
+    const walletService = new WalletService({
+      walletRepository,
+      verifyWalletUseCase,
+      validateWalletFormatUseCase,
+    });

Outside this file, update WalletService to accept deps:

// WalletService.ts
export interface WalletServiceDeps {
  walletRepository: HorizonWalletRepository; // or interface type
  verifyWalletUseCase: VerifyWalletUseCase;
  validateWalletFormatUseCase: ValidateWalletFormatUseCase;
}

export class WalletService {
  constructor(private readonly deps: WalletServiceDeps) {}
  // use: this.deps.verifyWalletUseCase, etc.
}
πŸ€– Prompt for AI Agents
In src/modules/wallet/factories/wallet.factory.ts around lines 12-15 the factory
is assigning private dependencies via unsafe `(walletService as any)` casts;
change to constructor injection by updating WalletService to accept a typed
dependency object (WalletServiceDeps) containing walletRepository,
verifyWalletUseCase and validateWalletFormatUseCase, then instantiate here with
`new WalletService({ walletRepository, verifyWalletUseCase,
validateWalletFormatUseCase })`; also update imports/types in both files and
refactor WalletService internals to read deps from the constructor
(this.deps.*), and remove the any-cast assignments.


return walletService;
}
}
6 changes: 0 additions & 6 deletions src/shared/infrastructure/container.ts

This file was deleted.