2022-02-23 21:13:42 +03:00
// Switch these lines once there are useful utils
// const testUtils = require('./utils');
require ( './utils' ) ;
const assert = require ( 'assert' ) ;
const sinon = require ( 'sinon' ) ;
2022-02-28 16:36:58 +03:00
const { LastSeenAtUpdater } = require ( '../' ) ;
2022-02-23 21:13:42 +03:00
const DomainEvents = require ( '@tryghost/domain-events' ) ;
2022-07-25 18:35:46 +03:00
const { MemberPageViewEvent , MemberCommentEvent } = require ( '@tryghost/member-events' ) ;
2022-02-28 16:36:58 +03:00
const moment = require ( 'moment' ) ;
2022-11-29 13:15:19 +03:00
const { EmailOpenedEvent } = require ( '@tryghost/email-events' ) ;
async function sleep ( ms ) {
return new Promise ( ( resolve ) => {
setTimeout ( resolve , ms ) ;
} ) ;
}
2022-02-23 21:13:42 +03:00
describe ( 'LastSeenAtUpdater' , function ( ) {
2022-05-02 21:07:30 +03:00
it ( 'Calls updateLastSeenAt on MemberPageViewEvents' , async function ( ) {
2022-02-28 16:36:58 +03:00
const now = moment ( '2022-02-28T18:00:00Z' ) . utc ( ) ;
const previousLastSeen = moment ( '2022-02-27T23:00:00Z' ) . toISOString ( ) ;
2022-05-02 21:07:30 +03:00
const stub = sinon . stub ( ) . resolves ( ) ;
2022-03-01 12:28:45 +03:00
const settingsCache = sinon . stub ( ) . returns ( 'Etc/UTC' ) ;
2022-05-02 21:07:30 +03:00
const updater = new LastSeenAtUpdater ( {
2022-03-01 12:28:45 +03:00
services : {
settingsCache : {
get : settingsCache
2022-09-07 17:41:59 +03:00
}
2022-05-02 21:07:30 +03:00
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-05-02 21:07:30 +03:00
return {
members : {
update : stub
}
} ;
2022-03-01 12:28:45 +03:00
}
} ) ;
2022-09-07 17:41:59 +03:00
updater . subscribe ( DomainEvents ) ;
2022-05-02 21:07:30 +03:00
sinon . stub ( updater , 'updateLastSeenAt' ) ;
2022-03-01 12:28:45 +03:00
DomainEvents . dispatch ( MemberPageViewEvent . create ( { memberId : '1' , memberLastSeenAt : previousLastSeen , url : '/' } , now . toDate ( ) ) ) ;
2022-05-02 21:07:30 +03:00
assert ( updater . updateLastSeenAt . calledOnceWithExactly ( '1' , previousLastSeen , now . toDate ( ) ) ) ;
2022-03-01 12:28:45 +03:00
} ) ;
2022-11-29 13:15:19 +03:00
it ( 'Calls updateLastSeenAt on email opened events' , async function ( ) {
const now = moment ( '2022-02-28T18:00:00Z' ) . utc ( ) ;
const settingsCache = sinon . stub ( ) . returns ( 'Etc/UTC' ) ;
2022-12-14 19:50:42 +03:00
const db = {
knex ( ) {
return this ;
} ,
where ( ) {
return this ;
} ,
andWhere ( ) {
return this ;
} ,
update : sinon . stub ( )
} ;
2022-11-29 13:15:19 +03:00
const updater = new LastSeenAtUpdater ( {
services : {
settingsCache : {
get : settingsCache
}
} ,
getMembersApi ( ) {
2022-12-14 19:50:42 +03:00
return { } ;
} ,
db
2022-11-29 13:15:19 +03:00
} ) ;
updater . subscribe ( DomainEvents ) ;
sinon . spy ( updater , 'updateLastSeenAt' ) ;
sinon . spy ( updater , 'updateLastSeenAtWithoutKnownLastSeen' ) ;
DomainEvents . dispatch ( EmailOpenedEvent . create ( { memberId : '1' , emailRecipientId : '1' , emailId : '1' , timestamp : now . toDate ( ) } ) ) ;
// Wait for next tick
await sleep ( 50 ) ;
assert ( updater . updateLastSeenAtWithoutKnownLastSeen . calledOnceWithExactly ( '1' , now . toDate ( ) ) ) ;
2022-12-14 19:50:42 +03:00
assert ( db . update . calledOnce ) ;
2022-11-29 13:15:19 +03:00
} ) ;
2022-07-25 18:35:46 +03:00
it ( 'Calls updateLastCommentedAt on MemberCommentEvents' , async function ( ) {
const now = moment ( '2022-02-28T18:00:00Z' ) . utc ( ) ;
const stub = sinon . stub ( ) . resolves ( ) ;
const settingsCache = sinon . stub ( ) . returns ( 'Etc/UTC' ) ;
const updater = new LastSeenAtUpdater ( {
services : {
settingsCache : {
get : settingsCache
2022-09-07 17:41:59 +03:00
}
2022-07-25 18:35:46 +03:00
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-07-25 18:35:46 +03:00
return {
members : {
update : stub
}
} ;
}
} ) ;
2022-09-07 17:41:59 +03:00
updater . subscribe ( DomainEvents ) ;
2022-07-25 18:35:46 +03:00
sinon . stub ( updater , 'updateLastCommentedAt' ) ;
DomainEvents . dispatch ( MemberCommentEvent . create ( { memberId : '1' } , now . toDate ( ) ) ) ;
assert ( updater . updateLastCommentedAt . calledOnceWithExactly ( '1' , now . toDate ( ) ) ) ;
} ) ;
2022-03-01 12:28:45 +03:00
it ( 'works correctly on another timezone (not updating last_seen_at)' , async function ( ) {
const now = moment ( '2022-02-28T04:00:00Z' ) . utc ( ) ;
const previousLastSeen = moment ( '2022-02-27T20:00:00Z' ) . toISOString ( ) ;
2022-05-02 21:07:30 +03:00
const stub = sinon . stub ( ) . resolves ( ) ;
2022-03-01 12:28:45 +03:00
const settingsCache = sinon . stub ( ) . returns ( 'Asia/Bangkok' ) ;
2022-05-02 21:07:30 +03:00
const updater = new LastSeenAtUpdater ( {
2022-03-01 12:28:45 +03:00
services : {
settingsCache : {
get : settingsCache
2022-09-07 17:41:59 +03:00
}
2022-05-02 21:07:30 +03:00
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-05-02 21:07:30 +03:00
return {
members : {
update : stub
}
} ;
2022-03-01 12:28:45 +03:00
}
} ) ;
2022-05-02 21:07:30 +03:00
await updater . updateLastSeenAt ( '1' , previousLastSeen , now . toDate ( ) ) ;
assert ( stub . notCalled , 'The LastSeenAtUpdater should attempt a member update when the new timestamp is within the same day in the publication timezone.' ) ;
2022-03-01 12:28:45 +03:00
} ) ;
2022-07-25 18:35:46 +03:00
it ( 'works correctly on another timezone (not updating last_commented_at)' , async function ( ) {
const now = moment ( '2022-02-28T04:00:00Z' ) . utc ( ) ;
const previousLastSeen = moment ( '2022-02-27T20:00:00Z' ) . toISOString ( ) ;
const stub = sinon . stub ( ) . resolves ( ) ;
const settingsCache = sinon . stub ( ) . returns ( 'Asia/Bangkok' ) ;
const updater = new LastSeenAtUpdater ( {
services : {
settingsCache : {
get : settingsCache
2022-09-07 17:41:59 +03:00
}
2022-07-25 18:35:46 +03:00
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-07-25 18:35:46 +03:00
return {
members : {
update : stub ,
get : ( ) => {
return {
id : '1' ,
get : ( ) => {
return previousLastSeen ;
}
} ;
}
}
} ;
}
} ) ;
await updater . updateLastCommentedAt ( '1' , now . toDate ( ) ) ;
assert ( stub . notCalled , 'The LastSeenAtUpdater should attempt a member update when the new timestamp is within the same day in the publication timezone.' ) ;
} ) ;
2022-03-01 12:28:45 +03:00
it ( 'works correctly on another timezone (updating last_seen_at)' , async function ( ) {
const now = moment ( '2022-02-28T04:00:00Z' ) . utc ( ) ;
const previousLastSeen = moment ( '2022-02-27T20:00:00Z' ) . toISOString ( ) ;
2022-05-02 21:07:30 +03:00
const stub = sinon . stub ( ) . resolves ( ) ;
2022-03-01 12:28:45 +03:00
const settingsCache = sinon . stub ( ) . returns ( 'Europe/Paris' ) ;
2022-05-02 21:07:30 +03:00
const updater = new LastSeenAtUpdater ( {
2022-03-01 12:28:45 +03:00
services : {
settingsCache : {
get : settingsCache
2022-09-07 17:41:59 +03:00
}
2022-05-02 21:07:30 +03:00
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-05-02 21:07:30 +03:00
return {
members : {
update : stub
}
} ;
2022-02-23 21:13:42 +03:00
}
} ) ;
2022-05-02 21:07:30 +03:00
await updater . updateLastSeenAt ( '1' , previousLastSeen , now . toDate ( ) ) ;
assert ( stub . calledOnceWithExactly ( {
2022-02-28 16:36:58 +03:00
last _seen _at : now . format ( 'YYYY-MM-DD HH:mm:ss' )
2022-02-23 21:13:42 +03:00
} , {
id : '1'
} ) , 'The LastSeenAtUpdater should attempt a member update with the current date.' ) ;
} ) ;
it ( 'Doesn\'t update when last_seen_at is too recent' , async function ( ) {
2022-02-28 16:36:58 +03:00
const now = moment ( '2022-02-28T18:00:00Z' ) ;
const previousLastSeen = moment ( '2022-02-28T00:00:00Z' ) . toISOString ( ) ;
2022-05-02 21:07:30 +03:00
const stub = sinon . stub ( ) . resolves ( ) ;
2022-03-01 12:28:45 +03:00
const settingsCache = sinon . stub ( ) . returns ( 'Etc/UTC' ) ;
2022-05-02 21:07:30 +03:00
const updater = new LastSeenAtUpdater ( {
2022-03-01 12:28:45 +03:00
services : {
settingsCache : {
get : settingsCache
2022-09-07 17:41:59 +03:00
}
2022-05-02 21:07:30 +03:00
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-05-02 21:07:30 +03:00
return {
members : {
update : stub
}
} ;
2022-02-23 21:13:42 +03:00
}
} ) ;
2022-05-02 21:07:30 +03:00
await updater . updateLastSeenAt ( '1' , previousLastSeen , now . toDate ( ) ) ;
assert ( stub . notCalled , 'The LastSeenAtUpdater should\'t update a member when the previous last_seen_at is close to the event timestamp.' ) ;
2022-02-23 21:13:42 +03:00
} ) ;
2022-07-25 18:35:46 +03:00
it ( 'Doesn\'t update when last_commented_at is too recent' , async function ( ) {
const now = moment ( '2022-02-28T18:00:00Z' ) ;
2022-09-07 17:41:59 +03:00
const previousLastSeen = moment ( '2022-02-28T00:00:00Z' ) . toDate ( ) ;
2022-07-25 18:35:46 +03:00
const stub = sinon . stub ( ) . resolves ( ) ;
const settingsCache = sinon . stub ( ) . returns ( 'Etc/UTC' ) ;
const updater = new LastSeenAtUpdater ( {
services : {
settingsCache : {
get : settingsCache
2022-09-07 17:41:59 +03:00
}
2022-07-25 18:35:46 +03:00
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-07-25 18:35:46 +03:00
return {
members : {
update : stub ,
get : ( ) => {
return {
id : '1' ,
get : ( ) => {
return previousLastSeen ;
}
} ;
}
}
} ;
}
} ) ;
await updater . updateLastCommentedAt ( '1' , now . toDate ( ) ) ;
2022-09-07 17:41:59 +03:00
assert ( stub . notCalled , 'The LastSeenAtUpdater should\'t update a member' ) ;
} ) ;
it ( 'Does not update when last_commented_at is same date in timezone' , async function ( ) {
const now = moment . utc ( '2022-02-28T18:00:00Z' ) ;
const previousLastSeen = moment . utc ( '2022-02-27T23:59:00Z' ) . toDate ( ) ;
const stub = sinon . stub ( ) . resolves ( ) ;
const settingsCache = sinon . stub ( ) . returns ( 'Europe/Brussels' ) ;
const updater = new LastSeenAtUpdater ( {
services : {
settingsCache : {
get : settingsCache
}
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-09-07 17:41:59 +03:00
return {
members : {
update : stub ,
get : ( ) => {
return {
id : '1' ,
get : ( ) => {
return previousLastSeen ;
}
} ;
}
}
} ;
}
} ) ;
await updater . updateLastCommentedAt ( '1' , now . toDate ( ) ) ;
assert ( stub . notCalled , 'The LastSeenAtUpdater should\'t update a member.' ) ;
} ) ;
it ( 'Does update when last_commented_at is different date' , async function ( ) {
const now = moment . utc ( '2022-02-28T18:00:00Z' ) ;
const previousLastSeen = moment . utc ( '2022-02-27T22:59:00Z' ) . toDate ( ) ;
const stub = sinon . stub ( ) . resolves ( ) ;
const settingsCache = sinon . stub ( ) . returns ( 'Europe/Brussels' ) ;
const updater = new LastSeenAtUpdater ( {
services : {
settingsCache : {
get : settingsCache
}
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-09-07 17:41:59 +03:00
return {
members : {
update : stub ,
get : ( ) => {
return {
id : '1' ,
get : ( ) => {
return previousLastSeen ;
}
} ;
}
}
} ;
}
} ) ;
await updater . updateLastCommentedAt ( '1' , now . toDate ( ) ) ;
assert ( stub . calledOnce , 'The LastSeenAtUpdater should attempt a member update' ) ;
assert ( stub . calledOnceWithExactly ( {
last _seen _at : now . tz ( 'utc' ) . format ( 'YYYY-MM-DD HH:mm:ss' ) ,
last _commented _at : now . tz ( 'utc' ) . format ( 'YYYY-MM-DD HH:mm:ss' )
} , {
id : '1'
} ) , 'The LastSeenAtUpdater should attempt a member update with the current date.' ) ;
} ) ;
it ( 'Does update when last_commented_at is null' , async function ( ) {
const now = moment . utc ( '2022-02-28T18:00:00Z' ) ;
const previousLastSeen = null ;
const stub = sinon . stub ( ) . resolves ( ) ;
const settingsCache = sinon . stub ( ) . returns ( 'Etc/UTC' ) ;
const updater = new LastSeenAtUpdater ( {
services : {
settingsCache : {
get : settingsCache
}
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-09-07 17:41:59 +03:00
return {
members : {
update : stub ,
get : ( ) => {
return {
id : '1' ,
get : ( ) => {
return previousLastSeen ;
}
} ;
}
}
} ;
}
} ) ;
await updater . updateLastCommentedAt ( '1' , now . toDate ( ) ) ;
assert ( stub . calledOnce , 'The LastSeenAtUpdater should attempt a member update' ) ;
assert ( stub . calledOnceWithExactly ( {
last _seen _at : now . tz ( 'utc' ) . format ( 'YYYY-MM-DD HH:mm:ss' ) ,
last _commented _at : now . tz ( 'utc' ) . format ( 'YYYY-MM-DD HH:mm:ss' )
} , {
id : '1'
} ) , 'The LastSeenAtUpdater should attempt a member update with the current date.' ) ;
2022-07-25 18:35:46 +03:00
} ) ;
2022-02-23 21:13:42 +03:00
it ( 'Doesn\'t fire on other events' , async function ( ) {
2022-02-28 16:36:58 +03:00
const now = moment ( '2022-02-28T18:00:00Z' ) ;
2022-05-02 21:07:30 +03:00
const stub = sinon . stub ( ) . resolves ( ) ;
2022-03-01 12:28:45 +03:00
const settingsCache = sinon . stub ( ) . returns ( 'Etc/UTC' ) ;
2022-05-02 21:07:30 +03:00
const updater = new LastSeenAtUpdater ( {
2022-03-01 12:28:45 +03:00
services : {
settingsCache : {
get : settingsCache
2022-09-07 17:41:59 +03:00
}
2022-05-02 21:07:30 +03:00
} ,
2022-11-29 13:15:19 +03:00
getMembersApi ( ) {
2022-05-02 21:07:30 +03:00
return {
members : {
update : stub
}
} ;
2022-02-23 21:13:42 +03:00
}
} ) ;
2022-05-02 21:07:30 +03:00
await updater . updateLastSeenAt ( '1' , undefined , now . toDate ( ) ) ;
assert ( stub . notCalled , 'The LastSeenAtUpdater should never fire on MemberPageViewEvent events.' ) ;
2022-02-23 21:13:42 +03:00
} ) ;
2022-07-25 18:35:46 +03:00
it ( 'throws if getMembersApi is not passed to LastSeenAtUpdater' , async function ( ) {
const settingsCache = sinon . stub ( ) . returns ( 'Asia/Bangkok' ) ;
2022-12-14 19:50:42 +03:00
2022-07-25 18:35:46 +03:00
should . throws ( ( ) => {
new LastSeenAtUpdater ( {
services : {
settingsCache : {
get : settingsCache
2022-09-07 17:41:59 +03:00
}
2022-07-25 18:35:46 +03:00
}
} ) ;
} , 'Missing option getMembersApi' ) ;
} ) ;
2022-02-23 21:13:42 +03:00
} ) ;