mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Reorganized test cases for lastSeenAtUpdater
(#20963)
no issue - These tests were all in a single `describe` block, making it difficult to navigate and add more tests. - This commit adds a nested `describe` block for each method on the `lastSeenAtUpdater`, making it easier to understand the test cases, and to add more. None of the tests themselves have changed, just the organization of them.
This commit is contained in:
parent
482cf08ee7
commit
a5dfd6016e
1 changed files with 469 additions and 461 deletions
|
@ -19,481 +19,489 @@ describe('LastSeenAtUpdater', function () {
|
|||
DomainEvents.ee.removeAllListeners();
|
||||
});
|
||||
|
||||
it('Calls updateLastSeenAt on MemberPageViewEvents', async function () {
|
||||
const now = moment('2022-02-28T18:00:00Z').utc();
|
||||
const previousLastSeen = moment('2022-02-27T23:00:00Z').toISOString();
|
||||
const stub = sinon.stub().resolves();
|
||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
updater.subscribe(DomainEvents);
|
||||
sinon.stub(updater, 'updateLastSeenAt');
|
||||
DomainEvents.dispatch(MemberPageViewEvent.create({memberId: '1', memberLastSeenAt: previousLastSeen, url: '/'}, now.toDate()));
|
||||
assert(updater.updateLastSeenAt.calledOnceWithExactly('1', previousLastSeen, now.toDate()));
|
||||
});
|
||||
describe('constructor', function () {
|
||||
it('throws if getMembersApi is not passed to LastSeenAtUpdater', async function () {
|
||||
const settingsCache = sinon.stub().returns('Asia/Bangkok');
|
||||
|
||||
it('Calls updateLastSeenAt on MemberLinkClickEvents', async function () {
|
||||
const now = moment('2022-02-28T18:00:00Z').utc();
|
||||
const previousLastSeen = moment('2022-02-27T23:00:00Z').toISOString();
|
||||
const stub = sinon.stub().resolves();
|
||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
updater.subscribe(DomainEvents);
|
||||
sinon.stub(updater, 'updateLastSeenAt');
|
||||
DomainEvents.dispatch(MemberLinkClickEvent.create({memberId: '1', memberLastSeenAt: previousLastSeen, url: '/'}, now.toDate()));
|
||||
assert(updater.updateLastSeenAt.calledOnceWithExactly('1', previousLastSeen, now.toDate()));
|
||||
});
|
||||
|
||||
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');
|
||||
const db = {
|
||||
knex() {
|
||||
return this;
|
||||
},
|
||||
where() {
|
||||
return this;
|
||||
},
|
||||
andWhere() {
|
||||
return this;
|
||||
},
|
||||
update: sinon.stub()
|
||||
};
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {};
|
||||
},
|
||||
db,
|
||||
events
|
||||
});
|
||||
updater.subscribe(DomainEvents);
|
||||
sinon.spy(updater, 'updateLastSeenAt');
|
||||
sinon.spy(updater, 'updateLastSeenAtWithoutKnownLastSeen');
|
||||
DomainEvents.dispatch(EmailOpenedEvent.create({memberId: '1', emailRecipientId: '1', emailId: '1', timestamp: now.toDate()}));
|
||||
await DomainEvents.allSettled();
|
||||
assert(updater.updateLastSeenAtWithoutKnownLastSeen.calledOnceWithExactly('1', now.toDate()));
|
||||
assert(db.update.calledOnce);
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
updater.subscribe(DomainEvents);
|
||||
sinon.stub(updater, 'updateLastCommentedAt');
|
||||
DomainEvents.dispatch(MemberCommentEvent.create({memberId: '1'}, now.toDate()));
|
||||
assert(updater.updateLastCommentedAt.calledOnceWithExactly('1', now.toDate()));
|
||||
});
|
||||
|
||||
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();
|
||||
const transactionStub = sinon.stub().callsFake((callback) => {
|
||||
return callback();
|
||||
});
|
||||
const saveStub = sinon.stub().resolves();
|
||||
const getStub = sinon.stub().resolves({get: () => previousLastSeen, save: saveStub});
|
||||
const settingsCache = sinon.stub().returns('Asia/Bangkok');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
get: getStub
|
||||
}
|
||||
};
|
||||
},
|
||||
db: {
|
||||
knex: {
|
||||
transaction: transactionStub
|
||||
}
|
||||
},
|
||||
events
|
||||
});
|
||||
await updater.updateLastSeenAt('1', previousLastSeen, now.toDate());
|
||||
assert(saveStub.notCalled, 'The LastSeenAtUpdater should attempt a member update when the new timestamp is within the same day in the publication timezone.');
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
should.throws(() => {
|
||||
new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
}, 'Missing option getMembersApi');
|
||||
});
|
||||
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.');
|
||||
});
|
||||
|
||||
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();
|
||||
const transactionStub = sinon.stub().callsFake((callback) => {
|
||||
return callback();
|
||||
});
|
||||
const saveStub = sinon.stub().resolves();
|
||||
const refreshStub = sinon.stub().resolves({save: saveStub});
|
||||
const getStub = sinon.stub().resolves({get: () => previousLastSeen, refresh: refreshStub});
|
||||
const settingsCache = sinon.stub().returns('Europe/Paris');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
get: getStub
|
||||
}
|
||||
};
|
||||
},
|
||||
db: {
|
||||
knex: {
|
||||
transaction: transactionStub
|
||||
}
|
||||
},
|
||||
events
|
||||
});
|
||||
await updater.updateLastSeenAt('1', previousLastSeen, now.toDate());
|
||||
assert(saveStub.calledOnceWithExactly(
|
||||
sinon.match({last_seen_at: now.tz('utc').format('YYYY-MM-DD HH:mm:ss')}),
|
||||
sinon.match({transacting: sinon.match.any, patch: true, method: 'update'})
|
||||
), 'The LastSeenAtUpdater should attempt a member update with the current date.');
|
||||
});
|
||||
|
||||
it('Doesn\'t update when last_seen_at is too recent', async function () {
|
||||
const now = moment('2022-02-28T18:00:00Z');
|
||||
const previousLastSeen = moment('2022-02-28T00:00:00Z').toISOString();
|
||||
const saveStub = sinon.stub().resolves();
|
||||
const getStub = sinon.stub().resolves({get: () => previousLastSeen, save: saveStub});
|
||||
const transactionStub = sinon.stub().callsFake((callback) => {
|
||||
return callback();
|
||||
});
|
||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
get: getStub
|
||||
}
|
||||
};
|
||||
},
|
||||
db: {
|
||||
knex: {
|
||||
transaction: transactionStub
|
||||
}
|
||||
},
|
||||
events
|
||||
});
|
||||
await updater.updateLastSeenAt('1', previousLastSeen, now.toDate());
|
||||
assert(saveStub.notCalled, 'The LastSeenAtUpdater should\'t update a member when the previous last_seen_at is close to the event timestamp.');
|
||||
});
|
||||
|
||||
it('Doesn\'t update when last_commented_at is too recent', async function () {
|
||||
const now = moment('2022-02-28T18:00:00Z');
|
||||
const previousLastSeen = moment('2022-02-28T00:00:00Z').toDate();
|
||||
const stub = sinon.stub().resolves();
|
||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
await updater.updateLastCommentedAt('1', now.toDate());
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
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('Doesn\'t fire on other events', async function () {
|
||||
const spy = sinon.spy();
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: () => 'Etc/UTC'
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: spy
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
updater.subscribe(DomainEvents);
|
||||
DomainEvents.dispatch(MemberSubscribeEvent.create({memberId: '1', source: 'api'}, new Date()));
|
||||
await DomainEvents.allSettled();
|
||||
assert(spy.notCalled, 'The LastSeenAtUpdater should never fire on MemberSubscribeEvent events.');
|
||||
});
|
||||
|
||||
it('throws if getMembersApi is not passed to LastSeenAtUpdater', async function () {
|
||||
const settingsCache = sinon.stub().returns('Asia/Bangkok');
|
||||
|
||||
should.throws(() => {
|
||||
new LastSeenAtUpdater({
|
||||
describe('subscribe', function () {
|
||||
it('Calls updateLastSeenAt on MemberPageViewEvents', async function () {
|
||||
const now = moment('2022-02-28T18:00:00Z').utc();
|
||||
const previousLastSeen = moment('2022-02-27T23:00:00Z').toISOString();
|
||||
const stub = sinon.stub().resolves();
|
||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
}, 'Missing option getMembersApi');
|
||||
updater.subscribe(DomainEvents);
|
||||
sinon.stub(updater, 'updateLastSeenAt');
|
||||
DomainEvents.dispatch(MemberPageViewEvent.create({memberId: '1', memberLastSeenAt: previousLastSeen, url: '/'}, now.toDate()));
|
||||
assert(updater.updateLastSeenAt.calledOnceWithExactly('1', previousLastSeen, now.toDate()));
|
||||
});
|
||||
|
||||
it('Calls updateLastSeenAt on MemberLinkClickEvents', async function () {
|
||||
const now = moment('2022-02-28T18:00:00Z').utc();
|
||||
const previousLastSeen = moment('2022-02-27T23:00:00Z').toISOString();
|
||||
const stub = sinon.stub().resolves();
|
||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
updater.subscribe(DomainEvents);
|
||||
sinon.stub(updater, 'updateLastSeenAt');
|
||||
DomainEvents.dispatch(MemberLinkClickEvent.create({memberId: '1', memberLastSeenAt: previousLastSeen, url: '/'}, now.toDate()));
|
||||
assert(updater.updateLastSeenAt.calledOnceWithExactly('1', previousLastSeen, now.toDate()));
|
||||
});
|
||||
|
||||
it('Calls updateLastSeenAt on EmailOpenedEvents', async function () {
|
||||
const now = moment('2022-02-28T18:00:00Z').utc();
|
||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||
const db = {
|
||||
knex() {
|
||||
return this;
|
||||
},
|
||||
where() {
|
||||
return this;
|
||||
},
|
||||
andWhere() {
|
||||
return this;
|
||||
},
|
||||
update: sinon.stub()
|
||||
};
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {};
|
||||
},
|
||||
db,
|
||||
events
|
||||
});
|
||||
updater.subscribe(DomainEvents);
|
||||
sinon.spy(updater, 'updateLastSeenAt');
|
||||
sinon.spy(updater, 'updateLastSeenAtWithoutKnownLastSeen');
|
||||
DomainEvents.dispatch(EmailOpenedEvent.create({memberId: '1', emailRecipientId: '1', emailId: '1', timestamp: now.toDate()}));
|
||||
await DomainEvents.allSettled();
|
||||
assert(updater.updateLastSeenAtWithoutKnownLastSeen.calledOnceWithExactly('1', now.toDate()));
|
||||
assert(db.update.calledOnce);
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
updater.subscribe(DomainEvents);
|
||||
sinon.stub(updater, 'updateLastCommentedAt');
|
||||
DomainEvents.dispatch(MemberCommentEvent.create({memberId: '1'}, now.toDate()));
|
||||
assert(updater.updateLastCommentedAt.calledOnceWithExactly('1', now.toDate()));
|
||||
});
|
||||
|
||||
it('Doesn\'t fire on other events', async function () {
|
||||
const spy = sinon.spy();
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: () => 'Etc/UTC'
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: spy
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
updater.subscribe(DomainEvents);
|
||||
DomainEvents.dispatch(MemberSubscribeEvent.create({memberId: '1', source: 'api'}, new Date()));
|
||||
await DomainEvents.allSettled();
|
||||
assert(spy.notCalled, 'The LastSeenAtUpdater should never fire on MemberSubscribeEvent events.');
|
||||
});
|
||||
});
|
||||
|
||||
it('avoids a race condition when updating last_seen_at', async function () {
|
||||
const now = moment.utc('2022-02-28T18:00:00Z');
|
||||
const saveStub = sinon.stub().resolves();
|
||||
const refreshStub = sinon.stub().resolves({save: saveStub});
|
||||
const settingsCache = sinon.stub().returns('Europe/Brussels');
|
||||
const transactionStub = sinon.stub().callsFake((callback) => {
|
||||
return callback();
|
||||
});
|
||||
const getStub = sinon.stub();
|
||||
getStub.onFirstCall().resolves({get: () => null, save: saveStub, refresh: refreshStub});
|
||||
getStub.onSecondCall().resolves({get: () => now.toDate(), save: saveStub, refresh: refreshStub});
|
||||
getStub.resolves({get: () => now.toDate(), save: saveStub, refresh: refreshStub});
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
get: getStub
|
||||
describe('updateLastSeenAt', function () {
|
||||
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();
|
||||
const transactionStub = sinon.stub().callsFake((callback) => {
|
||||
return callback();
|
||||
});
|
||||
const saveStub = sinon.stub().resolves();
|
||||
const getStub = sinon.stub().resolves({get: () => previousLastSeen, save: saveStub});
|
||||
const settingsCache = sinon.stub().returns('Asia/Bangkok');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
};
|
||||
},
|
||||
db: {
|
||||
knex: {
|
||||
transaction: transactionStub
|
||||
}
|
||||
},
|
||||
events
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
get: getStub
|
||||
}
|
||||
};
|
||||
},
|
||||
db: {
|
||||
knex: {
|
||||
transaction: transactionStub
|
||||
}
|
||||
},
|
||||
events
|
||||
});
|
||||
await updater.updateLastSeenAt('1', previousLastSeen, now.toDate());
|
||||
assert(saveStub.notCalled, 'The LastSeenAtUpdater should attempt a member update when the new timestamp is within the same day in the publication timezone.');
|
||||
});
|
||||
sinon.stub(events, 'emit');
|
||||
await Promise.all([
|
||||
updater.updateLastSeenAt('1', null, now.toDate()),
|
||||
updater.updateLastSeenAt('1', null, now.toDate()),
|
||||
updater.updateLastSeenAt('1', null, now.toDate()),
|
||||
updater.updateLastSeenAt('1', null, now.toDate())
|
||||
]);
|
||||
assert(saveStub.calledOnce, `The LastSeenAtUpdater should attempt a member update only once, but was called ${saveStub.callCount} times`);
|
||||
assert(saveStub.calledOnceWithExactly(
|
||||
sinon.match({last_seen_at: now.tz('utc').format('YYYY-MM-DD HH:mm:ss')}),
|
||||
sinon.match({transacting: undefined, patch: true, method: 'update'})
|
||||
), 'The LastSeenAtUpdater should attempt a member update with the current date.');
|
||||
|
||||
assert(events.emit.calledOnceWithExactly(
|
||||
'member.edited',
|
||||
sinon.match.any
|
||||
), 'The LastSeenAtUpdater should emit a member.edited event if it updated last_seen_at');
|
||||
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();
|
||||
const transactionStub = sinon.stub().callsFake((callback) => {
|
||||
return callback();
|
||||
});
|
||||
const saveStub = sinon.stub().resolves();
|
||||
const refreshStub = sinon.stub().resolves({save: saveStub});
|
||||
const getStub = sinon.stub().resolves({get: () => previousLastSeen, refresh: refreshStub});
|
||||
const settingsCache = sinon.stub().returns('Europe/Paris');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
get: getStub
|
||||
}
|
||||
};
|
||||
},
|
||||
db: {
|
||||
knex: {
|
||||
transaction: transactionStub
|
||||
}
|
||||
},
|
||||
events
|
||||
});
|
||||
await updater.updateLastSeenAt('1', previousLastSeen, now.toDate());
|
||||
assert(saveStub.calledOnceWithExactly(
|
||||
sinon.match({last_seen_at: now.tz('utc').format('YYYY-MM-DD HH:mm:ss')}),
|
||||
sinon.match({transacting: sinon.match.any, patch: true, method: 'update'})
|
||||
), 'The LastSeenAtUpdater should attempt a member update with the current date.');
|
||||
});
|
||||
|
||||
it('Doesn\'t update when last_seen_at is too recent', async function () {
|
||||
const now = moment('2022-02-28T18:00:00Z');
|
||||
const previousLastSeen = moment('2022-02-28T00:00:00Z').toISOString();
|
||||
const saveStub = sinon.stub().resolves();
|
||||
const getStub = sinon.stub().resolves({get: () => previousLastSeen, save: saveStub});
|
||||
const transactionStub = sinon.stub().callsFake((callback) => {
|
||||
return callback();
|
||||
});
|
||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
get: getStub
|
||||
}
|
||||
};
|
||||
},
|
||||
db: {
|
||||
knex: {
|
||||
transaction: transactionStub
|
||||
}
|
||||
},
|
||||
events
|
||||
});
|
||||
await updater.updateLastSeenAt('1', previousLastSeen, now.toDate());
|
||||
assert(saveStub.notCalled, 'The LastSeenAtUpdater should\'t update a member when the previous last_seen_at is close to the event timestamp.');
|
||||
});
|
||||
|
||||
it('avoids a race condition when updating last_seen_at', async function () {
|
||||
const now = moment.utc('2022-02-28T18:00:00Z');
|
||||
const saveStub = sinon.stub().resolves();
|
||||
const refreshStub = sinon.stub().resolves({save: saveStub});
|
||||
const settingsCache = sinon.stub().returns('Europe/Brussels');
|
||||
const transactionStub = sinon.stub().callsFake((callback) => {
|
||||
return callback();
|
||||
});
|
||||
const getStub = sinon.stub();
|
||||
getStub.onFirstCall().resolves({get: () => null, save: saveStub, refresh: refreshStub});
|
||||
getStub.onSecondCall().resolves({get: () => now.toDate(), save: saveStub, refresh: refreshStub});
|
||||
getStub.resolves({get: () => now.toDate(), save: saveStub, refresh: refreshStub});
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
get: getStub
|
||||
}
|
||||
};
|
||||
},
|
||||
db: {
|
||||
knex: {
|
||||
transaction: transactionStub
|
||||
}
|
||||
},
|
||||
events
|
||||
});
|
||||
sinon.stub(events, 'emit');
|
||||
await Promise.all([
|
||||
updater.updateLastSeenAt('1', null, now.toDate()),
|
||||
updater.updateLastSeenAt('1', null, now.toDate()),
|
||||
updater.updateLastSeenAt('1', null, now.toDate()),
|
||||
updater.updateLastSeenAt('1', null, now.toDate())
|
||||
]);
|
||||
assert(saveStub.calledOnce, `The LastSeenAtUpdater should attempt a member update only once, but was called ${saveStub.callCount} times`);
|
||||
assert(saveStub.calledOnceWithExactly(
|
||||
sinon.match({last_seen_at: now.tz('utc').format('YYYY-MM-DD HH:mm:ss')}),
|
||||
sinon.match({transacting: undefined, patch: true, method: 'update'})
|
||||
), 'The LastSeenAtUpdater should attempt a member update with the current date.');
|
||||
|
||||
assert(events.emit.calledOnceWithExactly(
|
||||
'member.edited',
|
||||
sinon.match.any
|
||||
), 'The LastSeenAtUpdater should emit a member.edited event if it updated last_seen_at');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateLastCommentedAt', function () {
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
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.');
|
||||
});
|
||||
|
||||
it('Doesn\'t update when last_commented_at is too recent', async function () {
|
||||
const now = moment('2022-02-28T18:00:00Z');
|
||||
const previousLastSeen = moment('2022-02-28T00:00:00Z').toDate();
|
||||
const stub = sinon.stub().resolves();
|
||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||
const updater = new LastSeenAtUpdater({
|
||||
services: {
|
||||
settingsCache: {
|
||||
get: settingsCache
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
await updater.updateLastCommentedAt('1', now.toDate());
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
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
|
||||
}
|
||||
},
|
||||
getMembersApi() {
|
||||
return {
|
||||
members: {
|
||||
update: stub,
|
||||
get: () => {
|
||||
return {
|
||||
id: '1',
|
||||
get: () => {
|
||||
return previousLastSeen;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
events
|
||||
});
|
||||
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.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue