diff --git a/README.md b/README.md index e0de9d6..edecd5a 100644 --- a/README.md +++ b/README.md @@ -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. + diff --git a/src/__tests__/lib/api/General.test.ts b/src/__tests__/lib/api/General.test.ts index 2053c45..99cbe79 100644 --- a/src/__tests__/lib/api/General.test.ts +++ b/src/__tests__/lib/api/General.test.ts @@ -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"); + }); }); }); }); diff --git a/src/lib/MailtrapClient.ts b/src/lib/MailtrapClient.ts index 7e22a6f..f88f65a 100644 --- a/src/lib/MailtrapClient.ts +++ b/src/lib/MailtrapClient.ts @@ -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; } /** @@ -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); } /** diff --git a/src/lib/api/ContactFields.ts b/src/lib/api/ContactFields.ts index 9b99c5d..12070bd 100644 --- a/src/lib/api/ContactFields.ts +++ b/src/lib/api/ContactFields.ts @@ -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"]; @@ -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); diff --git a/src/lib/api/ContactLists.ts b/src/lib/api/ContactLists.ts index 07f3604..23009ea 100644 --- a/src/lib/api/ContactLists.ts +++ b/src/lib/api/ContactLists.ts @@ -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"]; @@ -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); diff --git a/src/lib/api/Contacts.ts b/src/lib/api/Contacts.ts index 889a048..1c93be9 100644 --- a/src/lib/api/Contacts.ts +++ b/src/lib/api/Contacts.ts @@ -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"]; @@ -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); diff --git a/src/lib/api/General.ts b/src/lib/api/General.ts index 6931ffa..0c0df64 100644 --- a/src/lib/api/General.ts +++ b/src/lib/api/General.ts @@ -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; } } diff --git a/src/lib/api/Suppressions.ts b/src/lib/api/Suppressions.ts index c5da482..48027cf 100644 --- a/src/lib/api/Suppressions.ts +++ b/src/lib/api/Suppressions.ts @@ -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); } diff --git a/src/lib/api/Templates.ts b/src/lib/api/Templates.ts index 9d3f971..2b2c24b 100644 --- a/src/lib/api/Templates.ts +++ b/src/lib/api/Templates.ts @@ -3,10 +3,6 @@ import { AxiosInstance } from "axios"; import TemplatesApi from "./resources/Templates"; export default class TemplatesBaseAPI { - private client: AxiosInstance; - - private accountId?: number; - public get: TemplatesApi["get"]; public getList: TemplatesApi["getList"]; @@ -17,10 +13,8 @@ export default class TemplatesBaseAPI { public delete: TemplatesApi["delete"]; - constructor(client: AxiosInstance, accountId?: number) { - this.client = client; - this.accountId = accountId; - const templates = new TemplatesApi(this.client, this.accountId); + constructor(client: AxiosInstance, accountId: number) { + const templates = new TemplatesApi(client, accountId); this.get = templates.get.bind(templates); this.getList = templates.getList.bind(templates); this.create = templates.create.bind(templates); diff --git a/src/lib/api/Testing.ts b/src/lib/api/Testing.ts index bf2b58e..7569fd2 100644 --- a/src/lib/api/Testing.ts +++ b/src/lib/api/Testing.ts @@ -6,10 +6,6 @@ import MessagesApi from "./resources/Messages"; import AttachmentsApi from "./resources/Attachments"; export default class TestingAPI { - private client: AxiosInstance; - - private accountId?: number; - public projects: ProjectsApi; public inboxes: InboxesApi; @@ -18,12 +14,10 @@ export default class TestingAPI { public attachments: AttachmentsApi; - constructor(client: AxiosInstance, accountId?: number) { - this.client = client; - this.accountId = accountId; - this.projects = new ProjectsApi(this.client, this.accountId); - this.inboxes = new InboxesApi(this.client, this.accountId); - this.messages = new MessagesApi(this.client, this.accountId); - this.attachments = new AttachmentsApi(this.client, this.accountId); + constructor(client: AxiosInstance, accountId: number) { + this.projects = new ProjectsApi(client, accountId); + this.inboxes = new InboxesApi(client, accountId); + this.messages = new MessagesApi(client, accountId); + this.attachments = new AttachmentsApi(client, accountId); } } diff --git a/src/lib/api/resources/AccountAccesses.ts b/src/lib/api/resources/AccountAccesses.ts index f494eaa..5fe6efb 100644 --- a/src/lib/api/resources/AccountAccesses.ts +++ b/src/lib/api/resources/AccountAccesses.ts @@ -14,14 +14,11 @@ const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; export default class AccountsAccessesApi { private client: AxiosInstance; - private accountId?: number; - private accountAccessesURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; - this.accountId = accountId; - this.accountAccessesURL = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/account_accesses`; + this.accountAccessesURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/account_accesses`; } /** diff --git a/src/lib/api/resources/Attachments.ts b/src/lib/api/resources/Attachments.ts index 2edf17e..f6622bb 100644 --- a/src/lib/api/resources/Attachments.ts +++ b/src/lib/api/resources/Attachments.ts @@ -10,14 +10,11 @@ const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; export default class AttachmentsApi { private client: AxiosInstance; - private accountId?: number; - private inboxesURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; - this.accountId = accountId; - this.inboxesURL = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/inboxes`; + this.inboxesURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/inboxes`; } /** diff --git a/src/lib/api/resources/ContactFields.ts b/src/lib/api/resources/ContactFields.ts index 8f4c343..f5439c3 100644 --- a/src/lib/api/resources/ContactFields.ts +++ b/src/lib/api/resources/ContactFields.ts @@ -15,7 +15,7 @@ export default class ContactFieldsApi { private contactFieldsURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; this.contactFieldsURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/fields`; } diff --git a/src/lib/api/resources/ContactLists.ts b/src/lib/api/resources/ContactLists.ts index 40db668..9689144 100644 --- a/src/lib/api/resources/ContactLists.ts +++ b/src/lib/api/resources/ContactLists.ts @@ -14,7 +14,7 @@ export default class ContactListsApi { private contactListsURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; this.contactListsURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/lists`; } diff --git a/src/lib/api/resources/Contacts.ts b/src/lib/api/resources/Contacts.ts index ad1a18a..6b518ec 100644 --- a/src/lib/api/resources/Contacts.ts +++ b/src/lib/api/resources/Contacts.ts @@ -15,7 +15,7 @@ export default class ContactsApi { private contactsURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; this.contactsURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts`; } diff --git a/src/lib/api/resources/Inboxes.ts b/src/lib/api/resources/Inboxes.ts index 18e06ad..4c53fda 100644 --- a/src/lib/api/resources/Inboxes.ts +++ b/src/lib/api/resources/Inboxes.ts @@ -10,11 +10,11 @@ const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; export default class InboxesApi { private client: AxiosInstance; - private accountId?: number; + private accountId: number; private inboxesURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; this.accountId = accountId; this.inboxesURL = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/inboxes`; diff --git a/src/lib/api/resources/Messages.ts b/src/lib/api/resources/Messages.ts index 8037393..4e34239 100644 --- a/src/lib/api/resources/Messages.ts +++ b/src/lib/api/resources/Messages.ts @@ -18,7 +18,7 @@ export default class MessagesApi { private messagesURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; this.messagesURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/inboxes`; } diff --git a/src/lib/api/resources/Permissions.ts b/src/lib/api/resources/Permissions.ts index 6565198..eb17aa9 100644 --- a/src/lib/api/resources/Permissions.ts +++ b/src/lib/api/resources/Permissions.ts @@ -13,14 +13,11 @@ const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; export default class PermissionsApi { private client: AxiosInstance; - private accountId?: number; - private permissionsURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; - this.accountId = accountId; - this.permissionsURL = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/account_accesses`; + this.permissionsURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/account_accesses`; } /** @@ -36,7 +33,7 @@ export default class PermissionsApi { accountAccessId: number, permissions: PermissionResourceParams[] ) { - const url = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/account_accesses/${accountAccessId}/permissions/bulk`; + const url = `${this.permissionsURL}/${accountAccessId}/permissions/bulk`; const flattenPermissionObjects = permissions.map((permission) => ({ resource_id: permission.resourceId, diff --git a/src/lib/api/resources/Projects.ts b/src/lib/api/resources/Projects.ts index e3194c7..e34952e 100644 --- a/src/lib/api/resources/Projects.ts +++ b/src/lib/api/resources/Projects.ts @@ -10,14 +10,11 @@ const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; export default class ProjectsApi { private client: AxiosInstance; - private accountId?: number; - private projectsURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; - this.accountId = accountId; - this.projectsURL = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/projects`; + this.projectsURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/projects`; } /** diff --git a/src/lib/api/resources/Suppressions.ts b/src/lib/api/resources/Suppressions.ts index 2c10529..3a76cf5 100644 --- a/src/lib/api/resources/Suppressions.ts +++ b/src/lib/api/resources/Suppressions.ts @@ -9,14 +9,11 @@ const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; export default class SuppressionsApi { private client: AxiosInstance; - private accountId?: number; - private suppressionsURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; - this.accountId = accountId; - this.suppressionsURL = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/suppressions`; + this.suppressionsURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions`; } /** diff --git a/src/lib/api/resources/Templates.ts b/src/lib/api/resources/Templates.ts index de43117..7c036d4 100644 --- a/src/lib/api/resources/Templates.ts +++ b/src/lib/api/resources/Templates.ts @@ -13,14 +13,11 @@ const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; export default class TemplatesApi { private client: AxiosInstance; - private accountId?: number; - private templatesURL: string; - constructor(client: AxiosInstance, accountId?: number) { + constructor(client: AxiosInstance, accountId: number) { this.client = client; - this.accountId = accountId; - this.templatesURL = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/email_templates`; + this.templatesURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/email_templates`; } /**