From 9983109a5164edcb2ae7ceba7d305510007a0b94 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Thu, 19 Dec 2024 19:10:51 -0500 Subject: [PATCH] Add integration tests --- .fernignore | 3 +- tests/integration/admins.test.ts | 61 ++++ tests/integration/articles.test.ts | 116 +++++++ tests/integration/companies.test.ts | 148 +++++++++ tests/integration/contacts.test.ts | 243 +++++++++++++++ tests/integration/conversations.test.ts | 286 ++++++++++++++++++ tests/integration/dataAttributes.test.ts | 67 ++++ tests/integration/dataExport.test.ts | 57 ++++ tests/integration/events.test.ts | 69 +++++ .../helpCenter/collections.test.ts | 105 +++++++ tests/integration/helpers.ts | 87 ++++++ tests/integration/integration.test.ts | 86 ++++++ tests/integration/messages.test.ts | 81 +++++ tests/integration/notes.test.ts | 65 ++++ tests/integration/pagination.test.ts | 148 +++++++++ tests/integration/phoneCallRedirect.test.ts | 20 ++ tests/integration/segments.test.ts | 31 ++ tests/integration/subscriptions.test.ts | 13 + tests/integration/tags.test.ts | 217 +++++++++++++ tests/integration/teams.test.ts | 29 ++ tests/integration/utils/createClient.ts | 5 + tests/integration/utils/date.ts | 1 + tests/integration/utils/random.ts | 6 + tests/integration/utils/wait.ts | 3 + tests/integration/visitors.test.ts | 53 ++++ 25 files changed, 1999 insertions(+), 1 deletion(-) create mode 100644 tests/integration/admins.test.ts create mode 100644 tests/integration/articles.test.ts create mode 100644 tests/integration/companies.test.ts create mode 100644 tests/integration/contacts.test.ts create mode 100644 tests/integration/conversations.test.ts create mode 100644 tests/integration/dataAttributes.test.ts create mode 100644 tests/integration/dataExport.test.ts create mode 100644 tests/integration/events.test.ts create mode 100644 tests/integration/helpCenter/collections.test.ts create mode 100644 tests/integration/helpers.ts create mode 100644 tests/integration/integration.test.ts create mode 100644 tests/integration/messages.test.ts create mode 100644 tests/integration/notes.test.ts create mode 100644 tests/integration/pagination.test.ts create mode 100644 tests/integration/phoneCallRedirect.test.ts create mode 100644 tests/integration/segments.test.ts create mode 100644 tests/integration/subscriptions.test.ts create mode 100644 tests/integration/tags.test.ts create mode 100644 tests/integration/teams.test.ts create mode 100644 tests/integration/utils/createClient.ts create mode 100644 tests/integration/utils/date.ts create mode 100644 tests/integration/utils/random.ts create mode 100644 tests/integration/utils/wait.ts create mode 100644 tests/integration/visitors.test.ts diff --git a/.fernignore b/.fernignore index 41e2e16..57fb11d 100644 --- a/.fernignore +++ b/.fernignore @@ -3,4 +3,5 @@ .github/PULL_REQUEST_TEMPLATE.md .gitattributes LICENSE -REPO_OWNER \ No newline at end of file +REPO_OWNER +tests/integration \ No newline at end of file diff --git a/tests/integration/admins.test.ts b/tests/integration/admins.test.ts new file mode 100644 index 0000000..8302bf1 --- /dev/null +++ b/tests/integration/admins.test.ts @@ -0,0 +1,61 @@ +import { createClient } from "./utils/createClient"; + +describe("Admins", () => { + let adminId: string; + const client = createClient(); + + beforeAll(async () => { + // arrange + const adminList = await client.admins.list(); + adminId = adminList.admins[0].id; + }); + + it("list", async () => { + // act + const response = await client.admins.list(); + + // assert + expect(response).toBeDefined(); + }); + it("find", async () => { + // act + const response = await client.admins.find({ admin_id: adminId }); + + // assert + expect(response).toBeDefined(); + }); + + it("listAllActivityLogs", async () => { + // act + const response = await client.admins.listAllActivityLogs({ + created_at_after: new Date("2021-12-12").toISOString(), + created_at_before: new Date("2022-01-01").toISOString(), + }); + + // assert + expect(response).toBeDefined(); + }); + + it("away - ON", async () => { + // act + const response = await client.admins.away({ + admin_id: adminId, + away_mode_enabled: true, + away_mode_reassign: true, + }); + + // assert + expect(response).toBeDefined(); + }); + it("away - OFF", async () => { + // act + const response = await client.admins.away({ + admin_id: adminId, + away_mode_enabled: false, + away_mode_reassign: false, + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/articles.test.ts b/tests/integration/articles.test.ts new file mode 100644 index 0000000..1966891 --- /dev/null +++ b/tests/integration/articles.test.ts @@ -0,0 +1,116 @@ +import { IntercomClient as Client } from "../../src"; +import { randomString } from "./utils/random"; +import { createClient } from "./utils/createClient"; + +async function createArticle(client: Client, parentId: number, adminId: number) { + return await client.articles.create({ + title: randomString(), + description: randomString(), + body: "Eins Zwei", + author_id: adminId, + state: "draft", + parent_id: parentId, + parent_type: "collection", + translated_content: { + fr: { + type: "article_content", + title: "Allez les verts", + description: "French description", + body: "

French body in html

", + author_id: adminId, + state: "draft", + }, + }, + }); +} + +async function tryDeleteArticle(client: Client, articleId: string) { + try { + await client.articles.delete({ article_id: articleId }); + } catch (error) { + console.error("Failed to delete article:", error); + } +} + +describe("Articles", () => { + let articleId: string; + let parentId: number; + let adminId: number; + const client = createClient(); + + beforeAll(async () => { + // arrange + const randomCollections = await client.helpCenters.collections.list({ + per_page: 1, + }); + const randomAdmins = await client.admins.list(); + + parentId = parseInt(randomCollections.data[0].id, 10); + adminId = parseInt(randomAdmins.admins[0].id, 10); + + const article = await createArticle(client, parentId, adminId); + articleId = article.id; + }); + + afterAll(async () => { + // cleanup + await tryDeleteArticle(client, articleId); + }); + + it("create", async () => { + // act + const article = await createArticle(client, parentId, adminId); + + // assert + expect(article).toBeDefined(); + + // cleanup + await tryDeleteArticle(client, article.id); + }); + + it("find", async () => { + // act + const response = await client.articles.find({ article_id: articleId }); + + // assert + expect(response).toBeDefined(); + }); + + it("update", async () => { + // arrange + const article = await createArticle(client, parentId, adminId); + + // act + const response = await client.articles.update({ + article_id: article.id, + body: { + title: "Biba & Boba", + }, + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteArticle(client, article.id); + }); + + it("list", async () => { + // act + const response = await client.articles.list({ page: 1, per_page: 12 }); + + // assert + expect(response).toBeDefined(); + }); + + it("delete", async () => { + // arrange + const article = await createArticle(client, parentId, adminId); + + // act + const response = await client.articles.delete({ article_id: article.id }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/companies.test.ts b/tests/integration/companies.test.ts new file mode 100644 index 0000000..078d81e --- /dev/null +++ b/tests/integration/companies.test.ts @@ -0,0 +1,148 @@ +import { Intercom } from "../../src"; +import { dateToUnixTimestamp } from "./utils/date"; +import { createCompany, tryDeleteCompany } from "./helpers"; +import { createClient } from "./utils/createClient"; + +describe("Companies", () => { + let contactId: string; + let company: Intercom.Company; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const randomContacts = await client.contacts.list({ + per_page: 1, + }); + + contactId = randomContacts.data[0].id; + company = await createCompany(client); + }); + + afterAll(async () => { + // cleanup + await tryDeleteCompany(client, company.id); + }); + + it("create", async () => { + // act + const company = await createCompany(client); + + // assert + expect(company).toBeDefined(); + + // cleanup + await tryDeleteCompany(client, company.id); + }); + + it("update", async () => { + // arrange + const company = await createCompany(client); + + // act + const response = await client.companies.createOrUpdate({ + company_id: company.company_id, + remote_created_at: dateToUnixTimestamp(new Date()), + name: "BestCompanyInc", + monthly_spend: 9001, + plan: "1. Get pizzaid", + size: 62049, + website: "http://the-best.one", + industry: "The Best One", + custom_attributes: {}, + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteCompany(client, company.id); + }); + + it("find - by id", async () => { + // act + const response = await client.companies.find({ + company_id: company.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("list", async () => { + // act + const response = await client.companies.list({ + page: 1, + per_page: 35, + order: "desc", + }); + + // assert + expect(response).toBeDefined(); + }); + + it("delete", async () => { + // arrange + const company = await createCompany(client); + + // act + const response = await client.companies.delete({ + company_id: company.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it.skip("scroll - infinite one", async () => { + // act + const response = await client.companies.scroll(); + + // assert + expect(response.data).toBeDefined(); + }); + + it("attachContact", async () => { + // act + const response = await client.companies.attachContact({ + contact_id: contactId, + id: company.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("detachContact", async () => { + // act + const response = await client.companies.detachContact({ + contact_id: contactId, + company_id: company.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("listAttachedContacts", async () => { + // act + const response = await client.companies.listAttachedContacts({ + company_id: company.id, + page: 1, + per_page: 25, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("listAttachedSegments", async () => { + // act + const response = await client.companies.listAttachedSegments({ + company_id: company.id, + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/contacts.test.ts b/tests/integration/contacts.test.ts new file mode 100644 index 0000000..633714d --- /dev/null +++ b/tests/integration/contacts.test.ts @@ -0,0 +1,243 @@ +import { Intercom } from "../../src"; +import { dateToUnixTimestamp } from "./utils/date"; +import { randomString } from "./utils/random"; +import { createContact, tryDeleteCompany, tryDeleteContact, tryUntagContact } from "./helpers"; +import { createClient } from "./utils/createClient"; + +describe("Contacts", () => { + let subscriptionId: string; + let tag: Intercom.Tag; + let contact: Intercom.Contact; + let company: Intercom.Company; + + const client = createClient(); + + beforeAll(async () => { + // arrange + contact = await createContact(client); + + company = await client.companies.createOrUpdate({ + remote_created_at: dateToUnixTimestamp(new Date()), + company_id: randomString(), + name: randomString(), + monthly_spend: 9001, + plan: "1. Get pizzaid", + size: 62049, + website: "http://the-best.one", + industry: "The Best One", + custom_attributes: {}, + }); + + await client.companies.attachContact({ + id: company.id, + contact_id: contact.id, + }); + + const subscriptionTypes = await client.subscriptionTypes.list(); + subscriptionId = subscriptionTypes.data[0].id; + await client.contacts.attachSubscription({ + contact_id: contact.id, + id: subscriptionId, + consent_type: "opt_in", + }); + + tag = await client.tags.create({ + name: randomString(), + }); + await client.tags.tagContact({ + contact_id: contact.id, + id: tag.id, + }); + }); + + afterAll(async () => { + // cleanup + try { + await client.contacts.detachSubscription({ + contact_id: contact.id, + subscription_id: subscriptionId, + }); + } catch (error) { + console.error("Failed to detach subscription:", error); + } + await tryUntagContact(client, contact.id, tag.id); + await tryDeleteCompany(client, company.id); + await tryDeleteContact(client, contact.id); + }); + + it("list", async () => { + // act + const response = await client.contacts.list({ + per_page: 5, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("createUser", async () => { + // act + const response = await client.contacts.create({ + external_id: randomString(), + phone: "+353871234567", + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteContact(client, response.id); + }); + + it("createLead", async () => { + // act + const response = await client.contacts.create({ + name: "Roman Bowling", + role: "lead", + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteContact(client, response.id); + }); + + it("find - by id", async () => { + // act + const response = await client.contacts.find({ + contact_id: contact.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("update", async () => { + // arrange + const contact = await createContact(client); + + // act + const response = await client.contacts.update({ + contact_id: contact.id, + name: "Nico Bellic", + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteContact(client, contact.id); + }); + + it("archive", async () => { + // arrange + const contact = await createContact(client); + + // act + const response = await client.contacts.archive({ + contact_id: contact.id, + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteContact(client, contact.id); + }); + + it("unarchive", async () => { + // arrange + const contact = await createContact(client); + + // act + const response = await client.contacts.unarchive({ + contact_id: contact.id, + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteContact(client, contact.id); + }); + + it("delete", async () => { + // arrange + const contact = await client.contacts.create({ + external_id: randomString(), + phone: "+353871234567", + }); + + // act + const response = await client.contacts.delete({ + contact_id: contact.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("mergeLeadInUser", async () => { + // arrange + const createdUser = await client.contacts.create({ + external_id: randomString(), + phone: "+353871234567", + }); + const createdLead = await client.contacts.create({ + name: "Roman Bowling", + role: "lead", + }); + const response = await client.contacts.mergeLeadInUser({ + from: createdLead.id, + into: createdUser.id, + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteContact(client, createdUser.id); + await tryDeleteContact(client, createdLead.id); + }); + + it("listAttachedCompanies", async () => { + // act + const response = await client.contacts.listAttachedCompanies({ + contact_id: contact.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("listAttachedEmailSubscriptions", async () => { + // act + const response = await client.contacts.listAttachedSubscriptions({ + contact_id: contact.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("listAttachedSegments", async () => { + // act + const response = await client.contacts.listAttachedSegments({ + contact_id: contact.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("listAttachedTags", async () => { + // act + const response = await client.contacts.listAttachedTags({ + contact_id: contact.id, + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/conversations.test.ts b/tests/integration/conversations.test.ts new file mode 100644 index 0000000..c24b396 --- /dev/null +++ b/tests/integration/conversations.test.ts @@ -0,0 +1,286 @@ +import { Intercom } from "../../src"; +import { randomString } from "./utils/random"; +import { createConversation, tryDeleteContact } from "./helpers"; +import { wait } from "./utils/wait"; +import { createClient } from "./utils/createClient"; + +describe("Conversations", () => { + let user: Intercom.Contact; + let secondUser: Intercom.Contact; + let lead: Intercom.Contact; + + let adminId: string; + let secondAdminId: string; + let conversationId: string; + let conversation: Intercom.Conversation; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const admins = await client.admins.list(); + + const adminList = admins.admins.filter((admin) => admin.has_inbox_seat); + // Only admins with inbox seat can interact with conversations. + adminId = adminList[0].id; + secondAdminId = adminList[1].id; + user = await client.contacts.create({ + external_id: randomString(), + name: "Baba Booey", + }); + secondUser = await client.contacts.create({ + external_id: randomString(), + name: "Babushka Boy", + email: "babushka_boy@bababooey.com", + }); + lead = await client.contacts.create({ + name: "Babushka Lead", + email: "babushka_lead@bababooey.com", + role: "lead", + }); + + const conversationMessage = await client.conversations.create({ + from: { id: user.id, type: "user" }, + body: "Raz-dwa-try kalyna, czorniawaja diwczyna", + }); + conversationId = conversationMessage.conversation_id; + + // Give Intercom a few seconds to index conversation + await wait(5000); + + conversation = await client.conversations.find({ + conversation_id: conversationId, + }); + }, 20_000); + + afterAll(async () => { + // cleanup + await tryDeleteContact(client, user.id); + await tryDeleteContact(client, secondUser.id); + await tryDeleteContact(client, lead.id); + }); + + it("create conversation with user as default", async () => { + // act + const response = await client.conversations.create({ + from: { id: user.id, type: "user" }, + body: "Raz-dwa-try kalyna, czorniawaja diwczyna", + }); + + // assert + expect(response).toBeDefined(); + }); + + it("create conversation with user", async () => { + // act + const response = await client.conversations.create({ + from: { id: user.id, type: "user" }, + body: "Raz-dwa-try kalyna, czorniawaja diwczyna", + }); + + // assert + expect(response).toBeDefined(); + }); + + it("create conversation with lead", async () => { + // act + const response = await client.conversations.create({ + from: { id: lead.id, type: "lead" }, + body: "Raz-dwa-try kalyna, czorniawaja diwczyna", + }); + + // assert + expect(response).toBeDefined(); + }); + + it("find - by id", async () => { + // act + const response = await client.conversations.find({ + conversation_id: conversationId, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("update", async () => { + // act + const response = await client.conversations.update({ + conversation_id: conversationId, + read: false, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("replyByIdAsAdmin", async () => { + // act + const response = await client.conversations.reply({ + conversation_id: conversationId, + body: { + message_type: "comment", + type: "admin", + body: "test", + admin_id: adminId, + }, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("replyByIdAsUser", async () => { + // act + const response = await client.conversations.reply({ + conversation_id: conversationId, + body: { + message_type: "comment", + type: "user", + body: "*click* Nice!", + intercom_user_id: user.id, + }, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("assign", async () => { + // arrange + const message = await createConversation(client, user.id); + + // act + const response = await client.conversations.manage({ + conversation_id: message.conversation_id, + body: { + message_type: "assignment", + type: "admin", + admin_id: adminId, + assignee_id: secondAdminId, + body: "Goodbye :)", + }, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("run assignment rules", async () => { + // arrange + const message = await createConversation(client, user.id); + + // act + const response = await client.conversations.runAssignmentRules({ + conversation_id: message.conversation_id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("snooze", async () => { + // act + const response = await client.conversations.manage({ + conversation_id: conversationId, + body: { + message_type: "snoozed", + admin_id: adminId, + snoozed_until: new Date("2040.06.19").getTime() / 1000, + }, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("open", async () => { + // act + const response = await client.conversations.manage({ + conversation_id: conversationId, + body: { + message_type: "open", + admin_id: adminId, + }, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("attachContactAsAdmin", async () => { + // act + const response = await client.conversations.attachContactAsAdmin({ + conversation_id: conversationId, + customer: { intercom_user_id: secondUser.id }, + admin_id: adminId, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("detachContactAsAdmin", async () => { + // act + const response = await client.conversations.detachContactAsAdmin({ + admin_id: adminId, + contact_id: secondUser.id, + conversation_id: conversationId, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("redactConversationPart", async () => { + // arrange + const conversation = await client.conversations.find({ + conversation_id: conversationId, + }); + + // act + const response = await client.conversations.redactConversationPart({ + type: "conversation_part", + conversation_id: conversationId, + conversation_part_id: conversation.conversation_parts?.conversation_parts[2].id ?? "", + }); + + // assert + expect(response).toBeDefined(); + }); + + it("close", async () => { + // act + const response = await client.conversations.manage({ + conversation_id: conversationId, + body: { + type: "admin", + message_type: "close", + admin_id: adminId, + body: "Hasta la vista, baby", + }, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("search", async () => { + // act + const response = await client.conversations.search({ + query: { + operator: "AND", + value: [ + { + field: "id", + operator: "!=", + value: "123", + }, + ], + }, + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/dataAttributes.test.ts b/tests/integration/dataAttributes.test.ts new file mode 100644 index 0000000..22c3ca1 --- /dev/null +++ b/tests/integration/dataAttributes.test.ts @@ -0,0 +1,67 @@ +import { Intercom } from "../../src"; +import { randomString } from "./utils/random"; +import { createClient } from "./utils/createClient"; + +describe("Data Attributes", () => { + let randomDataAttribute: Intercom.DataAttribute | undefined; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const attributes = await client.dataAttributes.list(); + randomDataAttribute = attributes.data.find((attribute) => attribute.id !== undefined); + }); + + xit("create", async () => { + //act + + // The workspace we test on has hit the CDA limit, so we can't create any more + // for now. We should reenable this test once we have a new workspace. + const response = await client.dataAttributes.create({ + name: `Bebech${randomString()}`, + model: "contact", + data_type: "string", + description: "Dummy description", + options: ["yey", "yoy"], + }); + + // assert + expect(response).toBeDefined(); + }); + it("update", async () => { + // arrange + if (!randomDataAttribute?.id) { + console.log("randomDataAttribute", randomDataAttribute); + throw new Error("random_data_attribute.id is required to update"); + } + + // act + const response = await client.dataAttributes.update({ + data_attribute_id: randomDataAttribute.id.toString(), + archived: false, + description: "Woo-aaa", + options: [ + { + value: "1-10", + }, + { + value: "11-20", + }, + ], + }); + + // assert + expect(response).toBeDefined(); + }); + it("list", async () => { + // act + const response = await client.dataAttributes.list({ + include_archived: true, + model: "conversation", + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/dataExport.test.ts b/tests/integration/dataExport.test.ts new file mode 100644 index 0000000..a26d014 --- /dev/null +++ b/tests/integration/dataExport.test.ts @@ -0,0 +1,57 @@ +import { Intercom, IntercomClient } from "../../src"; +import { createClient } from "./utils/createClient"; + +async function createDataExport(client: IntercomClient): Promise { + const dataExport = await client.dataExport.create({ + created_at_after: 1670000000, + created_at_before: 1670940528, + }); + return dataExport; +} + +async function tryCancelDataExport(client: IntercomClient, jobIdentifier: string): Promise { + try { + await client.dataExport.cancel({ job_identifier: jobIdentifier }); + } catch (error: unknown) { + console.log(error); + } +} + +describe("dataExport", () => { + const client = createClient(); + it("create", async () => { + // act + const response = await createDataExport(client); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryCancelDataExport(client, response.job_identifier); + }); + + it("find", async () => { + // arrange + const dataExport = await createDataExport(client); + + // act + const response = await client.dataExport.find({ job_identifier: dataExport.job_identifier }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryCancelDataExport(client, dataExport.job_identifier); + }); + + it("cancel", async () => { + // arrange + const dataExport = await createDataExport(client); + + // act + const response = await client.dataExport.cancel({ job_identifier: dataExport.job_identifier }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/events.test.ts b/tests/integration/events.test.ts new file mode 100644 index 0000000..d86c25c --- /dev/null +++ b/tests/integration/events.test.ts @@ -0,0 +1,69 @@ +import { IntercomClient as Client } from "../../src"; +import { dateToUnixTimestamp } from "./utils/date"; +import { randomString } from "./utils/random"; +import { createClient } from "./utils/createClient"; + +describe("Events", () => { + let userId: string; + let eventId = randomString(); + + const client = createClient(); + + beforeAll(async () => { + // arrange + const randomUsers = await client.contacts.search({ + query: { + operator: "AND", + value: [ + { + field: "role", + operator: "=", + value: "user", + }, + { + field: "external_id", + operator: "!=", + value: undefined, + }, + ], + }, + pagination: { + per_page: 1, + }, + }); + userId = randomUsers.data[0].external_id || ""; + if (!userId) { + console.warn("user_id is required to run tests"); + } + }); + + it("create", async () => { + // act + expect( + async () => + await client.events.create({ + id: eventId, + user_id: userId, + event_name: "opinion-rejected", + created_at: dateToUnixTimestamp(new Date()), + metadata: { + guidance: "provided", + wereall: "gonna make it", + price: "9001", + }, + }) + ).not.toThrowError(); + }); + + // expected to fail for now + it("listBy", async () => { + const response = await client.events.list({ + type: "user", + user_id: userId, + per_page: 2, + summary: true, + }); + + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/helpCenter/collections.test.ts b/tests/integration/helpCenter/collections.test.ts new file mode 100644 index 0000000..6dcfd78 --- /dev/null +++ b/tests/integration/helpCenter/collections.test.ts @@ -0,0 +1,105 @@ +import { IntercomClient as Client } from "../../../src"; +import { Collection } from "../../../src/api/resources/helpCenter/types"; +import { createClient } from "../utils/createClient"; + +async function createCollection(client: Client): Promise { + return await client.helpCenters.collections.create({ + name: "The Bruh Moment", + description: "Bruuuuuh", + translated_content: { + type: "group_translated_content", + fr: { + type: "group_content", + name: "Le Moment Frère", + description: "Frèèèèère", + }, + }, + }); +} + +async function tryDeleteCollection(client: Client, collectionId: string) { + try { + await client.helpCenters.collections.delete({ collection_id: collectionId }); + } catch (error) { + console.log(error); + } +} + +describe("Collections", () => { + let collectionId: string; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const collection = await createCollection(client); + collectionId = collection.id; + }); + + afterAll(async () => { + // cleanup + await tryDeleteCollection(client, collectionId); + }); + + it("create", async () => { + // act + const collection = await createCollection(client); + + // assert + expect(collection).toBeDefined(); + + // cleanup + await tryDeleteCollection(client, collection.id); + }); + + it("find", async () => { + // act + const response = await client.helpCenters.collections.find({ + collection_id: collectionId, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("update", async () => { + // arrange + const collection = await createCollection(client); + + // act + const response = await client.helpCenters.collections.update({ + collection_id: collection.id, + name: "People of future, tell us if NFTs make sense in 2026", + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteCollection(client, collection.id); + }); + + it("list", async () => { + // act + const response = await client.helpCenters.collections.list({ + per_page: 25, + page: 1, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("delete", async () => { + // arrange + const collection = await createCollection(client); + + // act + const response = await client.helpCenters.collections.delete({ + collection_id: collection.id, + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts new file mode 100644 index 0000000..0d35844 --- /dev/null +++ b/tests/integration/helpers.ts @@ -0,0 +1,87 @@ +import { Intercom, IntercomClient } from "../../src"; +import { dateToUnixTimestamp } from "./utils/date"; +import { randomString } from "./utils/random"; + +export async function createCompany(client: IntercomClient) { + return await client.companies.createOrUpdate({ + remote_created_at: dateToUnixTimestamp(new Date()), + company_id: randomString(), + name: randomString(), + monthly_spend: 9001, + plan: "1. Get pizzaid", + size: 62049, + website: "http://the-best.one", + industry: "The Best One", + custom_attributes: {}, + }); +} + +export async function tryDeleteCompany(client: IntercomClient, companyId: string) { + try { + await client.companies.delete({ company_id: companyId }); + } catch (error) { + console.error("Failed to delete company:", error); + } +} + +export async function createContact(client: IntercomClient) { + return await client.contacts.create({ + external_id: randomString(), + phone: "+353871234567", + }); +} + +export async function tryDeleteContact(client: IntercomClient, contactId: string) { + try { + await client.contacts.delete({ contact_id: contactId }); + } catch (error) { + console.error("Failed to delete contact:", error); + } +} + +export async function tryDeleteTag(client: IntercomClient, tagId: string) { + try { + await client.tags.delete({ tag_id: tagId }); + } catch (error) { + console.error(error); + } +} + +export async function createConversation(client: IntercomClient, contactId: string) { + return await client.conversations.create({ + from: { id: contactId, type: "user" }, + body: randomString(), + }); +} + +export async function tryUntagContact(client: IntercomClient, contactId: string, tagId: string) { + try { + await client.tags.untagContact({ contact_id: contactId, tag_id: tagId }); + } catch (error) { + console.error(error); + } +} + +export async function tryUntagConversation( + client: IntercomClient, + conversationId: string, + tagId: string, + adminId: string +) { + try { + await client.tags.untagConversation({ conversation_id: conversationId, tag_id: tagId, admin_id: adminId }); + } catch (error) { + console.error(error); + } +} + +export async function tryUntagCompany(client: IntercomClient, tagName: string, company: Intercom.Company) { + try { + await client.tags.create({ + name: tagName, + companies: [{ id: company.id, company_id: company.company_id, untag: true }], + }); + } catch (error) { + console.error(error); + } +} diff --git a/tests/integration/integration.test.ts b/tests/integration/integration.test.ts new file mode 100644 index 0000000..321552f --- /dev/null +++ b/tests/integration/integration.test.ts @@ -0,0 +1,86 @@ +import { Intercom } from "../../src"; +import { randomString } from "./utils/random"; +import { createCompany, tryDeleteCompany, tryDeleteContact, tryDeleteTag } from "./helpers"; +import { createClient } from "./utils/createClient"; + +describe("Integration between Contact, Conversation, Company and Tag APIs", () => { + let adminId: string; + let company: Intercom.Company; + let user: Intercom.Contact; + let lead: Intercom.Contact; + let tag: Intercom.Tag; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const admins = await client.admins.list(); + adminId = admins.admins[0].id; + + company = await createCompany(client); + user = await client.contacts.create({ + external_id: randomString(), + }); + lead = await client.contacts.create({ + name: "Marek Barek", + role: "lead", + }); + tag = await client.tags.create({ + name: randomString(), + }); + }); + + afterAll(async () => { + // cleanup + await tryDeleteContact(client, lead.id); + await tryDeleteContact(client, user.id); + await tryDeleteCompany(client, company.id); + await tryDeleteTag(client, tag.id); + }); + + it("Add Contact to Company", async () => { + // act + const response = await client.companies.attachContact({ + contact_id: user.id, + id: company.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("Create Conversation with Contact", async () => { + // act + const response = await client.conversations.create({ + from: { + type: "user", + id: user.id, + }, + body: "Welcome to the club, buddy!", + }); + + // assert + expect(response).toBeDefined(); + }); + + it("Tag the Conversation", async () => { + // arrange + const conversation = await client.conversations.create({ + from: { + type: "user", + id: user.id, + }, + body: "Welcome to the club, buddy!", + }); + + // act + const response = await client.tags.tagConversation({ + conversation_id: conversation.conversation_id, + id: tag.id, + admin_id: adminId, + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/messages.test.ts b/tests/integration/messages.test.ts new file mode 100644 index 0000000..4b7df87 --- /dev/null +++ b/tests/integration/messages.test.ts @@ -0,0 +1,81 @@ +import { Intercom } from "../../src"; +import { tryDeleteContact } from "./helpers"; +import { randomString } from "./utils/random"; +import { wait } from "./utils/wait"; +import { createClient } from "./utils/createClient"; + +describe("Messages", () => { + let adminId: string; + let user: Intercom.Contact; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const admins = await client.admins.list(); + const adminList = admins.admins.filter((admin) => admin.has_inbox_seat); + + adminId = adminList[0].id; + user = await client.contacts.create({ + external_id: randomString(), + name: "Message Test User", + }); + }); + + afterAll(async () => { + // cleanup + await tryDeleteContact(client, user.id); + }); + + it("Message that creates a converation", async () => { + // act + const response = await client.messages.create({ + message_type: "inapp", + body: "Hey, look at me! I am the conversations creator now!", + from: { + type: "admin", + id: Number(adminId), + }, + to: { + type: "user", + id: user.id, + }, + create_conversation_without_contact_reply: true, + }); + + const messageId = response.id; + + // Give Intercom a few seconds to index conversation + await wait(5000); + + const searchResults = await client.conversations.search({ + query: { + field: "source.id", + operator: "=", + value: messageId, + }, + }); + + // assert + expect(searchResults.data.length).toBeGreaterThan(0); + }, 10_000); + + it("Create message, no conversation", async () => { + // act + const response = await client.messages.create({ + message_type: "inapp", + body: "Message without creating conversation", + from: { + type: "admin", + id: Number(adminId), + }, + to: { + type: "user", + id: user.id, + }, + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/notes.test.ts b/tests/integration/notes.test.ts new file mode 100644 index 0000000..b38c17f --- /dev/null +++ b/tests/integration/notes.test.ts @@ -0,0 +1,65 @@ +import { Intercom } from "../../src"; +import { tryDeleteContact } from "./helpers"; +import { randomString } from "./utils/random"; +import { createClient } from "./utils/createClient"; + +describe("Notes", () => { + let adminId: string; + let contact: Intercom.Contact; + let note: Intercom.Note; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const admins = await client.admins.list(); + adminId = admins.admins[0].id; + + contact = await client.contacts.create({ + external_id: randomString(), + }); + + note = await client.notes.create({ + admin_id: adminId, + body: randomString(), + contact_id: contact.id, + }); + }); + + afterAll(async () => { + // cleanup + await tryDeleteContact(client, contact.id); + }); + + it("create", async () => { + // act + const response = await client.notes.create({ + admin_id: adminId, + body: randomString(), + contact_id: contact.id, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("find", async () => { + // act + const response = await client.notes.find({ note_id: note.id }); + + // assert + expect(response).toBeDefined(); + }); + + it("list", async () => { + // act + const response = await client.notes.list({ + contact_id: contact.id, + per_page: 25, + page: 1, + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/pagination.test.ts b/tests/integration/pagination.test.ts new file mode 100644 index 0000000..bfe4b2b --- /dev/null +++ b/tests/integration/pagination.test.ts @@ -0,0 +1,148 @@ +import { Companies } from "../../src/api/resources/companies/client/Client"; +import { Contacts } from "../../src/api/resources/contacts/client/Client"; +import { Conversations } from "../../src/api/resources/conversations/client/Client"; +import { Collections } from "../../src/api/resources/helpCenters/resources/collections/client/Client"; +import { Notes } from "../../src/api/resources/notes/client/Client"; +import { createClient } from "./utils/createClient"; +import { randomString } from "./utils/random"; + +type TestClient = Collections | Companies | Contacts | Conversations | Notes; + +describe("Pagination", () => { + const client = createClient(); + + interface TestCase { + name: string; + client: TestClient; + limit: number; + perPage: number; + greaterThan: number; + setup?: () => Promise; + additionalParams?: Record; + } + + const testCases: TestCase[] = [ + { + name: "helpCenters collections", + client: client.helpCenters.collections, + limit: 2, + perPage: 1, + greaterThan: 1, + }, + { + name: "companies", + client: client.companies, + limit: 10, + perPage: 1, + greaterThan: 0, + }, + { + name: "contacts", + client: client.contacts, + limit: 100, + perPage: 50, + greaterThan: 0, + }, + { + name: "conversations", + client: client.conversations, + limit: 2, + perPage: 1, + greaterThan: 1, + }, + { + name: "notes", + client: client.notes, + limit: 2, + perPage: 1, + greaterThan: 1, + async setup() { + const contact = await client.contacts.create({ + email: `${randomString()}@test.com`, + }); + + await client.notes.create({ + contact_id: contact.id, + body: "one", + }); + + await client.notes.create({ + contact_id: contact.id, + body: "two", + }); + + return { contact_id: contact.id }; + }, + }, + ]; + + async function testIterator({ + client, + limit, + params, + }: { + client: TestClient; + limit: number; + params: Record; + }) { + const iterator = await client.list(params as any); + expect(iterator).toBeDefined(); + + let count = 0; + for await (const item of iterator) { + expect(item).toBeDefined(); + expect(item.id).toBeDefined(); + count++; + + if (count >= limit) { + break; + } + } + return count; + } + + async function testPager({ + client, + limit, + params, + }: { + client: TestClient; + limit: number; + params: Record; + }) { + const pager = await client.list(params as any); + expect(pager).toBeDefined(); + + let count = pager.data.length; + while (pager.hasNextPage()) { + await pager.getNextPage(); + count += pager.data.length; + + if (count >= limit) { + break; + } + } + return count; + } + + testCases.forEach(({ name, client, limit, perPage, greaterThan, setup, additionalParams }) => { + it(name, async () => { + let params: Record = { per_page: perPage }; + if (setup) { + const setupParams = await setup(); + params = { ...params, ...setupParams }; + } + if (additionalParams) { + params = { ...params, ...additionalParams }; + } + + const iteratorCount = await testIterator({ client, limit, params: { ...params } }); + const pagerCount = await testPager({ client, limit, params: { ...params } }); + expect(iteratorCount).toBeGreaterThan(greaterThan); + expect(pagerCount).toBeGreaterThan(greaterThan); + + // Confirm iterator and pager return same count. + expect(pagerCount).toEqual(iteratorCount); + }); + }); +}); diff --git a/tests/integration/phoneCallRedirect.test.ts b/tests/integration/phoneCallRedirect.test.ts new file mode 100644 index 0000000..3fc363d --- /dev/null +++ b/tests/integration/phoneCallRedirect.test.ts @@ -0,0 +1,20 @@ +import { createClient } from "./utils/createClient"; + +describe("phoneCallRedirect", () => { + const client = createClient(); + + // TODO: Configure Twilio to enable phone call redirect tests. + it.skip("create", async () => { + // act + const response = await client.phoneCallRedirects.create({ + phone: "+353832345678", + custom_attributes: { + issue_type: "Billing", + priority: "High", + }, + }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/segments.test.ts b/tests/integration/segments.test.ts new file mode 100644 index 0000000..902c4c7 --- /dev/null +++ b/tests/integration/segments.test.ts @@ -0,0 +1,31 @@ +import { createClient } from "./utils/createClient"; + +describe("Segments", () => { + let segmentId: string; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const response = await client.segments.list(); + segmentId = response.segments[0].id; + }); + + it("list", async () => { + // act + const response = await client.segments.list({ + include_count: true, + }); + + // assert + expect(response).toBeDefined(); + }); + + it("find", async () => { + // act + const response = await client.segments.find({ segment_id: segmentId }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/subscriptions.test.ts b/tests/integration/subscriptions.test.ts new file mode 100644 index 0000000..73b9dcb --- /dev/null +++ b/tests/integration/subscriptions.test.ts @@ -0,0 +1,13 @@ +import { createClient } from "./utils/createClient"; + +describe("Subscriptions", () => { + const client = createClient(); + + it("listTypes", async () => { + // act + const response = await client.subscriptionTypes.list(); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/tags.test.ts b/tests/integration/tags.test.ts new file mode 100644 index 0000000..f4fa85c --- /dev/null +++ b/tests/integration/tags.test.ts @@ -0,0 +1,217 @@ +import { Intercom } from "../../src"; +import { + tryDeleteTag, + createContact, + tryDeleteContact, + createConversation, + tryDeleteCompany, + createCompany, + tryUntagContact, + tryUntagConversation, + tryUntagCompany, +} from "./helpers"; +import { createClient } from "./utils/createClient"; +import { randomString } from "./utils/random"; +import { wait } from "./utils/wait"; + +describe("Tags", () => { + let adminId: string; + let tag: Intercom.Tag; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const randomAdmins = await client.admins.list(); + adminId = randomAdmins.admins[0].id; + tag = await client.tags.create({ + name: randomString(), + }); + }); + + afterAll(async () => { + // cleanup + await tryDeleteTag(client, tag.id); + }); + + it("create", async () => { + // act + const response = await client.tags.create({ + name: "Bellic and Partners", + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteTag(client, response.id); + }); + + it("update", async () => { + // arrange + const tag = await client.tags.create({ + name: randomString(), + }); + + // act + const response = await client.tags.create({ id: tag.id, name: "Poor" }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteTag(client, response.id); + }); + + it("find", async () => { + // act + const response = await client.tags.find({ tag_id: tag.id }); + + // assert + expect(response).toBeDefined(); + }); + + it("list", async () => { + // act + const response = await client.tags.list(); + + // assert + expect(response).toBeDefined(); + }); + + it("delete", async () => { + // arrange + const tag = await client.tags.create({ + name: randomString(), + }); + + // act & assert + expect(async () => await client.tags.delete({ tag_id: tag.id })).not.toThrow(); + + // cleanup + await tryDeleteTag(client, tag.id); + }); + + it("tagContact", async () => { + // arrange + const contact = await createContact(client); + + // act + const response = await client.tags.tagContact({ + contact_id: contact.id, + id: tag.id, + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryUntagContact(client, contact.id, tag.id); + await tryDeleteContact(client, contact.id); + }); + + it("tagConversation", async () => { + // arrange + const contact = await createContact(client); + const message = await createConversation(client, contact.id); + + // act + const response = await client.tags.tagConversation({ + conversation_id: message.conversation_id, + id: tag.id, + admin_id: adminId, + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryUntagConversation(client, message.conversation_id, tag.id, adminId); + await tryDeleteContact(client, contact.id); + }, 10_000); + + it("tagCompany", async () => { + // arrange + const company = await createCompany(client); + + // act + const response = await client.tags.create({ + name: "Poor", + companies: [{ company_id: company.company_id }], + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryUntagCompany(client, "Poor", company); + await tryDeleteCompany(client, company.id); + }); + + it("untagContact", async () => { + // arrange + const contact = await createContact(client); + await client.tags.tagContact({ + contact_id: contact.id, + id: tag.id, + }); + + // act + const response = await client.tags.untagContact({ + contact_id: contact.id, + tag_id: tag.id, + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteContact(client, contact.id); + }); + + it("untagConversation", async () => { + // arrange + const contact = await createContact(client); + const message = await createConversation(client, contact.id); + + await client.tags.tagConversation({ + conversation_id: message.conversation_id, + id: tag.id, + admin_id: adminId, + }); + + // act + const response = await client.tags.untagConversation({ + conversation_id: message.conversation_id, + tag_id: tag.id, + admin_id: adminId, + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteContact(client, contact.id); + }, 10_000); + + it("untagCompany", async () => { + // arrange + const company = await createCompany(client); + await client.tags.create({ + name: "Poor", + companies: [{ id: company.id, company_id: company.company_id }], + }); + + // act + const response = await client.tags.create({ + name: "Poor", + companies: [{ id: company.id, company_id: company.company_id, untag: true }], + }); + + // assert + expect(response).toBeDefined(); + + // cleanup + await tryDeleteCompany(client, company.id); + }); +}); diff --git a/tests/integration/teams.test.ts b/tests/integration/teams.test.ts new file mode 100644 index 0000000..5916546 --- /dev/null +++ b/tests/integration/teams.test.ts @@ -0,0 +1,29 @@ +import { createClient } from "./utils/createClient"; + +describe("Teams", () => { + let teamId: string; + + const client = createClient(); + + beforeAll(async () => { + // arrange + const response = await client.teams.list(); + teamId = response.teams[0].id; + }); + + it("list", async () => { + // act + const response = await client.teams.list(); + + // assert + expect(response).toBeDefined(); + }); + + it("find", async () => { + // act + const response = await client.teams.find({ team_id: teamId }); + + // assert + expect(response).toBeDefined(); + }); +}); diff --git a/tests/integration/utils/createClient.ts b/tests/integration/utils/createClient.ts new file mode 100644 index 0000000..4b2c547 --- /dev/null +++ b/tests/integration/utils/createClient.ts @@ -0,0 +1,5 @@ +import { IntercomClient } from "../../../src"; + +export function createClient(): IntercomClient { + return new IntercomClient({ token: process.env.API_TOKEN as string }); +} diff --git a/tests/integration/utils/date.ts b/tests/integration/utils/date.ts new file mode 100644 index 0000000..4ec8b32 --- /dev/null +++ b/tests/integration/utils/date.ts @@ -0,0 +1 @@ +export const dateToUnixTimestamp = (date: Date): number => Math.floor(date.getTime() / 1000); diff --git a/tests/integration/utils/random.ts b/tests/integration/utils/random.ts new file mode 100644 index 0000000..496df28 --- /dev/null +++ b/tests/integration/utils/random.ts @@ -0,0 +1,6 @@ +import crypto from "crypto"; + +export const randomString = (): string => crypto.randomBytes(16).toString("hex"); + +export const randomInt = (min = 0, max = 999): number => + Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min) + 1)) + Math.ceil(min); diff --git a/tests/integration/utils/wait.ts b/tests/integration/utils/wait.ts new file mode 100644 index 0000000..685944a --- /dev/null +++ b/tests/integration/utils/wait.ts @@ -0,0 +1,3 @@ +export async function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/tests/integration/visitors.test.ts b/tests/integration/visitors.test.ts new file mode 100644 index 0000000..10e2a46 --- /dev/null +++ b/tests/integration/visitors.test.ts @@ -0,0 +1,53 @@ +import { createClient } from "./utils/createClient"; + +// Skip by default, because there is no automation yet +describe.skip("Visitors", () => { + // Info: should be set manually. Find a way to automate it. + // Tip: headless browser to visit test application and get visitorId from ping request. + const visitorId = "0"; + const userId = "0"; + + const client = createClient(); + + it("find by id", async () => { + // act + const response = await client.visitors.find({ user_id: visitorId }); + + // assert + expect(response).toBeDefined(); + }); + it("find by user id", async () => { + // act + const response = await client.visitors.find({ user_id: userId }); + + // assert + expect(response).toBeDefined(); + }); + it("update", async () => { + // act + const response = await client.visitors.update({ + user_id: userId, + name: "Winston Smith", + }); + + // assert + + expect(response).toBeDefined(); + }); + + it("mergeToContact", async () => { + // act + const response = await client.visitors.mergeToContact({ + visitor: { + id: visitorId, + }, + user: { + email: "mcboxford@intercom-test.com", + } as any, + type: "user", + }); + + // assert + expect(response).toBeDefined(); + }); +});