From 0843e8d7f3f46b93d303ac646f39ba0e5d05f0da Mon Sep 17 00:00:00 2001 From: John Corser Date: Fri, 21 May 2021 03:07:11 +0000 Subject: [PATCH] fix(graphql-transformer-common): improve generated graphql pluralization This commit adds the `pluralize` npm library instead of blindly appending an "s" to acheive pluralization. For example, a model named "Match" should pluralize to "Matches", but before this commit Amplify would pluralize it to "Matchs" fix #4224 --- .../commands/api/add-graphql-datasource.js | 9 +- .../src/feature-flags/featureFlags.ts | 8 +- ...fy-graphql-searchable-transformer.tests.ts | 18 +++ .../src/graphql-searchable-transformer.ts | 4 +- .../src/transformation/transform.ts | 4 +- .../src/transformer-context/index.ts | 13 +- .../transform-graphql-schema.ts | 2 +- .../src/ModelAuthTransformer.ts | 8 +- .../src/ModelDirectiveConfiguration.ts | 6 +- .../src/__tests__/AmplifyAdminAuth.test.ts | 20 ++- .../__tests__/GroupAuthTransformer.test.ts | 21 +++- .../src/__tests__/MultiAuth.test.ts | 18 ++- .../__tests__/NonModelAuthTransformer.test.ts | 19 +++ .../src/__tests__/OperationsArgument.test.ts | 17 +++ .../__tests__/OwnerAuthTransformer.test.ts | 18 ++- .../__tests__/PerFieldAuthArgument.test.ts | 19 ++- .../SearchableAuthTransformer.test.ts | 17 ++- .../ModelConnectionTransformer.test.ts | 35 +++++- .../NewConnectionTransformer.test.ts | 32 +++++ .../src/DynamoDBModelTransformer.ts | 8 +- .../DynamoDBModelTransformer.test.ts | 39 ++++++ .../DynamoDBModelTransformer.test.ts.snap | 2 +- .../src/resources.ts | 10 +- .../src/SearchableModelTransformer.ts | 1 + .../SearchableModelTransformer.test.ts | 17 +++ .../src/resources.ts | 3 +- .../src/__tests__/KeyTransformer.test.ts | 52 +++++--- .../src/RelationalDBResolverGenerator.ts | 8 +- .../src/RelationalDBTemplateGenerator.ts | 4 +- .../RelationalDBResolverGenerator.test.ts | 45 +++++-- .../RelationalDBTemplateGenerator.test.ts | 2 +- ...RelationalDBResolverGenerator.test.ts.snap | 116 +++++++++++++++++- .../graphql-transformer-common/package.json | 3 +- .../graphql-transformer-common/src/util.ts | 9 +- .../VersionedModelTransformer.test.ts | 15 ++- yarn.lock | 2 +- 36 files changed, 543 insertions(+), 81 deletions(-) diff --git a/packages/amplify-category-api/src/commands/api/add-graphql-datasource.js b/packages/amplify-category-api/src/commands/api/add-graphql-datasource.js index 957fdce03e9..415da07562f 100644 --- a/packages/amplify-category-api/src/commands/api/add-graphql-datasource.js +++ b/packages/amplify-category-api/src/commands/api/add-graphql-datasource.js @@ -5,8 +5,7 @@ const path = require('path'); const { RelationalDBSchemaTransformer } = require('graphql-relational-schema-transformer'); const { RelationalDBTemplateGenerator, AuroraServerlessMySQLDatabaseReader } = require('graphql-relational-schema-transformer'); const { mergeTypeDefs } = require('@graphql-tools/merge'); -const { ResourceDoesNotExistError, exitOnNextTick } = require('amplify-cli-core'); - +const { FeatureFlags, ResourceDoesNotExistError, exitOnNextTick } = require('amplify-cli-core'); const subcommand = 'add-graphql-datasource'; const categories = 'categories'; const category = 'api'; @@ -143,7 +142,11 @@ module.exports = { context[rdsResourceName] = resourceName; context[rdsDatasource] = datasource; let template = templateGenerator.createTemplate(context); - template = templateGenerator.addRelationalResolvers(template, resolversDir); + template = templateGenerator.addRelationalResolvers( + template, + resolversDir, + FeatureFlags.getBoolean('graphqltransformer.improvePluralization'), + ); const cfn = templateGenerator.printCloudformationTemplate(template); /** diff --git a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts index 2e930666235..47abc0c880f 100644 --- a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts +++ b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts @@ -515,6 +515,12 @@ export class FeatureFlags { defaultValueForExistingProjects: false, defaultValueForNewProjects: true, }, + { + name: 'improvePluralization', + type: 'boolean', + defaultValueForExistingProjects: false, + defaultValueForNewProjects: true, + }, { name: 'validateTypeNameReservedWords', type: 'boolean', @@ -619,7 +625,7 @@ export class FeatureFlags { type: 'boolean', defaultValueForExistingProjects: false, defaultValueForNewProjects: true, - } + }, ]); this.registerFlag('appSync', [ diff --git a/packages/amplify-graphql-searchable-transformer/src/__tests__/amplify-graphql-searchable-transformer.tests.ts b/packages/amplify-graphql-searchable-transformer/src/__tests__/amplify-graphql-searchable-transformer.tests.ts index 613c4c0ab04..45be2e58cf8 100644 --- a/packages/amplify-graphql-searchable-transformer/src/__tests__/amplify-graphql-searchable-transformer.tests.ts +++ b/packages/amplify-graphql-searchable-transformer/src/__tests__/amplify-graphql-searchable-transformer.tests.ts @@ -3,6 +3,17 @@ import { SearchableModelTransformer } from '../'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { anything, countResources, expect as cdkExpect, haveResource } from '@aws-cdk/assert'; import { parse } from 'graphql'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test SearchableModelTransformer validation happy case', () => { const validSchema = ` @@ -15,6 +26,7 @@ test('Test SearchableModelTransformer validation happy case', () => { `; const transformer = new GraphQLTransform({ transformers: [new ModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -33,6 +45,7 @@ test('Test SearchableModelTransformer vtl', () => { `; const transformer = new GraphQLTransform({ transformers: [new ModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); @@ -50,6 +63,7 @@ test('Test SearchableModelTransformer with query overrides', () => { `; const transformer = new GraphQLTransform({ transformers: [new ModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -67,6 +81,7 @@ test('Test SearchableModelTransformer with only create mutations', () => { `; const transformer = new GraphQLTransform({ transformers: [new ModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -90,6 +105,7 @@ test('Test SearchableModelTransformer with multiple model searchable directives' `; const transformer = new GraphQLTransform({ transformers: [new ModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -108,6 +124,7 @@ test('Test SearchableModelTransformer with sort fields', () => { `; const transformer = new GraphQLTransform({ transformers: [new ModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -137,6 +154,7 @@ test('it generates expected resources', () => { `; const transformer = new GraphQLTransform({ transformers: [new ModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); diff --git a/packages/amplify-graphql-searchable-transformer/src/graphql-searchable-transformer.ts b/packages/amplify-graphql-searchable-transformer/src/graphql-searchable-transformer.ts index db39d2f9cf3..ef36cd4827f 100644 --- a/packages/amplify-graphql-searchable-transformer/src/graphql-searchable-transformer.ts +++ b/packages/amplify-graphql-searchable-transformer/src/graphql-searchable-transformer.ts @@ -158,7 +158,9 @@ export class SearchableModelTransformer extends TransformerPluginBase { searchFieldNameOverride = directiveArguments.queries.search; } } - const fieldName = searchFieldNameOverride ? searchFieldNameOverride : graphqlName(`search${plurality(toUpper(definition.name.value))}`); + const fieldName = searchFieldNameOverride + ? searchFieldNameOverride + : graphqlName(`search${plurality(toUpper(definition.name.value), ctx.featureFlags.getBoolean('improvePluralization'))}`); this.searchableObjectTypeDefinitions.push({ node: definition, fieldName, diff --git a/packages/amplify-graphql-transformer-core/src/transformation/transform.ts b/packages/amplify-graphql-transformer-core/src/transformation/transform.ts index 0bd632467b9..3442ce2d12c 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/transform.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/transform.ts @@ -61,7 +61,7 @@ export interface GraphQLTransformOptions { readonly authConfig?: AppSyncAuthConfiguration; readonly buildParameters?: Record; readonly stacks?: Record; - readonly featuerFlags?: FeatureFlagProvider; + readonly featureFlags?: FeatureFlagProvider; } export type StackMapping = { [resourceId: string]: string }; export class GraphQLTransform { @@ -110,7 +110,7 @@ export class GraphQLTransform { this.seenTransformations = {}; const parsedDocument = parse(schema); this.app = new App(); - const context = new TransformerContext(this.app, parsedDocument, this.stackMappingOverrides, this.options.featuerFlags); + const context = new TransformerContext(this.app, parsedDocument, this.stackMappingOverrides, this.options.featureFlags); const validDirectiveNameMap = this.transformers.reduce( (acc: any, t: TransformerPluginProvider) => ({ ...acc, [t.directive.name.value]: true }), { diff --git a/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts b/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts index 5a3a2ef5959..002893fa58c 100644 --- a/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts +++ b/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts @@ -26,15 +26,20 @@ export class TransformerContext implements TransformerContextProvider { public readonly resourceHelper: TransformerResourceHelper; public readonly featureFlags: FeatureFlagProvider; public _api?: GraphQLAPIProvider; - constructor(app: App, public readonly inputDocument: DocumentNode, stackMapping: Record, featuerFlags?: FeatureFlagProvider) { + constructor( + app: App, + public readonly inputDocument: DocumentNode, + stackMapping: Record, + featureFlags?: FeatureFlagProvider, + ) { this.output = new TransformerOutput(inputDocument); this.resolvers = new ResolverManager(); this.dataSources = new TransformerDataSourceManager(); this.providerRegistry = new TransformerContextProviderRegistry(); - const stackManager = new StackManager(app, stackMapping); - this.stackManager = stackManager + const stackManager = new StackManager(app, stackMapping); + this.stackManager = stackManager; this.resourceHelper = new TransformerResourceHelper(stackManager); - this.featureFlags = featuerFlags ?? new NoopFeatureFlagProvider(); + this.featureFlags = featureFlags ?? new NoopFeatureFlagProvider(); } /** diff --git a/packages/amplify-provider-awscloudformation/src/graphql-transformer/transform-graphql-schema.ts b/packages/amplify-provider-awscloudformation/src/graphql-transformer/transform-graphql-schema.ts index e9c01823397..8b27d65556b 100644 --- a/packages/amplify-provider-awscloudformation/src/graphql-transformer/transform-graphql-schema.ts +++ b/packages/amplify-provider-awscloudformation/src/graphql-transformer/transform-graphql-schema.ts @@ -414,7 +414,7 @@ async function _buildProject(opts: ProjectOptions) { authConfig: opts.authConfig, buildParameters: opts.buildParameters, stacks: opts.projectConfig.stacks || {}, - featuerFlags: new AmplifyCLIFeatureFlagAdapter(), + featureFlags: new AmplifyCLIFeatureFlagAdapter(), }); return transform.transform(userProjectConfig.schema.toString()); } diff --git a/packages/graphql-auth-transformer/src/ModelAuthTransformer.ts b/packages/graphql-auth-transformer/src/ModelAuthTransformer.ts index 7ac88f2106d..920b858e08a 100644 --- a/packages/graphql-auth-transformer/src/ModelAuthTransformer.ts +++ b/packages/graphql-auth-transformer/src/ModelAuthTransformer.ts @@ -368,7 +368,7 @@ export class ModelAuthTransformer extends Transformer { this.propagateAuthDirectivesToNestedTypes(def, rules, ctx); // Retrieve the configuration options for the related @model directive - const modelConfiguration = new ModelDirectiveConfiguration(modelDirective, def); + const modelConfiguration = new ModelDirectiveConfiguration(modelDirective, def, ctx.featureFlags.getBoolean('improvePluralization')); // Get the directives we need to add to the GraphQL nodes const directives = this.getDirectivesForRules(rules, rules.length === 0 ? this.shouldAddDefaultAuthDirective() : false); @@ -516,7 +516,11 @@ Static group authorization should perform as expected.`, const isDeleteRule = isOpRule('delete'); // Retrieve the configuration options for the related @model directive - const modelConfiguration = new ModelDirectiveConfiguration(modelDirective, parent); + const modelConfiguration = new ModelDirectiveConfiguration( + modelDirective, + parent, + ctx.featureFlags.getBoolean('improvePluralization'), + ); // The field handler adds the read rule on the object const readRules = rules.filter((rule: AuthRule) => isReadRule(rule)); this.protectReadForField(ctx, parent, definition, readRules, modelConfiguration); diff --git a/packages/graphql-auth-transformer/src/ModelDirectiveConfiguration.ts b/packages/graphql-auth-transformer/src/ModelDirectiveConfiguration.ts index 744718a7f52..44b11846f52 100644 --- a/packages/graphql-auth-transformer/src/ModelDirectiveConfiguration.ts +++ b/packages/graphql-auth-transformer/src/ModelDirectiveConfiguration.ts @@ -41,12 +41,14 @@ type ModelDirectiveOperation = { export class ModelDirectiveConfiguration { map: Map = new Map(); - constructor(directive: DirectiveNode, def: ObjectTypeDefinitionNode) { + constructor(directive: DirectiveNode, def: ObjectTypeDefinitionNode, improvePluralization: boolean) { const typeName = def.name.value; const directiveArguments: ModelDirectiveArgs = getDirectiveArguments(directive); const makeName = (operation: ModelDirectiveOperationType, nameOverride?: string, isList: boolean = false) => - nameOverride ? nameOverride : graphqlName(operation + (isList ? plurality(toUpper(typeName)) : toUpper(typeName))); + nameOverride + ? nameOverride + : graphqlName(operation + (isList ? plurality(toUpper(typeName), improvePluralization) : toUpper(typeName))); let shouldHaveCreate = true; let shouldHaveUpdate = true; diff --git a/packages/graphql-auth-transformer/src/__tests__/AmplifyAdminAuth.test.ts b/packages/graphql-auth-transformer/src/__tests__/AmplifyAdminAuth.test.ts index e4ab14130b1..a5e3bf380d7 100644 --- a/packages/graphql-auth-transformer/src/__tests__/AmplifyAdminAuth.test.ts +++ b/packages/graphql-auth-transformer/src/__tests__/AmplifyAdminAuth.test.ts @@ -1,8 +1,18 @@ import { GraphQLTransform } from 'graphql-transformer-core'; -import { ResourceConstants } from 'graphql-transformer-common'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { ModelAuthTransformer } from '../ModelAuthTransformer'; import _ from 'lodash'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test simple model with public auth rule and amplify admin app is present', () => { const validSchema = ` @@ -14,6 +24,7 @@ test('Test simple model with public auth rule and amplify admin app is present', } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -46,6 +57,7 @@ test('Test simple model with public auth rule and amplify admin app is not enabl } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -74,6 +86,7 @@ test('Test simple model with private auth rule and amplify admin app is present' } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -106,6 +119,7 @@ test('Test simple model with private auth rule and amplify admin app not enabled } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -138,6 +152,7 @@ test('Test model with public auth rule without all operations and amplify admin } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -182,6 +197,7 @@ test('Test simple model with private auth rule, few operations, and amplify admi } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -225,6 +241,7 @@ test('Test simple model with private IAM auth rule, few operations, and amplify } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -263,6 +280,7 @@ test('Test simple model with AdminUI enabled should add IAM policy only for fiel } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ diff --git a/packages/graphql-auth-transformer/src/__tests__/GroupAuthTransformer.test.ts b/packages/graphql-auth-transformer/src/__tests__/GroupAuthTransformer.test.ts index a1cd98a8a01..d184c517fd3 100644 --- a/packages/graphql-auth-transformer/src/__tests__/GroupAuthTransformer.test.ts +++ b/packages/graphql-auth-transformer/src/__tests__/GroupAuthTransformer.test.ts @@ -2,6 +2,17 @@ import { GraphQLTransform } from 'graphql-transformer-core'; import { ResourceConstants } from 'graphql-transformer-common'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { ModelAuthTransformer } from '../ModelAuthTransformer'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test ModelAuthTransformer validation happy case w/ static groups', () => { const validSchema = ` @@ -13,6 +24,7 @@ test('Test ModelAuthTransformer validation happy case w/ static groups', () => { } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -28,7 +40,7 @@ test('Test ModelAuthTransformer validation happy case w/ static groups', () => { const out = transformer.transform(validSchema); expect(out).toBeDefined(); expect(out.rootStack.Resources[ResourceConstants.RESOURCES.GraphQLAPILogicalID].Properties.AuthenticationType).toEqual( - 'AMAZON_COGNITO_USER_POOLS' + 'AMAZON_COGNITO_USER_POOLS', ); }); @@ -43,6 +55,7 @@ test('Test ModelAuthTransformer validation happy case w/ dynamic groups', () => } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -58,7 +71,7 @@ test('Test ModelAuthTransformer validation happy case w/ dynamic groups', () => const out = transformer.transform(validSchema); expect(out).toBeDefined(); expect(out.rootStack.Resources[ResourceConstants.RESOURCES.GraphQLAPILogicalID].Properties.AuthenticationType).toEqual( - 'AMAZON_COGNITO_USER_POOLS' + 'AMAZON_COGNITO_USER_POOLS', ); }); @@ -73,6 +86,7 @@ test('Test ModelAuthTransformer validation happy case w/ dynamic group', () => { } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -88,7 +102,7 @@ test('Test ModelAuthTransformer validation happy case w/ dynamic group', () => { const out = transformer.transform(validSchema); expect(out).toBeDefined(); expect(out.rootStack.Resources[ResourceConstants.RESOURCES.GraphQLAPILogicalID].Properties.AuthenticationType).toEqual( - 'AMAZON_COGNITO_USER_POOLS' + 'AMAZON_COGNITO_USER_POOLS', ); }); @@ -104,6 +118,7 @@ test('Test ModelAuthTransformer validation @auth on non @model. Should fail.', ( } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ diff --git a/packages/graphql-auth-transformer/src/__tests__/MultiAuth.test.ts b/packages/graphql-auth-transformer/src/__tests__/MultiAuth.test.ts index 4287ce66c12..63e097c1f52 100644 --- a/packages/graphql-auth-transformer/src/__tests__/MultiAuth.test.ts +++ b/packages/graphql-auth-transformer/src/__tests__/MultiAuth.test.ts @@ -4,6 +4,17 @@ import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { ModelConnectionTransformer } from 'graphql-connection-transformer'; import { ModelAuthTransformer, AppSyncAuthConfiguration, AppSyncAuthMode } from '../ModelAuthTransformer'; import { KeyTransformer } from '../../../graphql-key-transformer/src/KeyTransformer'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; const noAuthModeDefaultConfig: AppSyncAuthConfiguration = { defaultAuthentication: { @@ -180,6 +191,7 @@ const getRecursiveSchemaWithDiffModesOnParentType = (authDir1: string, authDir2: const getTransformer = (authConfig: AppSyncAuthConfiguration) => new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new KeyTransformer(), @@ -730,7 +742,7 @@ describe('Type directive transformation tests', () => { @auth(rules: [ { allow: private, provider: iam, operations: [read] }, { allow: groups, groups: ["Group"], operations: [read, update, delete] } - ]) + ]) @key(name: "byUser", fields: ["postUserId"]) { id: ID! @@ -768,7 +780,7 @@ describe('Type directive transformation tests', () => { title: String! editors: [PostEditor] @connection(keyName: "byPost", fields: ["id"]) } - + # Create a join model and disable queries as you don't need them # and can query through Post.editors and User.posts type PostEditor @@ -782,7 +794,7 @@ describe('Type directive transformation tests', () => { post: Post! @connection(fields: ["postID"]) editor: User! @connection(fields: ["editorID"]) } - + type User @model @auth(rules: [{ allow: owner }]) { id: ID! username: String! diff --git a/packages/graphql-auth-transformer/src/__tests__/NonModelAuthTransformer.test.ts b/packages/graphql-auth-transformer/src/__tests__/NonModelAuthTransformer.test.ts index 8b523ebc4a4..f3702bb5dfc 100644 --- a/packages/graphql-auth-transformer/src/__tests__/NonModelAuthTransformer.test.ts +++ b/packages/graphql-auth-transformer/src/__tests__/NonModelAuthTransformer.test.ts @@ -3,6 +3,17 @@ import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { FunctionTransformer } from 'graphql-function-transformer'; import { ModelAuthTransformer } from '../ModelAuthTransformer'; import { ObjectTypeDefinitionNode, DocumentNode, Kind, parse } from 'graphql'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; describe('@auth directive without @model', async () => { const getObjectType = (doc: DocumentNode, type: string): ObjectTypeDefinitionNode | undefined => { @@ -36,6 +47,7 @@ describe('@auth directive without @model', async () => { `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -70,6 +82,7 @@ describe('@auth directive without @model', async () => { `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -104,6 +117,7 @@ operations will be generated by the CLI.`); `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -137,6 +151,7 @@ are already on an operation already.`); `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -174,6 +189,7 @@ are already on an operation already.`); `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -212,6 +228,7 @@ are already on an operation already.`); `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -251,6 +268,7 @@ are already on an operation already.`); `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new FunctionTransformer(), @@ -289,6 +307,7 @@ are already on an operation already.`); `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new FunctionTransformer(), diff --git a/packages/graphql-auth-transformer/src/__tests__/OperationsArgument.test.ts b/packages/graphql-auth-transformer/src/__tests__/OperationsArgument.test.ts index a50edbac8ee..d0fbf598632 100644 --- a/packages/graphql-auth-transformer/src/__tests__/OperationsArgument.test.ts +++ b/packages/graphql-auth-transformer/src/__tests__/OperationsArgument.test.ts @@ -2,6 +2,17 @@ import { GraphQLTransform } from 'graphql-transformer-core'; import { ResourceConstants } from 'graphql-transformer-common'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { ModelAuthTransformer } from '../ModelAuthTransformer'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test "read" auth operation', () => { const validSchema = ` @@ -13,6 +24,7 @@ test('Test "read" auth operation', () => { } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -44,6 +56,7 @@ test('Test "create", "update", "delete" auth operations', () => { } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -83,6 +96,7 @@ test('Test that operation overwrites queries in auth operations', () => { } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -124,6 +138,7 @@ test('Test that checks subscription resolvers are generated with auth logic', () } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -164,6 +179,7 @@ test('Test that checks subscription resolvers are created without auth logic', ( } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -207,6 +223,7 @@ test('Test that subscriptions are only generated if the respective mutation oper } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ diff --git a/packages/graphql-auth-transformer/src/__tests__/OwnerAuthTransformer.test.ts b/packages/graphql-auth-transformer/src/__tests__/OwnerAuthTransformer.test.ts index 62caf321a08..a0314833991 100644 --- a/packages/graphql-auth-transformer/src/__tests__/OwnerAuthTransformer.test.ts +++ b/packages/graphql-auth-transformer/src/__tests__/OwnerAuthTransformer.test.ts @@ -1,9 +1,20 @@ -import { parse, DefinitionNode, InputObjectTypeDefinitionNode } from 'graphql'; +import { parse } from 'graphql'; import { FeatureFlagProvider, GraphQLTransform } from 'graphql-transformer-core'; import { ResourceConstants } from 'graphql-transformer-common'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { ModelAuthTransformer } from '../ModelAuthTransformer'; import { getObjectType, getField } from './test-helpers'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test ModelAuthTransformer validation happy case', () => { const validSchema = ` @@ -15,6 +26,7 @@ test('Test ModelAuthTransformer validation happy case', () => { } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -46,6 +58,7 @@ test('Test OwnerField with Subscriptions', () => { postOwner: String }`; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -102,6 +115,9 @@ describe('add missing implicit owner fields to type', () => { if (name === 'addMissingOwnerFields') { return true; } + if (name === 'improvePluralization') { + return true; + } }), getNumber: jest.fn(), getObject: jest.fn(), diff --git a/packages/graphql-auth-transformer/src/__tests__/PerFieldAuthArgument.test.ts b/packages/graphql-auth-transformer/src/__tests__/PerFieldAuthArgument.test.ts index 6a5237b6df5..aae4910f688 100644 --- a/packages/graphql-auth-transformer/src/__tests__/PerFieldAuthArgument.test.ts +++ b/packages/graphql-auth-transformer/src/__tests__/PerFieldAuthArgument.test.ts @@ -4,7 +4,17 @@ import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { ModelConnectionTransformer } from 'graphql-connection-transformer'; import { KeyTransformer } from 'graphql-key-transformer'; import { ModelAuthTransformer } from '../ModelAuthTransformer'; - +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test that subscriptions are only generated if the respective mutation operation exists', () => { const validSchema = ` type Salary @@ -19,6 +29,7 @@ test('Test that subscriptions are only generated if the respective mutation oper secret: String @auth(rules: [{allow: owner}]) }`; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ @@ -61,14 +72,14 @@ test('Test per-field @auth on a @connection field', () => { { id: ID! name: String! - tags: [Tag] + tags: [Tag] @connection(keyName: "byTags", fields: ["id"]) @auth(rules: [ { allow: groups, groups: ["admin"] } ]) } type Tag @model @key(name: "byTags", fields: ["postID"]) - @auth(rules: [ { allow: groups, groups: ["admin"] } ]) + @auth(rules: [ { allow: groups, groups: ["admin"] } ]) { id: ID! postID: ID! @@ -78,6 +89,7 @@ test('Test per-field @auth on a @connection field', () => { } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new KeyTransformer(), @@ -113,6 +125,7 @@ test('Test per-field @auth without model', () => { `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new ModelAuthTransformer({ diff --git a/packages/graphql-auth-transformer/src/__tests__/SearchableAuthTransformer.test.ts b/packages/graphql-auth-transformer/src/__tests__/SearchableAuthTransformer.test.ts index 1d911ad1240..20c3ffdf08d 100644 --- a/packages/graphql-auth-transformer/src/__tests__/SearchableAuthTransformer.test.ts +++ b/packages/graphql-auth-transformer/src/__tests__/SearchableAuthTransformer.test.ts @@ -2,6 +2,17 @@ import { GraphQLTransform } from 'graphql-transformer-core'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { ModelAuthTransformer } from '../ModelAuthTransformer'; import { SearchableModelTransformer } from 'graphql-elasticsearch-transformer'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('test auth logic is enabled on owner/static rules in resposne es resolver', () => { const validSchema = ` @@ -17,6 +28,7 @@ test('test auth logic is enabled on owner/static rules in resposne es resolver', } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new SearchableModelTransformer(), @@ -35,11 +47,11 @@ test('test auth logic is enabled on owner/static rules in resposne es resolver', // expect response resolver to contain auth logic for owner rule expect(out).toBeDefined(); expect(out.resolvers['Query.searchComments.res.vtl']).toContain( - '## Authorization rule: { allow: owner, ownerField: "owner", identityClaim: "cognito:username" } **' + '## Authorization rule: { allow: owner, ownerField: "owner", identityClaim: "cognito:username" } **', ); // expect response resolver to contain auth logic for group rule expect(out.resolvers['Query.searchComments.res.vtl']).toContain( - '## Authorization rule: { allow: groups, groups: ["writer"], groupClaim: "cognito:groups" } **' + '## Authorization rule: { allow: groups, groups: ["writer"], groupClaim: "cognito:groups" } **', ); }); @@ -57,6 +69,7 @@ test('test auth logic is enabled for iam/apiKey auth rules in response es resolv } `; const transformer = new GraphQLTransform({ + featureFlags, transformers: [ new DynamoDBModelTransformer(), new SearchableModelTransformer(), diff --git a/packages/graphql-connection-transformer/src/__tests__/ModelConnectionTransformer.test.ts b/packages/graphql-connection-transformer/src/__tests__/ModelConnectionTransformer.test.ts index 911508be9f9..c4c7bef91bb 100644 --- a/packages/graphql-connection-transformer/src/__tests__/ModelConnectionTransformer.test.ts +++ b/packages/graphql-connection-transformer/src/__tests__/ModelConnectionTransformer.test.ts @@ -12,6 +12,17 @@ import { GraphQLTransform, TRANSFORM_CURRENT_VERSION } from 'graphql-transformer import { ResolverResourceIDs, ModelResourceIDs, ResourceConstants } from 'graphql-transformer-common'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { ModelConnectionTransformer } from '../ModelConnectionTransformer'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test ModelConnectionTransformer simple one to many happy case', () => { const validSchema = ` @@ -27,6 +38,7 @@ test('Test ModelConnectionTransformer simple one to many happy case', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -67,6 +79,7 @@ test('Test ModelConnectionTransformer simple one to many happy case with custom `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -107,6 +120,7 @@ test('Test that ModelConnection Transformer throws error when the field in conne `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); try { transformer.transform(invalidSchema); @@ -135,6 +149,7 @@ test('Test ModelConnectionTransformer simple one to many happy case with custom `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -178,6 +193,7 @@ test('Test ModelConnectionTransformer complex one to many happy case', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -226,6 +242,7 @@ test('Test ModelConnectionTransformer many to many should fail', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); try { transformer.transform(validSchema); @@ -254,6 +271,7 @@ test('Test ModelConnectionTransformer many to many should fail due to missing ot `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); try { transformer.transform(validSchema); @@ -278,6 +296,7 @@ test('Test ModelConnectionTransformer many to many should fail due to missing ot `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -320,6 +339,7 @@ test('Test ModelConnectionTransformer with non null @connections', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -373,6 +393,7 @@ test('Test ModelConnectionTransformer with sortField fails if not specified in a `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => { transformer.transform(validSchema); @@ -395,6 +416,7 @@ test('Test ModelConnectionTransformer with sortField creates a connection resolv `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -412,6 +434,7 @@ test('Test ModelConnectionTransformer with sortField creates a connection resolv test('Test ModelConnectionTransformer throws with invalid key fields', () => { const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const invalidSchema = ` @@ -469,6 +492,7 @@ test('Test ModelConnectionTransformer throws with invalid key fields', () => { test('Test ModelConnectionTransformer does not throw with valid key fields', () => { const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const validSchema = ` @@ -541,6 +565,7 @@ test('Test ModelConnectionTransformer sortField with missing @key should fail', const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); try { @@ -567,6 +592,7 @@ test('Test ModelConnectionTransformer overrides the default limit', () => { const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); @@ -591,6 +617,7 @@ test('Test ModelConnectionTransformer uses the default limit', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -617,6 +644,7 @@ test('Test ModelConnectionTransformer with keyField overrides the default limit' const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); @@ -641,6 +669,7 @@ test('Test ModelConnectionTransformer with keyField uses the default limit', () `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -667,6 +696,7 @@ test('Connection on models with no codegen includes AttributeTypeEnum', () => { const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, transformConfig: { Version: TRANSFORM_CURRENT_VERSION, }, @@ -683,13 +713,13 @@ test('Connection on models with no codegen includes custom enum filters', () => id: ID!, cartItems: [CartItem] @connection(name: "CartCartItem") } - + type CartItem @model(queries: null, mutations: null, subscriptions: null) { id: ID! productType: PRODUCT_TYPE! cart: Cart @connection(name: "CartCartItem") } - + enum PRODUCT_TYPE { UNIT PACKAGE @@ -698,6 +728,7 @@ test('Connection on models with no codegen includes custom enum filters', () => const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, transformConfig: { Version: TRANSFORM_CURRENT_VERSION, }, diff --git a/packages/graphql-connection-transformer/src/__tests__/NewConnectionTransformer.test.ts b/packages/graphql-connection-transformer/src/__tests__/NewConnectionTransformer.test.ts index e5454fb12a4..bb2c42b3467 100644 --- a/packages/graphql-connection-transformer/src/__tests__/NewConnectionTransformer.test.ts +++ b/packages/graphql-connection-transformer/src/__tests__/NewConnectionTransformer.test.ts @@ -13,6 +13,17 @@ import { ResolverResourceIDs } from 'graphql-transformer-common'; import { ModelConnectionTransformer } from '../ModelConnectionTransformer'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { KeyTransformer } from 'graphql-key-transformer'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('ModelConnectionTransformer should fail if connection was called on an object that is not a Model type.', () => { const validSchema = ` @@ -30,6 +41,7 @@ test('ModelConnectionTransformer should fail if connection was called on an obje const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError(`@connection must be on an @model object type field.`); @@ -51,6 +63,7 @@ test('ModelConnectionTransformer should fail if connection was with an object th const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError(`Object type Test1 must be annotated with @model.`); @@ -72,6 +85,7 @@ test('ModelConnectionTransformer should fail if the field type where the directi const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError('Unknown type "Test2". Did you mean "Test" or "Test1"?'); @@ -93,6 +107,7 @@ test('ModelConnectionTransformer should fail if an empty list of fields is passe const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError('No fields passed in to @connection directive.'); @@ -118,6 +133,7 @@ test('ModelConnectionTransformer should fail if any of the fields passed in are const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError('name is not a field in Test'); @@ -143,6 +159,7 @@ test('ModelConnectionTransformer should fail if the query is not run on the defa const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError( @@ -167,6 +184,7 @@ test('ModelConnectionTransformer should fail if keyName provided does not exist. const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError('Key notDefault does not exist for model Test1'); @@ -189,6 +207,7 @@ test('ModelConnectionTransformer should fail if first field does not match PK of const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError('email field is not of type ID'); @@ -214,6 +233,7 @@ test('ModelConnectionTransformer should fail if sort key type passed in does not const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError('email field is not of type ID'); @@ -239,6 +259,7 @@ test('ModelConnectionTransformer should fail if partial sort key is passed in co const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError( @@ -266,6 +287,7 @@ test('ModelConnectionTransformer should accept connection without sort key', () const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).not.toThrowError(); @@ -291,6 +313,7 @@ test('ModelConnectionTransformer should fail if sort key type passed in does not const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError('email field is not of type ID'); @@ -316,6 +339,7 @@ test('ModelConnectionTransformer should fail if partition key type passed in doe const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); expect(() => transformer.transform(validSchema)).toThrowError('email field is not of type ID'); @@ -341,6 +365,7 @@ test('Test ModelConnectionTransformer for One-to-One getItem case.', () => { const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); @@ -374,6 +399,7 @@ test('Test ModelConnectionTransformer for One-to-Many query case.', () => { const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); @@ -411,6 +437,7 @@ test('Test ModelConnectionTransformer for bidirectional One-to-Many query case.' const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); @@ -457,6 +484,7 @@ test('Test ModelConnectionTransformer for One-to-Many query with a composite sor const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); @@ -496,6 +524,7 @@ test('Test ModelConnectionTransformer for One-to-Many query with a composite sor const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); @@ -536,6 +565,7 @@ test('Test ModelConnectionTransformer for One-to-One getItem with composite sort const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); @@ -579,6 +609,7 @@ test('Many-to-many without conflict resolution generates correct schema', () => const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, transformConfig: { Version: TRANSFORM_CURRENT_VERSION, }, @@ -619,6 +650,7 @@ test('Many-to-many with conflict resolution generates correct schema', () => { const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer(), new ModelConnectionTransformer()], + featureFlags, transformConfig: { Version: TRANSFORM_CURRENT_VERSION, ResolverConfig: { diff --git a/packages/graphql-dynamodb-transformer/src/DynamoDBModelTransformer.ts b/packages/graphql-dynamodb-transformer/src/DynamoDBModelTransformer.ts index 6849b752d91..c19e8f9f852 100644 --- a/packages/graphql-dynamodb-transformer/src/DynamoDBModelTransformer.ts +++ b/packages/graphql-dynamodb-transformer/src/DynamoDBModelTransformer.ts @@ -488,7 +488,13 @@ export class DynamoDBModelTransformer extends Transformer { this.generateModelXConnectionType(ctx, def); // Create the list resolver - const listResolver = this.resources.makeListResolver(def.name.value, listFieldNameOverride, isSyncEnabled, ctx.getQueryTypeName()); + const listResolver = this.resources.makeListResolver( + def.name.value, + ctx.featureFlags.getBoolean('improvePluralization'), + listFieldNameOverride, + isSyncEnabled, + ctx.getQueryTypeName(), + ); const resourceId = ResolverResourceIDs.DynamoDBListResolverResourceID(typeName); ctx.setResource(resourceId, listResolver); ctx.mapResourceToStack(typeName, resourceId); diff --git a/packages/graphql-dynamodb-transformer/src/__tests__/DynamoDBModelTransformer.test.ts b/packages/graphql-dynamodb-transformer/src/__tests__/DynamoDBModelTransformer.test.ts index 19d9156905a..02351e7eff7 100644 --- a/packages/graphql-dynamodb-transformer/src/__tests__/DynamoDBModelTransformer.test.ts +++ b/packages/graphql-dynamodb-transformer/src/__tests__/DynamoDBModelTransformer.test.ts @@ -14,6 +14,20 @@ import { import { FeatureFlagProvider, GraphQLTransform, TRANSFORM_BASE_VERSION, TRANSFORM_CURRENT_VERSION } from 'graphql-transformer-core'; import { DynamoDBModelTransformer } from '../DynamoDBModelTransformer'; import { getBaseType } from 'graphql-transformer-common'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + if (name === 'validateTypeNameReservedWords') { + return false; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test DynamoDBModelTransformer validation happy case', () => { const validSchema = ` @@ -26,6 +40,7 @@ test('Test DynamoDBModelTransformer validation happy case', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -41,6 +56,7 @@ test('Test DynamoDBModelTransformer with query overrides', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -75,6 +91,7 @@ test('Test DynamoDBModelTransformer with mutation overrides', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -96,6 +113,7 @@ test('Test DynamoDBModelTransformer with only create mutations', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -124,6 +142,7 @@ test('Test DynamoDBModelTransformer with multiple model directives', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -170,6 +189,7 @@ test('Test DynamoDBModelTransformer with filter', () => { }`; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -202,6 +222,7 @@ test('Test DynamoDBModelTransformer with mutations set to null', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -221,6 +242,7 @@ test('Test DynamoDBModelTransformer with queries set to null', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -242,6 +264,7 @@ test('Test DynamoDBModelTransformer with subscriptions set to null', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -265,6 +288,7 @@ test('Test DynamoDBModelTransformer with queries and mutations set to null', () `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -292,6 +316,7 @@ test('Test DynamoDBModelTransformer with advanced subscriptions', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -337,6 +362,7 @@ test('Test DynamoDBModelTransformer with non-model types and enums', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -385,6 +411,7 @@ test('Test DynamoDBModelTransformer with mutation input overrides when mutations `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -419,6 +446,7 @@ test('Test DynamoDBModelTransformer with mutation input overrides when mutations `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -446,6 +474,7 @@ test('Test non model objects contain id as a type for fields', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -470,6 +499,7 @@ test('Test schema includes attribute enum when only queries specified', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, transformConfig: { Version: 5, }, @@ -489,6 +519,7 @@ test('Test only get does not generate superfluous input and filter types', () => `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, transformConfig: { Version: 5, }, @@ -508,6 +539,7 @@ test('Test timestamp parameters when generating resolvers and output schema', () `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const result = transformer.transform(validSchema); expect(result).toBeDefined(); @@ -529,6 +561,7 @@ test('Test resolver template not to auto generate createdAt and updatedAt when t `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const result = transformer.transform(validSchema); expect(result).toBeDefined(); @@ -550,6 +583,7 @@ test('Test create and update mutation input should have timestamps as nullable f `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const result = transformer.transform(validSchema); expect(result).toBeDefined(); @@ -569,6 +603,7 @@ test('Test not to include createdAt and updatedAt field when timestamps is set t `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const result = transformer.transform(validSchema); expect(result).toBeDefined(); @@ -602,6 +637,7 @@ test('DynamoDB transformer should add default primary key when not defined', () `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const result = transformer.transform(validSchema); expect(result).toBeDefined(); @@ -627,6 +663,7 @@ test('DynamoDB transformer should add not default primary key when ID is defined `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const result = transformer.transform(validSchema); expect(result).toBeDefined(); @@ -677,6 +714,7 @@ test('Schema should compile successfully when subscription is missing from schem `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -719,6 +757,7 @@ function transformerVersionSnapshot(version: number): string { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer()], + featureFlags, transformConfig: { Version: version, }, diff --git a/packages/graphql-dynamodb-transformer/src/__tests__/__snapshots__/DynamoDBModelTransformer.test.ts.snap b/packages/graphql-dynamodb-transformer/src/__tests__/__snapshots__/DynamoDBModelTransformer.test.ts.snap index a9ce3ea1a9a..8e5fa541ffb 100644 --- a/packages/graphql-dynamodb-transformer/src/__tests__/__snapshots__/DynamoDBModelTransformer.test.ts.snap +++ b/packages/graphql-dynamodb-transformer/src/__tests__/__snapshots__/DynamoDBModelTransformer.test.ts.snap @@ -1178,7 +1178,7 @@ enum ModelAttributeTypes { type Query { getEntity(id: ID!): Entity - listEntitys(filter: ModelEntityFilterInput, limit: Int, nextToken: String): ModelEntityConnection + listEntities(filter: ModelEntityFilterInput, limit: Int, nextToken: String): ModelEntityConnection } " `; diff --git a/packages/graphql-dynamodb-transformer/src/resources.ts b/packages/graphql-dynamodb-transformer/src/resources.ts index 125ead9decf..10c24431925 100644 --- a/packages/graphql-dynamodb-transformer/src/resources.ts +++ b/packages/graphql-dynamodb-transformer/src/resources.ts @@ -701,8 +701,14 @@ export class ResourceFactory { * TODO: actually fill out the right filter expression. This is a placeholder only. * @param type */ - public makeListResolver(type: string, nameOverride?: string, isSyncEnabled: boolean = false, queryTypeName: string = 'Query') { - const fieldName = nameOverride ? nameOverride : graphqlName('list' + plurality(toUpper(type))); + public makeListResolver( + type: string, + improvePluralization: boolean, + nameOverride?: string, + isSyncEnabled: boolean = false, + queryTypeName: string = 'Query', + ) { + const fieldName = nameOverride ? nameOverride : graphqlName('list' + plurality(toUpper(type), improvePluralization)); const requestVariable = 'ListRequest'; return new AppSync.Resolver({ ApiId: Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId'), diff --git a/packages/graphql-elasticsearch-transformer/src/SearchableModelTransformer.ts b/packages/graphql-elasticsearch-transformer/src/SearchableModelTransformer.ts index 8ab736b93b0..48746de2b74 100644 --- a/packages/graphql-elasticsearch-transformer/src/SearchableModelTransformer.ts +++ b/packages/graphql-elasticsearch-transformer/src/SearchableModelTransformer.ts @@ -125,6 +125,7 @@ export class SearchableModelTransformer extends Transformer { nonKeywordFields, primaryKey, ctx.getQueryTypeName(), + ctx.featureFlags.getBoolean('improvePluralization'), searchFieldNameOverride, ctx.isProjectUsingDataStore(), ); diff --git a/packages/graphql-elasticsearch-transformer/src/__tests__/SearchableModelTransformer.test.ts b/packages/graphql-elasticsearch-transformer/src/__tests__/SearchableModelTransformer.test.ts index b32dac3d7da..7a47b2a2bd9 100644 --- a/packages/graphql-elasticsearch-transformer/src/__tests__/SearchableModelTransformer.test.ts +++ b/packages/graphql-elasticsearch-transformer/src/__tests__/SearchableModelTransformer.test.ts @@ -1,6 +1,17 @@ import { GraphQLTransform, TRANSFORM_CURRENT_VERSION, ConflictHandlerType } from 'graphql-transformer-core'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; import { SearchableModelTransformer } from '../SearchableModelTransformer'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test SearchableModelTransformer validation happy case', () => { const validSchema = ` @@ -13,6 +24,7 @@ test('Test SearchableModelTransformer validation happy case', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -30,6 +42,7 @@ test('Test SearchableModelTransformer with query overrides', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -47,6 +60,7 @@ test('Test SearchableModelTransformer with only create mutations', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -70,6 +84,7 @@ test('Test SearchableModelTransformer with multiple model searchable directives' `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -88,6 +103,7 @@ test('Test SearchableModelTransformer with sort fields', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new SearchableModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -108,6 +124,7 @@ test('SearchableModelTransformer with external versioning', () => { const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new SearchableModelTransformer()], + featureFlags, transformConfig: { Version: TRANSFORM_CURRENT_VERSION, ResolverConfig: { diff --git a/packages/graphql-elasticsearch-transformer/src/resources.ts b/packages/graphql-elasticsearch-transformer/src/resources.ts index c7dd78ac2e4..65c081d5153 100644 --- a/packages/graphql-elasticsearch-transformer/src/resources.ts +++ b/packages/graphql-elasticsearch-transformer/src/resources.ts @@ -437,10 +437,11 @@ export class ResourceFactory { nonKeywordFields: Expression[], primaryKey: string, queryTypeName: string, + improvePluralization: boolean, nameOverride?: string, includeVersion: boolean = false, ) { - const fieldName = nameOverride ? nameOverride : graphqlName('search' + plurality(toUpper(type))); + const fieldName = nameOverride ? nameOverride : graphqlName('search' + plurality(toUpper(type), improvePluralization)); return new AppSync.Resolver({ ApiId: Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId'), DataSourceName: Fn.GetAtt(ResourceConstants.RESOURCES.ElasticsearchDataSourceLogicalID, 'Name'), diff --git a/packages/graphql-key-transformer/src/__tests__/KeyTransformer.test.ts b/packages/graphql-key-transformer/src/__tests__/KeyTransformer.test.ts index 7463cf71a42..3102d6983dd 100644 --- a/packages/graphql-key-transformer/src/__tests__/KeyTransformer.test.ts +++ b/packages/graphql-key-transformer/src/__tests__/KeyTransformer.test.ts @@ -2,6 +2,20 @@ import { parse, InputObjectTypeDefinitionNode, DefinitionNode, DocumentNode, Kin import { GraphQLTransform, InvalidDirectiveError, SyncConfig, ConflictHandlerType, FeatureFlagProvider } from 'graphql-transformer-core'; import { KeyTransformer } from '../KeyTransformer'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + if (name === 'skipOverrideMutationInputTypes') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Check KeyTransformer Resolver Code', () => { const validSchema = ` @@ -20,6 +34,7 @@ test('Check KeyTransformer Resolver Code', () => { }`; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -35,6 +50,7 @@ test('KeyTransformer should fail if more than 1 @key is provided without a name. const transformer = new GraphQLTransform({ transformers: [new KeyTransformer()], + featureFlags, }); expect(() => transformer.transform(invalidSchema)).toThrowError(InvalidDirectiveError); @@ -50,6 +66,7 @@ test('KeyTransformer should fail if more than 1 @key is provided with the same n const transformer = new GraphQLTransform({ transformers: [new KeyTransformer()], + featureFlags, }); expect(() => transformer.transform(invalidSchema)).toThrowError(InvalidDirectiveError); @@ -65,6 +82,7 @@ test('KeyTransformer should fail if referencing a field that does not exist.', ( const transformer = new GraphQLTransform({ transformers: [new KeyTransformer()], + featureFlags, }); expect(() => transformer.transform(invalidSchema)).toThrowError(InvalidDirectiveError); @@ -80,6 +98,7 @@ test('Test that a primary @key fails if pointing to nullable fields.', () => { const transformer = new GraphQLTransform({ transformers: [new KeyTransformer()], + featureFlags, }); expect(() => transformer.transform(invalidSchema)).toThrowError(InvalidDirectiveError); @@ -95,6 +114,7 @@ test('Test that model with an LSI but no primary sort key will fail.', () => { const transformer = new GraphQLTransform({ transformers: [new KeyTransformer()], + featureFlags, }); expect(() => transformer.transform(invalidSchema)).toThrowError(InvalidDirectiveError); }); @@ -109,6 +129,7 @@ test('KeyTransformer should fail if a non-existing type field is defined as key const transformer = new GraphQLTransform({ transformers: [new KeyTransformer()], + featureFlags, }); expect(() => transformer.transform(invalidSchema)).toThrowError(InvalidDirectiveError); @@ -128,6 +149,7 @@ test('Check sortDirection validation code present in list resolver code for simp const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -149,6 +171,7 @@ test('Check sortDirection validation code present in list resolver code for comp const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -167,6 +190,7 @@ test('KeyTransformer should remove default primary key when primary key overidde const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -189,6 +213,7 @@ test('KeyTransformer should not remove default primary key when primary key not `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -223,6 +248,7 @@ test('Check KeyTransformer Resolver Code when sync enabled', () => { }; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags, transformConfig: { ResolverConfig: { project: config, @@ -249,6 +275,7 @@ test('Test that sort direction and filter input are generated if default list qu }`; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -286,6 +313,7 @@ test('GSI composite sort keys are wrapped in conditional to check presence in mu `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags, }); const result = transformer.transform(validSchema); @@ -300,20 +328,6 @@ function getInputType(doc: DocumentNode, type: string): InputObjectTypeDefinitio } describe('check schema input', () => { - let ff: FeatureFlagProvider; - beforeEach(() => { - ff = { - getBoolean: jest.fn().mockImplementation((name, defaultValue) => { - if (name === 'skipOverrideMutationInputTypes') { - return true; - } - }), - getNumber: jest.fn(), - getObject: jest.fn(), - getString: jest.fn(), - }; - }); - it('@model mutation with user defined null args ', () => { const validSchema = /* GraphQL */ ` type Call @@ -339,7 +353,7 @@ describe('check schema input', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], - featureFlags: ff, + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -376,7 +390,7 @@ describe('check schema input', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], - featureFlags: ff, + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -411,7 +425,7 @@ describe('check schema input', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], - featureFlags: ff, + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -457,7 +471,7 @@ describe('check schema input', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], - featureFlags: ff, + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); @@ -506,7 +520,7 @@ describe('check schema input', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], - featureFlags: ff, + featureFlags, }); const out = transformer.transform(validSchema); expect(out).toBeDefined(); diff --git a/packages/graphql-relational-schema-transformer/src/RelationalDBResolverGenerator.ts b/packages/graphql-relational-schema-transformer/src/RelationalDBResolverGenerator.ts index d233db1d528..77c0eb57671 100644 --- a/packages/graphql-relational-schema-transformer/src/RelationalDBResolverGenerator.ts +++ b/packages/graphql-relational-schema-transformer/src/RelationalDBResolverGenerator.ts @@ -43,7 +43,7 @@ export class RelationalDBResolverGenerator { * Creates the CRUDL+Q Resolvers as a Map of Cloudform Resources. The output can then be * merged with an existing Template's map of Resources. */ - public createRelationalResolvers(resolverFilePath: string) { + public createRelationalResolvers(resolverFilePath: string, improvePluralization: boolean) { let resources = {}; this.resolverFilePath = resolverFilePath; this.typePrimaryKeyMap.forEach((value: string, key: string) => { @@ -54,7 +54,7 @@ export class RelationalDBResolverGenerator { ...{ [resourceName + 'GetResolver']: this.makeGetRelationalResolver(key) }, ...{ [resourceName + 'UpdateResolver']: this.makeUpdateRelationalResolver(key) }, ...{ [resourceName + 'DeleteResolver']: this.makeDeleteRelationalResolver(key) }, - ...{ [resourceName + 'ListResolver']: this.makeListRelationalResolver(key) }, + ...{ [resourceName + 'ListResolver']: this.makeListRelationalResolver(key, improvePluralization) }, }; // TODO: Add Guesstimate Query Resolvers }); @@ -306,8 +306,8 @@ export class RelationalDBResolverGenerator { * @param type - the graphql type for which the list resolver will be created * @param queryTypeName - will be 'Query' */ - private makeListRelationalResolver(type: string, queryTypeName: string = 'Query') { - const fieldName = graphqlName(GRAPHQL_RESOLVER_OPERATION.List + plurality(toUpper(type))); + private makeListRelationalResolver(type: string, improvePluralization: boolean, queryTypeName: string = 'Query') { + const fieldName = graphqlName(GRAPHQL_RESOLVER_OPERATION.List + plurality(toUpper(type), improvePluralization)); const selectSql = this.generateSelectStatement(type); const reqFileName = `${queryTypeName}.${fieldName}.req.vtl`; const resFileName = `${queryTypeName}.${fieldName}.res.vtl`; diff --git a/packages/graphql-relational-schema-transformer/src/RelationalDBTemplateGenerator.ts b/packages/graphql-relational-schema-transformer/src/RelationalDBTemplateGenerator.ts index 8074ad90342..bdff2625e80 100644 --- a/packages/graphql-relational-schema-transformer/src/RelationalDBTemplateGenerator.ts +++ b/packages/graphql-relational-schema-transformer/src/RelationalDBTemplateGenerator.ts @@ -47,9 +47,9 @@ export class RelationalDBTemplateGenerator { * @param template - the Cloudform template * @returns the given template, updated with new resolvers. */ - public addRelationalResolvers(template: Template, resolverFilePath: string): Template { + public addRelationalResolvers(template: Template, resolverFilePath: string, improvePluralization: boolean): Template { let resolverGenerator = new RelationalDBResolverGenerator(this.context); - template.Resources = { ...template.Resources, ...resolverGenerator.createRelationalResolvers(resolverFilePath) }; + template.Resources = { ...template.Resources, ...resolverGenerator.createRelationalResolvers(resolverFilePath, improvePluralization) }; return template; } diff --git a/packages/graphql-relational-schema-transformer/src/__tests__/RelationalDBResolverGenerator.test.ts b/packages/graphql-relational-schema-transformer/src/__tests__/RelationalDBResolverGenerator.test.ts index ed9e58961ca..41ff1cf57fe 100644 --- a/packages/graphql-relational-schema-transformer/src/__tests__/RelationalDBResolverGenerator.test.ts +++ b/packages/graphql-relational-schema-transformer/src/__tests__/RelationalDBResolverGenerator.test.ts @@ -1,6 +1,4 @@ import * as fs from 'fs-extra'; - -import { JSONMappingParameters } from 'cloudform-types/types/kinesisAnalyticsV2/applicationReferenceDataSource'; import { RelationalDBResolverGenerator } from '../RelationalDBResolverGenerator'; import { TemplateContext } from '../RelationalDBSchemaTransformer'; import { parse } from 'graphql'; @@ -8,7 +6,7 @@ import { parse } from 'graphql'; jest.mock('fs-extra', () => ({ writeFileSync: jest.fn(), })); - +const writeFileSync_mock = fs.writeFileSync as jest.MockedFunction; afterEach(() => jest.clearAllMocks()); /** @@ -41,7 +39,7 @@ test('Test Basic CRUDL Resolver Generation', () => { const generator = new RelationalDBResolverGenerator(context); // TEST - const resources: { [key: string]: any } = generator.createRelationalResolvers('someFilePath'); + const resources: { [key: string]: any } = generator.createRelationalResolvers('someFilePath', true); // VERIFY expect(resources).toBeDefined(); @@ -79,9 +77,9 @@ test('verify generated templates', () => { simplePrimaryKeyTypeMap.set('Tomatoes', 'String'); const context = new TemplateContext(schema, simplePrimaryKeyMap, simpleStringFieldMap, simpleIntFieldMap, simplePrimaryKeyTypeMap); const generator = new RelationalDBResolverGenerator(context); - generator.createRelationalResolvers('testFilePath'); - expect(fs.writeFileSync.mock.calls.length).toBe(10); - fs.writeFileSync.mock.calls.forEach(call => { + generator.createRelationalResolvers('testFilePath', true); + expect(writeFileSync_mock.mock.calls.length).toBe(10); + writeFileSync_mock.mock.calls.forEach(call => { expect(call.length).toBe(3); expect(call[0]).toMatchSnapshot(); expect(call[1]).toMatchSnapshot(); @@ -106,9 +104,36 @@ test('verify generated templates using a Int primary key', () => { simplePrimaryKeyTypeMap.set('Apples', 'Int'); const context = new TemplateContext(schema, simplePrimaryKeyMap, simpleStringFieldMap, simpleIntFieldMap, simplePrimaryKeyTypeMap); const generator = new RelationalDBResolverGenerator(context); - generator.createRelationalResolvers('testFilePath'); - expect(fs.writeFileSync.mock.calls.length).toBe(10); - fs.writeFileSync.mock.calls.forEach(call => { + generator.createRelationalResolvers('testFilePath', true); + expect(writeFileSync_mock.mock.calls.length).toBe(10); + writeFileSync_mock.mock.calls.forEach(call => { + expect(call.length).toBe(3); + expect(call[0]).toMatchSnapshot(); + expect(call[1]).toMatchSnapshot(); + expect(call[2]).toBe('utf8'); + }); +}); + +test('verify generated templates with old pluralization', () => { + // SETUP + const schema = parse(` + type Apples { + id: Int + name: String + } + `); + let simpleStringFieldMap = new Map(); + let simpleIntFieldMap = new Map(); + let simplePrimaryKeyMap = new Map(); + let simplePrimaryKeyTypeMap = new Map(); + + simplePrimaryKeyMap.set('Apples', 'Id'); + simplePrimaryKeyTypeMap.set('Apples', 'Int'); + const context = new TemplateContext(schema, simplePrimaryKeyMap, simpleStringFieldMap, simpleIntFieldMap, simplePrimaryKeyTypeMap); + const generator = new RelationalDBResolverGenerator(context); + generator.createRelationalResolvers('testFilePath', false); + expect(writeFileSync_mock.mock.calls.length).toBe(10); + writeFileSync_mock.mock.calls.forEach(call => { expect(call.length).toBe(3); expect(call[0]).toMatchSnapshot(); expect(call[1]).toMatchSnapshot(); diff --git a/packages/graphql-relational-schema-transformer/src/__tests__/RelationalDBTemplateGenerator.test.ts b/packages/graphql-relational-schema-transformer/src/__tests__/RelationalDBTemplateGenerator.test.ts index 242050ec6d3..aac31eb06a1 100644 --- a/packages/graphql-relational-schema-transformer/src/__tests__/RelationalDBTemplateGenerator.test.ts +++ b/packages/graphql-relational-schema-transformer/src/__tests__/RelationalDBTemplateGenerator.test.ts @@ -67,7 +67,7 @@ test('Test Adding Resolvers to CloudForm Template', () => { expect(baseTemplate.Resources).toBeDefined(); expect(baseTemplate.Resources).not.toHaveProperty(''); - const finalTemplate = templateGenerator.addRelationalResolvers(baseTemplate, 'someFilePath'); + const finalTemplate = templateGenerator.addRelationalResolvers(baseTemplate, 'someFilePath', true); expect(finalTemplate).toBeDefined(); }); diff --git a/packages/graphql-relational-schema-transformer/src/__tests__/__snapshots__/RelationalDBResolverGenerator.test.ts.snap b/packages/graphql-relational-schema-transformer/src/__tests__/__snapshots__/RelationalDBResolverGenerator.test.ts.snap index 83c0937907e..56f2eef59b9 100644 --- a/packages/graphql-relational-schema-transformer/src/__tests__/__snapshots__/RelationalDBResolverGenerator.test.ts.snap +++ b/packages/graphql-relational-schema-transformer/src/__tests__/__snapshots__/RelationalDBResolverGenerator.test.ts.snap @@ -95,7 +95,7 @@ exports[`verify generated templates 16`] = ` $utils.toJson($output[0])" `; -exports[`verify generated templates 17`] = `"testFilePath/Query.listTomatoess.req.vtl"`; +exports[`verify generated templates 17`] = `"testFilePath/Query.listTomatoes.req.vtl"`; exports[`verify generated templates 18`] = ` "{ @@ -104,7 +104,7 @@ exports[`verify generated templates 18`] = ` }" `; -exports[`verify generated templates 19`] = `"testFilePath/Query.listTomatoess.res.vtl"`; +exports[`verify generated templates 19`] = `"testFilePath/Query.listTomatoes.res.vtl"`; exports[`verify generated templates 20`] = `"$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])"`; @@ -203,7 +203,7 @@ exports[`verify generated templates using a Int primary key 16`] = ` $utils.toJson($output[0])" `; -exports[`verify generated templates using a Int primary key 17`] = `"testFilePath/Query.listAppless.req.vtl"`; +exports[`verify generated templates using a Int primary key 17`] = `"testFilePath/Query.listApples.req.vtl"`; exports[`verify generated templates using a Int primary key 18`] = ` "{ @@ -212,6 +212,114 @@ exports[`verify generated templates using a Int primary key 18`] = ` }" `; -exports[`verify generated templates using a Int primary key 19`] = `"testFilePath/Query.listAppless.res.vtl"`; +exports[`verify generated templates using a Int primary key 19`] = `"testFilePath/Query.listApples.res.vtl"`; exports[`verify generated templates using a Int primary key 20`] = `"$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])"`; + +exports[`verify generated templates with old pluralization 1`] = `"testFilePath/Mutation.createApples.req.vtl"`; + +exports[`verify generated templates with old pluralization 2`] = ` +"#set( $cols = [] ) +#set( $vals = [] ) +#foreach( $entry in $ctx.args.createApplesInput.keySet() ) + #set( $discard = $cols.add($entry) ) + #set( $discard = $vals.add(\\"'$ctx.args.createApplesInput[$entry]'\\") ) +#end +#set( $valStr = $vals.toString().replace(\\"[\\",\\"(\\").replace(\\"]\\",\\")\\") ) +#set( $colStr = $cols.toString().replace(\\"[\\",\\"(\\").replace(\\"]\\",\\")\\") ) +{ + \\"version\\": \\"2018-05-29\\", + \\"statements\\": [\\"INSERT INTO Apples $colStr VALUES $valStr\\", \\"SELECT * FROM Apples WHERE Id=$ctx.args.createApplesInput.Id\\"] +}" +`; + +exports[`verify generated templates with old pluralization 3`] = `"testFilePath/Mutation.createApples.res.vtl"`; + +exports[`verify generated templates with old pluralization 4`] = `"$utils.toJson($utils.parseJson($utils.rds.toJsonString($ctx.result))[1][0])"`; + +exports[`verify generated templates with old pluralization 5`] = `"testFilePath/Query.getApples.req.vtl"`; + +exports[`verify generated templates with old pluralization 6`] = ` +"{ + \\"version\\": \\"2018-05-29\\", + \\"statements\\": [\\"SELECT * FROM Apples WHERE Id=$ctx.args.Id\\"] +}" +`; + +exports[`verify generated templates with old pluralization 7`] = `"testFilePath/Query.getApples.res.vtl"`; + +exports[`verify generated templates with old pluralization 8`] = ` +"#set( $output = $utils.rds.toJsonObject($ctx.result) ) +#if( $output.isEmpty() ) + $util.error(\\"Invalid response from RDS DataSource. See info for the full response.\\", \\"InvalidResponse\\", {}, $output) +#end +#set( $output = $output[0] ) +#if( $output.isEmpty() ) + #return +#end +$utils.toJson($output[0])" +`; + +exports[`verify generated templates with old pluralization 9`] = `"testFilePath/Mutation.updateApples.req.vtl"`; + +exports[`verify generated templates with old pluralization 10`] = ` +"#set( $updateList = {} ) +#foreach( $entry in $ctx.args.updateApplesInput.keySet() ) + #set( $discard = $updateList.put($entry, \\"'$ctx.args.updateApplesInput[$entry]'\\") ) +#end +#set( $update = $updateList.toString().replace(\\"{\\",\\"\\").replace(\\"}\\",\\"\\") ) +{ + \\"version\\": \\"2018-05-29\\", + \\"statements\\": [\\"UPDATE Apples SET $update WHERE Id=$ctx.args.updateApplesInput.Id}\\", \\"SELECT * FROM Apples WHERE Id=$ctx.args.updateApplesInput.Id\\"] +}" +`; + +exports[`verify generated templates with old pluralization 11`] = `"testFilePath/Mutation.updateApples.res.vtl"`; + +exports[`verify generated templates with old pluralization 12`] = ` +"#set( $output = $utils.rds.toJsonObject($ctx.result) ) +#if( $output.length() < 2 ) + $util.error(\\"Invalid response from RDS DataSource. See info for the full response.\\", \\"InvalidResponse\\", {}, $output) +#end +#set( $output = $output[1] ) +#if( $output.isEmpty() ) + #return +#end +$utils.toJson($output[0])" +`; + +exports[`verify generated templates with old pluralization 13`] = `"testFilePath/Mutation.deleteApples.req.vtl"`; + +exports[`verify generated templates with old pluralization 14`] = ` +"{ + \\"version\\": \\"2018-05-29\\", + \\"statements\\": [\\"SELECT * FROM Apples WHERE Id=$ctx.args.Id\\", \\"DELETE FROM Apples WHERE Id=$ctx.args.Id\\"] +}" +`; + +exports[`verify generated templates with old pluralization 15`] = `"testFilePath/Mutation.deleteApples.res.vtl"`; + +exports[`verify generated templates with old pluralization 16`] = ` +"#set( $output = $utils.rds.toJsonObject($ctx.result) ) +#if( $output.isEmpty() ) + $util.error(\\"Invalid response from RDS DataSource. See info for the full response.\\", \\"InvalidResponse\\", {}, $output) +#end +#set( $output = $output[0] ) +#if( $output.isEmpty() ) + #return +#end +$utils.toJson($output[0])" +`; + +exports[`verify generated templates with old pluralization 17`] = `"testFilePath/Query.listAppless.req.vtl"`; + +exports[`verify generated templates with old pluralization 18`] = ` +"{ + \\"version\\": \\"2018-05-29\\", + \\"statements\\": [\\"SELECT * FROM Apples\\"] +}" +`; + +exports[`verify generated templates with old pluralization 19`] = `"testFilePath/Query.listAppless.res.vtl"`; + +exports[`verify generated templates with old pluralization 20`] = `"$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])"`; diff --git a/packages/graphql-transformer-common/package.json b/packages/graphql-transformer-common/package.json index 445cb556867..32c109ce574 100644 --- a/packages/graphql-transformer-common/package.json +++ b/packages/graphql-transformer-common/package.json @@ -25,7 +25,8 @@ "dependencies": { "graphql": "^14.5.8", "graphql-mapping-template": "4.18.1", - "md5": "^2.2.1" + "md5": "^2.2.1", + "pluralize": "8.0.0" }, "devDependencies": { "@types/md5": "^2.1.33", diff --git a/packages/graphql-transformer-common/src/util.ts b/packages/graphql-transformer-common/src/util.ts index cca2086ee79..9e7c8a347c2 100644 --- a/packages/graphql-transformer-common/src/util.ts +++ b/packages/graphql-transformer-common/src/util.ts @@ -1,9 +1,14 @@ import md5 from 'md5'; - -export function plurality(val: string): string { +import pluralize from 'pluralize'; +export function plurality(val: string, improvePluralization: boolean): string { if (!val.trim()) { return ''; } + + if (improvePluralization) { + return pluralize(val); + } + return val.concat('s'); } diff --git a/packages/graphql-versioned-transformer/src/__tests__/VersionedModelTransformer.test.ts b/packages/graphql-versioned-transformer/src/__tests__/VersionedModelTransformer.test.ts index fdfd9915cdd..2d3f5efa3ea 100644 --- a/packages/graphql-versioned-transformer/src/__tests__/VersionedModelTransformer.test.ts +++ b/packages/graphql-versioned-transformer/src/__tests__/VersionedModelTransformer.test.ts @@ -2,13 +2,23 @@ import { ObjectTypeDefinitionNode, parse, DocumentNode, Kind, InputObjectTypeDef import { GraphQLTransform } from 'graphql-transformer-core'; import { VersionedModelTransformer } from '../VersionedModelTransformer'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; - const getInputType = (schemaDoc: DocumentNode) => (name: string): InputObjectTypeDefinitionNode => schemaDoc.definitions.find(d => d.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION && d.name.value === name) as InputObjectTypeDefinitionNode; const getInputField = (input: InputObjectTypeDefinitionNode, field: string) => input.fields.find(f => f.name.value === field); const getType = (schemaDoc: DocumentNode) => (name: string): ObjectTypeDefinitionNode => schemaDoc.definitions.find(d => d.kind === Kind.OBJECT_TYPE_DEFINITION && d.name.value === name) as ObjectTypeDefinitionNode; const getField = (input: ObjectTypeDefinitionNode, field: string) => input.fields.find(f => f.name.value === field); +const featureFlags = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'improvePluralization') { + return true; + } + return; + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), +}; test('Test VersionedModelTransformer validation happy case', () => { const validSchema = ` @@ -21,6 +31,7 @@ test('Test VersionedModelTransformer validation happy case', () => { `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new VersionedModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); // tslint:disable-next-line @@ -47,6 +58,7 @@ test('Test VersionedModelTransformer validation fails when provided version fiel try { const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new VersionedModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); } catch (e) { @@ -66,6 +78,7 @@ test('Test VersionedModelTransformer version field replaced by non-null if provi `; const transformer = new GraphQLTransform({ transformers: [new DynamoDBModelTransformer(), new VersionedModelTransformer()], + featureFlags, }); const out = transformer.transform(validSchema); const sdl = out.schema; diff --git a/yarn.lock b/yarn.lock index 32be6413219..cf487760c90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19389,7 +19389,7 @@ plist@^3.0.1: xmlbuilder "^9.0.7" xmldom "0.1.x" -pluralize@^8.0.0: +pluralize@8.0.0, pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==