Agent Skills: BambooHR CI Integration

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/bamboohr-ci-integration

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/bamboohr-pack/skills/bamboohr-ci-integration

Skill Files

Browse the full folder contents for bamboohr-ci-integration.

Download Skill

Loading file tree…

plugins/saas-packs/bamboohr-pack/skills/bamboohr-ci-integration/SKILL.md

Skill Metadata

Name
bamboohr-ci-integration
Description
|

BambooHR CI Integration

Overview

Set up CI/CD pipelines for BambooHR integrations with proper secret management, unit tests with mocked API, and optional integration tests against the real BambooHR API.

Prerequisites

  • GitHub repository with Actions enabled
  • BambooHR test API key (sandbox company or test account)
  • npm/pnpm project with test suite configured

Instructions

Step 1: Configure GitHub Secrets

# Required for integration tests
gh secret set BAMBOOHR_API_KEY --body "your-test-api-key"
gh secret set BAMBOOHR_COMPANY_DOMAIN --body "your-test-company"

# Optional: webhook testing
gh secret set BAMBOOHR_WEBHOOK_SECRET --body "your-webhook-hmac-secret"

Step 2: GitHub Actions Workflow

# .github/workflows/bamboohr-integration.yml
name: BambooHR Integration

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    # Run daily to catch BambooHR API changes early
    - cron: '0 6 * * 1-5'

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
      - name: Unit tests (mocked BambooHR API)
        run: npm test -- --coverage --reporter=verbose
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: coverage
          path: coverage/

  integration-tests:
    runs-on: ubuntu-latest
    # Only run on main branch and schedule (not PRs from forks)
    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
    needs: unit-tests
    env:
      BAMBOOHR_API_KEY: ${{ secrets.BAMBOOHR_API_KEY }}
      BAMBOOHR_COMPANY_DOMAIN: ${{ secrets.BAMBOOHR_COMPANY_DOMAIN }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - name: Integration tests (real BambooHR API)
        run: npm run test:integration
        timeout-minutes: 5
      - name: API health check
        run: |
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
            -u "${BAMBOOHR_API_KEY}:x" \
            -H "Accept: application/json" \
            "https://api.bamboohr.com/api/gateway.php/${BAMBOOHR_COMPANY_DOMAIN}/v1/employees/directory")
          echo "BambooHR API status: $STATUS"
          [ "$STATUS" -eq 200 ] || exit 1

Step 3: Test Structure

// tests/unit/bamboohr-client.test.ts
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { BambooHRClient } from '../../src/bamboohr/client';

const BASE = 'https://api.bamboohr.com/api/gateway.php/testco/v1';

const handlers = [
  http.get(`${BASE}/employees/directory`, () =>
    HttpResponse.json({
      employees: [
        { id: '1', displayName: 'Jane', jobTitle: 'Eng', department: 'Dev' },
      ],
    }),
  ),
  http.get(`${BASE}/employees/:id/`, () =>
    HttpResponse.json({ id: '1', firstName: 'Jane', lastName: 'Smith' }),
  ),
  http.post(`${BASE}/reports/custom`, () =>
    HttpResponse.json({ title: 'Report', employees: [] }),
  ),
  // Simulate rate limit
  http.get(`${BASE}/employees/ratelimited`, () =>
    new HttpResponse(null, { status: 503, headers: { 'Retry-After': '1' } }),
  ),
];

const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe('BambooHRClient', () => {
  const client = new BambooHRClient({ companyDomain: 'testco', apiKey: 'fake' });

  it('fetches employee directory', async () => {
    const dir = await client.getDirectory();
    expect(dir.employees).toHaveLength(1);
    expect(dir.employees[0].displayName).toBe('Jane');
  });

  it('fetches single employee with fields', async () => {
    const emp = await client.getEmployee(1, ['firstName', 'lastName']);
    expect(emp.firstName).toBe('Jane');
  });

  it('runs custom reports', async () => {
    const report = await client.customReport(['firstName', 'department']);
    expect(report.title).toBe('Report');
  });

  it('handles 503 rate limit with Retry-After', async () => {
    await expect(
      client.request('GET', '/employees/ratelimited'),
    ).rejects.toThrow(/503/);
  });
});
// tests/integration/bamboohr-live.test.ts
import { describe, it, expect } from 'vitest';
import { BambooHRClient } from '../../src/bamboohr/client';

const HAS_CREDS = !!process.env.BAMBOOHR_API_KEY && !!process.env.BAMBOOHR_COMPANY_DOMAIN;

describe.skipIf(!HAS_CREDS)('BambooHR Live API', () => {
  const client = new BambooHRClient({
    companyDomain: process.env.BAMBOOHR_COMPANY_DOMAIN!,
    apiKey: process.env.BAMBOOHR_API_KEY!,
  });

  it('should fetch employee directory', async () => {
    const dir = await client.getDirectory();
    expect(dir.employees.length).toBeGreaterThan(0);
    expect(dir.employees[0]).toHaveProperty('displayName');
    expect(dir.employees[0]).toHaveProperty('jobTitle');
  }, 15_000);

  it('should run a custom report', async () => {
    const report = await client.customReport(['firstName', 'lastName', 'department']);
    expect(report).toHaveProperty('employees');
    expect(Array.isArray(report.employees)).toBe(true);
  }, 15_000);

  it('should fetch time off types', async () => {
    const types = await client.request('GET', '/meta/time_off/types');
    expect(types).toBeTruthy();
  }, 15_000);
});

Step 4: PR Status Check

# Branch protection — require these checks to pass
# Settings > Branches > Branch protection rules
required_status_checks:
  - 'unit-tests'
  # integration-tests is optional (may fail if API is down)

Step 5: Scheduled API Health Monitoring

# .github/workflows/bamboohr-health.yml
name: BambooHR API Health

on:
  schedule:
    - cron: '0 */4 * * *'  # Every 4 hours

jobs:
  health-check:
    runs-on: ubuntu-latest
    env:
      BAMBOOHR_API_KEY: ${{ secrets.BAMBOOHR_API_KEY }}
      BAMBOOHR_COMPANY_DOMAIN: ${{ secrets.BAMBOOHR_COMPANY_DOMAIN }}
    steps:
      - name: Check BambooHR API
        run: |
          STATUS=$(curl -s -o /tmp/response.json -w "%{http_code}" \
            -u "${BAMBOOHR_API_KEY}:x" \
            -H "Accept: application/json" \
            "https://api.bamboohr.com/api/gateway.php/${BAMBOOHR_COMPANY_DOMAIN}/v1/employees/directory")

          if [ "$STATUS" -ne 200 ]; then
            echo "::error::BambooHR API returned $STATUS"
            exit 1
          fi

          COUNT=$(cat /tmp/response.json | jq '.employees | length')
          echo "BambooHR API healthy: $COUNT employees"

Output

  • Unit test pipeline with mocked BambooHR API
  • Integration test pipeline with real API (gated on secrets)
  • Scheduled health monitoring workflow
  • PR status checks configured
  • Coverage reports uploaded

Error Handling

| Issue | Cause | Solution | |-------|-------|----------| | Secret not available in PR | Fork PR (no secrets access) | Use if guard on integration job | | Integration test timeout | BambooHR API slow | Set timeout-minutes: 5 | | Flaky 503 in tests | Rate limiting in CI | Add retry logic to test helpers | | Health check false alarm | BambooHR maintenance | Check status page before alerting |

Resources

Next Steps

For deployment patterns, see bamboohr-deploy-integration.