Add automated tests, CI pipeline, and documentation improvements#30
Add automated tests, CI pipeline, and documentation improvements#30DhanushNehru merged 10 commits intoDhanushNehru:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces an automated testing setup for the Node.js signaling server, adds a CI workflow to run those tests on pushes/PRs, and updates documentation with test-running instructions.
Changes:
- Added Vitest + Supertest integration tests covering health, upload/download, and WebSocket signaling flows.
- Added a GitHub Actions workflow to install server deps and run the test suite.
- Updated server startup behavior and README to support/describe running tests.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
server/test/index.test.js |
New integration test suite for HTTP endpoints and WebSocket signaling. |
server/index.js |
Avoids auto-listening in tests and attempts to export app/server for test harness usage. |
server/package.json |
Adds Vitest/Supertest, test script, and switches module type. |
server/package-lock.json |
Locks newly added dev dependencies. |
README.md |
Adds “Running Tests” section and instructions. |
.github/workflows/test.yml |
New CI job to run server tests on push/PR. |
Files not reviewed (1)
- server/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const timeout = setTimeout(() => { | ||
| client.off('error', onError); | ||
| client.off('message', onMessage); | ||
| reject(new Error('Timed out waiting for init message')); | ||
| }, 3000); | ||
|
|
||
| const onError = (err) => { | ||
| clearTimeout(timeout); | ||
| client.off('message', onMessage); | ||
| reject(err); | ||
| }; |
There was a problem hiding this comment.
On timeout, connectAndInitClient rejects but leaves the WebSocket open (no terminate/close), which can leak sockets and keep the test process alive on failures. Ensure the timeout/error paths terminate the client and remove listeners before rejecting.
| ### Prerequisites | ||
|
|
||
| - Node.js 16+ and npm installed | ||
| - Server dependencies installed in the `server` folder (`npm install`) | ||
|
|
||
| ### Commands | ||
|
|
||
| From the project root: | ||
|
|
||
| ```bash | ||
| cd server | ||
| npx vitest run | ||
| ``` | ||
|
|
||
| Or run the npm test script (watch mode): | ||
|
|
||
| ```bash | ||
| cd server | ||
| npm test | ||
| ``` |
There was a problem hiding this comment.
README says Node.js 16+ and describes npm test as "watch mode", but server/package.json runs vitest run and the added devDependencies (vitest/vite) require a newer Node version. Please update the Node version requirement and adjust the command descriptions to match the actual scripts (or update scripts to match the docs).
| it('should upload file and return file id', async () => { | ||
| const res = await request(baseURL) | ||
| .post('/upload') | ||
| .attach('file', Buffer.from('hello world'), 'test.txt'); | ||
|
|
||
| expect(res.status).toBe(200); | ||
| expect(res.body.id).toBeDefined(); | ||
|
|
||
| // Save this id so the download test can fetch the same file. | ||
| uploadedFileId = res.body.id; | ||
| }); |
There was a problem hiding this comment.
These integration tests upload files to the server’s real uploads/ directory but never clean them up, so repeated local test runs will accumulate artifacts on disk. Consider using a temp uploads directory for NODE_ENV=test and/or deleting uploaded files in afterAll.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
I have updated all the required changes |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
Files not reviewed (1)
- server/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Save this id so the download test can fetch the same file. | ||
| uploadedFileId = res.body.id; | ||
| }); | ||
|
|
||
| // Sending no file should return a validation error. | ||
| it('should fail if no file is sent', async () => { | ||
| const res = await request(baseURL).post('/upload'); | ||
|
|
||
| expect(res.status).toBe(400); | ||
| }); | ||
| }); | ||
|
|
||
| // File download tests | ||
| describe('Download File', () => { | ||
| // Downloads the file uploaded in the previous test block. | ||
| it('should download file using valid id', async () => { | ||
| const res = await request(baseURL).get(`/download/${uploadedFileId}`); | ||
|
|
There was a problem hiding this comment.
uploadedFileId is populated in one test and then consumed by a different describe block. This makes the suite order-dependent and can fail if tests are retried/shuffled or if the upload test fails (download becomes /download/undefined). Prefer creating the upload in a beforeAll within the download suite, or uploading within the download test itself.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
Done |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 8 comments.
Files not reviewed (1)
- server/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| it('should connect and receive init message', async () => { | ||
| client1 = new WebSocket('ws://localhost:4000'); | ||
|
|
||
| const msg = await waitForMessageOfType(client1, 'init'); | ||
| expect(msg.id).toBeDefined(); | ||
| }); |
There was a problem hiding this comment.
client1/client2 are created without any 'error' handler. If the WS connection fails for any reason, ws will emit an 'error' event which (without a listener) can crash the test process. Consider reusing connectAndInitClient() for these cases or attaching an error handler in the test to fail fast/cleanly.
| beforeAll(async () => { | ||
| const PORT = 4000; | ||
|
|
||
| await new Promise((resolve) => { | ||
| server.listen(PORT, () => { | ||
| baseURL = `http://localhost:${PORT}`; | ||
| resolve(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
The test server is bound to a fixed port (4000) and the listen Promise never rejects on EADDRINUSE (it only resolves on the listening callback). This can make CI/local runs flaky or hang indefinitely if the port is taken. Prefer listening on an ephemeral port (server.listen(0)) and deriving baseURL/WS_URL from server.address().port, and add an 'error' handler that rejects the Promise.
| afterAll(async () => { | ||
| terminateClient(client1); | ||
| terminateClient(client2); | ||
|
|
||
| await new Promise((resolve) => { | ||
| server.close(() => { | ||
| resolve(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
afterAll always calls server.close(). If beforeAll failed to bind (or the server was never started), server.close() will throw ERR_SERVER_NOT_RUNNING and can mask the real failure. Consider guarding with server.listening (or tracking whether listen succeeded) and only closing when the server is actually listening.
| client2 = new WebSocket('ws://localhost:4000'); | ||
|
|
||
| const msg = await waitForMessageOfType(client1, 'peer-joined'); |
There was a problem hiding this comment.
This test attaches the 'peer-joined' listener after creating client2. If the server emits 'peer-joined' to client1 very quickly, the message can arrive before waitForMessageOfType() subscribes, causing a flaky failure. Safer pattern is to start waiting (set up the listener) before triggering the second connection.
| client2 = new WebSocket('ws://localhost:4000'); | |
| const msg = await waitForMessageOfType(client1, 'peer-joined'); | |
| const peerJoinedPromise = waitForMessageOfType(client1, 'peer-joined'); | |
| client2 = new WebSocket('ws://localhost:4000'); | |
| const msg = await peerJoinedPromise; |
| describe('Single File Upload', () => { | ||
| // Upload one file and store the returned id for later download tests. | ||
| it('should upload file and return file id', async () => { | ||
| const res = await request(baseURL) | ||
| .post('/upload') | ||
| .attach('file', Buffer.from('hello world'), 'test.txt'); | ||
|
|
||
| expect(res.status).toBe(200); | ||
| expect(res.body.id).toBeDefined(); | ||
|
|
||
| // Save this id so the download test can fetch the same file. | ||
| uploadedFileId = res.body.id; | ||
| }); | ||
|
|
||
| // Sending no file should return a validation error. | ||
| it('should fail if no file is sent', async () => { | ||
| const res = await request(baseURL).post('/upload'); | ||
|
|
||
| expect(res.status).toBe(400); | ||
| }); | ||
| }); | ||
|
|
||
| // File download tests | ||
| describe('Download File', () => { | ||
| // Downloads the file uploaded in the previous test block. | ||
| it('should download file using valid id', async () => { | ||
| const res = await request(baseURL).get(`/download/${uploadedFileId}`); | ||
|
|
||
| expect(res.status).toBe(200); | ||
| }); | ||
|
|
||
| // Unknown ids should return 404. | ||
| it('should return 404 for wrong id', async () => { | ||
| const res = await request(baseURL).get('/download/wrong-id'); | ||
|
|
||
| expect(res.status).toBe(404); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
These tests share state via uploadedFileId set in a prior it block. If the runner reorders tests or runs them concurrently in the future, the download test can become flaky (uploadedFileId undefined). Consider uploading in a beforeAll for the download suite, or performing the upload within the same test that asserts download behavior.
|
|
||
| ### Prerequisites | ||
|
|
||
| - Node.js 16+ and npm installed |
There was a problem hiding this comment.
The new test instructions say Node.js 16+, but the added test toolchain (vitest@4.1.0) declares Node >=20 in the lockfile. Update the README prerequisites to match the minimum Node version actually required to run npm test successfully.
| - Node.js 16+ and npm installed | |
| - Node.js 20+ and npm installed |
| Or run the npm test script (watch mode): | ||
|
|
||
| ```bash | ||
| cd server | ||
| npm test | ||
| ``` |
There was a problem hiding this comment.
README says "Or run the npm test script (watch mode)", but server/package.json defines npm test as vitest run (non-watch, single run). Adjust the wording (or the script) so the described behavior matches what actually runs.
|
|
||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v3 |
There was a problem hiding this comment.
GitHub Actions is using actions/checkout@v3. Consider bumping to actions/checkout@v4 to stay on the currently supported major version and receive the latest fixes.
| uses: actions/checkout@v3 | |
| uses: actions/checkout@v4 |
Add tests, CI workflow, and improve README
What’s included
This PR adds proper test coverage, sets up CI, and improves the project documentation.
Testing
Added tests for the main functionality using Vitest and Supertest.
Covered:
Also handled async cases and made sure WebSocket connections are cleaned up properly.
CI setup
Added a GitHub Actions workflow that:
This helps catch issues early before merging.
README updates
Notes
4000index.jsto support testing. The server start is now controlled so it does not run immediately during tests.