diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index cc26786119..d46a5f769b 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -3372,6 +3372,149 @@ describe('ParseGraphQLServer', () => { }); }); + describe('Functions Mutations', () => { + it('can be called', async () => { + Parse.Cloud.define('hello', async () => { + return 'Hello world!'; + }); + + const result = await apolloClient.mutate({ + mutation: gql` + mutation CallFunction { + functions { + call(functionName: "hello") + } + } + `, + }); + + expect(result.data.functions.call).toEqual('Hello world!'); + }); + + it('can throw errors', async () => { + Parse.Cloud.define('hello', async () => { + throw new Error('Some error message.'); + }); + + try { + await apolloClient.mutate({ + mutation: gql` + mutation CallFunction { + functions { + call(functionName: "hello") + } + } + `, + }); + fail('Should throw an error'); + } catch (e) { + const { graphQLErrors } = e; + expect(graphQLErrors.length).toBe(1); + expect(graphQLErrors[0].message).toBe('Some error message.'); + } + }); + + it('should accept different params', done => { + Parse.Cloud.define('hello', async req => { + expect(req.params.date instanceof Date).toBe(true); + expect(req.params.date.getTime()).toBe(1463907600000); + expect(req.params.dateList[0] instanceof Date).toBe(true); + expect(req.params.dateList[0].getTime()).toBe(1463907600000); + expect(req.params.complexStructure.date[0] instanceof Date).toBe( + true + ); + expect(req.params.complexStructure.date[0].getTime()).toBe( + 1463907600000 + ); + expect( + req.params.complexStructure.deepDate.date[0] instanceof Date + ).toBe(true); + expect(req.params.complexStructure.deepDate.date[0].getTime()).toBe( + 1463907600000 + ); + expect( + req.params.complexStructure.deepDate2[0].date instanceof Date + ).toBe(true); + expect( + req.params.complexStructure.deepDate2[0].date.getTime() + ).toBe(1463907600000); + // Regression for #2294 + expect(req.params.file instanceof Parse.File).toBe(true); + expect(req.params.file.url()).toEqual('https://some.url'); + // Regression for #2204 + expect(req.params.array).toEqual(['a', 'b', 'c']); + expect(Array.isArray(req.params.array)).toBe(true); + expect(req.params.arrayOfArray).toEqual([ + ['a', 'b', 'c'], + ['d', 'e', 'f'], + ]); + expect(Array.isArray(req.params.arrayOfArray)).toBe(true); + expect(Array.isArray(req.params.arrayOfArray[0])).toBe(true); + expect(Array.isArray(req.params.arrayOfArray[1])).toBe(true); + + done(); + }); + + const params = { + date: { + __type: 'Date', + iso: '2016-05-22T09:00:00.000Z', + }, + dateList: [ + { + __type: 'Date', + iso: '2016-05-22T09:00:00.000Z', + }, + ], + lol: 'hello', + complexStructure: { + date: [ + { + __type: 'Date', + iso: '2016-05-22T09:00:00.000Z', + }, + ], + deepDate: { + date: [ + { + __type: 'Date', + iso: '2016-05-22T09:00:00.000Z', + }, + ], + }, + deepDate2: [ + { + date: { + __type: 'Date', + iso: '2016-05-22T09:00:00.000Z', + }, + }, + ], + }, + file: Parse.File.fromJSON({ + __type: 'File', + name: 'name', + url: 'https://some.url', + }), + array: ['a', 'b', 'c'], + arrayOfArray: [['a', 'b', 'c'], ['d', 'e', 'f']], + }; + + apolloClient.mutate({ + mutation: gql` + mutation CallFunction($params: Object) { + functions { + call(functionName: "hello", params: $params) + } + } + `, + variables: { + params, + }, + }); + }); + }); + describe('Data Types', () => { it('should support String', async () => { const someFieldValue = 'some string'; diff --git a/src/GraphQL/loaders/defaultGraphQLMutations.js b/src/GraphQL/loaders/defaultGraphQLMutations.js index 4d36c9e50b..c7ebc6347d 100644 --- a/src/GraphQL/loaders/defaultGraphQLMutations.js +++ b/src/GraphQL/loaders/defaultGraphQLMutations.js @@ -1,11 +1,13 @@ import * as objectsMutations from './objectsMutations'; import * as filesMutations from './filesMutations'; import * as usersMutations from './usersMutations'; +import * as functionsMutations from './functionsMutations'; const load = parseGraphQLSchema => { objectsMutations.load(parseGraphQLSchema); filesMutations.load(parseGraphQLSchema); usersMutations.load(parseGraphQLSchema); + functionsMutations.load(parseGraphQLSchema); }; export { load }; diff --git a/src/GraphQL/loaders/functionsMutations.js b/src/GraphQL/loaders/functionsMutations.js new file mode 100644 index 0000000000..6a7b9a3de9 --- /dev/null +++ b/src/GraphQL/loaders/functionsMutations.js @@ -0,0 +1,57 @@ +import { GraphQLObjectType, GraphQLNonNull, GraphQLString } from 'graphql'; +import { FunctionsRouter } from '../../Routers/FunctionsRouter'; +import * as defaultGraphQLTypes from './defaultGraphQLTypes'; + +const load = parseGraphQLSchema => { + const fields = {}; + + fields.call = { + description: + 'The call mutation can be used to invoke a cloud code function.', + args: { + functionName: { + description: 'This is the name of the function to be called.', + type: new GraphQLNonNull(GraphQLString), + }, + params: { + description: 'These are the params to be passed to the function.', + type: defaultGraphQLTypes.OBJECT, + }, + }, + type: defaultGraphQLTypes.ANY, + async resolve(_source, args, context) { + try { + const { functionName, params } = args; + const { config, auth, info } = context; + + return (await FunctionsRouter.handleCloudFunction({ + params: { + functionName, + }, + config, + auth, + info, + body: params, + })).response.result; + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, + }; + + const functionsMutation = new GraphQLObjectType({ + name: 'FunctionsMutation', + description: + 'FunctionsMutation is the top level type for functions mutations.', + fields, + }); + parseGraphQLSchema.graphQLTypes.push(functionsMutation); + + parseGraphQLSchema.graphQLMutations.functions = { + description: 'This is the top level for functions mutations.', + type: functionsMutation, + resolve: () => new Object(), + }; +}; + +export { load };