Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 20, 2025

Refactoring @sthrift/service-sendgrid to support pluggable email providers with mock for local development, and implementing ReservationRequestCreated event handler structure.

Status: Code Review Feedback Addressed ✅

All packages built successfully. Addressed code review feedback:

  • ✅ Reverted config file changes
  • ✅ Fixed import paths to use .ts/.tsx extensions
  • ✅ Fixed markAsNew pattern to follow Cellix example
  • ✅ Removed duplicate vitest import

Architecture:

  • @cellix/transactional-email-service - Generic interface
  • @sthrift/transactional-email-service-sendgrid - SendGrid implementation
  • @sthrift/transactional-email-service-mock - Mock for local dev
  • ✅ Event infrastructure with ReservationRequestCreated
  • ✅ Event handler following Cellix pattern
  • ✅ Feature files and tests for all packages
Original prompt

This section details on the original issue you should resolve

<issue_title>Refactor @sthrift/service-sendgrid package to provide a ServiceSendGrid facade with mock for local development</issue_title>
<issue_description>## Problem
Developers currently rely on SendGrid for email template verification, which requires sending real emails and a valid API key. This makes local development and testing workflows cumbersome. This effort aligns with the overall direction we are heading for disconnected development, which allows local development to progress without reliance on any external integrations.

Public Interface Direction

The key consideration is that @sthrift/transactional-email-service should be just a package with a single interface in it. It has to be generic enough to support multiple 3rd party vendors. The publicly exposed interface for the facade should be minimal, hiding all proprietary details and specific datatypes that are part of the SendGrid library, exposing only the absolute simplest interface necessary to get the job done. The code within the facade should translate this simple public interface into the proprietary calls to the email provider's API. This ensures we can swap implementations of mail sending services without having to change the public interface or upstream logic—enabling a true plug-and-play approach (e.g., swapping in Azure Communication Services). We should take this approach for all of our 3rd party integrations going forward.

We should start by defining the public interface first—such as:

  • @sthrift/transactional-email-service (only defines the facade interface; the rest of the system refers only to this interface)

Then, the specific implementations are fully interchangeable and can be swapped without impacting upstream code:

  • @sthrift/transactional-email-service-sendgrid-v3 (facade-implementation)
  • @sthrift/transactional-email-service-sendgrid-v4 (facade-implementation)
  • @sthrift/transactional-email-service-azure-communication (facade-implementation, example only)
  • @sthrift/transactional-email-service-mock (facade-implementation)

For this task, only concrete implementations for transactional email service sendgrid and transactional email service mock are required. Azure Communication Services is provided as an example of how this architecture enables easily swappable integrations and may be explored in the future.

Configuration Approach

The @sthrift/api package will be responsible for determining which facade implementation is actually registered at application startup. This should be determined by environment variable values.

Proposal

Refactor the existing @sthrift/service-sendgrid package so it becomes the facade for ServiceSendGrid. This facade should:

  • Expose the same interface for sending emails as the current implementation.
  • The implementation should determine which email service to use based on the value of the SENDGRID_API_KEY environment variable. If the environment variable is set for local development, the mock implementation should be used; if set for production/remote, the actual SendGrid service should be used.

Mock Implementation

  • Instead of sending emails, the mock should save the HTML email template (including all styling) to a local folder (suggested: tmp/ inside the service-sendgrid package).
  • Ensure the folder is included in .gitignore so downloaded templates are not committed.
  • This enables developers to verify email content and styling locally without using SendGrid.
  • You can explore existing mock implementations such as:

Acceptance Criteria

  • @sthrift/transactional-email-service package provides a minimal, generic interface for sending transactional emails, hiding all proprietary details.
  • Only two concrete implementations are required for this task:
    • @sthrift/transactional-email-service-sendgrid-v3 (or v4) as the SendGrid implementation
    • @sthrift/transactional-email-service-mock as the mock implementation
  • The mock implementation saves HTML emails to tmp/ and never sends to SendGrid.
  • The mock's output folder is ignored by git.
  • The @sthrift/api package determines which implementation is registered at app startup via environment variables.
  • The system remains fully compatible with existing logic and can easily swap new providers in the future (e.g., Azure Communication Services).
  • The implementation does not need to replicate SendGrid exactly, only provide local verification for email templates.
  • API and integration are unchanged for consumers of the package.

Related Code & References


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Summary by Sourcery

Introduce a provider-agnostic transactional email facade with SendGrid and local mock implementations, wire it into the API and event-handling pipeline, and emit an integration event when reservation requests are created to notify listing owners by email.

New Features:

  • Add generic TransactionalEmailService interface and shared template utilities to support pluggable email providers.
  • Provide SendGrid-based and local mock implementations of the transactional email service, loading JSON templates and rendering HTML emails.
  • Emit a ReservationRequestCreated integration event from the reservation request aggregate to drive downstream notifications.
  • Handle ReservationRequestCreated events to send notification emails to listing owners using the transactional email service.

Enhancements:

  • Wire transactional email service selection into the API startup context and expose it via ApiContextSpec for downstream consumers.
  • Refine reservation request creation semantics to capture key IDs for events and adjust tests to use richer listing and user mocks.
  • Add test utilities for common Mongoose/Domain mocks and refactor persistence read-repository tests to use dedicated data sources and converters.
  • Extend conversation read-repository tests and feature specs to cover new query methods, error handling paths, and factory function behavior.
  • Adjust UI date handling for listing reservations to work directly with date values instead of numeric coercion.
  • Tidy various package scripts and configs, including mock payment server build steps and context/event exports.

Build:

  • Introduce new transactional email service packages (core interface, SendGrid implementation, and mock) with their own build, lint, and TypeScript configurations, and register them as workspace dependencies across relevant packages.
  • Update mock-payment-server build script and asset copying behavior to align with the compiled output layout.

Tests:

  • Expand domain and persistence tests around reservation request creation, repository behavior, and event-driven flows using new mocks and helpers.
  • Add comprehensive scenario coverage for conversation read-repository, including composite lookups, invalid IDs, and factory function usage.

Copilot AI and others added 2 commits November 20, 2025 14:47
…reated event

Co-authored-by: rohit-r-kumar <175348946+rohit-r-kumar@users.noreply.github.com>
Co-authored-by: rohit-r-kumar <175348946+rohit-r-kumar@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor service-sendgrid package for local development mock Refactor email service to pluggable facade architecture with mock for local development Nov 20, 2025
Copilot AI requested a review from rohit-r-kumar November 20, 2025 15:01
@rohit-r-kumar rohit-r-kumar marked this pull request as ready for review November 20, 2025 17:10
@jason-t-hankins jason-t-hankins self-requested a review November 21, 2025 18:55
@rohit-r-kumar
Copy link
Contributor

@sourcery-ai review

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 21, 2025

Reviewer's Guide

Introduces a provider-agnostic transactional email facade with SendGrid and filesystem-backed mock implementations, wires the email service into the API and event-handling stack to notify listing owners when reservation requests are created, and refactors persistence tests/utilities (especially reservation request and conversation read repositories) to use dedicated data sources, converters, and shared mock helpers.

Sequence diagram for reservation request email notification via transactional email service

sequenceDiagram
  actor ReserverUser
  participant UI as UiFrontend
  participant Api as ApiFunction
  participant AppServices as ApplicationServices
  participant Domain as ReservationRequestAggregate
  participant EventBus as EventBusInstance
  participant Handler as ReservationRequestCreatedHandler
  participant NotifySvc as ReservationRequestNotificationService
  participant DomainDS as DomainDataSource
  participant EmailSvc as TransactionalEmailService

  ReserverUser->>UI: Create_reservation_request
  UI->>Api: HTTP_request_createReservationRequest
  Api->>AppServices: createReservationRequestCommand
  AppServices->>Domain: ReservationRequest_create
  Domain->>Domain: set_state_requested_and_add_integration_event
  Domain->>EventBus: publish ReservationRequestCreated

  EventBus-->>Handler: invoke_with_ReservationRequestCreated_payload
  Handler->>NotifySvc: sendReservationRequestNotification(reservationRequestId,listingId,reserverId,sharerId,reservationPeriodStart,reservationPeriodEnd)

  Note over NotifySvc,DomainDS: Notification service uses UnitOfWork API on DomainDataSource
  NotifySvc->>DomainDS: PersonalUserUnitOfWork.withTransaction_get_sharer
  NotifySvc->>DomainDS: PersonalUserUnitOfWork.withTransaction_get_reserver
  NotifySvc->>DomainDS: ItemListingUnitOfWork.withTransaction_get_listing

  alt valid_email_and_entities
    NotifySvc->>EmailSvc: sendTemplatedEmail(templateName,recipient,templateData)
    EmailSvc-->>NotifySvc: Promise_resolved
  else missing_data_or_error
    NotifySvc-->>Handler: log_error_and_return
  end

  Handler-->>EventBus: completion
  EventBus-->>AppServices: return
  AppServices-->>Api: result
  Api-->>UI: HTTP_response_success
  UI-->>ReserverUser: success_message
Loading

Class diagram for transactional email facade and reservation request notification flow

classDiagram
  class TransactionalEmailService {
    <<interface>>
    +startUp() Promise~void~
    +shutDown() Promise~void~
    +sendTemplatedEmail(templateName string, recipient EmailRecipient, templateData EmailTemplateData) Promise~void~
  }

  class EmailRecipient {
    +email string
    +name string
  }

  class EmailTemplateData {
  }

  class TemplateUtils {
    -baseTemplateDir string
    +TemplateUtils()
    +loadTemplate(templateName string) EmailTemplate
    +substituteVariables(template string, data EmailTemplateData) string
  }

  class EmailTemplate {
    +fromEmail string
    +subject string
    +body string
  }

  class ServiceTransactionalEmailSendGrid {
    -templateUtils TemplateUtils
    +ServiceTransactionalEmailSendGrid()
    +startUp() Promise~void~
    +shutDown() Promise~void~
    +sendTemplatedEmail(templateName string, recipient EmailRecipient, templateData EmailTemplateData) Promise~void~
  }

  class ServiceTransactionalEmailMock {
    -outputDir string
    -templateUtils TemplateUtils
    +ServiceTransactionalEmailMock()
    +startUp() Promise~void~
    +shutDown() Promise~void~
    +sendTemplatedEmail(templateName string, recipient EmailRecipient, templateData EmailTemplateData) Promise~void~
    -createEmailHtml(recipient EmailRecipient, subject string, from string, bodyHtml string) string
    -escapeHtml(text string) string
  }

  class ReservationRequestCreatedProps {
    +reservationRequestId string
    +listingId string
    +reserverId string
    +sharerId string
    +reservationPeriodStart Date
    +reservationPeriodEnd Date
  }

  class ReservationRequestCreated {
  }

  class ReservationRequestNotificationService {
    -domainDataSource any
    -emailService TransactionalEmailService
    +ReservationRequestNotificationService(domainDataSource any, emailService TransactionalEmailService)
    +sendReservationRequestNotification(reservationRequestId string, listingId string, reserverId string, sharerId string, reservationPeriodStart Date, reservationPeriodEnd Date) Promise~void~
  }

  class ReservationRequest {
    -isNew boolean
    -visa ReservationRequestVisa
    -_listingId string
    -_reserverId string
    -_sharerId string
    +create(props ReservationRequestProps, passport Passport) ReservationRequest
    +request() void
    +addIntegrationEvent(eventType any, payload any) void
  }

  class DomainDataSource {
  }

  TransactionalEmailService <|.. ServiceTransactionalEmailSendGrid
  TransactionalEmailService <|.. ServiceTransactionalEmailMock

  TemplateUtils --> EmailTemplate
  ServiceTransactionalEmailSendGrid --> TemplateUtils
  ServiceTransactionalEmailMock --> TemplateUtils

  ReservationRequestCreated --* ReservationRequestCreatedProps
  ReservationRequestNotificationService --> TransactionalEmailService
  ReservationRequestNotificationService --> DomainDataSource

  ReservationRequest ..> ReservationRequestCreated : emits
  ReservationRequest ..> ReservationRequestCreatedProps

  EmailRecipient ..> EmailTemplateData
Loading

File-Level Changes

Change Details Files
Introduce generic transactional email service interface and SendGrid/mock implementations and integrate them into API and context.
  • Add @cellix/transactional-email-service core package defining TransactionalEmailService, EmailRecipient, EmailTemplateData, and TemplateUtils for loading/substituting JSON-based templates.
  • Add @cellix/transactional-email-service-sendgrid package implementing ServiceTransactionalEmailSendGrid using @sendgrid/mail and TemplateUtils, configured via SENDGRID_API_KEY.
  • Add @cellix/transactional-email-service-mock package implementing ServiceTransactionalEmailMock that renders templates and writes full HTML emails with metadata into tmp/emails for local verification.
  • Wire new email service packages into apps/api and @sthrift/context-spec package.json dependencies and ApiContextSpec, exposing emailService on the API context.
packages/cellix/transactional-email-service/package.json
packages/cellix/transactional-email-service/src/transactional-email-service.ts
packages/cellix/transactional-email-service/src/template-utils.ts
packages/cellix/transactional-email-service/src/index.ts
packages/cellix/transactional-email-service/tsconfig.json
packages/cellix/transactional-email-service-sendgrid/package.json
packages/cellix/transactional-email-service-sendgrid/src/service-transactional-email-sendgrid.ts
packages/cellix/transactional-email-service-sendgrid/src/index.ts
packages/cellix/transactional-email-service-sendgrid/tsconfig.json
packages/cellix/transactional-email-service-mock/package.json
packages/cellix/transactional-email-service-mock/src/service-transactional-email-mock.ts
packages/cellix/transactional-email-service-mock/src/index.ts
packages/cellix/transactional-email-service-mock/tsconfig.json
apps/api/package.json
packages/sthrift/context-spec/package.json
packages/sthrift/context-spec/src/index.ts
Register the transactional email service in the API infrastructure, pass it to event handlers, and extend event handler package to depend on it.
  • Extend API infrastructure initialization to register ServiceTransactionalEmailSendGrid in non-development and ServiceTransactionalEmailMock in development alongside existing messaging and payment services.
  • Resolve the concrete email service from the service registry in the API context factory and pass it to RegisterEventHandlers; include emailService in the returned context object.
  • Update @sthrift/event-handler to depend on the transactional email service, modify RegisterEventHandlers and RegisterIntegrationEventHandlers to accept an emailService parameter, and forward it to integration handlers.
apps/api/src/index.ts
apps/api/package.json
packages/sthrift/event-handler/package.json
packages/sthrift/event-handler/src/handlers/index.ts
packages/sthrift/event-handler/src/handlers/integration/index.ts
Emit a ReservationRequestCreated integration event from the ReservationRequest aggregate and handle it with a notification service that sends an email to the listing owner.
  • Augment ReservationRequest aggregate to track listing, reserver, and sharer IDs when created and to emit a ReservationRequestCreated integration event when state is set to REQUESTED, removing the previous isNew guard and adding ID validation.
  • Define ReservationRequestCreated domain event type, export events from domain index, and extend DomainDataSource typing/comment for event-driven access.
  • Implement ReservationRequestNotificationService that uses DomainDataSource UoWs with a system passport to load sharer, reserver, and listing, derive the sharer email, and invoke emailService.sendTemplatedEmail with the reservation-request-notification template, handling fallbacks and logging errors without throwing.
  • Register a ReservationRequestCreated integration handler that wires EventBusInstance to ReservationRequestNotificationService via RegisterReservationRequestCreatedHandler and uses the transactional email service passed from the API.
  • Adjust reservation-request repository tests to construct listing/reserver mocks with the necessary sharer.id to satisfy new event requirements.
packages/sthrift/domain/src/domain/contexts/reservation-request/reservation-request/reservation-request.ts
packages/sthrift/domain/src/domain/events/types/reservation-request-created.ts
packages/sthrift/domain/src/domain/events/index.ts
packages/sthrift/domain/src/domain/index.ts
packages/sthrift/domain/src/index.ts
packages/sthrift/event-handler/src/handlers/integration/reservation-request-created.ts
packages/sthrift/event-handler/src/services/reservation-request-notification-service.ts
packages/sthrift/persistence/src/datasources/domain/reservation-request/reservation-request/reservation-request.repository.test.ts
packages/sthrift/domain/src/domain/contexts/reservation-request/reservation-request/reservation-request.test.ts
assets/email-templates/reservation-request-notification.json
Refactor reservation request read-repository tests to use a mocked ReservationRequestDataSource and converter instead of directly mocking Mongoose models.
  • Replace ad-hoc Mongoose model mocks and ObjectId helpers in reservation-request.read-repository tests with mocks of ReservationRequestDataSourceImpl and ReservationRequestConverter, using deterministic TEST_IDS, document factories, and converters to verify repository query filters and calls.
  • Introduce helper functions to construct mock ModelsContext, passport, reservation request documents, and listing documents and to map documents to domain entities, focusing tests on repository behavior rather than Mongoose query chaining.
  • Update scenarios for getting all, by ID, by reserver, active/past by reserver with listing/sharer, listing requests by sharer (via aggregate pipeline), active by reserver+listing, overlapping active requests, and active by listing to assert against data source method arguments and converter invocations.
packages/sthrift/persistence/src/datasources/readonly/reservation-request/reservation-request/reservation-request.read-repository.test.ts
Centralize persistence test mock helpers and extend conversation read-repository tests to cover new query and factory behavior.
  • Create a shared mock-data-helpers module that provides createValidObjectId, makePassport, makeMockUser, makeMockListing, makeMockConversation, and createMockQuery utilities for persistence tests.
  • Refactor conversation.read-repository tests to consume the shared helpers, simplifying local helper definitions and using createMockQuery to emulate Mongoose query behavior.
  • Add new BDD feature scenarios and test implementations for getBySharerReserverListing (success, no match, empty/partial params, invalid ObjectId), additional error-handling for getByUser with invalid ObjectId, and verifying the getConversationReadRepository factory returns a ConversationReadRepositoryImpl.
  • Update the conversation read-repository feature file to describe the new scenarios.
packages/sthrift/persistence/src/test-utilities/mock-data-helpers.ts
packages/sthrift/persistence/src/datasources/readonly/conversation/conversation/conversation.read-repository.test.ts
packages/sthrift/persistence/src/datasources/readonly/conversation/conversation/features/conversation.read-repository.feature
Adjust ancillary packages, scripts, and UI code to align with the new architecture and test utilities.
  • Update mock-payment-server build script to execute the built copy-assets script and adjust copy-assets paths so iframe.min.js is copied from src to the correct dist location; tidy its package.json dependencies and scripts.
  • Simplify rest handler principal hints by removing unnecessary biome-ignore comments.
  • Fix UI date handling for reservation periods in listing-information component by passing raw reservationPeriodStart/End values directly into dayjs rather than coercing with Number, avoiding incorrect timestamp conversions.
  • Add transactional-email-service dependency to @sthrift/event-handler and adjust tsconfig/watch script typo in @sthrift/context-spec.
  • Remove the legacy @sthrift/service-sendgrid package and its configuration files, relying on the new transactional email facade packages instead.
packages/cellix/mock-payment-server/package.json
packages/cellix/mock-payment-server/src/copy-assets.ts
packages/sthrift/rest/src/index.ts
apps/ui-sharethrift/src/components/layouts/home/components/view-listing/listing-information/listing-information.tsx
packages/sthrift/event-handler/package.json
packages/sthrift/context-spec/package.json
packages/sthrift/service-sendgrid/.gitignore
packages/sthrift/service-sendgrid/package.json
packages/sthrift/service-sendgrid/src/get-email-template.ts
packages/sthrift/service-sendgrid/src/index.ts
packages/sthrift/service-sendgrid/src/sendgrid.ts
packages/sthrift/service-sendgrid/tsconfig.json
packages/sthrift/service-sendgrid/turbo.json

Assessment against linked issues

Issue Objective Addressed Explanation
#223 Introduce a generic transactional email facade package with a minimal interface, plus concrete SendGrid and mock implementations, where the mock writes full HTML emails to a tmp directory ignored by git; wire the facade into the API so @sthrift/api selects the implementation at startup based on environment variables, while keeping the consumer-facing API/integration effectively unchanged.
#223 Create and wire a ReservationRequestCreated event that, when a reservation request is created, triggers an integration handler which fetches the listing, sharer, and reserver via the domain data source and uses the transactional-email-service (passed in as a dependency) to send a notification email to the listing owner that their listing has been reserved.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • The new ReservationRequestCreated event type is not exported from the @sthrift/domain barrel (events/index.ts and/or domain/index.ts), but the handler imports it as ReservationRequestCreated from '@sthrift/domain', which will fail unless you add the appropriate re-exports.
  • ServiceTransactionalEmailSendGrid throws immediately if SENDGRID_API_KEY is missing, but the API currently selects the implementation based only on NODE_ENV; consider choosing the provider based on the presence of the API key (or deferring SendGrid instantiation) so non-dev environments without a key don’t crash at startup.
  • Both the mock and SendGrid transactional email services duplicate template loading and variable substitution logic; consider extracting this into a shared utility to keep behavior consistent and reduce maintenance overhead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new ReservationRequestCreated event type is not exported from the @sthrift/domain barrel (events/index.ts and/or domain/index.ts), but the handler imports it as `ReservationRequestCreated` from '@sthrift/domain', which will fail unless you add the appropriate re-exports.
- ServiceTransactionalEmailSendGrid throws immediately if SENDGRID_API_KEY is missing, but the API currently selects the implementation based only on NODE_ENV; consider choosing the provider based on the presence of the API key (or deferring SendGrid instantiation) so non-dev environments without a key don’t crash at startup.
- Both the mock and SendGrid transactional email services duplicate template loading and variable substitution logic; consider extracting this into a shared utility to keep behavior consistent and reduce maintenance overhead.

## Individual Comments

### Comment 1
<location> `packages/sthrift/persistence/src/datasources/readonly/reservation-request/reservation-request/reservation-request.read-repository.test.ts:106` </location>
<code_context>
+  } as unknown as Domain.Contexts.ReservationRequest.ReservationRequest.ReservationRequestEntityReference;
+}
+
+test.for(feature, ({ Scenario, BeforeEachScenario, Background }) => {
+  let models: ModelsContext;
+  let passport: Domain.Passport;
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring these tests to introduce shared helpers, focus more on observable behavior than internal calls, and centralize mock setup to reduce duplication and verbosity.

You can keep the new data‑source/converter integration but still reduce complexity and duplication.

Key issues:
- Many scenarios re‑encode the same filters and `ObjectId` constructions.
- Most scenarios assert internal calls instead of returned behavior.
- Constructor mocking for `ReservationRequestDataSourceImpl` and `ReservationRequestConverter` adds boilerplate.

Concrete suggestions:

1) Extract reusable helpers for filters and ObjectIds

Right now you have lots of repeated:

```ts
new MongooseSeedwork.ObjectId(TEST_IDS.USER_1)
new MongooseSeedwork.ObjectId(TEST_IDS.LISTING_1)
```

and inline filters like:

```ts
{ reserver: new MongooseSeedwork.ObjectId(TEST_IDS.USER_1) }
```

You can centralize those into helpers to both:
- reduce repetition, and
- keep the scenarios more declarative.

Example:

```ts
const TEST_OBJECT_IDS = {
  USER_1: new MongooseSeedwork.ObjectId(TEST_IDS.USER_1),
  LISTING_1: new MongooseSeedwork.ObjectId(TEST_IDS.LISTING_1),
  SHARER_1: new MongooseSeedwork.ObjectId(TEST_IDS.SHARER_1),
  RESERVATION_1: new MongooseSeedwork.ObjectId(TEST_IDS.RESERVATION_1),
} as const;

function makeActiveReserverFilter(userId: string) {
  return {
    reserver: new MongooseSeedwork.ObjectId(userId),
    state: { $in: ['Accepted', 'Requested'] },
  };
}

function makePastReserverFilter(userId: string) {
  return {
    reserver: new MongooseSeedwork.ObjectId(userId),
    state: { $in: ['Cancelled', 'Closed', 'Rejected'] },
  };
}
```

Then your expectations become shorter and more readable:

```ts
expect(mockDataSource.find).toHaveBeenCalledWith(
  makeActiveReserverFilter(TEST_IDS.USER_1),
  { populateFields: ['listing', 'reserver'] },
);

expect(mockDataSource.find).toHaveBeenCalledWith(
  { reserver: TEST_OBJECT_IDS.USER_1 },
  undefined,
);
```

This removes nested `new MongooseSeedwork.ObjectId(...)` noise across scenarios and makes it easier to see the intent of each test.

2) Add small assertion helpers instead of repeating `toHaveBeenCalledWith`

You can also wrap repeated patterns like `find` / `findOne` expectations:

```ts
function expectFindCalledWith(filter: unknown, options?: unknown) {
  expect(mockDataSource.find).toHaveBeenCalledWith(filter, options);
}

function expectFindOneCalledWith(filter: unknown, options?: unknown) {
  expect(mockDataSource.findOne).toHaveBeenCalledWith(filter, options);
}

function expectConverterCalledWithDoc(doc: Models.ReservationRequest.ReservationRequest) {
  expect(mockConverter.toDomain).toHaveBeenCalledWith(doc, passport);
}
```

Then scenarios reduce to:

```ts
Then('I should receive an array of ReservationRequest entities', () => {
  expectFindCalledWith({}, undefined);
});

And('the array should contain all reservation requests', () => {
  expectConverterCalledWithDoc(mockReservationRequestDoc);
});
```

This keeps all your current assertions but avoids repeating the raw call signatures everywhere.

3) Prefer behavior assertions in most scenarios; keep filter checks in a few

Today almost every scenario asserts the exact filter passed into the data source. This is useful in a couple of focused tests, but in most scenarios it is enough (and less brittle) to assert on the returned domain entities.

For example, instead of:

```ts
When('I call getByReserverId with "user-1"', async () => {
  await repository.getByReserverId(TEST_IDS.USER_1);
});

Then('I should receive an array of ReservationRequest entities', () => {
  expect(mockDataSource.find).toHaveBeenCalledWith(
    { reserver: new MongooseSeedwork.ObjectId(TEST_IDS.USER_1) },
    undefined,
  );
});

And('the array should contain reservation requests where reserver is "user-1"', () => {
  expect(mockConverter.toDomain).toHaveBeenCalledWith(mockReservationRequestDoc, passport);
});
```

capture the result and assert behavior:

```ts
let result: Domain.Contexts.ReservationRequest.ReservationRequest.ReservationRequestEntityReference[];

When('I call getByReserverId with "user-1"', async () => {
  result = await repository.getByReserverId(TEST_IDS.USER_1);
});

Then('I should receive an array of ReservationRequest entities', () => {
  expect(Array.isArray(result)).toBe(true);
  expect(result.length).toBeGreaterThan(0);
});

And('the array should contain reservation requests where reserver is "user-1"', () => {
  expect(result.every(r => String(r.reserver) === TEST_IDS.USER_1)).toBe(true);
});
```

Then keep only 1–2 dedicated scenarios that verify filter construction via the `expectFindCalledWith`/`expectFindOneCalledWith` helpers. This preserves your functional coverage but shifts most tests away from internal wiring.

4) Simplify constructor mocking by using a simple fake instance

You don’t have to mock the constructors for every scenario; you can construct the repository with a simple fake that matches the data source interface.

Instead of:

```ts
// Mock the constructors
vi.mocked(ReservationRequestDataSourceImpl).mockImplementation(
  () => mockDataSource as unknown as InstanceType<typeof ReservationRequestDataSourceImpl>
);
vi.mocked(ReservationRequestConverter).mockImplementation(
  () => mockConverter as unknown as ReservationRequestConverter
);

repository = new ReservationRequestReadRepositoryImpl(models, passport);
```

you can expose injection points in the tests only (without touching production) by constructing the data source/convertor directly when instantiating the repository (if the repository constructor allows it), or by wrapping the repository creation in a helper:

```ts
function makeRepositoryWithMocks() {
  const dataSource = mockDataSource as unknown as ReservationRequestDataSourceImpl;
  const converter = mockConverter as unknown as ReservationRequestConverter;

  // If your repository allows passing collaborators directly:
  return new ReservationRequestReadRepositoryImpl(models, passport, dataSource, converter);
}
```

If the constructor signature doesn’t currently support this, you can keep the constructor mocking but still wrap it in a helper to avoid repeating the same `mockImplementation` logic in `BeforeEachScenario`.

Either way, centralizing this setup into `makeRepositoryWithMocks()` (or similar) makes each scenario focus on “when I call X” and “then I get Y”, instead of on how the mocks are wired.

5) Reduce repeated converter assertions

Right now almost every scenario asserts:

```ts
expect(mockConverter.toDomain).toHaveBeenCalledWith(mockReservationRequestDoc, passport);
```

You can:
- have 1–2 scenarios dedicated to verifying the converter integration (called once per doc, with the right passport), and
- in other scenarios, assert only on the returned entities (as shown in point 3), trusting that the converter integration is already covered.

If you want to keep some safety in the other scenarios, you can use a lighter check:

```ts
expect(mockConverter.toDomain).toHaveBeenCalled();
```

instead of repeating the full argument structure.

These changes keep the new data‑source + converter design, but make the test suite less verbose, less implementation‑coupled, and easier to maintain.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@jasonmorais jasonmorais left a comment

Choose a reason for hiding this comment

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

Basically fixing the event integration and then testing it works properly along with fixing the other commetns i left.

@jasonmorais jasonmorais marked this pull request as draft December 18, 2025 20:20
@rohit-r-kumar rohit-r-kumar marked this pull request as ready for review December 22, 2025 16:14
Copy link
Contributor

@jasonmorais jasonmorais left a comment

Choose a reason for hiding this comment

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

Main thing is being careful of copilot suggestions. The aggregate file for reservation request keeps having patterns broken in it. Please try to follow cellix and our other files and the way they access information

Copy link
Contributor

@jasonmorais jasonmorais left a comment

Choose a reason for hiding this comment

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

Really just changes that need to be made in reservation-request.ts. Want to make sure we are following our common practices for aggregate files there/ After that we should be good.

Rohit Kumar and others added 7 commits January 6, 2026 21:10
- Extended SNYK-JS-QS-14724253 expiration from 2026-01-19 to 2026-07-19
- This vulnerability was set to expire in 11 days, causing build failures
- QS is a transitive dependency in express, Apollo Server, and Docusaurus
- Not exploitable in current usage pattern
- Extended for 6 months (per security best practices)
Copy link
Contributor

@jasonmorais jasonmorais left a comment

Choose a reason for hiding this comment

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

One last small comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor @sthrift/service-sendgrid package to provide a ServiceSendGrid facade with mock for local development

5 participants