@salesforce/pwa-kit-runtime 3.8.0-preview.0-basepath → 3.8.0-preview.2-basepath
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -5
- package/ssr/server/build-remote-server.js +1159 -0
- package/ssr/server/build-remote-server.test.js +41 -0
- package/ssr/server/constants.js +37 -0
- package/ssr/server/express.js +462 -0
- package/ssr/server/express.lambda.test.js +390 -0
- package/ssr/server/express.test.js +963 -0
- package/ssr/server/test_fixtures/favicon.ico +0 -0
- package/ssr/server/test_fixtures/loadable-stats.json +1 -0
- package/ssr/server/test_fixtures/localhost.pem +45 -0
- package/ssr/server/test_fixtures/main.js +7 -0
- package/ssr/server/test_fixtures/mobify.png +0 -0
- package/ssr/server/test_fixtures/server-renderer.js +12 -0
- package/ssr/server/test_fixtures/worker.js +7 -0
- package/ssr/server/test_fixtures/worker.js.map +1 -0
- package/utils/logger-factory.js +154 -0
- package/utils/logger-factory.test.js +71 -0
- package/utils/logger-instance.js +19 -0
- package/utils/middleware/index.js +16 -0
- package/utils/middleware/security.js +111 -0
- package/utils/middleware/security.test.js +110 -0
- package/utils/morgan-stream.js +28 -0
- package/utils/ssr-cache.js +177 -0
- package/utils/ssr-cache.test.js +64 -0
- package/utils/ssr-config.client.js +23 -0
- package/utils/ssr-config.client.test.js +25 -0
- package/utils/ssr-config.js +20 -0
- package/utils/ssr-config.server.js +98 -0
- package/utils/ssr-config.server.test.js +50 -0
- package/utils/ssr-namespace-paths.js +60 -0
- package/utils/ssr-namespace-paths.test.js +30 -0
- package/utils/ssr-paths.js +51 -0
- package/utils/ssr-paths.test.js +49 -0
- package/utils/ssr-proxying.js +859 -0
- package/utils/ssr-proxying.test.js +593 -0
- package/utils/ssr-request-processing.js +164 -0
- package/utils/ssr-request-processing.test.js +95 -0
- package/utils/ssr-server/cached-response.js +116 -0
- package/utils/ssr-server/configure-proxy.js +304 -0
- package/utils/ssr-server/metrics-sender.js +204 -0
- package/utils/ssr-server/outgoing-request-hook.js +139 -0
- package/utils/ssr-server/parse-end-parameters.js +38 -0
- package/utils/ssr-server/process-express-response.js +56 -0
- package/utils/ssr-server/process-lambda-response.js +43 -0
- package/utils/ssr-server/update-global-agent-options.js +41 -0
- package/utils/ssr-server/update-global-agent-options.test.js +28 -0
- package/utils/ssr-server/utils.js +124 -0
- package/utils/ssr-server/utils.test.js +69 -0
- package/utils/ssr-server/wrap-response-write.js +40 -0
- package/utils/ssr-server.js +115 -0
- package/utils/ssr-server.test.js +869 -0
- package/utils/ssr-shared.js +183 -0
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _fsExtra = _interopRequireDefault(require("fs-extra"));
|
|
4
|
+
var _https = _interopRequireDefault(require("https"));
|
|
5
|
+
var _nock = _interopRequireDefault(require("nock"));
|
|
6
|
+
var _os = _interopRequireDefault(require("os"));
|
|
7
|
+
var _path = _interopRequireDefault(require("path"));
|
|
8
|
+
var _sinon = _interopRequireDefault(require("sinon"));
|
|
9
|
+
var _superagent = _interopRequireDefault(require("superagent"));
|
|
10
|
+
var _supertest = _interopRequireDefault(require("supertest"));
|
|
11
|
+
var _express = _interopRequireDefault(require("express"));
|
|
12
|
+
var _ssrCache = require("../../utils/ssr-cache");
|
|
13
|
+
var _ssrServer = require("../../utils/ssr-server");
|
|
14
|
+
var ssrServerUtils = _interopRequireWildcard(require("../../utils/ssr-server/utils"));
|
|
15
|
+
var _buildRemoteServer = require("./build-remote-server");
|
|
16
|
+
var _constants = require("./constants");
|
|
17
|
+
var _express2 = require("./express");
|
|
18
|
+
var _crypto = require("crypto");
|
|
19
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
20
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
21
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
22
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
23
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
24
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
25
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
26
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
27
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
28
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
29
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /*
|
|
30
|
+
* Copyright (c) 2022, Salesforce, Inc.
|
|
31
|
+
* All rights reserved.
|
|
32
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
33
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
34
|
+
*/ // We need to mock isRemote in some tests, so we need to import it directly from
|
|
35
|
+
// the file it was defined in, because of the way jest works.
|
|
36
|
+
jest.mock('../../utils/ssr-config', () => {
|
|
37
|
+
return {
|
|
38
|
+
getConfig: () => {}
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Mock static assets (require path is relative to the 'ssr' directory)
|
|
43
|
+
const mockStaticAssets = {};
|
|
44
|
+
jest.mock('../static/assets.json', () => mockStaticAssets, {
|
|
45
|
+
virtual: true
|
|
46
|
+
});
|
|
47
|
+
const TEST_PORT = 3444;
|
|
48
|
+
const testFixtures = _path.default.resolve(process.cwd(), 'src/ssr/server/test_fixtures');
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* An HTTPS.Agent that allows self-signed certificates
|
|
52
|
+
* @type {module:https.Agent}
|
|
53
|
+
*/
|
|
54
|
+
const httpsAgent = new _https.default.Agent({
|
|
55
|
+
rejectUnauthorized: false
|
|
56
|
+
});
|
|
57
|
+
const opts = (overrides = {}) => {
|
|
58
|
+
const defaults = {
|
|
59
|
+
buildDir: './src/ssr/server/test_fixtures',
|
|
60
|
+
mobify: {
|
|
61
|
+
ssrEnabled: true,
|
|
62
|
+
ssrOnly: ['main.js.map', 'ssr.js', 'ssr.js.map'],
|
|
63
|
+
ssrShared: ['main.js', 'ssr-loader.js', 'worker.js'],
|
|
64
|
+
ssrParameters: {
|
|
65
|
+
proxyConfigs: [{
|
|
66
|
+
protocol: 'https',
|
|
67
|
+
host: 'test.proxy.com',
|
|
68
|
+
path: 'base'
|
|
69
|
+
}, {
|
|
70
|
+
protocol: 'https',
|
|
71
|
+
// This is intentionally an unreachable host
|
|
72
|
+
host: '0.0.0.0',
|
|
73
|
+
path: 'base2'
|
|
74
|
+
}, {
|
|
75
|
+
protocol: 'https',
|
|
76
|
+
host: 'test.proxy.com',
|
|
77
|
+
path: 'base3',
|
|
78
|
+
caching: true
|
|
79
|
+
}]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
sslFilePath: './src/ssr/server/test_fixtures/localhost.pem',
|
|
83
|
+
quiet: true,
|
|
84
|
+
port: TEST_PORT,
|
|
85
|
+
protocol: 'https',
|
|
86
|
+
fetchAgents: {
|
|
87
|
+
https: httpsAgent
|
|
88
|
+
},
|
|
89
|
+
defaultCacheTimeSeconds: 123,
|
|
90
|
+
enableLegacyRemoteProxying: false,
|
|
91
|
+
useSLASPrivateClient: false
|
|
92
|
+
};
|
|
93
|
+
return _objectSpread(_objectSpread({}, defaults), overrides);
|
|
94
|
+
};
|
|
95
|
+
const mkdtempSync = () => _fsExtra.default.mkdtempSync(_path.default.resolve(_os.default.tmpdir(), 'ssr-server-tests-'));
|
|
96
|
+
beforeAll(() => {
|
|
97
|
+
// The SSR app applies patches on creation. Those patches are specific to an
|
|
98
|
+
// environment (Lambda or not) and we need to ensure that the non-lambda patches
|
|
99
|
+
// are applied for testing. Creating and immediately discarding an app in
|
|
100
|
+
// local mode here applies the correct patches for all tests.
|
|
101
|
+
_buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
102
|
+
});
|
|
103
|
+
describe('_createApp validates the options object', () => {
|
|
104
|
+
let savedEnvironment;
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
savedEnvironment = _extends({}, process.env);
|
|
107
|
+
process.env = {
|
|
108
|
+
LISTEN_ADDRESS: '',
|
|
109
|
+
EXTERNAL_DOMAIN_NAME: ''
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
afterEach(() => {
|
|
113
|
+
process.env = savedEnvironment;
|
|
114
|
+
});
|
|
115
|
+
const invalidOptions = [{
|
|
116
|
+
name: 'mobify',
|
|
117
|
+
options: opts({
|
|
118
|
+
mobify: undefined
|
|
119
|
+
})
|
|
120
|
+
}, {
|
|
121
|
+
name: 'mobify',
|
|
122
|
+
options: opts({
|
|
123
|
+
mobify: 'a string'
|
|
124
|
+
})
|
|
125
|
+
}, {
|
|
126
|
+
name: 'buildDir empty',
|
|
127
|
+
options: opts({
|
|
128
|
+
buildDir: ''
|
|
129
|
+
})
|
|
130
|
+
}];
|
|
131
|
+
invalidOptions.forEach(({
|
|
132
|
+
name,
|
|
133
|
+
options
|
|
134
|
+
}) => {
|
|
135
|
+
test(`_createApp validates missing or invalid field "${name}"`, () => {
|
|
136
|
+
expect(() => _buildRemoteServer.RemoteServerFactory._createApp(options)).toThrow();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe('_createApp validates environment variables', () => {
|
|
141
|
+
let savedEnvironment;
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
savedEnvironment = _extends({}, process.env);
|
|
144
|
+
});
|
|
145
|
+
afterEach(() => {
|
|
146
|
+
process.env = savedEnvironment;
|
|
147
|
+
});
|
|
148
|
+
_buildRemoteServer.REMOTE_REQUIRED_ENV_VARS.forEach(envVar => {
|
|
149
|
+
test(`SSR Server verifies environment variable "${envVar}"`, () => {
|
|
150
|
+
// Set truthy values for all the env vars except the one we're testing.
|
|
151
|
+
const vars = _buildRemoteServer.REMOTE_REQUIRED_ENV_VARS.filter(name => name !== envVar).map(name => ({
|
|
152
|
+
[name]: 'value'
|
|
153
|
+
}));
|
|
154
|
+
// AWS_LAMBDA_FUNCTION_NAME indicates the server is running remotely on Lambda
|
|
155
|
+
vars.push({
|
|
156
|
+
AWS_LAMBDA_FUNCTION_NAME: 'pretend-to-be-remote'
|
|
157
|
+
});
|
|
158
|
+
process.env = _extends({}, savedEnvironment, ...vars);
|
|
159
|
+
expect(() => _buildRemoteServer.RemoteServerFactory._createApp(opts())).toThrow(envVar);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
describe('SSRServer operation', () => {
|
|
164
|
+
const savedEnvironment = _extends({}, process.env);
|
|
165
|
+
const sandbox = _sinon.default.createSandbox();
|
|
166
|
+
afterEach(() => {
|
|
167
|
+
sandbox.restore();
|
|
168
|
+
jest.resetModules();
|
|
169
|
+
_nock.default.cleanAll();
|
|
170
|
+
});
|
|
171
|
+
afterAll(() => {
|
|
172
|
+
process.env = savedEnvironment;
|
|
173
|
+
});
|
|
174
|
+
beforeEach(() => {
|
|
175
|
+
_buildRemoteServer.RemoteServerFactory._setRequestId = jest.fn().mockImplementation(_app => {
|
|
176
|
+
_app.use((req, res, next) => {
|
|
177
|
+
res.locals.requestId = (0, _crypto.randomUUID)();
|
|
178
|
+
next();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
// Ensure the environment is clean
|
|
182
|
+
process.env = {
|
|
183
|
+
LISTEN_ADDRESS: '',
|
|
184
|
+
EXTERNAL_DOMAIN_NAME: ''
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
test('_createApp creates an express app', () => {
|
|
188
|
+
const options = opts();
|
|
189
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(options);
|
|
190
|
+
const expected = `max-age=${options.defaultCacheTimeSeconds}, s-maxage=${options.defaultCacheTimeSeconds}`;
|
|
191
|
+
expect(app.options.defaultCacheControl).toEqual(expected);
|
|
192
|
+
});
|
|
193
|
+
test('SSRServer tracks responses', () => {
|
|
194
|
+
const route = jest.fn().mockImplementation((req, res) => {
|
|
195
|
+
res.send('<div>hello world</div>');
|
|
196
|
+
return Promise.resolve();
|
|
197
|
+
});
|
|
198
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
199
|
+
app.get('/*', route);
|
|
200
|
+
const response1 = {
|
|
201
|
+
locals: {
|
|
202
|
+
requestId: 1
|
|
203
|
+
},
|
|
204
|
+
once: () => null
|
|
205
|
+
};
|
|
206
|
+
const response2 = {
|
|
207
|
+
locals: {
|
|
208
|
+
requestId: 2
|
|
209
|
+
},
|
|
210
|
+
once: () => null
|
|
211
|
+
};
|
|
212
|
+
expect(app._requestMonitor._pendingResponses.ids).toEqual([]);
|
|
213
|
+
app._requestMonitor._responseFinished(response1);
|
|
214
|
+
expect(app._requestMonitor._pendingResponses.ids).toEqual([]);
|
|
215
|
+
const promise1 = app._requestMonitor._waitForResponses();
|
|
216
|
+
expect(promise1).toBe(_express2.RESOLVED_PROMISE);
|
|
217
|
+
app._requestMonitor._responseStarted(response1);
|
|
218
|
+
expect(app._requestMonitor._pendingResponses.ids).toEqual([1]);
|
|
219
|
+
const promise2 = app._requestMonitor._waitForResponses();
|
|
220
|
+
expect(promise2).not.toBe(_express2.RESOLVED_PROMISE);
|
|
221
|
+
app._requestMonitor._responseStarted(response2);
|
|
222
|
+
expect(app._requestMonitor._pendingResponses.ids).toEqual([1, 2]);
|
|
223
|
+
const promise3 = app._requestMonitor._waitForResponses();
|
|
224
|
+
expect(promise3).toBe(promise2);
|
|
225
|
+
app._requestMonitor._responseFinished(response1);
|
|
226
|
+
expect(app._requestMonitor._pendingResponses.ids).toEqual([2]);
|
|
227
|
+
app._requestMonitor._responseFinished(response1);
|
|
228
|
+
expect(app._requestMonitor._pendingResponses.ids).toEqual([2]);
|
|
229
|
+
app._requestMonitor._responseFinished(response2);
|
|
230
|
+
expect(app._requestMonitor._pendingResponses.ids).toEqual([]);
|
|
231
|
+
|
|
232
|
+
// If the promise doesn't resolve, this test will timeout
|
|
233
|
+
return promise2;
|
|
234
|
+
});
|
|
235
|
+
test(`The Remote SSRServer always uses https`, () => {
|
|
236
|
+
_buildRemoteServer.REMOTE_REQUIRED_ENV_VARS.forEach(envVar => {
|
|
237
|
+
process.env[envVar] = 'value';
|
|
238
|
+
});
|
|
239
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
|
|
240
|
+
protocol: 'http'
|
|
241
|
+
}));
|
|
242
|
+
expect(app.options.protocol).toBe('https');
|
|
243
|
+
process.env = savedEnvironment;
|
|
244
|
+
});
|
|
245
|
+
test('SSRServer renders correctly', () => {
|
|
246
|
+
const body = '<div>hello world</div>';
|
|
247
|
+
const route = jest.fn().mockImplementation((req, res) => {
|
|
248
|
+
res.send(body);
|
|
249
|
+
});
|
|
250
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
251
|
+
app.get('/*', route);
|
|
252
|
+
return (0, _supertest.default)(app).get('/').expect(200).then(res => {
|
|
253
|
+
expect(res.headers['x-powered-by']).toBeUndefined();
|
|
254
|
+
expect(res.text).toBe(body);
|
|
255
|
+
expect(route).toHaveBeenCalled();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
test('SSRServer renders with the react rendering', () => {
|
|
259
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
260
|
+
app.get('/*', _buildRemoteServer.RemoteServerFactory.render);
|
|
261
|
+
expect(app.__renderer).toBeUndefined();
|
|
262
|
+
return (0, _supertest.default)(app).get('/').expect(200).then(res => {
|
|
263
|
+
expect(res.text).toBe('OK');
|
|
264
|
+
expect(app.__renderer).toBeDefined();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
test('SSRServer rendering blocks cookie setting by default', () => {
|
|
268
|
+
const route = (req, res) => {
|
|
269
|
+
res.setHeader('set-cookie', 'blah123');
|
|
270
|
+
res.sendStatus(200);
|
|
271
|
+
};
|
|
272
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
273
|
+
app.get('/*', route);
|
|
274
|
+
jest.spyOn(console, 'warn');
|
|
275
|
+
return (0, _supertest.default)(app).get('/').expect(200).then(res => {
|
|
276
|
+
expect(console.warn.mock.calls[0][0]).toContain(`Discarding "Set-Cookie: blah123"`);
|
|
277
|
+
expect(res.headers['Set-Cookie']).toBeUndefined();
|
|
278
|
+
expect(res.headers['set-cookie']).toBeUndefined();
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
test('SSRServer rendering allows setting cookies with MRT_ALLOW_COOKIES env', () => {
|
|
282
|
+
process.env = {
|
|
283
|
+
MRT_ALLOW_COOKIES: 'true'
|
|
284
|
+
};
|
|
285
|
+
const route = (req, res) => {
|
|
286
|
+
res.setHeader('set-cookie', 'blah123');
|
|
287
|
+
res.sendStatus(200);
|
|
288
|
+
};
|
|
289
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
290
|
+
app.get('/*', route);
|
|
291
|
+
return (0, _supertest.default)(app).get('/').expect(200).then(res => {
|
|
292
|
+
expect(res.headers['set-cookie']).toEqual(['blah123']);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
test('SSRServer does not allow multi-value headers', () => {
|
|
296
|
+
const route = (req, res) => {
|
|
297
|
+
res.set('content-type', 'application/octet-stream');
|
|
298
|
+
res.set('content-type', 'text/plain');
|
|
299
|
+
res.send('<div>hello world</div>');
|
|
300
|
+
};
|
|
301
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
302
|
+
app.get('*', route);
|
|
303
|
+
return (0, _supertest.default)(app).get('/').expect(200).then(res => {
|
|
304
|
+
expect(res.headers['content-type']).toBe('text/plain; charset=utf-8');
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
test('SSRServer honours any x-mobify-querystring header', () => {
|
|
308
|
+
const route = jest.fn().mockImplementation((req, res) => {
|
|
309
|
+
res.send('<div> Hello world </div>');
|
|
310
|
+
});
|
|
311
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
312
|
+
app.get('/*', route);
|
|
313
|
+
return (0, _supertest.default)(app).get('/').set(_constants.X_MOBIFY_QUERYSTRING, 'z=1&y=2&x=3').expect(200).then(() => {
|
|
314
|
+
expect(route.mock.calls[0][0].query).toEqual({
|
|
315
|
+
z: '1',
|
|
316
|
+
y: '2',
|
|
317
|
+
x: '3'
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
describe('Running remotely', () => {
|
|
322
|
+
let isRemoteMock;
|
|
323
|
+
let savedEnvironment;
|
|
324
|
+
beforeEach(() => {
|
|
325
|
+
isRemoteMock = jest.spyOn(ssrServerUtils, 'isRemote').mockImplementation(() => true);
|
|
326
|
+
savedEnvironment = _extends({}, process.env);
|
|
327
|
+
_extends(process.env, {
|
|
328
|
+
BUNDLE_ID: 1,
|
|
329
|
+
DEPLOY_TARGET: 1,
|
|
330
|
+
EXTERNAL_DOMAIN_NAME: 'http://www.example.com',
|
|
331
|
+
MOBIFY_PROPERTY_ID: 'example'
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
afterEach(() => {
|
|
335
|
+
isRemoteMock.mockRestore();
|
|
336
|
+
process.env = savedEnvironment;
|
|
337
|
+
});
|
|
338
|
+
test('should not proxy', () => {
|
|
339
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
340
|
+
return (0, _supertest.default)(app).get('/mobify/proxy/base/test/path').expect(501);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
test('SSRServer handles /mobify/ping', () => {
|
|
344
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
345
|
+
return (0, _supertest.default)(app).get('/mobify/ping').expect(200);
|
|
346
|
+
});
|
|
347
|
+
describe('SSRServer worker.js handling', () => {
|
|
348
|
+
let tmpDir;
|
|
349
|
+
beforeEach(() => {
|
|
350
|
+
tmpDir = mkdtempSync();
|
|
351
|
+
});
|
|
352
|
+
afterEach(() => {
|
|
353
|
+
_fsExtra.default.removeSync(tmpDir);
|
|
354
|
+
});
|
|
355
|
+
const cases = [{
|
|
356
|
+
file: 'worker.js',
|
|
357
|
+
content: '// a service worker',
|
|
358
|
+
name: 'Should serve the service worker',
|
|
359
|
+
requestPath: '/worker.js'
|
|
360
|
+
}, {
|
|
361
|
+
file: 'worker.js.map',
|
|
362
|
+
content: '// a service worker source map',
|
|
363
|
+
name: 'Should serve the service worker source map',
|
|
364
|
+
requestPath: '/worker.js.map'
|
|
365
|
+
}];
|
|
366
|
+
cases.forEach(({
|
|
367
|
+
file,
|
|
368
|
+
content,
|
|
369
|
+
name,
|
|
370
|
+
requestPath
|
|
371
|
+
}) => {
|
|
372
|
+
test(`${name}`, () => {
|
|
373
|
+
const fixture = _path.default.join(__dirname, 'test_fixtures');
|
|
374
|
+
const buildDir = _path.default.join(tmpDir, 'build');
|
|
375
|
+
_fsExtra.default.copySync(fixture, buildDir);
|
|
376
|
+
const updatedFile = _path.default.resolve(buildDir, file);
|
|
377
|
+
_fsExtra.default.writeFileSync(updatedFile, content);
|
|
378
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
|
|
379
|
+
buildDir
|
|
380
|
+
}));
|
|
381
|
+
app.get('/worker.js(.map)?', _buildRemoteServer.RemoteServerFactory.serveServiceWorker);
|
|
382
|
+
return (0, _supertest.default)(app).get(requestPath).expect(200).then(res => expect(res.text).toEqual(content));
|
|
383
|
+
});
|
|
384
|
+
test(`${name} (and handle 404s correctly)`, () => {
|
|
385
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
|
|
386
|
+
buildDir: tmpDir
|
|
387
|
+
}));
|
|
388
|
+
app.get('/worker.js(.map)?', _buildRemoteServer.RemoteServerFactory.serveServiceWorker);
|
|
389
|
+
return (0, _supertest.default)(app).get(requestPath).expect(404);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
test('SSRServer creates cache on demand', () => {
|
|
394
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
395
|
+
expect(app._applicationCache).toBeUndefined();
|
|
396
|
+
expect(app.applicationCache).toBeInstanceOf(_ssrCache.PersistentCache);
|
|
397
|
+
expect(app._applicationCache).toBe(app.applicationCache);
|
|
398
|
+
});
|
|
399
|
+
test('should support redirects to bundle assets', () => {
|
|
400
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
401
|
+
const route = (req, res) => {
|
|
402
|
+
(0, _express2.respondFromBundle)({
|
|
403
|
+
req,
|
|
404
|
+
res
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
app.get('/*', route);
|
|
408
|
+
return (0, _supertest.default)(app).get('/some-bundle-path.jpg').then(response => {
|
|
409
|
+
expect(response.status).toBe(301);
|
|
410
|
+
expect(response.headers['location'].endsWith('/mobify/bundle/development/some-bundle-path.jpg')).toBe(true);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
test('should support other redirects', () => {
|
|
414
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
415
|
+
const route = (req, res) => {
|
|
416
|
+
res.redirect(302, '/elsewhere');
|
|
417
|
+
};
|
|
418
|
+
app.get('/*', route);
|
|
419
|
+
return (0, _supertest.default)(app).get('/some-bundle-path.jpg').then(response => {
|
|
420
|
+
expect(response.status).toBe(302);
|
|
421
|
+
expect(response.headers['location'].endsWith('/elsewhere')).toBe(true);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
test('should warn about non-strict SSL', () => {
|
|
425
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
426
|
+
const route = (req, res) => {
|
|
427
|
+
res.redirect(302, '/elsewhere');
|
|
428
|
+
};
|
|
429
|
+
app.get('/*', route);
|
|
430
|
+
return (0, _supertest.default)(app).get('/some-bundle-path.jpg').then(response => {
|
|
431
|
+
expect(response.status).toBe(302);
|
|
432
|
+
expect(response.headers['location'].endsWith('/elsewhere')).toBe(true);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
test('should support error codes', () => {
|
|
436
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
437
|
+
const route = (request, response) => {
|
|
438
|
+
response.sendStatus(500);
|
|
439
|
+
};
|
|
440
|
+
app.get('/*', route);
|
|
441
|
+
return (0, _supertest.default)(app).get('/').then(response => {
|
|
442
|
+
expect(response.status).toBe(500);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
test('should strip cookies before passing the request to the handler by default', () => {
|
|
446
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
447
|
+
const route = (req, res) => {
|
|
448
|
+
expect(req.headers.cookie).toBeUndefined();
|
|
449
|
+
res.sendStatus(200);
|
|
450
|
+
};
|
|
451
|
+
app.get('/*', route);
|
|
452
|
+
return (0, _supertest.default)(app).get('/').set('cookie', 'xyz=456').then(response => {
|
|
453
|
+
expect(response.status).toBe(200);
|
|
454
|
+
expect(response.headers['set-cookie']).toBeUndefined();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
test('should allow cookies in the request with MRT_ALLOW_COOKIES env', () => {
|
|
458
|
+
process.env = {
|
|
459
|
+
MRT_ALLOW_COOKIES: 'true'
|
|
460
|
+
};
|
|
461
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
462
|
+
const route = (req, res) => {
|
|
463
|
+
expect(req.headers.cookie).toBe('xyz=456');
|
|
464
|
+
res.sendStatus(200);
|
|
465
|
+
};
|
|
466
|
+
app.get('/*', route);
|
|
467
|
+
return (0, _supertest.default)(app).get('/').set('cookie', 'xyz=456').then(response => {
|
|
468
|
+
expect(response.status).toBe(200);
|
|
469
|
+
expect(response.headers['set-cookie']).toBeUndefined();
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
test('should fix host and origin headers before passing the request to the handler', () => {
|
|
473
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
474
|
+
const route = (req, res) => {
|
|
475
|
+
expect(req.headers.host).toEqual(app.options.appHostname);
|
|
476
|
+
expect(req.headers.origin).toEqual(app.options.appOrigin);
|
|
477
|
+
res.sendStatus(200);
|
|
478
|
+
};
|
|
479
|
+
app.get('/*', route);
|
|
480
|
+
return (0, _supertest.default)(app).get('/').set({
|
|
481
|
+
host: 'somewhere.over.the.rainbow',
|
|
482
|
+
origin: 'https://somewhere.over.the.rainbow'
|
|
483
|
+
}).then(response => {
|
|
484
|
+
expect(response.status).toBe(200);
|
|
485
|
+
expect(response.headers['set-cookie']).toBeUndefined();
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
test(`should reject POST requests to /`, () => {
|
|
489
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
490
|
+
const route = (req, res) => {
|
|
491
|
+
res.status(200).end();
|
|
492
|
+
};
|
|
493
|
+
app.get('/*', route);
|
|
494
|
+
return (0, _supertest.default)(app).post('/').then(response => {
|
|
495
|
+
expect(response.status).toBe(405);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
test('serveStaticFile serves static files from the build directory', () => {
|
|
499
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
500
|
+
const faviconPath = _path.default.resolve(testFixtures, 'favicon.ico');
|
|
501
|
+
app.get('/thing', _buildRemoteServer.RemoteServerFactory.serveStaticFile('favicon.ico'));
|
|
502
|
+
return (0, _supertest.default)(app).get('/thing').buffer(true).parse(_superagent.default.parse.image).expect(200).then(res => {
|
|
503
|
+
const iconData = _fsExtra.default.readFileSync(faviconPath);
|
|
504
|
+
expect(res.body).toEqual(iconData);
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
test('serveStaticFile returns 404 if the file does not exist', () => {
|
|
508
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
509
|
+
app.get('/thing', _buildRemoteServer.RemoteServerFactory.serveStaticFile('this-does-not-exist.ico'));
|
|
510
|
+
return (0, _supertest.default)(app).get('/thing').expect(404);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
describe('SSRServer persistent caching', () => {
|
|
514
|
+
const namespace = 'test';
|
|
515
|
+
const keyFromURL = url => encodeURIComponent(url);
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* A cache decorator for a route function that uses the percent-encoded req.url
|
|
519
|
+
* as keys for all cache entries (this makes testing easier).
|
|
520
|
+
*/
|
|
521
|
+
const cachedRoute = route => (req, res) => {
|
|
522
|
+
const shouldCache = !req.query.noCache;
|
|
523
|
+
const cacheArgs = {
|
|
524
|
+
req,
|
|
525
|
+
res,
|
|
526
|
+
namespace,
|
|
527
|
+
key: keyFromURL(req.url)
|
|
528
|
+
};
|
|
529
|
+
const shouldCacheResponse = (req, res) => res.statusCode >= 200 && res.statusCode < 300;
|
|
530
|
+
return Promise.resolve().then(() => (0, _express2.getResponseFromCache)(cacheArgs)).then(entry => {
|
|
531
|
+
if (entry.found) {
|
|
532
|
+
(0, _express2.sendCachedResponse)(entry);
|
|
533
|
+
} else {
|
|
534
|
+
if (shouldCache) {
|
|
535
|
+
(0, _express2.cacheResponseWhenDone)(_objectSpread({
|
|
536
|
+
shouldCacheResponse
|
|
537
|
+
}, cacheArgs));
|
|
538
|
+
}
|
|
539
|
+
return route(req, res);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* A test route that returns different content types based on query params.
|
|
546
|
+
*/
|
|
547
|
+
const routeImplementation = (req, res) => {
|
|
548
|
+
const status = parseInt(req.query.status || 200);
|
|
549
|
+
switch (req.query.type) {
|
|
550
|
+
case 'image':
|
|
551
|
+
res.status(status);
|
|
552
|
+
res.setHeader('content-type', 'image/png');
|
|
553
|
+
res.send(_fsExtra.default.readFileSync(_path.default.join(testFixtures, 'mobify.png')));
|
|
554
|
+
break;
|
|
555
|
+
case 'html':
|
|
556
|
+
res.status(status);
|
|
557
|
+
res.setHeader('x-rendered', 'true');
|
|
558
|
+
res.setHeader('cache-control', 's-maxage=60');
|
|
559
|
+
res.send('<div> Hello world </div>');
|
|
560
|
+
break;
|
|
561
|
+
case '500':
|
|
562
|
+
res.sendStatus(500);
|
|
563
|
+
break;
|
|
564
|
+
case '400':
|
|
565
|
+
res.sendStatus(400);
|
|
566
|
+
break;
|
|
567
|
+
default:
|
|
568
|
+
res.sendStatus(status);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
const sandbox = _sinon.default.createSandbox();
|
|
573
|
+
let app, route;
|
|
574
|
+
beforeEach(() => {
|
|
575
|
+
route = jest.fn().mockImplementation(routeImplementation);
|
|
576
|
+
const withCaching = cachedRoute(route);
|
|
577
|
+
app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
578
|
+
app.get('/*', withCaching);
|
|
579
|
+
});
|
|
580
|
+
afterEach(() => {
|
|
581
|
+
sandbox.restore();
|
|
582
|
+
app.applicationCache.close();
|
|
583
|
+
app = null;
|
|
584
|
+
route = null;
|
|
585
|
+
});
|
|
586
|
+
const testCases = [{
|
|
587
|
+
name: 'Should put HTML responses into the cache after rendering',
|
|
588
|
+
url: '/cacheme/?type=html',
|
|
589
|
+
expectOk: true,
|
|
590
|
+
expectHeaders: {
|
|
591
|
+
'x-mobify-from-cache': 'false',
|
|
592
|
+
'x-rendered': 'true',
|
|
593
|
+
'content-type': 'text/html; charset=utf-8'
|
|
594
|
+
},
|
|
595
|
+
expectToBeCached: false,
|
|
596
|
+
expectRenderCallCount: 1
|
|
597
|
+
}, {
|
|
598
|
+
name: 'Should put binary responses into the cache after rendering',
|
|
599
|
+
url: '/cacheme/?type=image',
|
|
600
|
+
expectOk: true,
|
|
601
|
+
expectHeaders: {
|
|
602
|
+
'x-mobify-from-cache': 'false',
|
|
603
|
+
'content-type': 'image/png'
|
|
604
|
+
},
|
|
605
|
+
expectToBeCached: false,
|
|
606
|
+
expectRenderCallCount: 1
|
|
607
|
+
}, {
|
|
608
|
+
name: 'Should skip putting responses into the cache when noCache is set',
|
|
609
|
+
url: '/cacheme/?type=image&noCache=1',
|
|
610
|
+
expectOk: true,
|
|
611
|
+
expectHeaders: {
|
|
612
|
+
'x-mobify-from-cache': 'false',
|
|
613
|
+
'content-type': 'image/png'
|
|
614
|
+
},
|
|
615
|
+
expectToBeCached: false,
|
|
616
|
+
expectRenderCallCount: 1
|
|
617
|
+
}, {
|
|
618
|
+
name: 'Should return a response even when the cache put fails',
|
|
619
|
+
url: '/cacheme/?type=image&a=1',
|
|
620
|
+
expectOk: true,
|
|
621
|
+
expectHeaders: {
|
|
622
|
+
'x-mobify-from-cache': 'false',
|
|
623
|
+
'content-type': 'image/png'
|
|
624
|
+
},
|
|
625
|
+
expectToBeCached: false,
|
|
626
|
+
expectRenderCallCount: 1,
|
|
627
|
+
forcePutFailure: true
|
|
628
|
+
}, {
|
|
629
|
+
name: 'Should serve responses from the cache, including HTTP headers',
|
|
630
|
+
url: '/cacheme/?type=html',
|
|
631
|
+
expectOk: true,
|
|
632
|
+
expectHeaders: {
|
|
633
|
+
'x-mobify-from-cache': 'false',
|
|
634
|
+
'content-type': 'text/html; charset=utf-8'
|
|
635
|
+
},
|
|
636
|
+
expectToBeCached: false,
|
|
637
|
+
expectRenderCallCount: 1,
|
|
638
|
+
preCache: {
|
|
639
|
+
data: Buffer.from('<html>456</html>'),
|
|
640
|
+
metadata: {
|
|
641
|
+
status: 200,
|
|
642
|
+
headers: {
|
|
643
|
+
'x-precached': 'false',
|
|
644
|
+
'content-type': 'text/html; charset=utf-8'
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}, {
|
|
649
|
+
name: 'Should serve responses from the cache without cached HTTP headers',
|
|
650
|
+
url: '/cacheme/?type=html',
|
|
651
|
+
expectOk: true,
|
|
652
|
+
expectHeaders: {
|
|
653
|
+
'x-mobify-from-cache': 'false'
|
|
654
|
+
},
|
|
655
|
+
expectToBeCached: false,
|
|
656
|
+
expectRenderCallCount: 1,
|
|
657
|
+
preCache: {
|
|
658
|
+
data: Buffer.from('<html>123</html>')
|
|
659
|
+
}
|
|
660
|
+
}, {
|
|
661
|
+
name: 'Should serve empty responses from the cache without errors',
|
|
662
|
+
url: '/cacheme/?type=none',
|
|
663
|
+
expectOk: true,
|
|
664
|
+
expectHeaders: {
|
|
665
|
+
'x-mobify-from-cache': 'false'
|
|
666
|
+
},
|
|
667
|
+
expectToBeCached: false,
|
|
668
|
+
expectRenderCallCount: 1,
|
|
669
|
+
preCache: {
|
|
670
|
+
data: undefined,
|
|
671
|
+
metadata: {
|
|
672
|
+
status: 200,
|
|
673
|
+
headers: {
|
|
674
|
+
'x-precached': 'true'
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}];
|
|
679
|
+
testCases.forEach(testCase => test(`${testCase.name}`, () => {
|
|
680
|
+
let url = testCase.url;
|
|
681
|
+
return Promise.resolve().then(() => {
|
|
682
|
+
const preCache = testCase.preCache;
|
|
683
|
+
if (preCache) {
|
|
684
|
+
return app.applicationCache.put({
|
|
685
|
+
namespace,
|
|
686
|
+
key: keyFromURL(url),
|
|
687
|
+
metadata: preCache.metadata,
|
|
688
|
+
data: preCache.data
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}).then(() => {
|
|
692
|
+
sandbox.stub(app.applicationCache, 'put');
|
|
693
|
+
if (testCase.forcePutFailure) {
|
|
694
|
+
app.applicationCache.put.onFirstCall().callsFake(() => Promise.reject('Fake put error'));
|
|
695
|
+
}
|
|
696
|
+
app.applicationCache.put.callThrough();
|
|
697
|
+
}).then(() => {
|
|
698
|
+
// Buffer and parse everything as binary for easy comparison
|
|
699
|
+
// across content types.
|
|
700
|
+
return (0, _supertest.default)(app).get(url).buffer(true).parse(_superagent.default.parse['application/octet-stream']);
|
|
701
|
+
})
|
|
702
|
+
// Wait for any caching to complete
|
|
703
|
+
.then(response => app._requestMonitor._waitForResponses().then(() => response))
|
|
704
|
+
// Handle and verify the response
|
|
705
|
+
.then(response => {
|
|
706
|
+
expect(response.ok).toEqual(testCase.expectOk);
|
|
707
|
+
expect(route.mock.calls).toHaveLength(testCase.expectRenderCallCount);
|
|
708
|
+
expect(response.headers).toMatchObject(testCase.expectHeaders);
|
|
709
|
+
return Promise.all([response, app.applicationCache.get({
|
|
710
|
+
key: keyFromURL(url),
|
|
711
|
+
namespace
|
|
712
|
+
})]);
|
|
713
|
+
}).then(([, entry]) => {
|
|
714
|
+
// Verify the response data against the cache
|
|
715
|
+
expect(entry.found).toBe(false);
|
|
716
|
+
});
|
|
717
|
+
}));
|
|
718
|
+
const errorCases = [{
|
|
719
|
+
url: '/?type=500',
|
|
720
|
+
status: 500
|
|
721
|
+
}, {
|
|
722
|
+
url: '/?type=400',
|
|
723
|
+
status: 400
|
|
724
|
+
}];
|
|
725
|
+
errorCases.forEach(({
|
|
726
|
+
url,
|
|
727
|
+
status
|
|
728
|
+
}) => {
|
|
729
|
+
test(`should not cache responses with ${status} status codes`, () => {
|
|
730
|
+
return (0, _supertest.default)(app).get(url).then(res => app._requestMonitor._waitForResponses().then(() => res)).then(res => {
|
|
731
|
+
expect(res.status).toBe(status);
|
|
732
|
+
expect(res.headers['x-mobify-from-cache']).toBe('false');
|
|
733
|
+
}).then(() => app.applicationCache.get({
|
|
734
|
+
key: keyFromURL(url),
|
|
735
|
+
namespace
|
|
736
|
+
})).then(entry => expect(entry.found).toBe(false));
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
test('Try to send non-cached response', () => {
|
|
740
|
+
expect(() => (0, _express2.sendCachedResponse)(new _ssrServer.CachedResponse({}))).toThrow('non-cached');
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
describe('generateCacheKey', () => {
|
|
744
|
+
const mockRequest = overrides => {
|
|
745
|
+
return _objectSpread({
|
|
746
|
+
url: '/test?a=1',
|
|
747
|
+
query: {},
|
|
748
|
+
headers: {},
|
|
749
|
+
get: function (key) {
|
|
750
|
+
return this.headers[key];
|
|
751
|
+
}
|
|
752
|
+
}, overrides);
|
|
753
|
+
};
|
|
754
|
+
test('returns expected results', () => {
|
|
755
|
+
expect((0, _express2.generateCacheKey)(mockRequest({
|
|
756
|
+
url: '/test/1?id=abc'
|
|
757
|
+
})).indexOf('/test/1')).toBe(0);
|
|
758
|
+
});
|
|
759
|
+
test('path affects key', () => {
|
|
760
|
+
const result1 = (0, _express2.generateCacheKey)(mockRequest({
|
|
761
|
+
url: '/test2a/'
|
|
762
|
+
}));
|
|
763
|
+
expect((0, _express2.generateCacheKey)(mockRequest({
|
|
764
|
+
url: '/testab/'
|
|
765
|
+
}))).not.toEqual(result1);
|
|
766
|
+
});
|
|
767
|
+
test('query affects key', () => {
|
|
768
|
+
const result1 = (0, _express2.generateCacheKey)(mockRequest({
|
|
769
|
+
url: '/test3?a=1'
|
|
770
|
+
}));
|
|
771
|
+
expect((0, _express2.generateCacheKey)(mockRequest({
|
|
772
|
+
url: '/test3?a=2'
|
|
773
|
+
}))).not.toEqual(result1);
|
|
774
|
+
});
|
|
775
|
+
test('request class affects key', () => {
|
|
776
|
+
const result1 = (0, _express2.generateCacheKey)(mockRequest());
|
|
777
|
+
const request2 = mockRequest({
|
|
778
|
+
headers: {
|
|
779
|
+
'x-mobify-request-class': 'bot'
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
expect((0, _express2.generateCacheKey)(request2)).not.toEqual(result1);
|
|
783
|
+
expect((0, _express2.generateCacheKey)(request2, {
|
|
784
|
+
ignoreRequestClass: true
|
|
785
|
+
})).toEqual(result1);
|
|
786
|
+
});
|
|
787
|
+
test('extras affect key', () => {
|
|
788
|
+
const result1 = (0, _express2.generateCacheKey)(mockRequest());
|
|
789
|
+
expect((0, _express2.generateCacheKey)(mockRequest(), {
|
|
790
|
+
extras: ['123']
|
|
791
|
+
})).not.toEqual(result1);
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
describe('getRuntime', () => {
|
|
795
|
+
let originalEnv;
|
|
796
|
+
let originalEval = global.eval;
|
|
797
|
+
|
|
798
|
+
// Mock the DevSeverFactory via `eval` so we don't have to include it as a dev
|
|
799
|
+
// dependency which will cause circular dependency warnings.
|
|
800
|
+
const MockDevServerFactory = {
|
|
801
|
+
name: 'MockDevServerFactory',
|
|
802
|
+
returnMyName() {
|
|
803
|
+
return this.name;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
const mockEval = () => ({
|
|
807
|
+
main: {
|
|
808
|
+
require: () => ({
|
|
809
|
+
DevServerFactory: MockDevServerFactory
|
|
810
|
+
})
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
const matchExceptFunctionValues = obj => {
|
|
814
|
+
const entries = Object.entries(obj);
|
|
815
|
+
const matchers = entries.map(([key, value]) => {
|
|
816
|
+
const matcher = typeof value === 'function' ? expect.any(Function) : value;
|
|
817
|
+
return [key, matcher];
|
|
818
|
+
});
|
|
819
|
+
return Object.fromEntries(matchers);
|
|
820
|
+
};
|
|
821
|
+
const cases = [{
|
|
822
|
+
env: {},
|
|
823
|
+
expectedRuntime: MockDevServerFactory,
|
|
824
|
+
msg: 'when running locally'
|
|
825
|
+
}, {
|
|
826
|
+
env: {
|
|
827
|
+
AWS_LAMBDA_FUNCTION_NAME: 'this-makes-it-remote'
|
|
828
|
+
},
|
|
829
|
+
expectedRuntime: _buildRemoteServer.RemoteServerFactory,
|
|
830
|
+
msg: 'when running remotely'
|
|
831
|
+
}];
|
|
832
|
+
beforeAll(() => {
|
|
833
|
+
global.eval = mockEval;
|
|
834
|
+
});
|
|
835
|
+
afterAll(() => {
|
|
836
|
+
global.eval = originalEval;
|
|
837
|
+
});
|
|
838
|
+
beforeEach(() => {
|
|
839
|
+
originalEnv = process.env;
|
|
840
|
+
});
|
|
841
|
+
afterEach(() => {
|
|
842
|
+
process.env = originalEnv;
|
|
843
|
+
});
|
|
844
|
+
test.each(cases)('should return a remote/development runtime $msg', ({
|
|
845
|
+
env,
|
|
846
|
+
expectedRuntime
|
|
847
|
+
}) => {
|
|
848
|
+
process.env = _objectSpread(_objectSpread({}, process.env), env);
|
|
849
|
+
expect((0, _express2.getRuntime)()).toMatchObject(matchExceptFunctionValues(expectedRuntime));
|
|
850
|
+
});
|
|
851
|
+
test('should return a remote/development runtime bound to the correct context', () => {
|
|
852
|
+
const mockDevRuntime = (0, _express2.getRuntime)();
|
|
853
|
+
const func = mockDevRuntime.returnMyName;
|
|
854
|
+
expect(func()).toBe(MockDevServerFactory.name);
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
describe('DevServer middleware', () => {
|
|
858
|
+
afterEach(() => {
|
|
859
|
+
jest.restoreAllMocks();
|
|
860
|
+
});
|
|
861
|
+
test('_validateConfiguration protocol', () => {
|
|
862
|
+
let protocol = 'ftp';
|
|
863
|
+
let error = `Invalid local development server protocol ${protocol}. Valid protocols are http and https.`;
|
|
864
|
+
expect(() => {
|
|
865
|
+
_buildRemoteServer.RemoteServerFactory._validateConfiguration(opts({
|
|
866
|
+
protocol
|
|
867
|
+
}));
|
|
868
|
+
}).toThrow(error);
|
|
869
|
+
});
|
|
870
|
+
test('_validateConfiguration sslFilePath', () => {
|
|
871
|
+
let sslFilePath = './does/not/exist';
|
|
872
|
+
let error = 'The sslFilePath option passed to the SSR server constructor ' + 'must be a path to an SSL certificate file ' + 'in PEM format, whose name ends with ".pem". ' + 'See the "cert" and "key" options on ' + 'https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options';
|
|
873
|
+
expect(() => {
|
|
874
|
+
_buildRemoteServer.RemoteServerFactory._validateConfiguration(opts({
|
|
875
|
+
sslFilePath
|
|
876
|
+
}));
|
|
877
|
+
}).toThrow(error);
|
|
878
|
+
});
|
|
879
|
+
test('_validateConfiguration strictSSL', () => {
|
|
880
|
+
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
881
|
+
_buildRemoteServer.RemoteServerFactory._validateConfiguration(opts({
|
|
882
|
+
strictSSL: false
|
|
883
|
+
}));
|
|
884
|
+
expect(warn.mock.calls).toEqual([['The SSR Server has _strictSSL turned off for https requests']]);
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
describe('SLAS private client proxy', () => {
|
|
888
|
+
const savedEnvironment = _extends({}, process.env);
|
|
889
|
+
let proxyApp;
|
|
890
|
+
const proxyPort = 12345;
|
|
891
|
+
const proxyPath = '/responseHeaders';
|
|
892
|
+
const slasTarget = `http://localhost:${proxyPort}${proxyPath}`;
|
|
893
|
+
beforeAll(() => {
|
|
894
|
+
// by setting slasTarget, rather than forwarding the request to SLAS,
|
|
895
|
+
// we send the proxy request here so we can return the request headers
|
|
896
|
+
proxyApp = (0, _express.default)();
|
|
897
|
+
proxyApp.use(proxyPath, (req, res) => {
|
|
898
|
+
res.send(req.headers);
|
|
899
|
+
});
|
|
900
|
+
proxyApp.listen(proxyPort);
|
|
901
|
+
});
|
|
902
|
+
afterEach(() => {
|
|
903
|
+
process.env = savedEnvironment;
|
|
904
|
+
});
|
|
905
|
+
afterAll(() => {
|
|
906
|
+
proxyApp.close();
|
|
907
|
+
});
|
|
908
|
+
test('should not create proxy by default', () => {
|
|
909
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
|
|
910
|
+
return (0, _supertest.default)(app).get('/mobify/slas/private').expect(404);
|
|
911
|
+
});
|
|
912
|
+
test('should return HTTP 501 if PWA_KIT_SLAS_CLIENT_SECRET env var not set', () => {
|
|
913
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
|
|
914
|
+
useSLASPrivateClient: true
|
|
915
|
+
}));
|
|
916
|
+
return (0, _supertest.default)(app).get('/mobify/slas/private').expect(501);
|
|
917
|
+
});
|
|
918
|
+
test('does not insert client secret if request not for /oauth2/token', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
919
|
+
process.env.PWA_KIT_SLAS_CLIENT_SECRET = 'a secret';
|
|
920
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
|
|
921
|
+
mobify: {
|
|
922
|
+
app: {
|
|
923
|
+
commerceAPI: {
|
|
924
|
+
parameters: {
|
|
925
|
+
clientId: 'clientId',
|
|
926
|
+
shortCode: 'shortCode'
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
useSLASPrivateClient: true,
|
|
932
|
+
slasTarget: slasTarget
|
|
933
|
+
}));
|
|
934
|
+
return yield (0, _supertest.default)(app).get('/mobify/slas/private/somePath').then(response => {
|
|
935
|
+
expect(response.body.authorization).toBeUndefined();
|
|
936
|
+
expect(response.body.host).toBe('shortCode.api.commercecloud.salesforce.com');
|
|
937
|
+
expect(response.body['x-mobify']).toBe('true');
|
|
938
|
+
});
|
|
939
|
+
}), 15000);
|
|
940
|
+
test('inserts client secret if request is for /oauth2/token', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
941
|
+
process.env.PWA_KIT_SLAS_CLIENT_SECRET = 'a secret';
|
|
942
|
+
const encodedCredentials = Buffer.from('clientId:a secret').toString('base64');
|
|
943
|
+
const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
|
|
944
|
+
mobify: {
|
|
945
|
+
app: {
|
|
946
|
+
commerceAPI: {
|
|
947
|
+
parameters: {
|
|
948
|
+
clientId: 'clientId',
|
|
949
|
+
shortCode: 'shortCode'
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
},
|
|
954
|
+
useSLASPrivateClient: true,
|
|
955
|
+
slasTarget: slasTarget
|
|
956
|
+
}));
|
|
957
|
+
return yield (0, _supertest.default)(app).get('/mobify/slas/private/oauth2/token').then(response => {
|
|
958
|
+
expect(response.body.authorization).toBe(`Basic ${encodedCredentials}`);
|
|
959
|
+
expect(response.body.host).toBe('shortCode.api.commercecloud.salesforce.com');
|
|
960
|
+
expect(response.body['x-mobify']).toBe('true');
|
|
961
|
+
});
|
|
962
|
+
}), 15000);
|
|
963
|
+
});
|