next-api-mock 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
package/src/middleware.ts CHANGED
@@ -1,14 +1,32 @@
1
- import { NextApiRequest } from 'next'
2
- import { MockResponse, MockInterceptor } from './types'
1
+ import { AnyRequest, AnyResponse, MockResponse, MockInterceptor, MiddlewareFunction } from './types'
3
2
 
4
3
  export async function applyMiddleware(
5
- req: NextApiRequest,
6
- response: MockResponse,
4
+ req: AnyRequest,
5
+ mockResponse: MockResponse<unknown>,
7
6
  interceptor: MockInterceptor | null
8
- ): Promise<MockResponse> {
7
+ ): Promise<MockResponse<unknown>> {
9
8
  if (interceptor) {
10
- return await interceptor(req, response)
9
+ return await interceptor(req, mockResponse)
11
10
  }
12
- return response
11
+ return mockResponse
13
12
  }
14
13
 
14
+ export function createMiddleware(fn: MiddlewareFunction): MiddlewareFunction {
15
+ return async (req: AnyRequest, res: AnyResponse) => {
16
+ await fn(req, res)
17
+ }
18
+ }
19
+
20
+ export const corsMiddleware = createMiddleware(async (req: AnyRequest, res: AnyResponse) => {
21
+ if ('setHeader' in res) {
22
+ res.setHeader('Access-Control-Allow-Origin', '*')
23
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
24
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
25
+ }
26
+ })
27
+
28
+ export const loggingMiddleware = createMiddleware(async (req: AnyRequest, res: AnyResponse) => {
29
+ console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`)
30
+ })
31
+
32
+
package/src/monitoring.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as prometheus from 'prom-client'
2
2
  import { NextApiRequest, NextApiResponse } from 'next'
3
- import { MockResponse } from './types'
3
+ import { NextResponse } from 'next/server'
4
+ import { AnyRequest, AnyResponse, MockResponse } from './types'
4
5
 
5
6
  const requestCounter = new prometheus.Counter({
6
7
  name: 'mock_requests_total',
@@ -20,21 +21,31 @@ const responseTimeHistogram = new prometheus.Histogram({
20
21
  * @param res The outgoing response.
21
22
  * @param mockResponse The mock response.
22
23
  */
23
- export function collectMetrics(req: NextApiRequest, res: NextApiResponse, mockResponse: MockResponse): void {
24
+ export function collectMetrics(req: AnyRequest, res: AnyResponse, mockResponse: MockResponse): void {
24
25
  const labels = {
25
26
  method: req.method || 'UNKNOWN',
26
- path: req.url || 'UNKNOWN',
27
- status: mockResponse.status.toString(),
27
+ path: 'url' in req ? req.url : (req as NextApiRequest).url || 'UNKNOWN',
28
+ status: (mockResponse.status || 200).toString(),
28
29
  }
29
30
 
30
31
  requestCounter.inc(labels)
31
32
 
32
33
  const responseTime = process.hrtime()
33
- res.on('finish', () => {
34
+
35
+ if ('on' in res) {
36
+ // NextApiResponse
37
+ (res as NextApiResponse).on('finish', () => {
38
+ const [seconds, nanoseconds] = process.hrtime(responseTime)
39
+ const duration = seconds + nanoseconds / 1e9
40
+ responseTimeHistogram.observe({ method: labels.method, path: labels.path }, duration)
41
+ })
42
+ } else {
43
+ // NextResponse
44
+ // For NextResponse, we can't hook into 'finish' event, so we'll record the time immediately
34
45
  const [seconds, nanoseconds] = process.hrtime(responseTime)
35
46
  const duration = seconds + nanoseconds / 1e9
36
47
  responseTimeHistogram.observe({ method: labels.method, path: labels.path }, duration)
37
- })
48
+ }
38
49
  }
39
50
 
40
51
  /**
@@ -43,5 +54,4 @@ export function collectMetrics(req: NextApiRequest, res: NextApiResponse, mockRe
43
54
  */
44
55
  export async function reportMetrics(): Promise<string> {
45
56
  return await prometheus.register.metrics()
46
- }
47
-
57
+ }
package/src/serverMock.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server'
2
- import { MockResponse, MockConfig, ServerComponentMock } from './types'
2
+ import { MockResponse, MockConfig, ServerComponentMock, NextRequestType, NextResponseType, AnyRequest } from './types'
3
+ import { isAppRouter, hasServerActions } from './utils/versionCheck'
3
4
 
4
5
  let serverMockConfig: MockConfig = {}
5
6
  let mockBaseUrl = 'https://example.com'
@@ -20,6 +21,10 @@ export async function mockServerAction<TArgs extends unknown[], TReturn>(
20
21
  actionName: string,
21
22
  ...args: TArgs
22
23
  ): Promise<TReturn> {
24
+ if (!hasServerActions) {
25
+ throw new Error('Server Actions are not supported in this Next.js version')
26
+ }
27
+
23
28
  const mockConfig = serverMockConfig[actionName]
24
29
  if (typeof mockConfig === 'function') {
25
30
  const mockRequest = await createMockNextRequest({
@@ -37,8 +42,12 @@ export async function mockServerAction<TArgs extends unknown[], TReturn>(
37
42
 
38
43
  export function createServerComponentMock<TProps>(
39
44
  componentName: string
40
- ): ServerComponentMock<TProps, Promise<NextResponse>> {
41
- return async (props: TProps): Promise<NextResponse> => {
45
+ ): ServerComponentMock<TProps, Promise<NextResponseType>> {
46
+ if (!isAppRouter) {
47
+ throw new Error('Server Components are not supported in this Next.js version')
48
+ }
49
+
50
+ return async (props: TProps): Promise<NextResponseType> => {
42
51
  const mockConfig = serverMockConfig[componentName]
43
52
  if (typeof mockConfig === 'function') {
44
53
  const searchParams = new URLSearchParams(props as Record<string, string>)
@@ -61,7 +70,7 @@ async function createMockNextRequest(options: {
61
70
  url?: string;
62
71
  headers?: HeadersInit;
63
72
  body?: BodyInit;
64
- }): Promise<NextRequest> {
73
+ }): Promise<NextRequestType> {
65
74
  const url = options.url || mockBaseUrl
66
75
 
67
76
  const init: RequestInit = {
@@ -93,27 +102,12 @@ async function createMockNextRequest(options: {
93
102
  return nextRequest
94
103
  }
95
104
 
96
- function createMockNextResponse(mockResponse: MockResponse): NextResponse {
105
+ function createMockNextResponse(mockResponse: MockResponse): NextResponseType {
97
106
  const { data, status = 200, headers = {} } = mockResponse
98
107
 
99
- const bodyContent = typeof data === 'string' ? data : JSON.stringify(data)
100
-
101
- const stream = new ReadableStream({
102
- start(controller) {
103
- controller.enqueue(new TextEncoder().encode(bodyContent))
104
- controller.close()
105
- }
106
- })
107
-
108
- const response = new Response(stream, {
109
- status,
110
- headers: new Headers(headers)
111
- })
112
-
113
- return new NextResponse(response.body, {
108
+ return NextResponse.json(data, {
114
109
  status,
115
110
  headers: new Headers(headers),
116
- url: mockBaseUrl
117
111
  })
118
112
  }
119
113
 
package/src/types.ts CHANGED
@@ -1,44 +1,54 @@
1
- import { NextApiRequest, NextApiResponse } from 'next'
2
- import { NextRequest, NextResponse } from 'next/server'
1
+ import type { NextApiRequest, NextApiResponse } from 'next'
2
+ import type { NextRequest, NextResponse } from 'next/server'
3
3
 
4
- export type MockResponse<T = unknown> = {
5
- status: number
4
+ export type AnyRequest = NextApiRequest | NextRequest
5
+ export type AnyResponse = NextApiResponse | NextResponse
6
+
7
+ export interface MockResponse<T = unknown> {
8
+ status?: number
6
9
  data: T
7
- delay?: number
8
10
  headers?: Record<string, string>
11
+ delay?: number
9
12
  }
10
13
 
11
- export type MockFunction = (req: NextApiRequest | NextRequest) => MockResponse<unknown> | Promise<MockResponse<unknown>>
14
+ export type MockFunction = (req: AnyRequest) => Promise<MockResponse>
12
15
 
13
- export type MockConfig = {
14
- [key: string]: MockResponse<unknown> | MockFunction
16
+ export interface MockConfig {
17
+ [path: string]: MockResponse | MockFunction
15
18
  }
16
19
 
17
- export type RequestLogger = (req: NextApiRequest, res: NextApiResponse) => void
20
+ export type RequestLogger = (req: AnyRequest, res: AnyResponse) => void
18
21
 
19
- export type MockInterceptor = (req: NextApiRequest | NextRequest, mockResponse: MockResponse<unknown>) => MockResponse<unknown> | Promise<MockResponse<unknown>>
22
+ export type MockInterceptor = (req: AnyRequest, mockResponse: MockResponse) => Promise<MockResponse>
20
23
 
21
- export type ServerActionMock<TArgs extends unknown[], TReturn> = (...args: TArgs) => TReturn
22
-
23
- export type ServerComponentMock<TProps, TReturn = Promise<NextResponse>> = (props: TProps) => TReturn
24
-
25
- export type MockOptions = {
24
+ export interface MockOptions {
26
25
  enableLogging: boolean
27
26
  cacheTimeout: number
28
27
  defaultDelay: number
29
28
  errorRate: number
30
29
  }
31
30
 
32
- export type MiddlewareFunction = (req: NextApiRequest, res: NextApiResponse) => Promise<void>
31
+ export type MiddlewareFunction = (req: AnyRequest, res: AnyResponse) => Promise<void>
32
+
33
+ export interface RateLimitOptions {
34
+ windowMs: number
35
+ max: number
36
+ }
33
37
 
34
- export type Plugin = {
38
+ export interface Plugin {
35
39
  name: string
36
40
  initialize: (config: MockConfig) => void
37
- beforeRequest?: (req: NextApiRequest | NextRequest) => void
38
- afterResponse?: (req: NextApiRequest | NextRequest, response: MockResponse<unknown>) => void
39
- errorHandler?: (error: Error, req: NextApiRequest | NextRequest) => MockResponse<unknown>
40
- transformResponse?: (response: MockResponse<unknown>) => MockResponse<unknown>
41
- validateConfig?: (config: MockConfig) => boolean
42
- cleanup?: () => void
43
41
  }
44
42
 
43
+ export function isNextApiRequest(req: AnyRequest): req is NextApiRequest {
44
+ return 'query' in req && 'body' in req && !('nextUrl' in req)
45
+ }
46
+
47
+ export function isNextRequest(req: AnyRequest): req is NextRequest {
48
+ return 'nextUrl' in req && 'geo' in req
49
+ }
50
+
51
+ export type NextRequestType = NextRequest
52
+ export type NextResponseType = NextResponse
53
+
54
+ export type ServerComponentMock<TProps, TReturn> = (props: TProps) => TReturn
@@ -0,0 +1,20 @@
1
+ import semver from 'semver'
2
+ import { version as nextVersion } from 'next/package.json'
3
+
4
+ const coercedVersion = semver.coerce(nextVersion)
5
+
6
+ if (!coercedVersion) {
7
+ throw new Error('Unable to determine Next.js version')
8
+ }
9
+
10
+ export const isNextJs12 = semver.satisfies(coercedVersion, '12.x')
11
+ export const isNextJs13 = semver.satisfies(coercedVersion, '13.x')
12
+ export const isNextJs14 = semver.satisfies(coercedVersion, '14.x')
13
+ export const isNextJs15 = semver.satisfies(coercedVersion, '15.x')
14
+
15
+ export const isAppRouter = semver.gte(coercedVersion, '13.0.0')
16
+ export const hasServerActions = semver.gte(coercedVersion, '13.4.0')
17
+
18
+ export function getNextVersion(): string {
19
+ return nextVersion
20
+ }