Skip to content

Commit

Permalink
feat: improve detection differences by ignoring extra properties in m…
Browse files Browse the repository at this point in the history
…etadata files retrieved from org
  • Loading branch information
Codeneos committed Sep 6, 2023
1 parent 1deb30a commit 44b7332
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 12 deletions.
12 changes: 7 additions & 5 deletions packages/salesforce/src/retrieveDeltaStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class RetrieveDeltaStrategy {

private readonly compareStrategies : Record<string, CompareStrategy> = {
'xmlStrictOrder': (a, b) => this.isXmlEqual(a, b, { strictOrder: true }),
'xml': (a, b) => this.isXmlEqual(a, b, { strictOrder: false }),
'xml': (a, b) => this.isXmlEqual(a, b, { strictOrder: false, ignoreExtra: true }),
'metaXml': (a, b) => this.isMetaXmlEqual(a, b),
'binary': this.isBinaryEqual,
'default': this.isStringEqual,
Expand Down Expand Up @@ -156,18 +156,19 @@ export class RetrieveDeltaStrategy {
return bufferA.compare(bufferB) === 0;
}

private isXmlEqual(a: Buffer | string, b: Buffer | string, options?: { arrayMode?: boolean, strictOrder?: boolean, ignoreMissing?: boolean }): boolean {
private isXmlEqual(a: Buffer | string, b: Buffer | string, options?: { arrayMode?: boolean, strictOrder?: boolean, ignoreExtra?: boolean }): boolean {
// Note: this function does not yet properly deal with changes in the order of XML elements in an array
// Parse XML and filter out attributes as they are not important for comparison of metadata
const parsedA = XML.parse(a, { arrayMode: options?.arrayMode, ignoreAttributes: true });
const parsedB = XML.parse(b, { arrayMode: options?.arrayMode, ignoreAttributes: true });

// Compare parsed XML
return deepCompare(parsedA, parsedB, {
const diff = deepCompare(parsedA, parsedB, {
primitiveCompare: this.primitiveCompare,
ignoreArrayOrder: !options?.strictOrder,
ignoreMissingProperties: !!options?.ignoreMissing
ignoreExtraProperties: !!options?.ignoreExtra
});
return !!diff;
}

private isMetaXmlEqual(a: Buffer | string, b: Buffer | string): boolean {
Expand All @@ -184,7 +185,8 @@ export class RetrieveDeltaStrategy {
// Compare parsed XML
return deepCompare(localMeta, orgMeta, {
primitiveCompare: this.primitiveCompare,
ignoreArrayOrder: false
ignoreArrayOrder: false,
ignoreExtraProperties: true
});
}

Expand Down
30 changes: 30 additions & 0 deletions packages/util/src/__tests__/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,31 @@ describe('util', () => {
const b = { foo: { foo: 'test' } };
expect(objectEquals(a, b)).toBe(false);
});
it('should return false for unequal keys when ignoreExtraProperties', () => {
const a = { foo: { bar: 'test' } };
const b = { foo: { foo: 'test' } };
expect(objectEquals(a, b, { ignoreExtraProperties: true })).toBe(false);
});
it('should return true when b has extra properties and ignoreExtraProperties = true', () => {
const a = { foo: { bar: 'test' } };
const b = { foo: { bar: 'test' }, bar: 'foo' };
expect(objectEquals(a, b, { ignoreExtraProperties: true })).toBe(true);
});
it('should return false when b has extra properties and ignoreExtraProperties = false', () => {
const a = { foo: { bar: 'test' } };
const b = { foo: { bar: 'test' }, bar: 'foo' };
expect(objectEquals(a, b, { ignoreExtraProperties: false })).toBe(false);
});
it('should return true when b is missing properties and ignoreMissingProperties = true', () => {
const a = { foo: { bar: 'test' }, bar: 'foo' };
const b = { foo: { bar: 'test' } };
expect(objectEquals(a, b, { ignoreMissingProperties: true })).toBe(true);
});
it('should return false when b is missing properties and ignoreMissingProperties = false', () => {
const a = { foo: { bar: 'test' }, bar: 'foo' };
const b = { foo: { bar: 'test' } };
expect(objectEquals(a, b, { ignoreMissingProperties: false })).toBe(false);
});
it('should return false for extra properties', () => {
const a = { foo: { bar: 'test' } };
const b = { foo: { bar: 'test', foo: 'test' } };
Expand Down Expand Up @@ -152,6 +177,11 @@ describe('util', () => {
const b = { foo: [ { bar: 2 }, { bar: 1 }, { bar: 1 } ] };
expect(objectEquals(a, b, { ignoreArrayOrder: false })).toBe(false);
});
it('should return true for arrays with extra elements and ignoreExtraProperties = true and ignoreArrayOrder = true', () => {
const a = { foo: [ { bar: 1 } ] };
const b = { foo: [ { bar: 1 }, { bar: 2 } ] };
expect(objectEquals(a, b, { ignoreExtraProperties: true, ignoreArrayOrder: true })).toBe(true);
});
it('should return true for equal primitives', () => {
expect(objectEquals('a', 'a')).toBe(true);
expect(objectEquals('b', 'b')).toBe(true);
Expand Down
25 changes: 18 additions & 7 deletions packages/util/src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,11 +394,17 @@ export interface ObjectEqualsOptions {
*/
ignoreArrayOrder?: boolean;
/**
* Ignore missing properties in object `a` when comparing object `b` for equality.
* For example when object `b` looks like `{ a: 1, b: 2 }` and object `a` looks like `{ a: 1 }`
* the objects are considered equal even though object `a` is missing the `b` property.
* Ignore missing properties in object `b` when comparing object `a` for equality.
* For example when object `a` looks like `{ a: 1, b: 2 }` and object `b` looks like `{ a: 1 }`
* the objects are considered equal even though object `b` is missing the `b` property.
*/
ignoreMissingProperties?: boolean;
/**
* Ignore extra properties in object `b` when comparing object `a` for equality.
* For example when object `b` looks like `{ a: 1, b: 2 }` and object `a` looks like `{ a: 1 }`
* the objects are considered equal even though object `b` has an extra property
*/
ignoreExtraProperties?: boolean;
}

/**
Expand Down Expand Up @@ -429,8 +435,13 @@ export function objectEquals(
return true;
}

if (!options?.ignoreMissingProperties && Object.keys(a).length !== Object.keys(b).length) {
// If A does not have the same amount of keys of B they cannot be equal
const missingKeys = Object.keys(a).filter(key => !(key in b));
if (missingKeys.length && !options?.ignoreMissingProperties) {
return false;
}

const extraKeys = Object.keys(b).filter(key => !(key in a));
if (extraKeys.length && !options?.ignoreExtraProperties) {
return false;
}

Expand All @@ -447,11 +458,11 @@ export function objectEquals(
}
validElements.splice(index, 1);
}
return validElements.length === 0;
return options?.ignoreExtraProperties === true || validElements.length === 0;
}

// Check if all keys of A are equal to the keys in B
for (const key of Object.keys(a)) {
for (const key of Object.keys(a).filter(key => key in b)) {
if (!objectEqualityFn(a[key], b[key], options)) {
return false;
}
Expand Down

0 comments on commit 44b7332

Please sign in to comment.