Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tests for Template versioning #132

Merged
merged 9 commits into from
Oct 30, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
### Added
- Added data-fns package to help with Date validation and formatting
- Added Language model, resolver and type. Added LanguageId to User and Template
- Built Question resolvers and models(#13)
- Fixed some bugs to allow frontend to access token change(Frontend #116)
Expand Down
6 changes: 3 additions & 3 deletions data-migrations/2024-09-25-1120-create-questions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ CREATE TABLE `questionConditions` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`questionId` INT NOT NULL,
`action` VARCHAR(255) NOT NULL DEFAULT 'SHOW_QUESTION',
`condition` VARCHAR(255) NOT NULL DEFAULT 'EQUAL',
`conditionType` VARCHAR(255) NOT NULL DEFAULT 'EQUAL',
`conditionMatch` VARCHAR(255),
`target` VARCHAR(255) NOT NULL,
`createdById` INT NOT NULL,
Expand Down Expand Up @@ -62,12 +62,12 @@ CREATE TABLE `versionedQuestions` (
INDEX versionedQuestions_section_idx (`versionedSectionId`, `displayOrder`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;

CREATE TABLE `versionedQuestionCondition` (
CREATE TABLE `versionedQuestionConditions` (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@briri: I believe that "condition" needs to still be updated to conditionType on line 70.

`id` INT AUTO_INCREMENT PRIMARY KEY,
`versionedQuestionId` INT NOT NULL,
`questionConditionId` INT NOT NULL,
`action` VARCHAR(255) NOT NULL DEFAULT 'SHOW_QUESTION',
`condition` VARCHAR(255) NOT NULL DEFAULT 'EQUAL',
`conditionType` VARCHAR(255) NOT NULL DEFAULT 'EQUAL',
`conditionMatch` VARCHAR(255),
`target` VARCHAR(255) NOT NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"body-parser": "^1.20.3",
"casual": "^1.6.2",
"cookie-parser": "^1.4.6",
"date-fns": "^4.1.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-jwt": "^8.4.1",
Expand Down
44 changes: 29 additions & 15 deletions src/models/MySqlModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { formatLogMessage } from "../logger";
import { MyContext } from '../context';
import { validateDate } from "../utils/helpers";
import { getCurrentDate } from "../utils/helpers";
import { formatISO9075, isDate } from "date-fns";

type MixedArray<T> = T[];

Expand Down Expand Up @@ -29,10 +30,10 @@ export class MySqlModel {
// - createdById and modifiedById should be numbers
// - id should be a number or null if its a new record
async isValid(): Promise<boolean> {
if (!validateDate(this.created)) {
if (!await validateDate(this.created)) {
this.errors.push('Created date can\'t be blank');
}
if (!validateDate(this.modified)) {
if (!await validateDate(this.modified)) {
this.errors.push('Modified date can\'t be blank');
}
if (this.createdById === null) {
Expand All @@ -41,10 +42,15 @@ export class MySqlModel {
if (this.modifiedById === null) {
this.errors.push('Modified by can\'t be blank');
}

return this.errors.length <= 0;
}

// Check whether or not the value is a Date
static valueIsDate(val: string): boolean {
const date = new Date(val);
return !isNaN(date.getTime());
}

/**
* Convert incoming value to appropriate type for insertion into a SQL query
* @param val
Expand All @@ -58,6 +64,7 @@ export class MySqlModel {
if (val === null || val === undefined) {
return null;
}

switch (type) {
case 'number':
return Number(val);
Expand All @@ -69,15 +76,19 @@ export class MySqlModel {
case 'boolean':
return Boolean(val);
default:
return String(val);

if (isDate(val)) {
const date = new Date(val).toISOString();
return formatISO9075(date);
} else {
return String(val);
}
}
}

// Fetches all of the property infor for the object to faciliate inserts and updates
// Fetches all of the property info for the object to faciliate inserts and updates
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static propertyInfo(obj: Record<string, any>, skipKeys: string[] = []): { name: string, value: string }[] {
const excludedKeys = ['id', 'errors'];
const excludedKeys = ['id', 'errors', 'tableName'];
return Object.keys(obj)
.filter((key) => ![...excludedKeys, ...skipKeys]
.includes(key)).map((key) => ({
Expand Down Expand Up @@ -165,7 +176,6 @@ export class MySqlModel {

// Fetch all of the data from the object
const props = this.propertyInfo(obj, skipKeys);

const sql = `INSERT INTO ${table} \
(${props.map((entry) => entry.name).join(', ')}) \
VALUES (${Array(props.length).fill('?').join(', ')})`
Expand All @@ -188,16 +198,20 @@ export class MySqlModel {
table: string,
obj: MySqlModel,
reference = 'undefined caller',
skipKeys?: string[]
skipKeys?: string[],
noTouch?: boolean,
): Promise<MySqlModel> {
// Update the modifier info
obj.modifiedById = apolloContext.token.id;
const currentDate = getCurrentDate();
obj.modified = currentDate;
obj.created = currentDate;
if (noTouch !== true) {
obj.modifiedById = apolloContext.token.id;
const currentDate = getCurrentDate();
obj.modified = currentDate;
}

// Fetch all of the data from the object
const props = this.propertyInfo(obj, skipKeys);
let props = this.propertyInfo(obj, skipKeys);
// We are updating, so remove the created info
props = props.filter((entry) => { return !['created', 'createdById'].includes(entry.name) });

props.map((entry) => `${entry.name} = ?`)

Expand All @@ -206,7 +220,7 @@ export class MySqlModel {
WHERE id = ?`;

const vals = props.map((entry) => this.prepareValue(entry.value, typeof (entry.value)));

// Make sure the record id is the last value
vals.push(obj.id.toString());

// Send the calcuated INSERT statement to the query function
Expand Down
13 changes: 9 additions & 4 deletions src/models/Question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ export class Question extends MySqlModel {
}

//Create a new Question
async create(context: MyContext, questionText: string, sectionId: number, templateId: number): Promise<Question> {
async create(
context: MyContext,
questionText: string,
sectionId: number,
templateId: number,
): Promise<Question> {
// First make sure the record is valid
if (await this.isValid()) {
const current = await Question.findByQuestionText(
Expand All @@ -65,7 +70,7 @@ export class Question extends MySqlModel {
this.errors.push('Question with this question text already exists');
} else {
// Save the record and then fetch it
const newId = await Question.insert(context, this.tableName, this, 'Question.create', ['tableName']);
const newId = await Question.insert(context, this.tableName, this, 'Question.create');
const response = await Question.findById('Section.create', context, newId);
return response;
}
Expand All @@ -75,12 +80,12 @@ export class Question extends MySqlModel {
}

//Update an existing Section
async update(context: MyContext): Promise<Question> {
async update(context: MyContext, noTouch = false): Promise<Question> {
const id = this.id;

if (await this.isValid()) {
if (id) {
await Question.update(context, this.tableName, this, 'Question.update', ['tableName']);
await Question.update(context, this.tableName, this, 'Question.update', [], noTouch);
return await Question.findById('Question.update', context, id);
}
// This template has never been saved before so we cannot update it!
Expand Down
12 changes: 6 additions & 6 deletions src/models/QuestionCondition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export enum QuestionConditionCondition {
export class QuestionCondition extends MySqlModel {
public questionId: number;
public action: QuestionConditionActionType;
public condition: QuestionConditionCondition;
public conditionType: QuestionConditionCondition;
public conditionMatch?: string;
public target: string;

Expand All @@ -27,7 +27,7 @@ export class QuestionCondition extends MySqlModel {

this.questionId = options.questionId;
this.action = options.action || QuestionConditionActionType.SHOW_QUESTION;
this.condition = options.condition || QuestionConditionCondition.EQUAL;
this.conditionType = options.conditionType || QuestionConditionCondition.EQUAL;
this.conditionMatch = options.conditionMatch;
this.target = options.target;
}
Expand All @@ -41,8 +41,8 @@ export class QuestionCondition extends MySqlModel {
if (!this.action) {
this.errors.push('Action can\'t be blank');
}
if (!this.condition) {
this.errors.push('Condition can\'t be blank');
if (!this.conditionType) {
this.errors.push('Condition Type can\'t be blank');
}
if (!this.target) {
this.errors.push('Target can\'t be blank');
Expand All @@ -55,7 +55,7 @@ export class QuestionCondition extends MySqlModel {
// First make sure the record is valid
if (await this.isValid()) {
// Save the record and then fetch it
const newId = await QuestionCondition.insert(context, this.tableName, this, 'QuestionCondition.create', ['tableName']);
const newId = await QuestionCondition.insert(context, this.tableName, this, 'QuestionCondition.create');
const response = await QuestionCondition.findById('QuestionCondition.create', context, newId);
return response;
}
Expand All @@ -69,7 +69,7 @@ export class QuestionCondition extends MySqlModel {

if (await this.isValid()) {
if (id) {
await QuestionCondition.update(context, this.tableName, this, 'QuestionCondition.update', ['tableName']);
await QuestionCondition.update(context, this.tableName, this, 'QuestionCondition.update');
return await QuestionCondition.findById('QuestionCondition.update', context, id);
}
// This QuestionCondition has never been saved before so we cannot update it!
Expand Down
4 changes: 2 additions & 2 deletions src/models/Section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ export class Section extends MySqlModel {
}

//Update an existing Section
async update(context: MyContext): Promise<Section> {
async update(context: MyContext, noTouch = false): Promise<Section> {
const id = this.id;

if (await this.isValid()) {
if (id) {
await Section.update(context, this.tableName, this, 'Section.update', ['tableName', 'tags']);
await Section.update(context, this.tableName, this, 'Section.update', ['tags'], noTouch);
return await Section.findById('Section.update', context, id);
}
// This template has never been saved before so we cannot update it!
Expand Down
9 changes: 3 additions & 6 deletions src/models/Template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ export class Template extends MySqlModel {
}

// Save the changes made to the template
async update(context: MyContext): Promise<Template> {
async update(context: MyContext, noTouch = false): Promise<Template> {
const id = this.id;

// First make sure the record is valid
if (await this.isValid()) {
if (id) {
// if the template is versioned then set the isDirty flag
if (this.currentVersion) {
if (this.currentVersion && noTouch !== true) {
this.isDirty = true;
}

Expand All @@ -104,10 +104,7 @@ export class Template extends MySqlModel {
}
So, we have to make a call to findById to get the updated data to return to user
*/
this.cleanup();
await Template.update(context, this.tableName, this, 'Template.update', ['tableName']);


await Template.update(context, this.tableName, this, 'Template.update', [], noTouch);
return await Template.findById('Template.update', context, id);
}
// This template has never been saved before so we cannot update it!
Expand Down
2 changes: 1 addition & 1 deletion src/models/VersionedQuestion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class VersionedQuestion extends MySqlModel {
// First make sure the record is valid
if (await this.isValid()) {
// Save the record and then fetch it
const newId = await VersionedQuestion.insert(context, this.tableName, this, 'VersionedQuestion.create', ['tableName']);
const newId = await VersionedQuestion.insert(context, this.tableName, this, 'VersionedQuestion.create');
return await VersionedQuestion.findById('VersionedQuestion.create', context, newId);
}
// Otherwise return as-is with all the errors
Expand Down
9 changes: 4 additions & 5 deletions src/models/VersionedQuestionCondition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export class VersionedQuestionCondition extends MySqlModel {
public versionedQuestionId: number;
public questionConditionId: number;
public action: QuestionConditionActionType;
public condition: QuestionConditionCondition;
public conditionType: QuestionConditionCondition;
public conditionMatch?: string;
public target: string;

Expand All @@ -18,7 +18,7 @@ export class VersionedQuestionCondition extends MySqlModel {
this.versionedQuestionId = options.versionedQuestionId;
this.questionConditionId = options.questionConditionId;
this.action = options.action;
this.condition = options.condition;
this.conditionType = options.conditionType;
this.conditionMatch = options.conditionMatch;
this.target = options.target;
}
Expand All @@ -36,8 +36,8 @@ export class VersionedQuestionCondition extends MySqlModel {
if (!this.action) {
this.errors.push('Action can\'t be blank');
}
if (!this.condition) {
this.errors.push('Condition text by can\'t be blank');
if (!this.conditionType) {
this.errors.push('Condition Type by can\'t be blank');
}
if (!this.target) {
this.errors.push('Target text by can\'t be blank');
Expand All @@ -55,7 +55,6 @@ export class VersionedQuestionCondition extends MySqlModel {
this.tableName,
this,
'VersionedQuestionCondition.create',
['tableName']
);
return await VersionedQuestionCondition.findById('VersionedQuestion.create', context, newId);
}
Expand Down
5 changes: 1 addition & 4 deletions src/models/VersionedSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,9 @@ export class VersionedSection extends MySqlModel {
async create(context: MyContext): Promise<VersionedSection> {
// First make sure the record is valid
if (await this.isValid()) {

// Save the record and then fetch it
const newId = await VersionedSection.insert(context, this.tableName, this, 'VersionedSection.create', ['tableName', 'tags']);
const newId = await VersionedSection.insert(context, this.tableName, this, 'VersionedSection.create', ['tags']);
return await VersionedSection.findById('VersionedSection.create', context, newId);


}
// Otherwise return as-is with all the errors
return this;
Expand Down
2 changes: 1 addition & 1 deletion src/models/VersionedTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class VersionedTemplate extends MySqlModel {
// First make sure the record is valid
if (await this.isValid()) {
// Save the record and then fetch it
const newId = await VersionedTemplate.insert(context, this.tableName, this, 'VersionedTemplate.create', ['tableName']);
const newId = await VersionedTemplate.insert(context, this.tableName, this, 'VersionedTemplate.create');
return await VersionedTemplate.findVersionedTemplateById('VersionedTemplate.create', context, newId);
}
// Otherwise return as-is with all the errors
Expand Down
Loading