@@ -10,84 +10,54 @@ import unionBy from 'lodash.unionby';
1010import { PluginProcess } from './plugin-process.js' ;
1111import { getPlatformOs , splitUserConfig } from './utils.js' ;
1212
13+ export interface FullTestOptions {
14+ skipUninstall ?: boolean ,
15+ skipImport ?: boolean ,
16+
17+ // Ensure these resources are installed before testing starts. Will install if missing, but never destroy after.
18+ prerequisites ?: ResourceConfig [ ] ,
19+
20+ validatePlan ?: ( plans : PlanResponseData [ ] ) => Promise < void > | void
21+ validateApply ?: ( plans : PlanResponseData [ ] ) => Promise < void > | void ,
22+ validateDestroy ?: ( plans : PlanResponseData [ ] ) => Promise < void > | void ,
23+ validateImport ?: ( importResults : ( ImportResponseData [ 'result' ] [ 0 ] ) [ ] ) => Promise < void > | void ,
24+ testModify ?: {
25+ modifiedConfigs : ResourceConfig [ ] ,
26+ validateModify ?: ( plans : PlanResponseData [ ] ) => Promise < void > | void ,
27+ }
28+ }
29+
1330export class PluginTester {
1431 static async fullTest (
1532 pluginPath : string ,
1633 configs : ResourceConfig [ ] ,
17- options ?: {
18- skipUninstall ?: boolean ,
19- skipImport ?: boolean ,
20- validatePlan ?: ( plans : PlanResponseData [ ] ) => Promise < void > | void
21- validateApply ?: ( plans : PlanResponseData [ ] ) => Promise < void > | void ,
22- validateDestroy ?: ( plans : PlanResponseData [ ] ) => Promise < void > | void ,
23- validateImport ?: ( importResults : ( ImportResponseData [ 'result' ] [ 0 ] ) [ ] ) => Promise < void > | void ,
24- testModify ?: {
25- modifiedConfigs : ResourceConfig [ ] ,
26- validateModify ?: ( plans : PlanResponseData [ ] ) => Promise < void > | void ,
27- }
28- } ) : Promise < void > {
34+ options ?: FullTestOptions ,
35+ ) : Promise < void > {
2936 configs = configs . filter ( ( c ) => ! c . os || c . os . includes ( getPlatformOs ( ) ) ) ;
3037 const ids = configs
3138 . map ( ( c ) => `${ c . type } ${ c . name ? `.${ c . name } ` : '' } ` )
3239 . join ( ', ' )
3340 console . info ( chalk . cyan ( `Starting full test of [ ${ ids } ]...` ) ) ;
3441
42+ const { skipUninstall = false } = options ?? { }
3543
36- const {
37- skipUninstall = false ,
38- } = options ?? { }
39-
44+ if ( options ?. prerequisites ?. length ) {
45+ await this . ensurePrerequisites ( pluginPath , options . prerequisites ) ;
46+ }
4047
4148 const plugin = new PluginProcess ( pluginPath ) ;
4249 try {
43- console . info ( chalk . cyan ( 'Testing initialization...' ) )
44- const initializeResult = await plugin . initialize ( ) ;
45-
46- const unsupportedConfigs = configs . filter ( ( c ) =>
47- ! initializeResult . resourceDefinitions . some ( ( rd ) => rd . type === c . type )
48- )
49- if ( unsupportedConfigs . length > 0 ) {
50- throw new Error ( `The plugin does not support the following configs supplied:\n ${ JSON . stringify ( unsupportedConfigs , null , 2 ) } \n Initialize result: ${ JSON . stringify ( initializeResult ) } ` )
51- }
52-
53- // configs = configs.filter((c) => initializeResult.resourceDefinitions.find((rd) => rd.type === c.type)?.operatingSystems?.includes(os.platform() as OS));
54-
55- console . info ( chalk . cyan ( 'Testing validate...' ) )
56- const validate = await plugin . validate ( {
57- configs : configs . map ( ( c ) => {
58- const { coreParameters, parameters } = splitUserConfig ( c )
59- return { core : coreParameters , parameters } ;
60- } )
61- } ) ;
62-
63- const invalidConfigs = validate . resourceValidations . filter ( ( v ) => ! v . isValid )
64- if ( invalidConfigs . length > 0 ) {
65- throw new Error ( `The following configs did not validate:\n ${ JSON . stringify ( invalidConfigs , null , 2 ) } ` )
66- }
50+ await this . initializeAndValidate ( plugin , configs ) ;
6751
6852 console . info ( chalk . cyan ( 'Testing plan...' ) )
69- const plans = [ ] ;
70- for ( const config of configs ) {
71- const { coreParameters, parameters } = splitUserConfig ( config ) ;
72-
73- plans . push ( await plugin . plan ( {
74- core : coreParameters ,
75- desired : parameters ,
76- isStateful : false ,
77- state : undefined ,
78- } ) ) ;
79- }
53+ const plans = await this . planConfigs ( plugin , configs ) ;
8054
8155 if ( options ?. validatePlan ) {
8256 await options . validatePlan ( plans ) ;
8357 }
8458
8559 console . info ( chalk . cyan ( 'Testing apply...' ) )
86- for ( const plan of plans ) {
87- await plugin . apply ( {
88- planId : plan . planId
89- } ) ;
90- }
60+ await this . applyPlans ( plugin , plans ) ;
9161
9262 if ( options ?. validateApply ) {
9363 await options . validateApply ( plans ) ;
@@ -97,63 +67,11 @@ export class PluginTester {
9767 }
9868
9969 if ( ! options ?. skipImport ) {
100- const importPlugin = new PluginProcess ( pluginPath ) ;
101- try {
102- await importPlugin . initialize ( ) ;
103- console . info ( chalk . cyan ( 'Testing import...' ) )
104-
105- const importResults = [ ] ;
106- for ( const config of configs ) {
107- const { coreParameters, parameters } = splitUserConfig ( config ) ;
108-
109- const importResult = await importPlugin . import ( { core : coreParameters , parameters } )
110- importResults . push ( importResult ) ;
111- }
112-
113- if ( options ?. validateImport ) {
114- await options . validateImport ( importResults . map ( ( r ) => r . result [ 0 ] ) ) ;
115- }
116- } finally {
117- importPlugin . kill ( ) ;
118- }
70+ await this . runImportPhase ( pluginPath , configs , options ?. validateImport ) ;
11971 }
12072
12173 if ( options ?. testModify ) {
122- const modifyPlugin = new PluginProcess ( pluginPath ) ;
123-
124- try {
125- await modifyPlugin . initialize ( ) ;
126- console . info ( chalk . cyan ( 'Testing modify...' ) )
127-
128- const modifyPlans = [ ] ;
129- for ( const config of options . testModify . modifiedConfigs ) {
130- const { coreParameters, parameters } = splitUserConfig ( config ) ;
131-
132- modifyPlans . push ( await modifyPlugin . plan ( {
133- core : coreParameters ,
134- desired : parameters ,
135- isStateful : false ,
136- state : undefined ,
137- } ) ) ;
138- }
139-
140- if ( modifyPlans . some ( ( p ) => p . operation !== ResourceOperation . MODIFY ) ) {
141- throw new Error ( `Error while testing modify. Non-modify results were found in the plan:
142- ${ JSON . stringify ( modifyPlans , null , 2 ) } `)
143- }
144-
145- for ( const plan of modifyPlans ) {
146- await modifyPlugin . apply ( {
147- planId : plan . planId
148- } ) ;
149- }
150-
151- if ( options . testModify . validateModify ) {
152- await options . testModify . validateModify ( modifyPlans ) ;
153- }
154- } finally {
155- modifyPlugin . kill ( ) ;
156- }
74+ await this . runModifyPhase ( pluginPath , options . testModify ) ;
15775 }
15876
15977 if ( ! skipUninstall ) {
@@ -172,50 +90,13 @@ ${JSON.stringify(modifyPlans, null, 2)}`)
17290 const plugin = new PluginProcess ( pluginPath ) ;
17391
17492 try {
175- console . info ( chalk . cyan ( 'Testing initialization...' ) )
176- const initializeResult = await plugin . initialize ( ) ;
177-
178- const unsupportedConfigs = configs . filter ( ( c ) =>
179- ! initializeResult . resourceDefinitions . some ( ( rd ) => rd . type === c . type )
180- )
181- if ( unsupportedConfigs . length > 0 ) {
182- throw new Error ( `The plugin does not support the following configs supplied:\n ${ JSON . stringify ( unsupportedConfigs , null , 2 ) } \n Initialize result: ${ JSON . stringify ( initializeResult ) } ` )
183- }
184-
185- // configs = configs.filter((c) => initializeResult.resourceDefinitions.find((rd) => rd.type === c.type)?.operatingSystems?.includes(os.platform() as OS));
186-
187- console . info ( chalk . cyan ( 'Testing validate...' ) )
188- const validate = await plugin . validate ( {
189- configs : configs . map ( ( c ) => {
190- const { coreParameters, parameters } = splitUserConfig ( c )
191- return { core : coreParameters , parameters } ;
192- } )
193- } ) ;
194-
195- const invalidConfigs = validate . resourceValidations . filter ( ( v ) => ! v . isValid )
196- if ( invalidConfigs . length > 0 ) {
197- throw new Error ( `The following configs did not validate:\n ${ JSON . stringify ( invalidConfigs , null , 2 ) } ` )
198- }
93+ await this . initializeAndValidate ( plugin , configs ) ;
19994
20095 console . info ( chalk . cyan ( 'Testing plan...' ) )
201- const plans = [ ] ;
202- for ( const config of configs ) {
203- const { coreParameters, parameters } = splitUserConfig ( config ) ;
204-
205- plans . push ( await plugin . plan ( {
206- core : coreParameters ,
207- desired : parameters ,
208- isStateful : false ,
209- state : undefined ,
210- } ) ) ;
211- }
96+ const plans = await this . planConfigs ( plugin , configs ) ;
21297
21398 console . info ( chalk . cyan ( 'Testing apply...' ) )
214- for ( const plan of plans ) {
215- await plugin . apply ( {
216- planId : plan . planId
217- } ) ;
218- }
99+ await this . applyPlans ( plugin , plans ) ;
219100 } finally {
220101 plugin . kill ( ) ;
221102 }
@@ -260,6 +141,166 @@ ${JSON.stringify(modifyPlans, null, 2)}`)
260141 }
261142 }
262143
144+ private static async ensurePrerequisites ( pluginPath : string , prerequisites : ResourceConfig [ ] ) : Promise < void > {
145+ prerequisites = prerequisites . filter ( ( c ) => ! c . os || c . os . includes ( getPlatformOs ( ) ) ) ;
146+ if ( prerequisites . length === 0 ) return ;
147+
148+ const ids = prerequisites . map ( ( c ) => `${ c . type } ${ c . name ? `.${ c . name } ` : '' } ` ) . join ( ', ' ) ;
149+ console . info ( chalk . cyan ( `Checking prerequisites [ ${ ids } ]...` ) ) ;
150+
151+ const plugin = new PluginProcess ( pluginPath ) ;
152+ try {
153+ const initializeResult = await plugin . initialize ( ) ;
154+
155+ const unsupportedConfigs = prerequisites . filter ( ( c ) =>
156+ ! initializeResult . resourceDefinitions . some ( ( rd ) => rd . type === c . type )
157+ )
158+ if ( unsupportedConfigs . length > 0 ) {
159+ throw new Error ( `The plugin does not support the following prerequisite configs:\n ${ JSON . stringify ( unsupportedConfigs , null , 2 ) } ` )
160+ }
161+
162+ const validate = await plugin . validate ( {
163+ configs : prerequisites . map ( ( c ) => {
164+ const { coreParameters, parameters } = splitUserConfig ( c )
165+ return { core : coreParameters , parameters } ;
166+ } )
167+ } ) ;
168+
169+ const invalidConfigs = validate . resourceValidations . filter ( ( v ) => ! v . isValid )
170+ if ( invalidConfigs . length > 0 ) {
171+ console . error ( chalk . red ( `Prerequisites validation failed:\n ${ JSON . stringify ( invalidConfigs , null , 2 ) } ` ) ) ;
172+ throw new Error ( `The following prerequisite configs did not validate:\n ${ JSON . stringify ( invalidConfigs , null , 2 ) } ` )
173+ }
174+
175+ for ( const config of prerequisites ) {
176+ const { coreParameters, parameters } = splitUserConfig ( config ) ;
177+ const plan = await plugin . plan ( {
178+ core : coreParameters ,
179+ desired : parameters ,
180+ isStateful : false ,
181+ state : undefined ,
182+ } ) ;
183+
184+ const label = `${ config . type } ${ config . name ? `.${ config . name } ` : '' } ` ;
185+ if ( plan . operation === ResourceOperation . NOOP ) {
186+ console . info ( chalk . cyan ( `Prerequisite already satisfied: ${ label } ` ) ) ;
187+ } else {
188+ await plugin . apply ( { planId : plan . planId } ) ;
189+ console . info ( chalk . cyan ( `Prerequisite installed: ${ label } ` ) ) ;
190+ }
191+ }
192+ } finally {
193+ plugin . kill ( ) ;
194+ }
195+ }
196+
197+ private static async initializeAndValidate ( plugin : PluginProcess , configs : ResourceConfig [ ] ) : Promise < void > {
198+ console . info ( chalk . cyan ( 'Testing initialization...' ) )
199+ const initializeResult = await plugin . initialize ( ) ;
200+
201+ const unsupportedConfigs = configs . filter ( ( c ) =>
202+ ! initializeResult . resourceDefinitions . some ( ( rd ) => rd . type === c . type )
203+ )
204+ if ( unsupportedConfigs . length > 0 ) {
205+ throw new Error ( `The plugin does not support the following configs supplied:\n ${ JSON . stringify ( unsupportedConfigs , null , 2 ) } \n Initialize result: ${ JSON . stringify ( initializeResult ) } ` )
206+ }
207+
208+ console . info ( chalk . cyan ( 'Testing validate...' ) )
209+ const validate = await plugin . validate ( {
210+ configs : configs . map ( ( c ) => {
211+ const { coreParameters, parameters } = splitUserConfig ( c )
212+ return { core : coreParameters , parameters } ;
213+ } )
214+ } ) ;
215+
216+ const invalidConfigs = validate . resourceValidations . filter ( ( v ) => ! v . isValid )
217+ if ( invalidConfigs . length > 0 ) {
218+ throw new Error ( `The following configs did not validate:\n ${ JSON . stringify ( invalidConfigs , null , 2 ) } ` )
219+ }
220+ }
221+
222+ private static async planConfigs ( plugin : PluginProcess , configs : ResourceConfig [ ] ) : Promise < PlanResponseData [ ] > {
223+ const plans = [ ] ;
224+ for ( const config of configs ) {
225+ const { coreParameters, parameters } = splitUserConfig ( config ) ;
226+ plans . push ( await plugin . plan ( {
227+ core : coreParameters ,
228+ desired : parameters ,
229+ isStateful : false ,
230+ state : undefined ,
231+ } ) ) ;
232+ }
233+
234+ return plans ;
235+ }
236+
237+ private static async applyPlans ( plugin : PluginProcess , plans : PlanResponseData [ ] ) : Promise < void > {
238+ for ( const plan of plans ) {
239+ await plugin . apply ( { planId : plan . planId } ) ;
240+ }
241+ }
242+
243+ private static async runImportPhase (
244+ pluginPath : string ,
245+ configs : ResourceConfig [ ] ,
246+ validateImport ?: ( importResults : ( ImportResponseData [ 'result' ] [ 0 ] ) [ ] ) => Promise < void > | void ,
247+ ) : Promise < void > {
248+ const importPlugin = new PluginProcess ( pluginPath ) ;
249+ try {
250+ await importPlugin . initialize ( ) ;
251+ console . info ( chalk . cyan ( 'Testing import...' ) )
252+
253+ const importResults = [ ] ;
254+ for ( const config of configs ) {
255+ const { coreParameters, parameters } = splitUserConfig ( config ) ;
256+ importResults . push ( await importPlugin . import ( { core : coreParameters , parameters } ) ) ;
257+ }
258+
259+ if ( validateImport ) {
260+ await validateImport ( importResults . map ( ( r ) => r . result [ 0 ] ) ) ;
261+ }
262+ } finally {
263+ importPlugin . kill ( ) ;
264+ }
265+ }
266+
267+ private static async runModifyPhase (
268+ pluginPath : string ,
269+ testModify : { modifiedConfigs : ResourceConfig [ ] , validateModify ?: ( plans : PlanResponseData [ ] ) => Promise < void > | void } ,
270+ ) : Promise < void > {
271+ const modifyPlugin = new PluginProcess ( pluginPath ) ;
272+
273+ try {
274+ await modifyPlugin . initialize ( ) ;
275+ console . info ( chalk . cyan ( 'Testing modify...' ) )
276+
277+ const modifyPlans = [ ] ;
278+ for ( const config of testModify . modifiedConfigs ) {
279+ const { coreParameters, parameters } = splitUserConfig ( config ) ;
280+
281+ modifyPlans . push ( await modifyPlugin . plan ( {
282+ core : coreParameters ,
283+ desired : parameters ,
284+ isStateful : false ,
285+ state : undefined ,
286+ } ) ) ;
287+ }
288+
289+ if ( modifyPlans . some ( ( p ) => p . operation !== ResourceOperation . MODIFY ) ) {
290+ throw new Error ( `Error while testing modify. Non-modify results were found in the plan:
291+ ${ JSON . stringify ( modifyPlans , null , 2 ) } `)
292+ }
293+
294+ await this . applyPlans ( modifyPlugin , modifyPlans ) ;
295+
296+ if ( testModify . validateModify ) {
297+ await testModify . validateModify ( modifyPlans ) ;
298+ }
299+ } finally {
300+ modifyPlugin . kill ( ) ;
301+ }
302+ }
303+
263304 private static addNamesToConfigs ( configs : ResourceConfig [ ] ) : ResourceConfig [ ] {
264305 const configsWithNames = new Array < ResourceConfig > ( ) ;
265306
0 commit comments