Skip to content

Commit 280a89e

Browse files
committed
Add removeOne resolver
1 parent 582d681 commit 280a89e

File tree

5 files changed

+218
-25
lines changed

5 files changed

+218
-25
lines changed

src/__mocks__/userModel.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ const UserSchema = new Schema(
2828
description: 'Person name',
2929
},
3030

31+
age: {
32+
type: Number,
33+
description: 'Full years',
34+
},
35+
3136
gender: {
3237
type: String,
3338
enum: ['male', 'female', 'ladyboy'],
@@ -65,11 +70,6 @@ const UserSchema = new Schema(
6570
description: 'Knowledge of languages',
6671
},
6772

68-
totalExperience: {
69-
type: Number,
70-
description: 'Work experience in months',
71-
},
72-
7373
// createdAt, created via option `timastamp: true`
7474

7575
// updatedAt, created via option `timastamp: true`

src/__tests__/fieldConverter-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ describe('fieldConverter', () => {
9494
it('schould derive SCALAR', () => {
9595
expect(deriveComplexType(fields.name)).to.equal(ComplexTypes.SCALAR);
9696
expect(deriveComplexType(fields.relocation)).to.equal(ComplexTypes.SCALAR);
97-
expect(deriveComplexType(fields.totalExperience)).to.equal(ComplexTypes.SCALAR);
97+
expect(deriveComplexType(fields.age)).to.equal(ComplexTypes.SCALAR);
9898
expect(deriveComplexType(fields.createdAt)).to.equal(ComplexTypes.SCALAR);
9999

100100
expect(deriveComplexType(fields.gender)).not.to.equal(ComplexTypes.SCALAR);

src/definition.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ export type MonooseModelIndex = [
2020

2121
export type MongooseModelT = {
2222
schema: MongooseModelSchemaT,
23-
findOne(criteria: ?Object, projection?: Object): MongooseQuery,
23+
findOne(conditions: ?Object, projection?: Object): MongooseQuery,
2424
findById(id: mixed, projection?: Object, options?: Object): MongooseQuery,
2525
find(conditions: ?Object, projection?: Object, options?: Object): MongooseQuery,
2626
findOneAndRemove(conditions: ?Object, options?: Object): MongooseQuery,
27-
where(criteria: ObjectMap): MongooseQuery,
27+
where(conditions: ObjectMap): MongooseQuery,
2828
findByIdAndRemove(id: mixed, options?: Object): MongooseQuery,
29+
findOneAndRemove(conditions: ?Object, options?: Object): MongooseQuery,
2930
}
3031

3132
export type MongooseFieldOptionsT = {
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/* @flow */
2+
3+
import { expect } from 'chai';
4+
import { UserModel } from '../../__mocks__/userModel.js';
5+
import removeOne from '../removeOne';
6+
import Resolver from '../../../../graphql-compose/src/resolver/resolver';
7+
import TypeComposer from '../../../../graphql-compose/src/typeComposer';
8+
import { convertModelToGraphQL } from '../../fieldsConverter';
9+
import GraphQLMongoID from '../../types/mongoid';
10+
import { mongoose } from '../../__mocks__/mongooseCommon';
11+
12+
const UserType = convertModelToGraphQL(UserModel, 'User');
13+
14+
describe('removeOne() ->', () => {
15+
let user1;
16+
let user2;
17+
let user3;
18+
19+
before('clear UserModel collection', (done) => {
20+
UserModel.collection.drop(() => {
21+
done();
22+
});
23+
});
24+
25+
beforeEach('add test user document to mongoDB', () => {
26+
user1 = new UserModel({
27+
name: 'userName1',
28+
gender: 'male',
29+
relocation: true,
30+
age: 28,
31+
});
32+
33+
user2 = new UserModel({
34+
name: 'userName2',
35+
gender: 'female',
36+
relocation: true,
37+
age: 29,
38+
});
39+
40+
user3 = new UserModel({
41+
name: 'userName3',
42+
gender: 'female',
43+
relocation: true,
44+
age: 30,
45+
});
46+
47+
return Promise.all([
48+
user1.save(),
49+
user2.save(),
50+
user3.save(),
51+
]);
52+
});
53+
54+
it('should return Resolver object', () => {
55+
const resolver = removeOne(UserModel, UserType);
56+
expect(resolver).to.be.instanceof(Resolver);
57+
});
58+
59+
describe('Resolver.args', () => {
60+
it('should have `filter` arg', () => {
61+
const resolver = removeOne(UserModel, UserType);
62+
expect(resolver.hasArg('filter')).to.be.true;
63+
});
64+
65+
it('should not have `skip` arg due mongoose error: '
66+
+ 'skip cannot be used with findOneAndRemove', () => {
67+
const resolver = removeOne(UserModel, UserType);
68+
expect(resolver.hasArg('skip')).to.be.false;
69+
});
70+
71+
it('should have `sort` arg', () => {
72+
const resolver = removeOne(UserModel, UserType);
73+
expect(resolver.hasArg('sort')).to.be.true;
74+
});
75+
});
76+
77+
describe('Resolver.resolve():Promise', () => {
78+
it('should be promise', () => {
79+
// some crazy shit for method `MongooseModel.findOneAndRemove`
80+
// needs to set explicitly Promise object
81+
// otherwise it returns Promise object, but it not instanse of global Promise
82+
mongoose.Promise = Promise; // eslint-disable-line
83+
const result = removeOne(UserModel, UserType).resolve({});
84+
expect(result).instanceof(Promise);
85+
result.catch(() => 'catch error if appears, hide it from mocha');
86+
});
87+
88+
it('should return payload.recordId if record existed in db', async () => {
89+
const result = await removeOne(UserModel, UserType).resolve({
90+
args: { filter: { _id: user1.id } },
91+
});
92+
expect(result).have.property('recordId', user1.id);
93+
});
94+
95+
it('should remove document in database', (done) => {
96+
const checkedName = 'nameForMongoDB';
97+
removeOne(UserModel, UserType).resolve({
98+
args: {
99+
filter: { _id: user1.id },
100+
input: { name: checkedName },
101+
},
102+
}).then(() => {
103+
UserModel.collection.findOne({ _id: user1._id }, (err, doc) => {
104+
expect(err).to.be.null;
105+
expect(doc).to.be.null;
106+
done();
107+
});
108+
});
109+
});
110+
111+
it('should return payload.record', async () => {
112+
const result = await removeOne(UserModel, UserType).resolve({
113+
args: { filter: { _id: user1.id } },
114+
});
115+
expect(result).have.deep.property('record.id', user1.id);
116+
});
117+
118+
it('should sort records', async () => {
119+
const result1 = await removeOne(UserModel, UserType).resolve({
120+
args: {
121+
filter: { relocation: true },
122+
sort: { age: 1 },
123+
},
124+
});
125+
expect(result1).have.deep.property('record.age', user1.age);
126+
127+
const result2 = await removeOne(UserModel, UserType).resolve({
128+
args: {
129+
filter: { relocation: true },
130+
sort: { age: -1 },
131+
},
132+
});
133+
expect(result2).have.deep.property('record.age', user3.age);
134+
});
135+
});
136+
137+
describe('Resolver.getOutputType()', () => {
138+
it('should have correct output type name', () => {
139+
const outputType = removeOne(UserModel, UserType).getOutputType();
140+
expect(outputType).property('name').to.equal(`RemoveOne${UserType.name}Payload`);
141+
});
142+
143+
it('should have recordId field', () => {
144+
const outputType = removeOne(UserModel, UserType).getOutputType();
145+
const recordIdField = new TypeComposer(outputType).getField('recordId');
146+
expect(recordIdField).property('type').to.equal(GraphQLMongoID);
147+
});
148+
149+
it('should have record field', () => {
150+
const outputType = removeOne(UserModel, UserType).getOutputType();
151+
const recordField = new TypeComposer(outputType).getField('record');
152+
expect(recordField).property('type').to.equal(UserType);
153+
});
154+
});
155+
});

src/resolvers/removeOne.js

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,69 @@
11
/* @flow */
22
/* eslint-disable no-param-reassign */
33

4-
import type {
5-
MongooseModelT,
6-
GraphQLObjectType,
7-
} from '../definition';
8-
import Resolver from '../../../graphql-compose/src/resolver/resolver';
4+
import { GraphQLObjectType } from 'graphql';
5+
import GraphQLMongoID from '../types/mongoid';
96

107
import { skipHelperArgs, skipHelper } from './helpers/skip';
118
import { filterHelperArgsGen, filterHelper } from './helpers/filter';
9+
import { sortHelperArgsGen, sortHelper } from './helpers/sort';
10+
import { projectionHelper } from './helpers/projection';
1211

12+
import type {
13+
MongooseModelT,
14+
ExtendedResolveParams,
15+
} from '../definition';
16+
import Resolver from '../../../graphql-compose/src/resolver/resolver';
1317

14-
export default function removeOne(model: MongooseModelT, gqType: GraphQLObjectType): Resolver {
15-
const filterHelperArgs = filterHelperArgsGen();
16-
17-
return new Resolver({
18-
outputType: 'someCrazy',
18+
export default function removeOne(
19+
model: MongooseModelT,
20+
gqType: GraphQLObjectType,
21+
): Resolver {
22+
const resolver = new Resolver({
1923
name: 'removeOne',
24+
kind: 'mutation',
25+
description: 'Remove one document: '
26+
+ '1) Remove with hooks via findOneAndRemove. '
27+
+ '2) Return removed document.',
28+
outputType: new GraphQLObjectType({
29+
name: `RemoveOne${gqType.name}Payload`,
30+
fields: {
31+
recordId: {
32+
type: GraphQLMongoID,
33+
description: 'Removed document ID',
34+
},
35+
record: {
36+
type: gqType,
37+
description: 'Removed document',
38+
},
39+
},
40+
}),
2041
args: {
21-
...filterHelperArgs,
22-
...skipHelperArgs,
42+
...filterHelperArgsGen(),
43+
...sortHelperArgsGen(model, {
44+
sortTypeName: `Sort${gqType.name}Input`,
45+
}),
2346
},
24-
resolve: ({ args }) => {
25-
let cursor = model.findOneAndRemove({});
26-
cursor = filterHelper(cursor, args);
27-
cursor = skipHelper(cursor, args);
47+
resolve: (resolveParams: ExtendedResolveParams) => {
48+
resolveParams.query = model.findOneAndRemove({});
49+
filterHelper(resolveParams);
50+
sortHelper(resolveParams);
51+
projectionHelper(resolveParams);
2852

29-
return cursor;
53+
return resolveParams.query
54+
.exec()
55+
.then(res => {
56+
if (res) {
57+
return {
58+
record: res.toObject(),
59+
recordId: res.id,
60+
};
61+
}
62+
63+
return null;
64+
});
3065
},
3166
});
67+
68+
return resolver;
3269
}

0 commit comments

Comments
 (0)