From 0fa57eb297f71c66b9fd7180edb129dc5b29e02b Mon Sep 17 00:00:00 2001 From: Petr Plenkov Date: Thu, 8 Dec 2022 16:10:27 +0100 Subject: [PATCH 01/15] Add repo url to point to a git project (#24) * update home page to point to a git project * Revert "update home page to point to a git project" This reverts commit dc05c23ff84af3233a908ab14574b02b718bb55a. * provide repository.url in package.json * package.json formatting --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a5a7f468..ddeaaf74 100644 --- a/package.json +++ b/package.json @@ -46,5 +46,9 @@ }, "jest": { "testTimeout": 10000 + }, + "repository": { + "type": "git", + "url": "https://github.com/cap-js/cds-adapter-graphql" } -} +} \ No newline at end of file From e1b7602cba8c865897f6baf183a6d1c36c01365d Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Fri, 9 Dec 2022 13:43:30 +0100 Subject: [PATCH 02/15] docs: prepare changelog for next release (#21) --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d83961b7..53235807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Version 0.2.0 - tbd + +### Added + +### Changed + +### Fixed + +### Removed + ## Version 0.1.0 - 2022-12-08 ### Added From 15ff45518ce4729d291449d44dfd510a4515be40 Mon Sep 17 00:00:00 2001 From: Robert Witt <46402879+robertwitt@users.noreply.github.com> Date: Thu, 22 Dec 2022 09:25:14 +0100 Subject: [PATCH 03/15] fix: server crash if "localized" is used as property name (#26) * fix: server crash if "localized" is used as property name * Update lib/schema/util/index.js Co-authored-by: Marcel Schwarz * Review Co-authored-by: Marcel Schwarz --- CHANGELOG.md | 2 ++ lib/schema/util/index.js | 2 +- test/resources/edge-cases/srv/field-named-localized.cds | 5 +++-- test/schemas/edge-cases/field-named-localized.gql | 5 +++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53235807..0a1f912c 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 +- Fixed a server crash that occourred if an entity property is named `localized`. + ### Removed ## Version 0.1.0 - 2022-12-08 diff --git a/lib/schema/util/index.js b/lib/schema/util/index.js index 5672d7ac..aea667f6 100644 --- a/lib/schema/util/index.js +++ b/lib/schema/util/index.js @@ -3,7 +3,7 @@ const hasScalarFields = entity => ([, el]) => !(shouldElementBeIgnored(el) || el.isAssociation || el.isComposition) ) -const _isLocalized = element => element.name === 'localized' && element.target.endsWith('.texts') +const _isLocalized = element => element.name === 'localized' && element.target?.endsWith('.texts') const shouldElementBeIgnored = element => element.name.startsWith('up_') || _isLocalized(element) diff --git a/test/resources/edge-cases/srv/field-named-localized.cds b/test/resources/edge-cases/srv/field-named-localized.cds index 5880ebb2..9089726d 100644 --- a/test/resources/edge-cases/srv/field-named-localized.cds +++ b/test/resources/edge-cases/srv/field-named-localized.cds @@ -2,8 +2,9 @@ using {managed} from '@sap/cds/common'; service FieldNamedLocalizedService { entity localized : managed { - key ID : Integer; - root : Association to Root; + key ID : Integer; + root : Association to Root; + localized : String; // to test that a property only named 'localized' is not confused with localized keyword } entity Root : managed { diff --git a/test/schemas/edge-cases/field-named-localized.gql b/test/schemas/edge-cases/field-named-localized.gql index e1ca36a2..d24b37b6 100644 --- a/test/schemas/edge-cases/field-named-localized.gql +++ b/test/schemas/edge-cases/field-named-localized.gql @@ -88,6 +88,7 @@ type FieldNamedLocalizedService_localized { ID: Int createdAt: Timestamp createdBy: String + localized: String modifiedAt: Timestamp modifiedBy: String root: FieldNamedLocalizedService_Root @@ -98,6 +99,7 @@ input FieldNamedLocalizedService_localized_C { ID: Int createdAt: Timestamp createdBy: String + localized: String modifiedAt: Timestamp modifiedBy: String root: FieldNamedLocalizedService_Root_C @@ -108,6 +110,7 @@ input FieldNamedLocalizedService_localized_U { ID: Int createdAt: Timestamp createdBy: String + localized: String modifiedAt: Timestamp modifiedBy: String root: FieldNamedLocalizedService_Root_U @@ -123,6 +126,7 @@ input FieldNamedLocalizedService_localized_filter { ID: [Int_filter] createdAt: [Timestamp_filter] createdBy: [String_filter] + localized: [String_filter] modifiedAt: [Timestamp_filter] modifiedBy: [String_filter] root_ID: [Int_filter] @@ -145,6 +149,7 @@ input FieldNamedLocalizedService_localized_orderBy { ID: SortDirection createdAt: SortDirection createdBy: SortDirection + localized: SortDirection modifiedAt: SortDirection modifiedBy: SortDirection root_ID: SortDirection From 04fd9904c592f5e1e97e2dfa7ee703202b68fc81 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Tue, 3 Jan 2023 11:28:51 +0100 Subject: [PATCH 04/15] test: lower timestamp precision for now (#28) --- test/tests/types.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/tests/types.test.js b/test/tests/types.test.js index 6aa61645..0bedbf08 100644 --- a/test/tests/types.test.js +++ b/test/tests/types.test.js @@ -1183,8 +1183,8 @@ describe('graphql - types parsing and validation', () => { expect(result).toContainEqual({ [field]: value }) }) - test('cds.Timestamp is correctly parsed from input literal high precision timestamp string value', async () => { - const value = '2021-06-27T14:52:23.123456789Z' + test('cds.Timestamp is correctly parsed from input literal timestamp string value', async () => { + const value = '2021-06-27T14:52:23.123Z' const returnValue = '2021-06-27T14:52:23.123Z' const query = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: returnValue }] } } } @@ -1225,8 +1225,8 @@ describe('graphql - types parsing and validation', () => { expect(result).toContainEqual({ [field]: value }) }) - test('cds.Timestamp is correctly parsed from variable high precision timestamp string value', async () => { - const value = '2021-06-27T14:52:23.123456789Z' + test('cds.Timestamp is correctly parsed from variable timestamp string value', async () => { + const value = '2021-06-27T14:52:23.123Z' const returnValue = '2021-06-27T14:52:23.123Z' const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: returnValue }] } } } From 5f78b9454812bec775a5f298c4d98ef2f4c1f9d9 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Wed, 11 Jan 2023 11:24:35 +0100 Subject: [PATCH 05/15] feat: register alias field resolver in schema generation (#31) * Register aliasFieldResolver in schema generation * Add changelog entry * Change changelog entry wording --- CHANGELOG.md | 2 ++ lib/index.js | 3 +-- lib/resolvers/field.js | 16 +++++++++++++++- lib/resolvers/index.js | 4 ++-- lib/schema/index.js | 3 ++- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a1f912c..ffbcf3c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Register `aliasFieldResolver` during schema generation instead of passing it to the GraphQL server + ### Fixed - Fixed a server crash that occourred if an entity property is named `localized`. diff --git a/lib/index.js b/lib/index.js index fa30e2de..ae0e511d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,11 +1,10 @@ const { graphqlHTTP } = require('express-graphql') const { generateSchema4 } = require('./schema') -const { fieldResolver } = require('./resolvers') function GraphQLAdapter(services, options) { const schema = generateSchema4(services) - return graphqlHTTP({ fieldResolver, schema, ...options }) + return graphqlHTTP({ schema, ...options }) } module.exports = GraphQLAdapter diff --git a/lib/resolvers/field.js b/lib/resolvers/field.js index 31f113a8..67131f31 100644 --- a/lib/resolvers/field.js +++ b/lib/resolvers/field.js @@ -1,5 +1,19 @@ +const { isObjectType, isIntrospectionType } = require('graphql') + // The GraphQL.js defaultFieldResolver does not support returning aliased values that resolve to fields with aliases -module.exports = (source, args, context, info) => { +function aliasFieldResolver(source, args, contextValue, info) { const responseKey = info.fieldNodes[0].alias ? info.fieldNodes[0].alias.value : info.fieldName return source[responseKey] } + +const registerAliasFieldResolvers = schema => { + for (const type of Object.values(schema.getTypeMap())) { + if (!isObjectType(type) || isIntrospectionType(type)) continue + + for (const field of Object.values(type.getFields())) { + if (!field.resolve) field.resolve = aliasFieldResolver + } + } +} + +module.exports = registerAliasFieldResolvers diff --git a/lib/resolvers/index.js b/lib/resolvers/index.js index 72f7a1f6..d3abda11 100644 --- a/lib/resolvers/index.js +++ b/lib/resolvers/index.js @@ -1,7 +1,7 @@ -const fieldResolver = require('./field') +const registerAliasFieldResolvers = require('./field') const createRootResolvers = require('./root') module.exports = { - fieldResolver, + registerAliasFieldResolvers, createRootResolvers } diff --git a/lib/schema/index.js b/lib/schema/index.js index d87f9748..a96e4701 100644 --- a/lib/schema/index.js +++ b/lib/schema/index.js @@ -1,7 +1,7 @@ const queryGenerator = require('./query') const mutationGenerator = require('./mutation') const { GraphQLSchema, printSchema } = require('graphql') -const { createRootResolvers } = require('../resolvers') +const { createRootResolvers, registerAliasFieldResolvers } = require('../resolvers') class SchemaGenerator { generate(services) { @@ -10,6 +10,7 @@ class SchemaGenerator { const query = queryGenerator(cache).generateQueryObjectType(services, resolvers.Query) const mutation = mutationGenerator(cache).generateMutationObjectType(services, resolvers.Mutation) this._schema = new GraphQLSchema({ query, mutation }) + registerAliasFieldResolvers(this._schema) return this } From a538e66165fadd0f35f23a477f5f84b08e11e66c Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Wed, 11 Jan 2023 11:29:44 +0100 Subject: [PATCH 06/15] refactor: use GraphQL Type APIs instead of internal properties (#29) * refactor: use GraphQL Type APIs instead of internal properties * Use Type API in test util --- lib/resolvers/parse/ast/enrich.js | 10 +++++----- test/util/index.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/resolvers/parse/ast/enrich.js b/lib/resolvers/parse/ast/enrich.js index 7f7cce6f..a14e3e29 100644 --- a/lib/resolvers/parse/ast/enrich.js +++ b/lib/resolvers/parse/ast/enrich.js @@ -34,10 +34,10 @@ const _traverseArgumentOrObjectField = (info, argumentOrObjectField, _fieldOr_ar argumentOrObjectField.value = substituteVariable(info, value) break case Kind.LIST: - _traverseListValue(info, value, type._fields) + _traverseListValue(info, value, type.getFields()) break case Kind.OBJECT: - _traverseObjectValue(info, value, type._fields) + _traverseObjectValue(info, value, type.getFields()) break } } @@ -60,7 +60,7 @@ const _getTypeFrom_fieldOr_arg = _field => { const _traverseField = (info, field, _field) => { if (field.selectionSet) { const type = _getTypeFrom_fieldOr_arg(_field) - _traverseSelectionSet(info, field.selectionSet, type._fields) + _traverseSelectionSet(info, field.selectionSet, type.getFields()) } field.arguments.forEach(arg => { @@ -79,8 +79,8 @@ module.exports = info => { const deepClonedFieldNodes = JSON.parse(JSON.stringify(info.fieldNodes)) const rootTypeName = info.parentType.name - const rootType = info.schema._typeMap[rootTypeName] - _traverseFieldNodes(info, deepClonedFieldNodes, rootType._fields) + const rootType = info.schema.getType(rootTypeName) + _traverseFieldNodes(info, deepClonedFieldNodes, rootType.getFields()) return deepClonedFieldNodes } diff --git a/test/util/index.js b/test/util/index.js index a261acbb..d24079d9 100644 --- a/test/util/index.js +++ b/test/util/index.js @@ -32,7 +32,7 @@ const fakeInfoObject = (document, schema, parentTypeName, variables) => { return { fieldNodes: operationDefinition.selectionSet.selections, schema, - parentType: schema._typeMap[parentTypeName], + parentType: schema.getType(parentTypeName), variableValues: { ...variables }, fragments } From 31c8eff8aee5112f5d1f7cb5a3b04ae4c19185a2 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Wed, 11 Jan 2023 15:32:40 +0100 Subject: [PATCH 07/15] refactor: make root resolver a named function (#33) --- lib/resolvers/root.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/resolvers/root.js b/lib/resolvers/root.js index c0ed8f72..2e943e81 100644 --- a/lib/resolvers/root.js +++ b/lib/resolvers/root.js @@ -4,23 +4,24 @@ const resolveQuery = require('./query') const resolveMutation = require('./mutation') const { enrichAST } = require('./parse/ast') -const _wrapResolver = (service, resolver) => (root, args, context, info) => { - const response = {} +const _wrapResolver = (service, resolver) => + function CDSRootResolver(root, args, context, info) { + const response = {} - const enrichedFieldNodes = enrichAST(info) + const enrichedFieldNodes = enrichAST(info) - for (const fieldNode of enrichedFieldNodes) { - for (const field of fieldNode.selectionSet.selections) { - const fieldName = field.name.value - const entity = service.entities[fieldName] - const responseKey = field.alias?.value || fieldName + for (const fieldNode of enrichedFieldNodes) { + for (const field of fieldNode.selectionSet.selections) { + const fieldName = field.name.value + const entity = service.entities[fieldName] + const responseKey = field.alias?.value || fieldName - response[responseKey] = resolver(service, entity, field) + response[responseKey] = resolver(service, entity, field) + } } - } - return response -} + return response + } module.exports = services => { const Query = {} From 317b54bacef0c455b29302afd90340dfa0775b08 Mon Sep 17 00:00:00 2001 From: Daniel Hutzel Date: Wed, 18 Jan 2023 14:24:13 +0100 Subject: [PATCH 08/15] chore: simplified protocols config (#27) * Simplified config * Rm unneeded middlewares config from package.jsons * Move server.js up one level into project roots * Remove unused server.js Co-authored-by: Marcel Schwarz --- README.md | 6 ++---- test/resources/bookshop-graphql/package.json | 7 ------- test/resources/{types/srv => bookshop-graphql}/server.js | 4 ++-- test/resources/model-structure/package.json | 7 ------- test/resources/model-structure/srv/server.js | 6 ------ test/resources/types/package.json | 7 ------- test/resources/{bookshop-graphql/srv => types}/server.js | 4 ++-- 7 files changed, 6 insertions(+), 35 deletions(-) delete mode 100644 test/resources/bookshop-graphql/package.json rename test/resources/{types/srv => bookshop-graphql}/server.js (87%) delete mode 100644 test/resources/model-structure/package.json delete mode 100644 test/resources/model-structure/srv/server.js delete mode 100644 test/resources/types/package.json rename test/resources/{bookshop-graphql/srv => types}/server.js (87%) diff --git a/README.md b/README.md index afb9e091..cf23844e 100644 --- a/README.md +++ b/README.md @@ -16,19 +16,17 @@ _**WARNING:** This package is in an early general availability state. This means npm add @cap-js/graphql ``` -2. Enable [middlewares](https://cap.cloud.sap/docs/node.js/middlewares) and the GraphQL adapter in your project's `package.json`: +2. Register the GraphQL adapter in your project's `package.json`: ```jsonc { "cds": { - "requires": { - "middlewares": true - }, "protocols": { "graphql": { "path": "/graphql", "impl": "@cap-js/graphql" } } } } ``` + > Note: This automatically enables the new [middlewares architecture](https://cap.cloud.sap/docs/node.js/middlewares) in Node.js. 3. Run your server as usual, e.g. using `cds watch`. > The runtime will automatically serve all services via GraphQL at the default configured endpoint. diff --git a/test/resources/bookshop-graphql/package.json b/test/resources/bookshop-graphql/package.json deleted file mode 100644 index 0ab2fbde..00000000 --- a/test/resources/bookshop-graphql/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cds": { - "requires": { - "middlewares": true - } - } -} \ No newline at end of file diff --git a/test/resources/types/srv/server.js b/test/resources/bookshop-graphql/server.js similarity index 87% rename from test/resources/types/srv/server.js rename to test/resources/bookshop-graphql/server.js index 76d68bbd..b4c2bd74 100644 --- a/test/resources/types/srv/server.js +++ b/test/resources/bookshop-graphql/server.js @@ -2,5 +2,5 @@ const cds = require('@sap/cds') const path = require('path') cds.env.protocols = { - graphql: { path: '/graphql', impl: path.join(__dirname, '../../../../index.js') } -} \ No newline at end of file + graphql: { path: '/graphql', impl: path.join(__dirname, '../../../index.js') } +} diff --git a/test/resources/model-structure/package.json b/test/resources/model-structure/package.json deleted file mode 100644 index 0ab2fbde..00000000 --- a/test/resources/model-structure/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cds": { - "requires": { - "middlewares": true - } - } -} \ No newline at end of file diff --git a/test/resources/model-structure/srv/server.js b/test/resources/model-structure/srv/server.js deleted file mode 100644 index 76d68bbd..00000000 --- a/test/resources/model-structure/srv/server.js +++ /dev/null @@ -1,6 +0,0 @@ -const cds = require('@sap/cds') -const path = require('path') - -cds.env.protocols = { - graphql: { path: '/graphql', impl: path.join(__dirname, '../../../../index.js') } -} \ No newline at end of file diff --git a/test/resources/types/package.json b/test/resources/types/package.json deleted file mode 100644 index fd6f41c9..00000000 --- a/test/resources/types/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cds": { - "requires": { - "middlewares": true - } - } -} diff --git a/test/resources/bookshop-graphql/srv/server.js b/test/resources/types/server.js similarity index 87% rename from test/resources/bookshop-graphql/srv/server.js rename to test/resources/types/server.js index 76d68bbd..b4c2bd74 100644 --- a/test/resources/bookshop-graphql/srv/server.js +++ b/test/resources/types/server.js @@ -2,5 +2,5 @@ const cds = require('@sap/cds') const path = require('path') cds.env.protocols = { - graphql: { path: '/graphql', impl: path.join(__dirname, '../../../../index.js') } -} \ No newline at end of file + graphql: { path: '/graphql', impl: path.join(__dirname, '../../../index.js') } +} From 3e570fedeff4ca30386fa6aeb4e710c8bff4f450 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Wed, 18 Jan 2023 14:53:59 +0100 Subject: [PATCH 09/15] chore: add CODEOWNERS file for automated PR reviewer assignment (#34) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..b2a1648b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @cap-js/node-js-runtime From f75415365b324975ae561a796385492a86a597b0 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Thu, 19 Jan 2023 15:34:23 +0100 Subject: [PATCH 10/15] style: enable prettier to format inline queries (#35) * Add dummy gql tag for query formatting and syntax highlighting * Prettier format --- lib/resolvers/parse/ast/util/index.js | 2 +- test/tests/context.test.js | 18 +++-- test/tests/enrich.test.js | 5 +- test/tests/localized.test.js | 31 +++++---- test/tests/mutations/create.test.js | 29 ++++---- test/tests/mutations/delete.test.js | 35 +++++----- test/tests/mutations/update.test.js | 77 +++++++------------- test/tests/queries/aliases.test.js | 53 +++++++------- test/tests/queries/filter.test.js | 89 ++++++++++-------------- test/tests/queries/fragments.test.js | 27 +++---- test/tests/queries/meta.test.js | 23 +++--- test/tests/queries/orderBy.test.js | 13 ++-- test/tests/queries/paging-offset.test.js | 13 ++-- test/tests/queries/queries.test.js | 29 ++++---- test/tests/queries/totalCount.test.js | 13 ++-- test/tests/queries/variables.test.js | 21 +++--- test/tests/types.test.js | 6 +- test/util/index.js | 14 +++- 18 files changed, 237 insertions(+), 261 deletions(-) diff --git a/lib/resolvers/parse/ast/util/index.js b/lib/resolvers/parse/ast/util/index.js index 9ffc969b..d7970344 100644 --- a/lib/resolvers/parse/ast/util/index.js +++ b/lib/resolvers/parse/ast/util/index.js @@ -1,3 +1,3 @@ const isPlainObject = value => value !== null && typeof value === 'object' && !Buffer.isBuffer(value) -module.exports = { isPlainObject } \ No newline at end of file +module.exports = { isPlainObject } diff --git a/test/tests/context.test.js b/test/tests/context.test.js index 49329964..e3ca3e08 100644 --- a/test/tests/context.test.js +++ b/test/tests/context.test.js @@ -1,13 +1,14 @@ describe('graphql - context is set', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../util') const { axios, POST } = cds.test(path.join(__dirname, '../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes axios.defaults.validateStatus = false beforeEach(async () => { - const deleteAll = `#graphql + const deleteAll = gql` mutation { TestService { Foo { @@ -17,7 +18,7 @@ describe('graphql - context is set', () => { } ` await POST('/graphql', { query: deleteAll }) - const insertOne = `#graphql + const insertOne = gql` mutation { TestService { Foo { @@ -32,7 +33,7 @@ describe('graphql - context is set', () => { }) test('read', async () => { - const query = `#graphql + const query = gql` { TestService { Foo { @@ -49,7 +50,7 @@ describe('graphql - context is set', () => { }) test('create', async () => { - const query = `#graphql + const query = gql` mutation { TestService { Foo { @@ -68,14 +69,11 @@ describe('graphql - context is set', () => { }) test('update', async () => { - const query = `#graphql + const query = gql` mutation { TestService { Foo { - update( - filter: { ID: { eq: 1 } }, - input: { bar: "boo" } - ) { + update(filter: { ID: { eq: 1 } }, input: { bar: "boo" }) { ID bar } @@ -90,7 +88,7 @@ describe('graphql - context is set', () => { }) test('delete', async () => { - const query = `#graphql + const query = gql` mutation { TestService { Foo { diff --git a/test/tests/enrich.test.js b/test/tests/enrich.test.js index 56375096..6ec334eb 100644 --- a/test/tests/enrich.test.js +++ b/test/tests/enrich.test.js @@ -1,4 +1,5 @@ describe('graphql - enrich AST with parsed inline literal values', () => { + const { gql } = require('../util') const { parse } = require('graphql') const enrich = require('../../lib/resolvers/parse/ast/enrich') const { models } = require('../resources') @@ -12,7 +13,7 @@ describe('graphql - enrich AST with parsed inline literal values', () => { }) test('parsing of literal value as top level argument', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors(top: 2) { @@ -31,7 +32,7 @@ describe('graphql - enrich AST with parsed inline literal values', () => { }) test('parsing of literal value in nested input value', async () => { - const query = `#graphql + const query = gql` { AdminService { Books(filter: { ID: { eq: 201 } }) { diff --git a/test/tests/localized.test.js b/test/tests/localized.test.js index 5edf91b0..058298dd 100644 --- a/test/tests/localized.test.js +++ b/test/tests/localized.test.js @@ -1,6 +1,7 @@ describe('graphql - queries with localized data', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../util') const { axios, POST, data } = cds.test(path.join(__dirname, '../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -8,7 +9,7 @@ describe('graphql - queries with localized data', () => { data.autoReset(true) test('query with default locale', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -39,7 +40,7 @@ describe('graphql - queries with localized data', () => { }) test('query with locale de', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -70,7 +71,7 @@ describe('graphql - queries with localized data', () => { }) test('query with unknown locale defaults to en', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -101,14 +102,14 @@ describe('graphql - queries with localized data', () => { }) test('create localized data with deep insert', async () => { - const query = `#graphql + const query = gql` mutation ($input: AdminService_Books_C) { AdminService { Books { create(input: [$input]) { ID title - texts { + texts { nodes { locale title @@ -141,8 +142,8 @@ describe('graphql - queries with localized data', () => { }) test('update localized data', async () => { - const query = `#graphql - mutation($filter: AdminService_Books_filter, $input: AdminService_Books_U!) { + const query = gql` + mutation ($filter: AdminService_Books_filter, $input: AdminService_Books_U!) { AdminService { Books { update(filter: [$filter], input: $input) { @@ -175,7 +176,7 @@ describe('graphql - queries with localized data', () => { }) test('update empty localized data', async () => { - const queryCreate = `#graphql + const queryCreate = gql` mutation ($input: AdminService_Books_C) { AdminService { Books { @@ -202,8 +203,8 @@ describe('graphql - queries with localized data', () => { const response = await POST('/graphql', { query: queryCreate, variables: variablesCreate }) expect(response.data).toEqual({ data: dataCreate }) - const queryUpdate = `#graphql - mutation($filter: AdminService_Books_filter, $input: AdminService_Books_U!) { + const queryUpdate = gql` + mutation ($filter: AdminService_Books_filter, $input: AdminService_Books_U!) { AdminService { Books { update(filter: [$filter], input: $input) { @@ -236,14 +237,14 @@ describe('graphql - queries with localized data', () => { }) test('update localized data with empty texts array', async () => { - const queryCreate = `#graphql + const queryCreate = gql` mutation ($input: AdminService_Books_C) { AdminService { Books { create(input: [$input]) { ID title - texts { + texts { nodes { locale title @@ -271,8 +272,8 @@ describe('graphql - queries with localized data', () => { const response = await POST('/graphql', { query: queryCreate, variables: variablesCreate }) expect(response.data).toEqual({ data: dataCreate }) - const queryUpdate = `#graphql - mutation($filter: AdminService_Books_filter, $input: AdminService_Books_U!) { + const queryUpdate = gql` + mutation ($filter: AdminService_Books_filter, $input: AdminService_Books_U!) { AdminService { Books { update(filter: [$filter], input: $input) { @@ -305,7 +306,7 @@ describe('graphql - queries with localized data', () => { }) test('delete single entry should delete localized data', async () => { - const query = `#graphql + const query = gql` mutation ($filter: AdminService_Books_filter) { AdminService { Books { diff --git a/test/tests/mutations/create.test.js b/test/tests/mutations/create.test.js index 82ddf943..2d6f105f 100644 --- a/test/tests/mutations/create.test.js +++ b/test/tests/mutations/create.test.js @@ -1,6 +1,7 @@ describe('graphql - create mutations', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST, data } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -8,7 +9,7 @@ describe('graphql - create mutations', () => { data.autoReset(true) test('create empty entry', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { @@ -34,7 +35,7 @@ describe('graphql - create mutations', () => { }) test('create single entry without variables', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { @@ -60,7 +61,7 @@ describe('graphql - create mutations', () => { }) test('create single entry with variables', async () => { - const query = `#graphql + const query = gql` mutation ($input: AdminService_Books_C) { AdminService { Books { @@ -88,7 +89,7 @@ describe('graphql - create mutations', () => { }) test('create multiple entries', async () => { - const query = `#graphql + const query = gql` mutation ($input: [AdminService_Books_C]!) { AdminService { Books { @@ -122,7 +123,7 @@ describe('graphql - create mutations', () => { }) test('create entry with deep insert', async () => { - const query = `#graphql + const query = gql` mutation ($input: AdminService_Books_C) { AdminService { Books { @@ -168,7 +169,7 @@ describe('graphql - create mutations', () => { }) test('create entry with fragment on create mutation', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { @@ -198,7 +199,7 @@ describe('graphql - create mutations', () => { }) test('create entry with alias on service', async () => { - const query = `#graphql + const query = gql` mutation { myAlias: AdminService { Books { @@ -224,7 +225,7 @@ describe('graphql - create mutations', () => { }) test('create entry with alias on entity', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { myAlias: Books { @@ -250,7 +251,7 @@ describe('graphql - create mutations', () => { }) test('create entry with alias on create mutation', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { @@ -276,7 +277,7 @@ describe('graphql - create mutations', () => { }) test('create entry with alias on field of create mutation', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { @@ -303,7 +304,7 @@ describe('graphql - create mutations', () => { }) test('create entry with deep insert with aliases on nested fields', async () => { - const query = `#graphql + const query = gql` mutation ($input: AdminService_Books_C) { AdminService { Books { @@ -349,7 +350,7 @@ describe('graphql - create mutations', () => { }) test('create entry with aliases on all fields', async () => { - const query = `#graphql + const query = gql` mutation { myAliasA: AdminService { myAliasB: Books { @@ -375,7 +376,7 @@ describe('graphql - create mutations', () => { }) test('create entry with meta field __typename in create mutation', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { @@ -402,7 +403,7 @@ describe('graphql - create mutations', () => { }) test('create entry with meta field __typename on all nesting levels', async () => { - const query = `#graphql + const query = gql` mutation { __typename AdminService { diff --git a/test/tests/mutations/delete.test.js b/test/tests/mutations/delete.test.js index c2d7730b..5747bea1 100644 --- a/test/tests/mutations/delete.test.js +++ b/test/tests/mutations/delete.test.js @@ -1,6 +1,7 @@ describe('graphql - delete mutations', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST, data } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -8,11 +9,11 @@ describe('graphql - delete mutations', () => { data.autoReset(true) test('delete no entries', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { - delete(filter: { ID: { eq: 0 } } ) + delete(filter: { ID: { eq: 0 } }) } } } @@ -38,11 +39,11 @@ describe('graphql - delete mutations', () => { }) test('delete single entry without variables', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { - delete(filter: { ID: { eq: 207 } } ) + delete(filter: { ID: { eq: 207 } }) } } } @@ -67,7 +68,7 @@ describe('graphql - delete mutations', () => { }) test('delete single entry with variables', async () => { - const query = `#graphql + const query = gql` mutation ($filter: AdminService_Books_filter) { AdminService { Books { @@ -97,7 +98,7 @@ describe('graphql - delete mutations', () => { }) test('delete multiple entries', async () => { - const query = `#graphql + const query = gql` mutation ($filter: AdminService_Books_filter) { AdminService { Books { @@ -126,7 +127,7 @@ describe('graphql - delete mutations', () => { }) test('delete all entries', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { @@ -150,11 +151,11 @@ describe('graphql - delete mutations', () => { }) test('delete entry with alias on service', async () => { - const query = `#graphql + const query = gql` mutation { myAlias: AdminService { Books { - delete(filter: { ID: { eq: 207 } } ) + delete(filter: { ID: { eq: 207 } }) } } } @@ -179,11 +180,11 @@ describe('graphql - delete mutations', () => { }) test('delete entry with alias on entity', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { myAlias: Books { - delete(filter: { ID: { eq: 207 } } ) + delete(filter: { ID: { eq: 207 } }) } } } @@ -208,11 +209,11 @@ describe('graphql - delete mutations', () => { }) test('delete entry with alias on delete mutation', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { - myAlias: delete(filter: { ID: { eq: 207 } } ) + myAlias: delete(filter: { ID: { eq: 207 } }) } } } @@ -237,11 +238,11 @@ describe('graphql - delete mutations', () => { }) test('delete entry with aliases on all fields', async () => { - const query = `#graphql + const query = gql` mutation { myAliasA: AdminService { myAliasB: Books { - myAliasC: delete(filter: { ID: { eq: 207 } } ) + myAliasC: delete(filter: { ID: { eq: 207 } }) } } } @@ -266,14 +267,14 @@ describe('graphql - delete mutations', () => { }) test('delete entry with meta field __typename on all nesting levels', async () => { - const query = `#graphql + const query = gql` mutation { __typename AdminService { __typename Books { __typename - delete(filter: { ID: { eq: 207 } } ) + delete(filter: { ID: { eq: 207 } }) } } } diff --git a/test/tests/mutations/update.test.js b/test/tests/mutations/update.test.js index 34039b56..adcedc8b 100644 --- a/test/tests/mutations/update.test.js +++ b/test/tests/mutations/update.test.js @@ -1,6 +1,7 @@ describe('graphql - update mutations', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST, data } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -8,14 +9,11 @@ describe('graphql - update mutations', () => { data.autoReset(true) test('update no entries', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { - update( - filter: { title: { eq: "This book does not exist" } }, - input: { stock: 0 } - ) { + update(filter: { title: { eq: "This book does not exist" } }, input: { stock: 0 }) { title stock } @@ -44,14 +42,11 @@ describe('graphql - update mutations', () => { }) test('update single entry without variables', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { - update( - filter: { title: { eq: "Wuthering Heights" } }, - input: { title: "Sturmhöhe" } - ) { + update(filter: { title: { eq: "Wuthering Heights" } }, input: { title: "Sturmhöhe" }) { title stock } @@ -74,7 +69,7 @@ describe('graphql - update mutations', () => { }) test('update single entry with variables', async () => { - const query = `#graphql + const query = gql` mutation ($filter: AdminService_Books_filter, $input: AdminService_Books_U!) { AdminService { Books { @@ -102,7 +97,7 @@ describe('graphql - update mutations', () => { }) test('update multiple entries', async () => { - const query = `#graphql + const query = gql` mutation ($filter: AdminService_Books_filter, $input: AdminService_Books_U!) { AdminService { Books { @@ -141,7 +136,7 @@ describe('graphql - update mutations', () => { }) test('update all entries', async () => { - const query = `#graphql + const query = gql` mutation ($input: AdminService_Books_U!) { AdminService { Books { @@ -176,14 +171,11 @@ describe('graphql - update mutations', () => { }) test('update entry with fragment', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { - update( - filter: { title: { eq: "Wuthering Heights" } }, - input: { title: "Sturmhöhe" } - ) { + update(filter: { title: { eq: "Wuthering Heights" } }, input: { title: "Sturmhöhe" }) { ...myFragment } } @@ -209,14 +201,11 @@ describe('graphql - update mutations', () => { }) test('update entry with alias on service', async () => { - const query = `#graphql + const query = gql` mutation { myAlias: AdminService { Books { - update( - filter: { title: { eq: "Wuthering Heights" } }, - input: { title: "Sturmhöhe" } - ) { + update(filter: { title: { eq: "Wuthering Heights" } }, input: { title: "Sturmhöhe" }) { title } } @@ -238,14 +227,11 @@ describe('graphql - update mutations', () => { }) test('update entry with alias on entity', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { myAlias: Books { - update( - filter: { title: { eq: "Wuthering Heights" } }, - input: { title: "Sturmhöhe" } - ) { + update(filter: { title: { eq: "Wuthering Heights" } }, input: { title: "Sturmhöhe" }) { title } } @@ -267,14 +253,11 @@ describe('graphql - update mutations', () => { }) test('update entry with alias on update mutation', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { - myAlias: update( - filter: { title: { eq: "Wuthering Heights" } }, - input: { title: "Sturmhöhe" } - ) { + myAlias: update(filter: { title: { eq: "Wuthering Heights" } }, input: { title: "Sturmhöhe" }) { title } } @@ -296,14 +279,11 @@ describe('graphql - update mutations', () => { }) test('update entry with alias on field of update mutation', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { - update( - filter: { title: { eq: "Wuthering Heights" } }, - input: { title: "Sturmhöhe" } - ) { + update(filter: { title: { eq: "Wuthering Heights" } }, input: { title: "Sturmhöhe" }) { myAliasA: title myAliasB: title } @@ -326,14 +306,11 @@ describe('graphql - update mutations', () => { }) test('update entry with aliases on all fields', async () => { - const query = `#graphql + const query = gql` mutation { myAliasA: AdminService { myAliasB: Books { - myAliasC: update( - filter: { title: { eq: "Wuthering Heights" } }, - input: { title: "Sturmhöhe" } - ) { + myAliasC: update(filter: { title: { eq: "Wuthering Heights" } }, input: { title: "Sturmhöhe" }) { myAliasD: title } } @@ -355,14 +332,11 @@ describe('graphql - update mutations', () => { }) test('update entry with meta field __typename in update mutation', async () => { - const query = `#graphql + const query = gql` mutation { AdminService { Books { - update( - filter: { title: { eq: "Wuthering Heights" } }, - input: { title: "Sturmhöhe" } - ) { + update(filter: { title: { eq: "Wuthering Heights" } }, input: { title: "Sturmhöhe" }) { title __typename } @@ -385,17 +359,14 @@ describe('graphql - update mutations', () => { }) test('update entry with meta field __typename on all nesting levels', async () => { - const query = `#graphql + const query = gql` mutation { __typename AdminService { __typename Books { __typename - update( - filter: { title: { eq: "Wuthering Heights" } }, - input: { title: "Sturmhöhe" } - ) { + update(filter: { title: { eq: "Wuthering Heights" } }, input: { title: "Sturmhöhe" }) { __typename title } diff --git a/test/tests/queries/aliases.test.js b/test/tests/queries/aliases.test.js index cedf9b12..2945d34c 100644 --- a/test/tests/queries/aliases.test.js +++ b/test/tests/queries/aliases.test.js @@ -1,6 +1,7 @@ describe('graphql - aliases', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -9,7 +10,7 @@ describe('graphql - aliases', () => { // REVISIT: unskip for support of configurable schema flavors describe.skip('queries with aliases without connections', () => { test('query with alias on service', async () => { - const query = `#graphql + const query = gql` { myAlias: AdminServiceBasic { Books { @@ -34,7 +35,7 @@ describe('graphql - aliases', () => { }) test('query with alias on entity', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { myAlias: Books { @@ -59,7 +60,7 @@ describe('graphql - aliases', () => { }) test('query with alias on element', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books { @@ -84,7 +85,7 @@ describe('graphql - aliases', () => { }) test('query with alias on association', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books { @@ -111,7 +112,7 @@ describe('graphql - aliases', () => { }) test('query with alias on element of association', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books { @@ -138,7 +139,7 @@ describe('graphql - aliases', () => { }) test('query with aliases on all fields', async () => { - const query = `#graphql + const query = gql` { myAliasA: AdminServiceBasic { myAliasB: Books { @@ -165,7 +166,7 @@ describe('graphql - aliases', () => { }) test('query with aliases on entity selecting different fields', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { myAliasA: Books { @@ -195,7 +196,7 @@ describe('graphql - aliases', () => { // REVISIT: aliases with expand currently not supported in cds test.skip('query with aliases on to-many element selecting different fields', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Authors { @@ -245,7 +246,7 @@ describe('graphql - aliases', () => { // REVISIT: aliases with expand currently not supported in cds test.skip('query with aliases on to-many element with different filters selecting different fields', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Authors { @@ -286,7 +287,7 @@ describe('graphql - aliases', () => { }) test('query with aliases on service allowing two different filters on the same field', async () => { - const query = `#graphql + const query = gql` { myAliasA: AdminServiceBasic { Books(filter: { ID: { eq: 201 } }) { @@ -315,7 +316,7 @@ describe('graphql - aliases', () => { }) test('query with aliases on entity allowing two different filters on the same field', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { myAliasA: Books(filter: { ID: { eq: 201 } }) { @@ -344,7 +345,7 @@ describe('graphql - aliases', () => { describe('queries with aliases with connections', () => { test('query with alias on service', async () => { - const query = `#graphql + const query = gql` { myAlias: AdminService { Books { @@ -373,7 +374,7 @@ describe('graphql - aliases', () => { }) test('query with alias on entity', async () => { - const query = `#graphql + const query = gql` { AdminService { myAlias: Books { @@ -402,7 +403,7 @@ describe('graphql - aliases', () => { }) test('query with alias on nodes of entity', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -431,7 +432,7 @@ describe('graphql - aliases', () => { }) test('query with alias on element', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -460,7 +461,7 @@ describe('graphql - aliases', () => { }) test('query with alias on association', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -491,7 +492,7 @@ describe('graphql - aliases', () => { }) test('query with alias on element of association', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -522,7 +523,7 @@ describe('graphql - aliases', () => { }) test('query with aliases on all fields', async () => { - const query = `#graphql + const query = gql` { myAliasA: AdminService { myAliasB: Books { @@ -553,7 +554,7 @@ describe('graphql - aliases', () => { }) test('query with aliases on entity selecting different fields', async () => { - const query = `#graphql + const query = gql` { AdminService { myAliasA: Books { @@ -589,7 +590,7 @@ describe('graphql - aliases', () => { // REVISIT: aliases with expand currently not supported in cds test.skip('query with aliases on to-many element selecting different fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors { @@ -647,7 +648,7 @@ describe('graphql - aliases', () => { // REVISIT: aliases with expand currently not supported in cds test.skip('query with aliases on to-many element with different filters selecting different fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors { @@ -694,7 +695,7 @@ describe('graphql - aliases', () => { }) test('query with aliases on service allowing two different filters on the same field', async () => { - const query = `#graphql + const query = gql` { myAliasA: AdminService { Books(filter: { ID: { eq: 201 } }) { @@ -727,7 +728,7 @@ describe('graphql - aliases', () => { }) test('query with aliases on entity allowing two different filters on the same field', async () => { - const query = `#graphql + const query = gql` { AdminService { myAliasA: Books(filter: { ID: { eq: 201 } }) { @@ -758,12 +759,12 @@ describe('graphql - aliases', () => { }) test('query with aliases on nodes returning the same result list twice', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { myAliasA: nodes { - title + title } myAliasB: nodes { title @@ -797,7 +798,7 @@ describe('graphql - aliases', () => { }) test('query with aliases on nodes returning different fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { diff --git a/test/tests/queries/filter.test.js b/test/tests/queries/filter.test.js index a1cd4a6a..5d3301b1 100644 --- a/test/tests/queries/filter.test.js +++ b/test/tests/queries/filter.test.js @@ -1,6 +1,7 @@ describe('graphql - filter', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -9,7 +10,7 @@ describe('graphql - filter', () => { // REVISIT: unskip for support of configurable schema flavors describe.skip('queries with filter argument without connections', () => { test('query with simple filter', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books(filter: { ID: { eq: 201 } }) { @@ -29,7 +30,7 @@ describe('graphql - filter', () => { }) test('query with simple filter wrapped as lists', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books(filter: [{ ID: [{ eq: 201 }] }]) { @@ -49,10 +50,10 @@ describe('graphql - filter', () => { }) test('query with filter joined by AND on the same field', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { - Books(filter: { ID: { gt: 250, lt:260 } }) { + Books(filter: { ID: { gt: 250, lt: 260 } }) { ID title } @@ -72,7 +73,7 @@ describe('graphql - filter', () => { }) test('query with filter joined by AND on different fields', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books(filter: { ID: { eq: 251 }, title: { eq: "The Raven" } }) { @@ -92,7 +93,7 @@ describe('graphql - filter', () => { }) test('query with filter joined by OR on the same field', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books(filter: { ID: [{ eq: 201 }, { eq: 251 }] }) { @@ -115,7 +116,7 @@ describe('graphql - filter', () => { }) test('query with filter joined by OR on different fields', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books(filter: [{ ID: { eq: 201 } }, { title: { eq: "The Raven" } }]) { @@ -138,26 +139,18 @@ describe('graphql - filter', () => { }) test('query with complex filter', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { - Books(filter: [ - { - title: [ - { startswith: "the", endswith: "raven" }, - { contains: "height" } - ] - ID: [ - { eq: 201 }, - { eq: 251 } - ] - } - { - title: { - contains: "cat" + Books( + filter: [ + { + title: [{ startswith: "the", endswith: "raven" }, { contains: "height" }] + ID: [{ eq: 201 }, { eq: 251 }] } - } - ]) { + { title: { contains: "cat" } } + ] + ) { ID title } @@ -178,13 +171,13 @@ describe('graphql - filter', () => { }) test('query with filters on nested fields', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Authors(filter: { ID: { gt: 110 } }) { ID name - books(filter: { ID: { lt: 260 } }) { + books(filter: { ID: { lt: 260 } }) { ID title } @@ -214,7 +207,7 @@ describe('graphql - filter', () => { describe('queries with filter argument with connections', () => { test('query with simple filter', async () => { - const query = `#graphql + const query = gql` { AdminService { Books(filter: { ID: { eq: 201 } }) { @@ -236,7 +229,7 @@ describe('graphql - filter', () => { }) test('query with simple filter wrapped as lists', async () => { - const query = `#graphql + const query = gql` { AdminService { Books(filter: [{ ID: [{ eq: 201 }] }]) { @@ -258,10 +251,10 @@ describe('graphql - filter', () => { }) test('query with filter joined by AND on the same field', async () => { - const query = `#graphql + const query = gql` { AdminService { - Books(filter: { ID: { gt: 250, lt:260 } }) { + Books(filter: { ID: { gt: 250, lt: 260 } }) { nodes { ID title @@ -285,7 +278,7 @@ describe('graphql - filter', () => { }) test('query with filter joined by AND on different fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Books(filter: { ID: { eq: 251 }, title: { eq: "The Raven" } }) { @@ -307,7 +300,7 @@ describe('graphql - filter', () => { }) test('query with filter joined by OR on the same field', async () => { - const query = `#graphql + const query = gql` { AdminService { Books(filter: { ID: [{ eq: 201 }, { eq: 251 }] }) { @@ -334,7 +327,7 @@ describe('graphql - filter', () => { }) test('query with filter joined by OR on different fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Books(filter: [{ ID: { eq: 201 } }, { title: { eq: "The Raven" } }]) { @@ -361,26 +354,18 @@ describe('graphql - filter', () => { }) test('query with complex filter', async () => { - const query = `#graphql + const query = gql` { AdminService { - Books(filter: [ - { - title: [ - { startswith: "the", endswith: "raven" }, - { contains: "height" } - ] - ID: [ - { eq: 201 }, - { eq: 251 } - ] - } - { - title: { - contains: "cat" + Books( + filter: [ + { + title: [{ startswith: "the", endswith: "raven" }, { contains: "height" }] + ID: [{ eq: 201 }, { eq: 251 }] } - } - ]) { + { title: { contains: "cat" } } + ] + ) { nodes { ID title @@ -405,14 +390,14 @@ describe('graphql - filter', () => { }) test('query with filters on nested fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors(filter: { ID: { gt: 110 } }) { nodes { ID name - books(filter: { ID: { lt: 260 } }) { + books(filter: { ID: { lt: 260 } }) { nodes { ID title diff --git a/test/tests/queries/fragments.test.js b/test/tests/queries/fragments.test.js index ba98db1b..df2ed667 100644 --- a/test/tests/queries/fragments.test.js +++ b/test/tests/queries/fragments.test.js @@ -1,6 +1,7 @@ describe('graphql - fragments', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -9,14 +10,14 @@ describe('graphql - fragments', () => { // REVISIT: unskip for support of configurable schema flavors describe.skip('queries with fragments without connections', () => { test('query with fragment on root query', async () => { - const query = `#graphql + const query = gql` query { ...myFragment } fragment myFragment on Query { AdminServiceBasic { - Books{ + Books { title } } @@ -38,7 +39,7 @@ describe('graphql - fragments', () => { }) test('query with fragment on service', async () => { - const query = `#graphql + const query = gql` query { AdminServiceBasic { ...myFragment @@ -67,7 +68,7 @@ describe('graphql - fragments', () => { }) test('query with fragment on entity', async () => { - const query = `#graphql + const query = gql` query { AdminServiceBasic { Books { @@ -96,7 +97,7 @@ describe('graphql - fragments', () => { }) test('query with nested fragments', async () => { - const query = `#graphql + const query = gql` query { ...myFragmentA } @@ -133,10 +134,10 @@ describe('graphql - fragments', () => { }) test('query with fragments along with other fields', async () => { - const query = `#graphql + const query = gql` query { AdminServiceBasic { - Books{ + Books { ...myFragmentA title ...myFragmentB @@ -172,7 +173,7 @@ describe('graphql - fragments', () => { describe('queries with fragments with connections', () => { test('query with fragment on root query', async () => { - const query = `#graphql + const query = gql` query { ...myFragment } @@ -205,7 +206,7 @@ describe('graphql - fragments', () => { }) test('query with fragment on service', async () => { - const query = `#graphql + const query = gql` query { AdminService { ...myFragment @@ -238,7 +239,7 @@ describe('graphql - fragments', () => { }) test('query with fragment on entity', async () => { - const query = `#graphql + const query = gql` query { AdminService { Books { @@ -271,7 +272,7 @@ describe('graphql - fragments', () => { }) test('query with fragment on nodes of entity', async () => { - const query = `#graphql + const query = gql` query { AdminService { Books { @@ -304,7 +305,7 @@ describe('graphql - fragments', () => { }) test('query with nested fragments', async () => { - const query = `#graphql + const query = gql` query { ...myFragmentA } @@ -349,7 +350,7 @@ describe('graphql - fragments', () => { }) test('query with fragments along with other fields', async () => { - const query = `#graphql + const query = gql` query { AdminService { Books { diff --git a/test/tests/queries/meta.test.js b/test/tests/queries/meta.test.js index 57f8d5e2..02c1a00b 100644 --- a/test/tests/queries/meta.test.js +++ b/test/tests/queries/meta.test.js @@ -1,6 +1,7 @@ describe('graphql - meta fields', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -9,7 +10,7 @@ describe('graphql - meta fields', () => { // REVISIT: unskip for support of configurable schema flavors describe.skip('queries with __typename meta field without connections', () => { test('query with __typename meta field on root query', async () => { - const query = `#graphql + const query = gql` { __typename } @@ -20,7 +21,7 @@ describe('graphql - meta fields', () => { }) test('query with __typename meta field on service', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { __typename @@ -37,7 +38,7 @@ describe('graphql - meta fields', () => { }) test('query with __typename meta field on entity', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books { @@ -62,7 +63,7 @@ describe('graphql - meta fields', () => { }) test('query with __typename meta field on association of entity', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books { @@ -89,7 +90,7 @@ describe('graphql - meta fields', () => { }) test('query with __typename meta field on all nesting levels', async () => { - const query = `#graphql + const query = gql` { __typename AdminServiceBasic { @@ -139,7 +140,7 @@ describe('graphql - meta fields', () => { describe('queries with __typename meta field with connections', () => { test('query with __typename meta field on root query', async () => { - const query = `#graphql + const query = gql` { __typename } @@ -150,7 +151,7 @@ describe('graphql - meta fields', () => { }) test('query with __typename meta field on service', async () => { - const query = `#graphql + const query = gql` { AdminService { __typename @@ -167,7 +168,7 @@ describe('graphql - meta fields', () => { }) test('query with __typename meta field on entity', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -186,7 +187,7 @@ describe('graphql - meta fields', () => { }) test('query with __typename meta field on entity nodes', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -215,7 +216,7 @@ describe('graphql - meta fields', () => { }) test('query with __typename meta field on association of entity', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -246,7 +247,7 @@ describe('graphql - meta fields', () => { }) test('query with __typename meta field on all nesting levels', async () => { - const query = `#graphql + const query = gql` { __typename AdminService { diff --git a/test/tests/queries/orderBy.test.js b/test/tests/queries/orderBy.test.js index 59fa24de..7c6e49f4 100644 --- a/test/tests/queries/orderBy.test.js +++ b/test/tests/queries/orderBy.test.js @@ -1,6 +1,7 @@ describe('graphql - orderBy', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -9,7 +10,7 @@ describe('graphql - orderBy', () => { // REVISIT: unskip for support of configurable schema flavors describe.skip('queries with orderBy argument without connections', () => { test('query with single orderBy object on field', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books(orderBy: { ID: desc }) { @@ -37,7 +38,7 @@ describe('graphql - orderBy', () => { test('query with list of orderBy object on field', async () => { // Use createdAt as first sort criteria to test second level sort criteria, // since they all have the same values due to being created at the same time - const query = `#graphql + const query = gql` { AdminServiceBasic { Books(orderBy: [{ createdAt: desc }, { ID: desc }]) { @@ -63,7 +64,7 @@ describe('graphql - orderBy', () => { }) test('query with orderBy objects on nested fields', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Authors(orderBy: { ID: desc }) { @@ -92,7 +93,7 @@ describe('graphql - orderBy', () => { describe('queries with orderBy argument with connections', () => { test('query with single orderBy object on field', async () => { - const query = `#graphql + const query = gql` { AdminService { Books(orderBy: { ID: desc }) { @@ -124,7 +125,7 @@ describe('graphql - orderBy', () => { test('query with list of orderBy object on field', async () => { // Use createdAt as first sort criteria to test second level sort criteria, // since they all have the same values due to being created at the same time - const query = `#graphql + const query = gql` { AdminService { Books(orderBy: [{ createdAt: desc }, { ID: desc }]) { @@ -154,7 +155,7 @@ describe('graphql - orderBy', () => { }) test('query with orderBy objects on nested fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors(orderBy: { ID: desc }) { diff --git a/test/tests/queries/paging-offset.test.js b/test/tests/queries/paging-offset.test.js index 39e1aa6b..caf4168b 100644 --- a/test/tests/queries/paging-offset.test.js +++ b/test/tests/queries/paging-offset.test.js @@ -1,6 +1,7 @@ describe('graphql - offset-based paging', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -9,7 +10,7 @@ describe('graphql - offset-based paging', () => { // REVISIT: unskip for support of configurable schema flavors describe.skip('queries with paging arguments without connections', () => { test('query with top argument on field', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Authors(top: 2) { @@ -28,7 +29,7 @@ describe('graphql - offset-based paging', () => { }) test('query with top and skip arguments on field', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Authors(top: 2, skip: 2) { @@ -47,7 +48,7 @@ describe('graphql - offset-based paging', () => { }) test('query with top and skip arguments on nested fields', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Authors(top: 2, skip: 2) { @@ -78,7 +79,7 @@ describe('graphql - offset-based paging', () => { describe('queries with paging arguments with connections', () => { test('query with top argument on field', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors(top: 2) { @@ -99,7 +100,7 @@ describe('graphql - offset-based paging', () => { }) test('query with top and skip arguments on field', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors(top: 2, skip: 2) { @@ -120,7 +121,7 @@ describe('graphql - offset-based paging', () => { }) test('query with top and skip arguments on nested fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors(top: 2, skip: 2) { diff --git a/test/tests/queries/queries.test.js b/test/tests/queries/queries.test.js index e42c1f24..59bd2bc6 100644 --- a/test/tests/queries/queries.test.js +++ b/test/tests/queries/queries.test.js @@ -1,6 +1,7 @@ describe('graphql - queries', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST, data } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -10,7 +11,7 @@ describe('graphql - queries', () => { // REVISIT: unskip for support of configurable schema flavors describe.skip('queries without arguments without connections', () => { test('simple query', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books { @@ -35,9 +36,9 @@ describe('graphql - queries', () => { }) test('query with null result values', async () => { - await INSERT.into('sap.capire.bookshop.Books').entries({ title: "Moby-Dick" }) + await INSERT.into('sap.capire.bookshop.Books').entries({ title: 'Moby-Dick' }) - const query = `#graphql + const query = gql` { AdminServiceBasic { Books { @@ -64,7 +65,7 @@ describe('graphql - queries', () => { }) test('nested query containing to-one association', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books { @@ -92,7 +93,7 @@ describe('graphql - queries', () => { }) test('nested query containing to-many association', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Authors { @@ -119,7 +120,7 @@ describe('graphql - queries', () => { }) test('complex query', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Books { @@ -181,7 +182,7 @@ describe('graphql - queries', () => { }) test('recursive query', async () => { - const query = `#graphql + const query = gql` { AdminServiceBasic { Authors { @@ -242,7 +243,7 @@ describe('graphql - queries', () => { describe('queries without arguments with connections', () => { test('simple query', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -271,9 +272,9 @@ describe('graphql - queries', () => { }) test('query with null result values', async () => { - await INSERT.into('sap.capire.bookshop.Books').entries({ title: "Moby-Dick" }) + await INSERT.into('sap.capire.bookshop.Books').entries({ title: 'Moby-Dick' }) - const query = `#graphql + const query = gql` { AdminService { Books { @@ -304,7 +305,7 @@ describe('graphql - queries', () => { }) test('nested query containing to-one association', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -336,7 +337,7 @@ describe('graphql - queries', () => { }) test('nested query containing to-many association', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors { @@ -369,7 +370,7 @@ describe('graphql - queries', () => { }) test('complex query', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -443,7 +444,7 @@ describe('graphql - queries', () => { }) test('recursive query', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors { diff --git a/test/tests/queries/totalCount.test.js b/test/tests/queries/totalCount.test.js index acb6df73..9e083c2e 100644 --- a/test/tests/queries/totalCount.test.js +++ b/test/tests/queries/totalCount.test.js @@ -1,13 +1,14 @@ describe('graphql - queries with totalCount', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes axios.defaults.validateStatus = false test('simple query with totalCount', async () => { - const query = `#graphql + const query = gql` { AdminService { Books { @@ -38,7 +39,7 @@ describe('graphql - queries with totalCount', () => { }) test('query with totalCount and simple filter', async () => { - const query = `#graphql + const query = gql` { AdminService { Books(filter: { ID: { eq: 201 } }) { @@ -61,7 +62,7 @@ describe('graphql - queries with totalCount', () => { }) test('query with totalCount on nested fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors { @@ -96,7 +97,7 @@ describe('graphql - queries with totalCount', () => { }) test('query with totalCount and top and skip', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors(top: 2, skip: 2) { @@ -118,7 +119,7 @@ describe('graphql - queries with totalCount', () => { }) test('query with totalCount and top and skip arguments on nested fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors(top: 2, skip: 2) { @@ -153,7 +154,7 @@ describe('graphql - queries with totalCount', () => { }) test('query with aliases on totalCount on nested fields', async () => { - const query = `#graphql + const query = gql` { AdminService { Authors { diff --git a/test/tests/queries/variables.test.js b/test/tests/queries/variables.test.js index 5cd2d058..f44b19a8 100644 --- a/test/tests/queries/variables.test.js +++ b/test/tests/queries/variables.test.js @@ -1,6 +1,7 @@ describe('graphql - variables', () => { const cds = require('@sap/cds/lib') const path = require('path') + const { gql } = require('../../util') const { axios, POST } = cds.test(path.join(__dirname, '../../resources/bookshop-graphql')) // Prevent axios from throwing errors for non 2xx status codes @@ -9,7 +10,7 @@ describe('graphql - variables', () => { // REVISIT: unskip for support of configurable schema flavors describe.skip('queries with variables without connections', () => { test('query variable of type input object passed as argument without connections', async () => { - const query = `#graphql + const query = gql` query ($filter: [AdminServiceBasic_Books_filter]) { AdminServiceBasic { Books(filter: $filter) { @@ -34,7 +35,7 @@ describe('graphql - variables', () => { }) test('query variable of type input object passed as argument wrapped in a list', async () => { - const query = `#graphql + const query = gql` query ($filter: AdminServiceBasic_Books_filter) { AdminServiceBasic { Books(filter: [$filter]) { @@ -59,7 +60,7 @@ describe('graphql - variables', () => { }) test('query variable of type input object passed as a field of an argument', async () => { - const query = `#graphql + const query = gql` query ($filter: [Int_filter]) { AdminServiceBasic { Books(filter: { ID: $filter }) { @@ -84,7 +85,7 @@ describe('graphql - variables', () => { }) test('query variable of type input object passed as a field of an argument wrapped in a list', async () => { - const query = `#graphql + const query = gql` query ($filter: Int_filter) { AdminServiceBasic { Books(filter: { ID: [$filter] }) { @@ -109,7 +110,7 @@ describe('graphql - variables', () => { }) test('query variable of type scalar value passed as a field of an argument', async () => { - const query = `#graphql + const query = gql` query ($filter: Int) { AdminServiceBasic { Books(filter: { ID: { ge: $filter } }) { @@ -136,7 +137,7 @@ describe('graphql - variables', () => { describe('queries with variables with connections', () => { test('query variable of type input object passed as argument', async () => { - const query = `#graphql + const query = gql` query ($filter: [AdminService_Books_filter]) { AdminService { Books(filter: $filter) { @@ -165,7 +166,7 @@ describe('graphql - variables', () => { }) test('query variable of type input object passed as argument wrapped in a list', async () => { - const query = `#graphql + const query = gql` query ($filter: AdminService_Books_filter) { AdminService { Books(filter: [$filter]) { @@ -194,7 +195,7 @@ describe('graphql - variables', () => { }) test('query variable of type input object passed as a field of an argument', async () => { - const query = `#graphql + const query = gql` query ($filter: [Int_filter]) { AdminService { Books(filter: { ID: $filter }) { @@ -223,7 +224,7 @@ describe('graphql - variables', () => { }) test('query variable of type input object passed as a field of an argument wrapped in a list', async () => { - const query = `#graphql + const query = gql` query ($filter: Int_filter) { AdminService { Books(filter: { ID: [$filter] }) { @@ -252,7 +253,7 @@ describe('graphql - variables', () => { }) test('query variable of type scalar value passed as a field of an argument', async () => { - const query = `#graphql + const query = gql` query ($filter: Int) { AdminService { Books(filter: { ID: { ge: $filter } }) { diff --git a/test/tests/types.test.js b/test/tests/types.test.js index 0bedbf08..15d819f2 100644 --- a/test/tests/types.test.js +++ b/test/tests/types.test.js @@ -1,3 +1,5 @@ +const { gql } = require('../util') + const _toBase64Url = value => value.replace(/\//g, '_').replace(/\+/g, '-') const _getTestBuffer = repetitions => { @@ -8,7 +10,7 @@ const _getTestBuffer = repetitions => { } const _getMutationForFieldWithLiteralValue = (field, value, quoted) => - `#graphql + gql` mutation { TypesService { MyEntity { @@ -21,7 +23,7 @@ const _getMutationForFieldWithLiteralValue = (field, value, quoted) => ` const _getMutationAndVariablesForFieldWithVariable = (field, value) => ({ - query: `#graphql + query: gql` mutation ($input: [TypesService_MyEntity_C]!) { TypesService { MyEntity { diff --git a/test/util/index.js b/test/util/index.js index d24079d9..f9c46bc5 100644 --- a/test/util/index.js +++ b/test/util/index.js @@ -1,12 +1,13 @@ -const cds = require('@sap/cds/lib') const path = require('path') const prettier = require('prettier') -const { generateSchema4 } = require('../../lib/schema') const { buildSchema, lexicographicSortSchema, printSchema, Kind } = require('graphql') const SCHEMAS_DIR = path.join(__dirname, '../schemas') const cdsFilesToGQLSchema = async files => { + const cds = require('@sap/cds/lib') + const { generateSchema4 } = require('../../lib/schema') + const m = cds.linked(await cds.load(files)) const services = Object.fromEntries(m.services.map(s => [s.name, new cds.ApplicationService(s.name, m)])) return generateSchema4(services) @@ -38,4 +39,11 @@ const fakeInfoObject = (document, schema, parentTypeName, variables) => { } } -module.exports = { SCHEMAS_DIR, cdsFilesToGQLSchema, formatSchema, fakeInfoObject } +/** + * Dummy template literal tag function that returns the raw string that was passed to it. + * Mocks the gql tag provided by the graphql-tag module and the graphql tag provided by the react-relay module. + * Usage of this tag allows IDEs and prettier to detect GraphQL query strings and provide syntax highlighting and code formatting. + */ +const gql = String.raw + +module.exports = { SCHEMAS_DIR, cdsFilesToGQLSchema, formatSchema, fakeInfoObject, gql } From b83825ed8551014d418eb190cbae6dae1bc2f602 Mon Sep 17 00:00:00 2001 From: Lars Lutz <56645452+larslutz96@users.noreply.github.com> Date: Mon, 23 Jan 2023 11:48:28 +0100 Subject: [PATCH 11/15] feat: replace `like` with CQN function calls (#36) * replace like with contains func * formating * Cleanup and use CQN functions for startswith and endsWith * Rm helper function * Add changelog entry * Improve wording Co-authored-by: Marcel Schwarz --- CHANGELOG.md | 1 + lib/resolvers/parse/ast2cqn/where.js | 25 +++++++++---------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffbcf3c8..58924f13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Register `aliasFieldResolver` during schema generation instead of passing it to the GraphQL server +- The filters `contains`, `startswith`, and `endswith` now generate CQN function calls instead of generating `like` expressions directly ### Fixed diff --git a/lib/resolvers/parse/ast2cqn/where.js b/lib/resolvers/parse/ast2cqn/where.js index ab2699b6..1b47085c 100644 --- a/lib/resolvers/parse/ast2cqn/where.js +++ b/lib/resolvers/parse/ast2cqn/where.js @@ -2,9 +2,9 @@ const { RELATIONAL_OPERATORS, STRING_OPERATIONS } = require('../../../constants' const { Kind } = require('graphql') const GQL_TO_CDS_STRING_OPERATIONS = { - [STRING_OPERATIONS.startswith]: 'like', - [STRING_OPERATIONS.endswith]: 'like', - [STRING_OPERATIONS.contains]: 'like' + [STRING_OPERATIONS.startswith]: 'startswith', + [STRING_OPERATIONS.endswith]: 'endswith', + [STRING_OPERATIONS.contains]: 'contains' } const GQL_TO_CDS_QL_OPERATOR = { @@ -16,26 +16,19 @@ const GQL_TO_CDS_QL_OPERATOR = { [RELATIONAL_OPERATORS.lt]: '<' } -const _stringOperationToLikeString = (operator, string) => - ({ - [STRING_OPERATIONS.startswith]: `${string}%`, - [STRING_OPERATIONS.endswith]: `%${string}`, - [STRING_OPERATIONS.contains]: `%${string}%` - }[operator]) - -const _gqlValueToCdsValue = (cdsOperator, gqlOperator, gqlValue) => - cdsOperator === 'like' ? _stringOperationToLikeString(gqlOperator, gqlValue) : gqlValue - const _gqlOperatorToCdsOperator = gqlOperator => GQL_TO_CDS_QL_OPERATOR[gqlOperator] || GQL_TO_CDS_STRING_OPERATIONS[gqlOperator] const _objectFieldTo_xpr = (objectField, columnName) => { const gqlOperator = objectField.name.value const cdsOperator = _gqlOperatorToCdsOperator(gqlOperator) - const gqlValue = objectField.value.value - const cdsValue = _gqlValueToCdsValue(cdsOperator, gqlOperator, gqlValue) - return [{ ref: [columnName] }, cdsOperator, { val: cdsValue }] + const ref = { ref: [columnName] } + const val = { val: objectField.value.value } + + if (STRING_OPERATIONS[gqlOperator]) return [{ func: cdsOperator, args: [ref, val] }] + + return [ref, cdsOperator, val] } const _parseObjectField = (objectField, columnName) => { From f66a4b8dce71256c2bfe57327ba91e8f99002252 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Tue, 24 Jan 2023 09:55:43 +0100 Subject: [PATCH 12/15] fix: query `totalCount` on its own (#38) * Add test querying only totalCount * Check if is connection from selected field names * Add changelog entry * Use gql tag in test * Use ternary conditional operator --- CHANGELOG.md | 1 + lib/resolvers/parse/util/index.js | 5 ++--- test/tests/queries/totalCount.test.js | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58924f13..385a7756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed a server crash that occourred if an entity property is named `localized`. +- A bug where the field `totalCount` could not be queried on its own ### Removed diff --git a/lib/resolvers/parse/util/index.js b/lib/resolvers/parse/util/index.js index 645e78ad..878676f1 100644 --- a/lib/resolvers/parse/util/index.js +++ b/lib/resolvers/parse/util/index.js @@ -16,9 +16,8 @@ const _filterOutDuplicateColumnsSelections = selections => { } const getPotentiallyNestedNodesSelections = selections => { - const nodesSelections = selections.filter(selection => selection.name.value === CONNECTION_FIELDS.nodes) - if (nodesSelections.length === 0) return selections - return _filterOutDuplicateColumnsSelections(selections) + const isConnection = selections.some(selection => Object.values(CONNECTION_FIELDS).includes(selection.name.value)) + return isConnection ? _filterOutDuplicateColumnsSelections(selections) : selections } module.exports = { getPotentiallyNestedNodesSelections } diff --git a/test/tests/queries/totalCount.test.js b/test/tests/queries/totalCount.test.js index 9e083c2e..dc4fd946 100644 --- a/test/tests/queries/totalCount.test.js +++ b/test/tests/queries/totalCount.test.js @@ -38,6 +38,27 @@ describe('graphql - queries with totalCount', () => { expect(response.data).toEqual({ data }) }) + test('query selecting only totalCount', async () => { + const query = gql` + { + AdminService { + Books { + totalCount + } + } + } + ` + const data = { + AdminService: { + Books: { + totalCount: 5 + } + } + } + const response = await POST('/graphql', { query }) + expect(response.data).toEqual({ data }) + }) + test('query with totalCount and simple filter', async () => { const query = gql` { From 5d4681f05d009cdf245cbc83a54945c0d5c2075f Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Tue, 24 Jan 2023 09:58:13 +0100 Subject: [PATCH 13/15] chore: miscellaneous cleanup (#37) * Fix folder name util.js -> util * Fix typo * Fix comment * Simplify repository in package.json * Move jest configuration out of package.json * Remove "Release" prefix from release title * Remove unneeded else * Remove managed from localized test * Rm space --- .github/workflows/release.yml | 3 +- CHANGELOG.md | 4 +- jest.config.js | 5 ++ lib/resolvers/parse/ast/result.js | 3 +- lib/schema/types/custom/GraphQLBinary.js | 2 +- lib/schema/types/custom/GraphQLDate.js | 2 +- lib/schema/types/custom/GraphQLDateTime.js | 2 +- lib/schema/types/custom/GraphQLDecimal.js | 2 +- lib/schema/types/custom/GraphQLInt16.js | 2 +- lib/schema/types/custom/GraphQLInt64.js | 2 +- lib/schema/types/custom/GraphQLTime.js | 2 +- lib/schema/types/custom/GraphQLTimestamp.js | 2 +- lib/schema/types/custom/GraphQLUInt8.js | 4 +- .../types/custom/{util.js => util}/index.js | 0 package.json | 10 +--- .../edge-cases/srv/field-named-localized.cds | 4 +- .../edge-cases/field-named-localized.gql | 54 ------------------- 17 files changed, 23 insertions(+), 80 deletions(-) create mode 100644 jest.config.js rename lib/schema/types/custom/{util.js => util}/index.js (100%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03ef8406..672678f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,8 +26,7 @@ jobs: - name: Create a GitHub release uses: ncipollo/release-action@v1 with: - tag: "v${{ steps.package-version.outputs.current-version}}" - name: "Release v${{ steps.package-version.outputs.current-version}}" + tag: "v${{ steps.package-version.outputs.current-version }}" # body: changelog... - run: npm publish --access public env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 385a7756..992a4f2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Register `aliasFieldResolver` during schema generation instead of passing it to the GraphQL server - The filters `contains`, `startswith`, and `endswith` now generate CQN function calls instead of generating `like` expressions directly -### Fixed +### Fixed -- Fixed a server crash that occourred if an entity property is named `localized`. +- Fixed a server crash that occurred if an entity property is named `localized` - A bug where the field `totalCount` could not be queried on its own ### Removed diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..4c4b200f --- /dev/null +++ b/jest.config.js @@ -0,0 +1,5 @@ +const config = { + testTimeout: 10000 +} + +module.exports = config diff --git a/lib/resolvers/parse/ast/result.js b/lib/resolvers/parse/ast/result.js index 7819b85c..c134e09c 100644 --- a/lib/resolvers/parse/ast/result.js +++ b/lib/resolvers/parse/ast/result.js @@ -46,9 +46,8 @@ const formatResult = (field, result, skipTopLevelConnection) => { return _formatArray(field, value, skipTopLevelConnection) } else if (isPlainObject(value)) { return _formatObject(field.selectionSet.selections, value) - } else { - return value } + return value } return _formatByType(field, result, skipTopLevelConnection) diff --git a/lib/schema/types/custom/GraphQLBinary.js b/lib/schema/types/custom/GraphQLBinary.js index cd45b5e9..dbf87f15 100644 --- a/lib/schema/types/custom/GraphQLBinary.js +++ b/lib/schema/types/custom/GraphQLBinary.js @@ -1,5 +1,5 @@ const { GraphQLScalarType, Kind, GraphQLError } = require('graphql') -const { getGraphQLValueError } = require('./util.js') +const { getGraphQLValueError } = require('./util') const ERROR_NON_STRING_VALUE = 'Binary cannot represent non string value' const ERROR_NON_BASE64_OR_BASE64URL = 'Binary values must be base64 or base64url encoded and normalized strings' diff --git a/lib/schema/types/custom/GraphQLDate.js b/lib/schema/types/custom/GraphQLDate.js index 7f7492d2..b72bec88 100644 --- a/lib/schema/types/custom/GraphQLDate.js +++ b/lib/schema/types/custom/GraphQLDate.js @@ -1,5 +1,5 @@ const { GraphQLScalarType, Kind } = require('graphql') -const { getGraphQLValueError, parseDate } = require('./util.js') +const { getGraphQLValueError, parseDate } = require('./util') const ERROR_NON_STRING_VALUE = 'Date cannot represent non string value' const ERROR_NON_DATE_VALUE = 'Date values must be strings in the ISO 8601 format YYYY-MM-DD' diff --git a/lib/schema/types/custom/GraphQLDateTime.js b/lib/schema/types/custom/GraphQLDateTime.js index 1ceac1fe..601a00cd 100644 --- a/lib/schema/types/custom/GraphQLDateTime.js +++ b/lib/schema/types/custom/GraphQLDateTime.js @@ -1,5 +1,5 @@ const { GraphQLScalarType, Kind } = require('graphql') -const { getGraphQLValueError, parseDate } = require('./util.js') +const { getGraphQLValueError, parseDate } = require('./util') const ERROR_NON_STRING_VALUE = 'DateTime cannot represent non string value' const ERROR_NON_DATE_TIME_VALUE = 'DateTime values must be strings in the ISO 8601 format YYYY-MM-DDThh-mm-ssTZD' diff --git a/lib/schema/types/custom/GraphQLDecimal.js b/lib/schema/types/custom/GraphQLDecimal.js index ffc695fd..8ed02461 100644 --- a/lib/schema/types/custom/GraphQLDecimal.js +++ b/lib/schema/types/custom/GraphQLDecimal.js @@ -1,5 +1,5 @@ const { GraphQLScalarType, Kind } = require('graphql') -const { getValueFromInputValueOrValueNode, getGraphQLValueError } = require('./util.js') +const { getValueFromInputValueOrValueNode, getGraphQLValueError } = require('./util') const ERROR_VARIABLE_NON_STRING_VALUE = 'Decimal variable value must be represented by a string' const ERROR_NON_NUMERIC_VALUE = 'Decimal must be a numeric value' diff --git a/lib/schema/types/custom/GraphQLInt16.js b/lib/schema/types/custom/GraphQLInt16.js index 8409093a..2d76363c 100644 --- a/lib/schema/types/custom/GraphQLInt16.js +++ b/lib/schema/types/custom/GraphQLInt16.js @@ -1,5 +1,5 @@ const { GraphQLScalarType, Kind } = require('graphql') -const { getGraphQLValueError, validateRange } = require('./util.js') +const { getGraphQLValueError, validateRange } = require('./util') const ERROR_NON_INTEGER_VALUE = 'Int16 cannot represent non integer value' const ERROR_NON_16_BIT_INTEGER_VALUE = 'Int16 must be an integer value between -(2^15) and 2^15 - 1' diff --git a/lib/schema/types/custom/GraphQLInt64.js b/lib/schema/types/custom/GraphQLInt64.js index c47a1093..9c23058d 100644 --- a/lib/schema/types/custom/GraphQLInt64.js +++ b/lib/schema/types/custom/GraphQLInt64.js @@ -1,5 +1,5 @@ const { GraphQLScalarType, Kind } = require('graphql') -const { getValueFromInputValueOrValueNode, getGraphQLValueError, validateRange } = require('./util.js') +const { getValueFromInputValueOrValueNode, getGraphQLValueError, validateRange } = require('./util') const ERROR_VARIABLE_NON_STRING_VALUE = 'Int64 variable value must be represented by a string' const ERROR_NON_INTEGER_VALUE = 'Int64 cannot represent non integer value' diff --git a/lib/schema/types/custom/GraphQLTime.js b/lib/schema/types/custom/GraphQLTime.js index 3f8c4fe7..8736b026 100644 --- a/lib/schema/types/custom/GraphQLTime.js +++ b/lib/schema/types/custom/GraphQLTime.js @@ -1,5 +1,5 @@ const { GraphQLScalarType, Kind } = require('graphql') -const { getGraphQLValueError, getValueFromInputValueOrValueNode, ISO_TIME_REGEX } = require('./util.js') +const { getGraphQLValueError, getValueFromInputValueOrValueNode, ISO_TIME_REGEX } = require('./util') const ERROR_NON_STRING_VALUE = 'Time cannot represent non string value' const ERROR_NON_TIME_VALUE = 'Time values must be strings in the ISO 8601 format hh:mm:ss' diff --git a/lib/schema/types/custom/GraphQLTimestamp.js b/lib/schema/types/custom/GraphQLTimestamp.js index e21a1b3a..6a517bdc 100644 --- a/lib/schema/types/custom/GraphQLTimestamp.js +++ b/lib/schema/types/custom/GraphQLTimestamp.js @@ -1,5 +1,5 @@ const { GraphQLScalarType, Kind } = require('graphql') -const { getGraphQLValueError, parseDate } = require('./util.js') +const { getGraphQLValueError, parseDate } = require('./util') const ERROR_NON_STRING_VALUE = 'Timestamp cannot represent non string value' const ERROR_NON_TIMESTAMP_VALUE = diff --git a/lib/schema/types/custom/GraphQLUInt8.js b/lib/schema/types/custom/GraphQLUInt8.js index d6c024f8..0f1e7076 100644 --- a/lib/schema/types/custom/GraphQLUInt8.js +++ b/lib/schema/types/custom/GraphQLUInt8.js @@ -1,11 +1,11 @@ const { GraphQLScalarType, Kind } = require('graphql') -const { getGraphQLValueError, validateRange } = require('./util.js') +const { getGraphQLValueError, validateRange } = require('./util') const ERROR_NON_INTEGER_VALUE = 'UInt8 cannot represent non integer value' const ERROR_NON_8_BIT_UNSIGNED_INTEGER_VALUE = 'UInt8 must be an integer value between 0 and 2^8 - 1' const MAX_UINT8 = 255 // 2^8 - 1 -const MIN_UINT8 = 0 // 2^0 - 1 +const MIN_UINT8 = 0 // 0 const parseValue = inputValue => { if (typeof inputValue !== 'number') throw getGraphQLValueError(ERROR_NON_INTEGER_VALUE, inputValue) diff --git a/lib/schema/types/custom/util.js/index.js b/lib/schema/types/custom/util/index.js similarity index 100% rename from lib/schema/types/custom/util.js/index.js rename to lib/schema/types/custom/util/index.js diff --git a/package.json b/package.json index ddeaaf74..16dad48d 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ ], "author": "SAP SE (https://www.sap.com)", "license": "SEE LICENSE IN LICENSE", + "repository": "cap-js/cds-adapter-graphql", "homepage": "https://cap.cloud.sap/", "main": "index.js", "files": [ @@ -43,12 +44,5 @@ "jest": "^29.3.1", "prettier": "^2.3.0", "sqlite3": "^5.0.2" - }, - "jest": { - "testTimeout": 10000 - }, - "repository": { - "type": "git", - "url": "https://github.com/cap-js/cds-adapter-graphql" } -} \ No newline at end of file +} diff --git a/test/resources/edge-cases/srv/field-named-localized.cds b/test/resources/edge-cases/srv/field-named-localized.cds index 9089726d..2cb150ad 100644 --- a/test/resources/edge-cases/srv/field-named-localized.cds +++ b/test/resources/edge-cases/srv/field-named-localized.cds @@ -1,13 +1,13 @@ using {managed} from '@sap/cds/common'; service FieldNamedLocalizedService { - entity localized : managed { + entity localized { key ID : Integer; root : Association to Root; localized : String; // to test that a property only named 'localized' is not confused with localized keyword } - entity Root : managed { + entity Root { key ID : Integer; // The resulting GraphQL schema should contain a field named // "localized" since it is a user modelled association and not an diff --git a/test/schemas/edge-cases/field-named-localized.gql b/test/schemas/edge-cases/field-named-localized.gql index d24b37b6..03908ca5 100644 --- a/test/schemas/edge-cases/field-named-localized.gql +++ b/test/schemas/edge-cases/field-named-localized.gql @@ -15,34 +15,22 @@ type FieldNamedLocalizedService { type FieldNamedLocalizedService_Root { ID: Int - createdAt: Timestamp - createdBy: String localized( filter: [FieldNamedLocalizedService_localized_filter] orderBy: [FieldNamedLocalizedService_localized_orderBy] skip: Int top: Int ): FieldNamedLocalizedService_localized_connection - modifiedAt: Timestamp - modifiedBy: String } input FieldNamedLocalizedService_Root_C { ID: Int - createdAt: Timestamp - createdBy: String localized: [FieldNamedLocalizedService_localized_C] - modifiedAt: Timestamp - modifiedBy: String } input FieldNamedLocalizedService_Root_U { ID: Int - createdAt: Timestamp - createdBy: String localized: [FieldNamedLocalizedService_localized_U] - modifiedAt: Timestamp - modifiedBy: String } type FieldNamedLocalizedService_Root_connection { @@ -52,10 +40,6 @@ type FieldNamedLocalizedService_Root_connection { input FieldNamedLocalizedService_Root_filter { ID: [Int_filter] - createdAt: [Timestamp_filter] - createdBy: [String_filter] - modifiedAt: [Timestamp_filter] - modifiedBy: [String_filter] } type FieldNamedLocalizedService_Root_input { @@ -73,10 +57,6 @@ type FieldNamedLocalizedService_Root_input { input FieldNamedLocalizedService_Root_orderBy { ID: SortDirection - createdAt: SortDirection - createdBy: SortDirection - modifiedAt: SortDirection - modifiedBy: SortDirection } type FieldNamedLocalizedService_input { @@ -86,33 +66,21 @@ type FieldNamedLocalizedService_input { type FieldNamedLocalizedService_localized { ID: Int - createdAt: Timestamp - createdBy: String localized: String - modifiedAt: Timestamp - modifiedBy: String root: FieldNamedLocalizedService_Root root_ID: Int } input FieldNamedLocalizedService_localized_C { ID: Int - createdAt: Timestamp - createdBy: String localized: String - modifiedAt: Timestamp - modifiedBy: String root: FieldNamedLocalizedService_Root_C root_ID: Int } input FieldNamedLocalizedService_localized_U { ID: Int - createdAt: Timestamp - createdBy: String localized: String - modifiedAt: Timestamp - modifiedBy: String root: FieldNamedLocalizedService_Root_U root_ID: Int } @@ -124,11 +92,7 @@ type FieldNamedLocalizedService_localized_connection { input FieldNamedLocalizedService_localized_filter { ID: [Int_filter] - createdAt: [Timestamp_filter] - createdBy: [String_filter] localized: [String_filter] - modifiedAt: [Timestamp_filter] - modifiedBy: [String_filter] root_ID: [Int_filter] } @@ -147,11 +111,7 @@ type FieldNamedLocalizedService_localized_input { input FieldNamedLocalizedService_localized_orderBy { ID: SortDirection - createdAt: SortDirection - createdBy: SortDirection localized: SortDirection - modifiedAt: SortDirection - modifiedBy: SortDirection root_ID: SortDirection } @@ -188,17 +148,3 @@ input String_filter { ne: String startswith: String } - -""" -The `Timestamp` scalar type represents timestamp values as strings in the ISO 8601 format `YYYY-MM-DDThh-mm-ss.sTZD` with up to 7 digits of fractional seconds. -""" -scalar Timestamp - -input Timestamp_filter { - eq: Timestamp - ge: Timestamp - gt: Timestamp - le: Timestamp - lt: Timestamp - ne: Timestamp -} From 1dc3862cfa8c40c20b6c69bfa68acdc60879d5d4 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Tue, 24 Jan 2023 17:33:09 +0100 Subject: [PATCH 14/15] docs: improve CHANGELOG wording (#39) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 992a4f2a..a6869514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fixed a server crash that occurred if an entity property is named `localized` -- A bug where the field `totalCount` could not be queried on its own +- Schema generation crash that occurred if an entity property is named `localized` +- The field `totalCount` could not be queried on its own ### Removed From 4ed17f8eac7378d1f30d1c7cb728ce7472874a99 Mon Sep 17 00:00:00 2001 From: Johannes Vogel <31311694+johannes-vogel@users.noreply.github.com> Date: Mon, 30 Jan 2023 09:51:50 +0100 Subject: [PATCH 15/15] add changelog and release date (#40) --- CHANGELOG.md | 6 +----- package.json | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6869514..f3ff4a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Version 0.2.0 - tbd - -### Added +## Version 0.2.0 - 2023-01-30 ### Changed @@ -19,8 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Schema generation crash that occurred if an entity property is named `localized` - The field `totalCount` could not be queried on its own -### Removed - ## Version 0.1.0 - 2022-12-08 ### Added diff --git a/package.json b/package.json index 16dad48d..4e70279e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cap-js/graphql", - "version": "0.1.0", + "version": "0.2.0", "description": "CDS protocol adapter for GraphQL", "keywords": [ "CAP",