Add Jest unit tests and CI workflow for TypeScript services#2
Conversation
…rvices Co-authored-by: Basil-Ismail <107148738+Basil-Ismail@users.noreply.github.com>
Co-authored-by: Basil-Ismail <107148738+Basil-Ismail@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds Jest-based unit test coverage for TypeScript packages in the monorepo and introduces a GitHub Actions workflow to run those tests on pushes/PRs to main.
Changes:
- Added Jest/ts-jest configs and
test/test:coveragescripts for@mono/sharedand@mono/github. - Introduced new unit tests across
@mono/shared,@mono/github, and@mono/api-gatewaymiddleware. - Added a CI workflow that runs tests for
@mono/shared,@mono/github, and@mono/api-gatewayin parallel jobs.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
pnpm-lock.yaml |
Locks newly added Jest-related dev dependencies for the updated packages. |
packages/shared/package.json |
Adds Jest scripts and devDependencies to enable running unit tests in @mono/shared. |
packages/shared/jest.config.js |
Introduces ts-jest configuration for @mono/shared test execution and coverage. |
packages/shared/src/utils/utils.test.ts |
Adds unit tests for shared utility helpers (successResponse, errorResponse, sleep, generateId). |
packages/shared/src/types/types.test.ts |
Adds tests around pagination schema behavior and (attempted) typing checks. |
packages/shared/src/constants/constants.test.ts |
Adds tests for shared constant objects and environment typing. |
packages/github/package.json |
Adds Jest scripts and devDependencies to enable running unit tests in @mono/github. |
packages/github/jest.config.js |
Introduces ts-jest configuration for @mono/github test execution and coverage. |
packages/github/src/oauth.test.ts |
Adds tests for OAuth URL building and fetch-based token/user flows. |
packages/github/src/webhook.test.ts |
Adds tests for webhook signature verification logic. |
packages/github/src/app-auth.test.ts |
Adds tests for Octokit factory helpers using module mocks. |
packages/github/src/user-auth.test.ts |
Adds tests for user-auth Octokit creation using module mocks. |
apps/api-gateway/src/middleware/auth.test.ts |
Adds tests for JWT signing and requireAuth middleware behavior. |
apps/api-gateway/src/middleware/error-handler.test.ts |
Adds tests for error handler status/message behavior. |
.github/workflows/test.yml |
Adds CI workflow to run Jest tests for each package/app on push/PR. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| test-shared: | ||
| name: Test @mono/shared | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: pnpm/action-setup@v4 | ||
| with: | ||
| version: 9.1.0 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| cache: 'pnpm' | ||
|
|
||
| - run: pnpm install --frozen-lockfile | ||
| - run: pnpm --filter @mono/shared test |
There was a problem hiding this comment.
The workflow repeats the same checkout/pnpm setup/install steps across three jobs. A matrix job (or a reusable composite action) would reduce duplication and make future changes (Node/pnpm version bumps, install flags) less error-prone.
| const start = Date.now(); | ||
| await sleep(50); | ||
| const elapsed = Date.now() - start; | ||
| expect(elapsed).toBeGreaterThanOrEqual(40); |
There was a problem hiding this comment.
The sleep test uses real timers (Date.now + awaiting sleep(50)), which slows the suite and can be brittle if the environment is under heavy load. Prefer Jest fake timers (useFakeTimers/advanceTimersByTime) to assert the promise resolves after advancing time instead of waiting in real time.
| const start = Date.now(); | |
| await sleep(50); | |
| const elapsed = Date.now() - start; | |
| expect(elapsed).toBeGreaterThanOrEqual(40); | |
| jest.useFakeTimers(); | |
| const sleepPromise = sleep(50); | |
| jest.advanceTimersByTime(50); | |
| await expect(sleepPromise).resolves.toBeUndefined(); | |
| jest.useRealTimers(); |
|
|
||
| describe('ApiResponse type', () => { | ||
| it('should allow creating a typed success response', () => { | ||
| const response: ApiResponse<string> = { | ||
| success: true, | ||
| data: 'hello', | ||
| }; | ||
| expect(response.success).toBe(true); | ||
| expect(response.data).toBe('hello'); | ||
| }); | ||
|
|
||
| it('should allow creating a typed error response', () => { | ||
| const response: ApiResponse = { | ||
| success: false, | ||
| error: 'something failed', | ||
| }; | ||
| expect(response.success).toBe(false); | ||
| expect(response.error).toBe('something failed'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('PaginationParams type', () => { | ||
| it('should allow creating pagination params', () => { | ||
| const params: PaginationParams = { page: 1, limit: 20 }; | ||
| expect(params.page).toBe(1); | ||
| expect(params.limit).toBe(20); | ||
| }); | ||
| }); | ||
|
|
||
| describe('PaginatedResponse type', () => { | ||
| it('should allow creating a paginated response', () => { | ||
| const response: PaginatedResponse<number> = { | ||
| success: true, | ||
| data: [1, 2, 3], | ||
| pagination: { | ||
| page: 1, | ||
| limit: 20, | ||
| total: 3, | ||
| totalPages: 1, | ||
| }, | ||
| }; | ||
| expect(response.data).toHaveLength(3); | ||
| expect(response.pagination.total).toBe(3); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
These tests under "ApiResponse/PaginationParams/PaginatedResponse type" don’t actually validate TypeScript types at runtime (types are erased), so they add coverage without catching type regressions. Consider removing these runtime assertions or replacing them with compile-time type tests (e.g., tsd or @ts-expect-error based checks) that fail when the types change incorrectly.
| describe('ApiResponse type', () => { | |
| it('should allow creating a typed success response', () => { | |
| const response: ApiResponse<string> = { | |
| success: true, | |
| data: 'hello', | |
| }; | |
| expect(response.success).toBe(true); | |
| expect(response.data).toBe('hello'); | |
| }); | |
| it('should allow creating a typed error response', () => { | |
| const response: ApiResponse = { | |
| success: false, | |
| error: 'something failed', | |
| }; | |
| expect(response.success).toBe(false); | |
| expect(response.error).toBe('something failed'); | |
| }); | |
| }); | |
| describe('PaginationParams type', () => { | |
| it('should allow creating pagination params', () => { | |
| const params: PaginationParams = { page: 1, limit: 20 }; | |
| expect(params.page).toBe(1); | |
| expect(params.limit).toBe(20); | |
| }); | |
| }); | |
| describe('PaginatedResponse type', () => { | |
| it('should allow creating a paginated response', () => { | |
| const response: PaginatedResponse<number> = { | |
| success: true, | |
| data: [1, 2, 3], | |
| pagination: { | |
| page: 1, | |
| limit: 20, | |
| total: 3, | |
| totalPages: 1, | |
| }, | |
| }; | |
| expect(response.data).toHaveLength(3); | |
| expect(response.pagination.total).toBe(3); | |
| }); | |
| }); |
Adds unit test coverage across all TypeScript packages/services and a GitHub Actions workflow to run them on push/PR to
main.CI Workflow (
.github/workflows/test.yml)@mono/shared,@mono/github,@mono/api-gatewaycontents: readpermissionsTest Infrastructure
jest.config.js,test/test:coveragescripts, andjest/ts-jestdevDeps to@mono/sharedand@mono/github(api-gateway already had them)Unit Tests (56 total, all mocked — no DB/network)
@mono/shared(24 tests):successResponse,errorResponse,sleep,generateId,HTTP_STATUS,ENVIRONMENTS,paginationSchemavalidation@mono/github(16 tests):getAuthorizationUrl,exchangeCodeForToken,getGitHubUser(fetch mocked),verifyWebhookSignature(valid/invalid/tampered),createAppOctokit,createInstallationOctokit,createUserOctokit@mono/api-gateway(8 new + 8 existing):requireAuthmiddleware (missing header, bad token, valid token flow),signTokenJWT structure,errorHandler(default/custom status codes, empty message fallback)✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.