0
Fork 0
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:
Chris Raible 2024-09-10 15:40:57 -07:00 committed by GitHub
parent 482cf08ee7
commit a5dfd6016e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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');
}); });
}); });