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,6 +19,23 @@ describe('LastSeenAtUpdater', function () {
|
||||||
DomainEvents.ee.removeAllListeners();
|
DomainEvents.ee.removeAllListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('constructor', function () {
|
||||||
|
it('throws if getMembersApi is not passed to LastSeenAtUpdater', async function () {
|
||||||
|
const settingsCache = sinon.stub().returns('Asia/Bangkok');
|
||||||
|
|
||||||
|
should.throws(() => {
|
||||||
|
new LastSeenAtUpdater({
|
||||||
|
services: {
|
||||||
|
settingsCache: {
|
||||||
|
get: settingsCache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 'Missing option getMembersApi');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('subscribe', function () {
|
||||||
it('Calls updateLastSeenAt on MemberPageViewEvents', async function () {
|
it('Calls updateLastSeenAt on MemberPageViewEvents', async function () {
|
||||||
const now = moment('2022-02-28T18:00:00Z').utc();
|
const now = moment('2022-02-28T18:00:00Z').utc();
|
||||||
const previousLastSeen = moment('2022-02-27T23:00:00Z').toISOString();
|
const previousLastSeen = moment('2022-02-27T23:00:00Z').toISOString();
|
||||||
|
@ -71,7 +88,7 @@ describe('LastSeenAtUpdater', function () {
|
||||||
assert(updater.updateLastSeenAt.calledOnceWithExactly('1', previousLastSeen, now.toDate()));
|
assert(updater.updateLastSeenAt.calledOnceWithExactly('1', previousLastSeen, now.toDate()));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Calls updateLastSeenAt on email opened events', async function () {
|
it('Calls updateLastSeenAt on EmailOpenedEvents', async function () {
|
||||||
const now = moment('2022-02-28T18:00:00Z').utc();
|
const now = moment('2022-02-28T18:00:00Z').utc();
|
||||||
const settingsCache = sinon.stub().returns('Etc/UTC');
|
const settingsCache = sinon.stub().returns('Etc/UTC');
|
||||||
const db = {
|
const db = {
|
||||||
|
@ -132,6 +149,31 @@ describe('LastSeenAtUpdater', function () {
|
||||||
assert(updater.updateLastCommentedAt.calledOnceWithExactly('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.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateLastSeenAt', function () {
|
||||||
it('works correctly on another timezone (not updating last_seen_at)', async function () {
|
it('works correctly on another timezone (not updating last_seen_at)', async function () {
|
||||||
const now = moment('2022-02-28T04:00:00Z').utc();
|
const now = moment('2022-02-28T04:00:00Z').utc();
|
||||||
const previousLastSeen = moment('2022-02-27T20:00:00Z').toISOString();
|
const previousLastSeen = moment('2022-02-27T20:00:00Z').toISOString();
|
||||||
|
@ -165,38 +207,6 @@ describe('LastSeenAtUpdater', function () {
|
||||||
assert(saveStub.notCalled, 'The LastSeenAtUpdater should attempt a member update when the new timestamp is within the same day in the publication timezone.');
|
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
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('works correctly on another timezone (updating last_seen_at)', async function () {
|
it('works correctly on another timezone (updating last_seen_at)', async function () {
|
||||||
const now = moment('2022-02-28T04:00:00Z').utc();
|
const now = moment('2022-02-28T04:00:00Z').utc();
|
||||||
const previousLastSeen = moment('2022-02-27T20:00:00Z').toISOString();
|
const previousLastSeen = moment('2022-02-27T20:00:00Z').toISOString();
|
||||||
|
@ -267,6 +277,91 @@ describe('LastSeenAtUpdater', function () {
|
||||||
assert(saveStub.notCalled, 'The LastSeenAtUpdater should\'t update a member when the previous last_seen_at is close to the event timestamp.');
|
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 () {
|
it('Doesn\'t update when last_commented_at is too recent', async function () {
|
||||||
const now = moment('2022-02-28T18:00:00Z');
|
const now = moment('2022-02-28T18:00:00Z');
|
||||||
const previousLastSeen = moment('2022-02-28T00:00:00Z').toDate();
|
const previousLastSeen = moment('2022-02-28T00:00:00Z').toDate();
|
||||||
|
@ -408,92 +503,5 @@ describe('LastSeenAtUpdater', function () {
|
||||||
id: '1'
|
id: '1'
|
||||||
}), 'The LastSeenAtUpdater should attempt a member update with the current date.');
|
}), '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({
|
|
||||||
services: {
|
|
||||||
settingsCache: {
|
|
||||||
get: settingsCache
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 'Missing option getMembersApi');
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue