4
4
5
5
import { runServer } from '@dd/core/helpers/server' ;
6
6
import { API_PREFIX , DEFAULT_PORT } from '@dd/synthetics-plugin/constants' ;
7
+ import type { ServerResponse } from '@dd/synthetics-plugin/types' ;
7
8
import { getPlugins } from '@dd/synthetics-plugin' ;
8
9
import { getContextMock } from '@dd/tests/_jest/helpers/mocks' ;
10
+ import { BUNDLERS , runBundlers } from '@dd/tests/_jest/helpers/runBundlers' ;
11
+ import fs from 'fs' ;
9
12
import nock from 'nock' ;
13
+ import path from 'path' ;
10
14
11
15
jest . mock ( '@dd/core/helpers/server' , ( ) => {
12
16
const original = jest . requireActual ( '@dd/core/helpers/server' ) ;
@@ -18,6 +22,48 @@ jest.mock('@dd/core/helpers/server', () => {
18
22
19
23
const runServerMocked = jest . mocked ( runServer ) ;
20
24
25
+ const getApiUrl = ( port : number = DEFAULT_PORT ) => `http://127.0.0.1:${ port } ` ;
26
+ const getInternalApiUrl = ( port : number = DEFAULT_PORT ) => `${ getApiUrl ( port ) } /${ API_PREFIX } ` ;
27
+ const safeFetch = async ( route : string , port : number ) => {
28
+ try {
29
+ return await fetch ( `${ getInternalApiUrl ( port ) } ${ route } ` ) ;
30
+ } catch ( e ) {
31
+ // Do nothing.
32
+ }
33
+ } ;
34
+
35
+ // Wait for the local server to tell us that the build is complete.
36
+ const waitingForBuild = ( port : number , cb : ( resp : ServerResponse ) => void ) => {
37
+ return new Promise < void > ( ( resolve , reject ) => {
38
+ // Stop the polling after 10 seconds.
39
+ const timeout = setTimeout ( ( ) => {
40
+ clearInterval ( interval ) ;
41
+ reject ( new Error ( 'Timeout.' ) ) ;
42
+ } , 10000 ) ;
43
+
44
+ // Poll all the local servers until we get the build status.
45
+ const interval = setInterval ( async ( ) => {
46
+ const res = await safeFetch ( '/build-status' , port ) ;
47
+ if ( res ?. ok ) {
48
+ const data = ( await res . json ( ) ) as ServerResponse ;
49
+ cb ( data ) ;
50
+
51
+ if ( [ 'success' , 'fail' ] . includes ( data . status ) ) {
52
+ clearInterval ( interval ) ;
53
+ clearTimeout ( timeout ) ;
54
+
55
+ if ( data . status === 'success' ) {
56
+ resolve ( ) ;
57
+ }
58
+ if ( data . status === 'fail' ) {
59
+ reject ( new Error ( 'Build failed.' ) ) ;
60
+ }
61
+ }
62
+ }
63
+ } , 100 ) ;
64
+ } ) ;
65
+ } ;
66
+
21
67
describe ( 'Synthetics Plugin' , ( ) => {
22
68
describe ( 'getPlugins' , ( ) => {
23
69
test ( 'Should not initialize the plugin if disabled' , async ( ) => {
@@ -40,71 +86,184 @@ describe('Synthetics Plugin', () => {
40
86
nock . enableNetConnect ( '127.0.0.1' ) ;
41
87
} ) ;
42
88
43
- afterEach ( async ( ) => {
44
- // Kill the server.
45
- try {
46
- await fetch ( `http://127.0.0.1:${ DEFAULT_PORT } /${ API_PREFIX } /kill` ) ;
47
- } catch ( e ) {
48
- // Do nothing.
49
- }
50
- } ) ;
51
-
52
89
afterAll ( ( ) => {
53
90
nock . cleanAll ( ) ;
54
91
nock . disableNetConnect ( ) ;
55
92
} ) ;
56
93
57
94
describe ( 'to run or not to run' , ( ) => {
58
- afterEach ( ( ) => {
95
+ afterEach ( async ( ) => {
59
96
// Remove the variables we've set.
60
97
delete process . env . BUILD_PLUGINS_S8S_LOCAL ;
61
98
delete process . env . BUILD_PLUGINS_S8S_PORT ;
99
+
100
+ // Kill the server.
101
+ await safeFetch ( '/kill' , DEFAULT_PORT ) ;
62
102
} ) ;
103
+
63
104
const expectations = [
64
105
{
65
- description : 'not run with no variables ' ,
106
+ description : 'not run with no env and no config ' ,
66
107
env : { } ,
108
+ config : { } ,
67
109
shouldRun : false ,
68
110
} ,
69
111
{
70
- description : 'not run with missing port' ,
112
+ description : 'run with port in env and no config ' ,
71
113
env : {
72
- BUILD_PLUGINS_S8S_LOCAL : '1' ,
114
+ BUILD_PLUGINS_S8S_PORT : JSON . stringify ( DEFAULT_PORT ) ,
73
115
} ,
74
- shouldRun : false ,
116
+ config : { } ,
117
+ shouldRun : true ,
118
+ } ,
119
+ {
120
+ description : 'run with no variables and full config' ,
121
+ env : { } ,
122
+ config : {
123
+ synthetics : {
124
+ server : {
125
+ run : true ,
126
+ port : DEFAULT_PORT ,
127
+ } ,
128
+ } ,
129
+ } ,
130
+ shouldRun : true ,
131
+ } ,
132
+ {
133
+ description : 'run with no variables and just config.run' ,
134
+ env : { } ,
135
+ config : {
136
+ synthetics : {
137
+ server : {
138
+ run : true ,
139
+ } ,
140
+ } ,
141
+ } ,
142
+ shouldRun : true ,
75
143
} ,
76
144
{
77
- description : 'not run with missing local ' ,
145
+ description : 'not run with config.run false ' ,
78
146
env : {
79
147
BUILD_PLUGINS_S8S_PORT : JSON . stringify ( DEFAULT_PORT ) ,
80
148
} ,
149
+ config : {
150
+ synthetics : {
151
+ server : {
152
+ run : false ,
153
+ } ,
154
+ } ,
155
+ } ,
81
156
shouldRun : false ,
82
157
} ,
83
158
{
84
- description : 'run with both variables' ,
85
- env : {
86
- BUILD_PLUGINS_S8S_PORT : JSON . stringify ( DEFAULT_PORT ) ,
87
- BUILD_PLUGINS_S8S_LOCAL : '1' ,
159
+ description : 'not run with disabled and config.run' ,
160
+ env : { } ,
161
+ config : {
162
+ synthetics : {
163
+ disabled : true ,
164
+ server : {
165
+ run : true ,
166
+ } ,
167
+ } ,
88
168
} ,
89
- shouldRun : true ,
169
+ shouldRun : false ,
90
170
} ,
91
171
] ;
92
172
93
- test . each ( expectations ) (
94
- 'Should $description.' ,
95
- async ( { description, env, shouldRun } ) => {
96
- // Set the variables.
97
- Object . assign ( process . env , env ) ;
98
- // Run the plugin.
99
- getPlugins ( { } , getContextMock ( ) ) ;
100
- // Check the server.
101
- if ( shouldRun ) {
102
- expect ( runServerMocked ) . toHaveBeenCalled ( ) ;
103
- } else {
104
- expect ( runServerMocked ) . not . toHaveBeenCalled ( ) ;
105
- }
106
- } ,
107
- ) ;
173
+ test . each ( expectations ) ( 'Should $description.' , async ( { config, env, shouldRun } ) => {
174
+ // Set the variables.
175
+ Object . assign ( process . env , env ) ;
176
+ // Run the plugin.
177
+ const [ plugin ] = getPlugins ( config , getContextMock ( ) ) ;
178
+ if ( plugin ?. bundlerReport ) {
179
+ // Trigger the bundlerReport hook where the server starts.
180
+ plugin . bundlerReport ( getContextMock ( ) . bundler ) ;
181
+ }
182
+ // Check the server.
183
+ if ( shouldRun ) {
184
+ expect ( runServerMocked ) . toHaveBeenCalled ( ) ;
185
+ } else {
186
+ expect ( runServerMocked ) . not . toHaveBeenCalled ( ) ;
187
+ }
188
+ } ) ;
189
+ } ) ;
190
+
191
+ // We need to loop over bundlers because we'll use a different port for each one of them
192
+ // to avoid port conflicts.
193
+ describe . each ( BUNDLERS ) ( '$name' , ( bundler ) => {
194
+ // Get an incremental port to prevent conflicts.
195
+ const port = DEFAULT_PORT + BUNDLERS . indexOf ( bundler ) ;
196
+
197
+ let buildProm : Promise < any > ;
198
+ let outDir : string ;
199
+ const serverResponses : Set < ServerResponse > = new Set ( ) ;
200
+
201
+ beforeAll ( async ( ) => {
202
+ // Run the builds.
203
+ // Do not await the promise as the server will be running.
204
+ buildProm = runBundlers (
205
+ {
206
+ synthetics : {
207
+ server : {
208
+ run : true ,
209
+ port,
210
+ } ,
211
+ } ,
212
+ // Use a custom plugin to get the cwd and outdir of the build.
213
+ customPlugins : ( ) => [
214
+ {
215
+ name : 'get-outdirs' ,
216
+ bundlerReport : ( report ) => {
217
+ outDir = report . outDir ;
218
+ } ,
219
+ } ,
220
+ ] ,
221
+ } ,
222
+ undefined ,
223
+ [ bundler . name ] ,
224
+ ) ;
225
+
226
+ // Instead, wait for the server to tell us that the build is complete.
227
+ await waitingForBuild ( port , ( resp ) => {
228
+ serverResponses . add ( resp ) ;
229
+ } ) ;
230
+ } ) ;
231
+
232
+ afterAll ( async ( ) => {
233
+ await safeFetch ( '/kill' , port ) ;
234
+ // Wait for the build to finish now that the server is killed.
235
+ if ( buildProm ) {
236
+ await buildProm ;
237
+ }
238
+ } ) ;
239
+
240
+ test ( 'Should report the build status.' , async ( ) => {
241
+ // Verify that we have the running and success statuses.
242
+ const reportedStatus = Array . from ( serverResponses ) . filter ( ( resp ) =>
243
+ [ 'fail' , 'success' , 'running' ] . includes ( resp . status ) ,
244
+ ) ;
245
+ expect ( reportedStatus . length ) . toBeGreaterThan ( 0 ) ;
246
+ } ) ;
247
+
248
+ test ( 'Should report the outDir.' , async ( ) => {
249
+ // Verify that we have the running and success statuses.
250
+ const reportedOutDirs = new Set (
251
+ Array . from ( serverResponses ) . map ( ( resp ) => resp . outDir ) ,
252
+ ) ;
253
+ // We should have only one outDir.
254
+ expect ( reportedOutDirs . size ) . toBe ( 1 ) ;
255
+ // It should be the same as the one we reported from the build.
256
+ expect ( reportedOutDirs . values ( ) . next ( ) . value ) . toEqual ( outDir ) ;
257
+ } ) ;
258
+
259
+ test ( 'Should actually serve the built files.' , async ( ) => {
260
+ // Query a file from the server.
261
+ const res = await fetch ( `${ getApiUrl ( port ) } /main.js` ) ;
262
+ expect ( res . ok ) . toBe ( true ) ;
263
+ const text = await res . text ( ) ;
264
+ // Confirm that the file served by the server is the same as the one on disk.
265
+ expect ( text ) . toEqual ( fs . readFileSync ( path . join ( outDir , 'main.js' ) , 'utf-8' ) ) ;
266
+ } ) ;
108
267
} ) ;
109
268
} ) ;
110
269
} ) ;
0 commit comments