diff --git a/src/services/dumpers/abstract-dumper.ts b/src/services/dumpers/abstract-dumper.ts index d532bdb12..429e49f45 100644 --- a/src/services/dumpers/abstract-dumper.ts +++ b/src/services/dumpers/abstract-dumper.ts @@ -10,7 +10,7 @@ export default abstract class AbstractDumper { private readonly fs; - private readonly logger: Logger; + protected readonly logger: Logger; private readonly chalk: Chalk; diff --git a/src/services/dumpers/agent-nodejs.ts b/src/services/dumpers/agent-nodejs.ts index 076d6154b..df05804ba 100644 --- a/src/services/dumpers/agent-nodejs.ts +++ b/src/services/dumpers/agent-nodejs.ts @@ -44,6 +44,7 @@ export default class AgentNodeJs extends AbstractDumper { lodash, strings, toValidPackageName, + logger, } = context; assertPresent({ @@ -54,6 +55,7 @@ export default class AgentNodeJs extends AbstractDumper { lodash, strings, toValidPackageName, + logger, }); super(context); @@ -249,6 +251,48 @@ export default class AgentNodeJs extends AbstractDumper { ); } + private removeNonCompliantNestedFields(collectionName: string, fieldsDefinition: any) { + if (typeof fieldsDefinition !== 'string') { + if (Array.isArray(fieldsDefinition)) { + fieldsDefinition.forEach(fieldDefinition => { + this.removeNonCompliantNestedFields(collectionName, fieldDefinition); + }); + } else { + Object.entries(fieldsDefinition).forEach(([key, fieldDefinition]) => { + if (key.includes(':')) { + this.logger.warn( + `Ignoring field ${key} from collection ${collectionName} as it contains column and is not valid.`, + ); + + delete fieldsDefinition[key]; + } else { + this.removeNonCompliantNestedFields(collectionName, fieldDefinition); + } + }); + } + } + } + + private removeNonCompliantFields(collectionName, fieldsDefinition) { + const compliantFieldsDefinition = JSON.parse(JSON.stringify(fieldsDefinition)); + + return compliantFieldsDefinition.reduce((correctFieldsDefinitions, definition) => { + if (definition.name.includes(':')) { + this.logger.warn( + `Ignoring field ${definition.name} from collection ${collectionName} as it contains column and is not valid.`, + ); + } else { + correctFieldsDefinitions.push(definition); + + if (definition.type && typeof definition.type !== 'string') { + this.removeNonCompliantNestedFields(collectionName, definition.type); + } + } + + return correctFieldsDefinitions; + }, []); + } + private computeModelsConfiguration(language: Language, schema: any): Array { const collectionNamesSorted = Object.keys(schema).sort(); @@ -264,10 +308,15 @@ export default class AgentNodeJs extends AbstractDumper { }; }); + const compliantFieldsDefinition = this.removeNonCompliantFields( + collectionName, + fieldsDefinition, + ); + return { modelName: this.strings.transformToCamelCaseSafeString(collectionName), collectionName, - fields: fieldsDefinition, + fields: compliantFieldsDefinition, timestamps: options.timestamps, modelFileName, modelPath, diff --git a/test/services/analyzer/expected/mongo/db-analysis-output/deep-nested-fields-column.expected.json b/test/services/analyzer/expected/mongo/db-analysis-output/deep-nested-fields-column.expected.json new file mode 100644 index 000000000..b21dd82ce --- /dev/null +++ b/test/services/analyzer/expected/mongo/db-analysis-output/deep-nested-fields-column.expected.json @@ -0,0 +1,68 @@ +{ + "persons": { + "fields": [ + { + "name": "name:column", + "type": "String" + }, + { + "name": "very", + "type": { + "deep": { + "model": { + "arrayOfNumber": [ + "Number" + ], + "arrayMixed": [ + "Object" + ], + "arrayOfObjectIds": [ + "Mongoose.Schema.Types.ObjectId" + ], + "arrayWithComplexObject": [ + { + "_id": "Mongoose.Schema.Types.ObjectId", + "name": "String", + "propGroup": { + "answer:column": "Boolean", + "date": "Date", + "sentence": "String", + "number": "Number" + } + } + ], + "arrayOfComplexObjects": [ + { + "_id": "Mongoose.Schema.Types.ObjectId", + "propGroup": { + "answer": "Boolean", + "date": "Date", + "sentence": "String", + "number": "Number" + }, + "so:column": { + "nested": { + "arrayMixed": [ + "Object" + ], + "arrayOfNumber": [ + "Number" + ] + } + } + } + ] + } + } + } + } + ], + "options": { + "timestamps": false + }, + "primaryKeys": [ + "_id" + ], + "references": [] + } +} diff --git a/test/services/dumpers/agent-nodejs/agent-nodejs-dumper-mongoose-models.test.ts b/test/services/dumpers/agent-nodejs/agent-nodejs-dumper-mongoose-models.test.ts index d292bf476..3669a7d92 100644 --- a/test/services/dumpers/agent-nodejs/agent-nodejs-dumper-mongoose-models.test.ts +++ b/test/services/dumpers/agent-nodejs/agent-nodejs-dumper-mongoose-models.test.ts @@ -6,6 +6,7 @@ import rimraf from 'rimraf'; import defaultPlan from '../../../../src/context/plan'; import AgentNodeJsDumper from '../../../../src/services/dumpers/agent-nodejs'; import languages from '../../../../src/utils/languages'; +import deepNestedColumn from '../../analyzer/expected/mongo/db-analysis-output/deep-nested-fields-column.expected.json'; import deepNested from '../../analyzer/expected/mongo/db-analysis-output/deep-nested-fields.expected.json'; import hasMany from '../../analyzer/expected/mongo/db-analysis-output/hasmany.expected.json'; import manyObjectIdFields from '../../analyzer/expected/mongo/db-analysis-output/many-objectid-fields.expected.json'; @@ -36,9 +37,16 @@ describe('services > dumpers > agentNodejsDumper > mongoose models', () => { language, }; - const injectedContext = Context.execute(defaultPlan); + const injectedContext = Context.execute(defaultPlan) as any; + + const loggerWarnSpy = jest.spyOn(injectedContext.logger, 'warn'); + const dumper = new AgentNodeJsDumper(injectedContext); await dumper.dump(config, schema); + + return { + loggerWarnSpy, + }; } describe.each([languages.Javascript, languages.Typescript])('language: $name', language => { @@ -98,6 +106,14 @@ describe('services > dumpers > agentNodejsDumper > mongoose models', () => { expectedFilePath: `${__dirname}/expected/${language.name}/mongo-models/sub-documents-with-ids.expected.${language.fileExtension}`, }, }, + { + name: 'should not dump any fields containing column', + schema: deepNestedColumn, + file: { + model: 'persons', + expectedFilePath: `${__dirname}/expected/${language.name}/mongo-models/deep-nested-column.expected.${language.fileExtension}`, + }, + }, ]; it.each(testCases)(`$name`, async ({ schema, file }) => { @@ -115,6 +131,25 @@ describe('services > dumpers > agentNodejsDumper > mongoose models', () => { expect(generatedFile).toStrictEqual(expectedFile); }); + + it('should warn information when a field containing column has been ignored', async () => { + expect.assertions(4); + + rimraf.sync(`${appRoot}/test-output/${language.name}/mongodb/`); + + const { loggerWarnSpy } = await dump(language, deepNestedColumn); + + expect(loggerWarnSpy).toHaveBeenCalledTimes(3); + expect(loggerWarnSpy).toHaveBeenCalledWith( + 'Ignoring field name:column from collection persons as it contains column and is not valid.', + ); + expect(loggerWarnSpy).toHaveBeenCalledWith( + 'Ignoring field answer:column from collection persons as it contains column and is not valid.', + ); + expect(loggerWarnSpy).toHaveBeenCalledWith( + 'Ignoring field so:column from collection persons as it contains column and is not valid.', + ); + }); }); }); diff --git a/test/services/dumpers/agent-nodejs/agent-nodejs.unit.test.ts b/test/services/dumpers/agent-nodejs/agent-nodejs.unit.test.ts index 41699d3ca..efc426dad 100644 --- a/test/services/dumpers/agent-nodejs/agent-nodejs.unit.test.ts +++ b/test/services/dumpers/agent-nodejs/agent-nodejs.unit.test.ts @@ -11,7 +11,7 @@ describe('services > dumpers > AgentNodeJs', () => { collectionA: { fields: [ { - field: 'aField', + name: 'aField', ref: 'a-collection', }, ], @@ -744,7 +744,7 @@ describe('services > dumpers > AgentNodeJs', () => { collectionName: 'collectionA', fields: [ { - field: 'aField', + name: 'aField', ref: 'a-collection', }, ], @@ -785,7 +785,7 @@ describe('services > dumpers > AgentNodeJs', () => { collectionName: 'collectionA', fields: [ { - field: 'aField', + name: 'aField', ref: 'a-collectioncamelCased', }, ], diff --git a/test/services/dumpers/agent-nodejs/expected/javascript/mongo-models/deep-nested-column.expected.js b/test/services/dumpers/agent-nodejs/expected/javascript/mongo-models/deep-nested-column.expected.js new file mode 100644 index 000000000..82ae20d3b --- /dev/null +++ b/test/services/dumpers/agent-nodejs/expected/javascript/mongo-models/deep-nested-column.expected.js @@ -0,0 +1,37 @@ +const Mongoose = require('mongoose'); + +const schema = new Mongoose.Schema({ + very: { + deep: { + model: { + arrayOfNumber: [Number], + arrayMixed: [Object], + arrayOfObjectIds: [Mongoose.Schema.Types.ObjectId], + arrayWithComplexObject: [{ + name: String, + propGroup: { + date: Date, + sentence: String, + number: Number, + }, + }], + arrayOfComplexObjects: [{ + propGroup: { + answer: Boolean, + date: Date, + sentence: String, + number: Number, + }, + }], + }, + }, + }, +}, { + timestamps: false, +}); + +module.exports = { + collectionName: 'persons', + modelName: 'persons', + schema, +}; diff --git a/test/services/dumpers/agent-nodejs/expected/typescript/mongo-models/deep-nested-column.expected.ts b/test/services/dumpers/agent-nodejs/expected/typescript/mongo-models/deep-nested-column.expected.ts new file mode 100644 index 000000000..2de6292ad --- /dev/null +++ b/test/services/dumpers/agent-nodejs/expected/typescript/mongo-models/deep-nested-column.expected.ts @@ -0,0 +1,63 @@ +import Mongoose from 'mongoose'; + +interface PersonsInterface { + very: { + deep: { + model: { + arrayOfNumber: Array; + arrayMixed: Array; + arrayOfObjectIds: Array; + arrayWithComplexObject: Array<{ + _id: Mongoose.Types.ObjectId; + name: string; + propGroup: { + date: Date; + sentence: string; + number: number; + }; + }>; + arrayOfComplexObjects: Array<{ + _id: Mongoose.Types.ObjectId; + propGroup: { + answer: boolean; + date: Date; + sentence: string; + number: number; + }; + }>; + }; + }; + }; +} + +const personsSchema = new Mongoose.Schema({ + very: { + deep: { + model: { + arrayOfNumber: [Number], + arrayMixed: [Object], + arrayOfObjectIds: [Mongoose.Schema.Types.ObjectId], + arrayWithComplexObject: [{ + name: String, + propGroup: { + date: Date, + sentence: String, + number: Number, + }, + }], + arrayOfComplexObjects: [{ + propGroup: { + answer: Boolean, + date: Date, + sentence: String, + number: Number, + }, + }], + }, + }, + }, +}, { + timestamps: false, +}); + +export { PersonsInterface, personsSchema };