next-api-mock 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.eslintrc.js +19 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/misc.xml +17 -0
- package/.idea/modules.xml +8 -0
- package/.idea/next-api-mock.iml +9 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +34 -0
- package/__tests__/configValidator.test.ts +69 -0
- package/__tests__/graphqlMock.test.ts +53 -0
- package/dist/cache.d.ts +19 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +37 -0
- package/dist/configValidator.d.ts +8 -0
- package/dist/configValidator.d.ts.map +1 -0
- package/dist/configValidator.js +23 -0
- package/dist/graphqlMock.d.ts +5 -0
- package/dist/graphqlMock.d.ts.map +1 -0
- package/dist/graphqlMock.js +49 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +242 -0
- package/dist/middleware.d.ts +4 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +20 -0
- package/dist/mockDatabase.d.ts +12 -0
- package/dist/mockDatabase.d.ts.map +1 -0
- package/dist/mockDatabase.js +35 -0
- package/dist/monitoring.d.ts +15 -0
- package/dist/monitoring.d.ts.map +1 -0
- package/dist/monitoring.js +86 -0
- package/dist/plugins.d.ts +12 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +19 -0
- package/dist/security.d.ts +7 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +40 -0
- package/dist/serverMock.d.ts +11 -0
- package/dist/serverMock.d.ts.map +1 -0
- package/dist/serverMock.js +115 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/validation.d.ts +9 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +19 -0
- package/jest.config.js +13 -0
- package/package.json +57 -0
- package/src/cache.ts +36 -0
- package/src/configValidator.ts +23 -0
- package/src/graphqlMock.ts +44 -0
- package/src/index.ts +210 -0
- package/src/middleware.ts +14 -0
- package/src/mockDatabase.ts +33 -0
- package/src/monitoring.ts +47 -0
- package/src/plugins.ts +16 -0
- package/src/rateLimit.ts +26 -0
- package/src/security.ts +42 -0
- package/src/serverMock.ts +127 -0
- package/src/types.ts +44 -0
- package/src/validation.ts +19 -0
- package/tsconfig.json +29 -0
- package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,4 @@
|
|
1
|
+
import { NextApiRequest } from 'next';
|
2
|
+
import { MockResponse, MockInterceptor } from './types';
|
3
|
+
export declare function applyMiddleware(req: NextApiRequest, response: MockResponse, interceptor: MockInterceptor | null): Promise<MockResponse>;
|
4
|
+
//# sourceMappingURL=middleware.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AACrC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEvD,wBAAsB,eAAe,CACjC,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,YAAY,EACtB,WAAW,EAAE,eAAe,GAAG,IAAI,GACpC,OAAO,CAAC,YAAY,CAAC,CAKvB"}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
+
exports.applyMiddleware = applyMiddleware;
|
13
|
+
function applyMiddleware(req, response, interceptor) {
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
15
|
+
if (interceptor) {
|
16
|
+
return yield interceptor(req, response);
|
17
|
+
}
|
18
|
+
return response;
|
19
|
+
});
|
20
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
export declare function createMockDatabase<T extends {
|
2
|
+
id: string | number;
|
3
|
+
}>(): {
|
4
|
+
getAll: () => T[];
|
5
|
+
getById: (id: string | number) => T | undefined;
|
6
|
+
create: (item: T) => T;
|
7
|
+
update: (id: string | number, updates: Partial<T>) => T | null;
|
8
|
+
delete: (id: string | number) => T | null;
|
9
|
+
reset: () => void;
|
10
|
+
query: (predicate: (item: T) => boolean) => T[];
|
11
|
+
};
|
12
|
+
//# sourceMappingURL=mockDatabase.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"mockDatabase.d.ts","sourceRoot":"","sources":["../src/mockDatabase.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE;;kBAK9C,MAAM,GAAG,MAAM;mBACd,CAAC;iBAIH,MAAM,GAAG,MAAM,WAAW,OAAO,CAAC,CAAC,CAAC;iBAQpC,MAAM,GAAG,MAAM;;uBAYT,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO;EAE9C"}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.createMockDatabase = createMockDatabase;
|
4
|
+
function createMockDatabase() {
|
5
|
+
let data = [];
|
6
|
+
return {
|
7
|
+
getAll: () => [...data],
|
8
|
+
getById: (id) => data.find(item => item.id === id),
|
9
|
+
create: (item) => {
|
10
|
+
data.push(item);
|
11
|
+
return item;
|
12
|
+
},
|
13
|
+
update: (id, updates) => {
|
14
|
+
const index = data.findIndex(item => item.id === id);
|
15
|
+
if (index !== -1) {
|
16
|
+
data[index] = Object.assign(Object.assign({}, data[index]), updates);
|
17
|
+
return data[index];
|
18
|
+
}
|
19
|
+
return null;
|
20
|
+
},
|
21
|
+
delete: (id) => {
|
22
|
+
const index = data.findIndex(item => item.id === id);
|
23
|
+
if (index !== -1) {
|
24
|
+
const deleted = data[index];
|
25
|
+
data.splice(index, 1);
|
26
|
+
return deleted;
|
27
|
+
}
|
28
|
+
return null;
|
29
|
+
},
|
30
|
+
reset: () => {
|
31
|
+
data = [];
|
32
|
+
},
|
33
|
+
query: (predicate) => data.filter(predicate)
|
34
|
+
};
|
35
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
2
|
+
import { MockResponse } from './types';
|
3
|
+
/**
|
4
|
+
* Collects metrics for a mock request.
|
5
|
+
* @param req The incoming request.
|
6
|
+
* @param res The outgoing response.
|
7
|
+
* @param mockResponse The mock response.
|
8
|
+
*/
|
9
|
+
export declare function collectMetrics(req: NextApiRequest, res: NextApiResponse, mockResponse: MockResponse): void;
|
10
|
+
/**
|
11
|
+
* Reports the collected metrics.
|
12
|
+
* @returns A Promise that resolves to a string representation of the collected metrics.
|
13
|
+
*/
|
14
|
+
export declare function reportMetrics(): Promise<string>;
|
15
|
+
//# sourceMappingURL=monitoring.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"monitoring.d.ts","sourceRoot":"","sources":["../src/monitoring.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AActC;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,GAAG,IAAI,CAe1G;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErD"}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
19
|
+
var ownKeys = function(o) {
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
21
|
+
var ar = [];
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
23
|
+
return ar;
|
24
|
+
};
|
25
|
+
return ownKeys(o);
|
26
|
+
};
|
27
|
+
return function (mod) {
|
28
|
+
if (mod && mod.__esModule) return mod;
|
29
|
+
var result = {};
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
31
|
+
__setModuleDefault(result, mod);
|
32
|
+
return result;
|
33
|
+
};
|
34
|
+
})();
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
42
|
+
});
|
43
|
+
};
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
45
|
+
exports.collectMetrics = collectMetrics;
|
46
|
+
exports.reportMetrics = reportMetrics;
|
47
|
+
const prometheus = __importStar(require("prom-client"));
|
48
|
+
const requestCounter = new prometheus.Counter({
|
49
|
+
name: 'mock_requests_total',
|
50
|
+
help: 'Total number of mock requests',
|
51
|
+
labelNames: ['method', 'path', 'status'],
|
52
|
+
});
|
53
|
+
const responseTimeHistogram = new prometheus.Histogram({
|
54
|
+
name: 'mock_response_time_seconds',
|
55
|
+
help: 'Response time in seconds',
|
56
|
+
labelNames: ['method', 'path'],
|
57
|
+
});
|
58
|
+
/**
|
59
|
+
* Collects metrics for a mock request.
|
60
|
+
* @param req The incoming request.
|
61
|
+
* @param res The outgoing response.
|
62
|
+
* @param mockResponse The mock response.
|
63
|
+
*/
|
64
|
+
function collectMetrics(req, res, mockResponse) {
|
65
|
+
const labels = {
|
66
|
+
method: req.method || 'UNKNOWN',
|
67
|
+
path: req.url || 'UNKNOWN',
|
68
|
+
status: mockResponse.status.toString(),
|
69
|
+
};
|
70
|
+
requestCounter.inc(labels);
|
71
|
+
const responseTime = process.hrtime();
|
72
|
+
res.on('finish', () => {
|
73
|
+
const [seconds, nanoseconds] = process.hrtime(responseTime);
|
74
|
+
const duration = seconds + nanoseconds / 1e9;
|
75
|
+
responseTimeHistogram.observe({ method: labels.method, path: labels.path }, duration);
|
76
|
+
});
|
77
|
+
}
|
78
|
+
/**
|
79
|
+
* Reports the collected metrics.
|
80
|
+
* @returns A Promise that resolves to a string representation of the collected metrics.
|
81
|
+
*/
|
82
|
+
function reportMetrics() {
|
83
|
+
return __awaiter(this, void 0, void 0, function* () {
|
84
|
+
return yield prometheus.register.metrics();
|
85
|
+
});
|
86
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { MockConfig, Plugin } from './types';
|
2
|
+
/**
|
3
|
+
* Registers a new plugin.
|
4
|
+
* @param plugin The plugin to register.
|
5
|
+
*/
|
6
|
+
export declare function registerPlugin(plugin: Plugin): void;
|
7
|
+
/**
|
8
|
+
* Initializes all registered plugins with the provided configuration.
|
9
|
+
* @param config The mock configuration.
|
10
|
+
*/
|
11
|
+
export declare function initializePlugins(config: MockConfig): void;
|
12
|
+
//# sourceMappingURL=plugins.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAE5C;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEnD;AACD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAE1D"}
|
package/dist/plugins.js
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.registerPlugin = registerPlugin;
|
4
|
+
exports.initializePlugins = initializePlugins;
|
5
|
+
const plugins = [];
|
6
|
+
/**
|
7
|
+
* Registers a new plugin.
|
8
|
+
* @param plugin The plugin to register.
|
9
|
+
*/
|
10
|
+
function registerPlugin(plugin) {
|
11
|
+
plugins.push(plugin);
|
12
|
+
}
|
13
|
+
/**
|
14
|
+
* Initializes all registered plugins with the provided configuration.
|
15
|
+
* @param config The mock configuration.
|
16
|
+
*/
|
17
|
+
function initializePlugins(config) {
|
18
|
+
plugins.forEach(plugin => plugin.initialize(config));
|
19
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAItC;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAgC3D"}
|
package/dist/security.js
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.setSecureHeaders = setSecureHeaders;
|
7
|
+
const helmet_1 = __importDefault(require("helmet"));
|
8
|
+
/**
|
9
|
+
* Sets secure headers on the response using helmet.
|
10
|
+
* @param res The Next.js API response object.
|
11
|
+
*/
|
12
|
+
function setSecureHeaders(res) {
|
13
|
+
const helmetMiddleware = (0, helmet_1.default)({
|
14
|
+
contentSecurityPolicy: {
|
15
|
+
directives: {
|
16
|
+
defaultSrc: ["'self'"],
|
17
|
+
scriptSrc: ["'self'", "'unsafe-inline'"],
|
18
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
19
|
+
imgSrc: ["'self'", "data:", "https:"],
|
20
|
+
},
|
21
|
+
},
|
22
|
+
referrerPolicy: {
|
23
|
+
policy: 'strict-origin-when-cross-origin',
|
24
|
+
},
|
25
|
+
});
|
26
|
+
// Create a mock request object
|
27
|
+
const mockReq = {};
|
28
|
+
// Cast NextApiResponse to ServerResponse
|
29
|
+
const nodeRes = res;
|
30
|
+
// Define a named function to handle potential errors
|
31
|
+
const handleHelmetError = (err) => {
|
32
|
+
if (err) {
|
33
|
+
console.error('Helmet middleware error:', err);
|
34
|
+
// You might want to set a default security header if Helmet fails
|
35
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
36
|
+
}
|
37
|
+
};
|
38
|
+
// Apply helmet middleware with error handling
|
39
|
+
helmetMiddleware(mockReq, nodeRes, handleHelmetError);
|
40
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { NextResponse } from 'next/server';
|
2
|
+
import { MockResponse, MockConfig, ServerComponentMock } from './types';
|
3
|
+
export declare function configureServerMocks(config: MockConfig, baseUrl?: string): void;
|
4
|
+
export declare function resetServerMocks(): void;
|
5
|
+
export declare function mockServerAction<TArgs extends unknown[], TReturn>(actionName: string, ...args: TArgs): Promise<TReturn>;
|
6
|
+
export declare function createServerComponentMock<TProps>(componentName: string): ServerComponentMock<TProps, Promise<NextResponse>>;
|
7
|
+
export interface TypedMockResponse<T> extends MockResponse {
|
8
|
+
data: T;
|
9
|
+
}
|
10
|
+
export declare function createTypedMock<T>(response: TypedMockResponse<T>): TypedMockResponse<T>;
|
11
|
+
//# sourceMappingURL=serverMock.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"serverMock.d.ts","sourceRoot":"","sources":["../src/serverMock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAKvE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAK/E;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED,wBAAsB,gBAAgB,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EACnE,UAAU,EAAE,MAAM,EAClB,GAAG,IAAI,EAAE,KAAK,GACf,OAAO,CAAC,OAAO,CAAC,CAclB;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAC5C,aAAa,EAAE,MAAM,GACtB,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAiBpD;AA+DD,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAE,SAAQ,YAAY;IACtD,IAAI,EAAE,CAAC,CAAA;CACV;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAEvF"}
|
@@ -0,0 +1,115 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
+
exports.configureServerMocks = configureServerMocks;
|
13
|
+
exports.resetServerMocks = resetServerMocks;
|
14
|
+
exports.mockServerAction = mockServerAction;
|
15
|
+
exports.createServerComponentMock = createServerComponentMock;
|
16
|
+
exports.createTypedMock = createTypedMock;
|
17
|
+
const server_1 = require("next/server");
|
18
|
+
let serverMockConfig = {};
|
19
|
+
let mockBaseUrl = 'https://example.com';
|
20
|
+
function configureServerMocks(config, baseUrl) {
|
21
|
+
serverMockConfig = Object.assign(Object.assign({}, serverMockConfig), config);
|
22
|
+
if (baseUrl) {
|
23
|
+
mockBaseUrl = baseUrl;
|
24
|
+
}
|
25
|
+
}
|
26
|
+
function resetServerMocks() {
|
27
|
+
serverMockConfig = {};
|
28
|
+
mockBaseUrl = 'https://example.com';
|
29
|
+
}
|
30
|
+
function mockServerAction(actionName, ...args) {
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
32
|
+
const mockConfig = serverMockConfig[actionName];
|
33
|
+
if (typeof mockConfig === 'function') {
|
34
|
+
const mockRequest = yield createMockNextRequest({
|
35
|
+
method: 'POST',
|
36
|
+
body: JSON.stringify(args),
|
37
|
+
});
|
38
|
+
const result = yield mockConfig(mockRequest);
|
39
|
+
return result.data;
|
40
|
+
}
|
41
|
+
else if (mockConfig) {
|
42
|
+
return mockConfig.data;
|
43
|
+
}
|
44
|
+
throw new Error(`No mock configured for server action: ${actionName}`);
|
45
|
+
});
|
46
|
+
}
|
47
|
+
function createServerComponentMock(componentName) {
|
48
|
+
return (props) => __awaiter(this, void 0, void 0, function* () {
|
49
|
+
const mockConfig = serverMockConfig[componentName];
|
50
|
+
if (typeof mockConfig === 'function') {
|
51
|
+
const searchParams = new URLSearchParams(props);
|
52
|
+
const mockRequest = yield createMockNextRequest({
|
53
|
+
method: 'GET',
|
54
|
+
url: `${mockBaseUrl}?${searchParams.toString()}`
|
55
|
+
});
|
56
|
+
const mockResponse = yield mockConfig(mockRequest);
|
57
|
+
return createMockNextResponse(mockResponse);
|
58
|
+
}
|
59
|
+
else if (mockConfig) {
|
60
|
+
return createMockNextResponse(mockConfig);
|
61
|
+
}
|
62
|
+
throw new Error(`No mock configured for server component: ${componentName}`);
|
63
|
+
});
|
64
|
+
}
|
65
|
+
function createMockNextRequest(options) {
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
67
|
+
const url = options.url || mockBaseUrl;
|
68
|
+
const init = {
|
69
|
+
method: options.method || 'GET',
|
70
|
+
headers: options.headers,
|
71
|
+
body: options.body,
|
72
|
+
};
|
73
|
+
const request = new Request(url, init);
|
74
|
+
const nextRequest = new server_1.NextRequest(request);
|
75
|
+
Object.defineProperties(nextRequest, {
|
76
|
+
geo: {
|
77
|
+
value: {
|
78
|
+
city: 'Mock City',
|
79
|
+
country: 'Mock Country',
|
80
|
+
region: 'Mock Region',
|
81
|
+
latitude: '0',
|
82
|
+
longitude: '0'
|
83
|
+
},
|
84
|
+
writable: true
|
85
|
+
},
|
86
|
+
ip: {
|
87
|
+
value: '127.0.0.1',
|
88
|
+
writable: true
|
89
|
+
}
|
90
|
+
});
|
91
|
+
return nextRequest;
|
92
|
+
});
|
93
|
+
}
|
94
|
+
function createMockNextResponse(mockResponse) {
|
95
|
+
const { data, status = 200, headers = {} } = mockResponse;
|
96
|
+
const bodyContent = typeof data === 'string' ? data : JSON.stringify(data);
|
97
|
+
const stream = new ReadableStream({
|
98
|
+
start(controller) {
|
99
|
+
controller.enqueue(new TextEncoder().encode(bodyContent));
|
100
|
+
controller.close();
|
101
|
+
}
|
102
|
+
});
|
103
|
+
const response = new Response(stream, {
|
104
|
+
status,
|
105
|
+
headers: new Headers(headers)
|
106
|
+
});
|
107
|
+
return new server_1.NextResponse(response.body, {
|
108
|
+
status,
|
109
|
+
headers: new Headers(headers),
|
110
|
+
url: mockBaseUrl
|
111
|
+
});
|
112
|
+
}
|
113
|
+
function createTypedMock(response) {
|
114
|
+
return response;
|
115
|
+
}
|
package/dist/types.d.ts
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
3
|
+
export type MockResponse<T = unknown> = {
|
4
|
+
status: number;
|
5
|
+
data: T;
|
6
|
+
delay?: number;
|
7
|
+
headers?: Record<string, string>;
|
8
|
+
};
|
9
|
+
export type MockFunction = (req: NextApiRequest | NextRequest) => MockResponse<unknown> | Promise<MockResponse<unknown>>;
|
10
|
+
export type MockConfig = {
|
11
|
+
[key: string]: MockResponse<unknown> | MockFunction;
|
12
|
+
};
|
13
|
+
export type RequestLogger = (req: NextApiRequest, res: NextApiResponse) => void;
|
14
|
+
export type MockInterceptor = (req: NextApiRequest | NextRequest, mockResponse: MockResponse<unknown>) => MockResponse<unknown> | Promise<MockResponse<unknown>>;
|
15
|
+
export type ServerActionMock<TArgs extends unknown[], TReturn> = (...args: TArgs) => TReturn;
|
16
|
+
export type ServerComponentMock<TProps, TReturn = Promise<NextResponse>> = (props: TProps) => TReturn;
|
17
|
+
export type MockOptions = {
|
18
|
+
enableLogging: boolean;
|
19
|
+
cacheTimeout: number;
|
20
|
+
defaultDelay: number;
|
21
|
+
errorRate: number;
|
22
|
+
};
|
23
|
+
export type MiddlewareFunction = (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
24
|
+
export type Plugin = {
|
25
|
+
name: string;
|
26
|
+
initialize: (config: MockConfig) => void;
|
27
|
+
beforeRequest?: (req: NextApiRequest | NextRequest) => void;
|
28
|
+
afterResponse?: (req: NextApiRequest | NextRequest, response: MockResponse<unknown>) => void;
|
29
|
+
errorHandler?: (error: Error, req: NextApiRequest | NextRequest) => MockResponse<unknown>;
|
30
|
+
transformResponse?: (response: MockResponse<unknown>) => MockResponse<unknown>;
|
31
|
+
validateConfig?: (config: MockConfig) => boolean;
|
32
|
+
cleanup?: () => void;
|
33
|
+
};
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AACtD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAEvD,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,CAAC,CAAA;IACP,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,cAAc,GAAG,WAAW,KAAK,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;AAExH,MAAM,MAAM,UAAU,GAAG;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,YAAY,CAAA;CACtD,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;AAE/E,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,cAAc,GAAG,WAAW,EAAE,YAAY,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;AAEhK,MAAM,MAAM,gBAAgB,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,IAAI,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAA;AAE5F,MAAM,MAAM,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;AAErG,MAAM,MAAM,WAAW,GAAG;IACtB,aAAa,EAAE,OAAO,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAE7F,MAAM,MAAM,MAAM,GAAG;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAA;IACxC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,GAAG,WAAW,KAAK,IAAI,CAAA;IAC3D,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,GAAG,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IAC5F,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,GAAG,WAAW,KAAK,YAAY,CAAC,OAAO,CAAC,CAAA;IACzF,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,YAAY,CAAC,OAAO,CAAC,CAAA;IAC9E,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAA;IAChD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB,CAAA"}
|
package/dist/types.js
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
import * as Joi from 'joi';
|
2
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
3
|
+
/**
|
4
|
+
* Creates a request validator middleware using the provided Joi schema.
|
5
|
+
* @param schema The Joi schema to validate against.
|
6
|
+
* @returns A middleware function that validates incoming requests.
|
7
|
+
*/
|
8
|
+
export declare function createRequestValidator(schema: Joi.ObjectSchema): (req: NextApiRequest, res: NextApiResponse, next: () => void) => void;
|
9
|
+
//# sourceMappingURL=validation.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAA;AAC1B,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAEtD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,GAAG,CAAC,YAAY,SAC9C,cAAc,OAAO,eAAe,QAAQ,MAAM,IAAI,UAQtE"}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.createRequestValidator = createRequestValidator;
|
4
|
+
/**
|
5
|
+
* Creates a request validator middleware using the provided Joi schema.
|
6
|
+
* @param schema The Joi schema to validate against.
|
7
|
+
* @returns A middleware function that validates incoming requests.
|
8
|
+
*/
|
9
|
+
function createRequestValidator(schema) {
|
10
|
+
return (req, res, next) => {
|
11
|
+
const { error } = schema.validate(req.body);
|
12
|
+
if (error) {
|
13
|
+
res.status(400).json({ error: 'Invalid request', details: error.details });
|
14
|
+
}
|
15
|
+
else {
|
16
|
+
next();
|
17
|
+
}
|
18
|
+
};
|
19
|
+
}
|
package/jest.config.js
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
/** @type {import('jest').Config} */
|
2
|
+
const config = {
|
3
|
+
preset: 'ts-jest',
|
4
|
+
testEnvironment: 'node',
|
5
|
+
moduleDirectories: ['node_modules', 'src'],
|
6
|
+
transform: {
|
7
|
+
'^.+\\.tsx?$': 'ts-jest',
|
8
|
+
},
|
9
|
+
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$',
|
10
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
11
|
+
};
|
12
|
+
|
13
|
+
module.exports = config;
|
package/package.json
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
{
|
2
|
+
"name": "next-api-mock",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "A production-ready, scalable tool for mocking API and GraphQL responses in Next.js applications",
|
5
|
+
"main": "dist/index.js",
|
6
|
+
"types": "dist/index.d.ts",
|
7
|
+
"scripts": {
|
8
|
+
"build": "tsc",
|
9
|
+
"test": "jest",
|
10
|
+
"lint": "eslint 'src/**/*.ts'",
|
11
|
+
"prepublishOnly": "npm run lint && npm run test && npm run build"
|
12
|
+
},
|
13
|
+
"keywords": [
|
14
|
+
"next.js",
|
15
|
+
"api",
|
16
|
+
"mock",
|
17
|
+
"testing",
|
18
|
+
"development",
|
19
|
+
"graphql",
|
20
|
+
"production-ready"
|
21
|
+
],
|
22
|
+
"author": "Arjun Sharda",
|
23
|
+
"license": "MIT",
|
24
|
+
"peerDependencies": {
|
25
|
+
"next": "^15.0.3",
|
26
|
+
"react": "^18.3.1",
|
27
|
+
"react-dom": "^18.3.1"
|
28
|
+
},
|
29
|
+
"dependencies": {
|
30
|
+
"config": "^3.3.12",
|
31
|
+
"express-rate-limit": "^7.4.1",
|
32
|
+
"graphql": "^16.6.0",
|
33
|
+
"helmet": "^8.0.0",
|
34
|
+
"joi": "^17.13.3",
|
35
|
+
"lru-cache": "^7.18.3",
|
36
|
+
"path-to-regexp": "^6.2.1",
|
37
|
+
"prom-client": "^15.1.3",
|
38
|
+
"winston": "^3.17.0"
|
39
|
+
},
|
40
|
+
"devDependencies": {
|
41
|
+
"@eslint/js": "^9.15.0",
|
42
|
+
"@types/config": "^3.3.3",
|
43
|
+
"@types/express": "^5.0.0",
|
44
|
+
"@types/jest": "^29.5.0",
|
45
|
+
"@types/lru-cache": "^7.10.10",
|
46
|
+
"@types/node": "^18.15.0",
|
47
|
+
"@types/react": "^18.0.28",
|
48
|
+
"@types/react-dom": "^18.3.1",
|
49
|
+
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
50
|
+
"@typescript-eslint/parser": "^5.59.0",
|
51
|
+
"eslint": "^8.38.0",
|
52
|
+
"jest": "^29.5.0",
|
53
|
+
"ts-jest": "^29.1.0",
|
54
|
+
"typescript": "^5.0.4",
|
55
|
+
"typescript-eslint": "^0.0.1-alpha.0"
|
56
|
+
}
|
57
|
+
}
|
package/src/cache.ts
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
import LRU from 'lru-cache'
|
2
|
+
import { MockResponse } from './types'
|
3
|
+
|
4
|
+
const options = {
|
5
|
+
max: 500,
|
6
|
+
ttl: 1000 * 60 * 5 // 5 minutes
|
7
|
+
}
|
8
|
+
|
9
|
+
const cache = new LRU<string, MockResponse>(options)
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Caches a mock response.
|
13
|
+
* @param key The cache key.
|
14
|
+
* @param value The mock response to cache.
|
15
|
+
* @param ttl The time-to-live for the cache entry in milliseconds.
|
16
|
+
*/
|
17
|
+
export function cacheResponse(key: string, value: MockResponse, ttl?: number): void {
|
18
|
+
cache.set(key, value, ttl ? { ttl } : undefined)
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Retrieves a cached mock response.
|
23
|
+
* @param key The cache key.
|
24
|
+
* @returns The cached mock response, or undefined if not found.
|
25
|
+
*/
|
26
|
+
export function getCachedResponse(key: string): MockResponse | undefined {
|
27
|
+
return cache.get(key)
|
28
|
+
}
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Clears the entire cache.
|
32
|
+
*/
|
33
|
+
export function clearCache(): void {
|
34
|
+
cache.clear()
|
35
|
+
}
|
36
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { MockConfig } from './types'
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Validates the provided mock configuration.
|
5
|
+
* @param config The mock configuration to validate.
|
6
|
+
* @throws Error if the configuration is invalid.
|
7
|
+
*/
|
8
|
+
export function validateConfig(config: MockConfig): void {
|
9
|
+
Object.entries(config).forEach(([path, value]) => {
|
10
|
+
if (typeof value !== 'function' && typeof value !== 'object') {
|
11
|
+
throw new Error(`Invalid configuration for path ${path}. Expected function or object, got ${typeof value}`)
|
12
|
+
}
|
13
|
+
if (typeof value === 'object' && value !== null) {
|
14
|
+
if (!Object.prototype.hasOwnProperty.call(value, 'data')) {
|
15
|
+
throw new Error(`Invalid configuration for path ${path}. Missing 'data' property`)
|
16
|
+
}
|
17
|
+
if (!Object.prototype.hasOwnProperty.call(value, 'status')) {
|
18
|
+
throw new Error(`Invalid configuration for path ${path}. Missing 'status' property`)
|
19
|
+
}
|
20
|
+
}
|
21
|
+
})
|
22
|
+
}
|
23
|
+
|