44
55import { runServer } from '@dd/core/helpers/server' ;
66import { API_PREFIX , DEFAULT_PORT } from '@dd/synthetics-plugin/constants' ;
7+ import type { ServerResponse } from '@dd/synthetics-plugin/types' ;
78import { getPlugins } from '@dd/synthetics-plugin' ;
89import { getContextMock } from '@dd/tests/_jest/helpers/mocks' ;
10+ import { BUNDLERS , runBundlers } from '@dd/tests/_jest/helpers/runBundlers' ;
11+ import fs from 'fs' ;
912import nock from 'nock' ;
13+ import path from 'path' ;
1014
1115jest . mock ( '@dd/core/helpers/server' , ( ) => {
1216 const original = jest . requireActual ( '@dd/core/helpers/server' ) ;
@@ -18,6 +22,48 @@ jest.mock('@dd/core/helpers/server', () => {
1822
1923const runServerMocked = jest . mocked ( runServer ) ;
2024
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+
2167describe ( 'Synthetics Plugin' , ( ) => {
2268 describe ( 'getPlugins' , ( ) => {
2369 test ( 'Should not initialize the plugin if disabled' , async ( ) => {
@@ -40,71 +86,184 @@ describe('Synthetics Plugin', () => {
4086 nock . enableNetConnect ( '127.0.0.1' ) ;
4187 } ) ;
4288
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-
5289 afterAll ( ( ) => {
5390 nock . cleanAll ( ) ;
5491 nock . disableNetConnect ( ) ;
5592 } ) ;
5693
5794 describe ( 'to run or not to run' , ( ) => {
58- afterEach ( ( ) => {
95+ afterEach ( async ( ) => {
5996 // Remove the variables we've set.
6097 delete process . env . BUILD_PLUGINS_S8S_LOCAL ;
6198 delete process . env . BUILD_PLUGINS_S8S_PORT ;
99+
100+ // Kill the server.
101+ await safeFetch ( '/kill' , DEFAULT_PORT ) ;
62102 } ) ;
103+
63104 const expectations = [
64105 {
65- description : 'not run with no variables ' ,
106+ description : 'not run with no env and no config ' ,
66107 env : { } ,
108+ config : { } ,
67109 shouldRun : false ,
68110 } ,
69111 {
70- description : 'not run with missing port' ,
112+ description : 'run with port in env and no config ' ,
71113 env : {
72- BUILD_PLUGINS_S8S_LOCAL : '1' ,
114+ BUILD_PLUGINS_S8S_PORT : JSON . stringify ( DEFAULT_PORT ) ,
73115 } ,
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 ,
75143 } ,
76144 {
77- description : 'not run with missing local ' ,
145+ description : 'not run with config.run false ' ,
78146 env : {
79147 BUILD_PLUGINS_S8S_PORT : JSON . stringify ( DEFAULT_PORT ) ,
80148 } ,
149+ config : {
150+ synthetics : {
151+ server : {
152+ run : false ,
153+ } ,
154+ } ,
155+ } ,
81156 shouldRun : false ,
82157 } ,
83158 {
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+ } ,
88168 } ,
89- shouldRun : true ,
169+ shouldRun : false ,
90170 } ,
91171 ] ;
92172
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+ } ) ;
108267 } ) ;
109268 } ) ;
110269} ) ;
0 commit comments