Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
1e9b176
refactor: change accountId from optional to required in API resource …
narekhovhannisyan Oct 7, 2025
74d82a1
refactor: update accountId to be a required parameter in multiple API…
narekhovhannisyan Oct 7, 2025
ec355c7
types: make accountId a required parameter in MailtrapClientConfig
narekhovhannisyan Oct 7, 2025
bb94a6a
refactor: change accountId from optional to required in MailtrapClien…
narekhovhannisyan Oct 7, 2025
dd9ac05
refactor: streamline accountId usage in API resource constructors
narekhovhannisyan Oct 7, 2025
dfb1032
refactor: simplify API constructors by removing unnecessary private f…
narekhovhannisyan Oct 7, 2025
db1b2bd
refactor: update validateAccountIdPresence to return accountId and ad…
narekhovhannisyan Oct 7, 2025
234d2dd
types: make accountId optional in MailtrapClientConfig
narekhovhannisyan Oct 7, 2025
60da81e
docs: add compatibility note for previous package versions in README
narekhovhannisyan Oct 9, 2025
9f6e4b0
refactor: enhance GeneralAPI to conditionally instantiate account-spe…
narekhovhannisyan Oct 10, 2025
15413d6
test: expand GeneralAPI tests to cover accountId presence and lazy in…
narekhovhannisyan Oct 10, 2025
974812f
refactor: use nullish coalescing operator for accountId initializatio…
narekhovhannisyan Oct 10, 2025
d98c06a
test: add test case for handling accountId value of 0 in GeneralAPI
narekhovhannisyan Oct 10, 2025
2eab139
test: update GeneralAPI tests to reflect lazy instantiation behavior …
narekhovhannisyan Oct 10, 2025
063e0bb
test: remove redundant lazy instantiation tests for account-specific …
narekhovhannisyan Oct 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,4 @@ Everyone interacting in the Mailtrap project's codebases, issue trackers, chat r
## Compatibility with previous releases

Versions of this package up to 2.0.2 were an [unofficial client](https://github.com/vchin/mailtrap-client) developed by [@vchin](https://github.com/vchin). Package version 3 is a completely new package.

109 changes: 105 additions & 4 deletions src/__tests__/lib/api/General.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,117 @@ import axios from "axios";
import General from "../../../lib/api/General";

describe("lib/api/General: ", () => {
const testInboxId = 100;
const generalAPI = new General(axios, testInboxId);
const testAccountId = 100;

describe("class General(): ", () => {
describe("init: ", () => {
it("initializes with all necessary params.", () => {
describe("constructor with accountId: ", () => {
const generalAPI = new General(axios, testAccountId);

it("initializes with all necessary properties when accountId is provided.", () => {
expect(generalAPI).toHaveProperty("accountAccesses");
expect(generalAPI).toHaveProperty("accounts");
expect(generalAPI).toHaveProperty("permissions");
});

it("lazily instantiates account-specific APIs via getters when accountId is provided.", () => {
expect(generalAPI.accountAccesses).toBeDefined();
expect(generalAPI.permissions).toBeDefined();
expect(generalAPI.accounts).toBeDefined();
});
});

describe("constructor without accountId: ", () => {
const generalAPI = new General(axios);

it("initializes with accounts property when accountId is not provided.", () => {
expect(generalAPI).toHaveProperty("accounts");
expect(generalAPI.accounts).toBeDefined();
});

it("allows access to accounts API for account discovery.", () => {
expect(generalAPI.accounts).toBeDefined();
expect(typeof generalAPI.accounts.getAllAccounts).toBe("function");
});
});

describe("lazy instantiation: ", () => {
const generalAPI = new General(axios);

it("throws error when accessing accountAccesses without accountId.", () => {
expect(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
generalAPI.accountAccesses;
}).toThrow(
"Account ID is required for this operation. Please provide accountId when creating GeneralAPI instance."
);
});

it("throws error when accessing permissions without accountId.", () => {
expect(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
generalAPI.permissions;
}).toThrow(
"Account ID is required for this operation. Please provide accountId when creating GeneralAPI instance."
);
});
});

describe("account discovery functionality: ", () => {
const generalAPI = new General(axios);

it("provides accounts API for listing all accounts.", () => {
expect(generalAPI.accounts).toBeDefined();
expect(typeof generalAPI.accounts.getAllAccounts).toBe("function");
});

it("does not require accountId for account discovery.", () => {
// This should not throw an error
expect(generalAPI.accounts).toBeDefined();
});
});

describe("backward compatibility: ", () => {
const generalAPI = new General(axios, testAccountId);

it("maintains existing API surface for account-specific operations.", () => {
expect(generalAPI.accountAccesses).toBeDefined();
expect(generalAPI.permissions).toBeDefined();
expect(typeof generalAPI.accountAccesses.listAccountAccesses).toBe(
"function"
);
expect(typeof generalAPI.permissions.getResources).toBe("function");
});
});

describe("edge cases: ", () => {
it("handles undefined accountId parameter.", () => {
const generalAPI = new General(axios, undefined);
expect(generalAPI.accounts).toBeDefined();
expect(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
generalAPI.accountAccesses;
}).toThrow();
});

it("handles null accountId parameter.", () => {
const generalAPI = new General(axios, null as any);
expect(generalAPI.accounts).toBeDefined();
expect(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
generalAPI.accountAccesses;
}).toThrow();
});

it("handles accountId value of 0 correctly.", () => {
const generalAPI = new General(axios, 0);
expect(generalAPI.accounts).toBeDefined();
expect(generalAPI.accountAccesses).toBeDefined();
expect(generalAPI.permissions).toBeDefined();
expect(typeof generalAPI.accountAccesses.listAccountAccesses).toBe(
"function"
);
expect(typeof generalAPI.permissions.getResources).toBe("function");
});
});
});
});
38 changes: 16 additions & 22 deletions src/lib/MailtrapClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,11 @@ export default class MailtrapClient {
/**
* Validates that account ID is present, throws MailtrapError if missing.
*/
private validateAccountIdPresence(): void {
private validateAccountIdPresence(): number {
if (!this.accountId) {
throw new MailtrapError(ACCOUNT_ID_MISSING);
}
return this.accountId;
}

/**
Expand All @@ -108,63 +109,56 @@ export default class MailtrapClient {
* Getter for Testing API. Warns if some of the required keys are missing.
*/
get testing() {
this.validateAccountIdPresence();

return new TestingAPI(this.axios, this.accountId);
const accountId = this.validateAccountIdPresence();
return new TestingAPI(this.axios, accountId);
}

/**
* Getter for General API.
*/
get general() {
this.validateAccountIdPresence();

return new GeneralAPI(this.axios, this.accountId);
const accountId = this.validateAccountIdPresence();
return new GeneralAPI(this.axios, accountId);
}

/**
* Getter for Contacts API.
*/
get contacts() {
this.validateAccountIdPresence();

return new ContactsBaseAPI(this.axios, this.accountId);
const accountId = this.validateAccountIdPresence();
return new ContactsBaseAPI(this.axios, accountId);
}

/**
* Getter for Contact Lists API.
*/
get contactLists() {
this.validateAccountIdPresence();

return new ContactListsBaseAPI(this.axios, this.accountId);
const accountId = this.validateAccountIdPresence();
return new ContactListsBaseAPI(this.axios, accountId);
}

/**
* Getter for Contact Fields API.
*/
get contactFields() {
this.validateAccountIdPresence();

return new ContactFieldsBaseAPI(this.axios, this.accountId);
const accountId = this.validateAccountIdPresence();
return new ContactFieldsBaseAPI(this.axios, accountId);
}

/**
* Getter for Templates API.
*/
get templates() {
this.validateAccountIdPresence();

return new TemplatesBaseAPI(this.axios, this.accountId);
const accountId = this.validateAccountIdPresence();
return new TemplatesBaseAPI(this.axios, accountId);
}

/**
* Getter for Suppressions API.
*/
get suppressions() {
this.validateAccountIdPresence();

return new SuppressionsBaseAPI(this.axios, this.accountId);
const accountId = this.validateAccountIdPresence();
return new SuppressionsBaseAPI(this.axios, accountId);
}

/**
Expand Down
10 changes: 2 additions & 8 deletions src/lib/api/ContactFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { AxiosInstance } from "axios";
import ContactFieldsApi from "./resources/ContactFields";

export default class ContactFieldsBaseAPI {
private client: AxiosInstance;

private accountId?: number;

public create: ContactFieldsApi["create"];

public get: ContactFieldsApi["get"];
Expand All @@ -17,10 +13,8 @@ export default class ContactFieldsBaseAPI {

public delete: ContactFieldsApi["delete"];

constructor(client: AxiosInstance, accountId?: number) {
this.client = client;
this.accountId = accountId;
const contactFields = new ContactFieldsApi(this.client, this.accountId);
constructor(client: AxiosInstance, accountId: number) {
const contactFields = new ContactFieldsApi(client, accountId);
this.create = contactFields.create.bind(contactFields);
this.get = contactFields.get.bind(contactFields);
this.getList = contactFields.getList.bind(contactFields);
Expand Down
10 changes: 2 additions & 8 deletions src/lib/api/ContactLists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { AxiosInstance } from "axios";
import ContactListsApi from "./resources/ContactLists";

export default class ContactListsBaseAPI {
private client: AxiosInstance;

private accountId?: number;

public create: ContactListsApi["create"];

public get: ContactListsApi["get"];
Expand All @@ -17,10 +13,8 @@ export default class ContactListsBaseAPI {

public delete: ContactListsApi["delete"];

constructor(client: AxiosInstance, accountId?: number) {
this.client = client;
this.accountId = accountId;
const contactLists = new ContactListsApi(this.client, this.accountId);
constructor(client: AxiosInstance, accountId: number) {
const contactLists = new ContactListsApi(client, accountId);
this.create = contactLists.create.bind(contactLists);
this.get = contactLists.get.bind(contactLists);
this.getList = contactLists.getList.bind(contactLists);
Expand Down
10 changes: 2 additions & 8 deletions src/lib/api/Contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { AxiosInstance } from "axios";
import ContactsApi from "./resources/Contacts";

export default class ContactsBaseAPI {
private client: AxiosInstance;

private accountId?: number;

public get: ContactsApi["get"];

public create: ContactsApi["create"];
Expand All @@ -15,10 +11,8 @@ export default class ContactsBaseAPI {

public delete: ContactsApi["delete"];

constructor(client: AxiosInstance, accountId?: number) {
this.client = client;
this.accountId = accountId;
const contacts = new ContactsApi(this.client, this.accountId);
constructor(client: AxiosInstance, accountId: number) {
const contacts = new ContactsApi(client, accountId);
this.get = contacts.get.bind(contacts);
this.create = contacts.create.bind(contacts);
this.update = contacts.update.bind(contacts);
Expand Down
55 changes: 46 additions & 9 deletions src/lib/api/General.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,58 @@ import AccountsApi from "./resources/Accounts";
import PermissionsApi from "./resources/Permissions";

export default class GeneralAPI {
private client: AxiosInstance;
public accounts: AccountsApi;

private accountId?: number;
private client: AxiosInstance;

public accountAccesses: AccountAccessesApi;
private accountId: number | null = null;

public accounts: AccountsApi;
private accountAccessesInstance: AccountAccessesApi | null = null;

public permissions: PermissionsApi;
private permissionsInstance: PermissionsApi | null = null;

constructor(client: AxiosInstance, accountId?: number) {
this.client = client;
this.accountId = accountId;
this.accountAccesses = new AccountAccessesApi(this.client, this.accountId);
this.accounts = new AccountsApi(this.client);
this.permissions = new PermissionsApi(this.client, this.accountId);
this.accountId = accountId ?? null;
this.accounts = new AccountsApi(client);

// Only instantiate account-specific APIs if accountId is provided
if (this.accountId !== null) {
this.accountAccessesInstance = new AccountAccessesApi(
client,
this.accountId
);
this.permissionsInstance = new PermissionsApi(client, this.accountId);
}
}

private checkAccountIdPresence(): number {
if (this.accountId === null) {
throw new Error(
"Account ID is required for this operation. Please provide accountId when creating GeneralAPI instance."
);
}
return this.accountId;
}

public get accountAccesses(): AccountAccessesApi {
if (this.accountAccessesInstance === null) {
const accountId = this.checkAccountIdPresence();
this.accountAccessesInstance = new AccountAccessesApi(
this.client,
accountId
);
}

return this.accountAccessesInstance;
}

public get permissions(): PermissionsApi {
if (this.permissionsInstance === null) {
const accountId = this.checkAccountIdPresence();
this.permissionsInstance = new PermissionsApi(this.client, accountId);
}

return this.permissionsInstance;
}
}
10 changes: 2 additions & 8 deletions src/lib/api/Suppressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@ 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);
constructor(client: AxiosInstance, accountId: number) {
const suppressions = new SuppressionsApi(client, accountId);
this.getList = suppressions.getList.bind(suppressions);
this.delete = suppressions.delete.bind(suppressions);
}
Expand Down
Loading