Skip to content

Commit b5f7c56

Browse files
corydeppennodkz
authored andcommitted
fix: _operators should filter with or/and filter args
* test: add test case for issue 128 * fix: _operators should filter with or/and filter args * chore: update dependencies Closes #128
1 parent 8b99046 commit b5f7c56

File tree

7 files changed

+612
-377
lines changed

7 files changed

+612
-377
lines changed

package.json

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,41 +39,41 @@
3939
"mongoose": "^5.0.0 || ^4.4.0"
4040
},
4141
"devDependencies": {
42-
"@babel/cli": "^7.4.4",
43-
"@babel/core": "^7.4.5",
44-
"@babel/plugin-proposal-class-properties": "^7.4.4",
45-
"@babel/plugin-proposal-object-rest-spread": "^7.4.4",
42+
"@babel/cli": "^7.5.5",
43+
"@babel/core": "^7.5.5",
44+
"@babel/plugin-proposal-class-properties": "^7.5.5",
45+
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
4646
"@babel/plugin-transform-flow-strip-types": "^7.4.4",
47-
"@babel/plugin-transform-runtime": "^7.4.4",
48-
"@babel/preset-env": "^7.4.5",
47+
"@babel/plugin-transform-runtime": "^7.5.5",
48+
"@babel/preset-env": "^7.5.5",
4949
"@babel/preset-flow": "^7.0.0",
50-
"@types/graphql": "^14.2.0",
51-
"@types/mongoose": "5.5.5",
50+
"@types/graphql": "^14.2.3",
51+
"@types/mongoose": "5.5.11",
5252
"babel-core": "^7.0.0-bridge.0",
53-
"babel-eslint": "^10.0.1",
53+
"babel-eslint": "^10.0.2",
5454
"babel-jest": "^24.8.0",
5555
"eslint": "^5.16.0",
56-
"eslint-config-airbnb-base": "^13.1.0",
56+
"eslint-config-airbnb-base": "^13.2.0",
5757
"eslint-config-prettier": "^4.3.0",
58-
"eslint-plugin-flowtype": "^3.9.1",
59-
"eslint-plugin-import": "^2.17.3",
58+
"eslint-plugin-flowtype": "^3.13.0",
59+
"eslint-plugin-import": "^2.18.2",
6060
"eslint-plugin-prettier": "^3.1.0",
61-
"flow-bin": "^0.100.0",
62-
"graphql": "14.3.1",
63-
"graphql-compose": "^7.1.0",
61+
"flow-bin": "^0.104.0",
62+
"graphql": "14.4.2",
63+
"graphql-compose": "^7.3.0",
6464
"graphql-compose-connection": "^6.0.3",
6565
"graphql-compose-pagination": "^6.0.3",
6666
"jest": "^24.8.0",
67-
"mongodb-memory-server": "^5.1.2",
68-
"mongoose": "^5.5.12",
69-
"prettier": "^1.17.1",
67+
"mongodb-memory-server": "^5.1.9",
68+
"mongoose": "^5.6.8",
69+
"prettier": "^1.18.2",
7070
"request": "^2.88.0",
7171
"rimraf": "^2.6.3",
72-
"semantic-release": "^15.13.12",
73-
"tslint": "^5.17.0",
72+
"semantic-release": "^15.13.19",
73+
"tslint": "^5.18.0",
7474
"tslint-config-prettier": "^1.18.0",
7575
"tslint-plugin-prettier": "^2.0.1",
76-
"typescript": "^3.5.1"
76+
"typescript": "^3.5.3"
7777
},
7878
"scripts": {
7979
"build": "npm run build-lib && npm run build-mjs && npm run build-es && npm run build-node8 && npm run build-ts",
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/* @flow */
2+
/* eslint-disable no-await-in-loop */
3+
4+
import mongoose from 'mongoose';
5+
import MongodbMemoryServer from 'mongodb-memory-server';
6+
import { composeWithMongoose } from '../../index';
7+
8+
let mongoServer;
9+
beforeAll(async () => {
10+
mongoServer = new MongodbMemoryServer();
11+
const mongoUri = await mongoServer.getConnectionString();
12+
await mongoose.connect(mongoUri, { useNewUrlParser: true });
13+
// mongoose.set('debug', true);
14+
});
15+
16+
afterAll(() => {
17+
mongoose.disconnect();
18+
mongoServer.stop();
19+
});
20+
21+
// May require additional time for downloading MongoDB binaries
22+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
23+
24+
describe('issue #128 - OR/AND filter args not working with some other filter args', () => {
25+
const RecordSchema = new mongoose.Schema({
26+
id: String,
27+
name: String,
28+
pets: [String],
29+
friends: [String],
30+
});
31+
const Record = mongoose.model('Record', RecordSchema);
32+
const RecordTC = composeWithMongoose(Record);
33+
const resolver = RecordTC.getResolver('findMany');
34+
35+
beforeAll(async () => {
36+
for (let i = 1; i <= 9; i++) {
37+
await Record.create({
38+
_id: `10000000000000000000000${i}`,
39+
name: `Name ${i}`,
40+
pets: [`Pet ${i}`],
41+
friends: [`Friend ${i}`],
42+
});
43+
}
44+
});
45+
46+
it('check with OR filter arg', async () => {
47+
const res1 = await resolver.resolve({
48+
args: {
49+
filter: {
50+
OR: [
51+
{ _operators: { pets: { in: ['Pet 2'] } } },
52+
{ _operators: { friends: { in: ['Friend 4'] } } },
53+
],
54+
},
55+
},
56+
});
57+
58+
expect(res1.map(({ pets, friends }) => ({ pets: [...pets], friends: [...friends] }))).toEqual([
59+
{
60+
pets: ['Pet 2'],
61+
friends: ['Friend 2'],
62+
},
63+
{
64+
pets: ['Pet 4'],
65+
friends: ['Friend 4'],
66+
},
67+
]);
68+
});
69+
70+
it('check with AND filter arg', async () => {
71+
const res1 = await resolver.resolve({
72+
args: {
73+
filter: {
74+
OR: [{ _operators: { pets: { in: ['Pet 2'] } } }, { name: 'Name 4' }],
75+
},
76+
},
77+
});
78+
79+
expect(res1.map(({ pets, friends }) => ({ pets: [...pets], friends: [...friends] }))).toEqual([
80+
{
81+
pets: ['Pet 2'],
82+
friends: ['Friend 2'],
83+
},
84+
{
85+
pets: ['Pet 4'],
86+
friends: ['Friend 4'],
87+
},
88+
]);
89+
});
90+
91+
it('check without OR filter arg', async () => {
92+
const res1 = await resolver.resolve({
93+
args: {
94+
filter: {
95+
_operators: { pets: { in: ['Pet 2'] } },
96+
},
97+
},
98+
});
99+
100+
expect(res1.map(res => ({ pets: [...res.pets], friends: [...res.friends] }))).toEqual([
101+
{
102+
pets: ['Pet 2'],
103+
friends: ['Friend 2'],
104+
},
105+
]);
106+
});
107+
});

src/resolvers/helpers/__tests__/filterOperators-test.js

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
processFilterOperators,
88
OPERATORS_FIELDNAME,
99
} from '../filterOperators';
10+
import { toMongoFilterDottedObject } from '../../../utils/toMongoDottedObject';
1011
import { UserModel } from '../../../__mocks__/userModel';
1112

1213
let itc: InputTypeComposer<any>;
@@ -89,10 +90,7 @@ describe('Resolver helper `filter` ->', () => {
8990
const filter = {
9091
[OPERATORS_FIELDNAME]: { age: { gt: 10, lt: 20 } },
9192
};
92-
const spyWhereFn = jest.fn();
93-
const resolveParams: any = { query: { where: spyWhereFn } };
94-
processFilterOperators(filter, resolveParams);
95-
expect(spyWhereFn).toBeCalledWith({ age: { $gt: 10, $lt: 20 } });
93+
expect(processFilterOperators(filter)).toEqual({ age: { $gt: 10, $lt: 20 } });
9694
});
9795

9896
it('should convert OR query', () => {
@@ -109,10 +107,7 @@ describe('Resolver helper `filter` ->', () => {
109107
},
110108
],
111109
};
112-
const spyWhereFn = jest.fn();
113-
const resolveParams: any = { query: { where: spyWhereFn } };
114-
processFilterOperators(filter, resolveParams);
115-
expect(spyWhereFn).toBeCalledWith({
110+
expect(toMongoFilterDottedObject(processFilterOperators(filter))).toEqual({
116111
$or: [
117112
{ age: 30, 'name.first': 'Pavel' },
118113
{
@@ -135,10 +130,7 @@ describe('Resolver helper `filter` ->', () => {
135130
},
136131
],
137132
};
138-
const spyWhereFn = jest.fn();
139-
const resolveParams: any = { query: { where: spyWhereFn } };
140-
processFilterOperators(filter, resolveParams);
141-
expect(spyWhereFn).toBeCalledWith({
133+
expect(toMongoFilterDottedObject(processFilterOperators(filter))).toEqual({
142134
$and: [
143135
{ 'name.first': 'Pavel' },
144136
{
@@ -164,10 +156,7 @@ describe('Resolver helper `filter` ->', () => {
164156
},
165157
],
166158
};
167-
const spyWhereFn = jest.fn();
168-
const resolveParams: any = { query: { where: spyWhereFn } };
169-
processFilterOperators(filter, resolveParams);
170-
expect(spyWhereFn).toBeCalledWith({
159+
expect(toMongoFilterDottedObject(processFilterOperators(filter))).toEqual({
171160
$or: [
172161
{
173162
$and: [

src/resolvers/helpers/filter.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,19 +108,17 @@ export function filterHelper(resolveParams: ExtendedResolveParams): void {
108108
// eslint-disable-next-line
109109
resolveParams.query = resolveParams.query.where({ _id: { $in: _ids } });
110110
}
111-
111+
processFilterOperators(filterFields);
112112
const clearedFilter = {};
113113
Object.keys(filterFields).forEach(key => {
114-
if (modelFields[key]) {
114+
if (modelFields[key] || key.indexOf('$') === 0) {
115115
clearedFilter[key] = filterFields[key];
116116
}
117117
});
118118
if (Object.keys(clearedFilter).length > 0) {
119119
// eslint-disable-next-line
120120
resolveParams.query = resolveParams.query.where(toMongoFilterDottedObject(clearedFilter));
121121
}
122-
123-
processFilterOperators(filter, resolveParams);
124122
}
125123

126124
if (isObject(resolveParams.rawQuery)) {

src/resolvers/helpers/filterOperators.js

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
import { getNamedType, type GraphQLInputType } from 'graphql-compose/lib/graphql';
55
import type { MongooseModel } from 'mongoose';
66
import type { InputTypeComposer } from 'graphql-compose';
7-
import type { ExtendedResolveParams } from '../index';
8-
import { upperFirst, getIndexedFieldNamesForGraphQL, toMongoDottedObject } from '../../utils';
7+
import { upperFirst, getIndexedFieldNamesForGraphQL } from '../../utils';
98
import type { FilterHelperArgsOpts } from './filter';
109

1110
export type FilterOperatorNames = 'gt' | 'gte' | 'lt' | 'lte' | 'ne' | 'in[]' | 'nin[]';
@@ -37,58 +36,54 @@ export function addFilterOperators(
3736
});
3837
}
3938

40-
export function processFilterOperators(filter: Object, resolveParams: ExtendedResolveParams) {
41-
_prepareAndOrFilter(filter, resolveParams);
42-
39+
export function processFilterOperators(filter: Object) {
40+
_prepareAndOrFilter(filter);
4341
if (filter[OPERATORS_FIELDNAME]) {
4442
const operatorFields = filter[OPERATORS_FIELDNAME];
4543
Object.keys(operatorFields).forEach(fieldName => {
4644
const fieldOperators = { ...operatorFields[fieldName] };
4745
const criteria = {};
4846
Object.keys(fieldOperators).forEach(operatorName => {
49-
criteria[`$${operatorName}`] = fieldOperators[operatorName];
47+
if (Array.isArray(fieldOperators[operatorName])) {
48+
criteria[`$${operatorName}`] = fieldOperators[operatorName].map(v =>
49+
processFilterOperators(v)
50+
);
51+
} else {
52+
criteria[`$${operatorName}`] = processFilterOperators(fieldOperators[operatorName]);
53+
}
5054
});
5155
if (Object.keys(criteria).length > 0) {
5256
// eslint-disable-next-line
53-
resolveParams.query = resolveParams.query.where({
54-
[fieldName]: criteria,
55-
});
57+
filter[fieldName] = criteria;
5658
}
5759
});
60+
// eslint-disable-next-line no-param-reassign
61+
delete filter[OPERATORS_FIELDNAME];
5862
}
63+
return filter;
5964
}
6065

61-
export function _prepareAndOrFilter(filter: Object, resolveParams?: ExtendedResolveParams) {
66+
export function _prepareAndOrFilter(filter: Object) {
6267
/* eslint-disable no-param-reassign */
6368
if (!filter.OR && !filter.AND) return;
6469

6570
const { OR, AND } = filter;
6671
if (OR) {
6772
const $or = OR.map(d => {
68-
_prepareAndOrFilter(d);
69-
return toMongoDottedObject(d);
73+
processFilterOperators(d);
74+
return d;
7075
});
71-
72-
if (resolveParams) {
73-
resolveParams.query.where({ $or });
74-
} else {
75-
filter.$or = $or;
76-
delete filter.OR;
77-
}
76+
filter.$or = $or;
77+
delete filter.OR;
7878
}
7979

8080
if (AND) {
8181
const $and = AND.map(d => {
82-
_prepareAndOrFilter(d);
83-
return toMongoDottedObject(d);
82+
processFilterOperators(d);
83+
return d;
8484
});
85-
86-
if (resolveParams) {
87-
resolveParams.query.where({ $and });
88-
} else {
89-
filter.$and = $and;
90-
delete filter.AND;
91-
}
85+
filter.$and = $and;
86+
delete filter.AND;
9287
}
9388
/* eslint-enable no-param-reassign */
9489
}

0 commit comments

Comments
 (0)