Skip to content

Commit

Permalink
feat: add compatibility check for TTL configs
Browse files Browse the repository at this point in the history
  • Loading branch information
Yogu committed Sep 10, 2024
1 parent 320feef commit 044ac39
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 12 deletions.
138 changes: 138 additions & 0 deletions spec/model/compatibility-check/check-ttl.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import gql from 'graphql-tag';
import {
expectSingleCompatibilityIssue,
expectToBeValid,
} from '../implementation/validation-utils';
import { runCheck } from './utils';
import { Project } from '../../../src/project/project';
import { assertValidatorAcceptsAndDoesNotWarn } from '../../schema/ast-validation-modules/helpers';

describe('checkModel', () => {
describe('ttl', () => {
it('rejects if a type should have a TTL config', () => {
const projectToCheck = new Project([
gql`
type Test @rootEntity {
dateField: DateTime
}
`.loc!.source,
]);
expectToBeValid(projectToCheck);

const baselineProject = new Project([
gql`
type Test @rootEntity {
dateField: DateTime
}
`.loc!.source,
{
name: 'ttl.json',
body: JSON.stringify({
timeToLive: [
{
typeName: 'Test',
dateField: 'dateField',
expireAfterDays: 30,
},
],
}),
},
]);
expectToBeValid(baselineProject);

const checkResult = projectToCheck.checkCompatibility(baselineProject);

expectSingleCompatibilityIssue(
checkResult,
'There should be a timeToLive configuration for type "Test".',
);
});

it('rejects if a type should not have a TTL config', () => {
const projectToCheck = new Project([
gql`
type Test @rootEntity {
dateField: DateTime
}
`.loc!.source,
{
name: 'ttl.json',
body: JSON.stringify({
timeToLive: [
{
typeName: 'Test',
dateField: 'dateField',
expireAfterDays: 30,
},
],
}),
},
]);
expectToBeValid(projectToCheck);

const baselineProject = new Project([
gql`
type Test @rootEntity {
dateField: DateTime
}
`.loc!.source,
]);
expectToBeValid(baselineProject);

const checkResult = projectToCheck.checkCompatibility(baselineProject);

expectSingleCompatibilityIssue(
checkResult,
'There does not need to be a timeToLive configuration for type "Test". If the timeToLive configuration is intentional, suppress this message.',
);
});

it('accepts if there is a TTL config in both baseline and project to check', () => {
const projectToCheck = new Project([
gql`
type Test @rootEntity {
dateField: DateTime
}
`.loc!.source,
{
name: 'ttl.json',
body: JSON.stringify({
timeToLive: [
{
typeName: 'Test',
dateField: 'dateField',
expireAfterDays: 15,
},
],
}),
},
]);
expectToBeValid(projectToCheck);

const baselineProject = new Project([
gql`
type Test @rootEntity {
dateField: DateTime
}
`.loc!.source,
{
name: 'ttl.json',
body: JSON.stringify({
timeToLive: [
{
typeName: 'Test',
dateField: 'dateField',
expireAfterDays: 30,
},
],
}),
},
]);
expectToBeValid(baselineProject);

const checkResult = projectToCheck.checkCompatibility(baselineProject);

expectToBeValid(checkResult);
});
});
});
23 changes: 23 additions & 0 deletions src/model/compatibility-check/check-business-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ObjectType, RootEntityType } from '../implementation';
import { ValidationContext, ValidationMessage } from '../validation';
import { checkField } from './check-field';
import { getRequiredBySuffix } from './describe-module-specification';

export function checkBusinessObject(
typeToCheck: RootEntityType,
baselineType: RootEntityType,
context: ValidationContext,
) {
if (baselineType.isBusinessObject && !typeToCheck.isBusinessObject) {
context.addMessage(
ValidationMessage.suppressableCompatibilityIssue(
'BUSINESS_OBJECT',
`Type "${
baselineType.name
}" needs to be decorated with @businessObject${getRequiredBySuffix(baselineType)}.`,
typeToCheck.astNode,
{ location: typeToCheck.nameASTNode },
),
);
}
}
16 changes: 4 additions & 12 deletions src/model/compatibility-check/check-root-entity-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,14 @@ import { ObjectType, RootEntityType } from '../implementation';
import { ValidationContext, ValidationMessage } from '../validation';
import { checkField } from './check-field';
import { getRequiredBySuffix } from './describe-module-specification';
import { checkBusinessObject } from './check-business-object';
import { checkTtl } from './check-ttl';

export function checkRootEntityType(
typeToCheck: RootEntityType,
baselineType: RootEntityType,
context: ValidationContext,
) {
if (baselineType.isBusinessObject && !typeToCheck.isBusinessObject) {
context.addMessage(
ValidationMessage.suppressableCompatibilityIssue(
'BUSINESS_OBJECT',
`Type "${
baselineType.name
}" needs to be decorated with @businessObject${getRequiredBySuffix(baselineType)}.`,
typeToCheck.astNode,
{ location: typeToCheck.nameASTNode },
),
);
}
checkBusinessObject(typeToCheck, baselineType, context);
checkTtl(typeToCheck, baselineType, context);
}
33 changes: 33 additions & 0 deletions src/model/compatibility-check/check-ttl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ObjectType, RootEntityType } from '../implementation';
import { ValidationContext, ValidationMessage } from '../validation';
import { checkField } from './check-field';
import { getRequiredBySuffix } from './describe-module-specification';

export function checkTtl(
typeToCheck: RootEntityType,
baselineType: RootEntityType,
context: ValidationContext,
) {
if (baselineType.timeToLiveTypes.length && !typeToCheck.timeToLiveTypes.length) {
context.addMessage(
ValidationMessage.suppressableCompatibilityIssue(
'TTL',
`There should be a timeToLive configuration for type "${baselineType.name}"${getRequiredBySuffix(baselineType)}.`,
typeToCheck.astNode,
{ location: typeToCheck.nameASTNode },
),
);
}
if (!baselineType.timeToLiveTypes.length && typeToCheck.timeToLiveTypes.length) {
// The @suppress needs to be specified on the root entity definition (because there is no @suppress for yaml/json files)
// For this reason, we report the error on the type and not on the TTL config
context.addMessage(
ValidationMessage.suppressableCompatibilityIssue(
'TTL',
`There does not need to be a timeToLive configuration for type "${baselineType.name}". If the timeToLive configuration is intentional, suppress this message.`,
typeToCheck.astNode,
{ location: typeToCheck.nameASTNode },
),
);
}
}
1 change: 1 addition & 0 deletions src/model/validation/suppress/message-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const COMPATIBILITY_ISSUE_CODES = {
PARENT_FIELD: 'Missing or superfluous @parent',
BUSINESS_OBJECT: 'Missing or superfluous @businessObject',
TYPE_KIND: 'A type declaration is of the wrong kind (e.g. root entity, value object or enum)',
TTL: 'Missing or superfluous time-to-live configuration',
} as const satisfies MessageCodes;

export type CompatibilityIssueCode = keyof typeof COMPATIBILITY_ISSUE_CODES;
Expand Down

0 comments on commit 044ac39

Please sign in to comment.