From 72f024695f4ddc34756076bc564c79812ef08af7 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Tue, 30 Jul 2024 14:30:49 +0200 Subject: [PATCH] release 0.11.0 (#182) Co-authored-by: Johannes Vogel <31311694+johannes-vogel@users.noreply.github.com> Co-authored-by: Daniel Hutzel Co-authored-by: d049904 Co-authored-by: Heiko Witteborg Co-authored-by: Mahati Shankar <93712176+smahati@users.noreply.github.com> Co-authored-by: Samuel Brucksch Co-authored-by: Samuel Brucksch Co-authored-by: D050513 Co-authored-by: sjvans <30337871+sjvans@users.noreply.github.com> Co-authored-by: Mariya Yordanova --- .github/workflows/label-issues.yml | 18 + CHANGELOG.md | 13 + lib/GraphQLAdapter.js | 2 +- lib/compile.js | 27 +- lib/resolvers/GraphQLRequest.js | 2 +- lib/resolvers/crud/create.js | 2 +- lib/resolvers/crud/delete.js | 2 +- lib/resolvers/crud/read.js | 2 +- lib/resolvers/crud/update.js | 2 +- lib/resolvers/parse/ast2cqn/where.js | 4 +- lib/schema/index.js | 19 +- lib/schema/types/custom/GraphQLBinary.js | 32 +- lib/schema/types/custom/GraphQLDate.js | 2 +- package.json | 17 +- test/resources/annotations/srv/protocols.cds | 40 +- .../bookshop-graphql/srv/test-service.js | 2 +- test/resources/bookshop/package.json | 1 + test/resources/custom-handlers/package.json | 4 + test/resources/types/package.json | 13 + test/resources/types/server.js | 6 - test/schemas/annotations.gql | 72 ++ test/tests/annotations.test.js | 34 +- test/tests/concurrency.test.js | 2 +- test/tests/context.test.js | 2 +- test/tests/custom-error-formatter.test.js | 2 +- test/tests/custom-handlers.test.js | 2 +- test/tests/edge-cases.test.js | 2 +- test/tests/error-handling-dev.test.js | 84 +- test/tests/error-handling-prod.test.js | 42 +- test/tests/graphiql.test.js | 2 +- test/tests/localized.test.js | 2 +- test/tests/logger-dev.test.js | 2 +- test/tests/logger-prod.test.js | 2 +- test/tests/mutations/create.test.js | 2 +- test/tests/mutations/delete.test.js | 2 +- test/tests/mutations/update.test.js | 2 +- test/tests/queries/aliases.test.js | 2 +- test/tests/queries/filter.test.js | 2 +- test/tests/queries/fragments.test.js | 2 +- test/tests/queries/meta.test.js | 2 +- test/tests/queries/orderBy.test.js | 2 +- test/tests/queries/paging-offset.test.js | 2 +- test/tests/queries/queries.test.js | 2 +- test/tests/queries/totalCount.test.js | 2 +- test/tests/queries/variables.test.js | 2 +- test/tests/request.test.js | 2 +- test/tests/types.test.js | 910 +++++++++--------- 47 files changed, 717 insertions(+), 679 deletions(-) create mode 100644 .github/workflows/label-issues.yml delete mode 100644 test/resources/types/server.js diff --git a/.github/workflows/label-issues.yml b/.github/workflows/label-issues.yml new file mode 100644 index 00000000..0788392a --- /dev/null +++ b/.github/workflows/label-issues.yml @@ -0,0 +1,18 @@ +name: Label issues +on: + issues: + types: + - reopened + - opened +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - run: gh issue edit "$NUMBER" --add-label "$LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABELS: new \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f7c7603e..f1e80c3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ 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.11.0 - 2024-07-30 + +### Added + +- Support for configuring request payload limit using global flag `cds.server.body_parser.limit` + +### Changed + +- Bump required `@sap/cds` version to `>=7.8` +- To improve performance, binary payloads are no longer validated to check if they are properly base64 or base64url encoded +- Bump required `node` version to `^16` due to usage of `Buffer.toString('base64url')` +- Use `cds.compile.to.serviceinfo` to determine if a service should be compiled to GraphQL schema + ## Version 0.10.1 - 2024-03-07 ### Fixed diff --git a/lib/GraphQLAdapter.js b/lib/GraphQLAdapter.js index edcab379..80182cdc 100644 --- a/lib/GraphQLAdapter.js +++ b/lib/GraphQLAdapter.js @@ -20,7 +20,7 @@ function GraphQLAdapter(options) { : defaultErrorFormatter router - .use(express.json()) //> required by logger below + .use(express.json({ ...cds.env.server.body_parser })) //> required by logger below .use(queryLogger) if (options.graphiql) router.use(graphiql) diff --git a/lib/compile.js b/lib/compile.js index 54d0e502..93635876 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -2,29 +2,16 @@ const cds = require('@sap/cds') const { generateSchema4 } = require('./schema') const { lexicographicSortSchema, printSchema } = require('graphql') -const _isServiceAnnotatedWithGraphQL = service => { - const { definition } = service - - if (definition['@graphql']) return true - - const protocol = definition['@protocol'] - if (protocol) { - // @protocol: 'graphql' or @protocol: ['graphql', 'odata'] - const protocols = Array.isArray(protocol) ? protocol : [protocol] - // Normalize objects such as { kind: 'graphql' } to strings - return protocols.map(p => (typeof p === 'object' ? p.kind : p)).some(p => p.match(/graphql/i)) - } - - return false -} - function cds_compile_to_gql(csn, options = {}) { - const m = cds.linked(csn) + const model = cds.linked(csn) + const serviceinfo = cds.compile.to.serviceinfo(csn, options) const services = Object.fromEntries( - m.services - .map(s => [s.name, new cds.ApplicationService(s.name, m)]) + model.services + .map(s => [s.name, new cds.ApplicationService(s.name, model)]) // Only compile services with GraphQL endpoints - .filter(([_, service]) => _isServiceAnnotatedWithGraphQL(service)) + .filter(([_, service]) => + serviceinfo.find(s => s.name === service.name)?.endpoints.some(e => e.kind === 'graphql') + ) ) let schema = generateSchema4(services) diff --git a/lib/resolvers/GraphQLRequest.js b/lib/resolvers/GraphQLRequest.js index e9d2ee82..19a662af 100644 --- a/lib/resolvers/GraphQLRequest.js +++ b/lib/resolvers/GraphQLRequest.js @@ -1,4 +1,4 @@ -const cds = require('@sap/cds/lib') +const cds = require('@sap/cds') class GraphQLRequest extends cds.Request { constructor(args) { diff --git a/lib/resolvers/crud/create.js b/lib/resolvers/crud/create.js index e050eac3..efa22b3f 100644 --- a/lib/resolvers/crud/create.js +++ b/lib/resolvers/crud/create.js @@ -1,4 +1,4 @@ -const cds = require('@sap/cds/lib') +const cds = require('@sap/cds') const { INSERT } = cds.ql const { ARGS } = require('../../constants') const { getArgumentByName, astToEntries } = require('../parse/ast2cqn') diff --git a/lib/resolvers/crud/delete.js b/lib/resolvers/crud/delete.js index 5d142927..93151921 100644 --- a/lib/resolvers/crud/delete.js +++ b/lib/resolvers/crud/delete.js @@ -1,4 +1,4 @@ -const cds = require('@sap/cds/lib') +const cds = require('@sap/cds') const { DELETE } = cds.ql const { ARGS } = require('../../constants') const { getArgumentByName, astToWhere } = require('../parse/ast2cqn') diff --git a/lib/resolvers/crud/read.js b/lib/resolvers/crud/read.js index e74d7028..5b18399f 100644 --- a/lib/resolvers/crud/read.js +++ b/lib/resolvers/crud/read.js @@ -1,4 +1,4 @@ -const cds = require('@sap/cds/lib') +const cds = require('@sap/cds') const { SELECT } = cds.ql const { ARGS, CONNECTION_FIELDS } = require('../../constants') const { getArgumentByName, astToColumns, astToWhere, astToOrderBy, astToLimit } = require('../parse/ast2cqn') diff --git a/lib/resolvers/crud/update.js b/lib/resolvers/crud/update.js index ae4d76fd..0a8da06b 100644 --- a/lib/resolvers/crud/update.js +++ b/lib/resolvers/crud/update.js @@ -1,4 +1,4 @@ -const cds = require('@sap/cds/lib') +const cds = require('@sap/cds') const { SELECT, UPDATE } = cds.ql const { ARGS } = require('../../constants') const { getArgumentByName, astToColumns, astToWhere, astToEntries } = require('../parse/ast2cqn') diff --git a/lib/resolvers/parse/ast2cqn/where.js b/lib/resolvers/parse/ast2cqn/where.js index ed3ef4c9..de012b0e 100644 --- a/lib/resolvers/parse/ast2cqn/where.js +++ b/lib/resolvers/parse/ast2cqn/where.js @@ -65,7 +65,7 @@ const _arrayInsertBetweenFlat = (array, element) => const _joinedXprFrom_xprs = (_xprs, operator) => ({ xpr: _arrayInsertBetweenFlat(_xprs, operator) }) -const _true_xpr = [{ val: 1 }, '=', { val: 1 }] +const _true_xpr = [{ val: '1' }, '=', { val: '1' }] const _parseObjectValue = (objectValue, columnName) => { const _xprs = objectValue.fields @@ -76,7 +76,7 @@ const _parseObjectValue = (objectValue, columnName) => { return _joinedXprFrom_xprs(_xprs, 'and') } -const _false_xpr = [{ val: 0 }, '=', { val: 1 }] +const _false_xpr = [{ val: '0' }, '=', { val: '1' }] const _parseListValue = (listValue, columnName) => { const _xprs = listValue.values.map(value => _parseObjectValue(value, columnName)).filter(value => value !== undefined) diff --git a/lib/schema/index.js b/lib/schema/index.js index 322b3403..2640e265 100644 --- a/lib/schema/index.js +++ b/lib/schema/index.js @@ -2,23 +2,6 @@ const queryGenerator = require('./query') const mutationGenerator = require('./mutation') const { GraphQLSchema } = require('graphql') const { createRootResolvers, registerAliasFieldResolvers } = require('../resolvers') -const { printSchema } = require('graphql') - -// REVISIT: remove class with cds^8 -class SchemaGenerator { - generate(services) { - this._schema = generateSchema4(services) - return this - } - - getSchema() { - return this._schema - } - - printSchema() { - return printSchema(this._schema) - } -} function generateSchema4(services) { const resolvers = createRootResolvers(services) @@ -30,4 +13,4 @@ function generateSchema4(services) { return schema } -module.exports = { SchemaGenerator, generateSchema4 } +module.exports = { generateSchema4 } diff --git a/lib/schema/types/custom/GraphQLBinary.js b/lib/schema/types/custom/GraphQLBinary.js index df19c753..2711606e 100644 --- a/lib/schema/types/custom/GraphQLBinary.js +++ b/lib/schema/types/custom/GraphQLBinary.js @@ -1,31 +1,20 @@ -const { GraphQLScalarType, Kind, GraphQLError } = require('graphql') +const { GraphQLScalarType, Kind } = require('graphql') 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' -const _normalizeBase64 = value => (Buffer.isBuffer(value) ? value : Buffer.from(value, 'base64')).toString('base64') - -const _validateBase64String = (value, buffer, valueNode) => { - const base64Value = _toBase64(value) - const normalized = _normalizeBase64(buffer) - if (_stripPadding(base64Value) !== _stripPadding(normalized) || base64Value.length > normalized.length) - throw new GraphQLError(ERROR_NON_BASE64_OR_BASE64URL, valueNode) +const serialize = value => { + // Normalize to base64url string + const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value, 'base64') + const base64url = buffer.toString('base64url') + // Buffer base64url encoding does not have padding by default -> add it + return base64url.padEnd(Math.ceil(base64url.length / 4) * 4, '=') } -const _toBase64 = value => value.replace(/_/g, '/').replace(/-/g, '+') -const _toBase64Url = value => value.replace(/\//g, '_').replace(/\+/g, '-') -const _stripPadding = value => value.replace(/=/g, '') - -const serialize = outputValue => _toBase64Url(_normalizeBase64(outputValue)) - const parseValue = inputValue => { if (typeof inputValue !== 'string') throw getGraphQLValueError(ERROR_NON_STRING_VALUE, inputValue) - const buffer = Buffer.from(inputValue, 'base64') - _validateBase64String(inputValue, buffer) - - return buffer + return Buffer.from(inputValue, 'base64') } const parseLiteral = valueNode => { @@ -36,10 +25,7 @@ const parseLiteral = valueNode => { // WORKAROUND: value could have already been parsed to a Buffer, necessary because of manual parsing in enrich AST if (Buffer.isBuffer(value)) return value - const buffer = Buffer.from(value, 'base64') - _validateBase64String(value, buffer, valueNode) - - return buffer + return Buffer.from(value, 'base64') } module.exports = new GraphQLScalarType({ diff --git a/lib/schema/types/custom/GraphQLDate.js b/lib/schema/types/custom/GraphQLDate.js index b72bec88..bbdd5ca9 100644 --- a/lib/schema/types/custom/GraphQLDate.js +++ b/lib/schema/types/custom/GraphQLDate.js @@ -7,7 +7,7 @@ const ERROR_NON_DATE_VALUE = 'Date values must be strings in the ISO 8601 format const _parseDate = inputValueOrValueNode => { const date = parseDate(inputValueOrValueNode, ERROR_NON_DATE_VALUE) // Only return YYYY-MM-DD - return date.substring(0, date.indexOf('T')) + return date.slice(0, 10) } const parseValue = inputValue => { diff --git a/package.json b/package.json index 456afb6c..25b5f732 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cap-js/graphql", - "version": "0.10.1", + "version": "0.11.0", "description": "CDS protocol adapter for GraphQL", "keywords": [ "CAP", @@ -22,12 +22,12 @@ "LICENSE" ], "engines": { - "node": ">=14" + "node": ">=16" }, "scripts": { - "prettier": "npx prettier --write app lib test", - "prettier:check": "npx prettier --check app lib test", - "lint": "npx eslint .", + "prettier": "npm_config_yes=true npx prettier@latest --write app lib test", + "prettier:check": "npm_config_yes=true npx prettier@latest --check app lib test", + "lint": "npm_config_yes=true npx eslint@latest .", "test": "jest --silent", "test:generate-schemas": "node ./test/scripts/generate-schemas.js" }, @@ -36,15 +36,14 @@ "graphql-http": "^1.18.0" }, "peerDependencies": { - "@sap/cds": ">=7.3" + "@sap/cds": ">=7.8" }, "devDependencies": { "@cap-js/graphql": "file:.", + "@cap-js/sqlite": "^1", "axios": "^1", - "eslint": "^8", "express": "^4.17.1", "jest": "^29.3.1", - "semver": "^7.4.0", - "@cap-js/sqlite": "^1" + "semver": "^7.4.0" } } diff --git a/test/resources/annotations/srv/protocols.cds b/test/resources/annotations/srv/protocols.cds index c4d0947c..654d4235 100644 --- a/test/resources/annotations/srv/protocols.cds +++ b/test/resources/annotations/srv/protocols.cds @@ -1,47 +1,49 @@ -service NotAnnotated { +context protocols { entity A { key id : UUID; } } +service NotAnnotated { + entity A as projection on protocols.A; +} + @protocol: 'none' service AnnotatedWithAtProtocolNone { - entity A { - key id : UUID; - } + entity A as projection on protocols.A; } @protocol: 'odata' service AnnotatedWithNonGraphQL { - entity A { - key id : UUID; - } + entity A as projection on protocols.A; } @graphql service AnnotatedWithAtGraphQL { - entity A { - key id : UUID; - } + entity A as projection on protocols.A; } @protocol: 'graphql' service AnnotatedWithAtProtocolString { - entity A { - key id : UUID; - } + entity A as projection on protocols.A; } @protocol: ['graphql'] service AnnotatedWithAtProtocolStringList { - entity A { - key id : UUID; - } + entity A as projection on protocols.A; } @protocol: [{kind: 'graphql'}] service AnnotatedWithAtProtocolObjectList { - entity A { - key id : UUID; - } + entity A as projection on protocols.A; +} + +@protocol: { graphql } +service AnnotatedWithAtProtocolObjectWithKey { + entity A as projection on protocols.A; +} + +@protocol: { graphql: 'dummy' } +service AnnotatedWithAtProtocolObjectWithKeyAndValue { + entity A as projection on protocols.A; } diff --git a/test/resources/bookshop-graphql/srv/test-service.js b/test/resources/bookshop-graphql/srv/test-service.js index a01d54de..cc840280 100644 --- a/test/resources/bookshop-graphql/srv/test-service.js +++ b/test/resources/bookshop-graphql/srv/test-service.js @@ -1,4 +1,4 @@ -const cds = require('@sap/cds/lib') +const cds = require('@sap/cds') module.exports = cds.service.impl(srv => { const { Foo } = srv.entities diff --git a/test/resources/bookshop/package.json b/test/resources/bookshop/package.json index 5e7113e1..3f604834 100644 --- a/test/resources/bookshop/package.json +++ b/test/resources/bookshop/package.json @@ -10,6 +10,7 @@ "index.js" ], "dependencies": { + "@cap-js/graphql": "*", "@sap/cds": ">=5.9", "express": "^4.17.1" }, diff --git a/test/resources/custom-handlers/package.json b/test/resources/custom-handlers/package.json index 580c138c..e31f286b 100644 --- a/test/resources/custom-handlers/package.json +++ b/test/resources/custom-handlers/package.json @@ -1,4 +1,8 @@ { + "Note: Programmatic configuration of GraphQL protocol adapter in server.js": "", + "__dependencies": { + "@cap-js/graphql": "*" + }, "devDependencies": { "@cap-js/sqlite": "*" } diff --git a/test/resources/types/package.json b/test/resources/types/package.json index 717be87e..f3d56914 100644 --- a/test/resources/types/package.json +++ b/test/resources/types/package.json @@ -1,5 +1,18 @@ { "dependencies": { + "@cap-js/graphql": "*" + }, + "devDependencies": { "@cap-js/sqlite": "*" + }, + "cds": { + "server": { + "body_parser": { + "limit": "110KB" + } + }, + "features": { + "ieee754compatible": true + } } } \ No newline at end of file diff --git a/test/resources/types/server.js b/test/resources/types/server.js deleted file mode 100644 index 6939c6d6..00000000 --- a/test/resources/types/server.js +++ /dev/null @@ -1,6 +0,0 @@ -const cds = require('@sap/cds') -const path = require('path') -const protocols = cds.env.protocols ??= {} -if (!protocols.graphql) protocols.graphql = { - path: '/graphql', impl: path.join(__dirname, '../../../index.js') -} \ No newline at end of file diff --git a/test/schemas/annotations.gql b/test/schemas/annotations.gql index 9918197c..690bd976 100644 --- a/test/schemas/annotations.gql +++ b/test/schemas/annotations.gql @@ -66,6 +66,74 @@ type AnnotatedWithAtProtocolObjectList_input { A: AnnotatedWithAtProtocolObjectList_A_input } +type AnnotatedWithAtProtocolObjectWithKey { + A(filter: [AnnotatedWithAtProtocolObjectWithKey_A_filter], orderBy: [AnnotatedWithAtProtocolObjectWithKey_A_orderBy], skip: Int, top: Int): AnnotatedWithAtProtocolObjectWithKey_A_connection +} + +type AnnotatedWithAtProtocolObjectWithKeyAndValue { + A(filter: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A_filter], orderBy: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A_orderBy], skip: Int, top: Int): AnnotatedWithAtProtocolObjectWithKeyAndValue_A_connection +} + +type AnnotatedWithAtProtocolObjectWithKeyAndValue_A { + id: ID +} + +input AnnotatedWithAtProtocolObjectWithKeyAndValue_A_C { + id: ID +} + +type AnnotatedWithAtProtocolObjectWithKeyAndValue_A_connection { + nodes: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A] + totalCount: Int +} + +input AnnotatedWithAtProtocolObjectWithKeyAndValue_A_filter { + id: [ID_filter] +} + +type AnnotatedWithAtProtocolObjectWithKeyAndValue_A_input { + create(input: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A_C]!): [AnnotatedWithAtProtocolObjectWithKeyAndValue_A] + delete(filter: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A_filter]!): Int +} + +input AnnotatedWithAtProtocolObjectWithKeyAndValue_A_orderBy { + id: SortDirection +} + +type AnnotatedWithAtProtocolObjectWithKeyAndValue_input { + A: AnnotatedWithAtProtocolObjectWithKeyAndValue_A_input +} + +type AnnotatedWithAtProtocolObjectWithKey_A { + id: ID +} + +input AnnotatedWithAtProtocolObjectWithKey_A_C { + id: ID +} + +type AnnotatedWithAtProtocolObjectWithKey_A_connection { + nodes: [AnnotatedWithAtProtocolObjectWithKey_A] + totalCount: Int +} + +input AnnotatedWithAtProtocolObjectWithKey_A_filter { + id: [ID_filter] +} + +type AnnotatedWithAtProtocolObjectWithKey_A_input { + create(input: [AnnotatedWithAtProtocolObjectWithKey_A_C]!): [AnnotatedWithAtProtocolObjectWithKey_A] + delete(filter: [AnnotatedWithAtProtocolObjectWithKey_A_filter]!): Int +} + +input AnnotatedWithAtProtocolObjectWithKey_A_orderBy { + id: SortDirection +} + +type AnnotatedWithAtProtocolObjectWithKey_input { + A: AnnotatedWithAtProtocolObjectWithKey_A_input +} + type AnnotatedWithAtProtocolString { A(filter: [AnnotatedWithAtProtocolString_A_filter], orderBy: [AnnotatedWithAtProtocolString_A_orderBy], skip: Int, top: Int): AnnotatedWithAtProtocolString_A_connection } @@ -147,6 +215,8 @@ input ID_filter { type Mutation { AnnotatedWithAtGraphQL: AnnotatedWithAtGraphQL_input AnnotatedWithAtProtocolObjectList: AnnotatedWithAtProtocolObjectList_input + AnnotatedWithAtProtocolObjectWithKey: AnnotatedWithAtProtocolObjectWithKey_input + AnnotatedWithAtProtocolObjectWithKeyAndValue: AnnotatedWithAtProtocolObjectWithKeyAndValue_input AnnotatedWithAtProtocolString: AnnotatedWithAtProtocolString_input AnnotatedWithAtProtocolStringList: AnnotatedWithAtProtocolStringList_input } @@ -154,6 +224,8 @@ type Mutation { type Query { AnnotatedWithAtGraphQL: AnnotatedWithAtGraphQL AnnotatedWithAtProtocolObjectList: AnnotatedWithAtProtocolObjectList + AnnotatedWithAtProtocolObjectWithKey: AnnotatedWithAtProtocolObjectWithKey + AnnotatedWithAtProtocolObjectWithKeyAndValue: AnnotatedWithAtProtocolObjectWithKeyAndValue AnnotatedWithAtProtocolString: AnnotatedWithAtProtocolString AnnotatedWithAtProtocolStringList: AnnotatedWithAtProtocolStringList } diff --git a/test/tests/annotations.test.js b/test/tests/annotations.test.js index 696174ba..e3ec8ad5 100644 --- a/test/tests/annotations.test.js +++ b/test/tests/annotations.test.js @@ -1,5 +1,5 @@ describe('graphql - annotations', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') @@ -123,5 +123,37 @@ describe('graphql - annotations', () => { const response = await POST(path, { query }) expect(response.data).not.toHaveProperty('errors') }) + + test('service annotated with "@protocol: { graphql }" is served at configured path', async () => { + const query = gql` + { + AnnotatedWithAtProtocolObjectWithKey { + A { + nodes { + id + } + } + } + } + ` + const response = await POST(path, { query }) + expect(response.data).not.toHaveProperty('errors') + }) + + test('service annotated with "@protocol: { graphql: \'dummy\' }" is served at configured path', async () => { + const query = gql` + { + AnnotatedWithAtProtocolObjectWithKeyAndValue { + A { + nodes { + id + } + } + } + } + ` + const response = await POST(path, { query }) + expect(response.data).not.toHaveProperty('errors') + }) }) }) diff --git a/test/tests/concurrency.test.js b/test/tests/concurrency.test.js index 4ae5d9e6..cd5bdf31 100644 --- a/test/tests/concurrency.test.js +++ b/test/tests/concurrency.test.js @@ -1,5 +1,5 @@ describe('graphql - resolver concurrency', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') diff --git a/test/tests/context.test.js b/test/tests/context.test.js index e3ca3e08..3636dd0c 100644 --- a/test/tests/context.test.js +++ b/test/tests/context.test.js @@ -1,5 +1,5 @@ describe('graphql - context is set', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') diff --git a/test/tests/custom-error-formatter.test.js b/test/tests/custom-error-formatter.test.js index ed114e14..41a19207 100644 --- a/test/tests/custom-error-formatter.test.js +++ b/test/tests/custom-error-formatter.test.js @@ -1,5 +1,5 @@ describe('graphql - custom error formatter function', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') diff --git a/test/tests/custom-handlers.test.js b/test/tests/custom-handlers.test.js index bb2c1577..6925539e 100644 --- a/test/tests/custom-handlers.test.js +++ b/test/tests/custom-handlers.test.js @@ -1,5 +1,5 @@ describe('graphql - custom handlers', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') diff --git a/test/tests/edge-cases.test.js b/test/tests/edge-cases.test.js index aa06f593..fa778dac 100644 --- a/test/tests/edge-cases.test.js +++ b/test/tests/edge-cases.test.js @@ -1,5 +1,5 @@ describe('graphql - edge cases', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') diff --git a/test/tests/error-handling-dev.test.js b/test/tests/error-handling-dev.test.js index c415cd3d..87f3184b 100644 --- a/test/tests/error-handling-dev.test.js +++ b/test/tests/error-handling-dev.test.js @@ -1,5 +1,5 @@ describe('graphql - error handling in development', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') @@ -35,11 +35,6 @@ describe('graphql - error handling in development', () => { extensions: { code: '400', target: 'notEmptyI', - args: ['notEmptyI'], - entity: 'ValidationErrorsService.A', - element: 'notEmptyI', - type: 'cds.Integer', - numericSeverity: 4, stacktrace: expect.any(Array) } } @@ -71,22 +66,12 @@ describe('graphql - error handling in development', () => { code: '400', message: 'Value is required', target: 'notEmptyI', - args: ['notEmptyI'], - entity: 'ValidationErrorsService.B', - element: 'notEmptyI', - type: 'cds.Integer', - numericSeverity: 4, stacktrace: expect.any(Array) }, { code: '400', message: 'Value is required', target: 'notEmptyS', - args: ['notEmptyS'], - entity: 'ValidationErrorsService.B', - element: 'notEmptyS', - type: 'cds.String', - numericSeverity: 4, stacktrace: expect.any(Array) } ] @@ -119,11 +104,6 @@ describe('graphql - error handling in development', () => { code: '400', target: 'inRange', args: [10, 0, 3], - entity: 'ValidationErrorsService.C', - element: 'inRange', - type: 'cds.Integer', - value: 10, - numericSeverity: 4, stacktrace: expect.any(Array) } } @@ -151,30 +131,8 @@ describe('graphql - error handling in development', () => { extensions: { code: '400', details: [ - { - code: '400', - message: 'Wert ist erforderlich', - target: 'inRange', - args: ['inRange'], - entity: 'ValidationErrorsService.C', - element: 'inRange', - type: 'cds.Integer', - numericSeverity: 4, - stacktrace: expect.any(Array) - }, - { - code: '400', - message: 'Value "foo" is invalid according to enum declaration {"high", "medium", "low"}', - target: 'oneOfEnumValues', - args: ['"foo"', '"high", "medium", "low"'], - entity: 'ValidationErrorsService.C', - element: 'oneOfEnumValues', - type: 'cds.String', - value: 'foo', - enum: ['@assert.range', 'type', 'enum'], - numericSeverity: 4, - stacktrace: expect.any(Array) - } + { target: 'inRange', message: 'Wert ist erforderlich' }, + { target: 'oneOfEnumValues', message: expect.stringContaining('Value "foo" is invalid') } ] } } @@ -184,35 +142,13 @@ describe('graphql - error handling in development', () => { expect(response.data.errors[0].extensions).not.toHaveProperty('stacktrace') // No stacktrace outside of error details expect(response.data.errors[0].extensions.details[0].stacktrace[0]).not.toHaveLength(0) // Stacktrace exists and is not empty expect(response.data.errors[0].extensions.details[1].stacktrace[0]).not.toHaveLength(0) // Stacktrace exists and is not empty - expect(console.warn.mock.calls[0][1]).toMatchObject({ + + const log = console.warn.mock.calls[0][1] + expect(log).toMatchObject({ code: '400', - message: 'Multiple errors occurred. Please see the details for more information.', details: [ - { - args: ['inRange'], - code: '400', - element: 'inRange', - entity: 'ValidationErrorsService.C', - message: 'Value is required', - numericSeverity: 4, - target: 'inRange', - type: 'cds.Integer', - value: undefined, - stack: expect.any(String) - }, - { - args: ['"foo"', '"high", "medium", "low"'], - code: '400', - element: 'oneOfEnumValues', - entity: 'ValidationErrorsService.C', - enum: ['@assert.range', 'type', 'enum'], - message: 'Value "foo" is invalid according to enum declaration {"high", "medium", "low"}', - numericSeverity: 4, - target: 'oneOfEnumValues', - type: 'cds.String', - value: 'foo', - stack: expect.any(String) - } + { target: 'inRange', message: 'Value is required' }, + { target: 'oneOfEnumValues', message: expect.stringContaining('Value "foo" is invalid') } ] }) expect(console.warn.mock.calls[0][1]).not.toHaveProperty('stacktrace') // No stacktrace outside of error details @@ -412,7 +348,7 @@ describe('graphql - error handling in development', () => { numericSeverity: 4, status: 418, target: 'some_field', - stack: expect.any(String) + // stack: expect.any(String) // doesn't work with Node 22 }, { code: 'Some-Custom-Code2', @@ -420,7 +356,7 @@ describe('graphql - error handling in development', () => { numericSeverity: 4, status: 500, target: 'some_field', - stack: expect.any(String) + // stack: expect.any(String) // doesn't work with Node 22 } ] }) diff --git a/test/tests/error-handling-prod.test.js b/test/tests/error-handling-prod.test.js index a409b751..b57f35c9 100644 --- a/test/tests/error-handling-prod.test.js +++ b/test/tests/error-handling-prod.test.js @@ -1,6 +1,6 @@ describe('graphql - error handling in production', () => { process.env.NODE_ENV = 'production' - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') @@ -43,15 +43,7 @@ describe('graphql - error handling in production', () => { expect(response.data).toMatchObject({ errors }) expect(response.data.errors[0].extensions).not.toHaveProperty('stacktrace') // No stacktrace in production const log = console.warn.mock.calls[0][1] || JSON.parse(console.warn.mock.calls[0][0]) - expect(log).toMatchObject({ - code: '400', - element: 'notEmptyI', - entity: 'ValidationErrorsService.A', - message: 'Value is required', - numericSeverity: 4, - target: 'notEmptyI', - type: 'cds.Integer' - }) + expect(log).toMatchObject({ code: '400', target: 'notEmptyI', msg: 'Value is required' }) }) test('Multiple @mandatory validation errors', async () => { @@ -144,7 +136,7 @@ describe('graphql - error handling in production', () => { }, { code: '400', - message: 'Value "foo" is invalid according to enum declaration {"high", "medium", "low"}', + message: expect.stringContaining('Value "foo" is invalid'), target: 'oneOfEnumValues' } ] @@ -157,35 +149,20 @@ describe('graphql - error handling in production', () => { expect(response.data.errors[0].extensions.details[0]).not.toHaveProperty('stacktrace') // No stacktrace in production expect(response.data.errors[0].extensions.details[1]).not.toHaveProperty('stacktrace') // No stacktrace in production const log = console.warn.mock.calls[0][1] || JSON.parse(console.warn.mock.calls[0][0]) - const msgProperty = log.msg ? 'msg' : 'message' + expect(log).toMatchObject({ code: '400', - [msgProperty]: 'Multiple errors occurred. Please see the details for more information.', + msg: 'Multiple errors occurred. Please see the details for more information.', details: [ + { code: '400', target: 'inRange', message: 'Value is required' }, { - args: ['inRange'], - code: '400', - element: 'inRange', - entity: 'ValidationErrorsService.C', - message: 'Value is required', - numericSeverity: 4, - target: 'inRange', - type: 'cds.Integer' - }, - { - args: ['"foo"', '"high", "medium", "low"'], code: '400', - element: 'oneOfEnumValues', - entity: 'ValidationErrorsService.C', - enum: ['@assert.range', 'type', 'enum'], - message: 'Value "foo" is invalid according to enum declaration {"high", "medium", "low"}', - numericSeverity: 4, target: 'oneOfEnumValues', - type: 'cds.String', - value: 'foo' + message: expect.stringContaining('Value "foo" is invalid') } ] }) + expect(log).not.toHaveProperty('stacktrace') // No stacktrace outside of error details }) }) @@ -361,10 +338,9 @@ describe('graphql - error handling in production', () => { expect(response.data.errors[0].extensions.details[0]).not.toHaveProperty('stacktrace') // No stacktrace in production expect(response.data.errors[0].extensions.details[1]).not.toHaveProperty('stacktrace') // No stacktrace in production const log = console.error.mock.calls[0][1] || JSON.parse(console.error.mock.calls[0][0]) - const msgProperty = log.msg ? 'msg' : 'message' expect(log).toMatchObject({ code: '500', - [msgProperty]: 'Multiple errors occurred. Please see the details for more information.', + msg: 'Multiple errors occurred. Please see the details for more information.', details: [ { code: 'Some-Custom-Code1', diff --git a/test/tests/graphiql.test.js b/test/tests/graphiql.test.js index 0784f561..4bd25a6a 100644 --- a/test/tests/graphiql.test.js +++ b/test/tests/graphiql.test.js @@ -1,5 +1,5 @@ describe('graphql - GraphiQL', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') diff --git a/test/tests/localized.test.js b/test/tests/localized.test.js index d1f012e5..e45896dd 100644 --- a/test/tests/localized.test.js +++ b/test/tests/localized.test.js @@ -1,5 +1,5 @@ describe('graphql - queries with localized data', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') diff --git a/test/tests/logger-dev.test.js b/test/tests/logger-dev.test.js index 4385a419..ce15683b 100644 --- a/test/tests/logger-dev.test.js +++ b/test/tests/logger-dev.test.js @@ -1,5 +1,5 @@ describe('graphql - query logging in development', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') const util = require('util') diff --git a/test/tests/logger-prod.test.js b/test/tests/logger-prod.test.js index c67bcd97..e771c938 100644 --- a/test/tests/logger-prod.test.js +++ b/test/tests/logger-prod.test.js @@ -1,6 +1,6 @@ describe('graphql - query logging with sanitization in production', () => { process.env.NODE_ENV = 'production' - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') const util = require('util') diff --git a/test/tests/mutations/create.test.js b/test/tests/mutations/create.test.js index ee5a04cb..269403fb 100644 --- a/test/tests/mutations/create.test.js +++ b/test/tests/mutations/create.test.js @@ -1,5 +1,5 @@ describe('graphql - create mutations', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/mutations/delete.test.js b/test/tests/mutations/delete.test.js index 50bfbc1b..53887f50 100644 --- a/test/tests/mutations/delete.test.js +++ b/test/tests/mutations/delete.test.js @@ -1,5 +1,5 @@ describe('graphql - delete mutations', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/mutations/update.test.js b/test/tests/mutations/update.test.js index 00112db6..b4a9398b 100644 --- a/test/tests/mutations/update.test.js +++ b/test/tests/mutations/update.test.js @@ -1,5 +1,5 @@ describe('graphql - update mutations', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/queries/aliases.test.js b/test/tests/queries/aliases.test.js index 2945d34c..3a5dcc8a 100644 --- a/test/tests/queries/aliases.test.js +++ b/test/tests/queries/aliases.test.js @@ -1,5 +1,5 @@ describe('graphql - aliases', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/queries/filter.test.js b/test/tests/queries/filter.test.js index fa8b4b55..c5f08c7f 100644 --- a/test/tests/queries/filter.test.js +++ b/test/tests/queries/filter.test.js @@ -1,5 +1,5 @@ describe('graphql - filter', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/queries/fragments.test.js b/test/tests/queries/fragments.test.js index 8eda167d..84523c69 100644 --- a/test/tests/queries/fragments.test.js +++ b/test/tests/queries/fragments.test.js @@ -1,5 +1,5 @@ describe('graphql - fragments', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/queries/meta.test.js b/test/tests/queries/meta.test.js index 02c1a00b..504700a3 100644 --- a/test/tests/queries/meta.test.js +++ b/test/tests/queries/meta.test.js @@ -1,5 +1,5 @@ describe('graphql - meta fields', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/queries/orderBy.test.js b/test/tests/queries/orderBy.test.js index 7c6e49f4..43c4c51a 100644 --- a/test/tests/queries/orderBy.test.js +++ b/test/tests/queries/orderBy.test.js @@ -1,5 +1,5 @@ describe('graphql - orderBy', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/queries/paging-offset.test.js b/test/tests/queries/paging-offset.test.js index 67ef4f97..9f3d89db 100644 --- a/test/tests/queries/paging-offset.test.js +++ b/test/tests/queries/paging-offset.test.js @@ -1,5 +1,5 @@ describe('graphql - offset-based paging', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/queries/queries.test.js b/test/tests/queries/queries.test.js index 0f1de2ad..d9a5b1d5 100644 --- a/test/tests/queries/queries.test.js +++ b/test/tests/queries/queries.test.js @@ -1,5 +1,5 @@ describe('graphql - queries', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/queries/totalCount.test.js b/test/tests/queries/totalCount.test.js index 07f75366..ba4c1ac8 100644 --- a/test/tests/queries/totalCount.test.js +++ b/test/tests/queries/totalCount.test.js @@ -1,5 +1,5 @@ describe('graphql - queries with totalCount', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/queries/variables.test.js b/test/tests/queries/variables.test.js index 91bfbf94..4c286a70 100644 --- a/test/tests/queries/variables.test.js +++ b/test/tests/queries/variables.test.js @@ -1,5 +1,5 @@ describe('graphql - variables', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../../util') diff --git a/test/tests/request.test.js b/test/tests/request.test.js index 07841efd..42b57a46 100644 --- a/test/tests/request.test.js +++ b/test/tests/request.test.js @@ -1,5 +1,5 @@ describe('graphql - cds.request', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const { gql } = require('../util') diff --git a/test/tests/types.test.js b/test/tests/types.test.js index 1323e2e4..5ccda6be 100644 --- a/test/tests/types.test.js +++ b/test/tests/types.test.js @@ -1,16 +1,21 @@ +const consumers = require('node:stream/consumers') + const { gql } = require('../util') const _toBase64Url = value => value.replace(/\//g, '_').replace(/\+/g, '-') -const _getTestBuffer = repetitions => { - const testString = 'Test String!!' - let string = '' - for (let i = 0; i < repetitions; i++) string += testString - return Buffer.from(string) +const _getTestString = length => { + const testString = 'This is a test string! ' + return testString.repeat(Math.ceil(length / testString.length)).slice(0, length) } -const _getMutationForFieldWithLiteralValue = (field, value, quoted) => - gql` +const _getTestBuffer = length => Buffer.from(_getTestString(length)) + +// e.g. base64 string with length 96 -> requires buffer with length 72 +const _neededBufferLengthForBase64StringWithLength = length => Math.ceil((length * 6) / 8) + +const _getMutationForFieldWithLiteralValue = (field, value, quoted) => ({ + query: gql` mutation { TypesService { MyEntity { @@ -21,6 +26,7 @@ const _getMutationForFieldWithLiteralValue = (field, value, quoted) => } } ` +}) const _getMutationAndVariablesForFieldWithVariable = (field, value) => ({ query: gql` @@ -40,7 +46,7 @@ const _getMutationAndVariablesForFieldWithVariable = (field, value) => ({ }) describe('graphql - types parsing and validation', () => { - const cds = require('@sap/cds/lib') + const cds = require('@sap/cds') const path = require('path') const fs = require('fs') @@ -48,9 +54,7 @@ describe('graphql - types parsing and validation', () => { // Prevent axios from throwing errors for non 2xx status codes axios.defaults.validateStatus = false - beforeEach(async () => { - await data.reset() - }) + beforeEach(data.reset) describe('cds.Binary', () => { const field = 'myBinary' @@ -60,81 +64,93 @@ describe('graphql - types parsing and validation', () => { test('cds.Binary is correctly parsed from input literal base64 encoded string value', async () => { const buffer = _fileBuffer const value = _fileBuffer.toString('base64') - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: _toBase64Url(value) }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) test('cds.Binary is correctly parsed from input literal base64url encoded string value', async () => { const buffer = _fileBuffer const value = _toBase64Url(_fileBuffer.toString('base64')) - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) test('cds.Binary is correctly parsed from input literal base64 encoded string value with padding', async () => { const buffer = Buffer.from('This is a test string!') const value = 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ==' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) test('cds.Binary is correctly parsed from input literal base64 encoded string value with no padding', async () => { const buffer = Buffer.from('This is a test string!') const value = 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ==' }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) test('cds.Binary throws error when input literal is not a string, but an integer', async () => { const value = 123 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'Binary cannot represent non string value: 123' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) - test('cds.Binary throws error when input literal string value contains non base64 or base64url characters', async () => { + test('cds.Binary is correctly parsed from input literal base64 encoded string value containing non base64 or base64url characters', async () => { const value = 'abc.def~123' - const query = _getMutationForFieldWithLiteralValue(field, value, true) - const message = 'Binary values must be base64 or base64url encoded and normalized strings' - const response = await POST('/graphql', { query }) - expect(response.data.errors[0].message).toEqual(message) + const buffer = Buffer.from(value, 'base64url') + const body = _getMutationForFieldWithLiteralValue(field, value, true) + const data = { TypesService: { MyEntity: { create: [{ [field]: 'abcdef12' }] } } } + const response = await POST('/graphql', body) + expect(response.data).toEqual({ data }) + + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) - test('cds.Binary throws error when input literal string value contains non-normalized base64 encoding', async () => { + test('cds.Binary is correctly parsed from input literal string value containing non-normalized base64 encoding', async () => { const value = 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nISF=' // Should be E= - const query = _getMutationForFieldWithLiteralValue(field, value, true) - const message = 'Binary values must be base64 or base64url encoded and normalized strings' - const response = await POST('/graphql', { query }) - expect(response.data.errors[0].message).toEqual(message) + const buffer = Buffer.from(value, 'base64url') + const body = _getMutationForFieldWithLiteralValue(field, value, true) + const data = { TypesService: { MyEntity: { create: [{ [field]: 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nISE=' }] } } } + const response = await POST('/graphql', body) + expect(response.data).toEqual({ data }) + + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) - test('cds.Binary throws error when input literal string value contains base64 encoded string value with excessive padding', async () => { + test('cds.Binary is correctly parsed from input literal string value containing base64 encoded string value with excessive padding', async () => { const value = 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ==========' - const query = _getMutationForFieldWithLiteralValue(field, value, true) - const message = 'Binary values must be base64 or base64url encoded and normalized strings' - const response = await POST('/graphql', { query }) - expect(response.data.errors[0].message).toEqual(message) + const buffer = Buffer.from(value, 'base64url') + const body = _getMutationForFieldWithLiteralValue(field, value, true) + const data = { TypesService: { MyEntity: { create: [{ [field]: 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ==' }] } } } + const response = await POST('/graphql', body) + expect(response.data).toEqual({ data }) + + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) }) @@ -142,83 +158,93 @@ describe('graphql - types parsing and validation', () => { test('cds.Binary is correctly parsed from variable base64 encoded string value', async () => { const buffer = _fileBuffer const value = _fileBuffer.toString('base64') - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: _toBase64Url(value) }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) test('cds.Binary is correctly parsed from variable base64url encoded string value', async () => { const buffer = _fileBuffer const value = _toBase64Url(_fileBuffer.toString('base64')) - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) test('cds.Binary is correctly parsed from variable base64 encoded string value with padding', async () => { const buffer = Buffer.from('This is a test string!') const value = 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ==' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) test('cds.Binary is correctly parsed from variable base64 encoded string value with no padding', async () => { const buffer = Buffer.from('This is a test string!') const value = 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ==' }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) - test('cds.Binary throws error when variable string value contains non base64 or base64url characters', async () => { + test('cds.Binary is correctly parsed from variable string value containing non base64 or base64url characters', async () => { const value = 'abc.def~123' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) - const message = - 'Variable "$input" got invalid value "abc.def~123" at "input.myBinary"; Binary values must be base64 or base64url encoded and normalized strings' - const response = await POST('/graphql', { query, variables }) - expect(response.data.errors[0].message).toEqual(message) - }) + const buffer = Buffer.from(value, 'base64url') + const body = _getMutationAndVariablesForFieldWithVariable(field, value) + const data = { TypesService: { MyEntity: { create: [{ [field]: 'abcdef12' }] } } } + const response = await POST('/graphql', body) + expect(response.data).toEqual({ data }) - test('cds.Binary throws error when variable value is not a string, but an integer', async () => { - const value = 123 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) - const message = - 'Variable "$input" got invalid value 123 at "input.myBinary"; Binary cannot represent non string value: 123' - const response = await POST('/graphql', { query, variables }) - expect(response.data.errors[0].message).toEqual(message) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) - test('cds.Binary throws error when variable string value contains non-normalized base64 encoding', async () => { + test('cds.Binary is correctly parsed from variable string value containing non-normalized base64 encoding', async () => { const value = 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nISF=' // Should be E= - const query = _getMutationForFieldWithLiteralValue(field, value, true) - const message = 'Binary values must be base64 or base64url encoded and normalized strings' - const response = await POST('/graphql', { query }) - expect(response.data.errors[0].message).toEqual(message) + const buffer = Buffer.from(value, 'base64url') + const body = _getMutationForFieldWithLiteralValue(field, value, true) + const data = { TypesService: { MyEntity: { create: [{ [field]: 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nISE=' }] } } } + const response = await POST('/graphql', body) + expect(response.data).toEqual({ data }) + + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) }) - test('cds.Binary throws error when variable string value contains base64 encoded string value with excessive padding', async () => { + test('cds.Binary is correctly parsed from variable string value containing base64 encoded string value with excessive padding', async () => { const value = 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ==========' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const buffer = Buffer.from(value, 'base64url') + const body = _getMutationAndVariablesForFieldWithVariable(field, value) + const data = { TypesService: { MyEntity: { create: [{ [field]: 'VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ==' }] } } } + const response = await POST('/graphql', body) + expect(response.data).toEqual({ data }) + + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(buffer) + }) + + test('cds.Binary throws error when variable value is not a string, but an integer', async () => { + const value = 123 + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = - 'Variable "$input" got invalid value "VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIQ==========" at "input.myBinary"; Binary values must be base64 or base64url encoded and normalized strings' - const response = await POST('/graphql', { query, variables }) + 'Variable "$input" got invalid value 123 at "input.myBinary"; Binary cannot represent non string value: 123' + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -229,23 +255,23 @@ describe('graphql - types parsing and validation', () => { const value = true test('cds.Boolean is correctly parsed from input literal', async () => { - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.Boolean is correctly parsed from variable value', async () => { - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) }) @@ -255,28 +281,28 @@ describe('graphql - types parsing and validation', () => { describe('input literal', () => { test('cds.Date is correctly parsed from input literal string value', async () => { const value = '2021-06-27' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.Date throws error when input literal is a string containing a non-date value', async () => { const value = 'bla' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Date values must be strings in the ISO 8601 format YYYY-MM-DD: "bla"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Date throws error when input literal is not a string, but an integer', async () => { const value = 20210627 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'Date cannot represent non string value: 20210627' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -284,30 +310,30 @@ describe('graphql - types parsing and validation', () => { describe('variable value', () => { test('cds.Date is correctly parsed from variable string value', async () => { const value = '2021-06-27' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.Date throws error when variable is a string containing a non-date value', async () => { const value = 'bla' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "bla" at "input.myDate"; Date values must be strings in the ISO 8601 format YYYY-MM-DD: "bla"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Date throws error when variable value is not a string, but an integer', async () => { const value = 20210627 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value 20210627 at "input.myDate"; Date cannot represent non string value: 20210627' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -319,40 +345,40 @@ describe('graphql - types parsing and validation', () => { describe('input literal', () => { test('cds.DateTime is correctly parsed from input literal UTC datetime string value', async () => { const value = '2021-06-27T14:52:23Z' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.DateTime is correctly parsed from input literal non-UTC datetime string value', async () => { const value = '2021-06-27T14:52:23+12:34' const returnValue = '2021-06-27T02:18:23Z' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: returnValue }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: returnValue }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(returnValue) }) test('cds.DateTime throws error when input literal is a string containing a non-datetime value', async () => { const value = 'bla' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'DateTime values must be strings in the ISO 8601 format YYYY-MM-DDThh-mm-ssTZD: "bla"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.DateTime throws error when input literal is not a string, but an integer', async () => { const value = 20210627145223 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'DateTime cannot represent non string value: 20210627145223' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -360,42 +386,42 @@ describe('graphql - types parsing and validation', () => { describe('variable value', () => { test('cds.DateTime is correctly parsed from variable UTC datetime string value', async () => { const value = '2021-06-27T14:52:23Z' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.DateTime is correctly parsed from variable non-UTC datetime string value', async () => { const value = '2021-06-27T14:52:23+12:34' const returnValue = '2021-06-27T02:18:23Z' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: returnValue }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: returnValue }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(returnValue) }) test('cds.DateTime throws error when variable is a string containing a non-datetime value', async () => { const value = 'bla' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "bla" at "input.myDateTime"; DateTime values must be strings in the ISO 8601 format YYYY-MM-DDThh-mm-ssTZD: "bla"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.DateTime throws error when variable value is not a string, but an integer', async () => { const value = 20210627145223 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value 20210627145223 at "input.myDateTime"; DateTime cannot represent non string value: 20210627145223' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -406,95 +432,97 @@ describe('graphql - types parsing and validation', () => { describe('input literal', () => { test('cds.Decimal is correctly parsed from input literal float value', async () => { - const value = 123.45 - const query = _getMutationForFieldWithLiteralValue(field, value, false) - const data = { TypesService: { MyEntity: { create: [{ [field]: String(value) }] } } } - const response = await POST('/graphql', { query }) + const number = 123.45 + const value = String(number) + const body = _getMutationForFieldWithLiteralValue(field, number, false) + const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(String(result[field])).toEqual(value) }) test('cds.Decimal is correctly parsed from input literal int value', async () => { - const value = 123 - const query = _getMutationForFieldWithLiteralValue(field, value, false) - const data = { TypesService: { MyEntity: { create: [{ [field]: String(value) }] } } } - const response = await POST('/graphql', { query }) + const number = 123 + const value = String(number) + const body = _getMutationForFieldWithLiteralValue(field, number, false) + const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(String(result[field])).toEqual(value) }) test('cds.Decimal is correctly parsed from input literal numeric string value', async () => { - const value = 123.45 - const query = _getMutationForFieldWithLiteralValue(field, value, true) - const data = { TypesService: { MyEntity: { create: [{ [field]: String(value) }] } } } - const response = await POST('/graphql', { query }) + const number = 123.45 + const value = String(number) + const body = _getMutationForFieldWithLiteralValue(field, value, true) + const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(String(result[field])).toEqual(value) }) test('cds.Decimal correctly determines large input literal numeric string to be a decimal number', async () => { const value = '12345678901234567890.01234567890123456789' - const query = _getMutationForFieldWithLiteralValue(field, value, true) - const data = { TypesService: { MyEntity: { create: [{ [field]: String(value) }] } } } - const response = await POST('/graphql', { query }) + const body = _getMutationForFieldWithLiteralValue(field, value, true) + const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result.length).toBe(1) - expect(result[0][field]).toBeGreaterThan(12345678901234560000) // Incorrect sqlite value due to dynamic typing system and rounding errors + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(parseFloat(result[field])).toBeGreaterThan(12345678901234560000) // Incorrect sqlite value due to dynamic typing system and rounding errors }) test('cds.Decimal throws error when input literal is not a decimal string, but a boolean', async () => { const value = false - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'Decimal must be a numeric value: false' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Decimal throws error when input literal is non-numeric string value', async () => { const value = 'bla' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Decimal must be a numeric value: "bla"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Decimal throws error when input literal contains non-numeric character embedded in numeric string value', async () => { const value = '123.450000000000000000a' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Decimal must be a numeric value: "123.450000000000000000a"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Decimal throws error when input literal is a whitespace string', async () => { const value = ' ' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Decimal must be a numeric value: " "' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Decimal throws error when input literal is a string containing NaN', async () => { const value = 'NaN' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Decimal must be a numeric value: "NaN"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Decimal throws error when input literal is a string containing Infinity', async () => { const value = 'Infinity' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Decimal must be a numeric value: "Infinity"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -503,81 +531,80 @@ describe('graphql - types parsing and validation', () => { test('cds.Decimal is correctly parsed from variable string float value', async () => { const number = 123.45 const value = String(number) - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: number }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(String(result[field])).toEqual(value) }) test('cds.Decimal is correctly parsed from variable string int value', async () => { const number = 123 const value = String(number) - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: number }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(String(result[field])).toEqual(value) }) test('cds.Decimal correctly determines large numeric string variable to be a decimal number', async () => { const value = '12345678901234567890.01234567890123456789' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result.length).toBe(1) - expect(result[0][field]).toBeGreaterThan(12345678901234560000) // Incorrect sqlite value due to dynamic typing system and rounding errors + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(parseFloat(result[field])).toBeGreaterThan(12345678901234560000) // Incorrect sqlite value due to dynamic typing system and rounding errors }) test('cds.Decimal throws error when variable value is a float', async () => { - const value = 123.45 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const number = 123.45 + const body = _getMutationAndVariablesForFieldWithVariable(field, number) const message = 'Variable "$input" got invalid value 123.45 at "input.myDecimal"; Decimal variable value must be represented by a string: 123.45' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Decimal throws error when variable value is an int', async () => { - const value = 123 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const number = 123 + const body = _getMutationAndVariablesForFieldWithVariable(field, number) const message = 'Variable "$input" got invalid value 123 at "input.myDecimal"; Decimal variable value must be represented by a string: 123' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Decimal throws error when variable value is a whitespace string', async () => { const value = ' ' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value " " at "input.myDecimal"; Decimal must be a numeric value: " "' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Decimal throws error when variable value is a string containing NaN', async () => { const value = 'NaN' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "NaN" at "input.myDecimal"; Decimal must be a numeric value: "NaN"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Decimal throws error when variable value is a string containing Infinity', async () => { const value = 'Infinity' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "Infinity" at "input.myDecimal"; Decimal must be a numeric value: "Infinity"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -585,51 +612,52 @@ describe('graphql - types parsing and validation', () => { describe('cds.DecimalFloat', () => { const field = 'myDecimalFloat' - const value = 1234.567 + const number = 1234.567 + const value = String(number) test('cds.DecimalFloat is correctly parsed from input literal', async () => { - const query = _getMutationForFieldWithLiteralValue(field, value, false) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const body = _getMutationForFieldWithLiteralValue(field, number, false) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(String(result[field])).toEqual(value) }) test('cds.DecimalFloat is correctly parsed from variable value', async () => { - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const body = _getMutationAndVariablesForFieldWithVariable(field, number) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(String(result[field])).toEqual(value) }) }) describe('cds.Double', () => { const field = 'myDouble' - const value = 1234.567 + const number = 1234.567 test('cds.Double is correctly parsed from input literal', async () => { - const query = _getMutationForFieldWithLiteralValue(field, value, false) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const body = _getMutationForFieldWithLiteralValue(field, number, false) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(number) }) test('cds.Double is correctly parsed from variable value', async () => { - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const body = _getMutationAndVariablesForFieldWithVariable(field, number) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(number) }) }) @@ -638,111 +666,111 @@ describe('graphql - types parsing and validation', () => { describe('input literal', () => { test('cds.Int16 is correctly parsed from input literal int value', async () => { - const value = 32767 // Max Int16 - const query = _getMutationForFieldWithLiteralValue(field, value, false) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const number = 32767 // Max Int16 + const body = _getMutationForFieldWithLiteralValue(field, number, false) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(number) }) test('cds.Int16 throws error when input literal int value exceeds max value', async () => { - const value = 32768 // Max Int16 + 1 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const number = 32768 // Max Int16 + 1 + const body = _getMutationForFieldWithLiteralValue(field, number, false) const message = 'Int16 must be an integer value between -(2^15) and 2^15 - 1: 32768' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Int16 throws error when input literal int value exceeds min value', async () => { - const value = -32769 // Min Int16 - 1 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const number = -32769 // Min Int16 - 1 + const body = _getMutationForFieldWithLiteralValue(field, number, false) const message = 'Int16 must be an integer value between -(2^15) and 2^15 - 1: -32769' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Int16 throws error when input literal is not a number, but a numeric string', async () => { const value = '12345' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Int16 cannot represent non integer value: "12345"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Int16 throws error when input literal is not a number, but a non-numeric string', async () => { const value = 'bla' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Int16 cannot represent non integer value: "bla"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Int16 throws error when input literal is not a number, but a boolean', async () => { const value = false - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'Int16 cannot represent non integer value: false' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) describe('variable value', () => { test('cds.Int16 is correctly parsed from variable number value', async () => { - const value = 32767 // Max Int16 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const number = 32767 // Max Int16 + const body = _getMutationAndVariablesForFieldWithVariable(field, number) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(number) }) test('cds.Int16 throws error when variable number value exceeds max value', async () => { - const value = 32768 // Max Int16 + 1 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const number = 32768 // Max Int16 + 1 + const body = _getMutationAndVariablesForFieldWithVariable(field, number) const message = 'Variable "$input" got invalid value 32768 at "input.myInt16"; Int16 must be an integer value between -(2^15) and 2^15 - 1: 32768' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Int16 throws error when variable number value exceeds min value', async () => { - const value = -32769 // Min Int16 - 1 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const number = -32769 // Min Int16 - 1 + const body = _getMutationAndVariablesForFieldWithVariable(field, number) const message = 'Variable "$input" got invalid value -32769 at "input.myInt16"; Int16 must be an integer value between -(2^15) and 2^15 - 1: -32769' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Int16 throws error when variable value is not a number, but a numeric string', async () => { const value = '12345' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "12345" at "input.myInt16"; Int16 cannot represent non integer value: "12345"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Int16 throws error when variable value is not a number, but a non-numeric string', async () => { const value = 'bla' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "bla" at "input.myInt16"; Int16 cannot represent non integer value: "bla"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Int16 throws error when variable value is not a number, but a boolean', async () => { const value = false - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value false at "input.myInt16"; Int16 cannot represent non integer value: false' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -751,26 +779,26 @@ describe('graphql - types parsing and validation', () => { // Note: maps to same type as cds.Integer describe('cds.Int32', () => { const field = 'myInt32' - const value = 2147483647 + const number = 2147483647 test('cds.Int32 is correctly parsed from input literal', async () => { - const query = _getMutationForFieldWithLiteralValue(field, value, false) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const body = _getMutationForFieldWithLiteralValue(field, number, false) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(number) }) test('cds.Int32 is correctly parsed from variable value', async () => { - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const body = _getMutationAndVariablesForFieldWithVariable(field, number) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(number) }) }) @@ -780,49 +808,49 @@ describe('graphql - types parsing and validation', () => { test('cds.Int64 is correctly parsed from input literal int value', async () => { const value = '999999999999999' // Max Int64 = 9223372036854775807, but lower due to SQLite rounding errors - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.Int64 is correctly parsed from variable string value', async () => { const value = '999999999999999' // Max Int64 = 9223372036854775807, but lower due to SQLite rounding errors - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) }) describe('cds.Integer', () => { const field = 'myInteger' - const value = 2147483647 + const number = 2147483647 test('cds.Integer is correctly parsed from input literal', async () => { - const query = _getMutationForFieldWithLiteralValue(field, value, false) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const body = _getMutationForFieldWithLiteralValue(field, number, false) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(number) }) test('cds.Integer is correctly parsed from variable value', async () => { - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) - const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const body = _getMutationAndVariablesForFieldWithVariable(field, number) + const data = { TypesService: { MyEntity: { create: [{ [field]: number }] } } } + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(number) }) }) @@ -832,79 +860,79 @@ describe('graphql - types parsing and validation', () => { describe('input literal', () => { test('cds.Integer64 is correctly parsed from input literal int value', async () => { const value = '999999999999999' // Max Integer64 = 9223372036854775807, but lower due to SQLite rounding errors - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.Integer64 throws error when input literal int value exceeds max value', async () => { const value = '9223372036854775808' // Max Integer64 + 1 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'Int64 must be an integer value between -(2^63) and 2^63 - 1: 9223372036854775808' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 throws error when input literal int value exceeds min value', async () => { const value = '-9223372036854775809' // Min Integer64 - 1 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'Int64 must be an integer value between -(2^63) and 2^63 - 1: -9223372036854775809' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 is correctly parsed from input literal numeric string value', async () => { const value = '999999999999999' // Max Integer64 = 9223372036854775807, but lower due to SQLite rounding errors - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.Integer64 throws error when input literal string value exceeds max value', async () => { const value = '9223372036854775808' // Max Integer64 + 1 - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Int64 must be an integer value between -(2^63) and 2^63 - 1: "9223372036854775808"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 throws error when input literal string value exceeds min value', async () => { const value = '-9223372036854775809' // Min Integer64 - 1 - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Int64 must be an integer value between -(2^63) and 2^63 - 1: "-9223372036854775809"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 throws error when input literal is not a number, but a non-numeric string', async () => { const value = 'bla' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Int64 cannot represent non integer value: "bla"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 throws error when input literal is not a number, but a whitespace string', async () => { const value = ' ' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Int64 cannot represent non integer value: " "' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 throws error when input literal is not a number, but a boolean', async () => { const value = false - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'Int64 cannot represent non integer value: false' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -912,158 +940,152 @@ describe('graphql - types parsing and validation', () => { describe('variable value', () => { test('cds.Integer64 throws error when variable value is a number, due to potential rounding errors', async () => { const value = 123 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value 123 at "input.myInteger64"; Int64 variable value must be represented by a string: 123' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 is correctly parsed from variable string value', async () => { const value = '999999999999999' // Max Integer64 = 9223372036854775807, but lower due to SQLite rounding errors - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.Integer64 throws error when variable string value exceeds max value', async () => { const value = '9223372036854775808' // Max Integer64 + 1 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "9223372036854775808" at "input.myInteger64"; Int64 must be an integer value between -(2^63) and 2^63 - 1: 9223372036854775808' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 throws error when variable string value exceeds min value', async () => { const value = '-9223372036854775809' // Min Integer64 - 1 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "-9223372036854775809" at "input.myInteger64"; Int64 must be an integer value between -(2^63) and 2^63 - 1: -9223372036854775809' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 throws error when variable value is not a number, but a non-numeric string', async () => { const value = 'bla' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "bla" at "input.myInteger64"; Int64 cannot represent non integer value: "bla"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 throws error when variable value is not a number, but a whitespace string', async () => { const value = ' ' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value " " at "input.myInteger64"; Int64 cannot represent non integer value: " "' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Integer64 throws error when variable value is not a number, but a boolean', async () => { const value = false - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value false at "input.myInteger64"; Int64 variable value must be represented by a string: false' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) }) // Note: maps to same type as cds.Binary - // REVISIT: express-graphql limits request body size to 100kb by default: - // - https://github.com/graphql/express-graphql/issues/346 - // - https://github.com/graphql/express-graphql/blob/28e4c2924ea6984bf918465cefdadae340d8780e/src/parseBody.ts#L96 - describe.skip('cds.LargeBinary', () => { + describe('cds.LargeBinary', () => { const field = 'myLargeBinary' - const buffer = _getTestBuffer(500000) + const buffer = _getTestBuffer(_neededBufferLengthForBase64StringWithLength(105000)) // 105 KB as base64 string describe('input literal', () => { test('cds.LargeBinary is correctly parsed from large input literal base64 encoded string value', async () => { const value = buffer.toString('base64') - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: _toBase64Url(value) }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + const bufferFromDB = await consumers.buffer(result[field]) + expect(bufferFromDB).toEqual(buffer) }) test('cds.LargeBinary is correctly parsed from large input literal base64url encoded string value', async () => { const value = _toBase64Url(buffer.toString('base64')) - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + const bufferFromDB = await consumers.buffer(result[field]) + expect(bufferFromDB).toEqual(buffer) }) }) describe('variable value', () => { test('cds.LargeBinary is correctly parsed from large variable base64 encoded string value', async () => { const value = buffer.toString('base64') - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: _toBase64Url(value) }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + const bufferFromDB = await consumers.buffer(result[field]) + expect(bufferFromDB).toEqual(buffer) }) test('cds.LargeBinary is correctly parsed from large variable base64url encoded string value', async () => { const value = _toBase64Url(buffer.toString('base64')) - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: buffer }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + const bufferFromDB = await consumers.buffer(result[field]) + expect(bufferFromDB).toEqual(buffer) }) }) }) // Note: maps to same type as cds.String - // REVISIT: express-graphql limits request body size to 100kb by default: - // - https://github.com/graphql/express-graphql/issues/346 - // - https://github.com/graphql/express-graphql/blob/28e4c2924ea6984bf918465cefdadae340d8780e/src/parseBody.ts#L96 - describe.skip('cds.LargeString', () => { + describe('cds.LargeString', () => { const field = 'myLargeString' - const value = (() => { - let string = '' - for (let i = 0; i < 100000; i++) string += 'This is a test string! ' - return string - })() + const value = _getTestString(105000) // 105 KB test('cds.LargeString is correctly parsed from input literal', async () => { - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.LargeString is correctly parsed from variable value', async () => { - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) }) @@ -1072,23 +1094,23 @@ describe('graphql - types parsing and validation', () => { const value = 'This is a test string' test('cds.String is correctly parsed from input literal', async () => { - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.String is correctly parsed from variable value', async () => { - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) }) @@ -1098,36 +1120,36 @@ describe('graphql - types parsing and validation', () => { describe('input literal', () => { test('cds.Time is correctly parsed from input literal string value', async () => { const value = '07:59:59' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.Time throws error when input literal is a string containing an invalid time format value', async () => { const value = '99:99:99' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Time values must be strings in the ISO 8601 format hh:mm:ss: "99:99:99"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Time throws error when input literal is a string containing a non-time value', async () => { const value = 'bla' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Time values must be strings in the ISO 8601 format hh:mm:ss: "bla"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Time throws error when input literal is not a string, but an integer', async () => { const value = 123456 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'Time cannot represent non string value: 123456' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -1135,39 +1157,39 @@ describe('graphql - types parsing and validation', () => { describe('variable value', () => { test('cds.Time is correctly parsed from variable string value', async () => { const value = '07:59:59' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.Time throws error when variable is a string containing an invalid time format value', async () => { const value = '99:99:99' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "99:99:99" at "input.myTime"; Time values must be strings in the ISO 8601 format hh:mm:ss: "99:99:99"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Time throws error when variable is a string containing a non-time value', async () => { const value = 'bla' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "bla" at "input.myTime"; Time values must be strings in the ISO 8601 format hh:mm:ss: "bla"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Time throws error when variable value is not a string, but an integer', async () => { const value = 123456 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value 123456 at "input.myTime"; Time cannot represent non string value: 123456' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -1180,41 +1202,41 @@ describe('graphql - types parsing and validation', () => { 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\.123(0000)?Z/ // timestamp precision increase with cds^7 - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: expect.stringMatching(returnValue) }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: expect.stringMatching(returnValue) }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toMatch(returnValue) }) test('cds.Timestamp is correctly parsed from input literal high precision timestamp string value', async () => { const value = '2021-06-27T14:52:23.1234567Z' const returnValue = /2021-06-27T14:52:23\.123(4567)?Z/ // timestamp precision increase with cds^7 - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: expect.stringMatching(returnValue) }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: expect.stringMatching(returnValue) }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toMatch(returnValue) }) test('cds.Timestamp throws error when input literal is a string containing a non-time value', async () => { const value = 'bla' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'Timestamp values must be strings in the ISO 8601 format YYYY-MM-DDThh-mm-ss.sTZD with up to 7 digits of fractional seconds: "bla"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Timestamp throws error when input literal is not a string, but an integer', async () => { const value = 123456 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'Timestamp cannot represent non string value: 123456' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -1223,42 +1245,42 @@ describe('graphql - types parsing and validation', () => { 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\.123(0000)?Z/ // timestamp precision increase with cds^7 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: expect.stringMatching(returnValue) }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: expect.stringMatching(returnValue) }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toMatch(returnValue) }) test('cds.Timestamp is correctly parsed from variable high precision timestamp string value', async () => { const value = '2021-06-27T14:52:23.1234567Z' const returnValue = /2021-06-27T14:52:23\.123(4567)?Z/ // timestamp precision increase with cds^7 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: expect.stringMatching(returnValue) }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: expect.stringMatching(returnValue) }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toMatch(returnValue) }) test('cds.Timestamp throws error when variable is a string containing a non-timestamp value', async () => { const value = 'bla' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "bla" at "input.myTimestamp"; Timestamp values must be strings in the ISO 8601 format YYYY-MM-DDThh-mm-ss.sTZD with up to 7 digits of fractional seconds: "bla"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.Timestamp throws error when variable value is not a string, but an integer', async () => { const value = 123456 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value 123456 at "input.myTimestamp"; Timestamp cannot represent non string value: 123456' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -1270,52 +1292,52 @@ describe('graphql - types parsing and validation', () => { describe('input literal', () => { test('cds.UInt8 is correctly parsed from input literal int value', async () => { const value = 255 // Max UInt8 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.UInt8 throws error when input literal int value exceeds max value', async () => { const value = 256 // Max UInt8 + 1 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'UInt8 must be an integer value between 0 and 2^8 - 1: 256' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.UInt8 throws error when input literal int value exceeds min value', async () => { const value = -1 // Min UInt8 - 1 - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'UInt8 must be an integer value between 0 and 2^8 - 1: -1' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.UInt8 throws error when input literal is not a number, but a numeric string', async () => { const value = '123' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'UInt8 cannot represent non integer value: "123"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.UInt8 throws error when input literal is not a number, but a non-numeric string', async () => { const value = 'bla' - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const message = 'UInt8 cannot represent non integer value: "bla"' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.UInt8 throws error when input literal is not a number, but a boolean', async () => { const value = false - const query = _getMutationForFieldWithLiteralValue(field, value, false) + const body = _getMutationForFieldWithLiteralValue(field, value, false) const message = 'UInt8 cannot represent non integer value: false' - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -1323,57 +1345,57 @@ describe('graphql - types parsing and validation', () => { describe('variable value', () => { test('cds.UInt8 is correctly parsed from variable number value', async () => { const value = 255 // Max UInt8 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.UInt8 throws error when variable number value exceeds max value', async () => { const value = 256 // Max UInt8 + 1 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value 256 at "input.myUInt8"; UInt8 must be an integer value between 0 and 2^8 - 1: 256' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.UInt8 throws error when variable number value exceeds min value', async () => { const value = -1 // Min UInt8 - 1 - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value -1 at "input.myUInt8"; UInt8 must be an integer value between 0 and 2^8 - 1: -1' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.UInt8 throws error when variable value is not a number, but a numeric string', async () => { const value = '123' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "123" at "input.myUInt8"; UInt8 cannot represent non integer value: "123"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.UInt8 throws error when variable value is not a number, but a non-numeric string', async () => { const value = 'bla' - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value "bla" at "input.myUInt8"; UInt8 cannot represent non integer value: "bla"' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) test('cds.UInt8 throws error when variable value is not a number, but a boolean', async () => { const value = false - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const message = 'Variable "$input" got invalid value false at "input.myUInt8"; UInt8 cannot represent non integer value: false' - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data.errors[0].message).toEqual(message) }) }) @@ -1384,23 +1406,23 @@ describe('graphql - types parsing and validation', () => { const value = 'a94f80eb-0a8e-4a12-b02f-0c1747200bf0' test('cds.UUID is correctly parsed from input literal', async () => { - const query = _getMutationForFieldWithLiteralValue(field, value, true) + const body = _getMutationForFieldWithLiteralValue(field, value, true) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) test('cds.UUID is correctly parsed from variable value', async () => { - const { query, variables } = _getMutationAndVariablesForFieldWithVariable(field, value) + const body = _getMutationAndVariablesForFieldWithVariable(field, value) const data = { TypesService: { MyEntity: { create: [{ [field]: value }] } } } - const response = await POST('/graphql', { query, variables }) + const response = await POST('/graphql', body) expect(response.data).toEqual({ data }) - const result = await SELECT.from('sap.cds.graphql.types.MyEntity').columns(field) - expect(result).toContainEqual({ [field]: value }) + const result = await SELECT.one.from('sap.cds.graphql.types.MyEntity').columns(field) + expect(result[field]).toEqual(value) }) }) })