Repository: asyncapi/modelina
Pull request #781 (2022-06-30 11:26:00)

chore: add test and fix constrain implementation
**Description**
This PR fixes the constrain functions and adds tests

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=asyncapi_generator-model-sdk&pullRequest=781)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=asyncapi_generator-model-sdk&pullRequest=781&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=asyncapi_generator-model-sdk&pullRequest=781) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=asyncapi_generator-model-sdk&pullRequest=781&metric=duplicated_lines_density&view=list) No Duplication information



Patch:
diff --git a/src/generators/csharp/CSharpFileGenerator.ts b/src/generators/csharp/CSharpFileGenerator.ts
index c08bed35..52eb9162 100644
--- a/src/generators/csharp/CSharpFileGenerator.ts
+++ b/src/generators/csharp/CSharpFileGenerator.ts
@@ -14,7 +14,8 @@ export class CSharpFileGenerator extends CSharpGenerator implements AbstractFile
    */
   public async generateToFiles(input: Record<string, unknown> | InputMetaModel, outputDirectory: string, options: CSharpRenderCompleteModelOptions): Promise<OutputModel[]> {
     let generatedModels = await this.generateCompleteModels(input, options);
-    generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; });
+    //Filter anything out that have not been successfully generated
+    generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== ''; });
     for (const outputModel of generatedModels) {
       const filePath = path.resolve(outputDirectory, `${outputModel.modelName}.cs`);
       await FileHelpers.writerToFileSystem(outputModel.result, filePath);
diff --git a/src/generators/dart/DartFileGenerator.ts b/src/generators/dart/DartFileGenerator.ts
index 54cafbd6..fc00ce26 100644
--- a/src/generators/dart/DartFileGenerator.ts
+++ b/src/generators/dart/DartFileGenerator.ts
@@ -14,7 +14,8 @@ export class DartFileGenerator extends DartGenerator implements AbstractFileGene
    */
   public async generateToFiles(input: Record<string, unknown> | InputMetaModel, outputDirectory: string, options: DartRenderCompleteModelOptions): Promise<OutputModel[]> {
     let generatedModels = await this.generateCompleteModels(input, options);
-    generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; });
+    //Filter anything out that have not been successfully generated
+    generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== ''; });
     for (const outputModel of generatedModels) {
       const filePath = path.resolve(outputDirectory, `${outputModel.modelName}.dart`);
       await FileHelpers.writerToFileSystem(outputModel.result, filePath);
diff --git a/src/generators/go/GoFileGenerator.ts b/src/generators/go/GoFileGenerator.ts
index ba519e78..5e050396 100644
--- a/src/generators/go/GoFileGenerator.ts
+++ b/src/generators/go/GoFileGenerator.ts
@@ -14,7 +14,8 @@ export class GoFileGenerator extends GoGenerator implements AbstractFileGenerato
    */
   public async generateToFiles(input: Record<string, unknown> | InputMetaModel, outputDirectory: string, options: GoRenderCompleteModelOptions): Promise<OutputModel[]> {
     let generatedModels = await this.generateCompleteModels(input, options);
-    generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; });
+    //Filter anything out that have not been successfully generated
+    generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== ''; });
     for (const outputModel of generatedModels) {
       const filePath = path.resolve(outputDirectory, `${outputModel.modelName}.go`);
       await FileHelpers.writerToFileSystem(outputModel.result, filePath);
diff --git a/src/generators/java/JavaFileGenerator.ts b/src/generators/java/JavaFileGenerator.ts
index 872aab70..8c6f073a 100644
--- a/src/generators/java/JavaFileGenerator.ts
+++ b/src/generators/java/JavaFileGenerator.ts
@@ -14,7 +14,8 @@ export class JavaFileGenerator extends JavaGenerator implements AbstractFileGene
    */
   public async generateToFiles(input: Record<string, unknown> | InputMetaModel, outputDirectory: string, options: JavaRenderCompleteModelOptions): Promise<OutputModel[]> {
     let generatedModels = await this.generateCompleteModels(input, options);
-    generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; });
+    //Filter anything out that have not been successfully generated
+    generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== ''; });
     for (const outputModel of generatedModels) {
       const filePath = path.resolve(outputDirectory, `${outputModel.modelName}.java`);
       await FileHelpers.writerToFileSystem(outputModel.result, filePath);
diff --git a/src/helpers/Constraints.ts b/src/helpers/Constraints.ts
index 53e05932..8f8861ff 100644
--- a/src/helpers/Constraints.ts
+++ b/src/helpers/Constraints.ts
@@ -10,30 +10,57 @@ export function NO_NUMBER_START_CHAR(value: string): string {
 }
 
 /**
- * Makes sure that no duplicate properties can be created. 
+ * Because a lot of the other constrain functions (such as NO_NUMBER_START_CHAR, NO_EMPTY_VALUE, etc) they might manipulate the property names by append, prepend, or manipulate it any other way. 
+ * We then need to make sure that they don't clash with any existing properties, this is what this function handles.
+ * If so, prepend `reserved_` to the property name and recheck.
+ * 
+ * @param constrainedObjectModel the current constrained object model, which contains already existing constrained properties
+ * @param objectModel the raw object model which is non-constrained to the output language.
+ * @param propertyName one of the properties in objectModel which might have been manipulated
+ * @param namingFormatter the name formatter which are used to format the property key
+ * @returns 
  */
 export function NO_DUPLICATE_PROPERTIES(constrainedObjectModel: ConstrainedObjectModel, objectModel: ObjectModel, propertyName: string, namingFormatter: (value: string) => string): string {
+  // Make sure that the given property name is formatted correctly for further comparisons
+  const formattedPropertyName = namingFormatter(propertyName);
   let newPropertyName = propertyName;
-
-  const alreadyPartOfMetaModel = Object.keys(objectModel.properties).map((key) => namingFormatter(key)).includes(propertyName);
-  const alreadyPartOfConstrainedModel = Object.keys(constrainedObjectModel.properties).includes(propertyName);
+  const alreadyPartOfMetaModel = Object.keys(objectModel.properties)
+    .filter((key) => propertyName !== key) // Filter out the potential same property name that we can safely ignore for this check.
+    .includes(formattedPropertyName);
+  const alreadyPartOfConstrainedModel = Object.keys(constrainedObjectModel.properties)
+    .includes(formattedPropertyName);
   if (alreadyPartOfMetaModel || alreadyPartOfConstrainedModel) {
     newPropertyName = `reserved_${propertyName}`;
-    newPropertyName = namingFormatter(newPropertyName);
     newPropertyName = NO_DUPLICATE_PROPERTIES(constrainedObjectModel, objectModel, newPropertyName, namingFormatter);
   }
   return newPropertyName;
 }
 
+/**
+ * Because a lot of the other constrain functions (such as NO_NUMBER_START_CHAR, NO_EMPTY_VALUE, etc) they might manipulate the enum keys by append, prepend, or manipulate it any other way. 
+ * We then need to make sure that they don't clash with any existing enum keys, this is what this function handles.
+ * If so, prepend `reserved_` to the enum key and recheck.
+ * 
+ * @param constrainedEnumModel the current constrained enum model, which contains already existing constrained enum keys
+ * @param enumModel the raw enum model which is non-constrained to the output language.
+ * @param enumKey one of the enum keys in enumModel which might have been manipulated
+ * @param namingFormatter the name formatter which are used to format the enum key
+ * @returns 
+ */
 export function NO_DUPLICATE_ENUM_KEYS(constrainedEnumModel: ConstrainedEnumModel, enumModel: EnumModel, enumKey: string, namingFormatter: (value: string) => string): string {
+  const formattedEnumKey = namingFormatter(enumKey);
   let newEnumKey = enumKey;
 
-  const alreadyPartOfMetaModel = enumModel.values.map((model) => model.key).map((key) => namingFormatter(key)).includes(enumKey);
-  const alreadyPartOfConstrainedModel = constrainedEnumModel.values.map((model) => model.key).includes(enumKey);
-
+  const alreadyPartOfMetaModel = enumModel.values
+    .map((model) => model.key)
+    .filter((key) => enumKey !== key)
+    .includes(formattedEnumKey);
+  const alreadyPartOfConstrainedModel = constrainedEnumModel.values
+    .map((model) => model.key)
+    .includes(formattedEnumKey);
+  
   if (alreadyPartOfMetaModel || alreadyPartOfConstrainedModel) {
     newEnumKey = `reserved_${enumKey}`;
-    newEnumKey = namingFormatter(newEnumKey);
     newEnumKey = NO_DUPLICATE_ENUM_KEYS(constrainedEnumModel, enumModel, newEnumKey, namingFormatter);
   }
   return newEnumKey;


Test patch:
diff --git a/test/generators/FileGenerators.spec.ts b/test/generators/FileGenerators.spec.ts
new file mode 100644
index 00000000..89c9c5f0
--- /dev/null
+++ b/test/generators/FileGenerators.spec.ts
@@ -0,0 +1,92 @@
+import { FileHelpers, DartFileGenerator, OutputModel, ConstrainedAnyModel, InputMetaModel, GoFileGenerator, JavaFileGenerator, JavaScriptFileGenerator, TypeScriptFileGenerator, CSharpFileGenerator } from '../../src';
+import * as path from 'path';
+
+const generatorsToTest = [
+  {
+    generator: new GoFileGenerator(),
+    generatorOptions: {packageName: 'some_package'},
+    fileExtension: 'go'
+  },
+  {
+    generator: new DartFileGenerator(),
+    generatorOptions: {packageName: 'SomePackage'},
+    fileExtension: 'dart'
+  },
+  {
+    generator: new JavaFileGenerator(),
+    generatorOptions: {packageName: 'SomePackage'},
+    fileExtension: 'java'
+  },
+  {
+    generator: new JavaScriptFileGenerator(),
+    generatorOptions: {},
+    fileExtension: 'js'
+  },
+  {
+    generator: new TypeScriptFileGenerator(),
+    generatorOptions: {},
+    fileExtension: 'ts'
+  },
+  {
+    generator: new CSharpFileGenerator(),
+    generatorOptions: {namespace: 'SomeNamespace'},
+    fileExtension: 'cs'
+  }
+];
+
+describe.each(generatorsToTest)('generateToFiles', ({generator, generatorOptions, fileExtension}) => {
+  afterEach(() => {
+    jest.restoreAllMocks();
+  });
+
+  const doc = {
+    $id: 'CustomClass',
+    type: 'object',
+    additionalProperties: true,
+    properties: {
+      someProp: { type: 'string' },
+      someEnum: {
+        $id: 'CustomEnum',
+        type: 'string',
+        enum: ['Texas', 'Alabama', 'California'],
+      }
+    }
+  };
+
+  test('should throw accurate error if file cannot be written', async () => {
+    const expectedError = new Error('write error');
+    jest.spyOn(FileHelpers, 'writerToFileSystem').mockRejectedValue(expectedError);
+    jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new ConstrainedAnyModel('', undefined, ''), 'test', new InputMetaModel(), [])]);
+  
+    await expect(generator.generateToFiles(doc, '/test/', generatorOptions as any)).rejects.toEqual(expectedError);
+    expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
+    expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
+  });
+  test('should try and generate models to files', async () => {
+    const outputDir = './test';
+    const expectedOutputDirPath = path.resolve(outputDir);
+    const expectedOutputFilePath = path.resolve(`${outputDir}/test.${fileExtension}`);
+    const expectedWriteToFileParameters = [
+      'content',
+      expectedOutputFilePath,
+    ];
+    jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
+    jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new ConstrainedAnyModel('', undefined, ''), 'test', new InputMetaModel(), [])]);
+    
+    await generator.generateToFiles(doc, expectedOutputDirPath, generatorOptions as any);
+    expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
+    expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
+    expect((FileHelpers.writerToFileSystem as jest.Mock).mock.calls[0]).toEqual(expectedWriteToFileParameters);
+  });
+  test('should ignore models that have not been rendered', async () => {
+    const outputDir = './test';
+    const expectedOutputDirPath = path.resolve(outputDir);
+    jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
+    jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('', new ConstrainedAnyModel('', undefined, ''), '', new InputMetaModel(), [])]);
+    
+    const models = await generator.generateToFiles(doc, expectedOutputDirPath, generatorOptions as any);
+    expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
+    expect(models).toHaveLength(0);
+    expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(0);
+  });
+});
diff --git a/test/generators/csharp/CSharpFileGenerator.spec.ts b/test/generators/csharp/CSharpFileGenerator.spec.ts
deleted file mode 100644
index 181f3cb3..00000000
--- a/test/generators/csharp/CSharpFileGenerator.spec.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { CommonInputModel, CommonModel, FileHelpers, CSharpFileGenerator, OutputModel } from '../../../src';
-import * as path from 'path';
-
-describe('CSharpFileGenerator', () => {
-  afterEach(() => {
-    jest.restoreAllMocks();
-  });
-
-  describe('generateToFile()', () => {
-    const doc = {
-      $id: 'CustomClass',
-      type: 'object',
-      additionalProperties: true,
-      properties: {
-        someProp: { type: 'string' },
-        someEnum: {
-          $id: 'CustomEnum',
-          type: 'string',
-          enum: ['Texas', 'Alabama', 'California'],
-        }
-      }
-    };
-    test('should throw accurate error if file cannot be written', async () => {
-      const generator = new CSharpFileGenerator();
-      const expectedError = new Error('write error');
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockRejectedValue(expectedError);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), '', new CommonInputModel(), [])]);
-    
-      await expect(generator.generateToFiles(doc, '/test/', {namespace: 'SomeNamespace'})).rejects.toEqual(expectedError);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-    });
-    test('should try and generate models to files', async () => {
-      const generator = new CSharpFileGenerator();
-      const outputDir = './test';
-      const expectedOutputDirPath = path.resolve(outputDir);
-      const expectedOutputFilePath = path.resolve(`${outputDir}/Test.cs`);
-      const expectedWriteToFileParameters = [
-        'content',
-        expectedOutputFilePath,
-      ];
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), 'Test', new CommonInputModel(), [])]);
-      
-      await generator.generateToFiles(doc, expectedOutputDirPath, {namespace: 'SomeNamespace'});
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-      expect((FileHelpers.writerToFileSystem as jest.Mock).mock.calls[0]).toEqual(expectedWriteToFileParameters);
-    });
-  });
-});
diff --git a/test/generators/dart/DartFileGenerator.spec.ts b/test/generators/dart/DartFileGenerator.spec.ts
deleted file mode 100644
index 5577f767..00000000
--- a/test/generators/dart/DartFileGenerator.spec.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { CommonInputModel, CommonModel, FileHelpers, DartFileGenerator, OutputModel } from '../../../src';
-import * as path from 'path';
-
-describe('DartFileGenerator', () => {
-  afterEach(() => {
-    jest.restoreAllMocks();
-  });
-
-  describe('generateToFile()', () => {
-    const doc = {
-      $id: 'CustomClass',
-      type: 'object',
-      additionalProperties: true,
-      properties: {
-        someProp: { type: 'string' },
-        someEnum: {
-          $id: 'CustomEnum',
-          type: 'string',
-          enum: ['Texas', 'Alabama', 'California'],
-        }
-      }
-    };
-    test('should throw accurate error if file cannot be written', async () => {
-      const generator = new DartFileGenerator();
-      const expectedError = new Error('write error');
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockRejectedValue(expectedError);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), '', new CommonInputModel(), [])]);
-    
-      await expect(generator.generateToFiles(doc, '/test/', {packageName: 'SomePackage'})).rejects.toEqual(expectedError);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-    });
-    test('should try and generate models to files', async () => {
-      const generator = new DartFileGenerator();
-      const outputDir = './test';
-      const expectedOutputDirPath = path.resolve(outputDir);
-      const expectedOutputFilePath = path.resolve(`${outputDir}/test.dart`);
-      const expectedWriteToFileParameters = [
-        'content',
-        expectedOutputFilePath,
-      ];
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), 'test', new CommonInputModel(), [])]);
-      
-      await generator.generateToFiles(doc, expectedOutputDirPath, {packageName: 'SomePackage'});
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-      expect((FileHelpers.writerToFileSystem as jest.Mock).mock.calls[0]).toEqual(expectedWriteToFileParameters);
-    });
-  });
-});
diff --git a/test/generators/go/GoFileGenerator.spec.ts b/test/generators/go/GoFileGenerator.spec.ts
deleted file mode 100644
index 396e55b1..00000000
--- a/test/generators/go/GoFileGenerator.spec.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { CommonInputModel, CommonModel, FileHelpers, GoFileGenerator, OutputModel } from '../../../src';
-import * as path from 'path';
-
-describe('GoFileGenerator', () => {
-  afterEach(() => {
-    jest.restoreAllMocks();
-  });
-
-  describe('generateToFile()', () => {
-    const doc = {
-      $id: 'CustomClass',
-      type: 'object',
-      additionalProperties: true,
-      properties: {
-        someProp: { type: 'string' },
-        someEnum: {
-          $id: 'CustomEnum',
-          type: 'string',
-          enum: ['Texas', 'Alabama', 'California'],
-        }
-      }
-    };
-    test('should throw accurate error if file cannot be written', async () => {
-      const generator = new GoFileGenerator();
-      const expectedError = new Error('write error');
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockRejectedValue(expectedError);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), '', new CommonInputModel(), [])]);
-    
-      await expect(generator.generateToFiles(doc, '/test/', {packageName: 'some_package'})).rejects.toEqual(expectedError);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-    });
-    test('should try and generate models to files', async () => {
-      const generator = new GoFileGenerator();
-      const outputDir = './test';
-      const expectedOutputDirPath = path.resolve(outputDir);
-      const expectedOutputFilePath = path.resolve(`${outputDir}/Test.go`);
-      const expectedWriteToFileParameters = [
-        'content',
-        expectedOutputFilePath,
-      ];
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), 'Test', new CommonInputModel(), [])]);
-      
-      await generator.generateToFiles(doc, expectedOutputDirPath, {packageName: 'some_package'});
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-      expect((FileHelpers.writerToFileSystem as jest.Mock).mock.calls[0]).toEqual(expectedWriteToFileParameters);
-    });
-  });
-});
diff --git a/test/generators/java/JavaFileGenerator.spec.ts b/test/generators/java/JavaFileGenerator.spec.ts
deleted file mode 100644
index d3a09957..00000000
--- a/test/generators/java/JavaFileGenerator.spec.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { CommonInputModel, CommonModel, FileHelpers, JavaFileGenerator, OutputModel } from '../../../src';
-import * as path from 'path';
-
-describe('JavaFileGenerator', () => {
-  afterEach(() => {
-    jest.restoreAllMocks();
-  });
-
-  describe('generateToFile()', () => {
-    const doc = {
-      $id: 'CustomClass',
-      type: 'object',
-      additionalProperties: true,
-      properties: {
-        someProp: { type: 'string' },
-        someEnum: {
-          $id: 'CustomEnum',
-          type: 'string',
-          enum: ['Texas', 'Alabama', 'California'],
-        }
-      }
-    };
-    test('should throw accurate error if file cannot be written', async () => {
-      const generator = new JavaFileGenerator();
-      const expectedError = new Error('write error');
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockRejectedValue(expectedError);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), '', new CommonInputModel(), [])]);
-    
-      await expect(generator.generateToFiles(doc, '/test/', {packageName: 'SomePackage'})).rejects.toEqual(expectedError);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-    });
-    test('should try and generate models to files', async () => {
-      const generator = new JavaFileGenerator();
-      const outputDir = './test';
-      const expectedOutputDirPath = path.resolve(outputDir);
-      const expectedOutputFilePath = path.resolve(`${outputDir}/Test.java`);
-      const expectedWriteToFileParameters = [
-        'content',
-        expectedOutputFilePath,
-      ];
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), 'Test', new CommonInputModel(), [])]);
-      
-      await generator.generateToFiles(doc, expectedOutputDirPath, {packageName: 'SomePackage'});
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-      expect((FileHelpers.writerToFileSystem as jest.Mock).mock.calls[0]).toEqual(expectedWriteToFileParameters);
-    });
-  });
-});
diff --git a/test/generators/javascript/JavaScriptFileGenerator.spec.ts b/test/generators/javascript/JavaScriptFileGenerator.spec.ts
deleted file mode 100644
index 074201be..00000000
--- a/test/generators/javascript/JavaScriptFileGenerator.spec.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { CommonInputModel, CommonModel, FileHelpers, JavaScriptFileGenerator, OutputModel } from '../../../src';
-import * as path from 'path';
-
-describe('JavaScriptFileGenerator', () => {
-  afterEach(() => {
-    jest.restoreAllMocks();
-  });
-
-  describe('generateToFile()', () => {
-    const doc = {
-      $id: 'CustomClass',
-      type: 'object',
-      additionalProperties: true,
-      properties: {
-        someProp: { type: 'string' },
-        someEnum: {
-          $id: 'CustomEnum',
-          type: 'string',
-          enum: ['Texas', 'Alabama', 'California'],
-        }
-      }
-    };
-    test('should throw accurate error if file cannot be written', async () => {
-      const generator = new JavaScriptFileGenerator();
-      const expectedError = new Error('write error');
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockRejectedValue(expectedError);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), 'Test', new CommonInputModel(), [])]);
-    
-      await expect(generator.generateToFiles(doc, '/test/')).rejects.toEqual(expectedError);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-    });
-    test('should try and generate models to files', async () => {
-      const generator = new JavaScriptFileGenerator();
-      const outputDir = './test';
-      const expectedOutputDirPath = path.resolve(outputDir);
-      const expectedOutputFilePath = path.resolve(`${outputDir}/Test.js`);
-      const expectedWriteToFileParameters = [
-        'content',
-        expectedOutputFilePath,
-      ];
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), 'Test', new CommonInputModel(), [])]);
-      
-      await generator.generateToFiles(doc, expectedOutputDirPath);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-      expect((FileHelpers.writerToFileSystem as jest.Mock).mock.calls[0]).toEqual(expectedWriteToFileParameters);
-    });
-    test('should ignore models that have not been rendered', async () => {
-      const generator = new JavaScriptFileGenerator();
-      const outputDir = './test';
-      const expectedOutputDirPath = path.resolve(outputDir);
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), '', new CommonInputModel(), [])]);
-      
-      const models = await generator.generateToFiles(doc, expectedOutputDirPath);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(0);
-      expect(models).toHaveLength(0);
-    });
-  });
-});
diff --git a/test/generators/typescript/TypeScriptFileGenerator.spec.ts b/test/generators/typescript/TypeScriptFileGenerator.spec.ts
deleted file mode 100644
index cad65e64..00000000
--- a/test/generators/typescript/TypeScriptFileGenerator.spec.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { CommonInputModel, CommonModel, FileHelpers, TypeScriptFileGenerator, OutputModel } from '../../../src';
-import * as path from 'path';
-
-describe('TypeScriptFileGenerator', () => {
-  afterEach(() => {
-    jest.restoreAllMocks();
-  });
-
-  describe('generateToFile()', () => {
-    const doc = {
-      $id: 'CustomClass',
-      type: 'object',
-      additionalProperties: true,
-      properties: {
-        someProp: { type: 'string' },
-        someEnum: {
-          $id: 'CustomEnum',
-          type: 'string',
-          enum: ['Texas', 'Alabama', 'California'],
-        }
-      }
-    };
-    test('should throw accurate error if file cannot be written', async () => {
-      const generator = new TypeScriptFileGenerator();
-      const expectedError = new Error('write error');
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockRejectedValue(expectedError);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), 'Test', new CommonInputModel(), [])]);
-    
-      await expect(generator.generateToFiles(doc, '/test/')).rejects.toEqual(expectedError);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-    });
-    test('should try and generate models to files', async () => {
-      const generator = new TypeScriptFileGenerator();
-      const outputDir = './test';
-      const expectedOutputDirPath = path.resolve(outputDir);
-      const expectedOutputFilePath = path.resolve(`${outputDir}/Test.ts`);
-      const expectedWriteToFileParameters = [
-        'content',
-        expectedOutputFilePath,
-      ];
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), 'Test', new CommonInputModel(), [])]);
-      
-      await generator.generateToFiles(doc, expectedOutputDirPath);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
-      expect((FileHelpers.writerToFileSystem as jest.Mock).mock.calls[0]).toEqual(expectedWriteToFileParameters);
-    });
-
-    test('should ignore models that have not been rendered', async () => {
-      const generator = new TypeScriptFileGenerator();
-      const outputDir = './test';
-      const expectedOutputDirPath = path.resolve(outputDir);
-      jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
-      jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), '', new CommonInputModel(), [])]);
-      
-      const models = await generator.generateToFiles(doc, expectedOutputDirPath);
-      expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
-      expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(0);
-      expect(models).toHaveLength(0);
-    });
-  });
-});
diff --git a/test/helpers/Constraints.spec.ts b/test/helpers/Constraints.spec.ts
index 63a6028e..b5ef8a80 100644
--- a/test/helpers/Constraints.spec.ts
+++ b/test/helpers/Constraints.spec.ts
@@ -1,4 +1,5 @@
-import { NO_NUMBER_START_CHAR, NO_EMPTY_VALUE } from '../../src/helpers';
+import { NO_NUMBER_START_CHAR, NO_EMPTY_VALUE, NO_DUPLICATE_PROPERTIES, FormatHelpers, NO_DUPLICATE_ENUM_KEYS } from '../../src/helpers';
+import { AnyModel, ConstrainedAnyModel, ConstrainedEnumModel, ConstrainedEnumValueModel, ConstrainedObjectModel, ConstrainedObjectPropertyModel, EnumModel, EnumValueModel, ObjectModel, ObjectPropertyModel } from '../../src/models';
 
 describe('Constraints', () => {
   describe('NO_NUMBER_START_CHAR', () => {
@@ -22,4 +23,86 @@ describe('Constraints', () => {
       expect(renderedValue).toEqual('1');
     });
   });
+  describe('NO_DUPLICATE_PROPERTIES', () => {
+    test('should not do anything with no duplicate properties', () => {
+      const constrainedModel = new ConstrainedObjectModel('', undefined, '', {});
+      const metaModel = new ObjectModel('', undefined, {
+        Test: new ObjectPropertyModel('Test', false, new AnyModel('', undefined))
+      });
+      const renderedValue = NO_DUPLICATE_PROPERTIES(constrainedModel, metaModel, 'Test', FormatHelpers.toPascalCase);
+      expect(renderedValue).toEqual('Test');
+    });
+    test('should not use formatted property name which another raw property is', () => {
+      const constrainedModel = new ConstrainedObjectModel('', undefined, '', {});
+      const metaModel = new ObjectModel('', undefined, {
+        test: new ObjectPropertyModel('test', false, new AnyModel('', undefined)),
+        Test: new ObjectPropertyModel('Test', false, new AnyModel('', undefined))
+      });
+      const renderedValue = NO_DUPLICATE_PROPERTIES(constrainedModel, metaModel, 'test', FormatHelpers.toPascalCase);
+      expect(renderedValue).toEqual('reserved_test');
+    });
+    test('should should keep the raw property name if no clash', () => {
+      const constrainedModel = new ConstrainedObjectModel('', undefined, '', {});
+      const metaModel = new ObjectModel('', undefined, {
+        test: new ObjectPropertyModel('test', false, new AnyModel('', undefined)),
+        Test: new ObjectPropertyModel('Test', false, new AnyModel('', undefined))
+      });
+      const renderedValue = NO_DUPLICATE_PROPERTIES(constrainedModel, metaModel, 'Test', FormatHelpers.toPascalCase);
+      expect(renderedValue).toEqual('Test');
+    });
+    test('should be able to handle multiple reserved properties', () => {
+      const constrainedModel = new ConstrainedObjectModel('', undefined, '', {
+        Test: new ConstrainedObjectPropertyModel('Test', false, new ConstrainedAnyModel('', undefined, '')),
+        ReservedTest: new ConstrainedObjectPropertyModel('ReservedTest', false, new ConstrainedAnyModel('', undefined, ''))
+      });
+      const metaModel = new ObjectModel('', undefined, {
+        test: new ObjectPropertyModel('test', false, new AnyModel('', undefined)),
+        Test: new ObjectPropertyModel('Test', false, new AnyModel('', undefined)),
+        ReservedTest: new ObjectPropertyModel('ReservedTest', false, new AnyModel('', undefined))
+      });
+      const renderedValue = NO_DUPLICATE_PROPERTIES(constrainedModel, metaModel, 'test', FormatHelpers.toPascalCase);
+      expect(renderedValue).toEqual('reserved_reserved_test');
+    });
+  });
+  describe('NO_DUPLICATE_ENUM_KEYS', () => {
+    test('should not do anything with no duplicate enum keys', () => {
+      const constrainedModel = new ConstrainedEnumModel('', undefined, '', []);
+      const metaModel = new EnumModel('', undefined, [
+        new EnumValueModel('test', new AnyModel('', undefined))
+      ]);
+      const renderedValue = NO_DUPLICATE_ENUM_KEYS(constrainedModel, metaModel, 'test', FormatHelpers.toPascalCase);
+      expect(renderedValue).toEqual('test');
+    });
+    test('should not use formatted enum key which another raw enum key is', () => {
+      const constrainedModel = new ConstrainedEnumModel('', undefined, '', []);
+      const metaModel = new EnumModel('', undefined, [
+        new EnumValueModel('test', new AnyModel('', undefined)),
+        new EnumValueModel('Test', new AnyModel('', undefined))
+      ]);
+      const renderedValue = NO_DUPLICATE_ENUM_KEYS(constrainedModel, metaModel, 'test', FormatHelpers.toPascalCase);
+      expect(renderedValue).toEqual('reserved_test');
+    });
+    test('should should keep the raw enum key if no clash', () => {
+      const constrainedModel = new ConstrainedEnumModel('', undefined, '', []);
+      const metaModel = new EnumModel('', undefined, [
+        new EnumValueModel('test', new AnyModel('', undefined)),
+        new EnumValueModel('Test', new AnyModel('', undefined))
+      ]);
+      const renderedValue = NO_DUPLICATE_ENUM_KEYS(constrainedModel, metaModel, 'Test', FormatHelpers.toPascalCase);
+      expect(renderedValue).toEqual('Test');
+    });
+    test('should be able to handle multiple reserved enum keys', () => {
+      const constrainedModel = new ConstrainedEnumModel('', undefined, '', [
+        new ConstrainedEnumValueModel('Test', new AnyModel('', undefined)),
+        new ConstrainedEnumValueModel('ReservedTest', new AnyModel('', undefined))
+      ]);
+      const metaModel = new EnumModel('', undefined, [
+        new EnumValueModel('test', new AnyModel('', undefined)),
+        new EnumValueModel('Test', new AnyModel('', undefined)),
+        new EnumValueModel('ReservedTest', new AnyModel('', undefined))
+      ]);
+      const renderedValue = NO_DUPLICATE_ENUM_KEYS(constrainedModel, metaModel, 'test', FormatHelpers.toPascalCase);
+      expect(renderedValue).toEqual('reserved_reserved_test');
+    });
+  });
 });