diff --git a/extensions/crossmodel-lang/test/language-server/cross-model-lang-relationship.test.ts b/extensions/crossmodel-lang/test/language-server/cross-model-lang-relationship.test.ts index dc6ed39..dab62b4 100644 --- a/extensions/crossmodel-lang/test/language-server/cross-model-lang-relationship.test.ts +++ b/extensions/crossmodel-lang/test/language-server/cross-model-lang-relationship.test.ts @@ -3,20 +3,32 @@ ********************************************************************************/ import { describe, expect, test } from '@jest/globals'; -import { EmptyFileSystem, isReference } from 'langium'; +import { isReference } from 'langium'; -import { relationship1, relationship2 } from './test-utils/test-documents/relationship/index.js'; -import { parseDocument } from './test-utils/utils.js'; +import { + relationship1, + relationship2, + relationship_with_attribute, + relationship_with_attribute_wrong_entity, + relationship_with_duplicate_attributes +} from './test-utils/test-documents/relationship/index.js'; +import { createCrossModelTestServices, parseAndForgetDocument, parseDocuments } from './test-utils/utils.js'; -import { createCrossModelServices } from '../../src/language-server/cross-model-module.js'; import { CrossModelRoot } from '../../src/language-server/generated/ast.js'; +import { address } from './test-utils/test-documents/entity/address.js'; +import { customer } from './test-utils/test-documents/entity/customer.js'; +import { order } from './test-utils/test-documents/entity/order.js'; -const services = createCrossModelServices({ ...EmptyFileSystem }).CrossModel; +const services = createCrossModelTestServices(); describe('CrossModel language Relationship', () => { + beforeAll(() => { + parseDocuments(services, [order, customer, address]); + }); + test('Simple file for relationship', async () => { const document = relationship1; - const parsedDocument = await parseDocument(services, document); + const parsedDocument = await parseAndForgetDocument(services, document); const model = parsedDocument.parseResult.value as CrossModelRoot; expect(model).toHaveProperty('relationship'); @@ -36,11 +48,47 @@ describe('CrossModel language Relationship', () => { test('relationship with indentation error', async () => { const document = relationship2; - const parsedDocument = await parseDocument(services, document); + const parsedDocument = await parseAndForgetDocument(services, document); const model = parsedDocument.parseResult.value as CrossModelRoot; expect(model).toHaveProperty('relationship'); expect(parsedDocument.parseResult.lexerErrors.length).toBe(0); expect(parsedDocument.parseResult.parserErrors.length).toBe(1); }); + + test('relationship with attributes', async () => { + const parsedDocument = await parseAndForgetDocument(services, relationship_with_attribute, { + validation: true + }); + const model = parsedDocument.parseResult.value as CrossModelRoot; + + expect(model).toHaveProperty('relationship'); + expect(parsedDocument.parseResult.lexerErrors.length).toBe(0); + expect(parsedDocument.parseResult.parserErrors.length).toBe(0); + expect(parsedDocument.diagnostics).toHaveLength(0); + }); + + test('relationship with wrong entity', async () => { + const parsedDocument = await parseAndForgetDocument(services, relationship_with_attribute_wrong_entity, { + validation: true + }); + const model = parsedDocument.parseResult.value as CrossModelRoot; + + expect(model).toHaveProperty('relationship'); + expect(parsedDocument.parseResult.lexerErrors.length).toBe(0); + expect(parsedDocument.parseResult.parserErrors.length).toBe(0); + expect(parsedDocument.diagnostics).toHaveLength(1); + }); + + test('relationship with duplicates', async () => { + const parsedDocument = await parseAndForgetDocument(services, relationship_with_duplicate_attributes, { + validation: true + }); + const model = parsedDocument.parseResult.value as CrossModelRoot; + + expect(model).toHaveProperty('relationship'); + expect(parsedDocument.parseResult.lexerErrors.length).toBe(0); + expect(parsedDocument.parseResult.parserErrors.length).toBe(0); + expect(parsedDocument.diagnostics).toHaveLength(2); + }); }); diff --git a/extensions/crossmodel-lang/test/language-server/serializer/cross-model-serializer.test.ts b/extensions/crossmodel-lang/test/language-server/serializer/cross-model-serializer.test.ts index 633e5cf..6d5a5f3 100644 --- a/extensions/crossmodel-lang/test/language-server/serializer/cross-model-serializer.test.ts +++ b/extensions/crossmodel-lang/test/language-server/serializer/cross-model-serializer.test.ts @@ -116,7 +116,8 @@ describe('CrossModelLexer', () => { name: 'test Name', parent: ref1, child: ref2, - type: 'n:m' + type: 'n:m', + attributes: [] }; }); @@ -168,7 +169,8 @@ describe('CrossModelLexer', () => { name: 'test Name', parent: ref1, child: ref2, - type: 'n:m' + type: 'n:m', + attributes: [] } }; diff --git a/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/address.ts b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/address.ts new file mode 100644 index 0000000..e9d2d13 --- /dev/null +++ b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/address.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +export const address = `entity: + id: Address + name: "Address" + attributes: + - id: CustomerID + name: "CustomerID" + datatype: "Integer" + - id: Street + name: "Street" + datatype: "Varchar" + - id: CountryCode + name: "CountryCode" + datatype: "Varchar"`; diff --git a/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/customer.ts b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/customer.ts new file mode 100644 index 0000000..1957315 --- /dev/null +++ b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/customer.ts @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +export const customer = `entity: + id: Customer + name: "Customer" + attributes: + - id: Id + name: "Id" + datatype: "Integer" + - id: FirstName + name: "FirstName" + datatype: "Varchar" + - id: LastName + name: "LastName" + datatype: "Varchar" + - id: City + name: "City" + datatype: "Varchar" + - id: Country + name: "Country" + datatype: "Varchar" + - id: Phone + name: "Phone" + datatype: "Varchar" + - id: BirthDate + name: "BirthDate" + datatype: "DateTime"`; diff --git a/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/index.ts b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/index.ts index 62de41f..74b2fd5 100644 --- a/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/index.ts +++ b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/index.ts @@ -1,7 +1,10 @@ /******************************************************************************** * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ +export * from './address.js'; +export * from './customer.js'; export * from './entity1.js'; export * from './entity2.js'; export * from './entity3.js'; export * from './entity4.js'; +export * from './order.js'; diff --git a/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/order.ts b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/order.ts new file mode 100644 index 0000000..c9d22b9 --- /dev/null +++ b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/entity/order.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +export const order = `entity: + id: Order + name: "Order" + description: "Order placed by a customer in the Customer table." + attributes: + - id: Id + name: "Id" + datatype: "Integer" + - id: OrderDate + name: "OrderDate" + datatype: "Integer" + - id: OrderNumber + name: "OrderNumber" + datatype: "Varchar" + - id: CustomerId + name: "CustomerId" + datatype: "Integer" + - id: TotalAmount + name: "TotalAmount" + datatype: "Float"`; diff --git a/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/relationship/index.ts b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/relationship/index.ts index eaf440c..456bfc9 100644 --- a/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/relationship/index.ts +++ b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/relationship/index.ts @@ -3,3 +3,4 @@ ********************************************************************************/ export * from './relationship1.js'; export * from './relationship2.js'; +export * from './relationship_attribute.js'; diff --git a/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/relationship/relationship_attribute.ts b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/relationship/relationship_attribute.ts new file mode 100644 index 0000000..2330adf --- /dev/null +++ b/extensions/crossmodel-lang/test/language-server/test-utils/test-documents/relationship/relationship_attribute.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +/** Valid relationship with attribute */ +export const relationship_with_attribute = `relationship: + id: Order_Customer + parent: Customer + child: Order + type: "1:1" + attributes: + - parent: Customer.Id + child: Order.CustomerId`; + +/** Relationship with invalid attribute (wrong entity) */ +export const relationship_with_attribute_wrong_entity = `relationship: + id: Order_Customer + parent: Customer + child: Order + type: "1:1" + attributes: + - parent: Customer.Id + child: Order.Address`; + +/** Relationship with invalid attribute (duplicates) */ +export const relationship_with_duplicate_attributes = `relationship: + id: Order_Customer + parent: Customer + child: Order + type: "1:1" + attributes: + - parent: Customer.Id + child: Order.CustomerId + - parent: Customer.Id + child: Order.CustomerId`; diff --git a/extensions/crossmodel-lang/test/language-server/test-utils/utils.ts b/extensions/crossmodel-lang/test/language-server/test-utils/utils.ts index 08362a7..e3b9c9f 100644 --- a/extensions/crossmodel-lang/test/language-server/test-utils/utils.ts +++ b/extensions/crossmodel-lang/test/language-server/test-utils/utils.ts @@ -1,26 +1,33 @@ /******************************************************************************** * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { AstNode, LangiumDocument, LangiumServices } from 'langium'; -import { URI } from 'vscode-uri'; -export async function parseDocument(services: LangiumServices, input: string): Promise> { - const document = await parseHelper(services)(input); - if (!document.parseResult) { - throw new Error('Could not parse document'); - } - return document; +import { AstNode, DefaultLangiumDocuments, EmptyFileSystem, LangiumDocument, LangiumServices } from 'langium'; +import { ParseHelperOptions, clearDocuments, parseDocument } from 'langium/test'; +import { CrossModelServices, createCrossModelServices } from '../../../src/language-server/cross-model-module.js'; + +export { parseDocument } from 'langium/test'; + +export function createCrossModelTestServices(): CrossModelServices { + const services = createCrossModelServices({ ...EmptyFileSystem }).CrossModel; + services.shared.workspace.LangiumDocuments = new DefaultLangiumDocuments(services.shared); + return services; } -export function parseHelper(services: LangiumServices): (input: string) => Promise> { - const metaData = services.LanguageMetaData; - const documentBuilder = services.shared.workspace.DocumentBuilder; - return async input => { - const randomNumber = Math.floor(Math.random() * 10000000) + 1000000; - const uri = URI.parse(`file:///${randomNumber}${metaData.fileExtensions[0]}`); - const document = services.shared.workspace.LangiumDocumentFactory.fromString(input, uri); - services.shared.workspace.LangiumDocuments.addDocument(document); - await documentBuilder.build([document]); - return document; - }; +export async function parseDocuments( + services: LangiumServices, + inputs: string[], + options?: ParseHelperOptions +): Promise[]> { + return Promise.all(inputs.map(input => parseDocument(services, input, options))); +} + +export async function parseAndForgetDocument( + services: LangiumServices, + input: string, + options?: ParseHelperOptions +): Promise> { + const document = await parseDocument(services, input, options); + await clearDocuments(services, [document]); + return document; }