2020-05-25 11:49:38 +03:00
const errors = require ( '@tryghost/errors' ) ;
2020-04-29 18:44:27 +03:00
const should = require ( 'should' ) ;
const sinon = require ( 'sinon' ) ;
const _ = require ( 'lodash' ) ;
const Promise = require ( 'bluebird' ) ;
2020-08-11 16:01:16 +03:00
const security = require ( '@tryghost/security' ) ;
2020-04-29 18:44:27 +03:00
const models = require ( '../../../../core/server/models' ) ;
2020-05-28 13:57:02 +03:00
const urlUtils = require ( '../../../../core/shared/url-utils' ) ;
2020-04-29 18:44:27 +03:00
const testUtils = require ( '../../../utils' ) ;
2018-03-05 11:10:27 +03:00
describe ( 'Models: base' , function ( ) {
before ( function ( ) {
models . init ( ) ;
} ) ;
afterEach ( function ( ) {
2019-01-21 19:53:44 +03:00
sinon . restore ( ) ;
2018-03-05 11:10:27 +03:00
} ) ;
2018-10-06 23:13:52 +03:00
describe ( 'generateSlug' , function ( ) {
let Model ;
let options = { } ;
beforeEach ( function ( ) {
2019-01-21 19:53:44 +03:00
sinon . stub ( security . string , 'safe' ) ;
2019-06-18 16:13:55 +03:00
sinon . stub ( urlUtils , 'getProtectedSlugs' ) . returns ( [ 'upsi' , 'schwupsi' ] ) ;
2018-10-06 23:13:52 +03:00
2019-01-21 19:53:44 +03:00
Model = sinon . stub ( ) ;
2018-10-06 23:13:52 +03:00
Model . prototype = {
tableName : 'tableName'
} ;
2019-01-21 19:53:44 +03:00
Model . findOne = sinon . stub ( ) ;
2018-10-06 23:13:52 +03:00
} ) ;
it ( 'default' , function ( ) {
Model . findOne . resolves ( false ) ;
security . string . safe . withArgs ( 'My-Slug' ) . returns ( 'my-slug' ) ;
return models . Base . Model . generateSlug ( Model , 'My-Slug' , options )
. then ( ( slug ) => {
slug . should . eql ( 'my-slug' ) ;
} ) ;
} ) ;
it ( 'slug exists' , function ( ) {
let i = 0 ;
Model . findOne . callsFake ( ( ) => {
i = i + 1 ;
if ( i === 1 ) {
return Promise . resolve ( true ) ;
}
return Promise . resolve ( false ) ;
} ) ;
security . string . safe . withArgs ( 'My-Slug' ) . returns ( 'my-slug' ) ;
return models . Base . Model . generateSlug ( Model , 'My-Slug' , options )
. then ( ( slug ) => {
slug . should . eql ( 'my-slug-2' ) ;
} ) ;
} ) ;
it ( 'too long' , function ( ) {
Model . findOne . resolves ( false ) ;
const slug = new Array ( 500 ) . join ( 'a' ) ;
security . string . safe . withArgs ( slug ) . returns ( slug ) ;
return models . Base . Model . generateSlug ( Model , slug , options )
2020-10-19 07:45:26 +03:00
. then ( ( generatedSlug ) => {
generatedSlug . should . eql ( new Array ( 186 ) . join ( 'a' ) ) ;
2018-10-06 23:13:52 +03:00
} ) ;
} ) ;
it ( 'protected slug' , function ( ) {
Model . findOne . resolves ( false ) ;
const slug = 'upsi' ;
security . string . safe . withArgs ( slug ) . returns ( slug ) ;
return models . Base . Model . generateSlug ( Model , slug , options )
2020-10-19 07:45:26 +03:00
. then ( ( generatedSlug ) => {
generatedSlug . should . eql ( 'upsi-tableName' ) ;
2018-10-06 23:13:52 +03:00
} ) ;
} ) ;
it ( 'internal tag' , function ( ) {
Model . findOne . resolves ( false ) ;
const slug = '#lul' ;
Model . prototype = {
tableName : 'tag'
} ;
security . string . safe . withArgs ( slug ) . returns ( slug ) ;
return models . Base . Model . generateSlug ( Model , slug , options )
2020-10-19 07:45:26 +03:00
. then ( ( generatedSlug ) => {
generatedSlug . should . eql ( 'hash-#lul' ) ;
2018-10-06 23:13:52 +03:00
} ) ;
} ) ;
2018-12-13 15:06:59 +03:00
it ( 'contains invisible unicode' , function ( ) {
Model . findOne . resolves ( false ) ;
2019-05-07 07:48:28 +03:00
security . string . safe . withArgs ( 'abc\u0008' ) . returns ( 'abc' ) ;
2018-12-13 15:06:59 +03:00
return models . Base . Model . generateSlug ( Model , 'abc\u0008' , options )
. then ( ( slug ) => {
slug . should . eql ( 'abc' ) ;
} ) ;
} ) ;
2018-10-06 23:13:52 +03:00
} ) ;
describe ( 'sanitizeData' , function ( ) {
2018-04-05 17:11:47 +03:00
it ( 'date is invalid' , function ( ) {
const data = testUtils . DataGenerator . forKnex . createPost ( { updated _at : '0000-00-00 00:00:00' } ) ;
try {
models . Base . Model . sanitizeData
. bind ( { prototype : { tableName : 'posts' } } ) ( data ) ;
} catch ( err ) {
err . code . should . eql ( 'DATE_INVALID' ) ;
}
} ) ;
it ( 'expect date transformation' , function ( ) {
const data = testUtils . DataGenerator . forKnex . createPost ( { updated _at : '2018-04-01 07:53:07' } ) ;
data . updated _at . should . be . a . String ( ) ;
models . Base . Model . sanitizeData
. bind ( { prototype : { tableName : 'posts' } } ) ( data ) ;
data . updated _at . should . be . a . Date ( ) ;
} ) ;
it ( 'date is JS date, ignore' , function ( ) {
const data = testUtils . DataGenerator . forKnex . createPost ( { updated _at : new Date ( ) } ) ;
data . updated _at . should . be . a . Date ( ) ;
models . Base . Model . sanitizeData
. bind ( { prototype : { tableName : 'posts' } } ) ( data ) ;
data . updated _at . should . be . a . Date ( ) ;
} ) ;
it ( 'expect date transformation for nested relations' , function ( ) {
const data = testUtils . DataGenerator . forKnex . createPost ( {
authors : [ {
name : 'Thomas' ,
updated _at : '2018-04-01 07:53:07'
} ]
} ) ;
data . authors [ 0 ] . updated _at . should . be . a . String ( ) ;
models . Base . Model . sanitizeData
. bind ( {
prototype : {
tableName : 'posts' ,
relationships : [ 'authors' ] ,
relationshipBelongsTo : { authors : 'users' }
}
} ) ( data ) ;
data . authors [ 0 ] . name . should . eql ( 'Thomas' ) ;
data . authors [ 0 ] . updated _at . should . be . a . Date ( ) ;
} ) ;
} ) ;
2018-10-06 23:13:52 +03:00
describe ( 'setEmptyValuesToNull' , function ( ) {
2018-03-05 11:10:27 +03:00
it ( 'resets given empty value to null' , function ( ) {
const base = models . Base . Model . forge ( { a : '' , b : '' } ) ;
2019-01-28 18:58:28 +03:00
base . getNullableStringProperties = sinon . stub ( ) ;
base . getNullableStringProperties . returns ( [ 'a' ] ) ;
2018-03-05 11:10:27 +03:00
base . get ( 'a' ) . should . eql ( '' ) ;
base . get ( 'b' ) . should . eql ( '' ) ;
base . setEmptyValuesToNull ( ) ;
should . not . exist ( base . get ( 'a' ) ) ;
base . get ( 'b' ) . should . eql ( '' ) ;
} ) ;
} ) ;
2018-10-01 15:44:52 +03:00
2018-10-06 23:13:52 +03:00
describe ( 'destroy' , function ( ) {
2018-10-01 15:44:52 +03:00
it ( 'forges model using destroyBy, fetches it, and calls destroy, passing filtered options' , function ( ) {
const unfilteredOptions = {
destroyBy : {
prop : 'whatever'
}
} ;
const model = models . Base . Model . forge ( { } ) ;
2019-01-21 19:53:44 +03:00
const filterOptionsSpy = sinon . spy ( models . Base . Model , 'filterOptions' ) ;
const forgeStub = sinon . stub ( models . Base . Model , 'forge' )
2018-10-01 15:44:52 +03:00
. returns ( model ) ;
2019-01-21 19:53:44 +03:00
const fetchStub = sinon . stub ( model , 'fetch' )
2018-10-01 15:44:52 +03:00
. resolves ( model ) ;
2019-01-21 19:53:44 +03:00
const destroyStub = sinon . stub ( model , 'destroy' ) ;
2018-10-01 15:44:52 +03:00
return models . Base . Model . destroy ( unfilteredOptions ) . then ( ( ) => {
should . equal ( filterOptionsSpy . args [ 0 ] [ 0 ] , unfilteredOptions ) ;
should . equal ( filterOptionsSpy . args [ 0 ] [ 1 ] , 'destroy' ) ;
should . deepEqual ( forgeStub . args [ 0 ] [ 0 ] , {
prop : 'whatever'
} ) ;
const filteredOptions = filterOptionsSpy . returnValues [ 0 ] ;
should . equal ( fetchStub . args [ 0 ] [ 0 ] , filteredOptions ) ;
should . equal ( destroyStub . args [ 0 ] [ 0 ] , filteredOptions ) ;
} ) ;
} ) ;
it ( 'uses options.id to forge model, if no destroyBy is provided' , function ( ) {
const unfilteredOptions = {
id : 23
} ;
const model = models . Base . Model . forge ( { } ) ;
2019-01-21 19:53:44 +03:00
const filterOptionsSpy = sinon . spy ( models . Base . Model , 'filterOptions' ) ;
const forgeStub = sinon . stub ( models . Base . Model , 'forge' )
2018-10-01 15:44:52 +03:00
. returns ( model ) ;
2019-01-21 19:53:44 +03:00
const fetchStub = sinon . stub ( model , 'fetch' )
2018-10-01 15:44:52 +03:00
. resolves ( model ) ;
2019-01-21 19:53:44 +03:00
const destroyStub = sinon . stub ( model , 'destroy' ) ;
2018-10-01 15:44:52 +03:00
return models . Base . Model . destroy ( unfilteredOptions ) . then ( ( ) => {
should . equal ( filterOptionsSpy . args [ 0 ] [ 0 ] , unfilteredOptions ) ;
should . equal ( filterOptionsSpy . args [ 0 ] [ 1 ] , 'destroy' ) ;
should . deepEqual ( forgeStub . args [ 0 ] [ 0 ] , {
id : 23
} ) ;
const filteredOptions = filterOptionsSpy . returnValues [ 0 ] ;
should . equal ( fetchStub . args [ 0 ] [ 0 ] , filteredOptions ) ;
should . equal ( destroyStub . args [ 0 ] [ 0 ] , filteredOptions ) ;
} ) ;
} ) ;
} ) ;
2018-10-02 17:43:19 +03:00
2018-10-06 23:13:52 +03:00
describe ( 'findOne' , function ( ) {
2018-10-02 17:43:19 +03:00
it ( 'forges model using filtered data, fetches it passing filtered options and resolves with the fetched model' , function ( ) {
const data = {
id : 670
} ;
const unfilteredOptions = {
donny : 'donson'
} ;
const model = models . Base . Model . forge ( { } ) ;
const fetchedModel = models . Base . Model . forge ( { } ) ;
2019-01-21 19:53:44 +03:00
const filterOptionsSpy = sinon . spy ( models . Base . Model , 'filterOptions' ) ;
const filterDataSpy = sinon . spy ( models . Base . Model , 'filterData' ) ;
const forgeStub = sinon . stub ( models . Base . Model , 'forge' )
2018-10-02 17:43:19 +03:00
. returns ( model ) ;
2019-01-21 19:53:44 +03:00
const fetchStub = sinon . stub ( model , 'fetch' )
2018-10-02 17:43:19 +03:00
. resolves ( fetchedModel ) ;
const findOneReturnValue = models . Base . Model . findOne ( data , unfilteredOptions ) ;
should . equal ( findOneReturnValue , fetchStub . returnValues [ 0 ] ) ;
return findOneReturnValue . then ( ( result ) => {
should . equal ( result , fetchedModel ) ;
should . equal ( filterOptionsSpy . args [ 0 ] [ 0 ] , unfilteredOptions ) ;
should . equal ( filterOptionsSpy . args [ 0 ] [ 1 ] , 'findOne' ) ;
should . equal ( filterDataSpy . args [ 0 ] [ 0 ] , data ) ;
const filteredData = filterDataSpy . returnValues [ 0 ] ;
should . deepEqual ( forgeStub . args [ 0 ] [ 0 ] , filteredData ) ;
const filteredOptions = filterOptionsSpy . returnValues [ 0 ] ;
should . equal ( fetchStub . args [ 0 ] [ 0 ] , filteredOptions ) ;
} ) ;
} ) ;
} ) ;
2018-10-06 23:13:52 +03:00
describe ( 'edit' , function ( ) {
2018-10-02 17:43:19 +03:00
it ( 'resolves with the savedModel after forges model w/ id, fetches w/ filtered options, saves w/ filtered data and options and method=update' , function ( ) {
const data = {
life : 'suffering'
} ;
const unfilteredOptions = {
2019-07-05 14:40:43 +03:00
id : 'something real special'
2018-10-02 17:43:19 +03:00
} ;
const model = models . Base . Model . forge ( { } ) ;
const savedModel = models . Base . Model . forge ( { } ) ;
2019-01-21 19:53:44 +03:00
const filterOptionsSpy = sinon . spy ( models . Base . Model , 'filterOptions' ) ;
const filterDataSpy = sinon . spy ( models . Base . Model , 'filterData' ) ;
const forgeStub = sinon . stub ( models . Base . Model , 'forge' )
2018-10-02 17:43:19 +03:00
. returns ( model ) ;
2019-01-21 19:53:44 +03:00
const fetchStub = sinon . stub ( model , 'fetch' )
2018-10-02 17:43:19 +03:00
. resolves ( model ) ;
2019-01-21 19:53:44 +03:00
const saveStub = sinon . stub ( model , 'save' )
2018-10-02 17:43:19 +03:00
. resolves ( savedModel ) ;
return models . Base . Model . edit ( data , unfilteredOptions ) . then ( ( result ) => {
should . equal ( result , savedModel ) ;
should . equal ( filterOptionsSpy . args [ 0 ] [ 0 ] , unfilteredOptions ) ;
should . equal ( filterOptionsSpy . args [ 0 ] [ 1 ] , 'edit' ) ;
should . equal ( filterDataSpy . args [ 0 ] [ 0 ] , data ) ;
const filteredOptions = filterOptionsSpy . returnValues [ 0 ] ;
should . deepEqual ( forgeStub . args [ 0 ] [ 0 ] , { id : filteredOptions . id } ) ;
should . equal ( fetchStub . args [ 0 ] [ 0 ] , filteredOptions ) ;
const filteredData = filterDataSpy . returnValues [ 0 ] ;
should . equal ( saveStub . args [ 0 ] [ 0 ] , filteredData ) ;
should . equal ( saveStub . args [ 0 ] [ 1 ] . method , 'update' ) ;
should . deepEqual ( saveStub . args [ 0 ] [ 1 ] , filteredOptions ) ;
} ) ;
} ) ;
it ( 'sets model.hasTimestamps to false if options.importing is truthy' , function ( ) {
const data = {
base : 'cannon'
} ;
const unfilteredOptions = {
importing : true
} ;
const model = models . Base . Model . forge ( { } ) ;
2019-01-21 19:53:44 +03:00
const forgeStub = sinon . stub ( models . Base . Model , 'forge' )
2018-10-02 17:43:19 +03:00
. returns ( model ) ;
2019-01-21 19:53:44 +03:00
const fetchStub = sinon . stub ( model , 'fetch' )
2018-10-02 17:43:19 +03:00
. resolves ( ) ;
return models . Base . Model . findOne ( data , unfilteredOptions ) . then ( ( ) => {
should . equal ( model . hasTimestamps , true ) ;
} ) ;
} ) ;
2019-02-22 06:17:14 +03:00
it ( 'throws an error if model cannot be found on edit' , function ( ) {
2018-10-02 17:43:19 +03:00
const data = {
db : 'cooper'
} ;
const unfilteredOptions = {
2019-07-05 14:40:43 +03:00
id : 'something real special'
2018-10-02 17:43:19 +03:00
} ;
const model = models . Base . Model . forge ( { } ) ;
2019-01-21 19:53:44 +03:00
const filterOptionsSpy = sinon . spy ( models . Base . Model , 'filterOptions' ) ;
const filterDataSpy = sinon . spy ( models . Base . Model , 'filterData' ) ;
const forgeStub = sinon . stub ( models . Base . Model , 'forge' )
2018-10-02 17:43:19 +03:00
. returns ( model ) ;
2019-01-21 19:53:44 +03:00
const fetchStub = sinon . stub ( model , 'fetch' )
2018-10-02 17:43:19 +03:00
. resolves ( ) ;
2019-01-21 19:53:44 +03:00
const saveSpy = sinon . stub ( model , 'save' ) ;
2018-10-02 17:43:19 +03:00
2019-02-22 06:17:14 +03:00
return models . Base . Model . edit ( data , unfilteredOptions ) . then ( ( ) => {
throw new Error ( 'That should not happen' ) ;
} ) . catch ( ( err ) => {
2020-05-25 11:49:38 +03:00
( err instanceof errors . NotFoundError ) . should . be . true ( ) ;
2018-10-02 17:43:19 +03:00
} ) ;
} ) ;
} ) ;
2018-10-06 23:13:52 +03:00
describe ( 'add' , function ( ) {
2018-10-02 17:43:19 +03:00
it ( 'forges model w/ filtered data, saves w/ null and options and method=insert' , function ( ) {
const data = {
rum : 'ham'
} ;
const unfilteredOptions = { } ;
const model = models . Base . Model . forge ( { } ) ;
const savedModel = models . Base . Model . forge ( { } ) ;
2019-01-21 19:53:44 +03:00
const filterOptionsSpy = sinon . spy ( models . Base . Model , 'filterOptions' ) ;
const filterDataSpy = sinon . spy ( models . Base . Model , 'filterData' ) ;
const forgeStub = sinon . stub ( models . Base . Model , 'forge' )
2018-10-02 17:43:19 +03:00
. returns ( model ) ;
2019-01-21 19:53:44 +03:00
const saveStub = sinon . stub ( model , 'save' )
2018-10-02 17:43:19 +03:00
. resolves ( savedModel ) ;
return models . Base . Model . add ( data , unfilteredOptions ) . then ( ( result ) => {
should . equal ( result , savedModel ) ;
should . equal ( filterOptionsSpy . args [ 0 ] [ 0 ] , unfilteredOptions ) ;
should . equal ( filterOptionsSpy . args [ 0 ] [ 1 ] , 'add' ) ;
should . equal ( filterDataSpy . args [ 0 ] [ 0 ] , data ) ;
const filteredData = filterDataSpy . returnValues [ 0 ] ;
should . deepEqual ( forgeStub . args [ 0 ] [ 0 ] , filteredData ) ;
const filteredOptions = filterOptionsSpy . returnValues [ 0 ] ;
should . equal ( saveStub . args [ 0 ] [ 0 ] , null ) ;
should . equal ( saveStub . args [ 0 ] [ 1 ] . method , 'insert' ) ;
should . deepEqual ( saveStub . args [ 0 ] [ 1 ] , filteredOptions ) ;
} ) ;
} ) ;
it ( 'sets model.hasTimestamps to false if options.importing is truthy' , function ( ) {
const data = {
newham : 'generals'
} ;
const unfilteredOptions = {
importing : true
} ;
const model = models . Base . Model . forge ( { } ) ;
2019-01-21 19:53:44 +03:00
const forgeStub = sinon . stub ( models . Base . Model , 'forge' )
2018-10-02 17:43:19 +03:00
. returns ( model ) ;
2019-01-21 19:53:44 +03:00
const saveStub = sinon . stub ( model , 'save' )
2018-10-02 17:43:19 +03:00
. resolves ( ) ;
return models . Base . Model . add ( data , unfilteredOptions ) . then ( ( ) => {
should . equal ( model . hasTimestamps , false ) ;
} ) ;
} ) ;
} ) ;
2018-03-05 11:10:27 +03:00
} ) ;