diff --git a/examples/java/io/mailtrap/examples/contactimports/ContactImports.java b/examples/java/io/mailtrap/examples/contactimports/ContactImports.java new file mode 100644 index 0000000..fa3cde1 --- /dev/null +++ b/examples/java/io/mailtrap/examples/contactimports/ContactImports.java @@ -0,0 +1,42 @@ +package io.mailtrap.examples.contactimports; + +import io.mailtrap.config.MailtrapConfig; +import io.mailtrap.factory.MailtrapClientFactory; +import io.mailtrap.model.request.contactimports.Contact; +import io.mailtrap.model.request.contactimports.ImportContactsRequest; +import io.mailtrap.model.request.contacts.UpdateContact; +import io.mailtrap.model.request.contacts.UpdateContactRequest; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class ContactImports { + + private static final String TOKEN = ""; + private static final long ACCOUNT_ID = 1L; + private static final long LIST_1_ID = 1L; + private static final long LIST_2_ID = 2L; + private static final String EMAIL = "contact_email@email.com"; + + public static void main(String[] args) { + final var config = new MailtrapConfig.Builder() + .token(TOKEN) + .build(); + + final var client = MailtrapClientFactory.createMailtrapClient(config); + + var importRequest = new ImportContactsRequest( + List.of(new Contact(EMAIL, Map.of("first_name", "Nick"), List.of(LIST_1_ID), List.of(LIST_2_ID)))); + + var createResponse = client.contactsApi().contactImports() + .importContacts(ACCOUNT_ID, importRequest); + + System.out.println(createResponse); + + var contactImportResponse = client.contactsApi().contactImports() + .getContactImport(ACCOUNT_ID, createResponse.getId()); + + System.out.println(contactImportResponse); + } +} diff --git a/src/main/java/io/mailtrap/api/contactimports/ContactImports.java b/src/main/java/io/mailtrap/api/contactimports/ContactImports.java new file mode 100644 index 0000000..540b238 --- /dev/null +++ b/src/main/java/io/mailtrap/api/contactimports/ContactImports.java @@ -0,0 +1,28 @@ +package io.mailtrap.api.contactimports; + +import io.mailtrap.model.request.contactimports.ImportContactsRequest; +import io.mailtrap.model.response.contactimports.ContactsImportResponse; +import io.mailtrap.model.response.contactimports.CreateContactsImportResponse; + +public interface ContactImports { + + /** + * 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 + * + * @param accountId unique account ID + * @param request request body + * @return contact data + */ + CreateContactsImportResponse importContacts(long accountId, ImportContactsRequest request); + + /** + * Get Contact Import + * + * @param accountId unique account ID + * @param importId unique Contact Import ID + * @return contact data + */ + ContactsImportResponse getContactImport(long accountId, long importId); +} diff --git a/src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java b/src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java new file mode 100644 index 0000000..16d7a58 --- /dev/null +++ b/src/main/java/io/mailtrap/api/contactimports/ContactImportsImpl.java @@ -0,0 +1,40 @@ +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.ContactsImportResponse; +import io.mailtrap.model.response.contactimports.CreateContactsImportResponse; + +public class ContactImportsImpl extends ApiResourceWithValidation implements ContactImports { + + public ContactImportsImpl(final MailtrapConfig config, final CustomValidator validator) { + super(config, validator); + this.apiHost = Constants.GENERAL_HOST; + } + + @Override + public CreateContactsImportResponse importContacts(long accountId, ImportContactsRequest request) { + + validateRequestBodyAndThrowException(request); + + return httpClient.post( + String.format(apiHost + "/api/accounts/%s/contacts/imports", accountId), + request, + new RequestData(), + CreateContactsImportResponse.class + ); + } + + @Override + public ContactsImportResponse getContactImport(long accountId, long importId) { + return httpClient.get( + String.format(apiHost + "/api/accounts/%s/contacts/imports/%s", accountId, importId), + new RequestData(), + ContactsImportResponse.class + ); + } +} diff --git a/src/main/java/io/mailtrap/client/api/MailtrapContactsApi.java b/src/main/java/io/mailtrap/client/api/MailtrapContactsApi.java index ebb482a..190664d 100644 --- a/src/main/java/io/mailtrap/client/api/MailtrapContactsApi.java +++ b/src/main/java/io/mailtrap/client/api/MailtrapContactsApi.java @@ -1,5 +1,6 @@ package io.mailtrap.client.api; +import io.mailtrap.api.contactimports.ContactImports; import io.mailtrap.api.contactlists.ContactLists; import io.mailtrap.api.contacts.Contacts; import lombok.Getter; @@ -15,4 +16,5 @@ public class MailtrapContactsApi { private final ContactLists contactLists; private final Contacts contacts; + private final ContactImports contactImports; } diff --git a/src/main/java/io/mailtrap/factory/MailtrapClientFactory.java b/src/main/java/io/mailtrap/factory/MailtrapClientFactory.java index e642c59..f0ad798 100644 --- a/src/main/java/io/mailtrap/factory/MailtrapClientFactory.java +++ b/src/main/java/io/mailtrap/factory/MailtrapClientFactory.java @@ -6,6 +6,7 @@ import io.mailtrap.api.attachments.AttachmentsImpl; import io.mailtrap.api.billing.BillingImpl; import io.mailtrap.api.bulkemails.BulkEmailsImpl; +import io.mailtrap.api.contactimports.ContactImportsImpl; import io.mailtrap.api.contactlists.ContactListsImpl; import io.mailtrap.api.contacts.ContactsImpl; import io.mailtrap.api.inboxes.InboxesImpl; @@ -43,18 +44,19 @@ public static MailtrapClient createMailtrapClient(MailtrapConfig config) { final var testingApi = createTestingApi(config, customValidator); final var bulkSendingApi = createBulkSendingApi(config, customValidator); final var generalApi = createGeneralApi(config); - final var contactsApi = createContactsApi(config); + final var contactsApi = createContactsApi(config, customValidator); final var sendingContextHolder = configureSendingContext(config); return new MailtrapClient(sendingApi, testingApi, bulkSendingApi, generalApi, contactsApi, sendingContextHolder); } - private static MailtrapContactsApi createContactsApi(MailtrapConfig config) { + private static MailtrapContactsApi createContactsApi(MailtrapConfig config, CustomValidator customValidator) { final var contactLists = new ContactListsImpl(config); final var contacts = new ContactsImpl(config); + final var contactImports = new ContactImportsImpl(config, customValidator); - return new MailtrapContactsApi(contactLists, contacts); + return new MailtrapContactsApi(contactLists, contacts, contactImports); } private static MailtrapGeneralApi createGeneralApi(MailtrapConfig config) { diff --git a/src/main/java/io/mailtrap/model/request/contactimports/Contact.java b/src/main/java/io/mailtrap/model/request/contactimports/Contact.java new file mode 100644 index 0000000..16f02ea --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/contactimports/Contact.java @@ -0,0 +1,22 @@ +package io.mailtrap.model.request.contactimports; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class Contact { + + private String email; + + private Map fields; + + @JsonProperty("list_ids_included") + private List listIdsIncluded; + + @JsonProperty("list_ids_excluded") + private List listIdsExcluded; +} diff --git a/src/main/java/io/mailtrap/model/request/contactimports/ImportContactsRequest.java b/src/main/java/io/mailtrap/model/request/contactimports/ImportContactsRequest.java new file mode 100644 index 0000000..df86be5 --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/contactimports/ImportContactsRequest.java @@ -0,0 +1,17 @@ +package io.mailtrap.model.request.contactimports; + +import io.mailtrap.model.AbstractModel; +import java.util.List; + +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ImportContactsRequest extends AbstractModel { + + @Size(max = 50_000, message = "Maximum 50000 contacts per request") + private List contacts; + +} diff --git a/src/main/java/io/mailtrap/model/response/contactimports/ContactImportStatus.java b/src/main/java/io/mailtrap/model/response/contactimports/ContactImportStatus.java new file mode 100644 index 0000000..b33b88c --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/contactimports/ContactImportStatus.java @@ -0,0 +1,33 @@ +package io.mailtrap.model.response.contactimports; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum ContactImportStatus { + CREATED("created"), + STARTED("started"), + FINISHED("finished"), + FAILED("failed"); + + private final String value; + + ContactImportStatus(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @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); + } +} diff --git a/src/main/java/io/mailtrap/model/response/contactimports/ContactsImportResponse.java b/src/main/java/io/mailtrap/model/response/contactimports/ContactsImportResponse.java new file mode 100644 index 0000000..f554e5a --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/contactimports/ContactsImportResponse.java @@ -0,0 +1,22 @@ +package io.mailtrap.model.response.contactimports; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class ContactsImportResponse { + + private long id; + + private ContactImportStatus status; + + @JsonProperty("created_contacts_count") + private Long createdContactsCount; + + @JsonProperty("updated_contacts_count") + private Long updatedContactsCount; + + @JsonProperty("contacts_over_limit_count") + private Long contactsOverLimitCount; + +} diff --git a/src/main/java/io/mailtrap/model/response/contactimports/CreateContactsImportResponse.java b/src/main/java/io/mailtrap/model/response/contactimports/CreateContactsImportResponse.java new file mode 100644 index 0000000..34959cb --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/contactimports/CreateContactsImportResponse.java @@ -0,0 +1,12 @@ +package io.mailtrap.model.response.contactimports; + +import lombok.Data; + +@Data +public class CreateContactsImportResponse { + + private long id; + + private ContactImportStatus status; + +} diff --git a/src/test/java/io/mailtrap/api/accountaccesses/AccountAccessesImplTest.java b/src/test/java/io/mailtrap/api/accountaccesses/AccountAccessesImplTest.java index b2cb255..fb87867 100644 --- a/src/test/java/io/mailtrap/api/accountaccesses/AccountAccessesImplTest.java +++ b/src/test/java/io/mailtrap/api/accountaccesses/AccountAccessesImplTest.java @@ -66,8 +66,8 @@ void test_listUserAndInviteAccountAccessesWithProjectIdsQueryParam() { AccountAccessResponse accountAccessResponse = accountAccessResponses.get(0); assertEquals(accountAccessResponse.getId(), accountAccessId); assertEquals(SpecifierType.API_TOKEN, accountAccessResponse.getSpecifierType()); - assertEquals(((ApiTokenSpecifier) accountAccessResponse.getSpecifier()).getToken(), "token-value-11-22-33"); - assertEquals(accountAccessResponse.getResources().size(), 2); + assertEquals("token-value-11-22-33", ((ApiTokenSpecifier) accountAccessResponse.getSpecifier()).getToken()); + assertEquals(2, accountAccessResponse.getResources().size()); assertEquals(accountAccessResponse.getResources().get(0).getResourceId(), projectId); } diff --git a/src/test/java/io/mailtrap/api/billing/BillingImplTest.java b/src/test/java/io/mailtrap/api/billing/BillingImplTest.java index 6c23d48..b7f63c6 100644 --- a/src/test/java/io/mailtrap/api/billing/BillingImplTest.java +++ b/src/test/java/io/mailtrap/api/billing/BillingImplTest.java @@ -38,7 +38,7 @@ void getCurrentBillingCycleUsage() { BillingResponse billingResponse = api.getCurrentBillingCycleUsage(accountId); assertNotNull(billingResponse); - assertEquals(billingResponse.getTestingBillingInfo().getPlan().getName(), "Individual"); - assertEquals(billingResponse.getSendingBillingInfo().getPlan().getName(), "Basic 10K"); + assertEquals("Individual", billingResponse.getTestingBillingInfo().getPlan().getName()); + assertEquals("Basic 10K", billingResponse.getSendingBillingInfo().getPlan().getName()); } } diff --git a/src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java b/src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java new file mode 100644 index 0000000..f86b88a --- /dev/null +++ b/src/test/java/io/mailtrap/api/contactimports/ContactImportsImplTest.java @@ -0,0 +1,85 @@ +package io.mailtrap.api.contactimports; + +import io.mailtrap.Constants; +import io.mailtrap.config.MailtrapConfig; +import io.mailtrap.exception.InvalidRequestBodyException; +import io.mailtrap.factory.MailtrapClientFactory; +import io.mailtrap.model.request.contactimports.Contact; +import io.mailtrap.model.request.contactimports.ImportContactsRequest; +import io.mailtrap.model.response.contactimports.ContactImportStatus; +import io.mailtrap.model.response.contactimports.ContactsImportResponse; +import io.mailtrap.model.response.contactimports.CreateContactsImportResponse; +import io.mailtrap.testutils.BaseTest; +import io.mailtrap.testutils.DataMock; +import io.mailtrap.testutils.TestHttpClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class ContactImportsImplTest extends BaseTest { + + private ContactImports api; + + @BeforeEach + public void init() { + TestHttpClient httpClient = new TestHttpClient(List.of( + DataMock.build(Constants.GENERAL_HOST + "/api/accounts/" + accountId + "/contacts/imports", + "POST", "api/contactimports/createContactsImportRequest.json", "api/contactimports/createContactsImportResponse.json"), + + DataMock.build(Constants.GENERAL_HOST + "/api/accounts/" + accountId + "/contacts/imports/" + importId, + "GET", null, "api/contactimports/getContactsImportResponse.json") + )); + + MailtrapConfig testConfig = new MailtrapConfig.Builder() + .httpClient(httpClient) + .token("dummy_token") + .build(); + + api = MailtrapClientFactory.createMailtrapClient(testConfig).contactsApi().contactImports(); + } + + @Test + void test_importContacts() { + final var firstContact = new Contact("customer1@example.com", Map.of("full_name", "Jane Doe"), List.of(1L), List.of(2L)); + final var secondContact = new Contact("customer2@example.com", Map.of("full_name", "John Doe"), List.of(3L), List.of(4L)); + final var request = new ImportContactsRequest(List.of(firstContact, secondContact)); + + CreateContactsImportResponse contactImportResponse = api.importContacts(accountId, request); + + assertEquals(importId, contactImportResponse.getId()); + assertSame(ContactImportStatus.STARTED, contactImportResponse.getStatus()); + } + + @Test + void test_importContacts_should_fail_validation() { + InvalidRequestBodyException exception = assertThrows(InvalidRequestBodyException.class, () -> api.importContacts(accountId, new ImportContactsRequest(generateContacts()))); + + assertEquals("Invalid request body. Violations: contacts=Maximum 50000 contacts per request", exception.getMessage()); + } + + private List generateContacts() { + List contacts = new ArrayList<>(); + + for (int i = 0; i < 50001; i++) { + contacts.add(new Contact("stub_contact_%d@example.com".formatted(i), Map.of(), List.of(), List.of())); + } + + return contacts; + } + + @Test + void test_getContactImport() { + ContactsImportResponse contactImport = api.getContactImport(accountId, importId); + + assertEquals(importId, contactImport.getId()); + assertSame(ContactImportStatus.FINISHED, contactImport.getStatus()); + assertEquals(1L, contactImport.getCreatedContactsCount()); + assertEquals(3L, contactImport.getUpdatedContactsCount()); + assertEquals(3L, contactImport.getContactsOverLimitCount()); + } +} diff --git a/src/test/java/io/mailtrap/api/inboxes/InboxesImplTest.java b/src/test/java/io/mailtrap/api/inboxes/InboxesImplTest.java index d436183..c0e0066 100644 --- a/src/test/java/io/mailtrap/api/inboxes/InboxesImplTest.java +++ b/src/test/java/io/mailtrap/api/inboxes/InboxesImplTest.java @@ -70,7 +70,7 @@ void test_createInbox() { assertNotNull(createdInbox); assertEquals(createdInbox.getProjectId(), projectId); - assertEquals(createdInbox.getName(), "Test inbox"); + assertEquals("Test inbox", createdInbox.getName()); } @Test @@ -80,7 +80,7 @@ void test_getInboxAttributes() { assertNotNull(inboxAttributes); assertTrue(inboxAttributes.getPermission().isCanRead()); assertEquals(inboxAttributes.getProjectId(), projectId); - assertEquals(inboxAttributes.getName(), "Test inbox"); + assertEquals("Test inbox", inboxAttributes.getName()); } @Test @@ -90,7 +90,7 @@ void test_deleteInbox() { assertNotNull(deletedInbox); assertTrue(deletedInbox.getPermission().isCanRead()); assertEquals(deletedInbox.getId(), inboxId); - assertEquals(deletedInbox.getName(), "Test inbox"); + assertEquals("Test inbox", deletedInbox.getName()); } @Test @@ -100,8 +100,8 @@ void test_updateInbox() { assertNotNull(updatedInbox); assertTrue(updatedInbox.getPermission().isCanRead()); assertEquals(updatedInbox.getId(), inboxId); - assertEquals(updatedInbox.getName(), "Updated Inbox Name"); - assertEquals(updatedInbox.getEmailUsername(), "updated-email-username"); + assertEquals("Updated Inbox Name", updatedInbox.getName()); + assertEquals("updated-email-username", updatedInbox.getEmailUsername()); } @Test @@ -111,7 +111,7 @@ void test_cleanInbox() { assertNotNull(cleanedInbox); assertTrue(cleanedInbox.getPermission().isCanRead()); assertEquals(cleanedInbox.getId(), inboxId); - assertEquals(cleanedInbox.getName(), "Test inbox"); + assertEquals("Test inbox", cleanedInbox.getName()); } @Test @@ -121,7 +121,7 @@ void test_markAsRead() { assertNotNull(markedAsRead); assertTrue(markedAsRead.getPermission().isCanRead()); assertEquals(markedAsRead.getId(), inboxId); - assertEquals(markedAsRead.getName(), "Test inbox"); + assertEquals("Test inbox", markedAsRead.getName()); } @Test @@ -131,7 +131,7 @@ void test_resetCredentials() { assertNotNull(resetCredentials); assertTrue(resetCredentials.getPermission().isCanRead()); assertEquals(resetCredentials.getId(), inboxId); - assertEquals(resetCredentials.getName(), "Test inbox"); + assertEquals("Test inbox", resetCredentials.getName()); } @Test @@ -141,7 +141,7 @@ void test_enableEmailAddress() { assertNotNull(enableEmailAddress); assertTrue(enableEmailAddress.getPermission().isCanRead()); assertEquals(enableEmailAddress.getId(), inboxId); - assertEquals(enableEmailAddress.getName(), "Test inbox"); + assertEquals("Test inbox", enableEmailAddress.getName()); } @Test @@ -151,7 +151,7 @@ void test_resetEmailAddresses() { assertNotNull(resetEmailAddresses); assertTrue(resetEmailAddresses.getPermission().isCanRead()); assertEquals(resetEmailAddresses.getId(), inboxId); - assertEquals(resetEmailAddresses.getName(), "Test inbox"); + assertEquals("Test inbox", resetEmailAddresses.getName()); } @Test @@ -162,7 +162,7 @@ void test_getInboxes() { assertEquals(1, inboxes.size()); assertTrue(inboxes.get(0).getPermission().isCanRead()); assertEquals(inboxes.get(0).getId(), inboxId); - assertEquals(inboxes.get(0).getName(), "Test inbox"); + assertEquals("Test inbox", inboxes.get(0).getName()); } } diff --git a/src/test/java/io/mailtrap/testutils/BaseTest.java b/src/test/java/io/mailtrap/testutils/BaseTest.java index 65e0360..9939ac7 100644 --- a/src/test/java/io/mailtrap/testutils/BaseTest.java +++ b/src/test/java/io/mailtrap/testutils/BaseTest.java @@ -2,7 +2,6 @@ import java.net.URLEncoder; import java.nio.charset.Charset; -import java.util.UUID; public class BaseTest { protected final Long accountId = 1L; @@ -18,4 +17,5 @@ public class BaseTest { protected final String emailEncoded = URLEncoder.encode(email, Charset.defaultCharset()); protected final String contactUUID = "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132"; protected final String contactUUIDEncoded = URLEncoder.encode(contactUUID, Charset.defaultCharset()); + protected final long importId = 1L; } diff --git a/src/test/resources/api/contactimports/createContactsImportRequest.json b/src/test/resources/api/contactimports/createContactsImportRequest.json new file mode 100644 index 0000000..fca1047 --- /dev/null +++ b/src/test/resources/api/contactimports/createContactsImportRequest.json @@ -0,0 +1,28 @@ +{ + "contacts": [ + { + "email": "customer1@example.com", + "fields": { + "full_name": "Jane Doe" + }, + "list_ids_included": [ + 1 + ], + "list_ids_excluded": [ + 2 + ] + }, + { + "email": "customer2@example.com", + "fields": { + "full_name": "John Doe" + }, + "list_ids_included": [ + 3 + ], + "list_ids_excluded": [ + 4 + ] + } + ] +} diff --git a/src/test/resources/api/contactimports/createContactsImportResponse.json b/src/test/resources/api/contactimports/createContactsImportResponse.json new file mode 100644 index 0000000..3bbdd48 --- /dev/null +++ b/src/test/resources/api/contactimports/createContactsImportResponse.json @@ -0,0 +1,4 @@ +{ + "id": 1, + "status": "started" +} diff --git a/src/test/resources/api/contactimports/getContactsImportResponse.json b/src/test/resources/api/contactimports/getContactsImportResponse.json new file mode 100644 index 0000000..1331ae9 --- /dev/null +++ b/src/test/resources/api/contactimports/getContactsImportResponse.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "status": "finished", + "created_contacts_count": 1, + "updated_contacts_count": 3, + "contacts_over_limit_count": 3 +}