Skip to content

Commit e1f13f9

Browse files
committed
refactor: change payload.errors field in mutations payloads to error; add addErrorCatcherField helper
1 parent 3f8ca45 commit e1f13f9

19 files changed

+235
-96
lines changed

src/__tests__/github_issues/248-test.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/* eslint-disable no-await-in-loop */
2-
31
import mongoose from 'mongoose';
42
import { MongoMemoryServer } from 'mongodb-memory-server';
53
import { schemaComposer, graphql } from 'graphql-compose';
@@ -45,7 +43,7 @@ describe("issue #248 - payloads' errors", () => {
4543
});
4644
const schema = schemaComposer.buildSchema();
4745

48-
it('check errors in payload if errors field requested', async () => {
46+
it('catch resolver error in payload if `error` field was requested by client', async () => {
4947
const res = await graphql.graphql({
5048
schema,
5149
source: `
@@ -54,7 +52,7 @@ describe("issue #248 - payloads' errors", () => {
5452
record {
5553
name
5654
}
57-
errors {
55+
error {
5856
__typename
5957
message
6058
... on ValidationError {
@@ -71,14 +69,12 @@ describe("issue #248 - payloads' errors", () => {
7169
data: {
7270
createOne: {
7371
record: null,
74-
errors: [
75-
{
76-
__typename: 'ValidationError',
77-
message: 'this is a validate message',
78-
path: 'someStrangeField',
79-
value: 'Test',
80-
},
81-
],
72+
error: {
73+
__typename: 'ValidationError',
74+
message: 'this is a validate message',
75+
path: 'someStrangeField',
76+
value: 'Test',
77+
},
8278
},
8379
},
8480
});

src/resolvers/__tests__/createMany-test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ describe('createMany() ->', () => {
104104
expect(result.records[1].name).toBe('newName1');
105105
});
106106

107+
it('should return resolver runtime error in payload.error', async () => {
108+
const resolver = createMany(UserModel, UserTC);
109+
await expect(resolver.resolve({ projection: { error: true } })).resolves.toEqual({
110+
error: expect.objectContaining({
111+
message: expect.stringContaining('requires args.records to be an Array'),
112+
}),
113+
});
114+
115+
// should throw error if error not requested in graphql query
116+
await expect(resolver.resolve({})).rejects.toThrowError(
117+
'requires args.records to be an Array'
118+
);
119+
});
120+
107121
it('should save documents to database', async () => {
108122
const checkedName = 'nameForMongoDB';
109123
const res = await createMany(UserModel, UserTC).resolve({

src/resolvers/__tests__/createOne-test.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,36 @@ describe('createOne() ->', () => {
9393
expect(result.record.id).toBe(result.recordId);
9494
});
9595

96-
it('should return payload.errors', async () => {
96+
it('should return resolver runtime error in payload.error', async () => {
97+
const resolver = createOne(UserModel, UserTC);
98+
await expect(resolver.resolve({ projection: { error: true } })).resolves.toEqual({
99+
error: expect.objectContaining({
100+
message: expect.stringContaining('requires at least one value in args'),
101+
}),
102+
});
103+
104+
// should throw error if error not requested in graphql query
105+
await expect(resolver.resolve({})).rejects.toThrowError(
106+
'requires at least one value in args'
107+
);
108+
});
109+
110+
it('should return validation error in payload.error', async () => {
97111
const result = await createOne(UserModel, UserTC).resolve({
98112
args: {
99113
record: { valid: 'AlwaysFails', contacts: { email: 'mail' } },
100114
},
101115
projection: {
102-
errors: true,
116+
error: true,
103117
},
104118
});
105-
expect(result.errors).toEqual([
106-
{ message: 'Path `n` is required.', path: 'n', value: undefined },
107-
{ message: 'this is a validate message', path: 'valid', value: 'AlwaysFails' },
108-
]);
119+
// TODO: FIX error
120+
expect(result.error).toEqual(
121+
[
122+
{ message: 'Path `n` is required.', path: 'n', value: undefined },
123+
{ message: 'this is a validate message', path: 'valid', value: 'AlwaysFails' },
124+
][0] // <---- [0] remove after preparation correct ValidationErrorsType
125+
);
109126
});
110127

111128
it('should throw GraphQLError if client does not request errors field in payload', async () => {
@@ -120,13 +137,13 @@ describe('createOne() ->', () => {
120137
);
121138
});
122139

123-
it('should return empty payload.errors', async () => {
140+
it('should return empty payload.error', async () => {
124141
const result = await createOne(UserModel, UserTC).resolve({
125142
args: {
126143
record: { n: 'foo', contacts: { email: 'mail' } },
127144
},
128145
});
129-
expect(result.errors).toEqual(null);
146+
expect(result.error).toEqual(undefined);
130147
});
131148

132149
it('should return mongoose document', async () => {

src/resolvers/__tests__/removeById-test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,18 @@ describe('removeById() ->', () => {
7272
expect(result.recordId).toBe(user.id);
7373
});
7474

75+
it('should return resolver runtime error in payload.error', async () => {
76+
const resolver = removeById(UserModel, UserTC);
77+
await expect(resolver.resolve({ projection: { error: true } })).resolves.toEqual({
78+
error: expect.objectContaining({
79+
message: expect.stringContaining('resolver requires args._id'),
80+
}),
81+
});
82+
83+
// should throw error if error not requested in graphql query
84+
await expect(resolver.resolve({})).rejects.toThrowError('resolver requires args._id');
85+
});
86+
7587
it('should remove document in database', async () => {
7688
await removeById(UserModel, UserTC).resolve({
7789
args: {

src/resolvers/__tests__/removeMany-test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,20 @@ describe('removeMany() ->', () => {
110110
expect(result.numAffected).toBe(2);
111111
});
112112

113+
it('should return resolver runtime error in payload.error', async () => {
114+
const resolver = removeMany(UserModel, UserTC);
115+
await expect(resolver.resolve({ projection: { error: true } })).resolves.toEqual({
116+
error: expect.objectContaining({
117+
message: expect.stringContaining('requires at least one value in args.filter'),
118+
}),
119+
});
120+
121+
// should throw error if error not requested in graphql query
122+
await expect(resolver.resolve({})).rejects.toThrowError(
123+
'requires at least one value in args.filter'
124+
);
125+
});
126+
113127
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {
114128
let beforeQueryCalled = false;
115129
const result = await removeMany(UserModel, UserTC).resolve({

src/resolvers/__tests__/removeOne-test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ describe('removeOne() ->', () => {
100100
expect(result.recordId).toBe(user1.id);
101101
});
102102

103+
it('should return resolver runtime error in payload.error', async () => {
104+
const resolver = removeOne(UserModel, UserTC);
105+
await expect(resolver.resolve({ projection: { error: true } })).resolves.toEqual({
106+
error: expect.objectContaining({
107+
message: expect.stringContaining('requires at least one value in args.filter'),
108+
}),
109+
});
110+
111+
// should throw error if error not requested in graphql query
112+
await expect(resolver.resolve({})).rejects.toThrowError(
113+
'requires at least one value in args.filter'
114+
);
115+
});
116+
103117
it('should remove document in database', async () => {
104118
const checkedName = 'nameForMongoDB';
105119
await removeOne(UserModel, UserTC).resolve({

src/resolvers/__tests__/updateById-test.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,27 +100,41 @@ describe('updateById() ->', () => {
100100
expect(result.recordId).toBe(user1.id);
101101
});
102102

103-
it('should return empty payload.errors', async () => {
103+
it('should return resolver runtime error in payload.error', async () => {
104+
const resolver = updateById(UserModel, UserTC);
105+
await expect(resolver.resolve({ projection: { error: true } })).resolves.toEqual({
106+
error: expect.objectContaining({
107+
message: expect.stringContaining('requires args.record'),
108+
}),
109+
});
110+
111+
// should throw error if error not requested in graphql query
112+
await expect(resolver.resolve({})).rejects.toThrowError('requires args.record');
113+
});
114+
115+
it('should return empty payload.error', async () => {
104116
const result = await updateById(UserModel, UserTC).resolve({
105117
args: {
106118
record: { _id: user1.id, name: 'some name' },
107119
},
108120
});
109-
expect(result.errors).toEqual(null);
121+
expect(result.error).toEqual(undefined);
110122
});
111123

112-
it('should return payload.errors', async () => {
124+
it('should return payload.error', async () => {
113125
const result = await updateById(UserModel, UserTC).resolve({
114126
args: {
115127
record: { _id: user1.id, name: 'some name', valid: 'AlwaysFails' },
116128
},
117129
projection: {
118-
errors: true,
130+
error: true,
119131
},
120132
});
121-
expect(result.errors).toEqual([
122-
{ message: 'this is a validate message', path: 'valid', value: 'AlwaysFails' },
123-
]);
133+
expect(result.error).toEqual({
134+
message: 'this is a validate message',
135+
path: 'valid',
136+
value: 'AlwaysFails',
137+
});
124138
});
125139

126140
it('should throw GraphQLError if client does not request errors field in payload', async () => {

src/resolvers/__tests__/updateMany-test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ describe('updateMany() ->', () => {
119119
expect(result.numAffected).toBe(2);
120120
});
121121

122+
it('should return resolver runtime error in payload.error', async () => {
123+
const resolver = updateMany(UserModel, UserTC);
124+
await expect(resolver.resolve({ projection: { error: true } })).resolves.toEqual({
125+
error: expect.objectContaining({
126+
message: expect.stringContaining('at least one value in args.record'),
127+
}),
128+
});
129+
130+
// should throw error if error not requested in graphql query
131+
await expect(resolver.resolve({})).rejects.toThrowError('at least one value in args.record');
132+
});
133+
122134
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {
123135
let beforeQueryCalled = false;
124136
const result = await updateMany(UserModel, UserTC).resolve({

src/resolvers/__tests__/updateOne-test.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,26 +134,42 @@ describe('updateOne() ->', () => {
134134
expect(result.record.id).toBe(user1.id);
135135
});
136136

137-
it('should return empty payload.errors', async () => {
137+
it('should return resolver runtime error in payload.error', async () => {
138+
const resolver = updateOne(UserModel, UserTC);
139+
await expect(resolver.resolve({ projection: { error: true } })).resolves.toEqual({
140+
error: expect.objectContaining({
141+
message: expect.stringContaining('requires at least one value in args.filter'),
142+
}),
143+
});
144+
145+
// should throw error if error not requested in graphql query
146+
await expect(resolver.resolve({})).rejects.toThrowError(
147+
'requires at least one value in args.filter'
148+
);
149+
});
150+
151+
it('should return empty payload.error', async () => {
138152
const result = await updateOne(UserModel, UserTC).resolve({
139153
args: { filter: { _id: user1.id } },
140154
});
141-
expect(result.errors).toEqual(null);
155+
expect(result.error).toEqual(undefined);
142156
});
143157

144-
it('should return payload.errors', async () => {
158+
it('should return payload.error', async () => {
145159
const result = await updateOne(UserModel, UserTC).resolve({
146160
args: {
147161
filter: { _id: user1.id },
148162
record: { valid: 'AlwaysFails' },
149163
},
150164
projection: {
151-
errors: true,
165+
error: true,
152166
},
153167
});
154-
expect(result.errors).toEqual([
155-
{ message: 'this is a validate message', path: 'valid', value: 'AlwaysFails' },
156-
]);
168+
expect(result.error).toEqual({
169+
message: 'this is a validate message',
170+
path: 'valid',
171+
value: 'AlwaysFails',
172+
});
157173
});
158174

159175
it('should throw GraphQLError if client does not request errors field in payload', async () => {

src/resolvers/createMany.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ObjectTypeComposer, Resolver } from 'graphql-compose';
22
import type { Model, Document } from 'mongoose';
33
import { recordHelperArgs } from './helpers';
44
import type { ExtendedResolveParams, GenResolverOpts } from './index';
5+
import { addErrorCatcherField } from './helpers/addErrorCatcherField';
56

67
async function createSingle(
78
model: Model<any>,
@@ -80,7 +81,7 @@ export default function createMany<TSource = Document, TContext = any>(
8081
},
8182
},
8283
resolve: async (resolveParams) => {
83-
const recordData = (resolveParams.args && resolveParams.args.records) || [];
84+
const recordData = resolveParams?.args?.records;
8485

8586
if (!Array.isArray(recordData) || recordData.length === 0) {
8687
throw new Error(
@@ -121,5 +122,7 @@ export default function createMany<TSource = Document, TContext = any>(
121122
},
122123
});
123124

125+
addErrorCatcherField(resolver);
126+
124127
return resolver;
125128
}

0 commit comments

Comments
 (0)