UNPKG

jinaga

Version:

Data management for web and mobile applications.

688 lines (670 loc) 24.9 kB
import { Specification, SpecificationParser } from "../../src"; function parseSpecification(input: string): Specification { const parser = new SpecificationParser(input); parser.skipWhitespace(); return parser.parseSpecification(); } describe("Specification parser", () => { it("parses an identity specification", () => { const specification = parseSpecification(`(root: Root) { }`); const expected: Specification = { given: [ { name: "root", type: "Root" } ], matches: [], projection: { type: "composite", components: [] } }; expect(specification).toEqual(expected); }); it("parses a simple specification", () => { const specification = parseSpecification(` (parent: MyApp.Parent) { child: MyApp.Child [ child->parent:MyApp.Parent = parent ] }`); const expected: Specification = { given: [ { name: "parent", type: "MyApp.Parent" } ], matches: [ { unknown: { name: "child", type: "MyApp.Child" }, conditions: [ { type: "path", rolesLeft: [ { name: "parent", predecessorType: "MyApp.Parent" } ], labelRight: "parent", rolesRight: [] } ] } ], projection: { type: "composite", components: [] } }; expect(specification).toEqual(expected); }); it("requires at least one given", () => { expect(() => parseSpecification("() { }")).toThrowError( /The specification must contain at least one given label/ ); }); it("requires at least one condition", () => { expect(() => parseSpecification(` (parent: MyApp.Parent) { child: MyApp.Child [] }`)).toThrowError( /The match for 'child' has no conditions/ ); }); it("requires that each label be different from given", () => { expect(() => parseSpecification(` (parent: MyApp.Parent) { parent: MyApp.Parent [ parent->parent:MyApp.Parent = parent ] }`)).toThrowError( /The name 'parent' has already been used/ ); }); it("requires that each label be unique", () => { expect(() => parseSpecification(` (parent: MyApp.Parent) { child: MyApp.Child [ child->parent:MyApp.Parent = parent ] child: MyApp.Child [ child->parent:MyApp.Parent = child ] }`)).toThrowError( /The name 'child' has already been used/ ); }); it("requires that the left label be the unknown", () => { expect(() => parseSpecification(` (parent: MyApp.Parent) { child: MyApp.Child [ parent = child->parent:MyApp.Parent ] }`)).toThrowError( /The unknown 'child' must appear on the left side of the path/ ); }); it("requires that a label be defined before use", () => { expect(() => parseSpecification(` (parent: MyApp.Parent) { child: MyApp.Child [ child->parent:MyApp.Parent = sibling ] }`)).toThrowError( /The label 'sibling' has not been defined/ ); }); it("accepts multiple givens", () => { const specification = parseSpecification(` (user: Jinaga.User, company: MyApp.Company) { assignment: MyApp.Assignment [ assignment->user:Jinaga.User = user assignment->project:MyApp.Project->company:MyApp.Company = company ] }`); const expected: Specification = { given: [ { name: "user", type: "Jinaga.User" }, { name: "company", type: "MyApp.Company" } ], matches: [ { unknown: { name: "assignment", type: "MyApp.Assignment" }, conditions: [ { type: "path", rolesLeft: [ { name: "user", predecessorType: "Jinaga.User" } ], labelRight: "user", rolesRight: [] }, { type: "path", rolesLeft: [ { name: "project", predecessorType: "MyApp.Project" }, { name: "company", predecessorType: "MyApp.Company" } ], labelRight: "company", rolesRight: [] } ] } ], projection: { type: "composite", components: [] } }; expect(specification).toEqual(expected); }); it("recognizes existential conditions", () => { const specification = parseSpecification(` (user: Jinaga.User, company: MyApp.Company) { assignment: MyApp.Assignment [ assignment->user:Jinaga.User = user assignment->project:MyApp.Project->company:MyApp.Company = company !E { revoked: MyApp.Assignment.Revoked [ revoked->assignment:MyApp.Assignment = assignment ] } ] }`); const expected: Specification = { given: [ { name: "user", type: "Jinaga.User" }, { name: "company", type: "MyApp.Company" } ], matches: [ { unknown: { name: "assignment", type: "MyApp.Assignment" }, conditions: [ { type: "path", rolesLeft: [ { name: "user", predecessorType: "Jinaga.User" } ], labelRight: "user", rolesRight: [] }, { type: "path", rolesLeft: [ { name: "project", predecessorType: "MyApp.Project" }, { name: "company", predecessorType: "MyApp.Company" } ], labelRight: "company", rolesRight: [] }, { type: "existential", exists: false, matches: [ { unknown: { name: "revoked", type: "MyApp.Assignment.Revoked" }, conditions: [ { type: "path", rolesLeft: [ { name: "assignment", predecessorType: "MyApp.Assignment" } ], labelRight: "assignment", rolesRight: [] } ] } ] } ] } ], projection: { type: "composite", components: [] } }; expect(specification).toEqual(expected); }); it("requires that the existential condition be based on the unknown", () => { expect(() => parseSpecification(` (user: Jinaga.User, company: MyApp.Company) { assignment: MyApp.Assignment [ assignment->user:Jinaga.User = user assignment->project:MyApp.Project->company:MyApp.Company = company !E { revoked: MyApp.Assignment.Revoked [ revoked->assignment:MyApp.Assignment->user:Jinaga.User = user ] } ] }`)).toThrowError( /The existential condition must be based on the unknown 'assignment'/ ); }); it("accepts projections", () => { const specification = parseSpecification(` (user: Jinaga.User) { assignment: MyApp.Assignment [ assignment->user:Jinaga.User = user ] } => { descriptions = { description: MyApp.Assignment.Description [ description->assignment:MyApp.Assignment = assignment ] } }`); const expected: Specification = { given: [ { name: "user", type: "Jinaga.User" } ], matches: [ { unknown: { name: "assignment", type: "MyApp.Assignment" }, conditions: [ { type: "path", rolesLeft: [ { name: "user", predecessorType: "Jinaga.User" } ], labelRight: "user", rolesRight: [] } ] } ], projection: { type: "composite", components: [ { type: "specification", name: "descriptions", matches: [ { unknown: { name: "description", type: "MyApp.Assignment.Description" }, conditions: [ { type: "path", rolesLeft: [ { name: "assignment", predecessorType: "MyApp.Assignment" } ], labelRight: "assignment", rolesRight: [] } ] } ], projection: { type: "composite", components: [] } } ] } }; expect(specification).toEqual(expected); }); it("accepts projections on projections", () => { const specification = parseSpecification(` (user: Jinaga.User) { assignment: MyApp.Assignment [ assignment->user:Jinaga.User = user ] } => { projects = { project: MyApp.Project [ project->assignment:MyApp.Assignment = assignment ] } => { descriptions = { description: MyApp.Project.Description [ description->project:MyApp.Project = project ] } } }`); const expected: Specification = { given: [ { name: "user", type: "Jinaga.User" } ], matches: [ { unknown: { name: "assignment", type: "MyApp.Assignment" }, conditions: [ { type: "path", rolesLeft: [ { name: "user", predecessorType: "Jinaga.User" } ], labelRight: "user", rolesRight: [] } ] } ], projection: { type: "composite", components: [ { type: "specification", name: "projects", matches: [ { unknown: { name: "project", type: "MyApp.Project" }, conditions: [ { type: "path", rolesLeft: [ { name: "assignment", predecessorType: "MyApp.Assignment" } ], labelRight: "assignment", rolesRight: [] } ] } ], projection: { type: "composite", components: [ { type: "specification", name: "descriptions", matches: [ { unknown: { name: "description", type: "MyApp.Project.Description" }, conditions: [ { type: "path", rolesLeft: [ { name: "project", predecessorType: "MyApp.Project" } ], labelRight: "project", rolesRight: [] } ] } ], projection: { type: "composite", components: [] } } ] } } ] } }; expect(specification).toEqual(expected); }); it("accepts field accessors", () => { const specification = parseSpecification(` (user: Jinaga.User) { name: MyApp.User.Name [ name->user:Jinaga.User = user ] } => { value = name.value }`); const expected: Specification = { given: [ { name: "user", type: "Jinaga.User" } ], matches: [ { unknown: { name: "name", type: "MyApp.User.Name" }, conditions: [ { type: "path", rolesLeft: [ { name: "user", predecessorType: "Jinaga.User" } ], labelRight: "user", rolesRight: [] } ] } ], projection: { type: "composite", components: [ { type: "field", name: "value", label: "name", field: "value" } ] } }; expect(specification).toEqual(expected); }); it("accepts fact component", () => { const specification = parseSpecification(` (user: Jinaga.User) { name: MyApp.User.Name [ name->user:Jinaga.User = user ] } => { userName = name }`); const expected: Specification = { given: [ { name: "user", type: "Jinaga.User" } ], matches: [ { unknown: { name: "name", type: "MyApp.User.Name" }, conditions: [ { type: "path", rolesLeft: [ { name: "user", predecessorType: "Jinaga.User" } ], labelRight: "user", rolesRight: [] } ] } ], projection: { type: "composite", components: [ { type: "fact", name: "userName", label: "name" } ] } }; expect(specification).toEqual(expected); }); it("accepts hash result", () => { const specification = parseSpecification(` (user: Jinaga.User) { name: MyApp.User.Name [ name->user:Jinaga.User = user ] } => #name`); const expected: Specification = { given: [ { name: "user", type: "Jinaga.User" } ], matches: [ { unknown: { name: "name", type: "MyApp.User.Name" }, conditions: [ { type: "path", rolesLeft: [ { name: "user", predecessorType: "Jinaga.User" } ], labelRight: "user", rolesRight: [] } ] } ], projection: { type: "hash", label: "name" } }; expect(specification).toEqual(expected); }); it("accepts fact result", () => { const specification = parseSpecification(` (user: Jinaga.User) { name: MyApp.User.Name [ name->user:Jinaga.User = user ] } => name`); const expected: Specification = { given: [ { name: "user", type: "Jinaga.User" } ], matches: [ { unknown: { name: "name", type: "MyApp.User.Name" }, conditions: [ { type: "path", rolesLeft: [ { name: "user", predecessorType: "Jinaga.User" } ], labelRight: "user", rolesRight: [] } ] } ], projection: { type: "fact", label: "name" } }; expect(specification).toEqual(expected); }); });