Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(assert): Adjust assertion behavior to be stricter #1289

Merged
merged 4 commits into from
Dec 6, 2018
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 40 additions & 14 deletions packages/@aws-cdk/assert/lib/assertions/have-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ import { StackInspector } from "../inspector";
/**
* An assertion to check whether a resource of a given type and with the given properties exists, disregarding properties
*
* Properties can be:
*
* - An object, in which case its properties will be compared to those of the actual resource found
* - A callable, in which case it will be treated as a predicate that is applied to the Properties of the found resources.
* @param resourceType the type of the resource that is expected to be present.
* @param properties the properties that the resource is expected to have. A function may be provided, in which case
* it will be called with the properties of candidate resources and an ``InspectionFailure``
* instance on which errors should be appended, and should return a truthy value to denote a match.
* @param comparison the entity that is being asserted against.
* @param allowValueExtension if properties is an object, tells whether values must match exactly, or if they are
RomainMuller marked this conversation as resolved.
Show resolved Hide resolved
* allowed to be supersets of the reference values. Meaningless if properties is a function.
*/
export function haveResource(resourceType: string, properties?: any, comparison?: ResourcePart): Assertion<StackInspector> {
return new HaveResourceAssertion(resourceType, properties, comparison);
export function haveResource(resourceType: string,
properties?: any,
comparison?: ResourcePart,
allowValueExtension: boolean = false): Assertion<StackInspector> {
return new HaveResourceAssertion(resourceType, properties, comparison, allowValueExtension);
}

type PropertyPredicate = (props: any, inspection: InspectionFailure) => boolean;
Expand All @@ -22,10 +28,11 @@ class HaveResourceAssertion extends Assertion<StackInspector> {

constructor(private readonly resourceType: string,
private readonly properties?: any,
part?: ResourcePart) {
part?: ResourcePart,
allowValueExtension: boolean = false) {
super();

this.predicate = typeof properties === 'function' ? properties : makeSuperObjectPredicate(properties);
this.predicate = typeof properties === 'function' ? properties : makeSuperObjectPredicate(properties, allowValueExtension);
this.part = part !== undefined ? part : ResourcePart.Properties;
}

Expand Down Expand Up @@ -78,10 +85,10 @@ function indent(n: number, s: string) {
/**
* Make a predicate that checks property superset
*/
function makeSuperObjectPredicate(obj: any) {
function makeSuperObjectPredicate(obj: any, allowValueExtension: boolean) {
return (resourceProps: any, inspection: InspectionFailure) => {
const errors: string[] = [];
const ret = isSuperObject(resourceProps, obj, errors);
const ret = isSuperObject(resourceProps, obj, errors, allowValueExtension);
inspection.failureReason = errors.join(',');
return ret;
};
Expand All @@ -95,9 +102,9 @@ interface InspectionFailure {
/**
* Return whether `superObj` is a super-object of `obj`.
*
* A super-object has the same or more property values, recursing into nested objects.
* A super-object has the same or more property values, recursing into sub properties if ``allowValueExtension`` is true.
*/
export function isSuperObject(superObj: any, obj: any, errors: string[] = []): boolean {
export function isSuperObject(superObj: any, obj: any, errors: string[], allowValueExtension: boolean): boolean {
if (obj == null) { return true; }
if (Array.isArray(superObj) !== Array.isArray(obj)) {
errors.push('Array type mismatch');
Expand All @@ -111,7 +118,7 @@ export function isSuperObject(superObj: any, obj: any, errors: string[] = []): b

// Do isSuperObject comparison for individual objects
for (let i = 0; i < obj.length; i++) {
if (!isSuperObject(superObj[i], obj[i], [])) {
if (!isSuperObject(superObj[i], obj[i], [], allowValueExtension)) {
errors.push(`Array element ${i} mismatch`);
}
}
Expand All @@ -128,7 +135,10 @@ export function isSuperObject(superObj: any, obj: any, errors: string[] = []): b
continue;
}

if (!isSuperObject(superObj[key], obj[key], [])) {
const valueMatches = allowValueExtension
? isSuperObject(superObj[key], obj[key], [], allowValueExtension)
: isStrictlyEqual(superObj[key], obj[key]);
if (!valueMatches) {
errors.push(`Field ${key} mismatch`);
}
}
Expand All @@ -139,6 +149,22 @@ export function isSuperObject(superObj: any, obj: any, errors: string[] = []): b
errors.push('Different values');
}
return errors.length === 0;

function isStrictlyEqual(left: any, right: any): boolean {
if (left === right) { return true; }
if (typeof left !== typeof right) { return false; }
if (typeof left === 'object' && typeof right === 'object') {
if (Array.isArray(left) !== Array.isArray(right)) { return false; }
const allKeys = new Set<string>([...Object.keys(left), ...Object.keys(right)]);
for (const key of allKeys) {
if (!isStrictlyEqual(left[key], right[key])) {
return false;
}
}
return true;
}
return false;
}
}

/**
Expand Down