-
-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Socket.io server code to TS and adds unit testing (#418)
* feat: initial typescript setup * feat: converts to ts `api-config` file * feat: converts to ts `socket-config` file * feat: converts to ts `rate-limiter` file * feat: converts to ts `utils` file * feat: updates the `Dockerfile` to support prebuilding from typescript * fix: fixes docker image running * feat: adds package.json start:docker script, setup unit testing and adds it to `api-config` file * feat: adds unit tests for `rate-limiter` and `socket-config` files * chore: add jest config files * fix: fixes socket-config tests and refactors utils.ts for testability --------- Co-authored-by: Omri <[email protected]>
- Loading branch information
1 parent
3c1a617
commit dc7e024
Showing
19 changed files
with
1,218 additions
and
359 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,27 @@ | ||
FROM node:18-alpine | ||
|
||
RUN apk update && apk upgrade && apk add --no-cache zlib | ||
# Build stage | ||
FROM node:18-alpine AS builder | ||
|
||
# Install build dependencies and build the project | ||
WORKDIR /app | ||
COPY package.json ./ | ||
RUN yarn install | ||
COPY . . | ||
RUN yarn build | ||
|
||
COPY package.json . | ||
# Runtime stage | ||
FROM node:18-alpine | ||
|
||
RUN yarn install | ||
# Install runtime dependencies | ||
WORKDIR /app | ||
COPY --from=builder /app/package.json ./ | ||
RUN yarn install --production | ||
|
||
COPY . . | ||
# Copy built project and .env file from the build stage | ||
COPY --from=builder /app/dist ./dist | ||
COPY .env ./ | ||
|
||
# Expose the server port | ||
EXPOSE 4000 | ||
|
||
CMD ["yarn","start"] | ||
# Start the server | ||
CMD ["node", "dist/index.js"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
class Analytics { | ||
constructor(writeKey: string, options?: object) { | ||
// Mock constructor | ||
} | ||
|
||
track(event: object, callback?: (err: Error | null) => void): void { | ||
// Mock track method | ||
if (callback) { | ||
callback(null); | ||
} | ||
} | ||
} | ||
|
||
export default Analytics; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import request from 'supertest'; | ||
import { app } from './api-config'; | ||
|
||
jest.mock('analytics-node'); | ||
|
||
describe('API Config', () => { | ||
describe('GET /', () => { | ||
it('should respond with success', async () => { | ||
const response = await request(app).get('/'); | ||
expect(response.status).toBe(200); | ||
expect(response.body).toStrictEqual({ success: true }); | ||
}); | ||
}); | ||
|
||
describe('POST /debug', () => { | ||
it('should respond with error when event is missing', async () => { | ||
const response = await request(app).post('/debug').send({}); | ||
expect(response.status).toBe(400); | ||
expect(response.body).toStrictEqual({ error: 'event is required' }); | ||
}); | ||
|
||
it('should respond with error when event name is wrong', async () => { | ||
const response = await request(app) | ||
.post('/debug') | ||
.send({ event: 'wrong_event' }); | ||
expect(response.status).toBe(400); | ||
expect(response.body).toStrictEqual({ error: 'wrong event name' }); | ||
}); | ||
|
||
it('should respond with success when event is correct', async () => { | ||
const response = await request(app) | ||
.post('/debug') | ||
.send({ event: 'sdk_test' }); | ||
expect(response.status).toBe(200); | ||
expect(response.body).toStrictEqual({ success: true }); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import dotenv from 'dotenv'; | ||
dotenv.config(); | ||
|
||
import http from 'http'; | ||
import { app, analytics } from './api-config'; | ||
import configureSocketIO from './socket-config'; | ||
import { cleanupAndExit } from './utils'; | ||
|
||
const isDevelopment: boolean = process.env.NODE_ENV === 'development'; | ||
|
||
const server = http.createServer(app); | ||
configureSocketIO(server); // configure socket.io server | ||
|
||
console.log('INFO> isDevelopment?', isDevelopment); | ||
|
||
// Register event listeners for process termination events | ||
process.on('SIGINT', async () => { | ||
await cleanupAndExit(server, analytics); | ||
}); | ||
process.on('SIGTERM', async () => { | ||
await cleanupAndExit(server, analytics); | ||
}); | ||
|
||
const port: number = Number(process.env.PORT) || 4000; | ||
server.listen(port, () => { | ||
console.log(`INFO> listening on *:${port}`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import baseConfig from '../../jest.config.base'; | ||
|
||
module.exports = { | ||
...baseConfig, | ||
testEnvironment: 'node', | ||
coveragePathIgnorePatterns: ['./types', './index.ts'], | ||
collectCoverageFrom: ['*.ts'], | ||
coverageThreshold: { | ||
global: { | ||
branches: 28, | ||
functions: 44, | ||
lines: 49, | ||
statements: 49, | ||
}, | ||
}, | ||
clearMocks: true, | ||
resetMocks: false, | ||
restoreMocks: false, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"watch": ["."], | ||
"ext": "ts", | ||
"exec": "ts-node" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { | ||
resetRateLimits, | ||
increaseRateLimits, | ||
setLastConnectionErrorTimestamp, | ||
} from './rate-limiter'; | ||
|
||
import os from 'os'; | ||
|
||
jest.mock('os'); | ||
|
||
describe('rate-limiter', () => { | ||
let consoleLogSpy: jest.SpyInstance; | ||
|
||
beforeEach(() => { | ||
jest.resetModules(); // Clear any cached modules, which includes the rateLimiter instances. | ||
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(); | ||
}); | ||
|
||
afterEach(() => { | ||
consoleLogSpy.mockRestore(); | ||
}); | ||
|
||
it('resetRateLimits should reset rate limits', () => { | ||
setLastConnectionErrorTimestamp(Date.now() - 10001); | ||
resetRateLimits(); | ||
|
||
expect(consoleLogSpy).toHaveBeenCalledWith( | ||
expect.stringContaining('INFO> RL points:'), | ||
); | ||
}); | ||
|
||
it('increaseRateLimits should adjust rate limits based on system load', () => { | ||
(os.loadavg as jest.Mock).mockReturnValue([0.5, 0.5, 0.5]); | ||
(os.cpus as jest.Mock).mockReturnValue(new Array(4)); | ||
(os.totalmem as jest.Mock).mockReturnValue(10000000); | ||
(os.freemem as jest.Mock).mockReturnValue(5000000); | ||
increaseRateLimits(50); | ||
|
||
expect(consoleLogSpy).toHaveBeenCalledWith( | ||
expect.stringContaining('INFO> RL points:'), | ||
); | ||
}); | ||
}); |
Oops, something went wrong.