Claude-code-plugins-plus intercom-ci-integration
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/intercom-pack/skills/intercom-ci-integration" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-intercom-ci-integration && rm -rf "$T"
manifest:
plugins/saas-packs/intercom-pack/skills/intercom-ci-integration/SKILL.mdsource content
Intercom CI Integration
Overview
Set up CI/CD pipelines for Intercom integrations with GitHub Actions, including unit tests with mocked SDK, integration tests against a dev workspace, and secret management.
Prerequisites
- GitHub repository with Actions enabled
- Intercom dev workspace access token (separate from production)
- npm/pnpm project with
installedintercom-client
Instructions
Step 1: GitHub Actions Workflow
# .github/workflows/intercom-ci.yml name: Intercom Integration CI on: push: branches: [main] pull_request: branches: [main] jobs: unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: "npm" - run: npm ci - run: npm run typecheck - run: npm test -- --coverage - uses: actions/upload-artifact@v4 with: name: coverage path: coverage/ integration-tests: runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main' env: INTERCOM_ACCESS_TOKEN: ${{ secrets.INTERCOM_DEV_TOKEN }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: "npm" - run: npm ci - name: Verify Intercom connectivity run: | STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: Bearer $INTERCOM_ACCESS_TOKEN" \ https://api.intercom.io/me) if [ "$STATUS" != "200" ]; then echo "Intercom auth failed: $STATUS" exit 1 fi - name: Run integration tests run: npm run test:integration timeout-minutes: 5
Step 2: Configure Secrets
# Store dev workspace token (never production!) gh secret set INTERCOM_DEV_TOKEN --body "dG9rOmRldl90b2tlbl9oZXJl" # If using webhooks in CI, store the signing secret gh secret set INTERCOM_WEBHOOK_SECRET --body "your-webhook-secret" # Verify secrets are set gh secret list
Step 3: Unit Tests with Mocked SDK
// tests/unit/intercom-service.test.ts import { describe, it, expect, vi, beforeEach } from "vitest"; import { IntercomError } from "intercom-client"; // Mock the entire module vi.mock("intercom-client", () => ({ IntercomClient: vi.fn().mockImplementation(() => mockClient), IntercomError: class extends Error { statusCode: number; constructor(message: string, statusCode: number) { super(message); this.statusCode = statusCode; } }, })); const mockClient = { contacts: { create: vi.fn(), find: vi.fn(), search: vi.fn(), list: vi.fn(), }, conversations: { create: vi.fn(), reply: vi.fn(), find: vi.fn(), }, admins: { list: vi.fn().mockResolvedValue({ admins: [{ id: "admin-1", name: "CI Admin" }], }), }, }; describe("Contact sync service", () => { beforeEach(() => vi.clearAllMocks()); it("should create a contact with correct attributes", async () => { mockClient.contacts.create.mockResolvedValue({ type: "contact", id: "test-id", role: "user", email: "test@example.com", }); const result = await mockClient.contacts.create({ role: "user", email: "test@example.com", externalId: "usr-1", }); expect(result.id).toBe("test-id"); expect(mockClient.contacts.create).toHaveBeenCalledWith({ role: "user", email: "test@example.com", externalId: "usr-1", }); }); it("should handle 409 conflict on duplicate contact", async () => { mockClient.contacts.create.mockRejectedValue( new IntercomError("A contact matching those details already exists", 409) ); await expect( mockClient.contacts.create({ role: "user", email: "dupe@example.com" }) ).rejects.toThrow("already exists"); }); });
Step 4: Integration Tests (Against Dev Workspace)
// tests/integration/contacts.integration.test.ts import { describe, it, expect, afterAll } from "vitest"; import { IntercomClient } from "intercom-client"; const token = process.env.INTERCOM_ACCESS_TOKEN; const client = token ? new IntercomClient({ token }) : null; // Track created resources for cleanup const createdContactIds: string[] = []; afterAll(async () => { if (!client) return; for (const id of createdContactIds) { try { await client.contacts.delete({ contactId: id }); } catch {} } }); describe.skipIf(!token)("Intercom API Integration", () => { it("should authenticate and list admins", async () => { const admins = await client!.admins.list(); expect(admins.admins.length).toBeGreaterThan(0); }); it("should create and retrieve a contact", async () => { const contact = await client!.contacts.create({ role: "lead", name: `CI Test ${Date.now()}`, }); createdContactIds.push(contact.id); expect(contact.role).toBe("lead"); const found = await client!.contacts.find({ contactId: contact.id }); expect(found.id).toBe(contact.id); }); it("should search contacts", async () => { const results = await client!.contacts.search({ query: { field: "role", operator: "=", value: "user" }, pagination: { per_page: 5 }, }); expect(results.data).toBeDefined(); }); });
Step 5: Webhook Signature Test
import { describe, it, expect } from "vitest"; import crypto from "crypto"; function verifySignature(payload: string, signature: string, secret: string): boolean { const expected = "sha1=" + crypto .createHmac("sha1", secret) .update(payload) .digest("hex"); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } describe("Webhook signature verification", () => { const secret = "test-webhook-secret"; const payload = '{"type":"notification_event","topic":"conversation.user.created"}'; it("should verify valid signature", () => { const signature = "sha1=" + crypto.createHmac("sha1", secret).update(payload).digest("hex"); expect(verifySignature(payload, signature, secret)).toBe(true); }); it("should reject invalid signature", () => { expect(verifySignature(payload, "sha1=invalid", secret)).toBe(false); }); });
Error Handling
| CI Issue | Cause | Solution |
|---|---|---|
not found | Secret not configured | |
| Integration tests timeout | Rate limited or slow API | Increase timeout, add delays |
| 401 in CI | Token expired or rotated | Update secret with new token |
| Flaky tests | Shared dev workspace state | Use unique names, clean up after |
Resources
Next Steps
For deployment patterns, see
intercom-deploy-integration.