@@ -13,10 +13,10 @@ export function haveResource(resourceType: string, properties?: any, comparison?
13
13
return new HaveResourceAssertion ( resourceType , properties , comparison ) ;
14
14
}
15
15
16
- type PropertyPredicate = ( props : any ) => boolean ;
16
+ type PropertyPredicate = ( props : any , inspection : InspectionFailure ) => boolean ;
17
17
18
18
class HaveResourceAssertion extends Assertion < StackInspector > {
19
- private inspected : any [ ] = [ ] ;
19
+ private inspected : InspectionFailure [ ] = [ ] ;
20
20
private readonly part : ResourcePart ;
21
21
private readonly predicate : PropertyPredicate ;
22
22
@@ -33,13 +33,17 @@ class HaveResourceAssertion extends Assertion<StackInspector> {
33
33
for ( const logicalId of Object . keys ( inspector . value . Resources ) ) {
34
34
const resource = inspector . value . Resources [ logicalId ] ;
35
35
if ( resource . Type === this . resourceType ) {
36
- this . inspected . push ( resource ) ;
37
-
38
36
const propsToCheck = this . part === ResourcePart . Properties ? resource . Properties : resource ;
39
37
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 ) ) {
41
43
return true ;
42
44
}
45
+
46
+ this . inspected . push ( inspection ) ;
43
47
}
44
48
}
45
49
@@ -48,7 +52,15 @@ class HaveResourceAssertion extends Assertion<StackInspector> {
48
52
49
53
public assertOrThrow ( inspector : StackInspector ) {
50
54
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' ) ) ;
52
64
}
53
65
}
54
66
@@ -58,46 +70,75 @@ class HaveResourceAssertion extends Assertion<StackInspector> {
58
70
}
59
71
}
60
72
73
+ function indent ( n : number , s : string ) {
74
+ const prefix = ' ' . repeat ( n ) ;
75
+ return prefix + s . replace ( / \n / g, '\n' + prefix ) ;
76
+ }
77
+
61
78
/**
62
79
* Make a predicate that checks property superset
63
80
*/
64
81
function 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 ;
67
87
} ;
68
88
}
69
89
90
+ interface InspectionFailure {
91
+ resource : any ;
92
+ failureReason : string ;
93
+ }
94
+
70
95
/**
71
96
* Return whether `superObj` is a super-object of `obj`.
72
97
*
73
98
* A super-object has the same or more property values, recursing into nested objects.
74
99
*/
75
- export function isSuperObject ( superObj : any , obj : any ) : boolean {
100
+ export function isSuperObject ( superObj : any , obj : any , errors : string [ ] = [ ] ) : boolean {
76
101
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
+ }
78
106
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
+ }
80
111
81
112
// Do isSuperObject comparison for individual objects
82
113
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` ) ;
85
116
}
86
117
}
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 ;
88
123
}
89
- if ( ( typeof superObj === 'object' ) !== ( typeof obj === 'object' ) ) { return false ; }
90
124
if ( typeof obj === 'object' ) {
91
125
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
+ }
93
130
94
- if ( ! isSuperObject ( superObj [ key ] , obj [ key ] ) ) {
95
- return false ;
131
+ if ( ! isSuperObject ( superObj [ key ] , obj [ key ] , [ ] ) ) {
132
+ errors . push ( `Field ${ key } mismatch` ) ;
96
133
}
97
134
}
98
- return true ;
135
+ return errors . length === 0 ;
136
+ }
137
+
138
+ if ( superObj !== obj ) {
139
+ errors . push ( 'Different values' ) ;
99
140
}
100
- return superObj === obj ;
141
+ return errors . length === 0 ;
101
142
}
102
143
103
144
/**
0 commit comments