Skip to content

Commit c774ed1

Browse files
committed
fix(repository): relax constrain check to allow input containing constrained values
Consider a Product constraint like `{categoryId: 1}` and a request to update (patch) Product with the following data: { name: 'updated', categoryId: 1 } Before this change, such request would be incorrectly rejected. With this change in place, such requests are allowed.
1 parent 301ccb2 commit c774ed1

File tree

2 files changed

+51
-19
lines changed

2 files changed

+51
-19
lines changed

packages/repository/src/__tests__/unit/repositories/constraint-utils.unit.ts

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55

66
import {expect} from '@loopback/testlab';
77
import {
8-
FilterBuilder,
9-
Filter,
10-
Where,
11-
constrainFilter,
12-
constrainWhere,
138
constrainDataObject,
149
constrainDataObjects,
10+
constrainFilter,
11+
constrainWhere,
1512
Entity,
13+
Filter,
14+
FilterBuilder,
15+
Where,
1616
} from '../../..';
1717

1818
describe('constraint utility functions', () => {
@@ -87,24 +87,58 @@ describe('constraint utility functions', () => {
8787
});
8888
});
8989

90-
it('throws error when the query changes field in constrain', () => {
90+
it('throws error when the query changes field in constraint', () => {
9191
const input = new Order({id: 1, description: 'order 1'});
9292
const constraint: Partial<Order> = {id: 2};
9393
expect(() => {
9494
constrainDataObject(input, constraint);
9595
}).to.throwError(/Property "id" cannot be changed!/);
9696
});
9797

98+
it('allows constrained fields with the same values', () => {
99+
const input = new Order({id: 2, description: 'order 1'});
100+
const constraint: Partial<Order> = {id: 2};
101+
const result = constrainDataObject(input, constraint);
102+
expect(result).to.deepEqual(
103+
new Order({
104+
id: 2,
105+
description: 'order 1',
106+
}),
107+
);
108+
});
109+
});
110+
111+
describe('constrainDataObjects', () => {
98112
it('constrains array of data objects', () => {
99113
const input = [
100-
new Order({id: 1, description: 'order 1'}),
101-
new Order({id: 2, description: 'order 2'}),
114+
new Order({description: 'order 1'}),
115+
new Order({description: 'order 2'}),
102116
];
103117
const constraint: Partial<Order> = {id: 3};
104118
const result = constrainDataObjects(input, constraint);
105119
expect(result[0]).to.containDeep(Object.assign({}, input[0], constraint));
106120
expect(result[1]).to.containDeep(Object.assign({}, input[1], constraint));
107121
});
122+
123+
it('throws error when the query changes field in constraint', () => {
124+
const input = [new Order({id: 1, description: 'order 1'})];
125+
const constraint: Partial<Order> = {id: 2};
126+
expect(() => {
127+
constrainDataObjects(input, constraint);
128+
}).to.throwError(/Property "id" cannot be changed!/);
129+
});
130+
131+
it('allows constrained fields with the same values', () => {
132+
const input = [new Order({id: 2, description: 'order 1'})];
133+
const constraint: Partial<Order> = {id: 2};
134+
const result = constrainDataObjects(input, constraint);
135+
expect(result).to.deepEqual([
136+
new Order({
137+
id: 2,
138+
description: 'order 1',
139+
}),
140+
]);
141+
});
108142
});
109143

110144
/*---------------HELPERS----------------*/

packages/repository/src/repositories/constraint-utils.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
55

6-
import {Filter, WhereBuilder, Where, FilterBuilder} from '../query';
7-
import {AnyObject, DataObject} from '../common-types';
86
import {cloneDeep} from 'lodash';
7+
import {AnyObject, DataObject} from '../common-types';
98
import {Entity} from '../model';
9+
import {Filter, FilterBuilder, Where, WhereBuilder} from '../query';
1010

1111
/**
1212
* A utility function which takes a filter and enforces constraint(s)
@@ -55,12 +55,16 @@ export function constrainDataObject<T extends Entity>(
5555
): DataObject<T> {
5656
const constrainedData = cloneDeep(originalData);
5757
for (const c in constraint) {
58-
if (constrainedData.hasOwnProperty(c))
58+
if (constrainedData.hasOwnProperty(c)) {
59+
// Known limitation: === does not work for objects such as ObjectId
60+
if (originalData[c] === constraint[c]) continue;
5961
throw new Error(`Property "${c}" cannot be changed!`);
62+
}
6063
(constrainedData as AnyObject)[c] = constraint[c];
6164
}
6265
return constrainedData;
6366
}
67+
6468
/**
6569
* A utility function which takes an array of model instance data and
6670
* enforces constraint(s) on it
@@ -71,13 +75,7 @@ export function constrainDataObject<T extends Entity>(
7175
*/
7276
export function constrainDataObjects<T extends Entity>(
7377
originalData: DataObject<T>[],
74-
constraint: Partial<T>,
78+
constraint: DataObject<T>,
7579
): DataObject<T>[] {
76-
const constrainedData = cloneDeep(originalData);
77-
for (let obj of constrainedData) {
78-
for (let prop in constraint) {
79-
(obj as AnyObject)[prop] = constraint[prop];
80-
}
81-
}
82-
return constrainedData;
80+
return originalData.map(obj => constrainDataObject(obj, constraint));
8381
}

0 commit comments

Comments
 (0)