* fix(GraphQL): Unable to run queries with order as array An internal server error is thrown if the `order` query parameter is an array. Currently, `order` only works with a single value. * Add tests * Reset cache * Remove one of the tests from Postgres Co-authored-by: Douglas Muraoka <douglas.muraoka@gmail.com>
10409 lines
340 KiB
JavaScript
10409 lines
340 KiB
JavaScript
const http = require('http');
|
|
const express = require('express');
|
|
const req = require('../lib/request');
|
|
const fetch = require('node-fetch');
|
|
const FormData = require('form-data');
|
|
const ws = require('ws');
|
|
const pluralize = require('pluralize');
|
|
const { getMainDefinition } = require('apollo-utilities');
|
|
const { ApolloLink, split } = require('apollo-link');
|
|
const { createHttpLink } = require('apollo-link-http');
|
|
const { InMemoryCache } = require('apollo-cache-inmemory');
|
|
const { createUploadLink } = require('apollo-upload-client');
|
|
const { SubscriptionClient } = require('subscriptions-transport-ws');
|
|
const { WebSocketLink } = require('apollo-link-ws');
|
|
const ApolloClient = require('apollo-client').default;
|
|
const gql = require('graphql-tag');
|
|
const { ParseServer } = require('../');
|
|
const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer');
|
|
const ReadPreference = require('mongodb').ReadPreference;
|
|
const uuidv4 = require('uuid/v4');
|
|
|
|
function handleError(e) {
|
|
if (
|
|
e &&
|
|
e.networkError &&
|
|
e.networkError.result &&
|
|
e.networkError.result.errors
|
|
) {
|
|
fail(e.networkError.result.errors);
|
|
} else {
|
|
fail(e);
|
|
}
|
|
}
|
|
|
|
describe('ParseGraphQLServer', () => {
|
|
let parseServer;
|
|
let parseGraphQLServer;
|
|
|
|
beforeAll(async () => {
|
|
parseServer = await global.reconfigureServer({});
|
|
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
|
|
graphQLPath: '/graphql',
|
|
playgroundPath: '/playground',
|
|
subscriptionsPath: '/subscriptions',
|
|
});
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should require a parseServer instance', () => {
|
|
expect(() => new ParseGraphQLServer()).toThrow(
|
|
'You must provide a parseServer instance!'
|
|
);
|
|
});
|
|
|
|
it('should require config.graphQLPath', () => {
|
|
expect(() => new ParseGraphQLServer(parseServer)).toThrow(
|
|
'You must provide a config.graphQLPath!'
|
|
);
|
|
expect(() => new ParseGraphQLServer(parseServer, {})).toThrow(
|
|
'You must provide a config.graphQLPath!'
|
|
);
|
|
});
|
|
|
|
it('should only require parseServer and config.graphQLPath args', () => {
|
|
let parseGraphQLServer;
|
|
expect(() => {
|
|
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
|
|
graphQLPath: 'graphql',
|
|
});
|
|
}).not.toThrow();
|
|
expect(parseGraphQLServer.parseGraphQLSchema).toBeDefined();
|
|
expect(parseGraphQLServer.parseGraphQLSchema.databaseController).toEqual(
|
|
parseServer.config.databaseController
|
|
);
|
|
});
|
|
|
|
it('should initialize parseGraphQLSchema with a log controller', async () => {
|
|
const loggerAdapter = {
|
|
log: () => {},
|
|
error: () => {},
|
|
};
|
|
const parseServer = await global.reconfigureServer({
|
|
loggerAdapter,
|
|
});
|
|
const parseGraphQLServer = new ParseGraphQLServer(parseServer, {
|
|
graphQLPath: 'graphql',
|
|
});
|
|
expect(parseGraphQLServer.parseGraphQLSchema.log.adapter).toBe(
|
|
loggerAdapter
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('_getGraphQLOptions', () => {
|
|
const req = {
|
|
info: new Object(),
|
|
config: new Object(),
|
|
auth: new Object(),
|
|
};
|
|
|
|
it("should return schema and context with req's info, config and auth", async () => {
|
|
const options = await parseGraphQLServer._getGraphQLOptions(req);
|
|
expect(options.schema).toEqual(
|
|
parseGraphQLServer.parseGraphQLSchema.graphQLSchema
|
|
);
|
|
expect(options.context.info).toEqual(req.info);
|
|
expect(options.context.config).toEqual(req.config);
|
|
expect(options.context.auth).toEqual(req.auth);
|
|
});
|
|
|
|
it('should load GraphQL schema in every call', async () => {
|
|
const originalLoad = parseGraphQLServer.parseGraphQLSchema.load;
|
|
let counter = 0;
|
|
parseGraphQLServer.parseGraphQLSchema.load = () => ++counter;
|
|
expect((await parseGraphQLServer._getGraphQLOptions(req)).schema).toEqual(
|
|
1
|
|
);
|
|
expect((await parseGraphQLServer._getGraphQLOptions(req)).schema).toEqual(
|
|
2
|
|
);
|
|
expect((await parseGraphQLServer._getGraphQLOptions(req)).schema).toEqual(
|
|
3
|
|
);
|
|
parseGraphQLServer.parseGraphQLSchema.load = originalLoad;
|
|
});
|
|
});
|
|
|
|
describe('_transformMaxUploadSizeToBytes', () => {
|
|
it('should transform to bytes', () => {
|
|
expect(parseGraphQLServer._transformMaxUploadSizeToBytes('20mb')).toBe(
|
|
20971520
|
|
);
|
|
expect(parseGraphQLServer._transformMaxUploadSizeToBytes('333Gb')).toBe(
|
|
357556027392
|
|
);
|
|
expect(
|
|
parseGraphQLServer._transformMaxUploadSizeToBytes('123456KB')
|
|
).toBe(126418944);
|
|
});
|
|
});
|
|
|
|
describe('applyGraphQL', () => {
|
|
it('should require an Express.js app instance', () => {
|
|
expect(() => parseGraphQLServer.applyGraphQL()).toThrow(
|
|
'You must provide an Express.js app instance!'
|
|
);
|
|
expect(() => parseGraphQLServer.applyGraphQL({})).toThrow(
|
|
'You must provide an Express.js app instance!'
|
|
);
|
|
expect(() =>
|
|
parseGraphQLServer.applyGraphQL(new express())
|
|
).not.toThrow();
|
|
});
|
|
|
|
it('should apply middlewares at config.graphQLPath', () => {
|
|
let useCount = 0;
|
|
expect(() =>
|
|
new ParseGraphQLServer(parseServer, {
|
|
graphQLPath: 'somepath',
|
|
}).applyGraphQL({
|
|
use: path => {
|
|
useCount++;
|
|
expect(path).toEqual('somepath');
|
|
},
|
|
})
|
|
).not.toThrow();
|
|
expect(useCount).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('applyPlayground', () => {
|
|
it('should require an Express.js app instance', () => {
|
|
expect(() => parseGraphQLServer.applyPlayground()).toThrow(
|
|
'You must provide an Express.js app instance!'
|
|
);
|
|
expect(() => parseGraphQLServer.applyPlayground({})).toThrow(
|
|
'You must provide an Express.js app instance!'
|
|
);
|
|
expect(() =>
|
|
parseGraphQLServer.applyPlayground(new express())
|
|
).not.toThrow();
|
|
});
|
|
|
|
it('should require initialization with config.playgroundPath', () => {
|
|
expect(() =>
|
|
new ParseGraphQLServer(parseServer, {
|
|
graphQLPath: 'graphql',
|
|
}).applyPlayground(new express())
|
|
).toThrow('You must provide a config.playgroundPath to applyPlayground!');
|
|
});
|
|
|
|
it('should apply middlewares at config.playgroundPath', () => {
|
|
let useCount = 0;
|
|
expect(() =>
|
|
new ParseGraphQLServer(parseServer, {
|
|
graphQLPath: 'graphQL',
|
|
playgroundPath: 'somepath',
|
|
}).applyPlayground({
|
|
get: path => {
|
|
useCount++;
|
|
expect(path).toEqual('somepath');
|
|
},
|
|
})
|
|
).not.toThrow();
|
|
expect(useCount).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('createSubscriptions', () => {
|
|
it('should require initialization with config.subscriptionsPath', () => {
|
|
expect(() =>
|
|
new ParseGraphQLServer(parseServer, {
|
|
graphQLPath: 'graphql',
|
|
}).createSubscriptions({})
|
|
).toThrow(
|
|
'You must provide a config.subscriptionsPath to createSubscriptions!'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('setGraphQLConfig', () => {
|
|
let parseGraphQLServer;
|
|
beforeEach(() => {
|
|
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
|
|
graphQLPath: 'graphql',
|
|
});
|
|
});
|
|
it('should pass the graphQLConfig onto the parseGraphQLController', async () => {
|
|
let received;
|
|
parseGraphQLServer.parseGraphQLController = {
|
|
async updateGraphQLConfig(graphQLConfig) {
|
|
received = graphQLConfig;
|
|
return {};
|
|
},
|
|
};
|
|
const graphQLConfig = { enabledForClasses: [] };
|
|
await parseGraphQLServer.setGraphQLConfig(graphQLConfig);
|
|
expect(received).toBe(graphQLConfig);
|
|
});
|
|
it('should not absorb exceptions from parseGraphQLController', async () => {
|
|
parseGraphQLServer.parseGraphQLController = {
|
|
async updateGraphQLConfig() {
|
|
throw new Error('Network request failed');
|
|
},
|
|
};
|
|
await expectAsync(
|
|
parseGraphQLServer.setGraphQLConfig({})
|
|
).toBeRejectedWith(new Error('Network request failed'));
|
|
});
|
|
it('should return the response from parseGraphQLController', async () => {
|
|
parseGraphQLServer.parseGraphQLController = {
|
|
async updateGraphQLConfig() {
|
|
return { response: { result: true } };
|
|
},
|
|
};
|
|
await expectAsync(
|
|
parseGraphQLServer.setGraphQLConfig({})
|
|
).toBeResolvedTo({ response: { result: true } });
|
|
});
|
|
});
|
|
|
|
describe('Auto API', () => {
|
|
let httpServer;
|
|
let parseLiveQueryServer;
|
|
const headers = {
|
|
'X-Parse-Application-Id': 'test',
|
|
'X-Parse-Javascript-Key': 'test',
|
|
};
|
|
|
|
let apolloClient;
|
|
|
|
let user1;
|
|
let user2;
|
|
let user3;
|
|
let user4;
|
|
let user5;
|
|
let role;
|
|
let object1;
|
|
let object2;
|
|
let object3;
|
|
let object4;
|
|
let objects = [];
|
|
|
|
async function prepareData() {
|
|
user1 = new Parse.User();
|
|
user1.setUsername('user1');
|
|
user1.setPassword('user1');
|
|
user1.setEmail('user1@user1.user1');
|
|
await user1.signUp();
|
|
|
|
user2 = new Parse.User();
|
|
user2.setUsername('user2');
|
|
user2.setPassword('user2');
|
|
await user2.signUp();
|
|
|
|
user3 = new Parse.User();
|
|
user3.setUsername('user3');
|
|
user3.setPassword('user3');
|
|
await user3.signUp();
|
|
|
|
user4 = new Parse.User();
|
|
user4.setUsername('user4');
|
|
user4.setPassword('user4');
|
|
await user4.signUp();
|
|
|
|
user5 = new Parse.User();
|
|
user5.setUsername('user5');
|
|
user5.setPassword('user5');
|
|
await user5.signUp();
|
|
|
|
const roleACL = new Parse.ACL();
|
|
roleACL.setPublicReadAccess(true);
|
|
role = new Parse.Role();
|
|
role.setName('role');
|
|
role.setACL(roleACL);
|
|
role.getUsers().add(user1);
|
|
role.getUsers().add(user3);
|
|
role = await role.save();
|
|
|
|
const schemaController = await parseServer.config.databaseController.loadSchema();
|
|
try {
|
|
await schemaController.addClassIfNotExists(
|
|
'GraphQLClass',
|
|
{
|
|
someField: { type: 'String' },
|
|
pointerToUser: { type: 'Pointer', targetClass: '_User' },
|
|
},
|
|
{
|
|
find: {
|
|
'role:role': true,
|
|
[user1.id]: true,
|
|
[user2.id]: true,
|
|
},
|
|
create: {
|
|
'role:role': true,
|
|
[user1.id]: true,
|
|
[user2.id]: true,
|
|
},
|
|
get: {
|
|
'role:role': true,
|
|
[user1.id]: true,
|
|
[user2.id]: true,
|
|
},
|
|
update: {
|
|
'role:role': true,
|
|
[user1.id]: true,
|
|
[user2.id]: true,
|
|
},
|
|
addField: {
|
|
'role:role': true,
|
|
[user1.id]: true,
|
|
[user2.id]: true,
|
|
},
|
|
delete: {
|
|
'role:role': true,
|
|
[user1.id]: true,
|
|
[user2.id]: true,
|
|
},
|
|
readUserFields: ['pointerToUser'],
|
|
writeUserFields: ['pointerToUser'],
|
|
},
|
|
{}
|
|
);
|
|
} catch (err) {
|
|
if (
|
|
!(err instanceof Parse.Error) ||
|
|
err.message !== 'Class GraphQLClass already exists.'
|
|
) {
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
object1 = new Parse.Object('GraphQLClass');
|
|
object1.set('someField', 'someValue1');
|
|
object1.set('someOtherField', 'A');
|
|
const object1ACL = new Parse.ACL();
|
|
object1ACL.setPublicReadAccess(false);
|
|
object1ACL.setPublicWriteAccess(false);
|
|
object1ACL.setRoleReadAccess(role, true);
|
|
object1ACL.setRoleWriteAccess(role, true);
|
|
object1ACL.setReadAccess(user1.id, true);
|
|
object1ACL.setWriteAccess(user1.id, true);
|
|
object1ACL.setReadAccess(user2.id, true);
|
|
object1ACL.setWriteAccess(user2.id, true);
|
|
object1.setACL(object1ACL);
|
|
await object1.save(undefined, { useMasterKey: true });
|
|
|
|
object2 = new Parse.Object('GraphQLClass');
|
|
object2.set('someField', 'someValue2');
|
|
object2.set('someOtherField', 'A');
|
|
const object2ACL = new Parse.ACL();
|
|
object2ACL.setPublicReadAccess(false);
|
|
object2ACL.setPublicWriteAccess(false);
|
|
object2ACL.setReadAccess(user1.id, true);
|
|
object2ACL.setWriteAccess(user1.id, true);
|
|
object2ACL.setReadAccess(user2.id, true);
|
|
object2ACL.setWriteAccess(user2.id, true);
|
|
object2ACL.setReadAccess(user5.id, true);
|
|
object2ACL.setWriteAccess(user5.id, true);
|
|
object2.setACL(object2ACL);
|
|
await object2.save(undefined, { useMasterKey: true });
|
|
|
|
object3 = new Parse.Object('GraphQLClass');
|
|
object3.set('someField', 'someValue3');
|
|
object3.set('someOtherField', 'B');
|
|
object3.set('pointerToUser', user5);
|
|
await object3.save(undefined, { useMasterKey: true });
|
|
|
|
object4 = new Parse.Object('PublicClass');
|
|
object4.set('someField', 'someValue4');
|
|
await object4.save();
|
|
|
|
objects = [];
|
|
objects.push(object1, object2, object3, object4);
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
const expressApp = express();
|
|
httpServer = http.createServer(expressApp);
|
|
expressApp.use('/parse', parseServer.app);
|
|
parseLiveQueryServer = ParseServer.createLiveQueryServer(httpServer, {
|
|
port: 1338,
|
|
});
|
|
parseGraphQLServer.applyGraphQL(expressApp);
|
|
parseGraphQLServer.applyPlayground(expressApp);
|
|
parseGraphQLServer.createSubscriptions(httpServer);
|
|
await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve));
|
|
|
|
const subscriptionClient = new SubscriptionClient(
|
|
'ws://localhost:13377/subscriptions',
|
|
{
|
|
reconnect: true,
|
|
connectionParams: headers,
|
|
},
|
|
ws
|
|
);
|
|
const wsLink = new WebSocketLink(subscriptionClient);
|
|
const httpLink = createUploadLink({
|
|
uri: 'http://localhost:13377/graphql',
|
|
fetch,
|
|
headers,
|
|
});
|
|
apolloClient = new ApolloClient({
|
|
link: split(
|
|
({ query }) => {
|
|
const { kind, operation } = getMainDefinition(query);
|
|
return (
|
|
kind === 'OperationDefinition' && operation === 'subscription'
|
|
);
|
|
},
|
|
wsLink,
|
|
httpLink
|
|
),
|
|
cache: new InMemoryCache(),
|
|
defaultOptions: {
|
|
query: {
|
|
fetchPolicy: 'no-cache',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
beforeEach(() => {
|
|
spyOn(console, 'warn').and.callFake(() => {});
|
|
spyOn(console, 'error').and.callFake(() => {});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await parseLiveQueryServer.server.close();
|
|
await httpServer.close();
|
|
});
|
|
|
|
describe('GraphQL', () => {
|
|
it('should be healthy', async () => {
|
|
try {
|
|
const health = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query Health {
|
|
health
|
|
}
|
|
`,
|
|
})
|
|
).data.health;
|
|
expect(health).toBeTruthy();
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should be cors enabled', async () => {
|
|
let checked = false;
|
|
const apolloClient = new ApolloClient({
|
|
link: new ApolloLink((operation, forward) => {
|
|
return forward(operation).map(response => {
|
|
const context = operation.getContext();
|
|
const {
|
|
response: { headers },
|
|
} = context;
|
|
expect(headers.get('access-control-allow-origin')).toEqual('*');
|
|
checked = true;
|
|
return response;
|
|
});
|
|
}).concat(
|
|
createHttpLink({
|
|
uri: 'http://localhost:13377/graphql',
|
|
fetch,
|
|
headers: {
|
|
...headers,
|
|
Origin: 'http://someorigin.com',
|
|
},
|
|
})
|
|
),
|
|
cache: new InMemoryCache(),
|
|
});
|
|
const healthResponse = await apolloClient.query({
|
|
query: gql`
|
|
query Health {
|
|
health
|
|
}
|
|
`,
|
|
});
|
|
expect(healthResponse.data.health).toBeTruthy();
|
|
expect(checked).toBeTruthy();
|
|
});
|
|
|
|
it('should handle Parse headers', async () => {
|
|
let checked = false;
|
|
const originalGetGraphQLOptions = parseGraphQLServer._getGraphQLOptions;
|
|
parseGraphQLServer._getGraphQLOptions = async req => {
|
|
expect(req.info).toBeDefined();
|
|
expect(req.config).toBeDefined();
|
|
expect(req.auth).toBeDefined();
|
|
checked = true;
|
|
return await originalGetGraphQLOptions.bind(parseGraphQLServer)(req);
|
|
};
|
|
const health = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query Health {
|
|
health
|
|
}
|
|
`,
|
|
})
|
|
).data.health;
|
|
expect(health).toBeTruthy();
|
|
expect(checked).toBeTruthy();
|
|
parseGraphQLServer._getGraphQLOptions = originalGetGraphQLOptions;
|
|
});
|
|
});
|
|
|
|
describe('Playground', () => {
|
|
it('should mount playground', async () => {
|
|
const res = await req({
|
|
method: 'GET',
|
|
url: 'http://localhost:13377/playground',
|
|
});
|
|
expect(res.status).toEqual(200);
|
|
});
|
|
});
|
|
|
|
describe('Schema', () => {
|
|
const resetGraphQLCache = async () => {
|
|
await Promise.all([
|
|
parseGraphQLServer.parseGraphQLController.cacheController.graphQL.clear(),
|
|
parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(),
|
|
]);
|
|
};
|
|
|
|
describe('Default Types', () => {
|
|
it('should have Object scalar type', async () => {
|
|
const objectType = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query ObjectType {
|
|
__type(name: "Object") {
|
|
kind
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
expect(objectType.kind).toEqual('SCALAR');
|
|
});
|
|
|
|
it('should have Date scalar type', async () => {
|
|
const dateType = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query DateType {
|
|
__type(name: "Date") {
|
|
kind
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
expect(dateType.kind).toEqual('SCALAR');
|
|
});
|
|
|
|
it('should have ArrayResult type', async () => {
|
|
const arrayResultType = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query ArrayResultType {
|
|
__type(name: "ArrayResult") {
|
|
kind
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
expect(arrayResultType.kind).toEqual('UNION');
|
|
});
|
|
|
|
it('should have File object type', async () => {
|
|
const fileType = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query FileType {
|
|
__type(name: "FileInfo") {
|
|
kind
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
expect(fileType.kind).toEqual('OBJECT');
|
|
expect(fileType.fields.map(field => field.name).sort()).toEqual([
|
|
'name',
|
|
'url',
|
|
]);
|
|
});
|
|
|
|
it('should have Class interface type', async () => {
|
|
const classType = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query ClassType {
|
|
__type(name: "ParseObject") {
|
|
kind
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
expect(classType.kind).toEqual('INTERFACE');
|
|
expect(classType.fields.map(field => field.name).sort()).toEqual([
|
|
'ACL',
|
|
'createdAt',
|
|
'objectId',
|
|
'updatedAt',
|
|
]);
|
|
});
|
|
|
|
it('should have ReadPreference enum type', async () => {
|
|
const readPreferenceType = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query ReadPreferenceType {
|
|
__type(name: "ReadPreference") {
|
|
kind
|
|
enumValues {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
expect(readPreferenceType.kind).toEqual('ENUM');
|
|
expect(
|
|
readPreferenceType.enumValues.map(value => value.name).sort()
|
|
).toEqual([
|
|
'NEAREST',
|
|
'PRIMARY',
|
|
'PRIMARY_PREFERRED',
|
|
'SECONDARY',
|
|
'SECONDARY_PREFERRED',
|
|
]);
|
|
});
|
|
|
|
it('should have GraphQLUpload object type', async () => {
|
|
const graphQLUploadType = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GraphQLUploadType {
|
|
__type(name: "Upload") {
|
|
kind
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
expect(graphQLUploadType.kind).toEqual('SCALAR');
|
|
});
|
|
|
|
it('should have all expected types', async () => {
|
|
const schemaTypes = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query SchemaTypes {
|
|
__schema {
|
|
types {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__schema'].types.map(type => type.name);
|
|
|
|
const expectedTypes = [
|
|
'ParseObject',
|
|
'Date',
|
|
'FileInfo',
|
|
'ReadPreference',
|
|
'Upload',
|
|
];
|
|
expect(
|
|
expectedTypes.every(type => schemaTypes.indexOf(type) !== -1)
|
|
).toBeTruthy(JSON.stringify(schemaTypes.types));
|
|
});
|
|
});
|
|
|
|
describe('Relay Specific Types', () => {
|
|
beforeAll(async () => {
|
|
await resetGraphQLCache();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await resetGraphQLCache();
|
|
});
|
|
|
|
it('should have Node interface', async () => {
|
|
const schemaTypes = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query SchemaTypes {
|
|
__schema {
|
|
types {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__schema'].types.map(type => type.name);
|
|
|
|
expect(schemaTypes).toContain('Node');
|
|
});
|
|
|
|
it('should have node query', async () => {
|
|
const queryFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query UserType {
|
|
__type(name: "Query") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields.map(field => field.name);
|
|
|
|
expect(queryFields).toContain('node');
|
|
});
|
|
|
|
it('should return global id', async () => {
|
|
const userFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query UserType {
|
|
__type(name: "User") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields.map(field => field.name);
|
|
|
|
expect(userFields).toContain('id');
|
|
expect(userFields).toContain('objectId');
|
|
});
|
|
|
|
it('should have clientMutationId in create file input', async () => {
|
|
const createFileInputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "CreateFileInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(createFileInputFields).toEqual(['clientMutationId', 'upload']);
|
|
});
|
|
|
|
it('should have clientMutationId in create file payload', async () => {
|
|
const createFilePayloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "CreateFilePayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(createFilePayloadFields).toEqual([
|
|
'clientMutationId',
|
|
'fileInfo',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in call function input', async () => {
|
|
Parse.Cloud.define('hello', () => {});
|
|
|
|
const callFunctionInputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "CallCloudCodeInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(callFunctionInputFields).toEqual([
|
|
'clientMutationId',
|
|
'functionName',
|
|
'params',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in call function payload', async () => {
|
|
Parse.Cloud.define('hello', () => {});
|
|
|
|
const callFunctionPayloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "CallCloudCodePayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(callFunctionPayloadFields).toEqual([
|
|
'clientMutationId',
|
|
'result',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in sign up mutation input', async () => {
|
|
const inputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "SignUpInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(inputFields).toEqual(['clientMutationId', 'userFields']);
|
|
});
|
|
|
|
it('should have clientMutationId in sign up mutation payload', async () => {
|
|
const payloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "SignUpPayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(payloadFields).toEqual(['clientMutationId', 'viewer']);
|
|
});
|
|
|
|
it('should have clientMutationId in log in mutation input', async () => {
|
|
const inputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "LogInInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(inputFields).toEqual([
|
|
'clientMutationId',
|
|
'password',
|
|
'username',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in log in mutation payload', async () => {
|
|
const payloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "LogInPayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(payloadFields).toEqual(['clientMutationId', 'viewer']);
|
|
});
|
|
|
|
it('should have clientMutationId in log out mutation input', async () => {
|
|
const inputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "LogOutInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(inputFields).toEqual(['clientMutationId']);
|
|
});
|
|
|
|
it('should have clientMutationId in log out mutation payload', async () => {
|
|
const payloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "LogOutPayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(payloadFields).toEqual(['clientMutationId', 'viewer']);
|
|
});
|
|
|
|
it('should have clientMutationId in createClass mutation input', async () => {
|
|
const inputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "CreateClassInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(inputFields).toEqual([
|
|
'clientMutationId',
|
|
'name',
|
|
'schemaFields',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in createClass mutation payload', async () => {
|
|
const payloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "CreateClassPayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(payloadFields).toEqual(['class', 'clientMutationId']);
|
|
});
|
|
|
|
it('should have clientMutationId in updateClass mutation input', async () => {
|
|
const inputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "UpdateClassInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(inputFields).toEqual([
|
|
'clientMutationId',
|
|
'name',
|
|
'schemaFields',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in updateClass mutation payload', async () => {
|
|
const payloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "UpdateClassPayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(payloadFields).toEqual(['class', 'clientMutationId']);
|
|
});
|
|
|
|
it('should have clientMutationId in deleteClass mutation input', async () => {
|
|
const inputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "DeleteClassInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(inputFields).toEqual(['clientMutationId', 'name']);
|
|
});
|
|
|
|
it('should have clientMutationId in deleteClass mutation payload', async () => {
|
|
const payloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "UpdateClassPayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(payloadFields).toEqual(['class', 'clientMutationId']);
|
|
});
|
|
|
|
it('should have clientMutationId in custom create object mutation input', async () => {
|
|
const obj = new Parse.Object('SomeClass');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createObjectInputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "CreateSomeClassInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(createObjectInputFields).toEqual([
|
|
'clientMutationId',
|
|
'fields',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in custom create object mutation payload', async () => {
|
|
const obj = new Parse.Object('SomeClass');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createObjectPayloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "CreateSomeClassPayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(createObjectPayloadFields).toEqual([
|
|
'clientMutationId',
|
|
'someClass',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in custom update object mutation input', async () => {
|
|
const obj = new Parse.Object('SomeClass');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createObjectInputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "UpdateSomeClassInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(createObjectInputFields).toEqual([
|
|
'clientMutationId',
|
|
'fields',
|
|
'id',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in custom update object mutation payload', async () => {
|
|
const obj = new Parse.Object('SomeClass');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createObjectPayloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "UpdateSomeClassPayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(createObjectPayloadFields).toEqual([
|
|
'clientMutationId',
|
|
'someClass',
|
|
]);
|
|
});
|
|
|
|
it('should have clientMutationId in custom delete object mutation input', async () => {
|
|
const obj = new Parse.Object('SomeClass');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createObjectInputFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "DeleteSomeClassInput") {
|
|
inputFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].inputFields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(createObjectInputFields).toEqual(['clientMutationId', 'id']);
|
|
});
|
|
|
|
it('should have clientMutationId in custom delete object mutation payload', async () => {
|
|
const obj = new Parse.Object('SomeClass');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createObjectPayloadFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
__type(name: "DeleteSomeClassPayload") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields
|
|
.map(field => field.name)
|
|
.sort();
|
|
|
|
expect(createObjectPayloadFields).toEqual([
|
|
'clientMutationId',
|
|
'someClass',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('Parse Class Types', () => {
|
|
it('should have all expected types', async () => {
|
|
await parseServer.config.databaseController.loadSchema();
|
|
|
|
const schemaTypes = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query SchemaTypes {
|
|
__schema {
|
|
types {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__schema'].types.map(type => type.name);
|
|
|
|
const expectedTypes = [
|
|
'Role',
|
|
'RoleWhereInput',
|
|
'CreateRoleFieldsInput',
|
|
'UpdateRoleFieldsInput',
|
|
'RoleConnection',
|
|
'User',
|
|
'UserWhereInput',
|
|
'UserConnection',
|
|
'CreateUserFieldsInput',
|
|
'UpdateUserFieldsInput',
|
|
];
|
|
expect(
|
|
expectedTypes.every(type => schemaTypes.indexOf(type) !== -1)
|
|
).toBeTruthy(JSON.stringify(schemaTypes));
|
|
});
|
|
|
|
it('should ArrayResult contains all types', async () => {
|
|
const objectType = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query ObjectType {
|
|
__type(name: "ArrayResult") {
|
|
kind
|
|
possibleTypes {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
const possibleTypes = objectType.possibleTypes.map(o => o.name);
|
|
expect(possibleTypes).toContain('User');
|
|
expect(possibleTypes).toContain('Role');
|
|
expect(possibleTypes).toContain('Element');
|
|
});
|
|
|
|
it('should update schema when it changes', async () => {
|
|
const schemaController = await parseServer.config.databaseController.loadSchema();
|
|
await schemaController.updateClass('_User', {
|
|
foo: { type: 'String' },
|
|
});
|
|
|
|
const userFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query UserType {
|
|
__type(name: "User") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields.map(field => field.name);
|
|
expect(userFields.indexOf('foo') !== -1).toBeTruthy();
|
|
});
|
|
|
|
it('should not contain password field from _User class', async () => {
|
|
const userFields = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query UserType {
|
|
__type(name: "User") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'].fields.map(field => field.name);
|
|
expect(userFields.includes('password')).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe('Configuration', function() {
|
|
const resetGraphQLCache = async () => {
|
|
await Promise.all([
|
|
parseGraphQLServer.parseGraphQLController.cacheController.graphQL.clear(),
|
|
parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(),
|
|
]);
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
await parseGraphQLServer.setGraphQLConfig({});
|
|
await resetGraphQLCache();
|
|
});
|
|
|
|
it('should only include types in the enabledForClasses list', async () => {
|
|
const schemaController = await parseServer.config.databaseController.loadSchema();
|
|
await schemaController.addClassIfNotExists('SuperCar', {
|
|
foo: { type: 'String' },
|
|
});
|
|
|
|
const graphQLConfig = {
|
|
enabledForClasses: ['SuperCar'],
|
|
};
|
|
await parseGraphQLServer.setGraphQLConfig(graphQLConfig);
|
|
await resetGraphQLCache();
|
|
|
|
const { data } = await apolloClient.query({
|
|
query: gql`
|
|
query UserType {
|
|
userType: __type(name: "User") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
superCarType: __type(name: "SuperCar") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
expect(data.userType).toBeNull();
|
|
expect(data.superCarType).toBeTruthy();
|
|
});
|
|
it('should not include types in the disabledForClasses list', async () => {
|
|
const schemaController = await parseServer.config.databaseController.loadSchema();
|
|
await schemaController.addClassIfNotExists('SuperCar', {
|
|
foo: { type: 'String' },
|
|
});
|
|
|
|
const graphQLConfig = {
|
|
disabledForClasses: ['SuperCar'],
|
|
};
|
|
await parseGraphQLServer.setGraphQLConfig(graphQLConfig);
|
|
await resetGraphQLCache();
|
|
|
|
const { data } = await apolloClient.query({
|
|
query: gql`
|
|
query UserType {
|
|
userType: __type(name: "User") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
superCarType: __type(name: "SuperCar") {
|
|
fields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
expect(data.superCarType).toBeNull();
|
|
expect(data.userType).toBeTruthy();
|
|
});
|
|
it('should remove query operations when disabled', async () => {
|
|
const superCar = new Parse.Object('SuperCar');
|
|
await superCar.save({ foo: 'bar' });
|
|
const customer = new Parse.Object('Customer');
|
|
await customer.save({ foo: 'bar' });
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query GetSuperCar($id: ID!) {
|
|
superCar(id: $id) {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCar.id,
|
|
},
|
|
})
|
|
).toBeResolved();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindCustomer {
|
|
customers {
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeResolved();
|
|
|
|
const graphQLConfig = {
|
|
classConfigs: [
|
|
{
|
|
className: 'SuperCar',
|
|
query: {
|
|
get: false,
|
|
find: true,
|
|
},
|
|
},
|
|
{
|
|
className: 'Customer',
|
|
query: {
|
|
get: true,
|
|
find: false,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
await parseGraphQLServer.setGraphQLConfig(graphQLConfig);
|
|
await resetGraphQLCache();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query GetSuperCar($id: ID!) {
|
|
superCar(id: $id) {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCar.id,
|
|
},
|
|
})
|
|
).toBeRejected();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query GetCustomer($id: ID!) {
|
|
customer(id: $id) {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: customer.id,
|
|
},
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars {
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindCustomer {
|
|
customers {
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeRejected();
|
|
});
|
|
|
|
it('should remove mutation operations, create, update and delete, when disabled', async () => {
|
|
const superCar1 = new Parse.Object('SuperCar');
|
|
await superCar1.save({ foo: 'bar' });
|
|
const customer1 = new Parse.Object('Customer');
|
|
await customer1.save({ foo: 'bar' });
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
mutation UpdateSuperCar($id: ID!, $foo: String!) {
|
|
updateSuperCar(input: { id: $id, fields: { foo: $foo } }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCar1.id,
|
|
foo: 'lah',
|
|
},
|
|
})
|
|
).toBeResolved();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
mutation DeleteCustomer($id: ID!) {
|
|
deleteCustomer(input: { id: $id }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: customer1.id,
|
|
},
|
|
})
|
|
).toBeResolved();
|
|
|
|
const { data: customerData } = await apolloClient.query({
|
|
query: gql`
|
|
mutation CreateCustomer($foo: String!) {
|
|
createCustomer(input: { fields: { foo: $foo } }) {
|
|
customer {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
foo: 'rah',
|
|
},
|
|
});
|
|
expect(customerData.createCustomer.customer).toBeTruthy();
|
|
|
|
// used later
|
|
const customer2Id = customerData.createCustomer.customer.id;
|
|
|
|
await parseGraphQLServer.setGraphQLConfig({
|
|
classConfigs: [
|
|
{
|
|
className: 'SuperCar',
|
|
mutation: {
|
|
create: true,
|
|
update: false,
|
|
destroy: true,
|
|
},
|
|
},
|
|
{
|
|
className: 'Customer',
|
|
mutation: {
|
|
create: false,
|
|
update: true,
|
|
destroy: false,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
await resetGraphQLCache();
|
|
|
|
const { data: superCarData } = await apolloClient.query({
|
|
query: gql`
|
|
mutation CreateSuperCar($foo: String!) {
|
|
createSuperCar(input: { fields: { foo: $foo } }) {
|
|
superCar {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
foo: 'mah',
|
|
},
|
|
});
|
|
expect(superCarData.createSuperCar).toBeTruthy();
|
|
const superCar3Id = superCarData.createSuperCar.superCar.id;
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
mutation UpdateSupercar($id: ID!, $foo: String!) {
|
|
updateSuperCar(input: { id: $id, fields: { foo: $foo } }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCar3Id,
|
|
},
|
|
})
|
|
).toBeRejected();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
mutation DeleteSuperCar($id: ID!) {
|
|
deleteSuperCar(input: { id: $id }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCar3Id,
|
|
},
|
|
})
|
|
).toBeResolved();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
mutation CreateCustomer($foo: String!) {
|
|
createCustomer(input: { fields: { foo: $foo } }) {
|
|
customer {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
foo: 'rah',
|
|
},
|
|
})
|
|
).toBeRejected();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
mutation UpdateCustomer($id: ID!, $foo: String!) {
|
|
updateCustomer(input: { id: $id, fields: { foo: $foo } }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: customer2Id,
|
|
foo: 'tah',
|
|
},
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
mutation DeleteCustomer($id: ID!, $foo: String!) {
|
|
deleteCustomer(input: { id: $id }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: customer2Id,
|
|
},
|
|
})
|
|
).toBeRejected();
|
|
});
|
|
|
|
it('should only allow the supplied create and update fields for a class', async () => {
|
|
const schemaController = await parseServer.config.databaseController.loadSchema();
|
|
await schemaController.addClassIfNotExists('SuperCar', {
|
|
engine: { type: 'String' },
|
|
doors: { type: 'Number' },
|
|
price: { type: 'String' },
|
|
mileage: { type: 'Number' },
|
|
});
|
|
|
|
await parseGraphQLServer.setGraphQLConfig({
|
|
classConfigs: [
|
|
{
|
|
className: 'SuperCar',
|
|
type: {
|
|
inputFields: {
|
|
create: ['engine', 'doors', 'price'],
|
|
update: ['price', 'mileage'],
|
|
},
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
await resetGraphQLCache();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
mutation InvalidCreateSuperCar {
|
|
createSuperCar(
|
|
input: { fields: { engine: "diesel", mileage: 1000 } }
|
|
) {
|
|
superCar {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeRejected();
|
|
const { id: superCarId } = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
mutation ValidCreateSuperCar {
|
|
createSuperCar(
|
|
input: {
|
|
fields: { engine: "diesel", doors: 5, price: "£10000" }
|
|
}
|
|
) {
|
|
superCar {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data.createSuperCar.superCar;
|
|
|
|
expect(superCarId).toBeTruthy();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
mutation InvalidUpdateSuperCar($id: ID!) {
|
|
updateSuperCar(
|
|
input: { id: $id, fields: { engine: "petrol" } }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCarId,
|
|
},
|
|
})
|
|
).toBeRejected();
|
|
|
|
const updatedSuperCar = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
mutation ValidUpdateSuperCar($id: ID!) {
|
|
updateSuperCar(
|
|
input: { id: $id, fields: { mileage: 2000 } }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCarId,
|
|
},
|
|
})
|
|
).data.updateSuperCar;
|
|
expect(updatedSuperCar).toBeTruthy();
|
|
});
|
|
|
|
it('should handle required fields from the Parse class', async () => {
|
|
const schemaController = await parseServer.config.databaseController.loadSchema();
|
|
await schemaController.addClassIfNotExists('SuperCar', {
|
|
engine: { type: 'String', required: true },
|
|
doors: { type: 'Number', required: true },
|
|
price: { type: 'String' },
|
|
mileage: { type: 'Number' },
|
|
});
|
|
|
|
await resetGraphQLCache();
|
|
|
|
const {
|
|
data: { __type },
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query requiredFields {
|
|
__type(name: "CreateSuperCarFieldsInput") {
|
|
inputFields {
|
|
name
|
|
type {
|
|
kind
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
expect(
|
|
__type.inputFields.find(o => o.name === 'price').type.kind
|
|
).toEqual('SCALAR');
|
|
expect(
|
|
__type.inputFields.find(o => o.name === 'engine').type.kind
|
|
).toEqual('NON_NULL');
|
|
expect(
|
|
__type.inputFields.find(o => o.name === 'doors').type.kind
|
|
).toEqual('NON_NULL');
|
|
|
|
const {
|
|
data: { __type: __type2 },
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query requiredFields {
|
|
__type(name: "SuperCar") {
|
|
fields {
|
|
name
|
|
type {
|
|
kind
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
expect(
|
|
__type2.fields.find(o => o.name === 'price').type.kind
|
|
).toEqual('SCALAR');
|
|
expect(
|
|
__type2.fields.find(o => o.name === 'engine').type.kind
|
|
).toEqual('NON_NULL');
|
|
expect(
|
|
__type2.fields.find(o => o.name === 'doors').type.kind
|
|
).toEqual('NON_NULL');
|
|
});
|
|
|
|
it('should only allow the supplied output fields for a class', async () => {
|
|
const schemaController = await parseServer.config.databaseController.loadSchema();
|
|
|
|
await schemaController.addClassIfNotExists('SuperCar', {
|
|
engine: { type: 'String' },
|
|
doors: { type: 'Number' },
|
|
price: { type: 'String' },
|
|
mileage: { type: 'Number' },
|
|
insuranceClaims: { type: 'Number' },
|
|
});
|
|
|
|
const superCar = await new Parse.Object('SuperCar').save({
|
|
engine: 'petrol',
|
|
doors: 3,
|
|
price: '£7500',
|
|
mileage: 0,
|
|
insuranceCertificate: 'private-file.pdf',
|
|
});
|
|
|
|
await parseGraphQLServer.setGraphQLConfig({
|
|
classConfigs: [
|
|
{
|
|
className: 'SuperCar',
|
|
type: {
|
|
outputFields: ['engine', 'doors', 'price', 'mileage'],
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
await resetGraphQLCache();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query GetSuperCar($id: ID!) {
|
|
superCar(id: $id) {
|
|
id
|
|
objectId
|
|
engine
|
|
doors
|
|
price
|
|
mileage
|
|
insuranceCertificate
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCar.id,
|
|
},
|
|
})
|
|
).toBeRejected();
|
|
let getSuperCar = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetSuperCar($id: ID!) {
|
|
superCar(id: $id) {
|
|
id
|
|
objectId
|
|
engine
|
|
doors
|
|
price
|
|
mileage
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCar.id,
|
|
},
|
|
})
|
|
).data.superCar;
|
|
expect(getSuperCar).toBeTruthy();
|
|
|
|
await parseGraphQLServer.setGraphQLConfig({
|
|
classConfigs: [
|
|
{
|
|
className: 'SuperCar',
|
|
type: {
|
|
outputFields: [],
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
await resetGraphQLCache();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query GetSuperCar($id: ID!) {
|
|
superCar(id: $id) {
|
|
engine
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCar.id,
|
|
},
|
|
})
|
|
).toBeRejected();
|
|
getSuperCar = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetSuperCar($id: ID!) {
|
|
superCar(id: $id) {
|
|
id
|
|
objectId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: superCar.id,
|
|
},
|
|
})
|
|
).data.superCar;
|
|
expect(getSuperCar.objectId).toBe(superCar.id);
|
|
});
|
|
|
|
it('should only allow the supplied constraint fields for a class', async () => {
|
|
try {
|
|
const schemaController = await parseServer.config.databaseController.loadSchema();
|
|
|
|
await schemaController.addClassIfNotExists('SuperCar', {
|
|
model: { type: 'String' },
|
|
engine: { type: 'String' },
|
|
doors: { type: 'Number' },
|
|
price: { type: 'String' },
|
|
mileage: { type: 'Number' },
|
|
insuranceCertificate: { type: 'String' },
|
|
});
|
|
|
|
await new Parse.Object('SuperCar').save({
|
|
model: 'McLaren',
|
|
engine: 'petrol',
|
|
doors: 3,
|
|
price: '£7500',
|
|
mileage: 0,
|
|
insuranceCertificate: 'private-file.pdf',
|
|
});
|
|
|
|
await parseGraphQLServer.setGraphQLConfig({
|
|
classConfigs: [
|
|
{
|
|
className: 'SuperCar',
|
|
type: {
|
|
constraintFields: ['engine', 'doors', 'price'],
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
await resetGraphQLCache();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(
|
|
where: {
|
|
insuranceCertificate: { equalTo: "private-file.pdf" }
|
|
}
|
|
) {
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeRejected();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(where: { mileage: { equalTo: 0 } }) {
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeRejected();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(where: { engine: { equalTo: "petrol" } }) {
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeResolved();
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should only allow the supplied sort fields for a class', async () => {
|
|
const schemaController = await parseServer.config.databaseController.loadSchema();
|
|
|
|
await schemaController.addClassIfNotExists('SuperCar', {
|
|
engine: { type: 'String' },
|
|
doors: { type: 'Number' },
|
|
price: { type: 'String' },
|
|
mileage: { type: 'Number' },
|
|
});
|
|
|
|
await new Parse.Object('SuperCar').save({
|
|
engine: 'petrol',
|
|
doors: 3,
|
|
price: '£7500',
|
|
mileage: 0,
|
|
});
|
|
|
|
await parseGraphQLServer.setGraphQLConfig({
|
|
classConfigs: [
|
|
{
|
|
className: 'SuperCar',
|
|
type: {
|
|
sortFields: [
|
|
{
|
|
field: 'doors',
|
|
asc: true,
|
|
desc: true,
|
|
},
|
|
{
|
|
field: 'price',
|
|
asc: true,
|
|
desc: true,
|
|
},
|
|
{
|
|
field: 'mileage',
|
|
asc: true,
|
|
desc: false,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
await resetGraphQLCache();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(order: [engine_ASC]) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeRejected();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(order: [engine_DESC]) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeRejected();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(order: [mileage_DESC]) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeRejected();
|
|
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(order: [mileage_ASC]) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(order: [doors_ASC]) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(order: [price_DESC]) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
apolloClient.query({
|
|
query: gql`
|
|
query FindSuperCar {
|
|
superCars(order: [price_ASC, doors_DESC]) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).toBeResolved();
|
|
});
|
|
});
|
|
|
|
describe('Relay Spec', () => {
|
|
beforeAll(async () => {
|
|
await resetGraphQLCache();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await resetGraphQLCache();
|
|
});
|
|
|
|
describe('Object Identification', () => {
|
|
it('Class get custom method should return valid gobal id', async () => {
|
|
const obj = new Parse.Object('SomeClass');
|
|
obj.set('someField', 'some value');
|
|
await obj.save();
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeClass($objectId: ID!) {
|
|
someClass(id: $objectId) {
|
|
id
|
|
objectId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
objectId: obj.id,
|
|
},
|
|
});
|
|
|
|
expect(getResult.data.someClass.objectId).toBe(obj.id);
|
|
|
|
const nodeResult = await apolloClient.query({
|
|
query: gql`
|
|
query Node($id: ID!) {
|
|
node(id: $id) {
|
|
id
|
|
... on SomeClass {
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: getResult.data.someClass.id,
|
|
},
|
|
});
|
|
|
|
expect(nodeResult.data.node.id).toBe(getResult.data.someClass.id);
|
|
expect(nodeResult.data.node.objectId).toBe(obj.id);
|
|
expect(nodeResult.data.node.someField).toBe('some value');
|
|
});
|
|
|
|
it('Class find custom method should return valid gobal id', async () => {
|
|
const obj1 = new Parse.Object('SomeClass');
|
|
obj1.set('someField', 'some value 1');
|
|
await obj1.save();
|
|
|
|
const obj2 = new Parse.Object('SomeClass');
|
|
obj2.set('someField', 'some value 2');
|
|
await obj2.save();
|
|
|
|
const findResult = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeClass {
|
|
someClasses(order: [createdAt_ASC]) {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
|
|
expect(findResult.data.someClasses.edges[0].node.objectId).toBe(
|
|
obj1.id
|
|
);
|
|
expect(findResult.data.someClasses.edges[1].node.objectId).toBe(
|
|
obj2.id
|
|
);
|
|
|
|
const nodeResult = await apolloClient.query({
|
|
query: gql`
|
|
query Node($id1: ID!, $id2: ID!) {
|
|
node1: node(id: $id1) {
|
|
id
|
|
... on SomeClass {
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
node2: node(id: $id2) {
|
|
id
|
|
... on SomeClass {
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id1: findResult.data.someClasses.edges[0].node.id,
|
|
id2: findResult.data.someClasses.edges[1].node.id,
|
|
},
|
|
});
|
|
|
|
expect(nodeResult.data.node1.id).toBe(
|
|
findResult.data.someClasses.edges[0].node.id
|
|
);
|
|
expect(nodeResult.data.node1.objectId).toBe(obj1.id);
|
|
expect(nodeResult.data.node1.someField).toBe('some value 1');
|
|
expect(nodeResult.data.node2.id).toBe(
|
|
findResult.data.someClasses.edges[1].node.id
|
|
);
|
|
expect(nodeResult.data.node2.objectId).toBe(obj2.id);
|
|
expect(nodeResult.data.node2.someField).toBe('some value 2');
|
|
});
|
|
|
|
it_only_db('mongo')(
|
|
'Id inputs should work either with global id or object id',
|
|
async () => {
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClasses {
|
|
secondaryObject: createClass(
|
|
input: {
|
|
name: "SecondaryObject"
|
|
schemaFields: { addStrings: [{ name: "someField" }] }
|
|
}
|
|
) {
|
|
clientMutationId
|
|
}
|
|
primaryObject: createClass(
|
|
input: {
|
|
name: "PrimaryObject"
|
|
schemaFields: {
|
|
addStrings: [{ name: "stringField" }]
|
|
addArrays: [{ name: "arrayField" }]
|
|
addPointers: [
|
|
{
|
|
name: "pointerField"
|
|
targetClassName: "SecondaryObject"
|
|
}
|
|
]
|
|
addRelations: [
|
|
{
|
|
name: "relationField"
|
|
targetClassName: "SecondaryObject"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await resetGraphQLCache();
|
|
|
|
const createSecondaryObjectsResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSecondaryObjects {
|
|
secondaryObject1: createSecondaryObject(
|
|
input: { fields: { someField: "some value 1" } }
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
secondaryObject2: createSecondaryObject(
|
|
input: { fields: { someField: "some value 2" } }
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
someField
|
|
}
|
|
}
|
|
secondaryObject3: createSecondaryObject(
|
|
input: { fields: { someField: "some value 3" } }
|
|
) {
|
|
secondaryObject {
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
secondaryObject4: createSecondaryObject(
|
|
input: { fields: { someField: "some value 4" } }
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
objectId
|
|
}
|
|
}
|
|
secondaryObject5: createSecondaryObject(
|
|
input: { fields: { someField: "some value 5" } }
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
}
|
|
}
|
|
secondaryObject6: createSecondaryObject(
|
|
input: { fields: { someField: "some value 6" } }
|
|
) {
|
|
secondaryObject {
|
|
objectId
|
|
}
|
|
}
|
|
secondaryObject7: createSecondaryObject(
|
|
input: { fields: { someField: "some value 7" } }
|
|
) {
|
|
secondaryObject {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
const updateSecondaryObjectsResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation UpdateSecondaryObjects(
|
|
$id1: ID!
|
|
$id2: ID!
|
|
$id3: ID!
|
|
$id4: ID!
|
|
$id5: ID!
|
|
$id6: ID!
|
|
) {
|
|
secondaryObject1: updateSecondaryObject(
|
|
input: {
|
|
id: $id1
|
|
fields: { someField: "some value 11" }
|
|
}
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
secondaryObject2: updateSecondaryObject(
|
|
input: {
|
|
id: $id2
|
|
fields: { someField: "some value 22" }
|
|
}
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
someField
|
|
}
|
|
}
|
|
secondaryObject3: updateSecondaryObject(
|
|
input: {
|
|
id: $id3
|
|
fields: { someField: "some value 33" }
|
|
}
|
|
) {
|
|
secondaryObject {
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
secondaryObject4: updateSecondaryObject(
|
|
input: {
|
|
id: $id4
|
|
fields: { someField: "some value 44" }
|
|
}
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
objectId
|
|
}
|
|
}
|
|
secondaryObject5: updateSecondaryObject(
|
|
input: {
|
|
id: $id5
|
|
fields: { someField: "some value 55" }
|
|
}
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
}
|
|
}
|
|
secondaryObject6: updateSecondaryObject(
|
|
input: {
|
|
id: $id6
|
|
fields: { someField: "some value 66" }
|
|
}
|
|
) {
|
|
secondaryObject {
|
|
objectId
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id1:
|
|
createSecondaryObjectsResult.data.secondaryObject1
|
|
.secondaryObject.id,
|
|
id2:
|
|
createSecondaryObjectsResult.data.secondaryObject2
|
|
.secondaryObject.id,
|
|
id3:
|
|
createSecondaryObjectsResult.data.secondaryObject3
|
|
.secondaryObject.objectId,
|
|
id4:
|
|
createSecondaryObjectsResult.data.secondaryObject4
|
|
.secondaryObject.objectId,
|
|
id5:
|
|
createSecondaryObjectsResult.data.secondaryObject5
|
|
.secondaryObject.id,
|
|
id6:
|
|
createSecondaryObjectsResult.data.secondaryObject6
|
|
.secondaryObject.objectId,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
const deleteSecondaryObjectsResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation DeleteSecondaryObjects(
|
|
$id1: ID!
|
|
$id3: ID!
|
|
$id5: ID!
|
|
$id6: ID!
|
|
) {
|
|
secondaryObject1: deleteSecondaryObject(
|
|
input: { id: $id1 }
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
secondaryObject3: deleteSecondaryObject(
|
|
input: { id: $id3 }
|
|
) {
|
|
secondaryObject {
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
secondaryObject5: deleteSecondaryObject(
|
|
input: { id: $id5 }
|
|
) {
|
|
secondaryObject {
|
|
id
|
|
}
|
|
}
|
|
secondaryObject6: deleteSecondaryObject(
|
|
input: { id: $id6 }
|
|
) {
|
|
secondaryObject {
|
|
objectId
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id1:
|
|
updateSecondaryObjectsResult.data.secondaryObject1
|
|
.secondaryObject.id,
|
|
id3:
|
|
updateSecondaryObjectsResult.data.secondaryObject3
|
|
.secondaryObject.objectId,
|
|
id5:
|
|
updateSecondaryObjectsResult.data.secondaryObject5
|
|
.secondaryObject.id,
|
|
id6:
|
|
updateSecondaryObjectsResult.data.secondaryObject6
|
|
.secondaryObject.objectId,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
const getSecondaryObjectsResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSecondaryObjects($id2: ID!, $id4: ID!) {
|
|
secondaryObject2: secondaryObject(id: $id2) {
|
|
id
|
|
objectId
|
|
someField
|
|
}
|
|
secondaryObject4: secondaryObject(id: $id4) {
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id2:
|
|
updateSecondaryObjectsResult.data.secondaryObject2
|
|
.secondaryObject.id,
|
|
id4:
|
|
updateSecondaryObjectsResult.data.secondaryObject4
|
|
.secondaryObject.objectId,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
const findSecondaryObjectsResult = await apolloClient.query({
|
|
query: gql`
|
|
query FindSecondaryObjects(
|
|
$id1: ID!
|
|
$id2: ID!
|
|
$id3: ID!
|
|
$id4: ID!
|
|
$id5: ID!
|
|
$id6: ID!
|
|
) {
|
|
secondaryObjects(
|
|
where: {
|
|
AND: [
|
|
{
|
|
OR: [
|
|
{ id: { equalTo: $id2 } }
|
|
{
|
|
AND: [
|
|
{ id: { equalTo: $id4 } }
|
|
{ objectId: { equalTo: $id4 } }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
{ id: { notEqualTo: $id1 } }
|
|
{ id: { notEqualTo: $id3 } }
|
|
{ objectId: { notEqualTo: $id2 } }
|
|
{ objectId: { notIn: [$id5, $id6] } }
|
|
{ id: { in: [$id2, $id4] } }
|
|
]
|
|
}
|
|
order: [id_ASC, objectId_ASC]
|
|
) {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id1:
|
|
deleteSecondaryObjectsResult.data.secondaryObject1
|
|
.secondaryObject.objectId,
|
|
id2: getSecondaryObjectsResult.data.secondaryObject2.id,
|
|
id3:
|
|
deleteSecondaryObjectsResult.data.secondaryObject3
|
|
.secondaryObject.objectId,
|
|
id4:
|
|
getSecondaryObjectsResult.data.secondaryObject4.objectId,
|
|
id5:
|
|
deleteSecondaryObjectsResult.data.secondaryObject5
|
|
.secondaryObject.id,
|
|
id6:
|
|
deleteSecondaryObjectsResult.data.secondaryObject6
|
|
.secondaryObject.objectId,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(
|
|
findSecondaryObjectsResult.data.secondaryObjects.count
|
|
).toEqual(2);
|
|
expect(
|
|
findSecondaryObjectsResult.data.secondaryObjects.edges
|
|
.map(value => value.node.someField)
|
|
.sort()
|
|
).toEqual(['some value 22', 'some value 44']);
|
|
expect(
|
|
findSecondaryObjectsResult.data.secondaryObjects.edges[0].node
|
|
.id
|
|
).toBeLessThan(
|
|
findSecondaryObjectsResult.data.secondaryObjects.edges[1].node
|
|
.id
|
|
);
|
|
expect(
|
|
findSecondaryObjectsResult.data.secondaryObjects.edges[0].node
|
|
.objectId
|
|
).toBeLessThan(
|
|
findSecondaryObjectsResult.data.secondaryObjects.edges[1].node
|
|
.objectId
|
|
);
|
|
|
|
const createPrimaryObjectResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreatePrimaryObject(
|
|
$pointer: Any
|
|
$secondaryObject2: ID!
|
|
$secondaryObject4: ID!
|
|
) {
|
|
createPrimaryObject(
|
|
input: {
|
|
fields: {
|
|
stringField: "some value"
|
|
arrayField: [1, "abc", $pointer]
|
|
pointerField: { link: $secondaryObject2 }
|
|
relationField: {
|
|
add: [$secondaryObject2, $secondaryObject4]
|
|
}
|
|
}
|
|
}
|
|
) {
|
|
primaryObject {
|
|
id
|
|
stringField
|
|
arrayField {
|
|
... on Element {
|
|
value
|
|
}
|
|
... on SecondaryObject {
|
|
someField
|
|
}
|
|
}
|
|
pointerField {
|
|
id
|
|
objectId
|
|
someField
|
|
}
|
|
relationField {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
pointer: {
|
|
__type: 'Pointer',
|
|
className: 'SecondaryObject',
|
|
objectId:
|
|
getSecondaryObjectsResult.data.secondaryObject4
|
|
.objectId,
|
|
},
|
|
secondaryObject2:
|
|
getSecondaryObjectsResult.data.secondaryObject2.id,
|
|
secondaryObject4:
|
|
getSecondaryObjectsResult.data.secondaryObject4.objectId,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
const updatePrimaryObjectResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation UpdatePrimaryObject(
|
|
$id: ID!
|
|
$secondaryObject2: ID!
|
|
$secondaryObject4: ID!
|
|
) {
|
|
updatePrimaryObject(
|
|
input: {
|
|
id: $id
|
|
fields: {
|
|
pointerField: { link: $secondaryObject4 }
|
|
relationField: {
|
|
remove: [$secondaryObject2, $secondaryObject4]
|
|
}
|
|
}
|
|
}
|
|
) {
|
|
primaryObject {
|
|
id
|
|
stringField
|
|
arrayField {
|
|
... on Element {
|
|
value
|
|
}
|
|
... on SecondaryObject {
|
|
someField
|
|
}
|
|
}
|
|
pointerField {
|
|
id
|
|
objectId
|
|
someField
|
|
}
|
|
relationField {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id:
|
|
createPrimaryObjectResult.data.createPrimaryObject
|
|
.primaryObject.id,
|
|
secondaryObject2:
|
|
getSecondaryObjectsResult.data.secondaryObject2.id,
|
|
secondaryObject4:
|
|
getSecondaryObjectsResult.data.secondaryObject4.objectId,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(
|
|
createPrimaryObjectResult.data.createPrimaryObject
|
|
.primaryObject.stringField
|
|
).toEqual('some value');
|
|
expect(
|
|
createPrimaryObjectResult.data.createPrimaryObject
|
|
.primaryObject.arrayField
|
|
).toEqual([
|
|
{ __typename: 'Element', value: 1 },
|
|
{ __typename: 'Element', value: 'abc' },
|
|
{ __typename: 'SecondaryObject', someField: 'some value 44' },
|
|
]);
|
|
expect(
|
|
createPrimaryObjectResult.data.createPrimaryObject
|
|
.primaryObject.pointerField.someField
|
|
).toEqual('some value 22');
|
|
expect(
|
|
createPrimaryObjectResult.data.createPrimaryObject.primaryObject.relationField.edges
|
|
.map(value => value.node.someField)
|
|
.sort()
|
|
).toEqual(['some value 22', 'some value 44']);
|
|
expect(
|
|
updatePrimaryObjectResult.data.updatePrimaryObject
|
|
.primaryObject.stringField
|
|
).toEqual('some value');
|
|
expect(
|
|
updatePrimaryObjectResult.data.updatePrimaryObject
|
|
.primaryObject.arrayField
|
|
).toEqual([
|
|
{ __typename: 'Element', value: 1 },
|
|
{ __typename: 'Element', value: 'abc' },
|
|
{ __typename: 'SecondaryObject', someField: 'some value 44' },
|
|
]);
|
|
expect(
|
|
updatePrimaryObjectResult.data.updatePrimaryObject
|
|
.primaryObject.pointerField.someField
|
|
).toEqual('some value 44');
|
|
expect(
|
|
updatePrimaryObjectResult.data.updatePrimaryObject
|
|
.primaryObject.relationField.edges
|
|
).toEqual([]);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Class Schema Mutations', () => {
|
|
it('should create a new class', async () => {
|
|
try {
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
class1: createClass(
|
|
input: { name: "Class1", clientMutationId: "cmid1" }
|
|
) {
|
|
clientMutationId
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
}
|
|
}
|
|
}
|
|
class2: createClass(
|
|
input: {
|
|
name: "Class2"
|
|
schemaFields: null
|
|
clientMutationId: "cmid2"
|
|
}
|
|
) {
|
|
clientMutationId
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
}
|
|
}
|
|
}
|
|
class3: createClass(
|
|
input: {
|
|
name: "Class3"
|
|
schemaFields: {}
|
|
clientMutationId: "cmid3"
|
|
}
|
|
) {
|
|
clientMutationId
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
}
|
|
}
|
|
}
|
|
class4: createClass(
|
|
input: {
|
|
name: "Class4"
|
|
schemaFields: {
|
|
addStrings: null
|
|
addNumbers: null
|
|
addBooleans: null
|
|
addArrays: null
|
|
addObjects: null
|
|
addDates: null
|
|
addFiles: null
|
|
addGeoPoint: null
|
|
addPolygons: null
|
|
addBytes: null
|
|
addPointers: null
|
|
addRelations: null
|
|
}
|
|
clientMutationId: "cmid4"
|
|
}
|
|
) {
|
|
clientMutationId
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
}
|
|
}
|
|
}
|
|
class5: createClass(
|
|
input: {
|
|
name: "Class5"
|
|
schemaFields: {
|
|
addStrings: []
|
|
addNumbers: []
|
|
addBooleans: []
|
|
addArrays: []
|
|
addObjects: []
|
|
addDates: []
|
|
addFiles: []
|
|
addPolygons: []
|
|
addBytes: []
|
|
addPointers: []
|
|
addRelations: []
|
|
}
|
|
clientMutationId: "cmid5"
|
|
}
|
|
) {
|
|
clientMutationId
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
}
|
|
}
|
|
}
|
|
class6: createClass(
|
|
input: {
|
|
name: "Class6"
|
|
schemaFields: {
|
|
addStrings: [
|
|
{ name: "stringField1" }
|
|
{ name: "stringField2" }
|
|
{ name: "stringField3" }
|
|
]
|
|
addNumbers: [
|
|
{ name: "numberField1" }
|
|
{ name: "numberField2" }
|
|
{ name: "numberField3" }
|
|
]
|
|
addBooleans: [
|
|
{ name: "booleanField1" }
|
|
{ name: "booleanField2" }
|
|
{ name: "booleanField3" }
|
|
]
|
|
addArrays: [
|
|
{ name: "arrayField1" }
|
|
{ name: "arrayField2" }
|
|
{ name: "arrayField3" }
|
|
]
|
|
addObjects: [
|
|
{ name: "objectField1" }
|
|
{ name: "objectField2" }
|
|
{ name: "objectField3" }
|
|
]
|
|
addDates: [
|
|
{ name: "dateField1" }
|
|
{ name: "dateField2" }
|
|
{ name: "dateField3" }
|
|
]
|
|
addFiles: [
|
|
{ name: "fileField1" }
|
|
{ name: "fileField2" }
|
|
{ name: "fileField3" }
|
|
]
|
|
addGeoPoint: { name: "geoPointField" }
|
|
addPolygons: [
|
|
{ name: "polygonField1" }
|
|
{ name: "polygonField2" }
|
|
{ name: "polygonField3" }
|
|
]
|
|
addBytes: [
|
|
{ name: "bytesField1" }
|
|
{ name: "bytesField2" }
|
|
{ name: "bytesField3" }
|
|
]
|
|
addPointers: [
|
|
{ name: "pointerField1", targetClassName: "Class1" }
|
|
{ name: "pointerField2", targetClassName: "Class6" }
|
|
{ name: "pointerField3", targetClassName: "Class2" }
|
|
]
|
|
addRelations: [
|
|
{ name: "relationField1", targetClassName: "Class1" }
|
|
{ name: "relationField2", targetClassName: "Class6" }
|
|
{ name: "relationField3", targetClassName: "Class2" }
|
|
]
|
|
remove: [
|
|
{ name: "stringField3" }
|
|
{ name: "numberField3" }
|
|
{ name: "booleanField3" }
|
|
{ name: "arrayField3" }
|
|
{ name: "objectField3" }
|
|
{ name: "dateField3" }
|
|
{ name: "fileField3" }
|
|
{ name: "polygonField3" }
|
|
{ name: "bytesField3" }
|
|
{ name: "pointerField3" }
|
|
{ name: "relationField3" }
|
|
{ name: "doesNotExist" }
|
|
]
|
|
}
|
|
clientMutationId: "cmid6"
|
|
}
|
|
) {
|
|
clientMutationId
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
... on SchemaPointerField {
|
|
targetClassName
|
|
}
|
|
... on SchemaRelationField {
|
|
targetClassName
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
const classes = Object.keys(result.data).map(fieldName => ({
|
|
clientMutationId: result.data[fieldName].clientMutationId,
|
|
class: {
|
|
name: result.data[fieldName].class.name,
|
|
schemaFields: result.data[
|
|
fieldName
|
|
].class.schemaFields.sort((a, b) => (a.name > b.name ? 1 : -1)),
|
|
__typename: result.data[fieldName].class.__typename,
|
|
},
|
|
__typename: result.data[fieldName].__typename,
|
|
}));
|
|
expect(classes).toEqual([
|
|
{
|
|
clientMutationId: 'cmid1',
|
|
class: {
|
|
name: 'Class1',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'CreateClassPayload',
|
|
},
|
|
{
|
|
clientMutationId: 'cmid2',
|
|
class: {
|
|
name: 'Class2',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'CreateClassPayload',
|
|
},
|
|
{
|
|
clientMutationId: 'cmid3',
|
|
class: {
|
|
name: 'Class3',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'CreateClassPayload',
|
|
},
|
|
{
|
|
clientMutationId: 'cmid4',
|
|
class: {
|
|
name: 'Class4',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'CreateClassPayload',
|
|
},
|
|
{
|
|
clientMutationId: 'cmid5',
|
|
class: {
|
|
name: 'Class5',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'CreateClassPayload',
|
|
},
|
|
{
|
|
clientMutationId: 'cmid6',
|
|
class: {
|
|
name: 'Class6',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'arrayField1', __typename: 'SchemaArrayField' },
|
|
{ name: 'arrayField2', __typename: 'SchemaArrayField' },
|
|
{ name: 'booleanField1', __typename: 'SchemaBooleanField' },
|
|
{ name: 'booleanField2', __typename: 'SchemaBooleanField' },
|
|
{ name: 'bytesField1', __typename: 'SchemaBytesField' },
|
|
{ name: 'bytesField2', __typename: 'SchemaBytesField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'dateField1', __typename: 'SchemaDateField' },
|
|
{ name: 'dateField2', __typename: 'SchemaDateField' },
|
|
{ name: 'fileField1', __typename: 'SchemaFileField' },
|
|
{ name: 'fileField2', __typename: 'SchemaFileField' },
|
|
{
|
|
name: 'geoPointField',
|
|
__typename: 'SchemaGeoPointField',
|
|
},
|
|
{ name: 'numberField1', __typename: 'SchemaNumberField' },
|
|
{ name: 'numberField2', __typename: 'SchemaNumberField' },
|
|
{ name: 'objectField1', __typename: 'SchemaObjectField' },
|
|
{ name: 'objectField2', __typename: 'SchemaObjectField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{
|
|
name: 'pointerField1',
|
|
__typename: 'SchemaPointerField',
|
|
targetClassName: 'Class1',
|
|
},
|
|
{
|
|
name: 'pointerField2',
|
|
__typename: 'SchemaPointerField',
|
|
targetClassName: 'Class6',
|
|
},
|
|
{ name: 'polygonField1', __typename: 'SchemaPolygonField' },
|
|
{ name: 'polygonField2', __typename: 'SchemaPolygonField' },
|
|
{
|
|
name: 'relationField1',
|
|
__typename: 'SchemaRelationField',
|
|
targetClassName: 'Class1',
|
|
},
|
|
{
|
|
name: 'relationField2',
|
|
__typename: 'SchemaRelationField',
|
|
targetClassName: 'Class6',
|
|
},
|
|
{ name: 'stringField1', __typename: 'SchemaStringField' },
|
|
{ name: 'stringField2', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'CreateClassPayload',
|
|
},
|
|
]);
|
|
|
|
const findResult = await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
classes {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
... on SchemaPointerField {
|
|
targetClassName
|
|
}
|
|
... on SchemaRelationField {
|
|
targetClassName
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
findResult.data.classes = findResult.data.classes
|
|
.filter(schemaClass => !schemaClass.name.startsWith('_'))
|
|
.sort((a, b) => (a.name > b.name ? 1 : -1));
|
|
findResult.data.classes.forEach(schemaClass => {
|
|
schemaClass.schemaFields = schemaClass.schemaFields.sort((a, b) =>
|
|
a.name > b.name ? 1 : -1
|
|
);
|
|
});
|
|
expect(findResult.data.classes).toEqual([
|
|
{
|
|
name: 'Class1',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
{
|
|
name: 'Class2',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
{
|
|
name: 'Class3',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
{
|
|
name: 'Class4',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
{
|
|
name: 'Class5',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
{
|
|
name: 'Class6',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'arrayField1', __typename: 'SchemaArrayField' },
|
|
{ name: 'arrayField2', __typename: 'SchemaArrayField' },
|
|
{ name: 'booleanField1', __typename: 'SchemaBooleanField' },
|
|
{ name: 'booleanField2', __typename: 'SchemaBooleanField' },
|
|
{ name: 'bytesField1', __typename: 'SchemaBytesField' },
|
|
{ name: 'bytesField2', __typename: 'SchemaBytesField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'dateField1', __typename: 'SchemaDateField' },
|
|
{ name: 'dateField2', __typename: 'SchemaDateField' },
|
|
{ name: 'fileField1', __typename: 'SchemaFileField' },
|
|
{ name: 'fileField2', __typename: 'SchemaFileField' },
|
|
{
|
|
name: 'geoPointField',
|
|
__typename: 'SchemaGeoPointField',
|
|
},
|
|
{ name: 'numberField1', __typename: 'SchemaNumberField' },
|
|
{ name: 'numberField2', __typename: 'SchemaNumberField' },
|
|
{ name: 'objectField1', __typename: 'SchemaObjectField' },
|
|
{ name: 'objectField2', __typename: 'SchemaObjectField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{
|
|
name: 'pointerField1',
|
|
__typename: 'SchemaPointerField',
|
|
targetClassName: 'Class1',
|
|
},
|
|
{
|
|
name: 'pointerField2',
|
|
__typename: 'SchemaPointerField',
|
|
targetClassName: 'Class6',
|
|
},
|
|
{ name: 'polygonField1', __typename: 'SchemaPolygonField' },
|
|
{ name: 'polygonField2', __typename: 'SchemaPolygonField' },
|
|
{
|
|
name: 'relationField1',
|
|
__typename: 'SchemaRelationField',
|
|
targetClassName: 'Class1',
|
|
},
|
|
{
|
|
name: 'relationField2',
|
|
__typename: 'SchemaRelationField',
|
|
targetClassName: 'Class6',
|
|
},
|
|
{ name: 'stringField1', __typename: 'SchemaStringField' },
|
|
{ name: 'stringField2', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
]);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should require master key to create a new class', async () => {
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
createClass(input: { name: "SomeClass" }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.OPERATION_FORBIDDEN
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'unauthorized: master key is required'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should not allow duplicated field names when creating', async () => {
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
createClass(
|
|
input: {
|
|
name: "SomeClass"
|
|
schemaFields: {
|
|
addStrings: [{ name: "someField" }]
|
|
addNumbers: [{ name: "someField" }]
|
|
}
|
|
}
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.INVALID_KEY_NAME
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'Duplicated field name: someField'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should update an existing class', async () => {
|
|
try {
|
|
const clientMutationId = uuidv4();
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
createClass(
|
|
input: {
|
|
name: "MyNewClass"
|
|
schemaFields: { addStrings: [{ name: "willBeRemoved" }] }
|
|
}
|
|
) {
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
}
|
|
}
|
|
}
|
|
updateClass(input: {
|
|
clientMutationId: "${clientMutationId}"
|
|
name: "MyNewClass"
|
|
schemaFields: {
|
|
addStrings: [
|
|
{ name: "stringField1" }
|
|
{ name: "stringField2" }
|
|
{ name: "stringField3" }
|
|
]
|
|
addNumbers: [
|
|
{ name: "numberField1" }
|
|
{ name: "numberField2" }
|
|
{ name: "numberField3" }
|
|
]
|
|
addBooleans: [
|
|
{ name: "booleanField1" }
|
|
{ name: "booleanField2" }
|
|
{ name: "booleanField3" }
|
|
]
|
|
addArrays: [
|
|
{ name: "arrayField1" }
|
|
{ name: "arrayField2" }
|
|
{ name: "arrayField3" }
|
|
]
|
|
addObjects: [
|
|
{ name: "objectField1" }
|
|
{ name: "objectField2" }
|
|
{ name: "objectField3" }
|
|
]
|
|
addDates: [
|
|
{ name: "dateField1" }
|
|
{ name: "dateField2" }
|
|
{ name: "dateField3" }
|
|
]
|
|
addFiles: [
|
|
{ name: "fileField1" }
|
|
{ name: "fileField2" }
|
|
{ name: "fileField3" }
|
|
]
|
|
addGeoPoint: { name: "geoPointField" }
|
|
addPolygons: [
|
|
{ name: "polygonField1" }
|
|
{ name: "polygonField2" }
|
|
{ name: "polygonField3" }
|
|
]
|
|
addBytes: [
|
|
{ name: "bytesField1" }
|
|
{ name: "bytesField2" }
|
|
{ name: "bytesField3" }
|
|
]
|
|
addPointers: [
|
|
{ name: "pointerField1", targetClassName: "Class1" }
|
|
{ name: "pointerField2", targetClassName: "Class6" }
|
|
{ name: "pointerField3", targetClassName: "Class2" }
|
|
]
|
|
addRelations: [
|
|
{ name: "relationField1", targetClassName: "Class1" }
|
|
{ name: "relationField2", targetClassName: "Class6" }
|
|
{ name: "relationField3", targetClassName: "Class2" }
|
|
]
|
|
remove: [
|
|
{ name: "willBeRemoved" }
|
|
{ name: "stringField3" }
|
|
{ name: "numberField3" }
|
|
{ name: "booleanField3" }
|
|
{ name: "arrayField3" }
|
|
{ name: "objectField3" }
|
|
{ name: "dateField3" }
|
|
{ name: "fileField3" }
|
|
{ name: "polygonField3" }
|
|
{ name: "bytesField3" }
|
|
{ name: "pointerField3" }
|
|
{ name: "relationField3" }
|
|
{ name: "doesNotExist" }
|
|
]
|
|
}
|
|
}) {
|
|
clientMutationId
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
... on SchemaPointerField {
|
|
targetClassName
|
|
}
|
|
... on SchemaRelationField {
|
|
targetClassName
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
result.data.createClass.class.schemaFields = result.data.createClass.class.schemaFields.sort(
|
|
(a, b) => (a.name > b.name ? 1 : -1)
|
|
);
|
|
result.data.updateClass.class.schemaFields = result.data.updateClass.class.schemaFields.sort(
|
|
(a, b) => (a.name > b.name ? 1 : -1)
|
|
);
|
|
expect(result).toEqual({
|
|
data: {
|
|
createClass: {
|
|
class: {
|
|
name: 'MyNewClass',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
{
|
|
name: 'willBeRemoved',
|
|
__typename: 'SchemaStringField',
|
|
},
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'CreateClassPayload',
|
|
},
|
|
updateClass: {
|
|
clientMutationId,
|
|
class: {
|
|
name: 'MyNewClass',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'arrayField1', __typename: 'SchemaArrayField' },
|
|
{ name: 'arrayField2', __typename: 'SchemaArrayField' },
|
|
{
|
|
name: 'booleanField1',
|
|
__typename: 'SchemaBooleanField',
|
|
},
|
|
{
|
|
name: 'booleanField2',
|
|
__typename: 'SchemaBooleanField',
|
|
},
|
|
{ name: 'bytesField1', __typename: 'SchemaBytesField' },
|
|
{ name: 'bytesField2', __typename: 'SchemaBytesField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'dateField1', __typename: 'SchemaDateField' },
|
|
{ name: 'dateField2', __typename: 'SchemaDateField' },
|
|
{ name: 'fileField1', __typename: 'SchemaFileField' },
|
|
{ name: 'fileField2', __typename: 'SchemaFileField' },
|
|
{
|
|
name: 'geoPointField',
|
|
__typename: 'SchemaGeoPointField',
|
|
},
|
|
{ name: 'numberField1', __typename: 'SchemaNumberField' },
|
|
{ name: 'numberField2', __typename: 'SchemaNumberField' },
|
|
{ name: 'objectField1', __typename: 'SchemaObjectField' },
|
|
{ name: 'objectField2', __typename: 'SchemaObjectField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{
|
|
name: 'pointerField1',
|
|
__typename: 'SchemaPointerField',
|
|
targetClassName: 'Class1',
|
|
},
|
|
{
|
|
name: 'pointerField2',
|
|
__typename: 'SchemaPointerField',
|
|
targetClassName: 'Class6',
|
|
},
|
|
{
|
|
name: 'polygonField1',
|
|
__typename: 'SchemaPolygonField',
|
|
},
|
|
{
|
|
name: 'polygonField2',
|
|
__typename: 'SchemaPolygonField',
|
|
},
|
|
{
|
|
name: 'relationField1',
|
|
__typename: 'SchemaRelationField',
|
|
targetClassName: 'Class1',
|
|
},
|
|
{
|
|
name: 'relationField2',
|
|
__typename: 'SchemaRelationField',
|
|
targetClassName: 'Class6',
|
|
},
|
|
{ name: 'stringField1', __typename: 'SchemaStringField' },
|
|
{ name: 'stringField2', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'UpdateClassPayload',
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
class(name: "MyNewClass") {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
... on SchemaPointerField {
|
|
targetClassName
|
|
}
|
|
... on SchemaRelationField {
|
|
targetClassName
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
getResult.data.class.schemaFields = getResult.data.class.schemaFields.sort(
|
|
(a, b) => (a.name > b.name ? 1 : -1)
|
|
);
|
|
expect(getResult.data).toEqual({
|
|
class: {
|
|
name: 'MyNewClass',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'arrayField1', __typename: 'SchemaArrayField' },
|
|
{ name: 'arrayField2', __typename: 'SchemaArrayField' },
|
|
{ name: 'booleanField1', __typename: 'SchemaBooleanField' },
|
|
{ name: 'booleanField2', __typename: 'SchemaBooleanField' },
|
|
{ name: 'bytesField1', __typename: 'SchemaBytesField' },
|
|
{ name: 'bytesField2', __typename: 'SchemaBytesField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'dateField1', __typename: 'SchemaDateField' },
|
|
{ name: 'dateField2', __typename: 'SchemaDateField' },
|
|
{ name: 'fileField1', __typename: 'SchemaFileField' },
|
|
{ name: 'fileField2', __typename: 'SchemaFileField' },
|
|
{
|
|
name: 'geoPointField',
|
|
__typename: 'SchemaGeoPointField',
|
|
},
|
|
{ name: 'numberField1', __typename: 'SchemaNumberField' },
|
|
{ name: 'numberField2', __typename: 'SchemaNumberField' },
|
|
{ name: 'objectField1', __typename: 'SchemaObjectField' },
|
|
{ name: 'objectField2', __typename: 'SchemaObjectField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{
|
|
name: 'pointerField1',
|
|
__typename: 'SchemaPointerField',
|
|
targetClassName: 'Class1',
|
|
},
|
|
{
|
|
name: 'pointerField2',
|
|
__typename: 'SchemaPointerField',
|
|
targetClassName: 'Class6',
|
|
},
|
|
{ name: 'polygonField1', __typename: 'SchemaPolygonField' },
|
|
{ name: 'polygonField2', __typename: 'SchemaPolygonField' },
|
|
{
|
|
name: 'relationField1',
|
|
__typename: 'SchemaRelationField',
|
|
targetClassName: 'Class1',
|
|
},
|
|
{
|
|
name: 'relationField2',
|
|
__typename: 'SchemaRelationField',
|
|
targetClassName: 'Class6',
|
|
},
|
|
{ name: 'stringField1', __typename: 'SchemaStringField' },
|
|
{ name: 'stringField2', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
});
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should require master key to update an existing class', async () => {
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
createClass(input: { name: "SomeClass" }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
updateClass(input: { name: "SomeClass" }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.OPERATION_FORBIDDEN
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'unauthorized: master key is required'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should not allow duplicated field names when updating', async () => {
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
createClass(
|
|
input: {
|
|
name: "SomeClass"
|
|
schemaFields: { addStrings: [{ name: "someField" }] }
|
|
}
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
updateClass(
|
|
input: {
|
|
name: "SomeClass"
|
|
schemaFields: { addNumbers: [{ name: "someField" }] }
|
|
}
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.INVALID_KEY_NAME
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'Duplicated field name: someField'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should fail if updating an inexistent class', async () => {
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
updateClass(
|
|
input: {
|
|
name: "SomeInexistentClass"
|
|
schemaFields: { addNumbers: [{ name: "someField" }] }
|
|
}
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.INVALID_CLASS_NAME
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'Class SomeInexistentClass does not exist.'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should delete an existing class', async () => {
|
|
try {
|
|
const clientMutationId = uuidv4();
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
createClass(
|
|
input: {
|
|
name: "MyNewClass"
|
|
schemaFields: { addStrings: [{ name: "willBeRemoved" }] }
|
|
}
|
|
) {
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
__typename
|
|
}
|
|
}
|
|
}
|
|
deleteClass(input: { clientMutationId: "${clientMutationId}" name: "MyNewClass" }) {
|
|
clientMutationId
|
|
class {
|
|
name
|
|
schemaFields {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
result.data.createClass.class.schemaFields = result.data.createClass.class.schemaFields.sort(
|
|
(a, b) => (a.name > b.name ? 1 : -1)
|
|
);
|
|
result.data.deleteClass.class.schemaFields = result.data.deleteClass.class.schemaFields.sort(
|
|
(a, b) => (a.name > b.name ? 1 : -1)
|
|
);
|
|
expect(result).toEqual({
|
|
data: {
|
|
createClass: {
|
|
class: {
|
|
name: 'MyNewClass',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
{
|
|
name: 'willBeRemoved',
|
|
__typename: 'SchemaStringField',
|
|
},
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'CreateClassPayload',
|
|
},
|
|
deleteClass: {
|
|
clientMutationId,
|
|
class: {
|
|
name: 'MyNewClass',
|
|
schemaFields: [
|
|
{ name: 'ACL', __typename: 'SchemaACLField' },
|
|
{ name: 'createdAt', __typename: 'SchemaDateField' },
|
|
{ name: 'objectId', __typename: 'SchemaStringField' },
|
|
{ name: 'updatedAt', __typename: 'SchemaDateField' },
|
|
{
|
|
name: 'willBeRemoved',
|
|
__typename: 'SchemaStringField',
|
|
},
|
|
],
|
|
__typename: 'Class',
|
|
},
|
|
__typename: 'DeleteClassPayload',
|
|
},
|
|
},
|
|
});
|
|
|
|
try {
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
class(name: "MyNewClass") {
|
|
name
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.INVALID_CLASS_NAME
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'Class MyNewClass does not exist.'
|
|
);
|
|
}
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should require master key to delete an existing class', async () => {
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
createClass(input: { name: "SomeClass" }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
deleteClass(input: { name: "SomeClass" }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.OPERATION_FORBIDDEN
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'unauthorized: master key is required'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should fail if deleting an inexistent class', async () => {
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
deleteClass(input: { name: "SomeInexistentClass" }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.INVALID_CLASS_NAME
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'Class SomeInexistentClass does not exist.'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should require master key to get an existing class', async () => {
|
|
try {
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
class(name: "_User") {
|
|
name
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.OPERATION_FORBIDDEN
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'unauthorized: master key is required'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should require master key to find the existing classes', async () => {
|
|
try {
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
classes {
|
|
name
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
fail('should fail');
|
|
} catch (e) {
|
|
expect(e.graphQLErrors[0].extensions.code).toEqual(
|
|
Parse.Error.OPERATION_FORBIDDEN
|
|
);
|
|
expect(e.graphQLErrors[0].message).toEqual(
|
|
'unauthorized: master key is required'
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Objects Queries', () => {
|
|
describe('Get', () => {
|
|
it('should return a class object using class specific query', async () => {
|
|
const obj = new Parse.Object('Customer');
|
|
obj.set('someField', 'someValue');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetCustomer($id: ID!) {
|
|
customer(id: $id) {
|
|
id
|
|
objectId
|
|
someField
|
|
createdAt
|
|
updatedAt
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: obj.id,
|
|
},
|
|
})
|
|
).data.customer;
|
|
|
|
expect(result.objectId).toEqual(obj.id);
|
|
expect(result.someField).toEqual('someValue');
|
|
expect(new Date(result.createdAt)).toEqual(obj.createdAt);
|
|
expect(new Date(result.updatedAt)).toEqual(obj.updatedAt);
|
|
});
|
|
|
|
it_only_db('mongo')(
|
|
'should return child objects in array fields',
|
|
async () => {
|
|
const obj1 = new Parse.Object('Customer');
|
|
const obj2 = new Parse.Object('SomeClass');
|
|
const obj3 = new Parse.Object('Customer');
|
|
|
|
obj1.set('someCustomerField', 'imCustomerOne');
|
|
const arrayField = [42.42, 42, 'string', true];
|
|
obj1.set('arrayField', arrayField);
|
|
await obj1.save();
|
|
|
|
obj2.set('someClassField', 'imSomeClassTwo');
|
|
await obj2.save();
|
|
|
|
obj3.set('manyRelations', [obj1, obj2]);
|
|
await obj3.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetCustomer($id: ID!) {
|
|
customer(id: $id) {
|
|
objectId
|
|
manyRelations {
|
|
... on Customer {
|
|
objectId
|
|
someCustomerField
|
|
arrayField {
|
|
... on Element {
|
|
value
|
|
}
|
|
}
|
|
}
|
|
... on SomeClass {
|
|
objectId
|
|
someClassField
|
|
}
|
|
}
|
|
createdAt
|
|
updatedAt
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: obj3.id,
|
|
},
|
|
})
|
|
).data.customer;
|
|
|
|
expect(result.objectId).toEqual(obj3.id);
|
|
expect(result.manyRelations.length).toEqual(2);
|
|
|
|
const customerSubObject = result.manyRelations.find(
|
|
o => o.objectId === obj1.id
|
|
);
|
|
const someClassSubObject = result.manyRelations.find(
|
|
o => o.objectId === obj2.id
|
|
);
|
|
|
|
expect(customerSubObject).toBeDefined();
|
|
expect(someClassSubObject).toBeDefined();
|
|
expect(customerSubObject.someCustomerField).toEqual(
|
|
'imCustomerOne'
|
|
);
|
|
const formatedArrayField = customerSubObject.arrayField.map(
|
|
elem => elem.value
|
|
);
|
|
expect(formatedArrayField).toEqual(arrayField);
|
|
expect(someClassSubObject.someClassField).toEqual(
|
|
'imSomeClassTwo'
|
|
);
|
|
}
|
|
);
|
|
|
|
it_only_db('mongo')(
|
|
'should return many child objects in allow cyclic query',
|
|
async () => {
|
|
const obj1 = new Parse.Object('Employee');
|
|
const obj2 = new Parse.Object('Team');
|
|
const obj3 = new Parse.Object('Company');
|
|
const obj4 = new Parse.Object('Country');
|
|
|
|
obj1.set('name', 'imAnEmployee');
|
|
await obj1.save();
|
|
|
|
obj2.set('name', 'imATeam');
|
|
obj2.set('employees', [obj1]);
|
|
await obj2.save();
|
|
|
|
obj3.set('name', 'imACompany');
|
|
obj3.set('teams', [obj2]);
|
|
obj3.set('employees', [obj1]);
|
|
await obj3.save();
|
|
|
|
obj4.set('name', 'imACountry');
|
|
obj4.set('companies', [obj3]);
|
|
await obj4.save();
|
|
|
|
obj1.set('country', obj4);
|
|
await obj1.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query DeepComplexGraphQLQuery($id: ID!) {
|
|
country(id: $id) {
|
|
objectId
|
|
name
|
|
companies {
|
|
... on Company {
|
|
objectId
|
|
name
|
|
employees {
|
|
... on Employee {
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
teams {
|
|
... on Team {
|
|
objectId
|
|
name
|
|
employees {
|
|
... on Employee {
|
|
objectId
|
|
name
|
|
country {
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: obj4.id,
|
|
},
|
|
})
|
|
).data.country;
|
|
|
|
const expectedResult = {
|
|
objectId: obj4.id,
|
|
name: 'imACountry',
|
|
__typename: 'Country',
|
|
companies: [
|
|
{
|
|
objectId: obj3.id,
|
|
name: 'imACompany',
|
|
__typename: 'Company',
|
|
employees: [
|
|
{
|
|
objectId: obj1.id,
|
|
name: 'imAnEmployee',
|
|
__typename: 'Employee',
|
|
},
|
|
],
|
|
teams: [
|
|
{
|
|
objectId: obj2.id,
|
|
name: 'imATeam',
|
|
__typename: 'Team',
|
|
employees: [
|
|
{
|
|
objectId: obj1.id,
|
|
name: 'imAnEmployee',
|
|
__typename: 'Employee',
|
|
country: {
|
|
objectId: obj4.id,
|
|
name: 'imACountry',
|
|
__typename: 'Country',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
expect(result).toEqual(expectedResult);
|
|
}
|
|
);
|
|
|
|
it('should respect level permissions', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
async function getObject(className, id, headers) {
|
|
const specificQueryResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
get: ${className.charAt(0).toLowerCase() +
|
|
className.slice(1)}(id: $id) {
|
|
id
|
|
createdAt
|
|
someField
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id,
|
|
},
|
|
context: {
|
|
headers,
|
|
},
|
|
});
|
|
|
|
return specificQueryResult;
|
|
}
|
|
|
|
await Promise.all(
|
|
objects
|
|
.slice(0, 3)
|
|
.map(obj =>
|
|
expectAsync(
|
|
getObject(obj.className, obj.id)
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'))
|
|
)
|
|
);
|
|
expect(
|
|
(await getObject(object4.className, object4.id)).data.get
|
|
.someField
|
|
).toEqual('someValue4');
|
|
await Promise.all(
|
|
objects.map(async obj =>
|
|
expect(
|
|
(
|
|
await getObject(obj.className, obj.id, {
|
|
'X-Parse-Master-Key': 'test',
|
|
})
|
|
).data.get.someField
|
|
).toEqual(obj.get('someField'))
|
|
)
|
|
);
|
|
await Promise.all(
|
|
objects.map(async obj =>
|
|
expect(
|
|
(
|
|
await getObject(obj.className, obj.id, {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
})
|
|
).data.get.someField
|
|
).toEqual(obj.get('someField'))
|
|
)
|
|
);
|
|
await Promise.all(
|
|
objects.map(async obj =>
|
|
expect(
|
|
(
|
|
await getObject(obj.className, obj.id, {
|
|
'X-Parse-Session-Token': user2.getSessionToken(),
|
|
})
|
|
).data.get.someField
|
|
).toEqual(obj.get('someField'))
|
|
)
|
|
);
|
|
await expectAsync(
|
|
getObject(object2.className, object2.id, {
|
|
'X-Parse-Session-Token': user3.getSessionToken(),
|
|
})
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await Promise.all(
|
|
[object1, object3, object4].map(async obj =>
|
|
expect(
|
|
(
|
|
await getObject(obj.className, obj.id, {
|
|
'X-Parse-Session-Token': user3.getSessionToken(),
|
|
})
|
|
).data.get.someField
|
|
).toEqual(obj.get('someField'))
|
|
)
|
|
);
|
|
await Promise.all(
|
|
objects.slice(0, 3).map(obj =>
|
|
expectAsync(
|
|
getObject(obj.className, obj.id, {
|
|
'X-Parse-Session-Token': user4.getSessionToken(),
|
|
})
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'))
|
|
)
|
|
);
|
|
expect(
|
|
(
|
|
await getObject(object4.className, object4.id, {
|
|
'X-Parse-Session-Token': user4.getSessionToken(),
|
|
})
|
|
).data.get.someField
|
|
).toEqual('someValue4');
|
|
await Promise.all(
|
|
objects.slice(0, 2).map(obj =>
|
|
expectAsync(
|
|
getObject(obj.className, obj.id, {
|
|
'X-Parse-Session-Token': user5.getSessionToken(),
|
|
})
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'))
|
|
)
|
|
);
|
|
expect(
|
|
(
|
|
await getObject(object3.className, object3.id, {
|
|
'X-Parse-Session-Token': user5.getSessionToken(),
|
|
})
|
|
).data.get.someField
|
|
).toEqual('someValue3');
|
|
expect(
|
|
(
|
|
await getObject(object4.className, object4.id, {
|
|
'X-Parse-Session-Token': user5.getSessionToken(),
|
|
})
|
|
).data.get.someField
|
|
).toEqual('someValue4');
|
|
});
|
|
|
|
it('should support keys argument', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result1 = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
get: graphQLClass(id: $id) {
|
|
someField
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: object3.id,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
const result2 = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
get: graphQLClass(id: $id) {
|
|
someField
|
|
pointerToUser {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: object3.id,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result1.data.get.someField).toBeDefined();
|
|
expect(result1.data.get.pointerToUser).toBeUndefined();
|
|
expect(result2.data.get.someField).toBeDefined();
|
|
expect(result2.data.get.pointerToUser).toBeDefined();
|
|
});
|
|
|
|
it('should support include argument', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result1 = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
get: graphQLClass(id: $id) {
|
|
pointerToUser {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: object3.id,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
const result2 = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
graphQLClass(id: $id) {
|
|
pointerToUser {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: object3.id,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result1.data.get.pointerToUser.username).toBeUndefined();
|
|
expect(
|
|
result2.data.graphQLClass.pointerToUser.username
|
|
).toBeDefined();
|
|
});
|
|
|
|
describe_only_db('mongo')('read preferences', () => {
|
|
it('should read from primary by default', async () => {
|
|
try {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const databaseAdapter =
|
|
parseServer.config.databaseController.adapter;
|
|
spyOn(
|
|
databaseAdapter.database.serverConfig,
|
|
'cursor'
|
|
).and.callThrough();
|
|
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
graphQLClass(id: $id) {
|
|
pointerToUser {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: object3.id,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
let foundGraphQLClassReadPreference = false;
|
|
let foundUserClassReadPreference = false;
|
|
databaseAdapter.database.serverConfig.cursor.calls
|
|
.all()
|
|
.forEach(call => {
|
|
if (
|
|
call.args[0].ns.collection.indexOf('GraphQLClass') >= 0
|
|
) {
|
|
foundGraphQLClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.PRIMARY
|
|
);
|
|
} else if (
|
|
call.args[0].ns.collection.indexOf('_User') >= 0
|
|
) {
|
|
foundUserClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.PRIMARY
|
|
);
|
|
}
|
|
});
|
|
|
|
expect(foundGraphQLClassReadPreference).toBe(true);
|
|
expect(foundUserClassReadPreference).toBe(true);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support readPreference argument', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const databaseAdapter =
|
|
parseServer.config.databaseController.adapter;
|
|
spyOn(
|
|
databaseAdapter.database.serverConfig,
|
|
'cursor'
|
|
).and.callThrough();
|
|
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
graphQLClass(
|
|
id: $id
|
|
options: { readPreference: SECONDARY }
|
|
) {
|
|
pointerToUser {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: object3.id,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
let foundGraphQLClassReadPreference = false;
|
|
let foundUserClassReadPreference = false;
|
|
databaseAdapter.database.serverConfig.cursor.calls
|
|
.all()
|
|
.forEach(call => {
|
|
if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) {
|
|
foundGraphQLClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.SECONDARY
|
|
);
|
|
} else if (call.args[0].ns.collection.indexOf('_User') >= 0) {
|
|
foundUserClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.SECONDARY
|
|
);
|
|
}
|
|
});
|
|
|
|
expect(foundGraphQLClassReadPreference).toBe(true);
|
|
expect(foundUserClassReadPreference).toBe(true);
|
|
});
|
|
|
|
it('should support includeReadPreference argument', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const databaseAdapter =
|
|
parseServer.config.databaseController.adapter;
|
|
spyOn(
|
|
databaseAdapter.database.serverConfig,
|
|
'cursor'
|
|
).and.callThrough();
|
|
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
graphQLClass(
|
|
id: $id
|
|
options: {
|
|
readPreference: SECONDARY
|
|
includeReadPreference: NEAREST
|
|
}
|
|
) {
|
|
pointerToUser {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: object3.id,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
let foundGraphQLClassReadPreference = false;
|
|
let foundUserClassReadPreference = false;
|
|
databaseAdapter.database.serverConfig.cursor.calls
|
|
.all()
|
|
.forEach(call => {
|
|
if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) {
|
|
foundGraphQLClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.SECONDARY
|
|
);
|
|
} else if (call.args[0].ns.collection.indexOf('_User') >= 0) {
|
|
foundUserClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.NEAREST
|
|
);
|
|
}
|
|
});
|
|
|
|
expect(foundGraphQLClassReadPreference).toBe(true);
|
|
expect(foundUserClassReadPreference).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Find', () => {
|
|
it('should return class objects using class specific query', async () => {
|
|
const obj1 = new Parse.Object('Customer');
|
|
obj1.set('someField', 'someValue1');
|
|
await obj1.save();
|
|
const obj2 = new Parse.Object('Customer');
|
|
obj2.set('someField', 'someValue1');
|
|
await obj2.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query FindCustomer {
|
|
customers {
|
|
edges {
|
|
node {
|
|
objectId
|
|
someField
|
|
createdAt
|
|
updatedAt
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
|
|
expect(result.data.customers.edges.length).toEqual(2);
|
|
|
|
result.data.customers.edges.forEach(resultObj => {
|
|
const obj = resultObj.node.objectId === obj1.id ? obj1 : obj2;
|
|
expect(resultObj.node.objectId).toEqual(obj.id);
|
|
expect(resultObj.node.someField).toEqual(obj.get('someField'));
|
|
expect(new Date(resultObj.node.createdAt)).toEqual(obj.createdAt);
|
|
expect(new Date(resultObj.node.updatedAt)).toEqual(obj.updatedAt);
|
|
});
|
|
});
|
|
|
|
it('should respect level permissions', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
async function findObjects(className, headers) {
|
|
const graphqlClassName = pluralize(
|
|
className.charAt(0).toLowerCase() + className.slice(1)
|
|
);
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects {
|
|
find: ${graphqlClassName} {
|
|
edges {
|
|
node {
|
|
id
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers,
|
|
},
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
expect(
|
|
(await findObjects('GraphQLClass')).data.find.edges.map(
|
|
object => object.node.someField
|
|
)
|
|
).toEqual([]);
|
|
expect(
|
|
(await findObjects('PublicClass')).data.find.edges.map(
|
|
object => object.node.someField
|
|
)
|
|
).toEqual(['someValue4']);
|
|
expect(
|
|
(
|
|
await findObjects('GraphQLClass', {
|
|
'X-Parse-Master-Key': 'test',
|
|
})
|
|
).data.find.edges
|
|
.map(object => object.node.someField)
|
|
.sort()
|
|
).toEqual(['someValue1', 'someValue2', 'someValue3']);
|
|
expect(
|
|
(
|
|
await findObjects('PublicClass', {
|
|
'X-Parse-Master-Key': 'test',
|
|
})
|
|
).data.find.edges.map(object => object.node.someField)
|
|
).toEqual(['someValue4']);
|
|
expect(
|
|
(
|
|
await findObjects('GraphQLClass', {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
})
|
|
).data.find.edges
|
|
.map(object => object.node.someField)
|
|
.sort()
|
|
).toEqual(['someValue1', 'someValue2', 'someValue3']);
|
|
expect(
|
|
(
|
|
await findObjects('PublicClass', {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
})
|
|
).data.find.edges.map(object => object.node.someField)
|
|
).toEqual(['someValue4']);
|
|
expect(
|
|
(
|
|
await findObjects('GraphQLClass', {
|
|
'X-Parse-Session-Token': user2.getSessionToken(),
|
|
})
|
|
).data.find.edges
|
|
.map(object => object.node.someField)
|
|
.sort()
|
|
).toEqual(['someValue1', 'someValue2', 'someValue3']);
|
|
expect(
|
|
(
|
|
await findObjects('GraphQLClass', {
|
|
'X-Parse-Session-Token': user3.getSessionToken(),
|
|
})
|
|
).data.find.edges
|
|
.map(object => object.node.someField)
|
|
.sort()
|
|
).toEqual(['someValue1', 'someValue3']);
|
|
expect(
|
|
(
|
|
await findObjects('GraphQLClass', {
|
|
'X-Parse-Session-Token': user4.getSessionToken(),
|
|
})
|
|
).data.find.edges.map(object => object.node.someField)
|
|
).toEqual([]);
|
|
expect(
|
|
(
|
|
await findObjects('GraphQLClass', {
|
|
'X-Parse-Session-Token': user5.getSessionToken(),
|
|
})
|
|
).data.find.edges.map(object => object.node.someField)
|
|
).toEqual(['someValue3']);
|
|
});
|
|
|
|
it('should support where argument using class specific query', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects($where: GraphQLClassWhereInput) {
|
|
graphQLClasses(where: $where) {
|
|
edges {
|
|
node {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
someField: {
|
|
in: ['someValue1', 'someValue2', 'someValue3'],
|
|
},
|
|
OR: [
|
|
{
|
|
pointerToUser: {
|
|
have: {
|
|
objectId: {
|
|
equalTo: user5.id,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: {
|
|
equalTo: object1.id,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(
|
|
result.data.graphQLClasses.edges
|
|
.map(object => object.node.someField)
|
|
.sort()
|
|
).toEqual(['someValue1', 'someValue3']);
|
|
});
|
|
|
|
it('should support in pointer operator using class specific query', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects($where: GraphQLClassWhereInput) {
|
|
graphQLClasses(where: $where) {
|
|
edges {
|
|
node {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
pointerToUser: {
|
|
have: {
|
|
objectId: {
|
|
in: [user5.id],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
const { edges } = result.data.graphQLClasses;
|
|
expect(edges.length).toBe(1);
|
|
expect(edges[0].node.someField).toEqual('someValue3');
|
|
});
|
|
|
|
it('should support OR operation', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query {
|
|
graphQLClasses(
|
|
where: {
|
|
OR: [
|
|
{ someField: { equalTo: "someValue1" } }
|
|
{ someField: { equalTo: "someValue2" } }
|
|
]
|
|
}
|
|
) {
|
|
edges {
|
|
node {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(
|
|
result.data.graphQLClasses.edges
|
|
.map(object => object.node.someField)
|
|
.sort()
|
|
).toEqual(['someValue1', 'someValue2']);
|
|
});
|
|
|
|
it('should support full text search', async () => {
|
|
try {
|
|
const obj = new Parse.Object('FullTextSearchTest');
|
|
obj.set('field1', 'Parse GraphQL Server');
|
|
obj.set('field2', 'It rocks!');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query FullTextSearchTests(
|
|
$where: FullTextSearchTestWhereInput
|
|
) {
|
|
fullTextSearchTests(where: $where) {
|
|
edges {
|
|
node {
|
|
objectId
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
variables: {
|
|
where: {
|
|
field1: {
|
|
text: {
|
|
search: {
|
|
term: 'graphql',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(
|
|
result.data.fullTextSearchTests.edges[0].node.objectId
|
|
).toEqual(obj.id);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support in query key', async () => {
|
|
try {
|
|
const country = new Parse.Object('Country');
|
|
country.set('code', 'FR');
|
|
await country.save();
|
|
|
|
const country2 = new Parse.Object('Country');
|
|
country2.set('code', 'US');
|
|
await country2.save();
|
|
|
|
const city = new Parse.Object('City');
|
|
city.set('country', 'FR');
|
|
city.set('name', 'city1');
|
|
await city.save();
|
|
|
|
const city2 = new Parse.Object('City');
|
|
city2.set('country', 'US');
|
|
city2.set('name', 'city2');
|
|
await city2.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: {
|
|
cities: { edges: result },
|
|
},
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query inQueryKey($where: CityWhereInput) {
|
|
cities(where: $where) {
|
|
edges {
|
|
node {
|
|
country
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
variables: {
|
|
where: {
|
|
country: {
|
|
inQueryKey: {
|
|
query: {
|
|
className: 'Country',
|
|
where: { code: { equalTo: 'US' } },
|
|
},
|
|
key: 'code',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.length).toEqual(1);
|
|
expect(result[0].node.name).toEqual('city2');
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support order, skip and first arguments', async () => {
|
|
const promises = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
const obj = new Parse.Object('SomeClass');
|
|
obj.set('someField', `someValue${i < 10 ? '0' : ''}${i}`);
|
|
obj.set('numberField', i % 3);
|
|
promises.push(obj.save());
|
|
}
|
|
await Promise.all(promises);
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects(
|
|
$where: SomeClassWhereInput
|
|
$order: [SomeClassOrder!]
|
|
$skip: Int
|
|
$first: Int
|
|
) {
|
|
find: someClasses(
|
|
where: $where
|
|
order: $order
|
|
skip: $skip
|
|
first: $first
|
|
) {
|
|
edges {
|
|
node {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
someField: {
|
|
matchesRegex: '^someValue',
|
|
},
|
|
},
|
|
order: ['numberField_DESC', 'someField_ASC'],
|
|
skip: 4,
|
|
first: 2,
|
|
},
|
|
});
|
|
|
|
expect(
|
|
result.data.find.edges.map(obj => obj.node.someField)
|
|
).toEqual(['someValue14', 'someValue17']);
|
|
});
|
|
|
|
it('should support pagination', async () => {
|
|
const numberArray = (first, last) => {
|
|
const array = [];
|
|
for (let i = first; i <= last; i++) {
|
|
array.push(i);
|
|
}
|
|
return array;
|
|
};
|
|
|
|
const promises = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
const obj = new Parse.Object('SomeClass');
|
|
obj.set('numberField', i);
|
|
promises.push(obj.save());
|
|
}
|
|
await Promise.all(promises);
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const find = async ({ skip, after, first, before, last } = {}) => {
|
|
return await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects(
|
|
$order: [SomeClassOrder!]
|
|
$skip: Int
|
|
$after: String
|
|
$first: Int
|
|
$before: String
|
|
$last: Int
|
|
) {
|
|
someClasses(
|
|
order: $order
|
|
skip: $skip
|
|
after: $after
|
|
first: $first
|
|
before: $before
|
|
last: $last
|
|
) {
|
|
edges {
|
|
cursor
|
|
node {
|
|
numberField
|
|
}
|
|
}
|
|
count
|
|
pageInfo {
|
|
hasPreviousPage
|
|
startCursor
|
|
endCursor
|
|
hasNextPage
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
order: ['numberField_ASC'],
|
|
skip,
|
|
after,
|
|
first,
|
|
before,
|
|
last,
|
|
},
|
|
});
|
|
};
|
|
|
|
let result = await find();
|
|
expect(
|
|
result.data.someClasses.edges.map(edge => edge.node.numberField)
|
|
).toEqual(numberArray(0, 99));
|
|
expect(result.data.someClasses.count).toEqual(100);
|
|
expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(
|
|
false
|
|
);
|
|
expect(result.data.someClasses.pageInfo.startCursor).toEqual(
|
|
result.data.someClasses.edges[0].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.endCursor).toEqual(
|
|
result.data.someClasses.edges[99].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(false);
|
|
|
|
result = await find({ first: 10 });
|
|
expect(
|
|
result.data.someClasses.edges.map(edge => edge.node.numberField)
|
|
).toEqual(numberArray(0, 9));
|
|
expect(result.data.someClasses.count).toEqual(100);
|
|
expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(
|
|
false
|
|
);
|
|
expect(result.data.someClasses.pageInfo.startCursor).toEqual(
|
|
result.data.someClasses.edges[0].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.endCursor).toEqual(
|
|
result.data.someClasses.edges[9].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true);
|
|
|
|
result = await find({
|
|
first: 10,
|
|
after: result.data.someClasses.pageInfo.endCursor,
|
|
});
|
|
expect(
|
|
result.data.someClasses.edges.map(edge => edge.node.numberField)
|
|
).toEqual(numberArray(10, 19));
|
|
expect(result.data.someClasses.count).toEqual(100);
|
|
expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(
|
|
true
|
|
);
|
|
expect(result.data.someClasses.pageInfo.startCursor).toEqual(
|
|
result.data.someClasses.edges[0].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.endCursor).toEqual(
|
|
result.data.someClasses.edges[9].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true);
|
|
|
|
result = await find({ last: 10 });
|
|
expect(
|
|
result.data.someClasses.edges.map(edge => edge.node.numberField)
|
|
).toEqual(numberArray(90, 99));
|
|
expect(result.data.someClasses.count).toEqual(100);
|
|
expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(
|
|
true
|
|
);
|
|
expect(result.data.someClasses.pageInfo.startCursor).toEqual(
|
|
result.data.someClasses.edges[0].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.endCursor).toEqual(
|
|
result.data.someClasses.edges[9].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(false);
|
|
|
|
result = await find({
|
|
last: 10,
|
|
before: result.data.someClasses.pageInfo.startCursor,
|
|
});
|
|
expect(
|
|
result.data.someClasses.edges.map(edge => edge.node.numberField)
|
|
).toEqual(numberArray(80, 89));
|
|
expect(result.data.someClasses.count).toEqual(100);
|
|
expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(
|
|
true
|
|
);
|
|
expect(result.data.someClasses.pageInfo.startCursor).toEqual(
|
|
result.data.someClasses.edges[0].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.endCursor).toEqual(
|
|
result.data.someClasses.edges[9].cursor
|
|
);
|
|
expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true);
|
|
});
|
|
|
|
it('should support count', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const where = {
|
|
someField: {
|
|
in: ['someValue1', 'someValue2', 'someValue3'],
|
|
},
|
|
OR: [
|
|
{
|
|
pointerToUser: {
|
|
have: {
|
|
objectId: {
|
|
equalTo: user5.id,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: {
|
|
equalTo: object1.id,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects(
|
|
$where: GraphQLClassWhereInput
|
|
$first: Int
|
|
) {
|
|
find: graphQLClasses(where: $where, first: $first) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where,
|
|
first: 0,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.find.edges).toEqual([]);
|
|
expect(result.data.find.count).toEqual(2);
|
|
});
|
|
|
|
it('should only count', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const where = {
|
|
someField: {
|
|
in: ['someValue1', 'someValue2', 'someValue3'],
|
|
},
|
|
OR: [
|
|
{
|
|
pointerToUser: {
|
|
have: {
|
|
objectId: {
|
|
equalTo: user5.id,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: {
|
|
equalTo: object1.id,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects($where: GraphQLClassWhereInput) {
|
|
find: graphQLClasses(where: $where) {
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.find.edges).toBeUndefined();
|
|
expect(result.data.find.count).toEqual(2);
|
|
});
|
|
|
|
it('should respect max limit', async () => {
|
|
parseServer = await global.reconfigureServer({
|
|
maxLimit: 10,
|
|
});
|
|
|
|
const promises = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
const obj = new Parse.Object('SomeClass');
|
|
promises.push(obj.save());
|
|
}
|
|
await Promise.all(promises);
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects($limit: Int) {
|
|
find: someClasses(
|
|
where: { id: { exists: true } }
|
|
first: $limit
|
|
) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
count
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
limit: 50,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.find.edges.length).toEqual(10);
|
|
expect(result.data.find.count).toEqual(100);
|
|
});
|
|
|
|
it('should support keys argument', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result1 = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObject($where: GraphQLClassWhereInput) {
|
|
find: graphQLClasses(where: $where) {
|
|
edges {
|
|
node {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
id: { equalTo: object3.id },
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
const result2 = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObject($where: GraphQLClassWhereInput) {
|
|
find: graphQLClasses(where: $where) {
|
|
edges {
|
|
node {
|
|
someField
|
|
pointerToUser {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
id: { equalTo: object3.id },
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result1.data.find.edges[0].node.someField).toBeDefined();
|
|
expect(
|
|
result1.data.find.edges[0].node.pointerToUser
|
|
).toBeUndefined();
|
|
expect(result2.data.find.edges[0].node.someField).toBeDefined();
|
|
expect(result2.data.find.edges[0].node.pointerToUser).toBeDefined();
|
|
});
|
|
|
|
it('should support include argument', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const where = {
|
|
id: {
|
|
equalTo: object3.id,
|
|
},
|
|
};
|
|
|
|
const result1 = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObject($where: GraphQLClassWhereInput) {
|
|
find: graphQLClasses(where: $where) {
|
|
edges {
|
|
node {
|
|
pointerToUser {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
const result2 = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObject($where: GraphQLClassWhereInput) {
|
|
find: graphQLClasses(where: $where) {
|
|
edges {
|
|
node {
|
|
pointerToUser {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
expect(
|
|
result1.data.find.edges[0].node.pointerToUser.username
|
|
).toBeUndefined();
|
|
expect(
|
|
result2.data.find.edges[0].node.pointerToUser.username
|
|
).toBeDefined();
|
|
});
|
|
|
|
describe_only_db('mongo')('read preferences', () => {
|
|
it('should read from primary by default', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const databaseAdapter =
|
|
parseServer.config.databaseController.adapter;
|
|
spyOn(
|
|
databaseAdapter.database.serverConfig,
|
|
'cursor'
|
|
).and.callThrough();
|
|
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects {
|
|
find: graphQLClasses {
|
|
edges {
|
|
node {
|
|
pointerToUser {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
let foundGraphQLClassReadPreference = false;
|
|
let foundUserClassReadPreference = false;
|
|
databaseAdapter.database.serverConfig.cursor.calls
|
|
.all()
|
|
.forEach(call => {
|
|
if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) {
|
|
foundGraphQLClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.PRIMARY
|
|
);
|
|
} else if (call.args[0].ns.collection.indexOf('_User') >= 0) {
|
|
foundUserClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.PRIMARY
|
|
);
|
|
}
|
|
});
|
|
|
|
expect(foundGraphQLClassReadPreference).toBe(true);
|
|
expect(foundUserClassReadPreference).toBe(true);
|
|
});
|
|
|
|
it('should support readPreference argument', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const databaseAdapter =
|
|
parseServer.config.databaseController.adapter;
|
|
spyOn(
|
|
databaseAdapter.database.serverConfig,
|
|
'cursor'
|
|
).and.callThrough();
|
|
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects {
|
|
find: graphQLClasses(
|
|
options: { readPreference: SECONDARY }
|
|
) {
|
|
edges {
|
|
node {
|
|
pointerToUser {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
let foundGraphQLClassReadPreference = false;
|
|
let foundUserClassReadPreference = false;
|
|
databaseAdapter.database.serverConfig.cursor.calls
|
|
.all()
|
|
.forEach(call => {
|
|
if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) {
|
|
foundGraphQLClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.SECONDARY
|
|
);
|
|
} else if (call.args[0].ns.collection.indexOf('_User') >= 0) {
|
|
foundUserClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.SECONDARY
|
|
);
|
|
}
|
|
});
|
|
|
|
expect(foundGraphQLClassReadPreference).toBe(true);
|
|
expect(foundUserClassReadPreference).toBe(true);
|
|
});
|
|
|
|
it('should support includeReadPreference argument', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const databaseAdapter =
|
|
parseServer.config.databaseController.adapter;
|
|
spyOn(
|
|
databaseAdapter.database.serverConfig,
|
|
'cursor'
|
|
).and.callThrough();
|
|
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects {
|
|
graphQLClasses(
|
|
options: {
|
|
readPreference: SECONDARY
|
|
includeReadPreference: NEAREST
|
|
}
|
|
) {
|
|
edges {
|
|
node {
|
|
pointerToUser {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
let foundGraphQLClassReadPreference = false;
|
|
let foundUserClassReadPreference = false;
|
|
databaseAdapter.database.serverConfig.cursor.calls
|
|
.all()
|
|
.forEach(call => {
|
|
if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) {
|
|
foundGraphQLClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.SECONDARY
|
|
);
|
|
} else if (call.args[0].ns.collection.indexOf('_User') >= 0) {
|
|
foundUserClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.NEAREST
|
|
);
|
|
}
|
|
});
|
|
|
|
expect(foundGraphQLClassReadPreference).toBe(true);
|
|
expect(foundUserClassReadPreference).toBe(true);
|
|
});
|
|
|
|
it('should support subqueryReadPreference argument', async () => {
|
|
try {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const databaseAdapter =
|
|
parseServer.config.databaseController.adapter;
|
|
spyOn(
|
|
databaseAdapter.database.serverConfig,
|
|
'cursor'
|
|
).and.callThrough();
|
|
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObjects($where: GraphQLClassWhereInput) {
|
|
find: graphQLClasses(
|
|
where: $where
|
|
options: {
|
|
readPreference: SECONDARY
|
|
subqueryReadPreference: NEAREST
|
|
}
|
|
) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
pointerToUser: {
|
|
have: {
|
|
objectId: {
|
|
equalTo: 'xxxx',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
let foundGraphQLClassReadPreference = false;
|
|
let foundUserClassReadPreference = false;
|
|
databaseAdapter.database.serverConfig.cursor.calls
|
|
.all()
|
|
.forEach(call => {
|
|
if (
|
|
call.args[0].ns.collection.indexOf('GraphQLClass') >= 0
|
|
) {
|
|
foundGraphQLClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.SECONDARY
|
|
);
|
|
} else if (
|
|
call.args[0].ns.collection.indexOf('_User') >= 0
|
|
) {
|
|
foundUserClassReadPreference = true;
|
|
expect(call.args[0].options.readPreference.mode).toBe(
|
|
ReadPreference.NEAREST
|
|
);
|
|
}
|
|
});
|
|
|
|
expect(foundGraphQLClassReadPreference).toBe(true);
|
|
expect(foundUserClassReadPreference).toBe(true);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should order by multiple fields', async () => {
|
|
await prepareData();
|
|
|
|
await resetGraphQLCache();
|
|
|
|
let result;
|
|
try {
|
|
result = await apolloClient.query({
|
|
query: gql`
|
|
query OrderByMultipleFields($order: [GraphQLClassOrder!]) {
|
|
graphQLClasses(order: $order) {
|
|
edges {
|
|
node {
|
|
objectId
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
order: ['someOtherField_DESC', 'someField_ASC'],
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
|
|
expect(
|
|
result.data.graphQLClasses.edges.map(edge => edge.node.objectId)
|
|
).toEqual([object3.id, object1.id, object2.id]);
|
|
});
|
|
|
|
it_only_db('mongo')(
|
|
'should order by multiple fields on a relation field',
|
|
async () => {
|
|
await prepareData();
|
|
|
|
const parentObject = new Parse.Object('ParentClass');
|
|
const relation = parentObject.relation('graphQLClasses');
|
|
relation.add(object1);
|
|
relation.add(object2);
|
|
relation.add(object3);
|
|
await parentObject.save();
|
|
|
|
await resetGraphQLCache();
|
|
|
|
let result;
|
|
try {
|
|
result = await apolloClient.query({
|
|
query: gql`
|
|
query OrderByMultipleFieldsOnRelation(
|
|
$id: ID!
|
|
$order: [GraphQLClassOrder!]
|
|
) {
|
|
parentClass(id: $id) {
|
|
graphQLClasses(order: $order) {
|
|
edges {
|
|
node {
|
|
objectId
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: parentObject.id,
|
|
order: ['someOtherField_DESC', 'someField_ASC'],
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
|
|
expect(
|
|
result.data.parentClass.graphQLClasses.edges.map(
|
|
edge => edge.node.objectId
|
|
)
|
|
).toEqual([object3.id, object1.id, object2.id]);
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Objects Mutations', () => {
|
|
describe('Create', () => {
|
|
it('should return specific type object using class specific mutation', async () => {
|
|
const clientMutationId = uuidv4();
|
|
const customerSchema = new Parse.Schema('Customer');
|
|
customerSchema.addString('someField');
|
|
await customerSchema.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateCustomer($input: CreateCustomerInput!) {
|
|
createCustomer(input: $input) {
|
|
clientMutationId
|
|
customer {
|
|
id
|
|
objectId
|
|
createdAt
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
fields: {
|
|
someField: 'someValue',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.createCustomer.clientMutationId).toEqual(
|
|
clientMutationId
|
|
);
|
|
expect(result.data.createCustomer.customer.id).toBeDefined();
|
|
expect(result.data.createCustomer.customer.someField).toEqual(
|
|
'someValue'
|
|
);
|
|
|
|
const customer = await new Parse.Query('Customer').get(
|
|
result.data.createCustomer.customer.objectId
|
|
);
|
|
|
|
expect(customer.createdAt).toEqual(
|
|
new Date(result.data.createCustomer.customer.createdAt)
|
|
);
|
|
expect(customer.get('someField')).toEqual('someValue');
|
|
});
|
|
|
|
it('should respect level permissions', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
async function createObject(className, headers) {
|
|
const getClassName =
|
|
className.charAt(0).toLowerCase() + className.slice(1);
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject {
|
|
create${className}(input: {}) {
|
|
${getClassName} {
|
|
id
|
|
createdAt
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers,
|
|
},
|
|
});
|
|
|
|
const specificCreate =
|
|
result.data[`create${className}`][getClassName];
|
|
expect(specificCreate.id).toBeDefined();
|
|
expect(specificCreate.createdAt).toBeDefined();
|
|
|
|
return result;
|
|
}
|
|
|
|
await expectAsync(createObject('GraphQLClass')).toBeRejectedWith(
|
|
jasmine.stringMatching(
|
|
'Permission denied for action create on class GraphQLClass'
|
|
)
|
|
);
|
|
await expectAsync(createObject('PublicClass')).toBeResolved();
|
|
await expectAsync(
|
|
createObject('GraphQLClass', { 'X-Parse-Master-Key': 'test' })
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
createObject('PublicClass', { 'X-Parse-Master-Key': 'test' })
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
createObject('GraphQLClass', {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
createObject('PublicClass', {
|
|
'X-Parse-Session-Token': user1.getSessionToken(),
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
createObject('GraphQLClass', {
|
|
'X-Parse-Session-Token': user2.getSessionToken(),
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
createObject('PublicClass', {
|
|
'X-Parse-Session-Token': user2.getSessionToken(),
|
|
})
|
|
).toBeResolved();
|
|
await expectAsync(
|
|
createObject('GraphQLClass', {
|
|
'X-Parse-Session-Token': user4.getSessionToken(),
|
|
})
|
|
).toBeRejectedWith(
|
|
jasmine.stringMatching(
|
|
'Permission denied for action create on class GraphQLClass'
|
|
)
|
|
);
|
|
await expectAsync(
|
|
createObject('PublicClass', {
|
|
'X-Parse-Session-Token': user4.getSessionToken(),
|
|
})
|
|
).toBeResolved();
|
|
});
|
|
});
|
|
|
|
describe('Update', () => {
|
|
it('should return specific type object using class specific mutation', async () => {
|
|
const clientMutationId = uuidv4();
|
|
const obj = new Parse.Object('Customer');
|
|
obj.set('someField1', 'someField1Value1');
|
|
obj.set('someField2', 'someField2Value1');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation UpdateCustomer($input: UpdateCustomerInput!) {
|
|
updateCustomer(input: $input) {
|
|
clientMutationId
|
|
customer {
|
|
updatedAt
|
|
someField1
|
|
someField2
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
id: obj.id,
|
|
fields: {
|
|
someField1: 'someField1Value2',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.updateCustomer.clientMutationId).toEqual(
|
|
clientMutationId
|
|
);
|
|
expect(result.data.updateCustomer.customer.updatedAt).toBeDefined();
|
|
expect(result.data.updateCustomer.customer.someField1).toEqual(
|
|
'someField1Value2'
|
|
);
|
|
expect(result.data.updateCustomer.customer.someField2).toEqual(
|
|
'someField2Value1'
|
|
);
|
|
|
|
await obj.fetch();
|
|
|
|
expect(obj.get('someField1')).toEqual('someField1Value2');
|
|
expect(obj.get('someField2')).toEqual('someField2Value1');
|
|
});
|
|
|
|
it('should return only id using class specific mutation', async () => {
|
|
const obj = new Parse.Object('Customer');
|
|
obj.set('someField1', 'someField1Value1');
|
|
obj.set('someField2', 'someField2Value1');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation UpdateCustomer(
|
|
$id: ID!
|
|
$fields: UpdateCustomerFieldsInput
|
|
) {
|
|
updateCustomer(input: { id: $id, fields: $fields }) {
|
|
customer {
|
|
id
|
|
objectId
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: obj.id,
|
|
fields: {
|
|
someField1: 'someField1Value2',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.updateCustomer.customer.objectId).toEqual(
|
|
obj.id
|
|
);
|
|
|
|
await obj.fetch();
|
|
|
|
expect(obj.get('someField1')).toEqual('someField1Value2');
|
|
expect(obj.get('someField2')).toEqual('someField2Value1');
|
|
});
|
|
|
|
it('should respect level permissions', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
async function updateObject(className, id, fields, headers) {
|
|
return await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation UpdateSomeObject(
|
|
$id: ID!
|
|
$fields: Update${className}FieldsInput
|
|
) {
|
|
update: update${className}(input: {
|
|
id: $id
|
|
fields: $fields
|
|
clientMutationId: "someid"
|
|
}) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id,
|
|
fields,
|
|
},
|
|
context: {
|
|
headers,
|
|
},
|
|
});
|
|
}
|
|
|
|
await Promise.all(
|
|
objects.slice(0, 3).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
updateObject(obj.className, obj.id, {
|
|
someField: 'changedValue1',
|
|
})
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
expect(
|
|
(
|
|
await updateObject(object4.className, object4.id, {
|
|
someField: 'changedValue1',
|
|
})
|
|
).data.update.clientMutationId
|
|
).toBeDefined();
|
|
await object4.fetch({ useMasterKey: true });
|
|
expect(object4.get('someField')).toEqual('changedValue1');
|
|
await Promise.all(
|
|
objects.map(async obj => {
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue2' },
|
|
{ 'X-Parse-Master-Key': 'test' }
|
|
)
|
|
).data.update.clientMutationId
|
|
).toBeDefined();
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual('changedValue2');
|
|
})
|
|
);
|
|
await Promise.all(
|
|
objects.map(async obj => {
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue3' },
|
|
{ 'X-Parse-Session-Token': user1.getSessionToken() }
|
|
)
|
|
).data.update.clientMutationId
|
|
).toBeDefined();
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual('changedValue3');
|
|
})
|
|
);
|
|
await Promise.all(
|
|
objects.map(async obj => {
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue4' },
|
|
{ 'X-Parse-Session-Token': user2.getSessionToken() }
|
|
)
|
|
).data.update.clientMutationId
|
|
).toBeDefined();
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual('changedValue4');
|
|
})
|
|
);
|
|
await Promise.all(
|
|
[object1, object3, object4].map(async obj => {
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue5' },
|
|
{ 'X-Parse-Session-Token': user3.getSessionToken() }
|
|
)
|
|
).data.update.clientMutationId
|
|
).toBeDefined();
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual('changedValue5');
|
|
})
|
|
);
|
|
const originalFieldValue = object2.get('someField');
|
|
await expectAsync(
|
|
updateObject(
|
|
object2.className,
|
|
object2.id,
|
|
{ someField: 'changedValue5' },
|
|
{ 'X-Parse-Session-Token': user3.getSessionToken() }
|
|
)
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await object2.fetch({ useMasterKey: true });
|
|
expect(object2.get('someField')).toEqual(originalFieldValue);
|
|
await Promise.all(
|
|
objects.slice(0, 3).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue6' },
|
|
{ 'X-Parse-Session-Token': user4.getSessionToken() }
|
|
)
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
object4.className,
|
|
object4.id,
|
|
{ someField: 'changedValue6' },
|
|
{ 'X-Parse-Session-Token': user4.getSessionToken() }
|
|
)
|
|
).data.update.clientMutationId
|
|
).toBeDefined();
|
|
await object4.fetch({ useMasterKey: true });
|
|
expect(object4.get('someField')).toEqual('changedValue6');
|
|
await Promise.all(
|
|
objects.slice(0, 2).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue7' },
|
|
{ 'X-Parse-Session-Token': user5.getSessionToken() }
|
|
)
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
object3.className,
|
|
object3.id,
|
|
{ someField: 'changedValue7' },
|
|
{ 'X-Parse-Session-Token': user5.getSessionToken() }
|
|
)
|
|
).data.update.clientMutationId
|
|
).toBeDefined();
|
|
await object3.fetch({ useMasterKey: true });
|
|
expect(object3.get('someField')).toEqual('changedValue7');
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
object4.className,
|
|
object4.id,
|
|
{ someField: 'changedValue7' },
|
|
{ 'X-Parse-Session-Token': user5.getSessionToken() }
|
|
)
|
|
).data.update.clientMutationId
|
|
).toBeDefined();
|
|
await object4.fetch({ useMasterKey: true });
|
|
expect(object4.get('someField')).toEqual('changedValue7');
|
|
});
|
|
|
|
it('should respect level permissions with specific class mutation', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
function updateObject(className, id, fields, headers) {
|
|
return apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation UpdateSomeObject(
|
|
$id: ID!
|
|
$fields: Update${className}FieldsInput
|
|
) {
|
|
update${className}(input: {
|
|
id: $id
|
|
fields: $fields
|
|
}) {
|
|
${className.charAt(0).toLowerCase() +
|
|
className.slice(1)} {
|
|
updatedAt
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id,
|
|
fields,
|
|
},
|
|
context: {
|
|
headers,
|
|
},
|
|
});
|
|
}
|
|
|
|
await Promise.all(
|
|
objects.slice(0, 3).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
updateObject(obj.className, obj.id, {
|
|
someField: 'changedValue1',
|
|
})
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
expect(
|
|
(
|
|
await updateObject(object4.className, object4.id, {
|
|
someField: 'changedValue1',
|
|
})
|
|
).data[`update${object4.className}`][
|
|
object4.className.charAt(0).toLowerCase() +
|
|
object4.className.slice(1)
|
|
].updatedAt
|
|
).toBeDefined();
|
|
await object4.fetch({ useMasterKey: true });
|
|
expect(object4.get('someField')).toEqual('changedValue1');
|
|
await Promise.all(
|
|
objects.map(async obj => {
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue2' },
|
|
{ 'X-Parse-Master-Key': 'test' }
|
|
)
|
|
).data[`update${obj.className}`][
|
|
obj.className.charAt(0).toLowerCase() +
|
|
obj.className.slice(1)
|
|
].updatedAt
|
|
).toBeDefined();
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual('changedValue2');
|
|
})
|
|
);
|
|
await Promise.all(
|
|
objects.map(async obj => {
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue3' },
|
|
{ 'X-Parse-Session-Token': user1.getSessionToken() }
|
|
)
|
|
).data[`update${obj.className}`][
|
|
obj.className.charAt(0).toLowerCase() +
|
|
obj.className.slice(1)
|
|
].updatedAt
|
|
).toBeDefined();
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual('changedValue3');
|
|
})
|
|
);
|
|
await Promise.all(
|
|
objects.map(async obj => {
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue4' },
|
|
{ 'X-Parse-Session-Token': user2.getSessionToken() }
|
|
)
|
|
).data[`update${obj.className}`][
|
|
obj.className.charAt(0).toLowerCase() +
|
|
obj.className.slice(1)
|
|
].updatedAt
|
|
).toBeDefined();
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual('changedValue4');
|
|
})
|
|
);
|
|
await Promise.all(
|
|
[object1, object3, object4].map(async obj => {
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue5' },
|
|
{ 'X-Parse-Session-Token': user3.getSessionToken() }
|
|
)
|
|
).data[`update${obj.className}`][
|
|
obj.className.charAt(0).toLowerCase() +
|
|
obj.className.slice(1)
|
|
].updatedAt
|
|
).toBeDefined();
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual('changedValue5');
|
|
})
|
|
);
|
|
const originalFieldValue = object2.get('someField');
|
|
await expectAsync(
|
|
updateObject(
|
|
object2.className,
|
|
object2.id,
|
|
{ someField: 'changedValue5' },
|
|
{ 'X-Parse-Session-Token': user3.getSessionToken() }
|
|
)
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await object2.fetch({ useMasterKey: true });
|
|
expect(object2.get('someField')).toEqual(originalFieldValue);
|
|
await Promise.all(
|
|
objects.slice(0, 3).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue6' },
|
|
{ 'X-Parse-Session-Token': user4.getSessionToken() }
|
|
)
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
object4.className,
|
|
object4.id,
|
|
{ someField: 'changedValue6' },
|
|
{ 'X-Parse-Session-Token': user4.getSessionToken() }
|
|
)
|
|
).data[`update${object4.className}`][
|
|
object4.className.charAt(0).toLowerCase() +
|
|
object4.className.slice(1)
|
|
].updatedAt
|
|
).toBeDefined();
|
|
await object4.fetch({ useMasterKey: true });
|
|
expect(object4.get('someField')).toEqual('changedValue6');
|
|
await Promise.all(
|
|
objects.slice(0, 2).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
updateObject(
|
|
obj.className,
|
|
obj.id,
|
|
{ someField: 'changedValue7' },
|
|
{ 'X-Parse-Session-Token': user5.getSessionToken() }
|
|
)
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
object3.className,
|
|
object3.id,
|
|
{ someField: 'changedValue7' },
|
|
{ 'X-Parse-Session-Token': user5.getSessionToken() }
|
|
)
|
|
).data[`update${object3.className}`][
|
|
object3.className.charAt(0).toLowerCase() +
|
|
object3.className.slice(1)
|
|
].updatedAt
|
|
).toBeDefined();
|
|
await object3.fetch({ useMasterKey: true });
|
|
expect(object3.get('someField')).toEqual('changedValue7');
|
|
expect(
|
|
(
|
|
await updateObject(
|
|
object4.className,
|
|
object4.id,
|
|
{ someField: 'changedValue7' },
|
|
{ 'X-Parse-Session-Token': user5.getSessionToken() }
|
|
)
|
|
).data[`update${object4.className}`][
|
|
object4.className.charAt(0).toLowerCase() +
|
|
object4.className.slice(1)
|
|
].updatedAt
|
|
).toBeDefined();
|
|
await object4.fetch({ useMasterKey: true });
|
|
expect(object4.get('someField')).toEqual('changedValue7');
|
|
});
|
|
});
|
|
|
|
describe('Delete', () => {
|
|
it('should return a specific type using class specific mutation', async () => {
|
|
const clientMutationId = uuidv4();
|
|
const obj = new Parse.Object('Customer');
|
|
obj.set('someField1', 'someField1Value1');
|
|
obj.set('someField2', 'someField2Value1');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation DeleteCustomer($input: DeleteCustomerInput!) {
|
|
deleteCustomer(input: $input) {
|
|
clientMutationId
|
|
customer {
|
|
id
|
|
objectId
|
|
someField1
|
|
someField2
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
id: obj.id,
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.deleteCustomer.clientMutationId).toEqual(
|
|
clientMutationId
|
|
);
|
|
expect(result.data.deleteCustomer.customer.objectId).toEqual(
|
|
obj.id
|
|
);
|
|
expect(result.data.deleteCustomer.customer.someField1).toEqual(
|
|
'someField1Value1'
|
|
);
|
|
expect(result.data.deleteCustomer.customer.someField2).toEqual(
|
|
'someField2Value1'
|
|
);
|
|
|
|
await expectAsync(
|
|
obj.fetch({ useMasterKey: true })
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
});
|
|
|
|
it('should respect level permissions', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
function deleteObject(className, id, headers) {
|
|
return apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation DeleteSomeObject(
|
|
$id: ID!
|
|
) {
|
|
delete: delete${className}(input: { id: $id }) {
|
|
${className.charAt(0).toLowerCase() +
|
|
className.slice(1)} {
|
|
objectId
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id,
|
|
},
|
|
context: {
|
|
headers,
|
|
},
|
|
});
|
|
}
|
|
|
|
await Promise.all(
|
|
objects.slice(0, 3).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
deleteObject(obj.className, obj.id)
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
await Promise.all(
|
|
objects.slice(0, 3).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
deleteObject(obj.className, obj.id, {
|
|
'X-Parse-Session-Token': user4.getSessionToken(),
|
|
})
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
expect(
|
|
(await deleteObject(object4.className, object4.id)).data.delete[
|
|
object4.className.charAt(0).toLowerCase() +
|
|
object4.className.slice(1)
|
|
]
|
|
).toEqual({ objectId: object4.id, __typename: 'PublicClass' });
|
|
await expectAsync(
|
|
object4.fetch({ useMasterKey: true })
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
expect(
|
|
(
|
|
await deleteObject(object1.className, object1.id, {
|
|
'X-Parse-Master-Key': 'test',
|
|
})
|
|
).data.delete[
|
|
object1.className.charAt(0).toLowerCase() +
|
|
object1.className.slice(1)
|
|
]
|
|
).toEqual({ objectId: object1.id, __typename: 'GraphQLClass' });
|
|
await expectAsync(
|
|
object1.fetch({ useMasterKey: true })
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
expect(
|
|
(
|
|
await deleteObject(object2.className, object2.id, {
|
|
'X-Parse-Session-Token': user2.getSessionToken(),
|
|
})
|
|
).data.delete[
|
|
object2.className.charAt(0).toLowerCase() +
|
|
object2.className.slice(1)
|
|
]
|
|
).toEqual({ objectId: object2.id, __typename: 'GraphQLClass' });
|
|
await expectAsync(
|
|
object2.fetch({ useMasterKey: true })
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
expect(
|
|
(
|
|
await deleteObject(object3.className, object3.id, {
|
|
'X-Parse-Session-Token': user5.getSessionToken(),
|
|
})
|
|
).data.delete[
|
|
object3.className.charAt(0).toLowerCase() +
|
|
object3.className.slice(1)
|
|
]
|
|
).toEqual({ objectId: object3.id, __typename: 'GraphQLClass' });
|
|
await expectAsync(
|
|
object3.fetch({ useMasterKey: true })
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
});
|
|
|
|
it('should respect level permissions with specific class mutation', async () => {
|
|
await prepareData();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
function deleteObject(className, id, headers) {
|
|
return apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation DeleteSomeObject(
|
|
$id: ID!
|
|
) {
|
|
delete${className}(input: { id: $id }) {
|
|
${className.charAt(0).toLowerCase() +
|
|
className.slice(1)} {
|
|
objectId
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id,
|
|
},
|
|
context: {
|
|
headers,
|
|
},
|
|
});
|
|
}
|
|
|
|
await Promise.all(
|
|
objects.slice(0, 3).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
deleteObject(obj.className, obj.id)
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
await Promise.all(
|
|
objects.slice(0, 3).map(async obj => {
|
|
const originalFieldValue = obj.get('someField');
|
|
await expectAsync(
|
|
deleteObject(obj.className, obj.id, {
|
|
'X-Parse-Session-Token': user4.getSessionToken(),
|
|
})
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
await obj.fetch({ useMasterKey: true });
|
|
expect(obj.get('someField')).toEqual(originalFieldValue);
|
|
})
|
|
);
|
|
expect(
|
|
(await deleteObject(object4.className, object4.id)).data[
|
|
`delete${object4.className}`
|
|
][
|
|
object4.className.charAt(0).toLowerCase() +
|
|
object4.className.slice(1)
|
|
].objectId
|
|
).toEqual(object4.id);
|
|
await expectAsync(
|
|
object4.fetch({ useMasterKey: true })
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
expect(
|
|
(
|
|
await deleteObject(object1.className, object1.id, {
|
|
'X-Parse-Master-Key': 'test',
|
|
})
|
|
).data[`delete${object1.className}`][
|
|
object1.className.charAt(0).toLowerCase() +
|
|
object1.className.slice(1)
|
|
].objectId
|
|
).toEqual(object1.id);
|
|
await expectAsync(
|
|
object1.fetch({ useMasterKey: true })
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
expect(
|
|
(
|
|
await deleteObject(object2.className, object2.id, {
|
|
'X-Parse-Session-Token': user2.getSessionToken(),
|
|
})
|
|
).data[`delete${object2.className}`][
|
|
object2.className.charAt(0).toLowerCase() +
|
|
object2.className.slice(1)
|
|
].objectId
|
|
).toEqual(object2.id);
|
|
await expectAsync(
|
|
object2.fetch({ useMasterKey: true })
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
expect(
|
|
(
|
|
await deleteObject(object3.className, object3.id, {
|
|
'X-Parse-Session-Token': user5.getSessionToken(),
|
|
})
|
|
).data[`delete${object3.className}`][
|
|
object3.className.charAt(0).toLowerCase() +
|
|
object3.className.slice(1)
|
|
].objectId
|
|
).toEqual(object3.id);
|
|
await expectAsync(
|
|
object3.fetch({ useMasterKey: true })
|
|
).toBeRejectedWith(jasmine.stringMatching('Object not found'));
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Files Mutations', () => {
|
|
describe('Create', () => {
|
|
it('should return File object', async () => {
|
|
const clientMutationId = uuidv4();
|
|
|
|
parseServer = await global.reconfigureServer({
|
|
publicServerURL: 'http://localhost:13377/parse',
|
|
});
|
|
|
|
const body = new FormData();
|
|
body.append(
|
|
'operations',
|
|
JSON.stringify({
|
|
query: `
|
|
mutation CreateFile($input: CreateFileInput!) {
|
|
createFile(input: $input) {
|
|
clientMutationId
|
|
fileInfo {
|
|
name
|
|
url
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
upload: null,
|
|
},
|
|
},
|
|
})
|
|
);
|
|
body.append(
|
|
'map',
|
|
JSON.stringify({ 1: ['variables.input.upload'] })
|
|
);
|
|
body.append('1', 'My File Content', {
|
|
filename: 'myFileName.txt',
|
|
contentType: 'text/plain',
|
|
});
|
|
|
|
let res = await fetch('http://localhost:13377/graphql', {
|
|
method: 'POST',
|
|
headers,
|
|
body,
|
|
});
|
|
|
|
expect(res.status).toEqual(200);
|
|
|
|
const result = JSON.parse(await res.text());
|
|
|
|
expect(result.data.createFile.clientMutationId).toEqual(
|
|
clientMutationId
|
|
);
|
|
expect(result.data.createFile.fileInfo.name).toEqual(
|
|
jasmine.stringMatching(/_myFileName.txt$/)
|
|
);
|
|
expect(result.data.createFile.fileInfo.url).toEqual(
|
|
jasmine.stringMatching(/_myFileName.txt$/)
|
|
);
|
|
|
|
res = await fetch(result.data.createFile.fileInfo.url);
|
|
|
|
expect(res.status).toEqual(200);
|
|
expect(await res.text()).toEqual('My File Content');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Users Queries', () => {
|
|
it('should return current logged user', async () => {
|
|
const userName = 'user1',
|
|
password = 'user1',
|
|
email = 'emailUser1@parse.com';
|
|
|
|
const user = new Parse.User();
|
|
user.setUsername(userName);
|
|
user.setPassword(password);
|
|
user.setEmail(email);
|
|
await user.signUp();
|
|
|
|
const session = await Parse.Session.current();
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query GetCurrentUser {
|
|
viewer {
|
|
user {
|
|
id
|
|
username
|
|
email
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': session.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
const {
|
|
id,
|
|
username: resultUserName,
|
|
email: resultEmail,
|
|
} = result.data.viewer.user;
|
|
expect(id).toBeDefined();
|
|
expect(resultUserName).toEqual(userName);
|
|
expect(resultEmail).toEqual(email);
|
|
});
|
|
|
|
it('should return logged user including pointer', async () => {
|
|
const foo = new Parse.Object('Foo');
|
|
foo.set('bar', 'hello');
|
|
|
|
const userName = 'user1',
|
|
password = 'user1',
|
|
email = 'emailUser1@parse.com';
|
|
|
|
const user = new Parse.User();
|
|
user.setUsername(userName);
|
|
user.setPassword(password);
|
|
user.setEmail(email);
|
|
user.set('userFoo', foo);
|
|
await user.signUp();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const session = await Parse.Session.current();
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query GetCurrentUser {
|
|
viewer {
|
|
sessionToken
|
|
user {
|
|
id
|
|
objectId
|
|
userFoo {
|
|
bar
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': session.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
const sessionToken = result.data.viewer.sessionToken;
|
|
const { objectId, userFoo: resultFoo } = result.data.viewer.user;
|
|
expect(objectId).toEqual(user.id);
|
|
expect(sessionToken).toBeDefined();
|
|
expect(resultFoo).toBeDefined();
|
|
expect(resultFoo.bar).toEqual('hello');
|
|
});
|
|
});
|
|
|
|
describe('Users Mutations', () => {
|
|
it('should sign user up', async () => {
|
|
const clientMutationId = uuidv4();
|
|
const userSchema = new Parse.Schema('_User');
|
|
userSchema.addString('someField');
|
|
await userSchema.update();
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation SignUp($input: SignUpInput!) {
|
|
signUp(input: $input) {
|
|
clientMutationId
|
|
viewer {
|
|
sessionToken
|
|
user {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
userFields: {
|
|
username: 'user1',
|
|
password: 'user1',
|
|
someField: 'someValue',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.signUp.clientMutationId).toEqual(clientMutationId);
|
|
expect(result.data.signUp.viewer.sessionToken).toBeDefined();
|
|
expect(result.data.signUp.viewer.user.someField).toEqual('someValue');
|
|
expect(typeof result.data.signUp.viewer.sessionToken).toBe('string');
|
|
});
|
|
|
|
it('should log the user in', async () => {
|
|
const clientMutationId = uuidv4();
|
|
const user = new Parse.User();
|
|
user.setUsername('user1');
|
|
user.setPassword('user1');
|
|
user.set('someField', 'someValue');
|
|
await user.signUp();
|
|
await Parse.User.logOut();
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation LogInUser($input: LogInInput!) {
|
|
logIn(input: $input) {
|
|
clientMutationId
|
|
viewer {
|
|
sessionToken
|
|
user {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
username: 'user1',
|
|
password: 'user1',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.logIn.clientMutationId).toEqual(clientMutationId);
|
|
expect(result.data.logIn.viewer.sessionToken).toBeDefined();
|
|
expect(result.data.logIn.viewer.user.someField).toEqual('someValue');
|
|
expect(typeof result.data.logIn.viewer.sessionToken).toBe('string');
|
|
});
|
|
|
|
it('should log the user out', async () => {
|
|
const clientMutationId = uuidv4();
|
|
const user = new Parse.User();
|
|
user.setUsername('user1');
|
|
user.setPassword('user1');
|
|
await user.signUp();
|
|
await Parse.User.logOut();
|
|
|
|
const logIn = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation LogInUser($input: LogInInput!) {
|
|
logIn(input: $input) {
|
|
viewer {
|
|
sessionToken
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
username: 'user1',
|
|
password: 'user1',
|
|
},
|
|
},
|
|
});
|
|
|
|
const sessionToken = logIn.data.logIn.viewer.sessionToken;
|
|
|
|
const logOut = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation LogOutUser($input: LogOutInput!) {
|
|
logOut(input: $input) {
|
|
clientMutationId
|
|
viewer {
|
|
sessionToken
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': sessionToken,
|
|
},
|
|
},
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
},
|
|
},
|
|
});
|
|
expect(logOut.data.logOut.clientMutationId).toEqual(clientMutationId);
|
|
expect(logOut.data.logOut.viewer.sessionToken).toEqual(sessionToken);
|
|
|
|
try {
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetCurrentUser {
|
|
viewer {
|
|
username
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': sessionToken,
|
|
},
|
|
},
|
|
});
|
|
fail('should not retrieve current user due to session token');
|
|
} catch (err) {
|
|
const { statusCode, result } = err.networkError;
|
|
expect(statusCode).toBe(400);
|
|
expect(result).toEqual({
|
|
code: 209,
|
|
error: 'Invalid session token',
|
|
});
|
|
}
|
|
});
|
|
|
|
it('should send reset password', async () => {
|
|
const clientMutationId = uuidv4();
|
|
const emailAdapter = {
|
|
sendVerificationEmail: () => {},
|
|
sendPasswordResetEmail: () => Promise.resolve(),
|
|
sendMail: () => {},
|
|
};
|
|
parseServer = await global.reconfigureServer({
|
|
appName: 'test',
|
|
emailAdapter: emailAdapter,
|
|
publicServerURL: 'http://test.test',
|
|
});
|
|
const user = new Parse.User();
|
|
user.setUsername('user1');
|
|
user.setPassword('user1');
|
|
user.setEmail('user1@user1.user1');
|
|
await user.signUp();
|
|
await Parse.User.logOut();
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation ResetPassword($input: ResetPasswordInput!) {
|
|
resetPassword(input: $input) {
|
|
clientMutationId
|
|
ok
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
email: 'user1@user1.user1',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.resetPassword.clientMutationId).toEqual(
|
|
clientMutationId
|
|
);
|
|
expect(result.data.resetPassword.ok).toBeTruthy();
|
|
});
|
|
it('should send verification email again', async () => {
|
|
const clientMutationId = uuidv4();
|
|
const emailAdapter = {
|
|
sendVerificationEmail: () => {},
|
|
sendPasswordResetEmail: () => Promise.resolve(),
|
|
sendMail: () => {},
|
|
};
|
|
parseServer = await global.reconfigureServer({
|
|
appName: 'test',
|
|
emailAdapter: emailAdapter,
|
|
publicServerURL: 'http://test.test',
|
|
});
|
|
const user = new Parse.User();
|
|
user.setUsername('user1');
|
|
user.setPassword('user1');
|
|
user.setEmail('user1@user1.user1');
|
|
await user.signUp();
|
|
await Parse.User.logOut();
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation SendVerificationEmail(
|
|
$input: SendVerificationEmailInput!
|
|
) {
|
|
sendVerificationEmail(input: $input) {
|
|
clientMutationId
|
|
ok
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
email: 'user1@user1.user1',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.sendVerificationEmail.clientMutationId).toEqual(
|
|
clientMutationId
|
|
);
|
|
expect(result.data.sendVerificationEmail.ok).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('Session Token', () => {
|
|
it('should fail due to invalid session token', async () => {
|
|
try {
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetCurrentUser {
|
|
me {
|
|
username
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': 'foo',
|
|
},
|
|
},
|
|
});
|
|
fail('should not retrieve current user due to session token');
|
|
} catch (err) {
|
|
const { statusCode, result } = err.networkError;
|
|
expect(statusCode).toBe(400);
|
|
expect(result).toEqual({
|
|
code: 209,
|
|
error: 'Invalid session token',
|
|
});
|
|
}
|
|
});
|
|
|
|
it('should fail due to empty session token', async () => {
|
|
try {
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetCurrentUser {
|
|
viewer {
|
|
user {
|
|
username
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': '',
|
|
},
|
|
},
|
|
});
|
|
fail('should not retrieve current user due to session token');
|
|
} catch (err) {
|
|
const { graphQLErrors } = err;
|
|
expect(graphQLErrors.length).toBe(1);
|
|
expect(graphQLErrors[0].message).toBe('Invalid session token');
|
|
}
|
|
});
|
|
|
|
it('should find a user and fail due to empty session token', async () => {
|
|
const car = new Parse.Object('Car');
|
|
await car.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
try {
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query GetCurrentUser {
|
|
viewer {
|
|
user {
|
|
username
|
|
}
|
|
}
|
|
cars {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': '',
|
|
},
|
|
},
|
|
});
|
|
fail('should not retrieve current user due to session token');
|
|
} catch (err) {
|
|
const { graphQLErrors } = err;
|
|
expect(graphQLErrors.length).toBe(1);
|
|
expect(graphQLErrors[0].message).toBe('Invalid session token');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Functions Mutations', () => {
|
|
it('can be called', async () => {
|
|
try {
|
|
const clientMutationId = uuidv4();
|
|
|
|
Parse.Cloud.define('hello', async () => {
|
|
return 'Hello world!';
|
|
});
|
|
|
|
const result = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CallFunction($input: CallCloudCodeInput!) {
|
|
callCloudCode(input: $input) {
|
|
clientMutationId
|
|
result
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
clientMutationId,
|
|
functionName: 'hello',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.callCloudCode.clientMutationId).toEqual(
|
|
clientMutationId
|
|
);
|
|
expect(result.data.callCloudCode.result).toEqual('Hello world!');
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('can throw errors', async () => {
|
|
Parse.Cloud.define('hello', async () => {
|
|
throw new Error('Some error message.');
|
|
});
|
|
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CallFunction {
|
|
callCloudCode(input: { functionName: hello }) {
|
|
result
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
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) {
|
|
callCloudCode(input: { functionName: hello, params: $params }) {
|
|
result
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
params,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should list all functions in the enum type', async () => {
|
|
try {
|
|
Parse.Cloud.define('a', async () => {
|
|
return 'hello a';
|
|
});
|
|
|
|
Parse.Cloud.define('b', async () => {
|
|
return 'hello b';
|
|
});
|
|
|
|
Parse.Cloud.define('_underscored', async () => {
|
|
return 'hello _underscored';
|
|
});
|
|
|
|
Parse.Cloud.define('contains1Number', async () => {
|
|
return 'hello contains1Number';
|
|
});
|
|
|
|
const functionEnum = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query ObjectType {
|
|
__type(name: "CloudCodeFunction") {
|
|
kind
|
|
enumValues {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
expect(functionEnum.kind).toEqual('ENUM');
|
|
expect(
|
|
functionEnum.enumValues.map(value => value.name).sort()
|
|
).toEqual(['_underscored', 'a', 'b', 'contains1Number']);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should warn functions not matching GraphQL allowed names', async () => {
|
|
try {
|
|
spyOn(
|
|
parseGraphQLServer.parseGraphQLSchema.log,
|
|
'warn'
|
|
).and.callThrough();
|
|
|
|
Parse.Cloud.define('a', async () => {
|
|
return 'hello a';
|
|
});
|
|
|
|
Parse.Cloud.define('double-barrelled', async () => {
|
|
return 'hello b';
|
|
});
|
|
|
|
Parse.Cloud.define('1NumberInTheBeggning', async () => {
|
|
return 'hello contains1Number';
|
|
});
|
|
|
|
const functionEnum = (
|
|
await apolloClient.query({
|
|
query: gql`
|
|
query ObjectType {
|
|
__type(name: "CloudCodeFunction") {
|
|
kind
|
|
enumValues {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
).data['__type'];
|
|
expect(functionEnum.kind).toEqual('ENUM');
|
|
expect(
|
|
functionEnum.enumValues.map(value => value.name).sort()
|
|
).toEqual(['a']);
|
|
expect(
|
|
parseGraphQLServer.parseGraphQLSchema.log.warn.calls
|
|
.all()
|
|
.map(call => call.args[0])
|
|
.sort()
|
|
).toEqual([
|
|
'Function 1NumberInTheBeggning could not be added to the auto schema because GraphQL names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/.',
|
|
'Function double-barrelled could not be added to the auto schema because GraphQL names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/.',
|
|
]);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Data Types', () => {
|
|
it('should support String', async () => {
|
|
try {
|
|
const someFieldValue = 'some string';
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addStrings: [{ name: 'someField' }],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('String');
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someField: someFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!, $someFieldValue: String) {
|
|
someClass(id: $id) {
|
|
someField
|
|
}
|
|
someClasses(
|
|
where: { someField: { equalTo: $someFieldValue } }
|
|
) {
|
|
edges {
|
|
node {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
someFieldValue,
|
|
},
|
|
});
|
|
|
|
expect(typeof getResult.data.someClass.someField).toEqual('string');
|
|
expect(getResult.data.someClass.someField).toEqual(someFieldValue);
|
|
expect(getResult.data.someClasses.edges.length).toEqual(1);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support Int numbers', async () => {
|
|
try {
|
|
const someFieldValue = 123;
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addNumbers: [{ name: 'someField' }],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someField: someFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('Number');
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!, $someFieldValue: Float) {
|
|
someClass(id: $id) {
|
|
someField
|
|
}
|
|
someClasses(
|
|
where: { someField: { equalTo: $someFieldValue } }
|
|
) {
|
|
edges {
|
|
node {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
someFieldValue,
|
|
},
|
|
});
|
|
|
|
expect(typeof getResult.data.someClass.someField).toEqual('number');
|
|
expect(getResult.data.someClass.someField).toEqual(someFieldValue);
|
|
expect(getResult.data.someClasses.edges.length).toEqual(1);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support Float numbers', async () => {
|
|
try {
|
|
const someFieldValue = 123.4;
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addNumbers: [{ name: 'someField' }],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('Number');
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someField: someFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!, $someFieldValue: Float) {
|
|
someClass(id: $id) {
|
|
someField
|
|
}
|
|
someClasses(
|
|
where: { someField: { equalTo: $someFieldValue } }
|
|
) {
|
|
edges {
|
|
node {
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
someFieldValue,
|
|
},
|
|
});
|
|
|
|
expect(typeof getResult.data.someClass.someField).toEqual('number');
|
|
expect(getResult.data.someClass.someField).toEqual(someFieldValue);
|
|
expect(getResult.data.someClasses.edges.length).toEqual(1);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support Boolean', async () => {
|
|
try {
|
|
const someFieldValueTrue = true;
|
|
const someFieldValueFalse = false;
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addBooleans: [
|
|
{ name: 'someFieldTrue' },
|
|
{ name: 'someFieldFalse' },
|
|
],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someFieldTrue.type).toEqual('Boolean');
|
|
expect(schema.fields.someFieldFalse.type).toEqual('Boolean');
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someFieldTrue: someFieldValueTrue,
|
|
someFieldFalse: someFieldValueFalse,
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject(
|
|
$id: ID!
|
|
$someFieldValueTrue: Boolean
|
|
$someFieldValueFalse: Boolean
|
|
) {
|
|
someClass(id: $id) {
|
|
someFieldTrue
|
|
someFieldFalse
|
|
}
|
|
someClasses(
|
|
where: {
|
|
someFieldTrue: { equalTo: $someFieldValueTrue }
|
|
someFieldFalse: { equalTo: $someFieldValueFalse }
|
|
}
|
|
) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
someFieldValueTrue,
|
|
someFieldValueFalse,
|
|
},
|
|
});
|
|
|
|
expect(typeof getResult.data.someClass.someFieldTrue).toEqual(
|
|
'boolean'
|
|
);
|
|
expect(typeof getResult.data.someClass.someFieldFalse).toEqual(
|
|
'boolean'
|
|
);
|
|
expect(getResult.data.someClass.someFieldTrue).toEqual(true);
|
|
expect(getResult.data.someClass.someFieldFalse).toEqual(false);
|
|
expect(getResult.data.someClasses.edges.length).toEqual(1);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support Date', async () => {
|
|
try {
|
|
const someFieldValue = new Date();
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addDates: [{ name: 'someField' }],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('Date');
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someField: someFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
someClass(id: $id) {
|
|
someField
|
|
}
|
|
someClasses(where: { someField: { exists: true } }) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
},
|
|
});
|
|
|
|
expect(new Date(getResult.data.someClass.someField)).toEqual(
|
|
someFieldValue
|
|
);
|
|
expect(getResult.data.someClasses.edges.length).toEqual(1);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support createdAt and updatedAt', async () => {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass {
|
|
createClass(input: { name: "SomeClass" }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.createdAt.type).toEqual('Date');
|
|
expect(schema.fields.updatedAt.type).toEqual('Date');
|
|
});
|
|
|
|
it('should support ACL', async () => {
|
|
const someClass = new Parse.Object('SomeClass');
|
|
await someClass.save();
|
|
|
|
const user = new Parse.User();
|
|
user.set('username', 'username');
|
|
user.set('password', 'password');
|
|
await user.signUp();
|
|
|
|
const user2 = new Parse.User();
|
|
user2.set('username', 'username2');
|
|
user2.set('password', 'password2');
|
|
await user2.signUp();
|
|
|
|
const roleACL = new Parse.ACL();
|
|
roleACL.setPublicReadAccess(true);
|
|
|
|
const role = new Parse.Role('aRole', roleACL);
|
|
await role.save();
|
|
|
|
const role2 = new Parse.Role('aRole2', roleACL);
|
|
await role2.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: { createSomeClass },
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation Create($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
objectId
|
|
ACL {
|
|
users {
|
|
userId
|
|
read
|
|
write
|
|
}
|
|
roles {
|
|
roleName
|
|
read
|
|
write
|
|
}
|
|
public {
|
|
read
|
|
write
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
ACL: {
|
|
users: [
|
|
{ userId: user.id, read: true, write: true },
|
|
{ userId: user2.id, read: true, write: false },
|
|
],
|
|
roles: [
|
|
{ roleName: 'aRole', read: true, write: false },
|
|
{ roleName: 'aRole2', read: false, write: true },
|
|
],
|
|
public: { read: true, write: true },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const expectedCreateACL = {
|
|
__typename: 'ACL',
|
|
users: [
|
|
{
|
|
userId: user.id,
|
|
read: true,
|
|
write: true,
|
|
__typename: 'UserACL',
|
|
},
|
|
{
|
|
userId: user2.id,
|
|
read: true,
|
|
write: false,
|
|
__typename: 'UserACL',
|
|
},
|
|
],
|
|
roles: [
|
|
{
|
|
roleName: 'aRole',
|
|
read: true,
|
|
write: false,
|
|
__typename: 'RoleACL',
|
|
},
|
|
{
|
|
roleName: 'aRole2',
|
|
read: false,
|
|
write: true,
|
|
__typename: 'RoleACL',
|
|
},
|
|
],
|
|
public: { read: true, write: true, __typename: 'PublicACL' },
|
|
};
|
|
const query1 = new Parse.Query('SomeClass');
|
|
const obj1 = (
|
|
await query1.get(createSomeClass.someClass.objectId, {
|
|
useMasterKey: true,
|
|
})
|
|
).toJSON();
|
|
expect(obj1.ACL[user.id]).toEqual({ read: true, write: true });
|
|
expect(obj1.ACL[user2.id]).toEqual({ read: true });
|
|
expect(obj1.ACL['role:aRole']).toEqual({ read: true });
|
|
expect(obj1.ACL['role:aRole2']).toEqual({ write: true });
|
|
expect(obj1.ACL['*']).toEqual({ read: true, write: true });
|
|
expect(createSomeClass.someClass.ACL).toEqual(expectedCreateACL);
|
|
|
|
const {
|
|
data: { updateSomeClass },
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation Update($id: ID!, $fields: UpdateSomeClassFieldsInput) {
|
|
updateSomeClass(input: { id: $id, fields: $fields }) {
|
|
someClass {
|
|
id
|
|
objectId
|
|
ACL {
|
|
users {
|
|
userId
|
|
read
|
|
write
|
|
}
|
|
roles {
|
|
roleName
|
|
read
|
|
write
|
|
}
|
|
public {
|
|
read
|
|
write
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createSomeClass.someClass.id,
|
|
fields: {
|
|
ACL: {
|
|
roles: [{ roleName: 'aRole', write: true, read: true }],
|
|
public: { read: true, write: false },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const expectedUpdateACL = {
|
|
__typename: 'ACL',
|
|
users: null,
|
|
roles: [
|
|
{
|
|
roleName: 'aRole',
|
|
read: true,
|
|
write: true,
|
|
__typename: 'RoleACL',
|
|
},
|
|
],
|
|
public: { read: true, write: false, __typename: 'PublicACL' },
|
|
};
|
|
|
|
const query2 = new Parse.Query('SomeClass');
|
|
const obj2 = (
|
|
await query2.get(createSomeClass.someClass.objectId, {
|
|
useMasterKey: true,
|
|
})
|
|
).toJSON();
|
|
|
|
expect(obj2.ACL['role:aRole']).toEqual({ write: true, read: true });
|
|
expect(obj2.ACL[user.id]).toBeUndefined();
|
|
expect(obj2.ACL['*']).toEqual({ read: true });
|
|
expect(updateSomeClass.someClass.ACL).toEqual(expectedUpdateACL);
|
|
});
|
|
|
|
it('should support pointer on create', async () => {
|
|
const company = new Parse.Object('Company');
|
|
company.set('name', 'imACompany1');
|
|
await company.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.set('company', company);
|
|
await country.save();
|
|
|
|
const company2 = new Parse.Object('Company');
|
|
company2.set('name', 'imACompany2');
|
|
await company2.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: {
|
|
createCountry: { country: result },
|
|
},
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation Create($fields: CreateCountryFieldsInput) {
|
|
createCountry(input: { fields: $fields }) {
|
|
country {
|
|
id
|
|
objectId
|
|
company {
|
|
id
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
name: 'imCountry2',
|
|
company: { link: company2.id },
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.id).toBeDefined();
|
|
expect(result.company.objectId).toEqual(company2.id);
|
|
expect(result.company.name).toEqual('imACompany2');
|
|
});
|
|
|
|
it('should support nested pointer on create', async () => {
|
|
const company = new Parse.Object('Company');
|
|
company.set('name', 'imACompany1');
|
|
await company.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.set('company', company);
|
|
await country.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: {
|
|
createCountry: { country: result },
|
|
},
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation Create($fields: CreateCountryFieldsInput) {
|
|
createCountry(input: { fields: $fields }) {
|
|
country {
|
|
id
|
|
company {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
name: 'imCountry2',
|
|
company: {
|
|
createAndLink: {
|
|
name: 'imACompany2',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.id).toBeDefined();
|
|
expect(result.company.id).toBeDefined();
|
|
expect(result.company.name).toEqual('imACompany2');
|
|
});
|
|
|
|
it('should support pointer on update', async () => {
|
|
const company = new Parse.Object('Company');
|
|
company.set('name', 'imACompany1');
|
|
await company.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.set('company', company);
|
|
await country.save();
|
|
|
|
const company2 = new Parse.Object('Company');
|
|
company2.set('name', 'imACompany2');
|
|
await company2.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: {
|
|
updateCountry: { country: result },
|
|
},
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation Update($id: ID!, $fields: UpdateCountryFieldsInput) {
|
|
updateCountry(input: { id: $id, fields: $fields }) {
|
|
country {
|
|
id
|
|
objectId
|
|
company {
|
|
id
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: country.id,
|
|
fields: {
|
|
company: { link: company2.id },
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.id).toBeDefined();
|
|
expect(result.company.objectId).toEqual(company2.id);
|
|
expect(result.company.name).toEqual('imACompany2');
|
|
});
|
|
|
|
it('should support nested pointer on update', async () => {
|
|
const company = new Parse.Object('Company');
|
|
company.set('name', 'imACompany1');
|
|
await company.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.set('company', company);
|
|
await country.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: {
|
|
updateCountry: { country: result },
|
|
},
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation Update($id: ID!, $fields: UpdateCountryFieldsInput) {
|
|
updateCountry(input: { id: $id, fields: $fields }) {
|
|
country {
|
|
id
|
|
company {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: country.id,
|
|
fields: {
|
|
company: {
|
|
createAndLink: {
|
|
name: 'imACompany2',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.id).toBeDefined();
|
|
expect(result.company.id).toBeDefined();
|
|
expect(result.company.name).toEqual('imACompany2');
|
|
});
|
|
|
|
it_only_db('mongo')(
|
|
'should support relation and nested relation on create',
|
|
async () => {
|
|
const company = new Parse.Object('Company');
|
|
company.set('name', 'imACompany1');
|
|
await company.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.relation('companies').add(company);
|
|
await country.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: {
|
|
createCountry: { country: result },
|
|
},
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateCountry($fields: CreateCountryFieldsInput) {
|
|
createCountry(input: { fields: $fields }) {
|
|
country {
|
|
id
|
|
objectId
|
|
name
|
|
companies {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
name: 'imACountry2',
|
|
companies: {
|
|
add: [company.id],
|
|
createAndAdd: [
|
|
{
|
|
name: 'imACompany2',
|
|
},
|
|
{
|
|
name: 'imACompany3',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.id).toBeDefined();
|
|
expect(result.name).toEqual('imACountry2');
|
|
expect(result.companies.edges.length).toEqual(3);
|
|
expect(
|
|
result.companies.edges.some(o => o.node.objectId === company.id)
|
|
).toBeTruthy();
|
|
expect(
|
|
result.companies.edges.some(o => o.node.name === 'imACompany2')
|
|
).toBeTruthy();
|
|
expect(
|
|
result.companies.edges.some(o => o.node.name === 'imACompany3')
|
|
).toBeTruthy();
|
|
}
|
|
);
|
|
|
|
it_only_db('mongo')('should support deep nested creation', async () => {
|
|
const team = new Parse.Object('Team');
|
|
team.set('name', 'imATeam1');
|
|
await team.save();
|
|
|
|
const company = new Parse.Object('Company');
|
|
company.set('name', 'imACompany1');
|
|
company.relation('teams').add(team);
|
|
await company.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.relation('companies').add(company);
|
|
await country.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: {
|
|
createCountry: { country: result },
|
|
},
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateCountry($fields: CreateCountryFieldsInput) {
|
|
createCountry(input: { fields: $fields }) {
|
|
country {
|
|
id
|
|
name
|
|
companies {
|
|
edges {
|
|
node {
|
|
id
|
|
name
|
|
teams {
|
|
edges {
|
|
node {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
name: 'imACountry2',
|
|
companies: {
|
|
createAndAdd: [
|
|
{
|
|
name: 'imACompany2',
|
|
teams: {
|
|
createAndAdd: {
|
|
name: 'imATeam2',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'imACompany3',
|
|
teams: {
|
|
createAndAdd: {
|
|
name: 'imATeam3',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.id).toBeDefined();
|
|
expect(result.name).toEqual('imACountry2');
|
|
expect(result.companies.edges.length).toEqual(2);
|
|
expect(
|
|
result.companies.edges.some(
|
|
c =>
|
|
c.node.name === 'imACompany2' &&
|
|
c.node.teams.edges.some(t => t.node.name === 'imATeam2')
|
|
)
|
|
).toBeTruthy();
|
|
expect(
|
|
result.companies.edges.some(
|
|
c =>
|
|
c.node.name === 'imACompany3' &&
|
|
c.node.teams.edges.some(t => t.node.name === 'imATeam3')
|
|
)
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it_only_db('mongo')(
|
|
'should support relation and nested relation on update',
|
|
async () => {
|
|
const company1 = new Parse.Object('Company');
|
|
company1.set('name', 'imACompany1');
|
|
await company1.save();
|
|
|
|
const company2 = new Parse.Object('Company');
|
|
company2.set('name', 'imACompany2');
|
|
await company2.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.relation('companies').add(company1);
|
|
await country.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: {
|
|
updateCountry: { country: result },
|
|
},
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation UpdateCountry(
|
|
$id: ID!
|
|
$fields: UpdateCountryFieldsInput
|
|
) {
|
|
updateCountry(input: { id: $id, fields: $fields }) {
|
|
country {
|
|
id
|
|
objectId
|
|
companies {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: country.id,
|
|
fields: {
|
|
companies: {
|
|
add: [company2.id],
|
|
remove: [company1.id],
|
|
createAndAdd: [
|
|
{
|
|
name: 'imACompany3',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.objectId).toEqual(country.id);
|
|
expect(result.companies.edges.length).toEqual(2);
|
|
expect(
|
|
result.companies.edges.some(o => o.node.objectId === company2.id)
|
|
).toBeTruthy();
|
|
expect(
|
|
result.companies.edges.some(o => o.node.name === 'imACompany3')
|
|
).toBeTruthy();
|
|
expect(
|
|
result.companies.edges.some(o => o.node.objectId === company1.id)
|
|
).toBeFalsy();
|
|
}
|
|
);
|
|
|
|
it_only_db('mongo')(
|
|
'should support nested relation on create with filter',
|
|
async () => {
|
|
const company = new Parse.Object('Company');
|
|
company.set('name', 'imACompany1');
|
|
await company.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.relation('companies').add(company);
|
|
await country.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const {
|
|
data: {
|
|
createCountry: { country: result },
|
|
},
|
|
} = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateCountry(
|
|
$fields: CreateCountryFieldsInput
|
|
$where: CompanyWhereInput
|
|
) {
|
|
createCountry(input: { fields: $fields }) {
|
|
country {
|
|
id
|
|
name
|
|
companies(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
name: {
|
|
equalTo: 'imACompany2',
|
|
},
|
|
},
|
|
fields: {
|
|
name: 'imACountry2',
|
|
companies: {
|
|
add: [company.id],
|
|
createAndAdd: [
|
|
{
|
|
name: 'imACompany2',
|
|
},
|
|
{
|
|
name: 'imACompany3',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.id).toBeDefined();
|
|
expect(result.name).toEqual('imACountry2');
|
|
expect(result.companies.edges.length).toEqual(1);
|
|
expect(
|
|
result.companies.edges.some(o => o.node.name === 'imACompany2')
|
|
).toBeTruthy();
|
|
}
|
|
);
|
|
|
|
it_only_db('mongo')('should support relation on query', async () => {
|
|
const company1 = new Parse.Object('Company');
|
|
company1.set('name', 'imACompany1');
|
|
await company1.save();
|
|
|
|
const company2 = new Parse.Object('Company');
|
|
company2.set('name', 'imACompany2');
|
|
await company2.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.relation('companies').add([company1, company2]);
|
|
await country.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
// Without where
|
|
const {
|
|
data: { country: result1 },
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query getCountry($id: ID!) {
|
|
country(id: $id) {
|
|
id
|
|
objectId
|
|
companies {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
count
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: country.id,
|
|
},
|
|
});
|
|
|
|
expect(result1.objectId).toEqual(country.id);
|
|
expect(result1.companies.edges.length).toEqual(2);
|
|
expect(
|
|
result1.companies.edges.some(o => o.node.objectId === company1.id)
|
|
).toBeTruthy();
|
|
expect(
|
|
result1.companies.edges.some(o => o.node.objectId === company2.id)
|
|
).toBeTruthy();
|
|
|
|
// With where
|
|
const {
|
|
data: { country: result2 },
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query getCountry($id: ID!, $where: CompanyWhereInput) {
|
|
country(id: $id) {
|
|
id
|
|
objectId
|
|
companies(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: country.id,
|
|
where: {
|
|
name: { equalTo: 'imACompany1' },
|
|
},
|
|
},
|
|
});
|
|
expect(result2.objectId).toEqual(country.id);
|
|
expect(result2.companies.edges.length).toEqual(1);
|
|
expect(result2.companies.edges[0].node.objectId).toEqual(company1.id);
|
|
});
|
|
|
|
it_only_db('mongo')(
|
|
'should support relational where query',
|
|
async () => {
|
|
const president = new Parse.Object('President');
|
|
president.set('name', 'James');
|
|
await president.save();
|
|
|
|
const employee = new Parse.Object('Employee');
|
|
employee.set('name', 'John');
|
|
await employee.save();
|
|
|
|
const company1 = new Parse.Object('Company');
|
|
company1.set('name', 'imACompany1');
|
|
await company1.save();
|
|
|
|
const company2 = new Parse.Object('Company');
|
|
company2.set('name', 'imACompany2');
|
|
company2.relation('employees').add([employee]);
|
|
await company2.save();
|
|
|
|
const country = new Parse.Object('Country');
|
|
country.set('name', 'imACountry');
|
|
country.relation('companies').add([company1, company2]);
|
|
await country.save();
|
|
|
|
const country2 = new Parse.Object('Country');
|
|
country2.set('name', 'imACountry2');
|
|
country2.relation('companies').add([company1]);
|
|
await country2.save();
|
|
|
|
const country3 = new Parse.Object('Country');
|
|
country3.set('name', 'imACountry3');
|
|
country3.set('president', president);
|
|
await country3.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
let {
|
|
data: {
|
|
countries: { edges: result },
|
|
},
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query findCountry($where: CountryWhereInput) {
|
|
countries(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
companies {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
companies: {
|
|
have: {
|
|
employees: { have: { name: { equalTo: 'John' } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(result.length).toEqual(1);
|
|
result = result[0].node;
|
|
expect(result.objectId).toEqual(country.id);
|
|
expect(result.companies.edges.length).toEqual(2);
|
|
|
|
const {
|
|
data: {
|
|
countries: { edges: result2 },
|
|
},
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query findCountry($where: CountryWhereInput) {
|
|
countries(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
companies {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
companies: {
|
|
have: {
|
|
OR: [
|
|
{ name: { equalTo: 'imACompany1' } },
|
|
{ name: { equalTo: 'imACompany2' } },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(result2.length).toEqual(2);
|
|
|
|
const {
|
|
data: {
|
|
countries: { edges: result3 },
|
|
},
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query findCountry($where: CountryWhereInput) {
|
|
countries(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
companies: { exists: false },
|
|
},
|
|
},
|
|
});
|
|
expect(result3.length).toEqual(1);
|
|
expect(result3[0].node.name).toEqual('imACountry3');
|
|
|
|
const {
|
|
data: {
|
|
countries: { edges: result4 },
|
|
},
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query findCountry($where: CountryWhereInput) {
|
|
countries(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
president: { exists: false },
|
|
},
|
|
},
|
|
});
|
|
expect(result4.length).toEqual(2);
|
|
const {
|
|
data: {
|
|
countries: { edges: result5 },
|
|
},
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query findCountry($where: CountryWhereInput) {
|
|
countries(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
president: { exists: true },
|
|
},
|
|
},
|
|
});
|
|
expect(result5.length).toEqual(1);
|
|
const {
|
|
data: {
|
|
countries: { edges: result6 },
|
|
},
|
|
} = await apolloClient.query({
|
|
query: gql`
|
|
query findCountry($where: CountryWhereInput) {
|
|
countries(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
objectId
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
companies: {
|
|
haveNot: {
|
|
OR: [
|
|
{ name: { equalTo: 'imACompany1' } },
|
|
{ name: { equalTo: 'imACompany2' } },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(result6.length).toEqual(1);
|
|
expect(result6.length).toEqual(1);
|
|
expect(result6[0].node.name).toEqual('imACountry3');
|
|
}
|
|
);
|
|
|
|
it('should support files', async () => {
|
|
try {
|
|
parseServer = await global.reconfigureServer({
|
|
publicServerURL: 'http://localhost:13377/parse',
|
|
});
|
|
|
|
const body = new FormData();
|
|
body.append(
|
|
'operations',
|
|
JSON.stringify({
|
|
query: `
|
|
mutation CreateFile($input: CreateFileInput!) {
|
|
createFile(input: $input) {
|
|
fileInfo {
|
|
name
|
|
url
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
input: {
|
|
upload: null,
|
|
},
|
|
},
|
|
})
|
|
);
|
|
body.append(
|
|
'map',
|
|
JSON.stringify({ 1: ['variables.input.upload'] })
|
|
);
|
|
body.append('1', 'My File Content', {
|
|
filename: 'myFileName.txt',
|
|
contentType: 'text/plain',
|
|
});
|
|
|
|
let res = await fetch('http://localhost:13377/graphql', {
|
|
method: 'POST',
|
|
headers,
|
|
body,
|
|
});
|
|
|
|
expect(res.status).toEqual(200);
|
|
|
|
const result = JSON.parse(await res.text());
|
|
|
|
expect(result.data.createFile.fileInfo.name).toEqual(
|
|
jasmine.stringMatching(/_myFileName.txt$/)
|
|
);
|
|
expect(result.data.createFile.fileInfo.url).toEqual(
|
|
jasmine.stringMatching(/_myFileName.txt$/)
|
|
);
|
|
|
|
const someFieldValue = result.data.createFile.fileInfo.name;
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addFiles: [{ name: 'someField' }],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject(
|
|
$fields1: CreateSomeClassFieldsInput
|
|
$fields2: CreateSomeClassFieldsInput
|
|
) {
|
|
createSomeClass1: createSomeClass(
|
|
input: { fields: $fields1 }
|
|
) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
createSomeClass2: createSomeClass(
|
|
input: { fields: $fields2 }
|
|
) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields1: {
|
|
someField: someFieldValue,
|
|
},
|
|
fields2: {
|
|
someField: someFieldValue.name,
|
|
},
|
|
},
|
|
});
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('File');
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
someClass(id: $id) {
|
|
someField {
|
|
name
|
|
url
|
|
}
|
|
}
|
|
findSomeClass1: someClasses(
|
|
where: { someField: { exists: true } }
|
|
) {
|
|
edges {
|
|
node {
|
|
someField {
|
|
name
|
|
url
|
|
}
|
|
}
|
|
}
|
|
}
|
|
findSomeClass2: someClasses(
|
|
where: { someField: { exists: true } }
|
|
) {
|
|
edges {
|
|
node {
|
|
someField {
|
|
name
|
|
url
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass1.someClass.id,
|
|
},
|
|
});
|
|
|
|
expect(typeof getResult.data.someClass.someField).toEqual('object');
|
|
expect(getResult.data.someClass.someField.name).toEqual(
|
|
result.data.createFile.fileInfo.name
|
|
);
|
|
expect(getResult.data.someClass.someField.url).toEqual(
|
|
result.data.createFile.fileInfo.url
|
|
);
|
|
expect(getResult.data.findSomeClass1.edges.length).toEqual(1);
|
|
expect(getResult.data.findSomeClass2.edges.length).toEqual(1);
|
|
|
|
res = await fetch(getResult.data.someClass.someField.url);
|
|
|
|
expect(res.status).toEqual(200);
|
|
expect(await res.text()).toEqual('My File Content');
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support object values', async () => {
|
|
try {
|
|
const someFieldValue = {
|
|
foo: { bar: 'baz' },
|
|
number: 10,
|
|
};
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addObjects: [{ name: 'someField' }],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('Object');
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someField: someFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const where = {
|
|
someField: {
|
|
equalTo: { key: 'foo.bar', value: 'baz' },
|
|
notEqualTo: { key: 'foo.bar', value: 'bat' },
|
|
greaterThan: { key: 'number', value: 9 },
|
|
lessThan: { key: 'number', value: 11 },
|
|
},
|
|
};
|
|
const queryResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!, $where: SomeClassWhereInput) {
|
|
someClass(id: $id) {
|
|
id
|
|
someField
|
|
}
|
|
someClasses(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
where,
|
|
},
|
|
});
|
|
|
|
const { someClass: getResult, someClasses } = queryResult.data;
|
|
|
|
const { someField } = getResult;
|
|
expect(typeof someField).toEqual('object');
|
|
expect(someField).toEqual(someFieldValue);
|
|
|
|
// Checks class query results
|
|
expect(someClasses.edges.length).toEqual(1);
|
|
expect(someClasses.edges[0].node.someField).toEqual(someFieldValue);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support object composed queries', async () => {
|
|
try {
|
|
const someFieldValue = {
|
|
lorem: 'ipsum',
|
|
number: 10,
|
|
};
|
|
const someFieldValue2 = {
|
|
foo: {
|
|
test: 'bar',
|
|
},
|
|
number: 10,
|
|
};
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass {
|
|
createClass(
|
|
input: {
|
|
name: "SomeClass"
|
|
schemaFields: { addObjects: [{ name: "someField" }] }
|
|
}
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject(
|
|
$fields1: CreateSomeClassFieldsInput
|
|
$fields2: CreateSomeClassFieldsInput
|
|
) {
|
|
create1: createSomeClass(input: { fields: $fields1 }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
create2: createSomeClass(input: { fields: $fields2 }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields1: {
|
|
someField: someFieldValue,
|
|
},
|
|
fields2: {
|
|
someField: someFieldValue2,
|
|
},
|
|
},
|
|
});
|
|
|
|
const where = {
|
|
AND: [
|
|
{
|
|
someField: {
|
|
greaterThan: { key: 'number', value: 9 },
|
|
},
|
|
},
|
|
{
|
|
someField: {
|
|
lessThan: { key: 'number', value: 11 },
|
|
},
|
|
},
|
|
{
|
|
OR: [
|
|
{
|
|
someField: {
|
|
equalTo: { key: 'lorem', value: 'ipsum' },
|
|
},
|
|
},
|
|
{
|
|
someField: {
|
|
equalTo: { key: 'foo.test', value: 'bar' },
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const findResult = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObject($where: SomeClassWhereInput) {
|
|
someClasses(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where,
|
|
},
|
|
});
|
|
|
|
const { create1, create2 } = createResult.data;
|
|
const { someClasses } = findResult.data;
|
|
|
|
// Checks class query results
|
|
const { edges } = someClasses;
|
|
expect(edges.length).toEqual(2);
|
|
expect(
|
|
edges.find(result => result.node.id === create1.someClass.id).node
|
|
.someField
|
|
).toEqual(someFieldValue);
|
|
expect(
|
|
edges.find(result => result.node.id === create2.someClass.id).node
|
|
.someField
|
|
).toEqual(someFieldValue2);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support array values', async () => {
|
|
try {
|
|
const someFieldValue = [
|
|
1,
|
|
'foo',
|
|
['bar'],
|
|
{ lorem: 'ipsum' },
|
|
true,
|
|
];
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addArrays: [{ name: 'someField' }],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('Array');
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someField: someFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
someClass(id: $id) {
|
|
someField {
|
|
... on Element {
|
|
value
|
|
}
|
|
}
|
|
}
|
|
someClasses(where: { someField: { exists: true } }) {
|
|
edges {
|
|
node {
|
|
id
|
|
someField {
|
|
... on Element {
|
|
value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
},
|
|
});
|
|
|
|
const { someField } = getResult.data.someClass;
|
|
expect(Array.isArray(someField)).toBeTruthy();
|
|
expect(someField.map(element => element.value)).toEqual(
|
|
someFieldValue
|
|
);
|
|
expect(getResult.data.someClasses.edges.length).toEqual(1);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support undefined array', async () => {
|
|
const schema = await new Parse.Schema('SomeClass');
|
|
schema.addArray('someArray');
|
|
await schema.save();
|
|
|
|
const obj = new Parse.Object('SomeClass');
|
|
await obj.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
someClass(id: $id) {
|
|
id
|
|
someArray {
|
|
... on Element {
|
|
value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: obj.id,
|
|
},
|
|
});
|
|
expect(getResult.data.someClass.someArray).toEqual(null);
|
|
});
|
|
|
|
it('should support null values', async () => {
|
|
try {
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass {
|
|
createClass(
|
|
input: {
|
|
name: "SomeClass"
|
|
schemaFields: {
|
|
addStrings: [
|
|
{ name: "someStringField" }
|
|
{ name: "someNullField" }
|
|
]
|
|
addNumbers: [{ name: "someNumberField" }]
|
|
addBooleans: [{ name: "someBooleanField" }]
|
|
addObjects: [{ name: "someObjectField" }]
|
|
}
|
|
}
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someStringField: 'some string',
|
|
someNumberField: 123,
|
|
someBooleanField: true,
|
|
someObjectField: { someField: 'some value' },
|
|
someNullField: null,
|
|
},
|
|
},
|
|
});
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation UpdateSomeObject(
|
|
$id: ID!
|
|
$fields: UpdateSomeClassFieldsInput
|
|
) {
|
|
updateSomeClass(input: { id: $id, fields: $fields }) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
fields: {
|
|
someStringField: null,
|
|
someNumberField: null,
|
|
someBooleanField: null,
|
|
someObjectField: null,
|
|
someNullField: 'now it has a string',
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
someClass(id: $id) {
|
|
someStringField
|
|
someNumberField
|
|
someBooleanField
|
|
someObjectField
|
|
someNullField
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
},
|
|
});
|
|
|
|
expect(getResult.data.someClass.someStringField).toBeFalsy();
|
|
expect(getResult.data.someClass.someNumberField).toBeFalsy();
|
|
expect(getResult.data.someClass.someBooleanField).toBeFalsy();
|
|
expect(getResult.data.someClass.someObjectField).toBeFalsy();
|
|
expect(getResult.data.someClass.someNullField).toEqual(
|
|
'now it has a string'
|
|
);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support Bytes', async () => {
|
|
try {
|
|
const someFieldValue = 'aGVsbG8gd29ybGQ=';
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addBytes: [{ name: 'someField' }],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('Bytes');
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject(
|
|
$fields1: CreateSomeClassFieldsInput
|
|
$fields2: CreateSomeClassFieldsInput
|
|
) {
|
|
createSomeClass1: createSomeClass(
|
|
input: { fields: $fields1 }
|
|
) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
createSomeClass2: createSomeClass(
|
|
input: { fields: $fields2 }
|
|
) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields1: {
|
|
someField: someFieldValue,
|
|
},
|
|
fields2: {
|
|
someField: someFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!, $someFieldValue: Bytes) {
|
|
someClass(id: $id) {
|
|
someField
|
|
}
|
|
someClasses(
|
|
where: { someField: { equalTo: $someFieldValue } }
|
|
) {
|
|
edges {
|
|
node {
|
|
id
|
|
someField
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass1.someClass.id,
|
|
someFieldValue,
|
|
},
|
|
});
|
|
|
|
expect(typeof getResult.data.someClass.someField).toEqual('string');
|
|
expect(getResult.data.someClass.someField).toEqual(someFieldValue);
|
|
expect(getResult.data.someClasses.edges.length).toEqual(2);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support Geo Points', async () => {
|
|
try {
|
|
const someFieldValue = {
|
|
__typename: 'GeoPoint',
|
|
latitude: 45,
|
|
longitude: 45,
|
|
};
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addGeoPoint: { name: 'someField' },
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('GeoPoint');
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someField: {
|
|
latitude: someFieldValue.latitude,
|
|
longitude: someFieldValue.longitude,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
someClass(id: $id) {
|
|
someField {
|
|
latitude
|
|
longitude
|
|
}
|
|
}
|
|
someClasses(where: { someField: { exists: true } }) {
|
|
edges {
|
|
node {
|
|
id
|
|
someField {
|
|
latitude
|
|
longitude
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
},
|
|
});
|
|
|
|
expect(typeof getResult.data.someClass.someField).toEqual('object');
|
|
expect(getResult.data.someClass.someField).toEqual(someFieldValue);
|
|
expect(getResult.data.someClasses.edges.length).toEqual(1);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it('should support Polygons', async () => {
|
|
try {
|
|
const somePolygonFieldValue = [
|
|
[44, 45],
|
|
[46, 47],
|
|
[48, 49],
|
|
[44, 45],
|
|
].map(point => ({
|
|
latitude: point[0],
|
|
longitude: point[1],
|
|
}));
|
|
|
|
await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateClass($schemaFields: SchemaFieldsInput) {
|
|
createClass(
|
|
input: { name: "SomeClass", schemaFields: $schemaFields }
|
|
) {
|
|
clientMutationId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
schemaFields: {
|
|
addPolygons: [{ name: 'somePolygonField' }],
|
|
},
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.somePolygonField.type).toEqual('Polygon');
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
somePolygonField: somePolygonFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
someClass(id: $id) {
|
|
somePolygonField {
|
|
latitude
|
|
longitude
|
|
}
|
|
}
|
|
someClasses(where: { somePolygonField: { exists: true } }) {
|
|
edges {
|
|
node {
|
|
id
|
|
somePolygonField {
|
|
latitude
|
|
longitude
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
},
|
|
});
|
|
|
|
expect(typeof getResult.data.someClass.somePolygonField).toEqual(
|
|
'object'
|
|
);
|
|
expect(getResult.data.someClass.somePolygonField).toEqual(
|
|
somePolygonFieldValue.map(geoPoint => ({
|
|
...geoPoint,
|
|
__typename: 'GeoPoint',
|
|
}))
|
|
);
|
|
expect(getResult.data.someClasses.edges.length).toEqual(1);
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
});
|
|
|
|
it_only_db('mongo')('should support bytes values', async () => {
|
|
const SomeClass = Parse.Object.extend('SomeClass');
|
|
const someClass = new SomeClass();
|
|
someClass.set('someField', {
|
|
__type: 'Bytes',
|
|
base64: 'foo',
|
|
});
|
|
await someClass.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
const schema = await new Parse.Schema('SomeClass').get();
|
|
expect(schema.fields.someField.type).toEqual('Bytes');
|
|
|
|
const someFieldValue = {
|
|
__type: 'Bytes',
|
|
base64: 'bytesContent',
|
|
};
|
|
|
|
const createResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) {
|
|
createSomeClass(input: { fields: $fields }) {
|
|
someClass {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
fields: {
|
|
someField: someFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
someClass(id: $id) {
|
|
someField
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
},
|
|
});
|
|
|
|
expect(getResult.data.someClass.someField).toEqual(
|
|
someFieldValue.base64
|
|
);
|
|
|
|
const updatedSomeFieldValue = {
|
|
__type: 'Bytes',
|
|
base64: 'newBytesContent',
|
|
};
|
|
|
|
const updatedResult = await apolloClient.mutate({
|
|
mutation: gql`
|
|
mutation UpdateSomeObject(
|
|
$id: ID!
|
|
$fields: UpdateSomeClassFieldsInput
|
|
) {
|
|
updateSomeClass(input: { id: $id, fields: $fields }) {
|
|
someClass {
|
|
updatedAt
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: createResult.data.createSomeClass.someClass.id,
|
|
fields: {
|
|
someField: updatedSomeFieldValue,
|
|
},
|
|
},
|
|
});
|
|
|
|
const { updatedAt } = updatedResult.data.updateSomeClass.someClass;
|
|
expect(updatedAt).toBeDefined();
|
|
|
|
const findResult = await apolloClient.query({
|
|
query: gql`
|
|
query FindSomeObject($where: SomeClassWhereInput!) {
|
|
someClasses(where: $where) {
|
|
edges {
|
|
node {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
where: {
|
|
someField: {
|
|
equalTo: updatedSomeFieldValue.base64,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
const findResults = findResult.data.someClasses.edges;
|
|
expect(findResults.length).toBe(1);
|
|
expect(findResults[0].node.id).toBe(
|
|
createResult.data.createSomeClass.someClass.id
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Special Classes', () => {
|
|
it('should support User class', async () => {
|
|
const user = new Parse.User();
|
|
user.setUsername('user1');
|
|
user.setPassword('user1');
|
|
await user.signUp();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
get: user(id: $id) {
|
|
objectId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: user.id,
|
|
},
|
|
});
|
|
|
|
expect(getResult.data.get.objectId).toEqual(user.id);
|
|
});
|
|
|
|
it('should support Installation class', async () => {
|
|
const installation = new Parse.Installation();
|
|
await installation.save({
|
|
deviceType: 'foo',
|
|
});
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
get: installation(id: $id) {
|
|
objectId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: installation.id,
|
|
},
|
|
});
|
|
|
|
expect(getResult.data.get.objectId).toEqual(installation.id);
|
|
});
|
|
|
|
it('should support Role class', async () => {
|
|
const roleACL = new Parse.ACL();
|
|
roleACL.setPublicReadAccess(true);
|
|
const role = new Parse.Role('MyRole', roleACL);
|
|
await role.save();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
get: role(id: $id) {
|
|
objectId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: role.id,
|
|
},
|
|
});
|
|
|
|
expect(getResult.data.get.objectId).toEqual(role.id);
|
|
});
|
|
|
|
it('should support Session class', async () => {
|
|
const user = new Parse.User();
|
|
user.setUsername('user1');
|
|
user.setPassword('user1');
|
|
await user.signUp();
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const session = await Parse.Session.current();
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
get: session(id: $id) {
|
|
id
|
|
objectId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: session.id,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Session-Token': session.getSessionToken(),
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(getResult.data.get.objectId).toEqual(session.id);
|
|
});
|
|
|
|
it('should support Product class', async () => {
|
|
const Product = Parse.Object.extend('_Product');
|
|
const product = new Product();
|
|
await product.save(
|
|
{
|
|
productIdentifier: 'foo',
|
|
icon: new Parse.File('icon', ['foo']),
|
|
order: 1,
|
|
title: 'Foo',
|
|
subtitle: 'My product',
|
|
},
|
|
{ useMasterKey: true }
|
|
);
|
|
|
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
|
|
|
const getResult = await apolloClient.query({
|
|
query: gql`
|
|
query GetSomeObject($id: ID!) {
|
|
get: product(id: $id) {
|
|
objectId
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
id: product.id,
|
|
},
|
|
context: {
|
|
headers: {
|
|
'X-Parse-Master-Key': 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(getResult.data.get.objectId).toEqual(product.id);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Custom API', () => {
|
|
let httpServer;
|
|
const headers = {
|
|
'X-Parse-Application-Id': 'test',
|
|
'X-Parse-Javascript-Key': 'test',
|
|
};
|
|
let apolloClient;
|
|
|
|
beforeAll(async () => {
|
|
const expressApp = express();
|
|
httpServer = http.createServer(expressApp);
|
|
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
|
|
graphQLPath: '/graphql',
|
|
graphQLCustomTypeDefs: gql`
|
|
extend type Query {
|
|
hello: String @resolve
|
|
hello2: String @resolve(to: "hello")
|
|
userEcho(user: CreateUserFieldsInput!): User! @resolve
|
|
hello3: String! @mock(with: "Hello world!")
|
|
hello4: User! @mock(with: { username: "somefolk" })
|
|
}
|
|
`,
|
|
});
|
|
parseGraphQLServer.applyGraphQL(expressApp);
|
|
await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve));
|
|
const httpLink = createUploadLink({
|
|
uri: 'http://localhost:13377/graphql',
|
|
fetch,
|
|
headers,
|
|
});
|
|
apolloClient = new ApolloClient({
|
|
link: httpLink,
|
|
cache: new InMemoryCache(),
|
|
defaultOptions: {
|
|
query: {
|
|
fetchPolicy: 'no-cache',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await httpServer.close();
|
|
});
|
|
|
|
it('can resolve a custom query using default function name', async () => {
|
|
Parse.Cloud.define('hello', async () => {
|
|
return 'Hello world!';
|
|
});
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query Hello {
|
|
hello
|
|
}
|
|
`,
|
|
});
|
|
|
|
expect(result.data.hello).toEqual('Hello world!');
|
|
});
|
|
|
|
it('can resolve a custom query using function name set by "to" argument', async () => {
|
|
Parse.Cloud.define('hello', async () => {
|
|
return 'Hello world!';
|
|
});
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query Hello {
|
|
hello2
|
|
}
|
|
`,
|
|
});
|
|
|
|
expect(result.data.hello2).toEqual('Hello world!');
|
|
});
|
|
|
|
it('should resolve auto types', async () => {
|
|
Parse.Cloud.define('userEcho', async req => {
|
|
return req.params.user;
|
|
});
|
|
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query UserEcho($user: CreateUserFieldsInput!) {
|
|
userEcho(user: $user) {
|
|
username
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
user: {
|
|
username: 'somefolk',
|
|
password: 'somepassword',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.data.userEcho.username).toEqual('somefolk');
|
|
});
|
|
|
|
it('can mock a custom query with string', async () => {
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query Hello {
|
|
hello3
|
|
}
|
|
`,
|
|
});
|
|
|
|
expect(result.data.hello3).toEqual('Hello world!');
|
|
});
|
|
|
|
it('can mock a custom query with auto type', async () => {
|
|
const result = await apolloClient.query({
|
|
query: gql`
|
|
query Hello {
|
|
hello4 {
|
|
username
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
|
|
expect(result.data.hello4.username).toEqual('somefolk');
|
|
});
|
|
});
|
|
});
|