From 38caffede4626b689d84a80485a60dac795cb799 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Tue, 5 Mar 2024 15:29:15 +0100 Subject: [PATCH] fix(enrich): parsing of literals passed within args on scalar fields (#158) * Add enrich test with input object containing literal value on scalar field * Add test for delete by non-key field * Improve test title * Use index vs existence of `parseLiteral` fn to check if field is a leaf * Add changelog entry * Prettier format * Improve test titles * Improve changelog wording * Improve comment wording --- CHANGELOG.md | 2 ++ lib/resolvers/parse/ast/literal.js | 4 ++-- lib/resolvers/parse/ast2cqn/where.js | 3 ++- test/tests/annotations.test.js | 4 +++- test/tests/enrich.test.js | 21 +++++++++++++++++++- test/tests/mutations/delete.test.js | 29 ++++++++++++++++++++++++++++ 6 files changed, 58 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a720d5b..48931262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Type parsing error for literal values passed within arguments on fields of scalar type differing from the literal type. This case occurred for delete mutations when the filter operands had a type other than `Int`. + ### Removed ## Version 0.10.0 - 2023-01-30 diff --git a/lib/resolvers/parse/ast/literal.js b/lib/resolvers/parse/ast/literal.js index 653a360f..aedb46f2 100644 --- a/lib/resolvers/parse/ast/literal.js +++ b/lib/resolvers/parse/ast/literal.js @@ -12,8 +12,8 @@ const _getTypeFrom_fields = (_fields, path, index = 0) => { const _field = _fields[name] const type = _getTypeFrom_fieldOr_arg(_field) - // If type has the parseLiteral function it is a scalar type -> leaf -> end of path - if (type.parseLiteral) return type + // If we are at the end of the path, this field is a leaf and therefore is of scalar type with a parseLiteral function + if (index === path.length) return type const next = path[index] // Is the next path element an argument? If yes, follow the argument diff --git a/lib/resolvers/parse/ast2cqn/where.js b/lib/resolvers/parse/ast2cqn/where.js index 3004c3a1..ed3ef4c9 100644 --- a/lib/resolvers/parse/ast2cqn/where.js +++ b/lib/resolvers/parse/ast2cqn/where.js @@ -34,7 +34,8 @@ const _objectFieldTo_xpr = (objectField, columnName) => { const operand = objectField.value if (gqlOperator === LOGICAL_OPERATORS.in) { - const list = operand.kind === Kind.LIST ? operand.values.map(value => ({ val: value.value })) : [{ val: operand.value }] + const list = + operand.kind === Kind.LIST ? operand.values.map(value => ({ val: value.value })) : [{ val: operand.value }] return [ref, _gqlOperatorToCdsOperator(gqlOperator), { list }] } diff --git a/test/tests/annotations.test.js b/test/tests/annotations.test.js index df1d4806..696174ba 100644 --- a/test/tests/annotations.test.js +++ b/test/tests/annotations.test.js @@ -39,7 +39,9 @@ describe('graphql - annotations', () => { } ` const response = await POST(path, { query }) - expect(response.data.errors[0].message).toMatch(/^Cannot query field "AnnotatedWithAtProtocolNone" on type "Query"\./) + expect(response.data.errors[0].message).toMatch( + /^Cannot query field "AnnotatedWithAtProtocolNone" on type "Query"\./ + ) }) test('service annotated with non-GraphQL protocol is not served', async () => { diff --git a/test/tests/enrich.test.js b/test/tests/enrich.test.js index 168e73ff..311329e7 100644 --- a/test/tests/enrich.test.js +++ b/test/tests/enrich.test.js @@ -179,7 +179,7 @@ describe('graphql - enrich AST ', () => { expect(value).toEqual(2) }) - test('parsing of literal value in nested input value', async () => { + test('parsing of literal value in nested input value passed as arg on field with sub-selection of fields', async () => { const query = gql` { AdminService { @@ -198,6 +198,25 @@ describe('graphql - enrich AST ', () => { const value = enrichedAST[0].selectionSet.selections[0].arguments[0].value.fields[0].value.fields[0].value.value expect(value).toEqual(201) }) + + test('parsing of literal value in nested input value passed as arg on field of scalar type', async () => { + const query = gql` + mutation { + AdminService { + Authors { + delete(filter: { dateOfBirth: { eq: "1818-07-30T00:00:00.000Z" } }) + } + } + } + ` + const document = parse(query) + const fakeInfo = fakeInfoObject(document, bookshopSchema, 'Mutation') + const enrichedAST = enrich(fakeInfo) + const value = + enrichedAST[0].selectionSet.selections[0].selectionSet.selections[0].arguments[0].value.fields[0].value + .fields[0].value.value + expect(value).toEqual('1818-07-30') + }) }) describe('variable values are substituted into the AST', () => { diff --git a/test/tests/mutations/delete.test.js b/test/tests/mutations/delete.test.js index 919a19c0..50bfbc1b 100644 --- a/test/tests/mutations/delete.test.js +++ b/test/tests/mutations/delete.test.js @@ -100,6 +100,35 @@ describe('graphql - delete mutations', () => { ]) }) + test('delete single entry by filtering for non-key field', async () => { + const query = gql` + mutation { + AdminService { + Books { + delete(filter: { title: { eq: "Jane Eyre" } }) + } + } + } + ` + const data = { + AdminService: { + Books: { + delete: 1 + } + } + } + const response = await POST('/graphql', { query }) + expect(response.data).toEqual({ data }) + + const result = await SELECT.from('sap.capire.bookshop.Books').columns('ID', 'title') + expect(result).toEqual([ + { ID: 201, title: 'Wuthering Heights' }, + { ID: 251, title: 'The Raven' }, + { ID: 252, title: 'Eleonora' }, + { ID: 271, title: 'Catweazle' } + ]) + }) + test('delete multiple entries', async () => { const query = gql` mutation ($filter: AdminService_Books_filter) {