1import type { IncomingMessage, ServerResponse } from 'http';
2import type { Socket } from 'net';
3import { URL } from 'url';
4
5import { METRO_INSPECTOR_RESPONSE_FIXTURE } from './fixtures/metroInspectorResponse';
6import * as JsInspector from '../JsInspector';
7import createJsInspectorMiddleware from '../createJsInspectorMiddleware';
8
9jest.mock('../JsInspector');
10
11describe('createJsInspectorMiddleware', () => {
12  it('should return specific app entity for GET request with given applicationId', async () => {
13    const app = METRO_INSPECTOR_RESPONSE_FIXTURE[0];
14    const req = createRequest(`http://localhost:8081/inspector?applicationId=${app.description}`);
15    const res = createMockedResponse();
16    const next = jest.fn();
17    (
18      JsInspector.queryInspectorAppAsync as jest.MockedFunction<
19        typeof JsInspector.queryInspectorAppAsync
20      >
21    ).mockReturnValue(Promise.resolve(app));
22
23    const middlewareAsync = createJsInspectorMiddleware();
24    await middlewareAsync(req, res as ServerResponse, next);
25
26    expectMockedResponse(res, 200, JSON.stringify(app));
27  });
28
29  it('should handle ipv6 address', async () => {
30    const app = METRO_INSPECTOR_RESPONSE_FIXTURE[0];
31    const req = createRequest(
32      `http://[::ffff:127.0.0.1]/inspector?applicationId=${app.description}`
33    );
34    const res = createMockedResponse();
35    const next = jest.fn();
36    (
37      JsInspector.queryInspectorAppAsync as jest.MockedFunction<
38        typeof JsInspector.queryInspectorAppAsync
39      >
40    ).mockReturnValue(Promise.resolve(app));
41
42    const middlewareAsync = createJsInspectorMiddleware();
43    await middlewareAsync(req, res as ServerResponse, next);
44
45    expectMockedResponse(res, 200, JSON.stringify(app));
46  });
47
48  it('should return 404 for GET request with nonexistent applicationId', async () => {
49    const req = createRequest('http://localhost:8081/inspector?applicationId=nonExistentApp');
50    const res = createMockedResponse();
51    const next = jest.fn();
52    (
53      JsInspector.queryInspectorAppAsync as jest.MockedFunction<
54        typeof JsInspector.queryInspectorAppAsync
55      >
56    ).mockReturnValue(Promise.resolve(null));
57
58    const middlewareAsync = createJsInspectorMiddleware();
59    await middlewareAsync(req, res as ServerResponse, next);
60
61    expectMockedResponse(res, 404);
62  });
63
64  it('should return 400 for GET request without parameters', async () => {
65    const req = createRequest('http://localhost:8081/inspector');
66    const res = createMockedResponse();
67    const next = jest.fn();
68
69    const middlewareAsync = createJsInspectorMiddleware();
70    await middlewareAsync(req, res as ServerResponse, next);
71
72    expectMockedResponse(res, 400);
73  });
74
75  it('should open browser for PUT request with given applicationId', async () => {
76    const app = METRO_INSPECTOR_RESPONSE_FIXTURE[0];
77    const req = createRequest(
78      `http://localhost:8081/inspector?applicationId=${app.description}`,
79      'PUT'
80    );
81    const res = createMockedResponse();
82    const next = jest.fn();
83    (
84      JsInspector.queryInspectorAppAsync as jest.MockedFunction<
85        typeof JsInspector.queryInspectorAppAsync
86      >
87    ).mockReturnValue(Promise.resolve(app));
88
89    const middlewareAsync = createJsInspectorMiddleware();
90    await middlewareAsync(req, res as ServerResponse, next);
91
92    expectMockedResponse(res, 200);
93    expect(JsInspector.openJsInspector).toHaveBeenCalledTimes(1);
94  });
95});
96
97function createRequest(requestUrl: string, method?: 'GET' | 'POST' | 'PUT'): IncomingMessage {
98  const url = new URL(requestUrl);
99  const req: Partial<IncomingMessage> = {
100    method: method || 'GET',
101    headers: {
102      host: url.host,
103    },
104    socket: {
105      localAddress: url.hostname,
106      localPort: Number(url.port || 80),
107    } as Socket,
108    url: `${url.pathname}${url.search}`,
109  };
110  return req as IncomingMessage;
111}
112
113interface MockedResponse extends Partial<ServerResponse> {
114  end: jest.Mock;
115  writeHead: jest.Mock;
116  write: jest.Mock;
117}
118
119function createMockedResponse(): MockedResponse {
120  return {
121    end: jest.fn(),
122    writeHead: jest.fn().mockReturnThis(),
123    write: jest.fn().mockReturnThis(),
124  };
125}
126
127function expectMockedResponse(res: MockedResponse, status: number, body?: string) {
128  if (status !== 200) {
129    expect(res.writeHead.mock.calls[0][0]).toBe(status);
130  }
131  if (body) {
132    expect(res.end.mock.calls[0][0]).toBe(body);
133  }
134}
135