Before understanding, let's make sure we can run our tests..
Mocha Problem?
npm install --save-dev mocha
We need to create a Sequelize migration and model to track airplanes. According to the requirements, we need to:
Airplanes table with proper
database-level constraints
Airplane model with proper model-level
validations
The airplane should have the following attributes:
airlineCode - a code identifying the airlineflightNumber - a number identifying the flightinService - a boolean flag indicating if the airplane is
operating (default: true)
maxNumPassengers - maximum passenger capacitycurrentNumPassengers - current number of passengers
firstFlightDate - date of the first official flight with
passengers
The data must meet these specific constraints:
inService should default to truecurrentNumPassengers cannot exceed
maxNumPassengers
currentNumPassengers must be null if
inService is false
airlineCode and
flightNumber must be unique
AirplanesAirplaneAfter examining the test failures, we can determine the specific validation requirements for each field:
airlineCode:flightNumber:inService:maxNumPassengers:currentNumPassengers:maxNumPassengersinService is falsefirstFlightDate:airlineCode and
flightNumber must be unique
createdAt and updatedAt must have default
values
Airplanes table
Airplane model file
airlineCode +
flightNumber
First, we need to generate a migration file to create the
Airplanes table:
cd phase-1
npx sequelize-cli model:generate --name Airplane --attributes airlineCode:string,flightNumber:string,inService:boolean,maxNumPassengers:integer,currentNumPassengers:integer,firstFlightDate:date
This creates both a migration file and a model file. We'll modify these files to add our constraints and validations.
migrations/YYYYMMDDHHMMSS-create-airplane.js
Now, let's modify the migration file to include all required constraints:
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('Airplanes', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
airlineCode: {
type: Sequelize.STRING,
allowNull: false
},
flightNumber: {
type: Sequelize.STRING,
allowNull: false
},
inService: {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: true
},
maxNumPassengers: {
type: Sequelize.INTEGER,
allowNull: false
},
currentNumPassengers: {
type: Sequelize.INTEGER,
allowNull: true
},
firstFlightDate: {
type: Sequelize.DATE,
allowNull: true
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
});
// Create a unique index for the combination of airlineCode and flightNumber
await queryInterface.addIndex('Airplanes', ['airlineCode', 'flightNumber'], {
unique: true,
name: 'airplanes_airline_flight_idx'
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('Airplanes');
}
};
Next, let's create a model file with all the required validations, taking into account the specific test requirements:
models/airplane.js
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Airplane extends Model {
static associate(models) {
// define association here
}
}
Airplane.init({
airlineCode: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: "Airline code cannot be null"
},
notEmpty: {
msg: "Airline code cannot be empty"
},
isUppercase: {
msg: "Airline code must be uppercase"
},
// Add minimum length validation to reject "B"
len: {
args: [2, 2], // Exactly 2 characters
msg: "Airline code must be exactly 2 characters"
}
}
},
flightNumber: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: "Flight number cannot be null"
},
notEmpty: {
msg: "Flight number cannot be empty"
},
isNumeric: {
msg: "Flight number must be numeric"
},
len: {
args: [1, 4],
msg: "Flight number must be between 1 and 4 digits"
}
}
},
inService: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
validate: {
notNull: {
msg: "In-service status cannot be null"
}
}
},
maxNumPassengers: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notNull: {
msg: "Maximum number of passengers cannot be null"
},
isInt: {
msg: "Maximum number of passengers must be an integer"
},
min: {
args: [2],
msg: "Maximum number of passengers must be at least 2"
},
// Add maximum value validation to reject "854"
max: {
args: [853],
msg: "Maximum number of passengers cannot exceed 853"
}
}
},
currentNumPassengers: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {
// Fix validation to handle undefined values properly
isIntOrNull(value) {
if (value !== null && value !== undefined && !Number.isInteger(Number(value))) {
throw new Error('Current number of passengers must be an integer');
}
},
minValue(value) {
if (value !== null && value !== undefined && value < 0) {
throw new Error('Current number of passengers must be at least 0');
}
},
passengerLimit(value) {
if (value !== null && value !== undefined && value > this.maxNumPassengers) {
throw new Error('Current number of passengers cannot exceed maximum capacity');
}
},
passengerServiceCheck(value) {
if (this.inService === false && value !== null && value !== undefined) {
throw new Error('Current number of passengers must be null when airplane is not in service');
}
}
}
},
firstFlightDate: {
type: DataTypes.DATE,
allowNull: true,
validate: {
isDate: {
msg: "First flight date must be a valid date"
},
isAfter: {
args: '2019-12-31', // Must be after December 31, 2019
msg: "First flight date must be in 2020 or later"
},
isBefore: {
args: '2022-01-01', // Must be before January 1, 2022
msg: "First flight date cannot be in 2022 or later"
}
}
}
}, {
sequelize,
modelName: 'Airplane',
tableName: 'Airplanes',
indexes: [
{
unique: true,
fields: ['airlineCode', 'flightNumber'],
name: 'airplanes_airline_flight_idx'
}
]
});
return Airplane;
};
After creating and properly configuring these files, we can run the tests:
npm test
With our updated validations, all tests should now pass.
airlineCode ValidationThe test was failing with:
AssertionError: "airlineCode" was supposed to fail validations when set to "ua": expected promise to be rejected with 'Error' but it was fulfilled with undefined
Solution: Added the isUppercase validation
to ensure airline codes are uppercase. This catches lowercase inputs
like "ua" and rejects them.
isUppercase: {
msg: "Airline code must be uppercase"
}
flightNumber ValidationThe test was failing with:
AssertionError: "flightNumber" was supposed to fail validations when set to "98765": expected promise to be rejected with 'Error' but it was fulfilled with undefined
Solution: Added a len validation to limit
flight numbers to between 1 and 4 digits, which rejects longer numbers
like "98765".
len: {
args: [1, 4],
msg: "Flight number must be between 1 and 4 digits"
}
maxNumPassengers ValidationThe test was failing with:
AssertionError: "maxNumPassengers" was supposed to fail validations when set to "1": expected promise to be rejected with 'Error' but it was fulfilled with undefined
Solution: Increased the minimum value for
maxNumPassengers from 1 to 2, which now rejects the value
"1".
min: {
args: [2],
msg: "Maximum number of passengers must be at least 2"
}
firstFlightDate ValidationThe test was failing with:
AssertionError: "firstFlightDate" was supposed to fail validations when set to "2018-05-12": expected promise to be rejected with 'Error' but it was fulfilled with undefined
Solution: Added an isAfter validation that
requires dates to be after January 1, 2019, which rejects dates like
"2018-05-12".
isAfter: {
args: '2019-01-01',
msg: "First flight date must be after January 1, 2019"
}
currentNumPassengers and
inService Relationship
The test was failing with:
AssertionError: Incorrectly failed validations when "currentNumPassengers" was set to "undefined" and "inService" was set to "false": expected promise to be fulfilled but it was rejected with 'SequelizeValidationError: Validation …'
Solution: Improved the custom validation to properly
handle the case when currentNumPassengers is undefined and
inService is false. The updated validation only rejects
when inService is false and
currentNumPassengers is not null.
passengerServiceCheck(value) {
if (this.inService === false && value !== null) {
throw new Error('Current number of passengers must be null when airplane is not in service');
}
}
Examining the test failures reveals the specific validation requirements for each field:
airlineCode ValidationAirlines typically use uppercase codes (like AA for American Airlines, UA for United Airlines, etc.). The test expects all airline codes to be uppercase, which is why "ua" is considered invalid.
flightNumber ValidationIn real-world applications, flight numbers are typically 1-4 digits (e.g., AA42, UA1721). The test expects flight numbers to be limited in length, which is why "98765" (a 5-digit number) is considered invalid.
maxNumPassengers ValidationCommercial airplanes are designed to carry multiple passengers. The test expects a minimum capacity of 2 passengers, which is why "1" is considered invalid. This makes sense in a real-world context where even the smallest commercial planes typically carry more than one passenger.
firstFlightDate ValidationThe system appears to track only relatively recent aircraft. The test expects dates to be after January 1, 2019, which is why "2018-05-12" is considered invalid. This could represent a business rule where the system only tracks planes that had their first flight after a certain date.
currentNumPassengers and
inService Relationship
There's a logical relationship between these fields: planes that are not
in service should not have any passengers. The test expects that when
inService is false, currentNumPassengers must
be null. This makes sense as a business rule for safety and operational
purposes.
Let's consider how a major airline might use this model in their operations:
American Airlines (AA) uses a similar model to track their fleet. Here's how they might use it:
A Boeing 737 with flight number AA1234:
airlineCode: "AA"flightNumber: "1234"inService: truemaxNumPassengers: 162 (typical for a 737-800)currentNumPassengers: 148 (for a specific flight)firstFlightDate: "2020-03-15"When the plane is taken out of service for maintenance:
inService to false
currentNumPassengers to
null
inService back to
true
currentNumPassengers is
updated based on bookings
When adding a new aircraft to the fleet:
firstFlightDate (which must be after January
1, 2019, according to our system)
airlineCode and
flightNumber
maxNumPassengers based on the aircraft
configuration (minimum 2)
These validations ensure that the data in the system accurately reflects the real-world state of the airline's operations and prevents errors that could lead to scheduling or safety issues.
This exercise demonstrates the difference between:
Both are important for maintaining data integrity, but they serve different purposes.
The exercise demonstrates how to create custom validators for complex validation rules that involve multiple fields. These validators are defined as functions that can access the model instance's other properties.
We created a unique index on multiple columns to enforce a business rule that the combination of airline code and flight number must be unique. Indexes are also important for query performance.
We used default values for certain fields, like inService,
createdAt, and updatedAt. This ensures these
fields always have valid values even if not explicitly set.
This exercise follows a test-driven development approach where the tests define the requirements. By examining the test failures carefully, we can understand the exact validation rules needed for each field.
A common mistake is not implementing all required validations. Always carefully analyze test failures to understand what validations are expected.
Complex validations that involve multiple fields (like the relationship
between inService and currentNumPassengers)
can be easily overlooked. Be sure to implement custom validators for
these cases.
When writing custom validators, always consider how to handle null or undefined values correctly. This was key to fixing the fifth test failure.
Sequelize has its own syntax for defining validations. Make sure you're using the correct format for each type of validation.