@@ -13,10 +13,10 @@ export function haveResource(resourceType: string, properties?: any, comparison?
1313 return new HaveResourceAssertion ( resourceType , properties , comparison ) ;
1414}
1515
16- type PropertyPredicate = ( props : any ) => boolean ;
16+ type PropertyPredicate = ( props : any , inspection : InspectionFailure ) => boolean ;
1717
1818class HaveResourceAssertion extends Assertion < StackInspector > {
19- private inspected : any [ ] = [ ] ;
19+ private inspected : InspectionFailure [ ] = [ ] ;
2020 private readonly part : ResourcePart ;
2121 private readonly predicate : PropertyPredicate ;
2222
@@ -33,13 +33,17 @@ class HaveResourceAssertion extends Assertion<StackInspector> {
3333 for ( const logicalId of Object . keys ( inspector . value . Resources ) ) {
3434 const resource = inspector . value . Resources [ logicalId ] ;
3535 if ( resource . Type === this . resourceType ) {
36- this . inspected . push ( resource ) ;
37-
3836 const propsToCheck = this . part === ResourcePart . Properties ? resource . Properties : resource ;
3937
40- if ( this . predicate ( propsToCheck ) ) {
38+ // Pass inspection object as 2nd argument, initialize failure with default string,
39+ // to maintain backwards compatibility with old predicate API.
40+ const inspection = { resource, failureReason : 'Object did not match predicate' } ;
41+
42+ if ( this . predicate ( propsToCheck , inspection ) ) {
4143 return true ;
4244 }
45+
46+ this . inspected . push ( inspection ) ;
4347 }
4448 }
4549
@@ -48,7 +52,15 @@ class HaveResourceAssertion extends Assertion<StackInspector> {
4852
4953 public assertOrThrow ( inspector : StackInspector ) {
5054 if ( ! this . assertUsing ( inspector ) ) {
51- throw new Error ( `None of ${ JSON . stringify ( this . inspected , null , 2 ) } match ${ this . description } ` ) ;
55+ const lines : string [ ] = [ ] ;
56+ lines . push ( `None of ${ this . inspected . length } resources matches ${ this . description } .` ) ;
57+
58+ for ( const inspected of this . inspected ) {
59+ lines . push ( `- ${ inspected . failureReason } in:` ) ;
60+ lines . push ( indent ( 4 , JSON . stringify ( inspected . resource , null , 2 ) ) ) ;
61+ }
62+
63+ throw new Error ( lines . join ( '\n' ) ) ;
5264 }
5365 }
5466
@@ -58,46 +70,75 @@ class HaveResourceAssertion extends Assertion<StackInspector> {
5870 }
5971}
6072
73+ function indent ( n : number , s : string ) {
74+ const prefix = ' ' . repeat ( n ) ;
75+ return prefix + s . replace ( / \n / g, '\n' + prefix ) ;
76+ }
77+
6178/**
6279 * Make a predicate that checks property superset
6380 */
6481function makeSuperObjectPredicate ( obj : any ) {
65- return ( resourceProps : any ) => {
66- return isSuperObject ( resourceProps , obj ) ;
82+ return ( resourceProps : any , inspection : InspectionFailure ) => {
83+ const errors : string [ ] = [ ] ;
84+ const ret = isSuperObject ( resourceProps , obj , errors ) ;
85+ inspection . failureReason = errors . join ( ',' ) ;
86+ return ret ;
6787 } ;
6888}
6989
90+ interface InspectionFailure {
91+ resource : any ;
92+ failureReason : string ;
93+ }
94+
7095/**
7196 * Return whether `superObj` is a super-object of `obj`.
7297 *
7398 * A super-object has the same or more property values, recursing into nested objects.
7499 */
75- export function isSuperObject ( superObj : any , obj : any ) : boolean {
100+ export function isSuperObject ( superObj : any , obj : any , errors : string [ ] = [ ] ) : boolean {
76101 if ( obj == null ) { return true ; }
77- if ( Array . isArray ( superObj ) !== Array . isArray ( obj ) ) { return false ; }
102+ if ( Array . isArray ( superObj ) !== Array . isArray ( obj ) ) {
103+ errors . push ( 'Array type mismatch' ) ;
104+ return false ;
105+ }
78106 if ( Array . isArray ( superObj ) ) {
79- if ( obj . length !== superObj . length ) { return false ; }
107+ if ( obj . length !== superObj . length ) {
108+ errors . push ( 'Array length mismatch' ) ;
109+ return false ;
110+ }
80111
81112 // Do isSuperObject comparison for individual objects
82113 for ( let i = 0 ; i < obj . length ; i ++ ) {
83- if ( ! isSuperObject ( superObj [ i ] , obj [ i ] ) ) {
84- return false ;
114+ if ( ! isSuperObject ( superObj [ i ] , obj [ i ] , [ ] ) ) {
115+ errors . push ( `Array element ${ i } mismatch` ) ;
85116 }
86117 }
87- return true ;
118+ return errors . length === 0 ;
119+ }
120+ if ( ( typeof superObj === 'object' ) !== ( typeof obj === 'object' ) ) {
121+ errors . push ( 'Object type mismatch' ) ;
122+ return false ;
88123 }
89- if ( ( typeof superObj === 'object' ) !== ( typeof obj === 'object' ) ) { return false ; }
90124 if ( typeof obj === 'object' ) {
91125 for ( const key of Object . keys ( obj ) ) {
92- if ( ! ( key in superObj ) ) { return false ; }
126+ if ( ! ( key in superObj ) ) {
127+ errors . push ( `Field ${ key } missing` ) ;
128+ continue ;
129+ }
93130
94- if ( ! isSuperObject ( superObj [ key ] , obj [ key ] ) ) {
95- return false ;
131+ if ( ! isSuperObject ( superObj [ key ] , obj [ key ] , [ ] ) ) {
132+ errors . push ( `Field ${ key } mismatch` ) ;
96133 }
97134 }
98- return true ;
135+ return errors . length === 0 ;
136+ }
137+
138+ if ( superObj !== obj ) {
139+ errors . push ( 'Different values' ) ;
99140 }
100- return superObj === obj ;
141+ return errors . length === 0 ;
101142}
102143
103144/**
0 commit comments