Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions spec/GraphQLQueryComplexity.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,38 @@ describe('graphql query complexity', () => {
expect(result.errors).toBeUndefined();
});
});

describe('where argument breadth', () => {
it('should enforce depth and field limits regardless of where argument breadth', async () => {
await setupGraphQL({
requestComplexity: { graphQLDepth: 3, graphQLFields: 200, subqueryDepth: 1 },
});
// The GraphQL where argument may contain many OR branches, but the
// complexity check correctly measures the selection set depth/fields,
// not the where variable content. A query exceeding graphQLDepth is
// rejected even when the where argument is simple.
const result = await graphqlRequest(buildDeepQuery());
expect(result.errors).toBeDefined();
expect(result.errors[0].message).toMatch(
/GraphQL query depth of \d+ exceeds maximum allowed depth of 3/
);
});

it('should allow query with wide where argument when selection set is within limits', async () => {
await setupGraphQL({
requestComplexity: { graphQLDepth: 10, graphQLFields: 200, subqueryDepth: 1 },
});

const obj = new Parse.Object('TestItem');
obj.set('name', 'test');
await obj.save();

// Wide where with many OR branches — complexity check measures selection
// set depth and field count, not where argument structure
const orBranches = Array.from({ length: 20 }, (_, i) => `{ name: { equalTo: "test${i}" } }`).join(', ');
const query = `{ testItems(where: { OR: [${orBranches}] }) { edges { node { objectId } } } }`;
const result = await graphqlRequest(query);
expect(result.errors).toBeUndefined();
});
});
});
87 changes: 87 additions & 0 deletions spec/RequestComplexity.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,93 @@ describe('request complexity', () => {
rest.find(config, auth.nobody(config), '_User', where)
).toBeResolved();
});

it('should allow multiple sibling $inQuery at same depth within limit', async () => {
await reconfigureServer({
requestComplexity: { subqueryDepth: 1 },
});
config = Config.get('test');
// Multiple sibling $inQuery operators in $or, each at depth 1 — within the limit
const where = {
$or: [
{ username: { $inQuery: { className: '_User', where: { username: 'a' } } } },
{ username: { $inQuery: { className: '_User', where: { username: 'b' } } } },
{ username: { $inQuery: { className: '_User', where: { username: 'c' } } } },
],
};
await expectAsync(
rest.find(config, auth.nobody(config), '_User', where)
).toBeResolved();
});

it('should reject sibling $inQuery when nested beyond depth limit', async () => {
await reconfigureServer({
requestComplexity: { subqueryDepth: 1 },
});
config = Config.get('test');
// Each sibling contains a nested $inQuery at depth 2 — exceeds limit
const where = {
$or: [
{
username: {
$inQuery: {
className: '_User',
where: { username: { $inQuery: { className: '_User', where: {} } } },
},
},
},
{
username: {
$inQuery: {
className: '_User',
where: { username: { $inQuery: { className: '_User', where: {} } } },
},
},
},
],
};
await expectAsync(
rest.find(config, auth.nobody(config), '_User', where)
).toBeRejectedWith(
jasmine.objectContaining({
message: jasmine.stringMatching(/Subquery nesting depth exceeds maximum allowed depth of 1/),
})
);
});

it('should allow multiple sibling $notInQuery at same depth within limit', async () => {
await reconfigureServer({
requestComplexity: { subqueryDepth: 1 },
});
config = Config.get('test');
const where = {
$or: [
{ username: { $notInQuery: { className: '_User', where: { username: 'a' } } } },
{ username: { $notInQuery: { className: '_User', where: { username: 'b' } } } },
{ username: { $notInQuery: { className: '_User', where: { username: 'c' } } } },
],
};
await expectAsync(
rest.find(config, auth.nobody(config), '_User', where)
).toBeResolved();
});

it('should allow mixed sibling $inQuery and $notInQuery at same depth within limit', async () => {
await reconfigureServer({
requestComplexity: { subqueryDepth: 1 },
});
config = Config.get('test');
const where = {
$or: [
{ username: { $inQuery: { className: '_User', where: { username: 'a' } } } },
{ username: { $notInQuery: { className: '_User', where: { username: 'b' } } } },
{ username: { $inQuery: { className: '_User', where: { username: 'c' } } } },
],
};
await expectAsync(
rest.find(config, auth.nobody(config), '_User', where)
).toBeResolved();
});
});

describe('query depth', () => {
Expand Down
Loading