diff --git a/package-lock.json b/package-lock.json index 7076b56810..af5edbe483 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16703,6 +16703,9 @@ }, "node_modules/fs-extra": { "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -17258,6 +17261,9 @@ }, "node_modules/immer": { "version": "9.0.6", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz", + "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==", + "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -18868,6 +18874,7 @@ }, "node_modules/mime-db": { "version": "1.52.0", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -18875,6 +18882,7 @@ }, "node_modules/mime-types": { "version": "2.1.35", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" diff --git a/packages/backend-data/src/convert_schema.ts b/packages/backend-data/src/convert_schema.ts index 18311816f6..6a77058a1c 100644 --- a/packages/backend-data/src/convert_schema.ts +++ b/packages/backend-data/src/convert_schema.ts @@ -261,6 +261,13 @@ export const extractImportedModels = ( importedSchemas: { schema: string; importedTableName: string }[]; nonImportedSchema: string; } => { + if (importedAmplifyDynamoDBTableMap && Object.keys(importedAmplifyDynamoDBTableMap).length) { + Object.keys(importedAmplifyDynamoDBTableMap).forEach((modelName) => { + if (!importedModels?.includes(modelName)) { + throw new Error(`Imported table defined in importedAmplifyDynamoDBTableMap not found in importedModels list: ${modelName}`); + } + }); + } if (importedModels?.length) { // TODO: maybe provide exported function from construct const parsedSchema = parse(schema); @@ -273,28 +280,37 @@ export const extractImportedModels = ( ); } ); + // ok to cast as ObjectTypeDefinitionNode because the type was checked in the partition function + const importedObjectTypeDefinitionNodes = importedDefinitionNodes as ObjectTypeDefinitionNode[]; + + importedModels.forEach((modelName) => { + if (!importedObjectTypeDefinitionNodes.some((definitionNode) => definitionNode.name.value === modelName)) { + throw new Error(`Imported model not found in schema: ${modelName}`) + } + }); + + const importedSchemas = + importedObjectTypeDefinitionNodes + .map((definitionNode) => { + const importedTableName = (importedAmplifyDynamoDBTableMap ?? {})[ + definitionNode.name.value + ]; + if (!importedTableName) { + throw new Error( + `No table found for imported model ${definitionNode.name.value}.` + ); + } + return { + schema: print({ + definitions: [definitionNode], + kind: 'Document' as const, + }), + importedTableName, + }; + }); return { - // ok to cast as ObjectTypeDefinitionNode because the type was checked in the partition function - importedSchemas: ( - importedDefinitionNodes as ObjectTypeDefinitionNode[] - ).map((definitionNode) => { - const importedTableName = (importedAmplifyDynamoDBTableMap ?? {})[ - definitionNode.name.value - ]; - if (!importedTableName) { - throw new Error( - `No table found for imported model ${definitionNode.name.value}.` - ); - } - return { - schema: print({ - definitions: [definitionNode], - kind: 'Document' as const, - }), - importedTableName, - }; - }), + importedSchemas, nonImportedSchema: print({ definitions: nonImportedDefinitionNodes, kind: 'Document' as const, diff --git a/packages/backend-data/src/factory.test.ts b/packages/backend-data/src/factory.test.ts index 38aff8a4b1..011ada733a 100644 --- a/packages/backend-data/src/factory.test.ts +++ b/packages/backend-data/src/factory.test.ts @@ -41,6 +41,7 @@ import { a } from '@aws-amplify/data-schema'; import { AmplifyDataError } from './types.js'; const CUSTOM_DDB_CFN_TYPE = 'Custom::AmplifyDynamoDBTable'; +const CUSTOM_IMPORTED_DDB_CFN_TYPE = 'Custom::ImportedAmplifyDynamoDBTable'; const testSchema = /* GraphQL */ ` type Todo @model { @@ -856,14 +857,151 @@ void describe('Table Import', () => { const getInstanceProps = createInstancePropsBySetupCDKApp({ isSandboxMode: true, }); - dataFactory.getInstance(getInstanceProps); - // TODO: add assertions + const instance = dataFactory.getInstance(getInstanceProps); + const blogStack = Template.fromStack( + Stack.of(instance.resources.nestedStacks['Blog']) + ); + const importedModelStack = Template.fromStack( + Stack.of(instance.resources.nestedStacks['ImportedModel']) + ); + importedModelStack.hasResourceProperties(CUSTOM_IMPORTED_DDB_CFN_TYPE, { + isImported: true, + tableName: 'ImportedModel-1234-dev', + }); + blogStack.hasResource(CUSTOM_DDB_CFN_TYPE, {}); + }); + + void it('fails when importedModels is not supplied', () => { + const schema = /* GraphQL */ ` + type Blog @model { + title: String + content: String + authors: [String] + } + + type ImportedModel @model { + description: String + } + `; + const dataFactory = defineData({ + schema, + importedAmplifyDynamoDBTableMap: { + ImportedModel: 'ImportedModel-1234-dev', + }, + }); + const getInstanceProps = createInstancePropsBySetupCDKApp({ + isSandboxMode: true, + }); + assert.throws(() => dataFactory.getInstance(getInstanceProps), { + message: 'importedAmplifyDynamoDBTableMap is defined but importedModels is not defined.', + }); + }); + + void it('fails when importedAmplifyDynamoDBTableMap is not supplied', () => { + const schema = /* GraphQL */ ` + type Blog @model { + title: String + content: String + authors: [String] + } + + type ImportedModel @model { + description: String + } + `; + const dataFactory = defineData({ + schema, + importedModels: ['ImportedModel'], + }); + const getInstanceProps = createInstancePropsBySetupCDKApp({ + isSandboxMode: true, + }); + assert.throws(() => dataFactory.getInstance(getInstanceProps), { + message: 'importedModels is defined but importedAmplifyDynamoDBTableMap is not defined.', + }); + }); + + void it('fails when imported model is missing from the schema', () => { + const schema = /* GraphQL */ ` + type Blog @model { + title: String + content: String + authors: [String] + } + `; + const dataFactory = defineData({ + schema, + importedAmplifyDynamoDBTableMap: { + ImportedModel: 'ImportedModel-1234-dev', + }, + importedModels: ['ImportedModel'], + }); + const getInstanceProps = createInstancePropsBySetupCDKApp({ + isSandboxMode: true, + }); + assert.throws(() => dataFactory.getInstance(getInstanceProps), { + message: 'Imported model not found in schema: ImportedModel', + }); }); - // TODO: add tests for error cases - // 1. importedModels or importedAmplifyDynamoDBTableMap not supplied - // 2. models missing in schema - // 3. models missing in table map + void it('throws when model missing in imported models list', () => { + const schema = /* GraphQL */ ` + type Blog @model { + title: String + content: String + authors: [String] + } + + type ImportedModel @model { + description: String + } + + type Foo @model { + description: String + } + `; + const dataFactory = defineData({ + schema, + importedAmplifyDynamoDBTableMap: { + ImportedModel: 'ImportedModel-1234-dev', + Foo: 'Foo-1234-dev', + }, + importedModels: ['Foo'], + }); + const getInstanceProps = createInstancePropsBySetupCDKApp({ + isSandboxMode: true, + }); + assert.throws(() => dataFactory.getInstance(getInstanceProps), { + message: 'Imported table defined in importedAmplifyDynamoDBTableMap not found in importedModels list: ImportedModel', + }); + }); + + + void it('throws when model missing in imported models map', () => { + const schema = /* GraphQL */ ` + type Blog @model { + title: String + content: String + authors: [String] + } + + type ImportedModel @model { + description: String + } + `; + const dataFactory = defineData({ + schema, + importedAmplifyDynamoDBTableMap: { + }, + importedModels: ['ImportedModel'], + }); + const getInstanceProps = createInstancePropsBySetupCDKApp({ + isSandboxMode: true, + }); + assert.throws(() => dataFactory.getInstance(getInstanceProps), { + message: 'No table found for imported model ImportedModel.', + }); + }); }); const resetFactoryCount = () => {