-
Notifications
You must be signed in to change notification settings - Fork 11
Suppressions api #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Suppressions api #68
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
66ef0e4
docs: add Suppressions example to README for enhanced API usage clarity
narekhovhannisyan 3171823
examples: add suppressions example to demonstrate MailtrapClient func…
narekhovhannisyan 4c8580b
test: add unit tests for MailtrapClient suppressions getter to valida…
narekhovhannisyan f0e3907
test: add unit tests for SuppressionsBaseAPI to verify initialization…
narekhovhannisyan 5f88ec2
test: add comprehensive unit tests for SuppressionsApi to validate ge…
narekhovhannisyan 4912a5b
feat: add suppressions getter to MailtrapClient for managing suppress…
narekhovhannisyan f576198
feat: implement SuppressionsBaseAPI for managing suppressions with Ax…
narekhovhannisyan 43a42bd
feat: create SuppressionsApi class for managing suppressions with Axi…
narekhovhannisyan 1520fdb
feat: add Suppression type definition for managing suppression data s…
narekhovhannisyan 8e47f33
Merge branch 'template-api-integration' of github.com:railsware/mailt…
narekhovhannisyan 13b8ad9
test: rename supression test to supressions
narekhovhannisyan a2c3e6e
refactor: update getList method in SuppressionsApi to accept ListOpti…
narekhovhannisyan a8ec09e
feat: add ListOptions type to enhance flexibility in suppression queries
narekhovhannisyan bdc8e9e
Merge branch 'main' of github.com:railsware/mailtrap-nodejs into supr…
narekhovhannisyan 88bff56
test: update Suppressions API test to pass email as an object
narekhovhannisyan 70541b5
refactor: update suppressions flow to improve clarity and use object …
narekhovhannisyan 8c28b49
test: enhance Suppressions API tests to specify error types and impro…
narekhovhannisyan 9312aa2
refactor: streamline suppressions getter by introducing account ID va…
narekhovhannisyan 3ba3365
fix: correct spelling of "initializes" in multiple test files
narekhovhannisyan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { MailtrapClient } from "mailtrap"; | ||
|
||
const TOKEN = "<YOUR-TOKEN-HERE>"; | ||
const ACCOUNT_ID = "<YOUR-ACCOUNT-ID-HERE>"; | ||
|
||
const client = new MailtrapClient({ | ||
token: TOKEN, | ||
accountId: ACCOUNT_ID | ||
}); | ||
|
||
async function suppressionsFlow() { | ||
// Get suppressions (up to 1000 per request) | ||
const suppressions = await client.suppressions.getList(); | ||
console.log("Suppressions (up to 1000):", suppressions); | ||
|
||
// Get suppressions filtered by email | ||
const filteredSuppressions = await client.suppressions.getList({email: "test@example.com"}); | ||
console.log("Filtered suppressions:", filteredSuppressions); | ||
|
||
// Delete a suppression by ID (if any exist) | ||
if (suppressions.length > 0) { | ||
const suppressionToDelete = suppressions[0]; | ||
await client.suppressions.delete(suppressionToDelete.id); | ||
console.log(`Suppression ${suppressionToDelete.id} deleted successfully`); | ||
} else { | ||
console.log("No suppressions found to delete"); | ||
} | ||
} | ||
|
||
suppressionsFlow().catch(console.error); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import axios from "axios"; | ||
|
||
import SuppressionsBaseAPI from "../../../lib/api/Suppressions"; | ||
|
||
describe("lib/api/Suppressions: ", () => { | ||
const accountId = 100; | ||
const suppressionsAPI = new SuppressionsBaseAPI(axios, accountId); | ||
|
||
describe("class SuppressionsBaseAPI(): ", () => { | ||
describe("init: ", () => { | ||
it("initalizes with all necessary params.", () => { | ||
expect(suppressionsAPI).toHaveProperty("getList"); | ||
expect(suppressionsAPI).toHaveProperty("delete"); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
import axios from "axios"; | ||
import AxiosMockAdapter from "axios-mock-adapter"; | ||
|
||
import SuppressionsApi from "../../../../lib/api/resources/Suppressions"; | ||
import handleSendingError from "../../../../lib/axios-logger"; | ||
import MailtrapError from "../../../../lib/MailtrapError"; | ||
import { Suppression } from "../../../../types/api/suppressions"; | ||
|
||
import CONFIG from "../../../../config"; | ||
|
||
const { CLIENT_SETTINGS } = CONFIG; | ||
const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; | ||
|
||
describe("lib/api/resources/Suppressions: ", () => { | ||
let mock: AxiosMockAdapter; | ||
const accountId = 100; | ||
const suppressionsAPI = new SuppressionsApi(axios, accountId); | ||
|
||
const mockSuppression: Suppression = { | ||
id: "1", | ||
email: "test@example.com", | ||
type: "hard bounce", | ||
created_at: "2023-01-01T00:00:00Z", | ||
sending_stream: "transactional", | ||
domain_name: "example.com", | ||
message_bounce_category: "bad_mailbox", | ||
message_category: "test", | ||
message_client_ip: "192.168.1.1", | ||
message_created_at: "2023-01-01T00:00:00Z", | ||
message_outgoing_ip: "10.0.0.1", | ||
message_recipient_mx_name: "mx.example.com", | ||
message_sender_email: "sender@example.com", | ||
message_subject: "Test Email", | ||
}; | ||
|
||
const mockSuppressions: Suppression[] = [ | ||
{ | ||
id: "1", | ||
email: "test1@example.com", | ||
type: "hard bounce", | ||
created_at: "2023-01-01T00:00:00Z", | ||
sending_stream: "transactional", | ||
domain_name: "example.com", | ||
message_bounce_category: "bad_mailbox", | ||
message_category: "test", | ||
message_client_ip: "192.168.1.1", | ||
message_created_at: "2023-01-01T00:00:00Z", | ||
message_outgoing_ip: "10.0.0.1", | ||
message_recipient_mx_name: "mx.example.com", | ||
message_sender_email: "sender@example.com", | ||
message_subject: "Test Email 1", | ||
}, | ||
{ | ||
id: "2", | ||
email: "test2@example.com", | ||
type: "spam complaint", | ||
created_at: "2023-01-02T00:00:00Z", | ||
sending_stream: "bulk", | ||
domain_name: "example.com", | ||
message_bounce_category: null, | ||
message_category: "promotional", | ||
message_client_ip: "192.168.1.2", | ||
message_created_at: "2023-01-02T00:00:00Z", | ||
message_outgoing_ip: "10.0.0.2", | ||
message_recipient_mx_name: "mx.example.com", | ||
message_sender_email: "sender@example.com", | ||
message_subject: "Test Email 2", | ||
}, | ||
]; | ||
|
||
describe("class SuppressionsApi(): ", () => { | ||
describe("init: ", () => { | ||
it("initializes with all necessary params.", () => { | ||
expect(suppressionsAPI).toHaveProperty("getList"); | ||
expect(suppressionsAPI).toHaveProperty("delete"); | ||
}); | ||
}); | ||
}); | ||
|
||
beforeAll(() => { | ||
axios.interceptors.response.use( | ||
(response) => response.data, | ||
handleSendingError | ||
); | ||
mock = new AxiosMockAdapter(axios); | ||
}); | ||
|
||
afterEach(() => { | ||
mock.reset(); | ||
}); | ||
|
||
describe("getList(): ", () => { | ||
it("successfully gets suppressions (up to 1000 per request).", async () => { | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions`; | ||
|
||
expect.assertions(2); | ||
|
||
mock.onGet(endpoint).reply(200, mockSuppressions); | ||
const result = await suppressionsAPI.getList(); | ||
|
||
expect(mock.history.get[0].url).toEqual(endpoint); | ||
expect(result).toEqual(mockSuppressions); | ||
}); | ||
|
||
it("successfully gets suppressions filtered by email.", async () => { | ||
const email = "test@example.com"; | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions`; | ||
|
||
expect.assertions(3); | ||
|
||
mock.onGet(endpoint, { params: { email } }).reply(200, [mockSuppression]); | ||
const result = await suppressionsAPI.getList({ email }); | ||
|
||
expect(mock.history.get[0].url).toEqual(endpoint); | ||
expect(mock.history.get[0].params).toEqual({ email }); | ||
expect(result).toEqual([mockSuppression]); | ||
}); | ||
|
||
it("fails with unauthorized error (401).", async () => { | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions`; | ||
const expectedErrorMessage = "Incorrect API token"; | ||
|
||
expect.assertions(2); | ||
|
||
mock.onGet(endpoint).reply(401, { error: expectedErrorMessage }); | ||
|
||
try { | ||
await suppressionsAPI.getList(); | ||
} catch (error) { | ||
expect(error).toBeInstanceOf(MailtrapError); | ||
if (error instanceof MailtrapError) { | ||
expect(error.message).toEqual(expectedErrorMessage); | ||
} | ||
} | ||
}); | ||
|
||
it("fails with forbidden error (403).", async () => { | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions`; | ||
const expectedErrorMessage = "Access forbidden"; | ||
|
||
expect.assertions(2); | ||
|
||
mock.onGet(endpoint).reply(403, { errors: expectedErrorMessage }); | ||
|
||
try { | ||
await suppressionsAPI.getList(); | ||
} catch (error) { | ||
expect(error).toBeInstanceOf(MailtrapError); | ||
if (error instanceof MailtrapError) { | ||
expect(error.message).toEqual(expectedErrorMessage); | ||
} | ||
} | ||
}); | ||
}); | ||
|
||
describe("delete(): ", () => { | ||
const suppressionId = "1"; | ||
|
||
it("successfully deletes a suppression.", async () => { | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions/${suppressionId}`; | ||
|
||
expect.assertions(1); | ||
|
||
mock.onDelete(endpoint).reply(204); | ||
await suppressionsAPI.delete(suppressionId); | ||
|
||
expect(mock.history.delete[0].url).toEqual(endpoint); | ||
}); | ||
|
||
it("fails with unauthorized error (401).", async () => { | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions/${suppressionId}`; | ||
const expectedErrorMessage = "Incorrect API token"; | ||
|
||
expect.assertions(2); | ||
|
||
mock.onDelete(endpoint).reply(401, { error: expectedErrorMessage }); | ||
|
||
try { | ||
await suppressionsAPI.delete(suppressionId); | ||
} catch (error) { | ||
expect(error).toBeInstanceOf(MailtrapError); | ||
if (error instanceof MailtrapError) { | ||
expect(error.message).toEqual(expectedErrorMessage); | ||
} | ||
} | ||
}); | ||
|
||
it("fails with forbidden error (403).", async () => { | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions/${suppressionId}`; | ||
const expectedErrorMessage = "Access forbidden"; | ||
|
||
expect.assertions(2); | ||
|
||
mock.onDelete(endpoint).reply(403, { errors: expectedErrorMessage }); | ||
|
||
try { | ||
await suppressionsAPI.delete(suppressionId); | ||
} catch (error) { | ||
expect(error).toBeInstanceOf(MailtrapError); | ||
if (error instanceof MailtrapError) { | ||
expect(error.message).toEqual(expectedErrorMessage); | ||
} | ||
} | ||
}); | ||
}); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ import { | |
BatchSendResponse, | ||
BatchSendRequest, | ||
} from "../types/mailtrap"; | ||
import SuppressionsBaseAPI from "./api/Suppressions"; | ||
|
||
const { CLIENT_SETTINGS, ERRORS } = CONFIG; | ||
const { | ||
|
@@ -146,6 +147,17 @@ export default class MailtrapClient { | |
return new TemplatesBaseAPI(this.axios, this.accountId); | ||
} | ||
|
||
/** | ||
* Getter for Suppressions API. | ||
*/ | ||
get suppressions() { | ||
if (!this.accountId) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please rebase |
||
throw new MailtrapError(ACCOUNT_ID_MISSING); | ||
} | ||
|
||
return new SuppressionsBaseAPI(this.axios, this.accountId); | ||
} | ||
|
||
/** | ||
* Returns configured host. Checks if `bulk` and `sandbox` modes are activated simultaneously, | ||
* then reject with Mailtrap Error. | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { AxiosInstance } from "axios"; | ||
|
||
import SuppressionsApi from "./resources/Suppressions"; | ||
|
||
export default class SuppressionsBaseAPI { | ||
private client: AxiosInstance; | ||
|
||
private accountId?: number; | ||
|
||
public getList: SuppressionsApi["getList"]; | ||
|
||
public delete: SuppressionsApi["delete"]; | ||
|
||
constructor(client: AxiosInstance, accountId?: number) { | ||
this.client = client; | ||
this.accountId = accountId; | ||
const suppressions = new SuppressionsApi(this.client, this.accountId); | ||
this.getList = suppressions.getList.bind(suppressions); | ||
this.delete = suppressions.delete.bind(suppressions); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { AxiosInstance } from "axios"; | ||
|
||
import CONFIG from "../../../config"; | ||
import { ListOptions, Suppression } from "../../../types/api/suppressions"; | ||
|
||
const { CLIENT_SETTINGS } = CONFIG; | ||
const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; | ||
|
||
export default class SuppressionsApi { | ||
private client: AxiosInstance; | ||
|
||
private accountId?: number; | ||
|
||
private suppressionsURL: string; | ||
|
||
constructor(client: AxiosInstance, accountId?: number) { | ||
this.client = client; | ||
this.accountId = accountId; | ||
this.suppressionsURL = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/suppressions`; | ||
} | ||
|
||
/** | ||
* List and search suppressions by email. The endpoint returns up to 1000 suppressions per request. | ||
*/ | ||
public async getList(options?: ListOptions) { | ||
const params = { | ||
...(options?.email && { email: options.email }), | ||
}; | ||
|
||
return this.client.get<Suppression[], Suppression[]>(this.suppressionsURL, { | ||
params, | ||
}); | ||
} | ||
|
||
/** | ||
* Delete a suppression by ID. | ||
* Mailtrap will no longer prevent sending to this email unless it's recorded in suppressions again. | ||
*/ | ||
public async delete(id: string) { | ||
return this.client.delete<Suppression, Suppression>( | ||
`${this.suppressionsURL}/${id}` | ||
); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A small typo: