Skip to content

Conversation

@gaalferov
Copy link
Collaborator

@gaalferov gaalferov commented Oct 9, 2025

Motivation

Support new export contact functionality

Changes

  • Add Contact Export functionality with filters and related tests

How to test

composer test

Summary by CodeRabbit

  • New Features

    • Introduced Contact Export: create exports with filters (e.g., list or subscription status) and check export status/download link.
  • Documentation

    • Updated README to reflect Import/Export capability.
    • Added changelog entry for v3.9.0 highlighting Contact Export.
    • Expanded examples demonstrating creating and polling contact exports.
  • Tests

    • Added coverage for creating and retrieving contact exports, including unauthorized, forbidden, validation, rate limit, and not-found scenarios.

@gaalferov gaalferov linked an issue Oct 9, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Oct 9, 2025

Walkthrough

Adds Contact Export functionality: new DTO for export filters, new API methods to create and fetch contact exports, example usage, tests, and documentation updates including changelog and README list item.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md, README.md
Added v3.9.0 changelog entry for Contact Export; updated README capability list from "Import" to "Import/Export".
Examples
examples/general/contacts.php
Appended examples for creating a contact export with filters and retrieving export status/download URL; imported ContactExportFilter.
API: Contact exports
src/Api/General/Contact.php
Added createContactExport(array $filters = []) and getContactExport(int $exportId); validates filters are ContactExportFilter; POST /contacts/exports and GET /contacts/exports/{id}.
DTO: ContactExportFilter
src/DTO/Request/Contact/ContactExportFilter.php
New final DTO implementing RequestInterface with name/operator/value, factory init(), getters, and toArray() for request serialization.
Tests: Contact exports
tests/Api/General/ContactTest.php
New tests covering creation success and 4xx cases, and retrieval success/not-found; uses ContactExportFilter in payloads.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as Caller
  participant SDK as Contact API
  participant HTTP as HTTP Client
  participant Svc as Mail Service

  rect rgba(230,245,255,0.6)
  note over User,SDK: Create Contact Export (async task)
  User->>SDK: createContactExport(filters: ContactExportFilter[])
  SDK->>SDK: validate filters are ContactExportFilter
  SDK->>HTTP: POST /contacts/exports {filters:[...]}
  HTTP->>Svc: Request
  Svc-->>HTTP: 202 Accepted {export_id}
  HTTP-->>SDK: Response
  SDK-->>User: Response (export_id)
  end

  rect rgba(240,255,230,0.6)
  note over User,SDK: Poll Export Status / Get Download URL
  loop until completed
    User->>SDK: getContactExport(export_id)
    SDK->>HTTP: GET /contacts/exports/{id}
    HTTP->>Svc: Request
    Svc-->>HTTP: 200 {status, url?}
    HTTP-->>SDK: Response
    SDK-->>User: Response
  end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • IgorDobryn
  • i7an
  • yanchuk
  • mklocek
  • VladimirTaytor

Poem

I thump my paws—export’s here at last!
Filters packed, the CSV’s cast.
Hop, post, and wait—an ID in paw,
Polling the burrow per export law.
A link appears—hurrah, let’s go!
Data carrots in a tidy row. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The description includes the Motivation, Changes, and How to test sections as per the template but omits the required Images and GIFs section, which is specified in the repository’s PR description template. Please add the “## Images and GIFs” section to the PR description, even if no screenshots are needed, indicate “N/A” or “No UI changes” under that heading.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly summarizes the primary change of adding contact export functionality and accompanying tests, which matches the main updates in the pull request without extraneous detail.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/contacts-export

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (1)
examples/general/contacts.php (1)

353-388: Consider documenting valid filter parameters.

The example demonstrates the export workflow well, including the async polling pattern. However, users may benefit from inline comments or documentation explaining:

  • Valid filter names (e.g., list_id, subscription_status)
  • Valid operators (e.g., equal, not_equal, etc.)
  • Expected value types for each filter

This would help developers understand what filters are available without consulting external API documentation.

Example enhancement:

/**
 * Create a new Contact Export (asynchronous task)
 *
 * POST https://mailtrap.io/api/accounts/{account_id}/contacts/exports
 * 
 * Common filter names: list_id, subscription_status, created_at, etc.
 * Common operators: equal, not_equal, greater_than, less_than, etc.
 */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09a4cd5 and 3ac1bc7.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • README.md (1 hunks)
  • examples/general/contacts.php (2 hunks)
  • src/Api/General/Contact.php (2 hunks)
  • src/DTO/Request/Contact/ContactExportFilter.php (1 hunks)
  • tests/Api/General/ContactTest.php (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
examples/general/contacts.php (3)
src/Api/General/Contact.php (3)
  • Contact (20-408)
  • createContactExport (320-339)
  • getContactExport (349-354)
src/DTO/Request/Contact/ContactExportFilter.php (3)
  • ContactExportFilter (12-49)
  • init (21-24)
  • toArray (41-48)
src/Helper/ResponseHelper.php (1)
  • ResponseHelper (14-37)
src/Api/General/Contact.php (2)
src/DTO/Request/Contact/ContactExportFilter.php (2)
  • ContactExportFilter (12-49)
  • toArray (41-48)
src/Api/AbstractApi.php (3)
  • handleResponse (84-111)
  • httpPost (43-50)
  • httpGet (34-41)
src/DTO/Request/Contact/ContactExportFilter.php (1)
src/Api/General/Contact.php (1)
  • Contact (20-408)
tests/Api/General/ContactTest.php (5)
src/Api/General/Contact.php (3)
  • Contact (20-408)
  • createContactExport (320-339)
  • getContactExport (349-354)
src/DTO/Request/Contact/ContactExportFilter.php (2)
  • ContactExportFilter (12-49)
  • toArray (41-48)
src/Api/AbstractApi.php (1)
  • AbstractApi (18-150)
src/Helper/ResponseHelper.php (1)
  • ResponseHelper (14-37)
src/Exception/HttpClientException.php (1)
  • HttpClientException (14-84)
🔇 Additional comments (9)
README.md (1)

37-37: LGTM!

The documentation update accurately reflects the new Contact Export capability alongside the existing Import feature.

CHANGELOG.md (1)

1-3: LGTM!

The changelog entry follows the established format and clearly documents the new feature.

examples/general/contacts.php (1)

10-10: LGTM!

The import is correctly placed alongside other Contact-related DTOs.

src/Api/General/Contact.php (3)

11-11: LGTM!

The import is correctly placed with other Contact-related DTO imports.


312-339: LGTM!

The implementation is solid and follows the established pattern from importContacts. The filter validation ensures type safety, and the array transformation via toArray() is consistent with other DTO usage in the codebase.


341-354: LGTM!

The implementation correctly retrieves export status by ID using the established patterns and error handling.

src/DTO/Request/Contact/ContactExportFilter.php (1)

1-50: LGTM!

The DTO implementation is clean and follows PHP best practices:

  • Final class prevents unintended inheritance
  • Constructor property promotion (PHP 8.0+)
  • Static factory method for convenient instantiation
  • Simple getters and serialization method

The mixed type for value provides flexibility for different filter types (arrays, strings, etc.), which aligns with the example usage showing both array and string values.

tests/Api/General/ContactTest.php (2)

9-9: LGTM!

The import is correctly placed with other DTO imports.


929-1061: LGTM!

The test suite provides excellent coverage of the contact export functionality:

  • Success scenarios for both create and get operations
  • Comprehensive error handling (401, 403, 404, 422, 429)
  • Proper mocking and assertions
  • Realistic response structures
  • Follows established testing patterns in the codebase

The tests demonstrate both filter construction approaches (ContactExportFilter::init() and new ContactExportFilter()) and validate the complete workflow from creation to retrieval.

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

🧹 Nitpick comments (2)
examples/general/contacts.php (1)

353-388: Consider adding a comment linking the two examples.

The create and get export examples are well-structured. However, it might be helpful to add a brief comment in the get export example (around line 381) explaining how to obtain the exportId from the create response, since these two operations are typically used together in a polling pattern.

Example enhancement:

/**
 * Get Contact Export status / download URL
 * (Poll this endpoint until status becomes `finished` and `url` is not null)
 *
 * GET https://mailtrap.io/api/accounts/{account_id}/contacts/exports/{export_id}
 */
try {
    $exportId = 1; // Replace 1 with the actual export ID obtained from createContactExport response (see previous example)
    $response = $contacts->getContactExport($exportId);
tests/Api/General/ContactTest.php (1)

929-1061: Add test for invalid filter type validation.

The test suite comprehensively covers the export functionality with various HTTP response scenarios (unauthorized, forbidden, validation errors, rate limits, not found). However, there's a missing test case for the InvalidArgumentException thrown when a non-ContactExportFilter instance is passed to createContactExport().

Compare with the importContacts test suite, which includes testImportContactsThrowsExceptionForInvalidInput (line 632) to verify the validation logic. Adding a similar test for exports would ensure consistent test coverage for the validation logic in createContactExport (lines 328-330 in Contact.php).

Add this test case after line 1030:

public function testCreateContactExportThrowsExceptionForInvalidInput(): void
{
    $filters = [
        new ContactExportFilter('list_id', 'equal', [1]),
        // Invalid input - should be ContactExportFilter
        new ImportContact(
            email: '[email protected]',
            fields: [],
            listIdsIncluded: [],
            listIdsExcluded: []
        ),
    ];

    $this->contact->expects($this->never())->method('httpPost');

    $this->expectException(InvalidArgumentException::class);
    $this->expectExceptionMessage('Each filter must be an instance of ContactExportFilter.');

    $this->contact->createContactExport($filters);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09a4cd5 and 564f025.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • README.md (1 hunks)
  • examples/general/contacts.php (2 hunks)
  • src/Api/General/Contact.php (2 hunks)
  • src/DTO/Request/Contact/ContactExportFilter.php (1 hunks)
  • tests/Api/General/ContactTest.php (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/Api/General/Contact.php (3)
src/DTO/Request/Contact/ContactExportFilter.php (2)
  • ContactExportFilter (12-49)
  • toArray (41-48)
src/Api/AbstractApi.php (3)
  • handleResponse (84-111)
  • httpPost (43-50)
  • httpGet (34-41)
src/Exception/InvalidArgumentException.php (1)
  • InvalidArgumentException (10-12)
examples/general/contacts.php (3)
src/Api/General/Contact.php (3)
  • Contact (20-408)
  • createContactExport (320-339)
  • getContactExport (349-354)
src/DTO/Request/Contact/ContactExportFilter.php (3)
  • ContactExportFilter (12-49)
  • init (21-24)
  • toArray (41-48)
src/Helper/ResponseHelper.php (1)
  • ResponseHelper (14-37)
tests/Api/General/ContactTest.php (5)
src/Api/General/Contact.php (3)
  • Contact (20-408)
  • createContactExport (320-339)
  • getContactExport (349-354)
src/DTO/Request/Contact/ContactExportFilter.php (2)
  • ContactExportFilter (12-49)
  • toArray (41-48)
src/Api/AbstractApi.php (1)
  • AbstractApi (18-150)
src/Helper/ResponseHelper.php (1)
  • ResponseHelper (14-37)
src/Exception/HttpClientException.php (1)
  • HttpClientException (14-84)
src/DTO/Request/Contact/ContactExportFilter.php (1)
src/Api/General/Contact.php (1)
  • Contact (20-408)
🔇 Additional comments (5)
src/Api/General/Contact.php (3)

11-11: LGTM!

The import for ContactExportFilter is correctly placed and necessary for the new export methods.


312-339: LGTM!

The createContactExport method implementation is solid:

  • Validates each filter is an instance of ContactExportFilter (lines 328-330)
  • Throws InvalidArgumentException for invalid filter types (consistent with importContacts pattern)
  • Properly transforms filters to array format using array_map and toArray() (lines 326-335)
  • Empty filters array is allowed by default parameter, which is reasonable for exporting all contacts

341-354: LGTM!

The getContactExport method is straightforward and correct:

  • Simple GET request to the appropriate endpoint
  • Uses handleResponse for consistent error handling
  • Proper path construction with exportId
src/DTO/Request/Contact/ContactExportFilter.php (1)

1-49: LGTM!

The ContactExportFilter class is well-implemented as an immutable value object:

  • Final class prevents unintended extension
  • Private properties with public getters ensure immutability
  • Static factory method init() provides a convenient construction pattern
  • toArray() method properly serializes the filter for API requests
  • mixed type for $value allows flexibility for different filter types (arrays, strings, etc.)
  • Consistent with other DTO patterns in the codebase (e.g., ImportContact, CreateContact)
tests/Api/General/ContactTest.php (1)

9-9: LGTM!

The import for ContactExportFilter is correctly placed and necessary for the new export tests.

Copy link

@leonid-shevtsov leonid-shevtsov left a comment

Choose a reason for hiding this comment

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

Looks good although I'm not an expert on the contacts features.

@gaalferov gaalferov merged commit 9fa8b36 into main Oct 13, 2025
20 checks passed
@gaalferov gaalferov deleted the feature/contacts-export branch October 13, 2025 11:21
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.

Contacts: Get all Contacts

4 participants