2021-06-01 15:09:12 +03:00
// Utility Packages
2021-07-06 21:36:30 +03:00
const debug = require ( '@tryghost/debug' ) ( 'test' ) ;
2021-06-01 15:09:12 +03:00
const Promise = require ( 'bluebird' ) ;
const _ = require ( 'lodash' ) ;
const fs = require ( 'fs-extra' ) ;
const path = require ( 'path' ) ;
const os = require ( 'os' ) ;
const uuid = require ( 'uuid' ) ;
const KnexMigrator = require ( 'knex-migrator' ) ;
const knexMigrator = new KnexMigrator ( ) ;
// Ghost Internals
const config = require ( '../../core/shared/config' ) ;
const boot = require ( '../../core/boot' ) ;
const db = require ( '../../core/server/data/db' ) ;
const models = require ( '../../core/server/models' ) ;
const urlService = require ( '../../core/frontend/services/url' ) ;
const settingsService = require ( '../../core/server/services/settings' ) ;
2021-09-23 19:17:46 +03:00
const routeSettingsService = require ( '../../core/server/services/route-settings' ) ;
2021-06-01 15:09:12 +03:00
const web = require ( '../../core/server/web' ) ;
const themeService = require ( '../../core/server/services/themes' ) ;
const limits = require ( '../../core/server/services/limits' ) ;
// Other Test Utilities
const configUtils = require ( './configUtils' ) ;
const dbUtils = require ( './db-utils' ) ;
const urlServiceUtils = require ( './url-service-utils' ) ;
const redirects = require ( './redirects' ) ;
const context = require ( './fixtures/context' ) ;
let ghostServer ;
let existingData = { } ;
2021-07-06 21:36:30 +03:00
let totalStartTime = 0 ;
2021-06-01 15:09:12 +03:00
/ * *
* Because we use ObjectID we don ' t know the ID of fixtures ahead of time
* This function fetches all of our fixtures and exposes them so that tests can use them
2021-06-09 18:32:48 +03:00
* @ TODO : Optimize this by making it optional / selective
2021-06-01 15:09:12 +03:00
* /
const exposeFixtures = async ( ) => {
const fixturePromises = {
roles : models . Role . findAll ( { columns : [ 'id' ] } ) ,
users : models . User . findAll ( { columns : [ 'id' , 'email' ] } ) ,
tags : models . Tag . findAll ( { columns : [ 'id' ] } ) ,
apiKeys : models . ApiKey . findAll ( { withRelated : 'integration' } )
} ;
const keys = Object . keys ( fixturePromises ) ;
existingData = { } ;
return Promise
. all ( Object . values ( fixturePromises ) )
. then ( ( results ) => {
for ( let i = 0 ; i < keys . length ; i += 1 ) {
existingData [ keys [ i ] ] = results [ i ] . toJSON ( context . internal ) ;
}
} )
. catch ( ( err ) => {
console . error ( 'Unable to expose fixtures' , err ) ; // eslint-disable-line no-console
process . exit ( 1 ) ;
} ) ;
} ;
const prepareContentFolder = ( options ) => {
const contentFolderForTests = options . contentFolder ;
/ * *
* We never use the root content folder for testing !
* We use a tmp folder .
* /
configUtils . set ( 'paths:contentPath' , contentFolderForTests ) ;
fs . ensureDirSync ( contentFolderForTests ) ;
fs . ensureDirSync ( path . join ( contentFolderForTests , 'data' ) ) ;
fs . ensureDirSync ( path . join ( contentFolderForTests , 'themes' ) ) ;
fs . ensureDirSync ( path . join ( contentFolderForTests , 'images' ) ) ;
fs . ensureDirSync ( path . join ( contentFolderForTests , 'logs' ) ) ;
fs . ensureDirSync ( path . join ( contentFolderForTests , 'adapters' ) ) ;
fs . ensureDirSync ( path . join ( contentFolderForTests , 'settings' ) ) ;
if ( options . copyThemes ) {
// Copy all themes into the new test content folder. Default active theme is always casper. If you want to use a different theme, you have to set the active theme (e.g. stub)
fs . copySync ( path . join ( _ _dirname , 'fixtures' , 'themes' ) , path . join ( contentFolderForTests , 'themes' ) ) ;
}
if ( options . redirectsFile ) {
redirects . setupFile ( contentFolderForTests , options . redirectsFileExt ) ;
}
if ( options . copySettings ) {
fs . copySync ( path . join ( _ _dirname , 'fixtures' , 'settings' , 'routes.yaml' ) , path . join ( contentFolderForTests , 'settings' , 'routes.yaml' ) ) ;
}
} ;
// CASE: Ghost Server is Running
// In this case we need to reset things so it's as though Ghost just booted:
// - truncate database
// - re-run default fixtures
// - reload affected services
const restartModeGhostStart = async ( ) => {
2021-07-06 21:36:30 +03:00
debug ( 'Reload Mode' ) ;
2021-06-01 15:09:12 +03:00
// Teardown truncates all tables and also calls urlServiceUtils.reset();
await dbUtils . teardown ( ) ;
// The tables have been truncated, this runs the fixture init task (init file 2) to re-add our default fixtures
await knexMigrator . init ( { only : 2 } ) ;
2021-07-06 21:36:30 +03:00
debug ( 'init done' ) ;
2021-06-01 15:09:12 +03:00
// Reset the settings cache
await settingsService . init ( ) ;
2021-07-06 21:36:30 +03:00
debug ( 'settings done' ) ;
2021-06-01 15:09:12 +03:00
2021-09-23 19:17:46 +03:00
// Load the frontend-related components
await routeSettingsService . init ( ) ;
2021-06-01 15:09:12 +03:00
await themeService . init ( ) ;
2021-07-06 21:36:30 +03:00
debug ( 'frontend done' ) ;
2021-06-01 15:09:12 +03:00
// Reload the URL service & wait for it to be ready again
2021-06-30 12:32:04 +03:00
// @TODO: why/how is this different to urlService.resetGenerators?
2021-06-01 15:09:12 +03:00
urlServiceUtils . reset ( ) ;
urlServiceUtils . init ( ) ;
await urlServiceUtils . isFinished ( ) ;
2021-07-06 21:36:30 +03:00
debug ( 'routes done' ) ;
2021-06-01 15:09:12 +03:00
// @TODO: why does this happen _after_ URL service
web . shared . middlewares . customRedirects . reload ( ) ;
// Reload limits service
limits . init ( ) ;
} ;
const bootGhost = async ( ) => {
ghostServer = await boot ( ) ;
} ;
// CASE: Ghost Server needs Starting
// In this case we need to ensure that Ghost is started cleanly:
// - ensure the DB is reset
// - CASE: If we are in force start mode the server is already running so we
// - stop the server (if we are in force start mode it will be running)
// - reload affected services - just settings and not the frontend!?
// - Start Ghost: Uses OLD Boot process
const freshModeGhostStart = async ( options ) => {
if ( options . forceStart ) {
2021-07-06 21:36:30 +03:00
debug ( 'Forced Restart Mode' ) ;
2021-06-01 15:09:12 +03:00
} else {
2021-07-06 21:36:30 +03:00
debug ( 'Fresh Start Mode' ) ;
2021-06-01 15:09:12 +03:00
}
// Reset the DB
await knexMigrator . reset ( { force : true } ) ;
2021-06-30 12:32:04 +03:00
// Stop the server (forceStart Mode)
2021-06-01 15:09:12 +03:00
await stopGhost ( ) ;
2021-06-30 12:32:04 +03:00
// Reset the settings cache and disable listeners so they don't get triggered further
settingsService . shutdown ( ) ;
2021-06-01 15:09:12 +03:00
// Do a full database initialisation
await knexMigrator . init ( ) ;
if ( config . get ( 'database:client' ) === 'sqlite3' ) {
await db . knex . raw ( 'PRAGMA journal_mode = TRUNCATE;' ) ;
}
2021-06-30 12:32:04 +03:00
await settingsService . init ( ) ;
2021-06-01 15:09:12 +03:00
// Reset the URL service generators
// @TODO: Prob B: why/how is this different to urlService.reset?
// @TODO: why would we do this on a fresh boot?!
urlService . resetGenerators ( ) ;
// Actually boot Ghost
await bootGhost ( options ) ;
2021-06-30 12:32:04 +03:00
// Wait for the URL service to be ready, which happens after bootYou
2021-06-01 15:09:12 +03:00
await urlServiceUtils . isFinished ( ) ;
} ;
const startGhost = async ( options ) => {
2021-07-06 21:36:30 +03:00
const startTime = Date . now ( ) ;
debug ( 'Start Ghost' ) ;
2021-06-01 15:09:12 +03:00
options = _ . merge ( {
redirectsFile : true ,
redirectsFileExt : '.json' ,
forceStart : false ,
copyThemes : true ,
copySettings : true ,
contentFolder : path . join ( os . tmpdir ( ) , uuid . v4 ( ) , 'ghost-test' ) ,
subdir : false
} , options ) ;
// Ensure we have tmp content folders populated ready for testing
// @TODO: tidy up the tmp folders after tests
prepareContentFolder ( options ) ;
if ( ghostServer && ghostServer . httpServer && ! options . forceStart ) {
await restartModeGhostStart ( options ) ;
} else {
await freshModeGhostStart ( options ) ;
}
// Expose fixture data, wrap-up and return
await exposeFixtures ( ) ;
2021-07-06 21:36:30 +03:00
// Reporting
const totalTime = Date . now ( ) - startTime ;
totalStartTime += totalTime ;
debug ( ` Started Ghost in ${ totalTime / 1000 } s ` ) ;
debug ( ` Accumulated start time is ${ totalStartTime / 1000 } s ` ) ;
2021-06-01 15:09:12 +03:00
return ghostServer ;
} ;
const stopGhost = async ( ) => {
if ( ghostServer && ghostServer . httpServer ) {
await ghostServer . stop ( ) ;
delete require . cache [ require . resolve ( '../../core/app' ) ] ;
urlService . resetGenerators ( ) ;
}
} ;
module . exports = {
startGhost ,
stopGhost ,
getExistingData : ( ) => {
return existingData ;
}
} ;