Skip to content

Conversation

vitalii-t
Copy link
Collaborator

@vitalii-t vitalii-t commented Aug 26, 2025

Motivation

Next steps implementing Mailtrap API - Contact Imports

Changes

  • Added Contact Imports interface and implementation
  • Added request and response models
  • Covered with unit tests
  • Minor tweaks to incorrect asserts in existing tests (flipped params)

Summary by CodeRabbit

  • New Features
    • Added bulk contact import (up to 50,000), with custom fields and list include/exclude support; existing contacts auto-updated by email.
    • Added ability to fetch import status and result counts (created, updated, over-limit).
  • Documentation
    • Added Java example demonstrating contact import and status retrieval.
  • Tests
    • Added unit tests for creating, validating, and retrieving contact imports.
    • Fixed assertion order in multiple tests and added JSON fixtures for import scenarios.

Copy link

coderabbitai bot commented Aug 26, 2025

Walkthrough

Introduces Contact Imports feature: new API interface and implementation, request/response models, client wiring, example usage, tests, and JSON fixtures. Also adjusts constructor signatures to include the new API, fixes assert argument orders in several tests, and adds a base test field.

Changes

Cohort / File(s) Summary
Contact Imports API
src/main/java/io/mailtrap/api/contactimports/ContactImports.java, src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java
Adds interface with methods to create and fetch contact imports; implementation posts/gets against /api/accounts/{accountId}/contacts/imports[...] with validation and HTTP client usage.
Client wiring
src/main/java/io/mailtrap/client/api/MailtrapContactsApi.java, src/main/java/io/mailtrap/factory/MailtrapClientFactory.java
Wires ContactImports into Contacts API surface; updates constructor and factory to instantiate and pass ContactImportsImpl with CustomValidator.
Request models
src/main/java/io/mailtrap/model/request/contactimports/Contact.java, src/main/java/io/mailtrap/model/request/contactimports/ImportContactsRequest.java
Adds DTOs for import payload: Contact (email, fields, include/exclude list IDs) and ImportContactsRequest (contacts with @Size(max=50_000)).
Response models
src/main/java/io/mailtrap/model/response/contactimports/ImportContactResponse.java, src/main/java/io/mailtrap/model/response/contactimports/ContactImportResponse.java, src/main/java/io/mailtrap/model/response/contactimports/ContactImportStatus.java
Adds response DTOs for import creation and retrieval, plus enum for status with Jackson serialization/deserialization.
Example
examples/java/io/mailtrap/examples/contactimports/ContactImports.java
New Java example demonstrating creating an import and fetching its status using placeholder token and IDs.
Contact Imports tests
src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java, src/test/resources/api/contactimports/*
Adds unit tests for create/retrieve and validation failure; introduces JSON fixtures for request and responses.
Test adjustments
src/test/java/io/mailtrap/api/inboxes/InboxesImplTest.java, src/test/java/io/mailtrap/api/billing/BillingImplTest.java, src/test/java/io/mailtrap/api/accountaccesses/AccountAccessesImplTest.java, src/test/java/io/mailtrap/testutils/BaseTest.java
Swaps assertEquals argument order to (expected, actual); adds importId field to BaseTest.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant App as App (Example)
    participant Client as MailtrapClientFactory
    participant API as MailtrapContactsApi
    participant CI as ContactImportsImpl
    participant HTTP as HTTP API (/api/accounts/.../contacts/imports)

    App->>Client: createMailtrapClient(config)
    Client-->>App: MailtrapClient (with Contacts API)

    Note over App,API: Create contacts import
    App->>API: contacts().contactImports().importContacts(accountId, request)
    API->>CI: importContacts(accountId, request)
    CI->>CI: validate request
    CI->>HTTP: POST /contacts/imports (request)
    HTTP-->>CI: 200 ImportContactResponse
    CI-->>API: ImportContactResponse
    API-->>App: ImportContactResponse (id, status)

    Note over App,API: Fetch import status
    App->>API: contacts().contactImports().getContactImport(accountId, importId)
    API->>CI: getContactImport(accountId, importId)
    CI->>HTTP: GET /contacts/imports/{importId}
    HTTP-->>CI: 200 ContactImportResponse
    CI-->>API: ContactImportResponse
    API-->>App: ContactImportResponse (id, status, counts)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

I hop through fields of JSON bright,
New contacts queue, then take to flight.
Imports start, their status sings—
Created, started, finished things.
With tidy tests and models neat,
This carrot-coded change is sweet. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/contact-imports

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@vitalii-t vitalii-t requested review from vittorius and mklocek August 26, 2025 23:18
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (25)
src/test/java/io/mailtrap/testutils/BaseTest.java (1)

21-21: Nit: Prefer clearer naming and wrapper type for consistency

Line 21 introduces contactId as a primitive long. Most IDs in this class use the Long wrapper. Consider:

  • Renaming to contactImportId to avoid confusion with an individual Contact’s ID (you already have contactUUID above).
  • Using Long for consistency with other ID fields and typical assertEquals usage.
src/test/java/io/mailtrap/api/accountaccesses/AccountAccessesImplTest.java (1)

69-71: Good fix: assertEquals now uses (expected, actual)

Switching to expected-first improves failure messages. Consider applying the same convention to nearby assertions for consistency (e.g., Line 51 getId(), Line 71 resourceId).

src/test/java/io/mailtrap/api/inboxes/InboxesImplTest.java (1)

73-73: LGTM: corrected expected-first ordering across string assertions

These changes improve readability and diagnostics when tests fail. As a minor follow-up, several numeric ID assertions in this file still use actual-first (e.g., Lines 72, 82, 92, 102, 113, 123, 133, 143, 153, 164). Aligning those to (expected, actual) would make the file fully consistent.

Also applies to: 83-83, 93-93, 103-104, 114-114, 124-124, 134-134, 144-144, 154-154, 165-165

src/main/java/io/mailtrap/model/response/contactimports/ContactImportStatus.java (1)

23-32: Harden @JsonCreator to avoid ClassCastException and improve messages

fromValue(Object value) casts to String without type checks. If Jackson ever supplies a non-String token, this can throw a ClassCastException. Make the factory accept String directly, guard null/blank, and keep case-insensitivity.

Apply this diff:

-  @JsonCreator
-  public static ContactImportStatus fromValue(Object value) {
-    for (ContactImportStatus level : ContactImportStatus.values()) {
-      if (level.value.equalsIgnoreCase((String) value)) {
-        return level;
-      }
-    }
-
-    throw new IllegalArgumentException("Unknown value: " + value);
-  }
+  @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+  public static ContactImportStatus fromValue(String value) {
+    if (value == null || value.isBlank()) {
+      throw new IllegalArgumentException("ContactImportStatus value must not be null/blank");
+    }
+    for (ContactImportStatus status : ContactImportStatus.values()) {
+      if (status.value.equalsIgnoreCase(value)) {
+        return status;
+      }
+    }
+    throw new IllegalArgumentException("Unknown ContactImportStatus: " + value);
+  }
src/main/java/io/mailtrap/model/request/contactimports/Contact.java (3)

9-11: Consider omitting nulls from JSON payloads for forward compatibility

Contacts often have optional attributes. Emitting nulls can bloat requests and cause stricter backends to reject payloads. Recommend adding @JsonInclude(Include.NON_NULL).

Apply this diff:

 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import java.util.List;
 import java.util.Map;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
 @Getter
-@AllArgsConstructor
+@AllArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
 public class Contact {

13-16: Validate email early to prevent API roundtrips

If the API requires a valid, non-empty email, add bean validation to fail fast before HTTP calls.

Apply this diff (uses Jakarta Validation; switch to javax.validation.* if your project hasn’t migrated):

 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import java.util.List;
 import java.util.Map;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
 
 @Getter
 @AllArgsConstructor
 @JsonInclude(JsonInclude.Include.NON_NULL)
 public class Contact {
 
-  private String email;
+  @NotBlank
+  @Email
+  private String email;
 
   private Map<String, Object> fields;

17-21: JSON mapping looks correct; consider handling empty vs null lists

Snake_case mappings for included/excluded list IDs are correct. If the server treats null and empty arrays differently, ensure callers pass empty lists when appropriate or normalize in a builder/factory to avoid nulls.

src/main/java/io/mailtrap/client/api/MailtrapContactsApi.java (1)

15-20: Constructor usage verified; consider null‐safety improvements

I’ve confirmed that the only direct instantiation of MailtrapContactsApi is in MailtrapClientFactory.createContactsApi (and transitively via createMailtrapClient). No other raw new MailtrapContactsApi( calls remain in the codebase—so consumers wired through the factory won’t break.

As suggested, you can improve fail‐fast behavior by annotating the required fields with Lombok’s @NonNull. This is an optional refactor but helps catch misconfiguration early:

 import lombok.RequiredArgsConstructor;
+import lombok.NonNull;
 import lombok.experimental.Accessors;
 
 @Getter
 @Accessors(fluent = true)
 @RequiredArgsConstructor
 public class MailtrapContactsApi {
-    private final ContactLists contactLists;
-    private final Contacts contacts;
-    private final ContactImports contactImports;
+    private final @NonNull ContactLists contactLists;
+    private final @NonNull Contacts contacts;
+    private final @NonNull ContactImports contactImports;
 }

No further constructor‐related fixes are required.

src/main/java/io/mailtrap/model/request/contactimports/ImportContactsRequest.java (2)

4-9: Validate nested items and guard against null/empty payloads

Today the list can be null or empty and still pass client-side validation; the server will likely reject it later. Also, nested Contact objects won’t be validated without @Valid.

Please confirm the API allows an empty contacts array. If not, consider the following changes:

 import io.mailtrap.model.AbstractModel;
 import java.util.List;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;

 import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Getter;

@@
-  @Size(max = 50_000, message = "Maximum 50000 contacts per request")
-  private List<Contact> contacts;
+  @NotNull(message = "Contacts list must be provided")
+  @Size(min = 1, max = 50_000, message = "Contacts must contain 1 to 50000 items")
+  @Valid
+  private List<Contact> contacts;

Follow-up: The Contact model currently lacks field-level validation (e.g., @Email, @notblank on email). If the server requires these, add them in src/main/java/io/mailtrap/model/request/contactimports/Contact.java (Lines 8-21).

Also applies to: 14-15


4-9: Prevent accidental mutation by exposing an unmodifiable view

The current Lombok-generated getter returns the mutable List. Downstream code can mutate the request after validation/serialization, making debugging harder.

Apply a custom getter and import Collections to return an unmodifiable view (null-safe):

 import io.mailtrap.model.AbstractModel;
 import java.util.List;
+import java.util.Collections;

@@
-@Getter
-@AllArgsConstructor
+@Getter
+@AllArgsConstructor
 public class ImportContactsRequest extends AbstractModel {

@@
   private List<Contact> contacts;
 
+  public List<Contact> getContacts() {
+    return contacts == null ? null : Collections.unmodifiableList(contacts);
+  }

Alternative: switch to @builder + @Singular to construct an unmodifiable list at build time and drop @AllArgsConstructor.

Also applies to: 10-17

src/main/java/io/mailtrap/model/response/contactimports/ImportContactsResponse.java (1)

3-7: Add ignoreUnknown for forward compatibility

Mailtrap responses may gain new fields over time. Ignoring unknowns avoids unexpected deserialization failures.

 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import lombok.Data;
 
-@Data
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class ImportContactsResponse {
examples/java/io/mailtrap/examples/contactimports/ContactImports.java (3)

7-10: Remove unused imports

Tidies the example and avoids IDE warnings.

-import io.mailtrap.model.request.contacts.UpdateContact;
-import io.mailtrap.model.request.contacts.UpdateContactRequest;
-
-import java.util.Collections;

16-21: Prefer sourcing TOKEN and ACCOUNT_ID from env/system properties for runnable examples

Keeps secrets out of source and makes the example copy-paste runnable.

-  private static final String TOKEN = "<YOUR MAILTRAP TOKEN>";
-  private static final long ACCOUNT_ID = 1L;
+  private static final String TOKEN = System.getenv().getOrDefault("MAILTRAP_TOKEN", "<YOUR MAILTRAP TOKEN>");
+  private static final long ACCOUNT_ID = Long.parseLong(System.getenv().getOrDefault("MAILTRAP_ACCOUNT_ID", "1"));

37-40: Rename variable to reflect operation

The second call fetches the created import; rename for clarity.

-    var updateResponse = client.contactsApi().contactImports()
-        .getContactImport(ACCOUNT_ID, createResponse.getId());
+    var getResponse = client.contactsApi().contactImports()
+        .getContactImport(ACCOUNT_ID, createResponse.getId());
 
-    System.out.println(updateResponse);
+    System.out.println(getResponse);
src/main/java/io/mailtrap/api/contactimports/ContactImports.java (2)

8-16: Tighten Javadoc wording

Minor clarity/punctuation: make the rate limit sentence complete and adjust the return type description.

   /**
    * Import contacts in bulk with support for custom fields and list management.
    * Existing contacts with matching email addresses will be updated automatically.
-   * Up to 50,000 contacts per request
+   * Up to 50,000 contacts per request.
    *
    * @param accountId unique account ID
    * @param request   request body
-   * @return contact data
+   * @return contact import metadata
    */

19-26: Update parameter name for clarity in interface and implementation

To align the Javadoc and symbol names with the fact that this ID refers to a contact import (not a contact), please optionally refactor the method signatures and Javadoc:

• In src/main/java/io/mailtrap/api/contactimports/ContactImports.java

  • Change the second parameter name from contactId to importId
  • Update the Javadoc tag and return description accordingly
  /**
   * Get Contact Import
   *
   * @param accountId unique account ID
  -* @param contactId unique Contact Import ID
  -* @return contact data
  +* @param importId unique Contact Import ID
  +* @return contact import metadata
   */
  -ImportContactsResponse getContactImport(long accountId, long contactId);
  +ImportContactsResponse getContactImport(long accountId, long importId);

• In src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java

  • Mirror the signature rename (contactIdimportId) and adjust any in-method references or Javadoc

Call sites (in tests and examples) invoke by position and continue to pass an ID value (contactId variable or createResponse.getId()), so they won’t break. You may choose to rename local variables in tests/examples for consistency (e.g. contactIdimportId), but that’s entirely optional.

src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java (5)

45-56: Strengthen assertions for partial response shape

For STARTED status, also assert other counts are null to lock expected contract.

   assertSame(ContactImportStatus.STARTED, importContactsResponse.getStatus());
   assertNull(importContactsResponse.getCreatedContactsCount());
+  assertNull(importContactsResponse.getUpdatedContactsCount());
+  assertNull(importContactsResponse.getContactsOverLimitCount());

58-63: Make validation message assertion resilient to localization/wording changes

Exact string equality is brittle. Assert key fragments instead.

-    assertEquals("Invalid request body. Violations: contacts=Maximum 50000 contacts per request", exception.getMessage());
+    assertTrue(
+        exception.getMessage().contains("Invalid request body.") &&
+        exception.getMessage().contains("contacts=") &&
+        exception.getMessage().contains("50000"),
+        "Unexpected validation message: " + exception.getMessage()
+    );

65-73: Avoid constructing 50k distinct Contact objects; use nCopies to cut test cost

You only need to exceed the size limit; object identity/uniqueness doesn’t matter for @SiZe. This reduces memory and CPU.

-  private List<Contact> generateContacts() {
-    List<Contact> contacts = new ArrayList<>();
-
-    for (int i = 0; i < 50001; i++) {
-      contacts.add(new Contact("stub_contact_%[email protected]".formatted(i), Map.of(), List.of(), List.of()));
-    }
-
-    return contacts;
-  }
+  private List<Contact> generateContacts() {
+    Contact stub = new Contact("[email protected]", Map.of(), List.of(), List.of());
+    // 50_001 > 50_000 to trigger @Size(max = 50_000)
+    return List.copyOf(java.util.Collections.nCopies(50_001, stub));
+  }

53-54: Naming: contactId here represents an importId

The path is /contacts/imports/{id}; this ID is for the import job, not a contact. Consider renaming usages to importId for clarity (and update BaseTest accordingly).

I can prep a follow-up PR renaming the field in BaseTest and its usages in these tests if you want.

Also applies to: 79-84


75-84: Boundary/null cases worth covering

  • Exactly 50,000 contacts should succeed validation.
  • Null request should fail fast with InvalidRequestBodyException (after implementing the null check suggested in the impl).

Sample tests you can add:

@Test
void test_importContacts_exactly_50000_should_pass_validation() {
  var c = new Contact("[email protected]", Map.of(), List.of(), List.of());
  var request = new ImportContactsRequest(java.util.Collections.nCopies(50_000, c));
  // If you want to avoid hitting HTTP, you can just call the validator through the API and expect no exception.
  assertDoesNotThrow(() -> api.importContacts(accountId, request));
}

@Test
void test_importContacts_null_request_should_fail_fast() {
  var ex = assertThrows(InvalidRequestBodyException.class, () -> api.importContacts(accountId, null));
  assertTrue(ex.getMessage().contains("Invalid request body"));
}
src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java (4)

19-22: Add a null request guard for consistent error semantics

Bean Validation may not reliably handle null here; fail fast with InvalidRequestBodyException to keep error type/message consistent with other validation failures.

   @Override
   public ImportContactsResponse importContacts(long accountId, ImportContactsRequest request) {
-
-    validateRequestBodyAndThrowException(request);
+    if (request == null) {
+      throw new io.mailtrap.exception.InvalidRequestBodyException(
+          "Invalid request body. Violations: request=must not be null"
+      );
+    }
+    validateRequestBodyAndThrowException(request);

Note: add the import (see next comment) or fully qualify the class as above.


3-10: Import required if you prefer not to fully-qualify the exception class

Add this import to support the null-guard change cleanly.

 import io.mailtrap.CustomValidator;
 import io.mailtrap.api.apiresource.ApiResourceWithValidation;
 import io.mailtrap.config.MailtrapConfig;
 import io.mailtrap.http.RequestData;
+import io.mailtrap.exception.InvalidRequestBodyException;

23-28: Avoid mixing concatenation with String.format and use numeric specifiers

Slightly cleaner and avoids accidental format surprises.

-    return httpClient.post(
-        String.format(apiHost + "/api/accounts/%s/contacts/imports", accountId),
-        request,
-        new RequestData(),
-        ImportContactsResponse.class
-    );
+    return httpClient.post(
+        String.format("%s/api/accounts/%d/contacts/imports", apiHost, accountId),
+        request,
+        new RequestData(),
+        ImportContactsResponse.class
+    );

31-38: Rename contactId to importId and update format string in getContactImport

To improve clarity—since the second parameter is the import-job ID—please update the method signature, its implementation, and all call sites/tests:

• src/main/java/io/mailtrap/api/contactimports/ContactImports.java

@@ -26,1 +26,1 @@
-  ImportContactsResponse getContactImport(long accountId, long contactId);
+  ImportContactsResponse getContactImport(long accountId, long importId);

• src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java

@@ -32,1 +32,1 @@
-  public ImportContactsResponse getContactImport(long accountId, long contactId) {
+  public ImportContactsResponse getContactImport(long accountId, long importId) {
     return httpClient.get(
-        String.format(apiHost + "/api/accounts/%s/contacts/imports/%s", accountId, contactId),
+        String.format("%s/api/accounts/%d/contacts/imports/%d", apiHost, accountId, importId),
       new RequestData(),
       ImportContactsResponse.class
     );

• src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java

@@ -75,4 +75,6 @@
    @Test
    void test_getContactImport() {
-      ImportContactsResponse contactImport = api.getContactImport(accountId, contactId);
-      assertEquals(contactId, contactImport.getId());
+      // rename contactId → importId for clarity
+      long importId = contactId;
+      ImportContactsResponse contactImport = api.getContactImport(accountId, importId);
+      assertEquals(importId, contactImport.getId());
    }

These changes are confined to a new interface and its tests/examples, so they won’t break existing consumers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 93685cf and b957168.

📒 Files selected for processing (17)
  • examples/java/io/mailtrap/examples/contactimports/ContactImports.java (1 hunks)
  • src/main/java/io/mailtrap/api/contactimports/ContactImports.java (1 hunks)
  • src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java (1 hunks)
  • src/main/java/io/mailtrap/client/api/MailtrapContactsApi.java (2 hunks)
  • src/main/java/io/mailtrap/factory/MailtrapClientFactory.java (2 hunks)
  • src/main/java/io/mailtrap/model/request/contactimports/Contact.java (1 hunks)
  • src/main/java/io/mailtrap/model/request/contactimports/ImportContactsRequest.java (1 hunks)
  • src/main/java/io/mailtrap/model/response/contactimports/ContactImportStatus.java (1 hunks)
  • src/main/java/io/mailtrap/model/response/contactimports/ImportContactsResponse.java (1 hunks)
  • src/test/java/io/mailtrap/api/accountaccesses/AccountAccessesImplTest.java (1 hunks)
  • src/test/java/io/mailtrap/api/billing/BillingImplTest.java (1 hunks)
  • src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java (1 hunks)
  • src/test/java/io/mailtrap/api/inboxes/InboxesImplTest.java (10 hunks)
  • src/test/java/io/mailtrap/testutils/BaseTest.java (1 hunks)
  • src/test/resources/api/contactimports/createContactsImportRequest.json (1 hunks)
  • src/test/resources/api/contactimports/createContactsImportResponse.json (1 hunks)
  • src/test/resources/api/contactimports/getContactsImportResponse.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/main/java/io/mailtrap/api/contactimports/ContactImports.java (1)
examples/java/io/mailtrap/examples/contactimports/ContactImports.java (1)
  • ContactImports (14-42)
examples/java/io/mailtrap/examples/contactimports/ContactImports.java (1)
src/main/java/io/mailtrap/factory/MailtrapClientFactory.java (1)
  • MailtrapClientFactory (29-112)
src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java (5)
src/main/java/io/mailtrap/Constants.java (1)
  • Constants (6-15)
src/main/java/io/mailtrap/exception/InvalidRequestBodyException.java (1)
  • InvalidRequestBodyException (6-12)
src/main/java/io/mailtrap/factory/MailtrapClientFactory.java (1)
  • MailtrapClientFactory (29-112)
src/test/java/io/mailtrap/testutils/BaseTest.java (1)
  • BaseTest (7-22)
src/test/java/io/mailtrap/testutils/TestHttpClient.java (1)
  • TestHttpClient (19-160)
src/main/java/io/mailtrap/model/request/contactimports/ImportContactsRequest.java (2)
src/main/java/io/mailtrap/model/AbstractModel.java (1)
  • AbstractModel (10-29)
src/main/java/io/mailtrap/model/request/contactimports/Contact.java (1)
  • Getter (9-22)
src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java (3)
src/main/java/io/mailtrap/Constants.java (1)
  • Constants (6-15)
src/main/java/io/mailtrap/CustomValidator.java (1)
  • CustomValidator (12-56)
src/main/java/io/mailtrap/api/apiresource/ApiResourceWithValidation.java (1)
  • ApiResourceWithValidation (7-26)
src/main/java/io/mailtrap/model/request/contactimports/Contact.java (1)
src/main/java/io/mailtrap/model/request/contactimports/ImportContactsRequest.java (1)
  • Getter (10-17)
src/main/java/io/mailtrap/client/api/MailtrapContactsApi.java (1)
examples/java/io/mailtrap/examples/contactimports/ContactImports.java (1)
  • ContactImports (14-42)
src/main/java/io/mailtrap/factory/MailtrapClientFactory.java (1)
src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java (1)
  • ContactImportsImpl (11-39)
🔇 Additional comments (9)
src/test/java/io/mailtrap/api/billing/BillingImplTest.java (1)

41-42: LGTM: flipped to (expected, actual) for clearer failures

Both assertions now read naturally and produce better messages on failure.

src/test/resources/api/contactimports/createContactsImportResponse.json (1)

1-4: Fixture LGTM

Shape matches a typical create/import response (id + status). No issues.

src/test/resources/api/contactimports/getContactsImportResponse.json (1)

1-7: ContactImportStatus enum covers all API statuses

Reviewed the ContactImportStatus enum and confirmed it defines all statuses returned by the API—CREATED, STARTED, FINISHED, and FAILED—so the fixture’s "finished" value is fully supported. No further action needed.

src/test/resources/api/contactimports/createContactsImportRequest.json (1)

1-28: Request fixture matches model mapping

Fields map cleanly to Contact.java (email, fields, list_ids_included/excluded). This will exercise Map<String,Object> as intended.

src/main/java/io/mailtrap/client/api/MailtrapContactsApi.java (1)

3-3: New dependency wiring looks correct

Importing ContactImports integrates the new API surface into the Contacts API. No concerns here.

src/main/java/io/mailtrap/factory/MailtrapClientFactory.java (2)

47-52: Wiring looks correct; nice addition of validation into contacts API

Passing CustomValidator into createContactsApi keeps validation consistent with other APIs. The overall factory composition remains cohesive.


54-60: No changes needed: constructor already uses the interface

The MailtrapContactsApi class is annotated with Lombok’s @RequiredArgsConstructor, and its third field is declared as the ContactImports interface, not the implementation. The factory passing a ContactImportsImpl instance still injects via the interface, so no update is required.

Likely an incorrect or invalid review comment.

src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java (1)

27-43: Good use of factory + mock HTTP client to exercise validation and wiring

Setup mirrors production wiring (validator included) and validates request/response via fixtures. Solid baseline.

src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java (1)

13-16: Host selection is correct

Contact imports belong to the GENERAL host; setting apiHost = Constants.GENERAL_HOST is consistent with other general endpoints.

Copy link
Contributor

@mklocek mklocek left a comment

Choose a reason for hiding this comment

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

Let's just fix naming so it's consistent with the API docs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/main/java/io/mailtrap/api/contactimports/ContactImports.java (1)

7-27: Confirm naming intent: ImportContactResponse vs ContactImportResponse.
Two similarly named types (create vs get) may confuse users; if aligned with API docs, fine—otherwise consider a clearer pair (e.g., CreateContactImportResponse vs ContactImportResponse).

Would you like me to propose a rename across the API surface if we decide to adjust?

🧹 Nitpick comments (10)
src/test/java/io/mailtrap/testutils/BaseTest.java (1)

20-20: Optional: align type with other ID fields

Most IDs here use Long; consider matching for consistency unless primitives are intentional.

Apply if desired:

-protected final long importId = 1L;
+protected final Long importId = 1L;
src/main/java/io/mailtrap/model/response/contactimports/ImportContactResponse.java (1)

3-6: Be defensive to future API fields.
Recommend ignoring unknown JSON to avoid breakage if the API adds properties.

Apply:

 package io.mailtrap.model.response.contactimports;

+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import lombok.Data;

-@Data
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class ImportContactResponse {
src/main/java/io/mailtrap/model/response/contactimports/ContactImportResponse.java (1)

3-7: Harden JSON parsing for forward compatibility.
Same suggestion as for the create response: ignore unknowns.

 package io.mailtrap.model.response.contactimports;

 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import lombok.Data;

-@Data
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class ContactImportResponse {
src/main/java/io/mailtrap/api/contactimports/ContactImports.java (2)

9-18: Clarify Javadoc wording.
Tighten wording and punctuation; specify that return is the created import metadata.

   /**
    * Import contacts in bulk with support for custom fields and list management.
    * Existing contacts with matching email addresses will be updated automatically.
-   * Up to 50,000 contacts per request
+   * Up to 50,000 contacts per request.
    *
    * @param accountId unique account ID
    * @param request   request body
-   * @return contact data
+   * @return created contact import metadata
    */
   ImportContactResponse importContacts(long accountId, ImportContactsRequest request);

20-28: Use precise return description for GET.
Minor Javadoc fix to reflect returned type.

   /**
    * Get Contact Import
    *
    * @param accountId unique account ID
    * @param importId unique Contact Import ID
-   * @return contact data
+   * @return contact import data
    */
   ContactImportResponse getContactImport(long accountId, long importId);
src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java (5)

19-30: Null-safety before validation and cleaner URL formatting.

  • Guard against null request to avoid Validator throwing an IllegalArgumentException.
  • Prefer %d for numeric IDs and avoid string concatenation around apiHost for readability.
   @Override
   public ImportContactResponse importContacts(long accountId, ImportContactsRequest request) {

-    validateRequestBodyAndThrowException(request);
+    java.util.Objects.requireNonNull(request, "request must not be null");
+    validateRequestBodyAndThrowException(request);

-    return httpClient.post(
-        String.format(apiHost + "/api/accounts/%s/contacts/imports", accountId),
+    return httpClient.post(
+        String.format("%s/api/accounts/%d/contacts/imports", apiHost, accountId),
         request,
         new RequestData(),
         ImportContactResponse.class
     );
   }

3-11: Import Objects explicitly (style).
Optional: add the import instead of FQN.

 package io.mailtrap.api.contactimports;

 import io.mailtrap.Constants;
 import io.mailtrap.CustomValidator;
 import io.mailtrap.api.apiresource.ApiResourceWithValidation;
 import io.mailtrap.config.MailtrapConfig;
 import io.mailtrap.http.RequestData;
 import io.mailtrap.model.request.contactimports.ImportContactsRequest;
 import io.mailtrap.model.response.contactimports.ContactImportResponse;
 import io.mailtrap.model.response.contactimports.ImportContactResponse;
+import java.util.Objects;

32-39: Consistent URL formatting for GET.
Match the POST style with %d placeholders and no concatenation.

   @Override
   public ContactImportResponse getContactImport(long accountId, long importId) {
     return httpClient.get(
-        String.format(apiHost + "/api/accounts/%s/contacts/imports/%s", accountId, importId),
+        String.format("%s/api/accounts/%d/contacts/imports/%d", apiHost, accountId, importId),
         new RequestData(),
         ContactImportResponse.class
     );
   }

19-39: Optional: extract a reusable path template.
Reduce duplication and risk of typos by centralizing the path.

 public class ContactImportsImpl extends ApiResourceWithValidation implements ContactImports {

+  private static final String CONTACT_IMPORTS_BASE = "%s/api/accounts/%d/contacts/imports";
+
   public ContactImportsImpl(final MailtrapConfig config, final CustomValidator validator) {
     super(config, validator);
     this.apiHost = Constants.GENERAL_HOST;
   }
@@
   public ImportContactResponse importContacts(long accountId, ImportContactsRequest request) {
-    Objects.requireNonNull(request, "request must not be null");
-    validateRequestBodyAndThrowException(request);
-
-    return httpClient.post(
-        String.format("%s/api/accounts/%d/contacts/imports", apiHost, accountId),
+    Objects.requireNonNull(request, "request must not be null");
+    validateRequestBodyAndThrowException(request);
+
+    return httpClient.post(
+        String.format(CONTACT_IMPORTS_BASE, apiHost, accountId),
         request,
         new RequestData(),
         ImportContactResponse.class
     );
   }
@@
   public ContactImportResponse getContactImport(long accountId, long importId) {
-    return httpClient.get(
-        String.format("%s/api/accounts/%d/contacts/imports/%d", apiHost, accountId, importId),
+    return httpClient.get(
+        String.format(CONTACT_IMPORTS_BASE + "/%d", apiHost, accountId, importId),
         new RequestData(),
         ContactImportResponse.class
     );
   }

19-39: Consider validating IDs are positive.
If upstream doesn’t guarantee it, add lightweight checks to fail fast on invalid IDs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b957168 and 2c6c473.

📒 Files selected for processing (8)
  • examples/java/io/mailtrap/examples/contactimports/ContactImports.java (1 hunks)
  • src/main/java/io/mailtrap/api/contactimports/ContactImports.java (1 hunks)
  • src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java (1 hunks)
  • src/main/java/io/mailtrap/model/response/contactimports/ContactImportResponse.java (1 hunks)
  • src/main/java/io/mailtrap/model/response/contactimports/ContactImportStatus.java (1 hunks)
  • src/main/java/io/mailtrap/model/response/contactimports/ImportContactResponse.java (1 hunks)
  • src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java (1 hunks)
  • src/test/java/io/mailtrap/testutils/BaseTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java
  • examples/java/io/mailtrap/examples/contactimports/ContactImports.java
  • src/main/java/io/mailtrap/model/response/contactimports/ContactImportStatus.java
🧰 Additional context used
🧬 Code graph analysis (4)
src/main/java/io/mailtrap/model/response/contactimports/ImportContactResponse.java (1)
src/main/java/io/mailtrap/model/response/contactimports/ContactImportResponse.java (1)
  • Data (6-22)
src/main/java/io/mailtrap/model/response/contactimports/ContactImportResponse.java (1)
src/main/java/io/mailtrap/model/response/contactimports/ImportContactResponse.java (1)
  • Data (5-12)
src/main/java/io/mailtrap/api/contactimports/ContactImports.java (1)
examples/java/io/mailtrap/examples/contactimports/ContactImports.java (1)
  • ContactImports (14-42)
src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java (3)
src/main/java/io/mailtrap/Constants.java (1)
  • Constants (6-15)
src/main/java/io/mailtrap/CustomValidator.java (1)
  • CustomValidator (12-56)
src/main/java/io/mailtrap/api/apiresource/ApiResourceWithValidation.java (1)
  • ApiResourceWithValidation (7-26)
🔇 Additional comments (4)
src/test/java/io/mailtrap/testutils/BaseTest.java (1)

20-20: LGTM: sensible shared test constant for Contact Imports

Matches fixtures using id 1 and keeps tests tidy.

src/main/java/io/mailtrap/model/response/contactimports/ImportContactResponse.java (1)

5-12: LGTM: minimal DTO fits the create-import response shape.
Fields and naming align with GET model and enum.

src/main/java/io/mailtrap/model/response/contactimports/ContactImportResponse.java (1)

6-22: LGTM: response model matches fixture (snake_case counts).
Wrapper Longs for counts are appropriate since they can be absent until completion.

src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java (1)

12-17: Host selection looks correct.
Using GENERAL_HOST matches the public API base used elsewhere.

Copy link
Contributor

@mklocek mklocek left a comment

Choose a reason for hiding this comment

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

Just one more rename and we're good, thanks!

* @param request request body
* @return contact data
*/
ImportContactResponse importContacts(long accountId, ImportContactsRequest request);
Copy link
Contributor

Choose a reason for hiding this comment

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

Well, if the method is plural, request is plural, why would response not be plural? 😃

Suggested change
ImportContactResponse importContacts(long accountId, ImportContactsRequest request);
ImportContactsResponse importContacts(long accountId, ImportContactsRequest request);

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.

2 participants