Skip to content

Commit a41b8d1

Browse files
Add more tests
1 parent 422fa2c commit a41b8d1

File tree

13 files changed

+1003
-418
lines changed

13 files changed

+1003
-418
lines changed

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,64 @@ app.listen();
183183
console.log('Server running on http://localhost:3000');
184184
```
185185

186+
#### ConnectionManager API
187+
188+
The `ConnectionManager` class provides the following methods and properties:
189+
190+
- **Methods**:
191+
- `setServer(server: Server)`: Associates the connection manager with a server instance
192+
- `addConnection(socket: Socket)`: Registers a new socket connection for tracking
193+
- `removeConnection(socket: Socket)`: Removes a socket from tracking when it closes
194+
- `getStats()`: Returns statistics about connections (`IConnectionStats`)
195+
- `closeAllConnections(gracePeriod?: number)`: Gracefully closes all active connections
196+
- `on(event: ConnectionEvent, listener: Function)`: Subscribes to connection events
197+
- `off(event: ConnectionEvent, listener: Function)`: Unsubscribes from connection events
198+
199+
- **Events**:
200+
- `CONNECTION_ADDED`: Fired when a new connection is established
201+
- `CONNECTION_REMOVED`: Fired when a connection is closed
202+
- `CONNECTION_ERROR`: Fired when a connection encounters an error
203+
- `ALL_CONNECTIONS_CLOSED`: Fired when all connections have been closed
204+
- `SERVER_LISTENING`: Fired when the server starts listening
205+
- `SERVER_CLOSED`: Fired when the server is closed
206+
207+
- **Statistics**:
208+
- `activeConnections`: Number of currently active connections
209+
- `totalConnections`: Total number of connections since server start
210+
- `connectionErrors`: Number of connection errors encountered
211+
- `uptime`: Server uptime in milliseconds
212+
213+
#### Graceful Shutdown Pattern
214+
215+
For production applications, implementing a graceful shutdown pattern is recommended:
216+
217+
```typescript
218+
// Graceful shutdown handler
219+
const gracefulShutdown = async (signal: string) => {
220+
console.log(`${signal} received, starting graceful shutdown`);
221+
222+
// Step 1: Stop accepting new connections (optional)
223+
server.close();
224+
225+
// Step 2: Allow existing connections to finish (with timeout)
226+
console.log('Closing remaining connections...');
227+
await app.connectionManager.closeAllConnections(10000); // 10 second grace period
228+
229+
// Step 3: Close the server completely
230+
console.log('Shutting down server...');
231+
await app.close();
232+
233+
console.log('Shutdown complete');
234+
process.exit(0);
235+
};
236+
237+
// Register shutdown handlers
238+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
239+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
240+
```
241+
242+
This pattern ensures that your server can handle restarts and deployments without dropping active connections.
243+
186244
## Examples
187245

188246
Check out the [examples](/example) directory for more detailed usage examples:
@@ -199,6 +257,7 @@ For detailed documentation, see the [docs](/docs) directory:
199257
- [Request Lifecycle Hooks](/docs/hooks.md) - In-depth documentation of the hooks system
200258
- [Content Type Handling](/docs/content-types.md) - Working with different content types
201259
- [Error Handling](/docs/error-handling.md) - Guide to handling errors at different levels
260+
- [Testing Guide](/TESTING.md) - Comprehensive guide to testing the framework
202261

203262
## Project Structure
204263

@@ -210,6 +269,7 @@ yinzerflow/
210269
│ ├── javascript/ # JavaScript example
211270
│ └── typescript/ # TypeScript example
212271
├── package.json # Package configuration
272+
├── TESTING.md # Testing documentation
213273
└── README.md # This file
214274
```
215275

app/core/__mocks__/Context.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { mock } from 'bun:test';
2+
import type { Context } from '../Context.ts';
3+
4+
/**
5+
* Creates a mock Context object for testing
6+
*
7+
* @returns A mock Context object
8+
*/
9+
export const createMockContext = (): Context =>
10+
({
11+
request: {
12+
method: 'GET',
13+
path: '/test',
14+
params: {},
15+
query: {},
16+
headers: {},
17+
body: null,
18+
},
19+
response: {
20+
setStatus: mock(() => {}),
21+
},
22+
}) as unknown as Context;

app/core/__mocks__/Hook.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { mock } from 'bun:test';
2+
import type { TUndefinableResponseFunction } from '../../types/Route.ts';
3+
4+
type MockHook = TUndefinableResponseFunction & { mockClear: () => void };
5+
6+
/**
7+
* Creates a mock hook function that returns undefined (doesn't interrupt the request flow)
8+
*
9+
* @returns A mock hook function with mock methods
10+
*/
11+
export const createPassthroughHook = (): MockHook => {
12+
return mock(() => undefined) as MockHook;
13+
};
14+
15+
/**
16+
* Creates a mock hook function that returns a response (interrupts the request flow)
17+
*
18+
* @param message - The message to return in the response
19+
* @returns A mock hook function with mock methods
20+
*/
21+
export const createInterruptingHook = (message = 'Interrupted'): MockHook => {
22+
return mock(() => ({ message })) as MockHook;
23+
};
24+
25+
/**
26+
* Creates a mock hook function that throws an error
27+
*
28+
* @param errorMessage - The error message
29+
* @returns A mock hook function with mock methods
30+
*/
31+
export const createErrorHook = (errorMessage = 'Hook error'): MockHook => {
32+
return mock(() => {
33+
throw new Error(errorMessage);
34+
}) as MockHook;
35+
};

app/core/__mocks__/Request.spec.ts

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,56 @@
11
import { HttpMethod } from '../../constants/http.ts';
2-
import type { IHeaders } from '../../types/http/Response.ts';
3-
import type { IRequest, THttpMethod, TRequestBody } from '../../types/http/Request.ts';
2+
import type { Request } from '../Request.ts';
43

54
/**
6-
* A mock implementation of the Request class for testing
5+
* Creates a mock Request object for testing
6+
*
7+
* @param method - HTTP method
8+
* @param path - Request path
9+
* @returns A mock Request object
710
*/
8-
export class MockRequest implements Partial<IRequest> {
9-
protocol: string;
10-
method: THttpMethod;
11+
export const createMockRequest = (method: string = HttpMethod.GET, path = '/test'): Request =>
12+
({
13+
method,
14+
path,
15+
params: {},
16+
query: {},
17+
headers: {},
18+
body: null,
19+
}) as unknown as Request;
20+
21+
/**
22+
* Mock Request class for testing Response
23+
*/
24+
export class MockRequest {
25+
method: string;
1126
path: string;
12-
headers: IHeaders;
13-
body: TRequestBody;
14-
query: Record<string, string>;
27+
headers: Record<string, string>;
28+
body: any;
1529
params: Record<string, string>;
30+
query: Record<string, string>;
31+
protocol: string;
1632

17-
constructor({ protocol = 'HTTP/1.1', method = HttpMethod.GET, path = '/', headers = {}, body = {}, query = {}, params = {} }: Partial<IRequest> = {}) {
18-
this.protocol = protocol;
19-
this.method = method;
20-
this.path = path;
21-
this.headers = headers;
22-
this.body = body;
23-
this.query = query as Record<string, string>;
24-
this.params = params as Record<string, string>;
33+
constructor(
34+
options: {
35+
method?: string;
36+
path?: string;
37+
headers?: Record<string, string>;
38+
body?: any;
39+
params?: Record<string, string>;
40+
query?: Record<string, string>;
41+
protocol?: string;
42+
} = {},
43+
) {
44+
this.method = options.method ?? HttpMethod.GET;
45+
this.path = options.path ?? '/';
46+
this.headers = options.headers ?? {};
47+
this.body = options.body ?? null;
48+
this.params = options.params ?? {};
49+
this.query = options.query ?? {};
50+
this.protocol = options.protocol ?? 'HTTP/1.1';
2551
}
2652

27-
/**
28-
* Creates a default mock request
29-
*/
3053
static createDefault(): MockRequest {
3154
return new MockRequest();
3255
}
33-
34-
/**
35-
* Creates a mock GET request
36-
*/
37-
static createGet(path = '/', query = {}): MockRequest {
38-
return new MockRequest({ method: HttpMethod.GET, path, query });
39-
}
40-
41-
/**
42-
* Creates a mock POST request
43-
*/
44-
static createPost(path = '/', body = {}): MockRequest {
45-
return new MockRequest({
46-
method: HttpMethod.POST,
47-
path,
48-
body,
49-
headers: { 'Content-Type': 'application/json' } as IHeaders,
50-
});
51-
}
5256
}

app/core/__mocks__/Route.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { IRoute } from '../../types/Route.ts';
2+
3+
/**
4+
* Creates a mock Route object for testing
5+
*
6+
* @param path - The route path
7+
* @returns A mock IRoute object
8+
*/
9+
export const createMockRoute = (path: string): IRoute => ({
10+
method: 'GET',
11+
path,
12+
handler: () => ({}),
13+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { TResponseFunction } from '../../types/Route.ts';
2+
3+
/**
4+
* Creates a mock route handler that returns a simple message
5+
*
6+
* @param message - The message to return
7+
* @returns A mock route handler function
8+
*/
9+
export const createMockHandler = (message = 'Hello'): TResponseFunction => {
10+
return () => ({ message });
11+
};

0 commit comments

Comments
 (0)