Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Socket
Sign inDemoInstall

@financial-times/biz-ops-schema

Package Overview
Dependencies
Maintainers
15
Versions
151
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@financial-times/biz-ops-schema - npm Package Compare versions

Comparing version 0.1.0 to 0.2.0

5

CONTRIBUTING.md

@@ -31,2 +31,7 @@ # Contributing to the biz-ops model

label: Code //Short label to be used when displaying this field in forms etc.
fieldset: main // Optional fieldset that this property belongs to. Useful for grouping related properties
fieldsets:
main: // name of a fieldset
heading: Main properties // heading that can be used in UIs that group properties by fieldset
description: These are all essential to fill out // description fo the fieldset
```

@@ -33,0 +38,0 @@

2

lib/raw-data.js

@@ -5,3 +5,2 @@ const readYaml = require('./read-yaml');

types: readYaml.directory('types'),
relationships: readYaml.file('relationships.yaml'),
stringPatterns: readYaml.file('string-patterns.yaml'),

@@ -13,5 +12,4 @@ enums: readYaml.file('enums.yaml')

getTypes: () => rawData.types,
getRelationships: () => rawData.relationships,
getStringPatterns: () => rawData.stringPatterns,
getEnums: () => rawData.enums
};

@@ -40,3 +40,3 @@ const getEnums = require('../methods/get-enums');

[
({ neo4jName }) => relationshipType === neo4jName,
({ relationship }) => relationshipType === relationship,
`${relationshipType} is not a valid relationship on ${nodeType}`

@@ -43,0 +43,0 @@ ],

@@ -42,3 +42,3 @@ const getTypes = require('../methods/get-types').method;

statement: "MATCH (this)${relFragment(
def.neo4jName,
def.relationship,
def.direction,

@@ -49,5 +49,5 @@ '*1..20'

} else {
return `@relation(name: "${def.neo4jName}", direction: "${graphqlDirection(
def.direction
)}")`;
return `@relation(name: "${
def.relationship
}", direction: "${graphqlDirection(def.direction)}")`;
}

@@ -54,0 +54,0 @@ };

@@ -9,6 +9,6 @@ const rawData = require('../lib/raw-data');

obj,
{ neo4jName, direction, endNode, hasMany, name, description, label }
{ relationship, direction, endNode, hasMany, name, description, label }
) => {
obj[neo4jName] = obj[neo4jName] || [];
obj[neo4jName].push({
obj[relationship] = obj[relationship] || [];
obj[relationship].push({
direction,

@@ -27,7 +27,12 @@ nodeType: endNode,

const createGraphqlRelationship = (
{ name, description, label },
isRecursive,
{ hasMany, direction, neo4jName, endNode }
) => ({
const createGraphqlRelationship = ({
name,
description,
label,
hasMany,
direction,
relationship,
endNode,
isRecursive
}) => ({
type: endNode,

@@ -37,5 +42,5 @@ hasMany,

name,
isRecursive,
isRecursive: !!isRecursive,
isRelationship: true,
neo4jName,
relationship,
description,

@@ -46,74 +51,19 @@ label

const graphqlRelationships = relationships => {
return relationships.reduce((arr, def) => {
if (def.name) {
arr.push(createGraphqlRelationship(def, false, def));
}
if (def.recursiveName) {
arr.push(
createGraphqlRelationship(
{
name: def.recursiveName,
description: def.recursiveDescription,
label: def.recursiveLabel
},
true,
def
)
);
}
return arr;
}, []);
return relationships
.filter(({ hidden }) => !hidden)
.map(createGraphqlRelationship);
};
const buildTwinRelationships = ({
neo4jName,
cardinality,
fromType,
toType
}) => {
const startNode = fromType.type;
const endNode = toType.type;
const normalize = (propName, def, typeName) => {
const obj = Object.assign({}, def, {
endNode: def.type,
hasMany: !!def.hasMany,
name: propName,
startNode: typeName
});
return [
Object.assign({}, fromType, {
startNode,
endNode,
neo4jName,
direction: 'outgoing',
hasMany: /MANY$/.test(cardinality)
}),
Object.assign({}, toType, {
startNode: endNode,
endNode: startNode,
neo4jName,
direction: 'incoming',
hasMany: /^MANY/.test(cardinality)
})
].map(obj => {
delete obj.type;
return obj;
});
delete obj.type;
return obj;
};
const getNormalizedRawData = cache.cacheify(
() => {
return deepFreeze(
Object.entries(rawData.getRelationships()).reduce(
(configsList, [neo4jName, definitions]) => {
if (!Array.isArray(definitions)) {
definitions = [definitions];
}
return configsList.concat(
definitions.map(definition =>
Object.assign({ neo4jName }, definition)
)
);
},
[]
)
);
},
() => 'relationships:normalizedRawData'
);
const getRelationships = (

@@ -123,11 +73,6 @@ typeName = undefined,

) => {
let relationships = getNormalizedRawData()
.filter(({ fromType, toType }) =>
[fromType.type, toType.type].includes(typeName)
)
.reduce(
(list, definition) => list.concat(buildTwinRelationships(definition)),
[]
)
.filter(({ startNode }) => startNode === typeName);
const type = rawData.getTypes().find(({ name }) => name === typeName);
let relationships = Object.entries(type.properties || {})
.filter(([, { relationship }]) => !!relationship)
.map(([key, val]) => normalize(key, val, typeName));

@@ -134,0 +79,0 @@ if (structure === 'rest') {

@@ -9,2 +9,5 @@ const rawData = require('../lib/raw-data');

const entriesArrayToObject = arr =>
arr.reduce((obj, [name, val]) => Object.assign(obj, { [name]: val }), {});
const getType = (

@@ -14,4 +17,4 @@ typeName,

primitiveTypes = 'biz-ops', // graphql
relationshipStructure = false // flat, rest, graphql
// groupProperties = false
relationshipStructure = false, // flat, rest, graphql
groupProperties = false
} = {}

@@ -32,3 +35,16 @@ ) => {

type.properties = Object.entries(type.properties)
if (relationshipStructure) {
const relationships = getRelationships.method(type.name, {
structure: relationshipStructure
});
if (relationshipStructure === 'graphql') {
relationships.forEach(def => {
type.properties[def.name] = def;
});
} else {
type.relationships = relationships;
}
}
const properties = Object.entries(type.properties)
.map(([name, def]) => {

@@ -38,3 +54,3 @@ if (primitiveTypes === 'graphql') {

// documents are too big to be served by graphql
return
return;
}

@@ -47,19 +63,63 @@ // If not a primitive type we assume it's an enum and leave it unaltered

}
return [name, def]
return [name, def];
})
.filter(entry => !!entry)
.reduce((obj, [name, def])=> Object.assign(obj, {[name]: def}), {});
.filter(entry => !!entry);
if (relationshipStructure) {
const relationships = getRelationships.method(type.name, {
structure: relationshipStructure
});
if (relationshipStructure === 'graphql') {
relationships.forEach(def => {
type.properties[def.name] = def;
});
} else {
type.relationships = relationships;
}
if (!groupProperties) {
type.properties = entriesArrayToObject(properties);
} else {
const virtualFieldsetProperties = properties.filter(
([, { fieldset }]) => fieldset === 'self'
);
const realFieldsetProperties = properties.filter(
([, { fieldset }]) => fieldset && fieldset !== 'self'
);
const miscProperties = properties.filter(([, { fieldset }]) => !fieldset);
const realFieldsets = Object.entries(type.fieldsets || {})
.map(([fieldsetName, fieldsetDef]) => {
fieldsetDef.properties = entriesArrayToObject(
realFieldsetProperties.filter(
([, { fieldset }]) => fieldset === fieldsetName
)
);
return [fieldsetName, fieldsetDef];
})
.filter(([, { properties }]) => !!Object.keys(properties).length);
const virtualFieldsets = virtualFieldsetProperties.map(
([propertyName, propertyDef]) => {
return [
propertyName,
{
heading: propertyDef.label,
description: propertyDef.description,
properties: { [propertyName]: propertyDef }
}
];
}
);
const miscellaneous = miscProperties.length
? [
[
'misc',
{
heading: realFieldsets.length ? 'Miscellaneous' : 'General',
properties: entriesArrayToObject(miscProperties)
}
]
]
: [];
type.fieldsets = entriesArrayToObject(
[].concat(realFieldsets, virtualFieldsets, miscellaneous)
);
delete type.properties;
}
return deepFreeze(type);

@@ -66,0 +126,0 @@ };

{
"name": "@financial-times/biz-ops-schema",
"version": "0.1.0",
"version": "0.2.0",
"description": "Schema for biz-ops data store and api. It provides two things: - yaml files which define which types, properties and relationships are allowed - a nodejs library for extracting subsets of this information",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -19,3 +19,5 @@ # biz-ops-schema

- `relationshipStructure` [default: `flat`]: Include the relationships for the type. Can take any value accepted by `getRelationships()` options. If it is set to `graphql` then the relationships are assigned to the `properties` object of the type as additional entries. Otherwise, they are assigned to a separate, top-level property, 'relationships'
- `relationshipStructure` [default: `false`]: Include the relationships for the type. Can take any value accepted by `getRelationships()` options. If it is set to `graphql` then the relationships are assigned to the `properties` object of the type as additional entries. Otherwise, they are assigned to a separate, top-level property, 'relationships'
- `primitiveTypes` [default: `'biz-ops'`]: Graphql only has 4 primitive types - String, Boolean, Int and Float - whereas the biz-ops ecosystem recognises a richer variety e.g Document, Url. They are stored in the schema as these biz-ops types. Setting `primitiveTypes: 'graphql'` will output property type names converted to their graphql equivalent. This option shouldn't really be needed by anywhere other than the graphql server
- `groupProperties` [default: `false`]: Each property may have a `section` attribute. Setting `groupProperties: true` removes the `properties` object from the data, and replaces it with `sections`, where all properties are grouped by section

@@ -22,0 +24,0 @@ ### getAllTypes(options)

@@ -13,6 +13,25 @@ const rawData = require('../../lib/raw-data');

const getTwinnedRelationship = (
homeTypeName,
awayTypeName,
relationshipName,
homeDirection
) => {
const awayType = types.find(({ name }) => name === awayTypeName);
return Object.values(awayType.properties).find(
({ relationship, type, direction }) =>
relationship === relationshipName &&
type === homeTypeName &&
direction !== homeDirection
);
};
describe('data quality: types', () => {
const validEnums = Object.keys(enums);
const validStringPatterns = Object.keys(stringPatterns);
const validPropTypes = validEnums.concat(Object.keys(primitiveTypesMap));
const typeNames = types.map(({ name }) => name);
const validPropTypes = validEnums.concat(
Object.keys(primitiveTypesMap),
typeNames
);

@@ -41,2 +60,27 @@ fs.readdirSync(path.join(process.cwd(), 'schema/types'))

});
const fieldsets = type.fieldsets;
const validFieldsetNames = fieldsets
? ['self'].concat(Object.keys(fieldsets))
: [];
if (fieldsets) {
describe('fieldsets', () => {
it('is an object if it exists', () => {
expect(fieldsets).to.be.an('object');
});
Object.entries(type.fieldsets).forEach(([name, fieldsetConfig]) => {
describe(name, () => {
it('has a heading', () => {
expect(fieldsetConfig.heading).to.be.a('string');
});
it('may have a description', () => {
if ('description' in fieldsetConfig) {
expect(fieldsetConfig.description).to.be.a('string');
}
});
});
});
});
}
describe('properties', () => {

@@ -47,14 +91,31 @@ Object.entries(type.properties).forEach(([name, config]) => {

Object.keys(config).forEach(key => {
expect(key).to.be.oneOf([
'type',
'unique',
'required',
'canIdentify',
'canFilter',
'description',
'pattern',
'label'
]);
const commonKeys = ['type', 'description', 'label'];
if (fieldsets) {
commonKeys.push('fieldset');
}
if (typeNames.includes(config.type)) {
// it's a relationship
expect(key).to.be.oneOf(
commonKeys.concat([
'direction',
'relationship',
'hasMany',
'isRecursive',
'hidden'
])
);
} else {
expect(key).to.be.oneOf(
commonKeys.concat([
'unique',
'required',
'canIdentify',
'canFilter',
'pattern'
])
);
}
});
});
it('has valid name', () => {

@@ -76,23 +137,74 @@ if (name !== 'SF_ID') {

});
it('may be required', () => {
if (config.required) {
expect(config.required).to.be.true;
it('has valid fieldset', () => {
if (config.fieldset) {
expect(validFieldsetNames).to.contain(config.fieldset);
}
});
it('may be an identifier', () => {
if (config.canIdentify) {
expect(config.canIdentify).to.be.true;
}
});
it('may be a filterer', () => {
if (config.canFilter) {
expect(config.canFilter).to.be.true;
}
});
it('may define a pattern', () => {
if (config.pattern) {
expect(config.pattern).to.be.oneOf(validStringPatterns);
}
});
if (!typeNames.includes(config.type)) {
context('direct property', () => {
// tests for direct properties
it('may be required', () => {
if (config.required) {
expect(config.required).to.be.true;
}
});
it('may be an identifier', () => {
if (config.canIdentify) {
expect(config.canIdentify).to.be.true;
}
});
it('may be a filterer', () => {
if (config.canFilter) {
expect(config.canFilter).to.be.true;
}
});
it('may define a pattern', () => {
if (config.pattern) {
expect(config.pattern).to.be.oneOf(validStringPatterns);
}
});
});
} else {
const RELATIONSHIP_NAME = getStringValidator('RELATIONSHIP_NAME');
context('relationship property', () => {
it('must specify underlying relationship', () => {
expect(config.relationship).to.match(RELATIONSHIP_NAME);
});
it('must specify direction', () => {
expect(config.direction).to.be.oneOf([
'incoming',
'outgoing'
]);
});
it('may be hidden', () => {
if (config.hidden) {
expect(config.hidden).to.be.true;
}
});
it('may have many', () => {
if (config.hasMany) {
expect(config.hasMany).to.be.true;
}
});
it('may be recursive', () => {
if (config.isRecursive) {
expect(config.isRecursive).to.be.true;
}
});
it('is defined at both ends', () => {
expect(
getTwinnedRelationship(
type.name,
config.type,
config.relationship,
config.direction
)
).to.exist;
});
});
}
});

@@ -99,0 +211,0 @@ });

@@ -45,2 +45,18 @@ const { expect } = require('chai');

description: 'The name of the cost centre'
},
hasGroups: {
type: 'Group',
relationship: 'PAYS_FOR',
direction: 'outgoing',
hasMany: true,
description: 'The groups which are costed to the cost centre'
},
hasNestedGroups: {
type: 'Group',
relationship: 'PAYS_FOR',
direction: 'outgoing',
hasMany: true,
isRecursive: true,
description:
'The recursive groups which are costed to the cost centre'
}

@@ -71,2 +87,15 @@ }

description: 'Whether or not the group is still in existence'
},
hasBudget: {
type: 'CostCentre',
relationship: 'PAYS_FOR',
direction: 'incoming',
description: 'The Cost Centre associated with the group'
},
hasEventualBudget: {
type: 'CostCentre',
description: 'The Cost Centre associated with the group in the end',
isRecursive: true,
relationship: 'PAYS_FOR',
direction: 'incoming'
}

@@ -77,24 +106,2 @@ }

sandbox.stub(rawData, 'getRelationships').returns({
PAYS_FOR: {
cardinality: 'ONE_TO_MANY',
fromType: {
type: 'CostCentre',
name: 'hasGroups',
description: 'The groups which are costed to the cost centre',
recursiveName: 'hasNestedGroups',
recursiveDescription:
'The recursive groups which are costed to the cost centre'
},
toType: {
type: 'Group',
name: 'hasBudget',
description: 'The Cost Centre associated with the group',
recursiveName: 'hasEventualBudget',
recursiveDescription:
'The Cost Centre associated with the group in the end'
}
}
});
sandbox.stub(rawData, 'getEnums').returns({

@@ -209,3 +216,2 @@ Lifecycle: {

]);
sandbox.stub(rawData, 'getRelationships').returns({});
sandbox.stub(rawData, 'getEnums').returns({});

@@ -230,3 +236,2 @@ const generated = [].concat(...generateGraphqlDefs()).join('');

]);
sandbox.stub(rawData, 'getRelationships').returns({});
sandbox.stub(rawData, 'getEnums').returns({});

@@ -233,0 +238,0 @@ const generated = [].concat(...generateGraphqlDefs()).join('');

@@ -13,6 +13,89 @@ const rawData = require('../../lib/raw-data');

const mocks = {
pointingAway: [
{
name: 'Type1',
properties: {
'test-name': {
type: 'Type2',
direction: 'outgoing',
relationship: 'HAS',
label: 'test label',
description: 'test description'
}
}
}
],
pointingTo: [
{
name: 'Type2',
properties: {
'test-name': {
type: 'Type1',
direction: 'incoming',
relationship: 'HAS',
label: 'test label',
description: 'test description'
}
}
}
],
sameUnderlyingRelationship: [
{
name: 'Type1',
properties: {
'test-name1': {
type: 'Type2',
direction: 'outgoing',
relationship: 'HAS'
},
'test-name2': {
type: 'Type3',
direction: 'incoming',
relationship: 'HAS'
}
}
}
],
selfReferencing: [
{
name: 'Type1',
properties: {
'test-name1': {
type: 'Type1',
direction: 'outgoing',
relationship: 'HAS'
},
'test-name2': {
type: 'Type1',
direction: 'incoming',
relationship: 'HAS'
}
}
}
],
cardinality: [
{
name: 'Type1',
properties: {
many: {
type: 'Type2',
hasMany: true,
direction: 'outgoing',
relationship: 'HAS'
},
singular: {
type: 'Type2',
direction: 'incoming',
relationship: 'HAS'
}
}
}
]
};
describe('get-relationships', () => {
const sandbox = sinon.createSandbox();
beforeEach(() => {
sandbox.stub(rawData, 'getRelationships');
sandbox.stub(rawData, 'getTypes');
});

@@ -27,3 +110,3 @@

it('returns an array', () => {
rawData.getRelationships.returns({});
rawData.getTypes.returns([{ name: 'Type' }]);
expect(getRelationships('Type')).to.eql([]);

@@ -33,17 +116,6 @@ });

it('retrieve relationships pointing away from the node', () => {
rawData.getRelationships.returns({
HAS: {
cardinality: 'ONE_TO_ONE',
fromType: {
type: 'Type1',
name: 'test-name',
description: 'test description',
label: 'test label'
},
toType: { type: 'Type2' }
}
});
rawData.getTypes.returns(mocks.pointingAway);
expect(getRelationships('Type1')).to.eql([
{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'outgoing',

@@ -61,17 +133,6 @@ endNode: 'Type2',

it('retrieve relationships pointing to the node', () => {
rawData.getRelationships.returns({
HAS: {
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type1' },
toType: {
type: 'Type2',
name: 'test-name',
description: 'test description',
label: 'test label'
}
}
});
rawData.getTypes.returns(mocks.pointingTo);
expect(getRelationships('Type2')).to.eql([
{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'incoming',

@@ -89,29 +150,18 @@ endNode: 'Type1',

it('retrieve multiple relationships with same name', () => {
rawData.getRelationships.returns({
HAS: [
{
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type1' },
toType: { type: 'Type2' }
},
{
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type3' },
toType: { type: 'Type1' }
}
]
});
rawData.getTypes.returns(mocks.sameUnderlyingRelationship);
expect(getRelationships('Type1')).to.eql([
{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'outgoing',
hasMany: false,
endNode: 'Type2',
name: 'test-name1',
startNode: 'Type1'
},
{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'incoming',
hasMany: false,
endNode: 'Type3',
name: 'test-name2',
startNode: 'Type1'

@@ -123,24 +173,18 @@ }

it('retrieve two relationships when pointing at self', () => {
rawData.getRelationships.returns({
HAS: [
{
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type1' },
toType: { type: 'Type1' }
}
]
});
rawData.getTypes.returns(mocks.selfReferencing);
expect(getRelationships('Type1')).to.eql([
{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'outgoing',
endNode: 'Type1',
startNode: 'Type1',
name: 'test-name1',
hasMany: false
},
{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'incoming',
endNode: 'Type1',
startNode: 'Type1',
name: 'test-name2',
hasMany: false

@@ -150,33 +194,45 @@ }

});
describe('cardinality', () => {
['ONE_TO_ONE', 'ONE_TO_MANY', 'MANY_TO_ONE', 'MANY_TO_MANY']
.reduce(
(arr, cardinality) =>
arr.concat([
[cardinality, 'incoming', [1, 2]],
[cardinality, 'outgoing', [2, 1]]
]),
[]
)
.map(([cardinality, direction, [fromNum, toNum]]) => {
it(`assigns correct cardinality for ${direction} ${cardinality} relationship`, () => {
rawData.getRelationships.returns({
HAS: [
{
cardinality,
fromType: { type: `Type${fromNum}` },
toType: { type: `Type${toNum}` }
}
]
});
const hasMany =
/(ONE|MANY)_TO_(ONE|MANY)/.exec(cardinality)[toNum] === 'MANY';
expect(
getRelationships('Type1').find(
({ neo4jName }) => neo4jName === 'HAS'
).hasMany
).to.equal(hasMany);
});
});
it('retrieve two relationships when pointing at self', () => {
rawData.getTypes.returns(mocks.selfReferencing);
expect(getRelationships('Type1')).to.eql([
{
relationship: 'HAS',
direction: 'outgoing',
endNode: 'Type1',
startNode: 'Type1',
name: 'test-name1',
hasMany: false
},
{
relationship: 'HAS',
direction: 'incoming',
endNode: 'Type1',
startNode: 'Type1',
name: 'test-name2',
hasMany: false
}
]);
});
it('cardinality', () => {
rawData.getTypes.returns(mocks.cardinality);
expect(getRelationships('Type1')).to.eql([
{
relationship: 'HAS',
direction: 'outgoing',
endNode: 'Type2',
startNode: 'Type1',
hasMany: true,
name: 'many'
},
{
relationship: 'HAS',
direction: 'incoming',
endNode: 'Type2',
startNode: 'Type1',
name: 'singular',
hasMany: false
}
]);
});
});

@@ -186,3 +242,3 @@

it('returns an object', () => {
rawData.getRelationships.returns({});
rawData.getTypes.returns([{ name: 'Type' }]);
expect(getRelationships('Type', { structure: 'rest' })).to.eql({});

@@ -192,14 +248,3 @@ });

it('retrieve relationships pointing away from the node', () => {
rawData.getRelationships.returns({
HAS: {
cardinality: 'ONE_TO_ONE',
fromType: {
type: 'Type1',
name: 'test-name',
description: 'test description',
label: 'test label'
},
toType: { type: 'Type2' }
}
});
rawData.getTypes.returns(mocks.pointingAway);
expect(getRelationships('Type1', { structure: 'rest' })).to.eql({

@@ -220,14 +265,3 @@ HAS: [

it('retrieve relationships pointing to the node', () => {
rawData.getRelationships.returns({
HAS: {
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type1' },
toType: {
type: 'Type2',
name: 'test-name',
description: 'test description',
label: 'test label'
}
}
});
rawData.getTypes.returns(mocks.pointingTo);
expect(getRelationships('Type2', { structure: 'rest' })).to.eql({

@@ -248,16 +282,3 @@ HAS: [

it('retrieve multiple relationships with same name', () => {
rawData.getRelationships.returns({
HAS: [
{
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type1' },
toType: { type: 'Type2' }
},
{
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type3' },
toType: { type: 'Type1' }
}
]
});
rawData.getTypes.returns(mocks.sameUnderlyingRelationship);
expect(getRelationships('Type1', { structure: 'rest' })).to.eql({

@@ -270,3 +291,3 @@ HAS: [

label: undefined,
name: undefined,
name: 'test-name1',
nodeType: 'Type2'

@@ -279,3 +300,3 @@ },

label: undefined,
name: undefined,
name: 'test-name2',
nodeType: 'Type3'

@@ -288,11 +309,26 @@ }

it('retrieve two relationships when pointing at self', () => {
rawData.getRelationships.returns({
rawData.getTypes.returns(mocks.selfReferencing);
expect(getRelationships('Type1', { structure: 'rest' })).to.eql({
HAS: [
{
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type1' },
toType: { type: 'Type1' }
direction: 'outgoing',
nodeType: 'Type1',
hasMany: false,
name: 'test-name1',
description: undefined,
label: undefined
},
{
direction: 'incoming',
nodeType: 'Type1',
hasMany: false,
name: 'test-name2',
description: undefined,
label: undefined
}
]
});
});
it('cardinality', () => {
rawData.getTypes.returns(mocks.cardinality);
expect(getRelationships('Type1', { structure: 'rest' })).to.eql({

@@ -302,5 +338,5 @@ HAS: [

direction: 'outgoing',
nodeType: 'Type1',
hasMany: false,
name: undefined,
nodeType: 'Type2',
hasMany: true,
name: 'many',
description: undefined,

@@ -311,5 +347,5 @@ label: undefined

direction: 'incoming',
nodeType: 'Type1',
nodeType: 'Type2',
name: 'singular',
hasMany: false,
name: undefined,
description: undefined,

@@ -321,31 +357,2 @@ label: undefined

});
describe('cardinality', () => {
['ONE_TO_ONE', 'ONE_TO_MANY', 'MANY_TO_ONE', 'MANY_TO_MANY']
.reduce(
(arr, cardinality) =>
arr.concat([
[cardinality, 'incoming', [1, 2]],
[cardinality, 'outgoing', [2, 1]]
]),
[]
)
.map(([cardinality, direction, [fromNum, toNum]]) => {
it(`assigns correct cardinality for ${direction} ${cardinality} relationship`, () => {
rawData.getRelationships.returns({
HAS: [
{
cardinality,
fromType: { type: `Type${fromNum}` },
toType: { type: `Type${toNum}` }
}
]
});
const hasMany =
/(ONE|MANY)_TO_(ONE|MANY)/.exec(cardinality)[toNum] === 'MANY';
expect(
getRelationships('Type1', { structure: 'rest' }).HAS[0].hasMany
).to.equal(hasMany);
});
});
});
});

@@ -355,3 +362,3 @@

it('returns an array', () => {
rawData.getRelationships.returns({});
rawData.getTypes.returns([{ name: 'Type' }]);
expect(getRelationships('Type', { structure: 'graphql' })).to.eql([]);

@@ -361,17 +368,6 @@ });

it('retrieve relationships pointing away from the node', () => {
rawData.getRelationships.returns({
HAS: {
cardinality: 'ONE_TO_ONE',
fromType: {
type: 'Type1',
name: 'test-name',
description: 'test description',
label: 'test label'
},
toType: { type: 'Type2' }
}
});
rawData.getTypes.returns(mocks.pointingAway);
expect(getRelationships('Type1', { structure: 'graphql' })).to.eql([
{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'outgoing',

@@ -390,17 +386,6 @@ type: 'Type2',

it('retrieve relationships pointing to the node', () => {
rawData.getRelationships.returns({
HAS: {
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type1' },
toType: {
type: 'Type2',
name: 'test-name',
description: 'test description',
label: 'test label'
}
}
});
rawData.getTypes.returns(mocks.pointingTo);
expect(getRelationships('Type2', { structure: 'graphql' })).to.eql([
{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'incoming',

@@ -419,16 +404,3 @@ type: 'Type1',

it('retrieve multiple relationships with same name', () => {
rawData.getRelationships.returns({
HAS: [
{
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type1', name: 'name1a' },
toType: { type: 'Type2', name: 'name1b' }
},
{
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type3', name: 'name2a' },
toType: { type: 'Type1', name: 'name2b' }
}
]
});
rawData.getTypes.returns(mocks.sameUnderlyingRelationship);
expect(

@@ -441,7 +413,7 @@ getRelationships('Type1', { structure: 'graphql' }).map(

direction: 'outgoing',
name: 'name1a'
name: 'test-name1'
},
{
direction: 'incoming',
name: 'name2b'
name: 'test-name2'
}

@@ -452,11 +424,3 @@ ]);

it('retrieve two relationships when pointing at self', () => {
rawData.getRelationships.returns({
HAS: [
{
cardinality: 'ONE_TO_ONE',
fromType: { type: 'Type1', name: 'name-outgoing' },
toType: { type: 'Type1', name: 'name-incoming' }
}
]
});
rawData.getTypes.returns(mocks.selfReferencing);
expect(

@@ -469,7 +433,7 @@ getRelationships('Type1', { structure: 'graphql' }).map(

direction: 'outgoing',
name: 'name-outgoing'
name: 'test-name1'
},
{
direction: 'incoming',
name: 'name-incoming'
name: 'test-name2'
}

@@ -479,16 +443,17 @@ ]);

it('define recursive relationships', () => {
rawData.getRelationships.returns({
HAS: [
{
cardinality: 'ONE_TO_ONE',
fromType: {
type: 'Type1',
name: 'name1',
recursiveName: 'recursiveName1',
recursiveDescription: 'recursiveDescription1'
},
toType: { type: 'Type2' }
rawData.getTypes.returns([
{
name: 'Type1',
properties: {
'test-name': {
type: 'Type2',
direction: 'outgoing',
isRecursive: true,
relationship: 'HAS',
label: 'test label',
description: 'test description'
}
}
]
});
}
]);

@@ -500,19 +465,36 @@ expect(getRelationships('Type1', { structure: 'graphql' })).to.eql([

direction: 'outgoing',
name: 'name1',
name: 'test-name',
isRecursive: true,
isRelationship: true,
relationship: 'HAS',
description: 'test description',
label: 'test label'
}
]);
});
it('cardinality', () => {
rawData.getTypes.returns(mocks.cardinality);
expect(getRelationships('Type1', { structure: 'graphql' })).to.eql([
{
description: undefined,
isRecursive: false,
isRelationship: true,
neo4jName: 'HAS',
description: undefined,
label: undefined
label: undefined,
type: 'Type2',
relationship: 'HAS',
direction: 'outgoing',
hasMany: true,
name: 'many'
},
{
description: undefined,
isRecursive: false,
isRelationship: true,
label: undefined,
type: 'Type2',
hasMany: false,
direction: 'outgoing',
name: 'recursiveName1',
isRecursive: true,
isRelationship: true,
neo4jName: 'HAS',
description: 'recursiveDescription1',
label: undefined
relationship: 'HAS',
direction: 'incoming',
name: 'singular',
hasMany: false
}

@@ -522,34 +504,21 @@ ]);

describe('cardinality', () => {
['ONE_TO_ONE', 'ONE_TO_MANY', 'MANY_TO_ONE', 'MANY_TO_MANY']
.reduce(
(arr, cardinality) =>
arr.concat([
[cardinality, 'incoming', [1, 2]],
[cardinality, 'outgoing', [2, 1]]
]),
[]
)
.map(([cardinality, direction, [fromNum, toNum]]) => {
it(`assigns correct cardinality for ${direction} ${cardinality} relationship`, () => {
rawData.getRelationships.returns({
HAS: [
{
cardinality,
fromType: { type: `Type${fromNum}`, name: `name${fromNum}` },
toType: { type: `Type${toNum}`, name: `name${toNum}` }
}
]
});
const hasMany =
/(ONE|MANY)_TO_(ONE|MANY)/.exec(cardinality)[toNum] === 'MANY';
expect(
getRelationships('Type1', { structure: 'graphql' }).find(
({ neo4jName }) => neo4jName === 'HAS'
).hasMany
).to.equal(hasMany);
});
});
it('hidden relationships', () => {
rawData.getTypes.returns([
{
name: 'Type1',
properties: {
'test-name': {
type: 'Type2',
direction: 'outgoing',
relationship: 'HAS',
label: 'test label',
description: 'test description',
hidden: true
}
}
}
]);
expect(getRelationships('Type1', { structure: 'graphql' })).to.eql([]);
});
});
});

@@ -106,27 +106,125 @@ const { getType } = require('../../');

it('it maps types to graphql properties', async () => {
sandbox.stub(getRelationships, 'method');
rawData.getTypes.returns([
{
name: 'Type1',
properties: {
primitiveProp: {
type: 'Word'
},
documentProp: {
type: 'Document'
},
enumProp: {
type: 'SomeEnum'
}
}
}
]);
rawData.getTypes.returns([
{
name: 'Type1',
properties: {
primitiveProp: {
type: 'Word'
},
documentProp: {
type: 'Document'
},
enumProp: {
type: 'SomeEnum'
}
const type = getType('Type1', { primitiveTypes: 'graphql' });
expect(type.properties.primitiveProp).to.eql({ type: 'String' });
expect(type.properties.documentProp).to.not.exist;
expect(type.properties.enumProp).to.eql({ type: 'SomeEnum' });
});
it('groups properties by fieldset', () => {
rawData.getTypes.returns([
{
name: 'Type1',
properties: {
mainProp: {
type: 'Word',
fieldset: 'main'
},
secondaryProp: {
type: 'Document',
fieldset: 'self',
label: 'Standalone'
},
miscProp: {
type: 'SomeEnum'
}
},
fieldsets: {
main: {
heading: 'Main properties',
description: 'Fill these out please'
},
secondary: {
heading: 'Secondary properties',
description: 'Fill these out optionally'
}
}
]);
}
]);
const type = getType('Type1', { primitiveTypes: 'graphql' });
expect(type.properties.primitiveProp).to.eql({ type: 'String' });
expect(type.properties.documentProp).to.not.exist;
expect(type.properties.enumProp).to.eql({ type: 'SomeEnum' });
});
const type = getType('Type1', { groupProperties: true });
expect(type.properties).to.not.exist;
expect(type.fieldsets.main.properties.mainProp).to.exist;
expect(type.fieldsets.main.heading).to.equal('Main properties');
expect(type.fieldsets.main.description).to.equal('Fill these out please');
expect(type.fieldsets.secondaryProp.properties.secondaryProp).to.exist;
expect(type.fieldsets.secondaryProp.heading).to.equal('Standalone');
expect(type.fieldsets.secondaryProp.description).to.not.exist;
expect(type.fieldsets.misc.properties.miscProp).to.exist;
expect(type.fieldsets.misc.heading).to.equal('Miscellaneous');
expect(type.fieldsets.misc.description).not.to.exist;
});
it('not have empty fieldsets', () => {
rawData.getTypes.returns([
{
name: 'Type1',
properties: {
mainProp: {
type: 'Word',
fieldset: 'main'
}
},
fieldsets: {
main: {
heading: 'Main properties',
description: 'Fill these out please'
},
secondary: {
heading: 'Secondary properties',
description: 'Fill these out optionally'
}
}
}
]);
const type = getType('Type1', { groupProperties: true });
expect(type.properties).to.not.exist;
expect(type.fieldsets.secondary).to.not.exist;
expect(type.fieldsets.misc).to.not.exist;
});
it('handle lack of custom fieldsets well when grouping', () => {
rawData.getTypes.returns([
{
name: 'Type1',
properties: {
mainProp: {
type: 'Word'
},
secondaryProp: {
type: 'Document',
label: 'Standalone'
},
miscProp: {
type: 'SomeEnum'
}
}
}
]);
const type = getType('Type1', { groupProperties: true });
expect(type.properties).to.not.exist;
expect(type.fieldsets.misc.properties.mainProp).to.exist;
expect(type.fieldsets.misc.properties.secondaryProp).to.exist;
expect(type.fieldsets.misc.properties.miscProp).to.exist;
expect(type.fieldsets.misc.heading).to.equal('General');
});
describe('relationships', () => {

@@ -184,3 +282,52 @@ it('it includes rest api relationship definitions', async () => {

});
it('groups relationship properties by fieldset', () => {
sandbox.stub(getRelationships, 'method');
rawData.getTypes.returns([
{
name: 'Type1',
properties: {
mainProp: {
type: 'Word',
fieldset: 'main',
relationship: 'HAS'
},
secondaryProp: {
type: 'Document',
fieldset: 'self',
relationship: 'HAS',
label: 'Standalone'
},
miscProp: {
type: 'SomeEnum',
relationship: 'HAS'
}
},
fieldsets: {
main: {
heading: 'Main properties',
description: 'Fill these out please'
},
secondary: {
heading: 'Secondary properties',
description: 'Fill these out optionally'
}
}
}
]);
const type = getType('Type1', { groupProperties: true });
expect(type.properties).to.not.exist;
expect(type.fieldsets.main.properties.mainProp).to.exist;
expect(type.fieldsets.main.heading).to.equal('Main properties');
expect(type.fieldsets.main.description).to.equal('Fill these out please');
expect(type.fieldsets.secondaryProp.properties.secondaryProp).to.exist;
expect(type.fieldsets.secondaryProp.heading).to.equal('Standalone');
expect(type.fieldsets.secondaryProp.description).to.not.exist;
expect(type.fieldsets.misc.properties.miscProp).to.exist;
expect(type.fieldsets.misc.heading).to.equal('Miscellaneous');
expect(type.fieldsets.misc.description).not.to.exist;
});
});
});

@@ -5,3 +5,3 @@ const sinon = require('sinon');

const { validateAttributes } = require('../../');
const primitiveTypesMap = require('../../lib/primitive-types-map')
const primitiveTypesMap = require('../../lib/primitive-types-map');
describe('validateAttributes', () => {

@@ -57,4 +57,3 @@ const sandbox = sinon.createSandbox();

}
})
});
});

@@ -61,0 +60,0 @@ describe('validating booleans', () => {

@@ -38,3 +38,3 @@ const sinon = require('sinon');

{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'outgoing',

@@ -57,3 +57,3 @@ endNode: 'EndType',

{
neo4jName: 'HAS',
relationship: 'HAS',
direction: 'incoming',

@@ -60,0 +60,0 @@ endNode: 'StartType',

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc