Claude-code-plugins bamboohr-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/bamboohr-pack/skills/bamboohr-ci-integration" ~/.claude/skills/jeremylongshore-claude-code-plugins-bamboohr-ci-integration && rm -rf "$T"
manifest:
plugins/saas-packs/bamboohr-pack/skills/bamboohr-ci-integration/SKILL.mdsource content
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 guard on integration job |
| Integration test timeout | BambooHR API slow | Set |
| 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.