snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
354
apps/meteor/tests/unit/server/publications/messages.spec.ts
Normal file
354
apps/meteor/tests/unit/server/publications/messages.spec.ts
Normal file
@@ -0,0 +1,354 @@
|
||||
import type { IMessage } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { describe, it } from 'mocha';
|
||||
import proxyquire from 'proxyquire';
|
||||
import sinon from 'sinon';
|
||||
|
||||
const mockMeteorError = class extends Error {
|
||||
constructor(
|
||||
public error: string,
|
||||
public reason: string,
|
||||
public details: any,
|
||||
) {
|
||||
super(reason);
|
||||
this.name = 'Meteor.Error';
|
||||
}
|
||||
};
|
||||
|
||||
const messagesMock = {
|
||||
findForUpdates: sinon.stub(),
|
||||
trashFindDeletedAfter: sinon.stub(),
|
||||
trashFind: sinon.stub(),
|
||||
};
|
||||
|
||||
const canAccessRoomIdAsyncStub = sinon.stub().resolves(true);
|
||||
const getChannelHistoryStub = sinon.stub();
|
||||
|
||||
const {
|
||||
extractTimestampFromCursor,
|
||||
mountCursorQuery,
|
||||
mountCursorFromMessage,
|
||||
mountNextCursor,
|
||||
mountPreviousCursor,
|
||||
handleWithoutPagination,
|
||||
handleCursorPagination,
|
||||
getMessageHistory,
|
||||
} = proxyquire.noCallThru().load('../../../../server/publications/messages', {
|
||||
'meteor/check': {
|
||||
check: sinon.stub(),
|
||||
},
|
||||
'../lib/authorization/canAccessRoom': {
|
||||
canAccessRoomIdAsync: canAccessRoomIdAsyncStub,
|
||||
},
|
||||
'../meteor-methods/messages/getChannelHistory': {
|
||||
getChannelHistory: getChannelHistoryStub,
|
||||
},
|
||||
'meteor/meteor': {
|
||||
'Meteor': {
|
||||
methods: sinon.stub(),
|
||||
Error: mockMeteorError,
|
||||
},
|
||||
'@global': true,
|
||||
},
|
||||
'@rocket.chat/models': {
|
||||
Messages: messagesMock,
|
||||
},
|
||||
});
|
||||
|
||||
describe('extractTimestampFromCursor', () => {
|
||||
it('should return the correct timestamp', () => {
|
||||
const cursor = new Date('2024-01-01T00:00:00.000Z').getTime().toString();
|
||||
const timestamp = extractTimestampFromCursor(cursor);
|
||||
expect(timestamp).to.be.an.instanceOf(Date);
|
||||
expect(timestamp.getTime()).to.equal(parseInt(cursor, 10));
|
||||
});
|
||||
|
||||
it('should handle non-date compliant string', () => {
|
||||
expect(() => extractTimestampFromCursor('not-a-date')).to.throw(Error, 'Invalid Date');
|
||||
});
|
||||
});
|
||||
|
||||
describe('mountCursorQuery', () => {
|
||||
const mockDate = new Date('2024-01-01T00:00:00.000Z').getTime();
|
||||
|
||||
it('should return a query with $gt when next is provided', () => {
|
||||
const result = mountCursorQuery({ next: mockDate.toString() });
|
||||
expect(result.query).to.deep.equal({ $gt: new Date(mockDate) });
|
||||
});
|
||||
|
||||
it('should return a query with $lt when previous is provided', () => {
|
||||
const result = mountCursorQuery({ previous: mockDate.toString() });
|
||||
expect(result.query).to.deep.equal({ $lt: new Date(mockDate) });
|
||||
});
|
||||
|
||||
it('should return a query with $gt and sort when neither next nor previous is provided', () => {
|
||||
const result = mountCursorQuery({ count: 10 });
|
||||
expect(result.query).to.deep.equal({ $gt: new Date(0) });
|
||||
expect(result.options).to.deep.equal({ sort: { _updatedAt: 1 } });
|
||||
});
|
||||
|
||||
it('should return a query with $gt and ascending sort when next is provided', () => {
|
||||
const result = mountCursorQuery({ next: mockDate.toString(), count: 10 });
|
||||
expect(result.query).to.deep.equal({ $gt: new Date(mockDate) });
|
||||
expect(result.options).to.deep.equal({ sort: { _updatedAt: 1 }, limit: 10 + 1 });
|
||||
});
|
||||
|
||||
it('should return a query with $gt and descending sort when previous is provided', () => {
|
||||
const result = mountCursorQuery({ previous: mockDate.toString(), count: 10 });
|
||||
expect(result.query).to.deep.equal({ $lt: new Date(mockDate) });
|
||||
expect(result.options).to.deep.equal({ sort: { _updatedAt: -1 }, limit: 10 + 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('mountCursorFromMessage', () => {
|
||||
it('should return the updated timestamp for UPDATED type', () => {
|
||||
const message: Pick<IMessage, '_updatedAt'> = {
|
||||
_updatedAt: new Date('2024-01-01T00:00:00Z'),
|
||||
};
|
||||
|
||||
const result = mountCursorFromMessage(message, 'UPDATED');
|
||||
expect(result).to.equal(`${message._updatedAt.getTime()}`);
|
||||
});
|
||||
|
||||
it('should return the deleted timestamp for DELETED type', () => {
|
||||
const message: Partial<IMessage> & { _deletedAt: Date } = {
|
||||
_deletedAt: new Date('2024-01-01T00:00:00Z'),
|
||||
};
|
||||
|
||||
const result = mountCursorFromMessage(message, 'DELETED');
|
||||
expect(result).to.equal(`${message._deletedAt.getTime()}`);
|
||||
});
|
||||
|
||||
it('should throw an error if DELETED type and _deletedAt is not present', () => {
|
||||
const message: Pick<IMessage, '_updatedAt'> = {
|
||||
_updatedAt: new Date('2024-01-01T00:00:00Z'),
|
||||
};
|
||||
|
||||
expect(() => mountCursorFromMessage(message, 'DELETED')).to.throw(mockMeteorError, 'Cursor not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('mountNextCursor', () => {
|
||||
const mockMessage = (timestamp: number): Pick<IMessage, '_id' | 'ts' | '_updatedAt'> => ({
|
||||
_id: '1',
|
||||
ts: new Date(timestamp),
|
||||
_updatedAt: new Date(timestamp),
|
||||
});
|
||||
|
||||
it('should return null if messages array is empty', () => {
|
||||
expect(mountNextCursor([], 10, 'UPDATED')).to.be.null;
|
||||
});
|
||||
|
||||
it('should return the first message cursor if previous is provided', () => {
|
||||
const messages = [mockMessage(1000), mockMessage(2000)];
|
||||
expect(mountNextCursor(messages, 10, 'UPDATED', undefined, 'prev')).to.equal('1000');
|
||||
});
|
||||
|
||||
it('should return null if messages length is less than or equal to count and next is provided', () => {
|
||||
const messages = [mockMessage(1000), mockMessage(2000)];
|
||||
expect(mountNextCursor(messages, 2, 'UPDATED', 'next')).to.be.null;
|
||||
});
|
||||
|
||||
it('should return the second last message cursor if messages length is greater than count and next is provided', () => {
|
||||
const messages = [mockMessage(1000), mockMessage(2000), mockMessage(3000)];
|
||||
expect(mountNextCursor(messages, 2, 'UPDATED', 'next')).to.equal('2000');
|
||||
});
|
||||
|
||||
it('should return the last message cursor if no next or previous is provided', () => {
|
||||
const messages = [mockMessage(1000), mockMessage(2000)];
|
||||
expect(mountNextCursor(messages, 10, 'UPDATED')).to.equal('2000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('mountPreviousCursor', () => {
|
||||
const mockMessage = (timestamp: number): Pick<IMessage, '_id' | 'ts' | '_updatedAt'> => ({
|
||||
_id: '1',
|
||||
ts: new Date(timestamp),
|
||||
_updatedAt: new Date(timestamp),
|
||||
});
|
||||
|
||||
it('should return null if messages array is empty', () => {
|
||||
expect(mountPreviousCursor([], 10, 'UPDATED')).to.be.null;
|
||||
});
|
||||
|
||||
it('should return the first message cursor if messages length is less than or equal to count and next is provided', () => {
|
||||
const messages = [mockMessage(1000)];
|
||||
expect(mountPreviousCursor(messages, 1, 'UPDATED', 'nextCursor')).to.equal('1000');
|
||||
});
|
||||
|
||||
it('should return the first message cursor if messages length is greater than count and next is provided', () => {
|
||||
const messages = [mockMessage(1000), mockMessage(2000)];
|
||||
expect(mountPreviousCursor(messages, 1, 'UPDATED', 'nextCursor')).to.equal('1000');
|
||||
});
|
||||
|
||||
it('should return null if messages length is less than or equal to count and previous is provided', () => {
|
||||
const messages = [mockMessage(1000)];
|
||||
expect(mountPreviousCursor(messages, 1, 'UPDATED', undefined, 'previousCursor')).to.be.null;
|
||||
});
|
||||
|
||||
it('should return the second last message cursor if messages length is greater than count and previous is provided', () => {
|
||||
const messages = [mockMessage(1000), mockMessage(2000), mockMessage(3000)];
|
||||
expect(mountPreviousCursor(messages, 2, 'UPDATED', undefined, 'previousCursor')).to.equal('2000');
|
||||
});
|
||||
|
||||
it('should return the first message cursor if no next or previous is provided', () => {
|
||||
const messages = [mockMessage(1000)];
|
||||
expect(mountPreviousCursor(messages, 1, 'UPDATED')).to.equal('1000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleWithoutPagination', () => {
|
||||
afterEach(() => {
|
||||
messagesMock.findForUpdates.reset();
|
||||
messagesMock.trashFindDeletedAfter.reset();
|
||||
});
|
||||
|
||||
it('should return updated and deleted messages', async () => {
|
||||
const rid = 'roomId';
|
||||
const lastUpdate = new Date();
|
||||
|
||||
const updatedMessages = [{ _id: '1', text: 'Hello' }];
|
||||
const deletedMessages = [{ _id: '2', _deletedAt: new Date() }];
|
||||
|
||||
messagesMock.findForUpdates.returns({ toArray: sinon.stub().resolves(updatedMessages) });
|
||||
messagesMock.trashFindDeletedAfter.returns({ toArray: sinon.stub().resolves(deletedMessages) });
|
||||
|
||||
const result = await handleWithoutPagination(rid, lastUpdate);
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
updated: updatedMessages,
|
||||
deleted: deletedMessages,
|
||||
});
|
||||
|
||||
sinon.assert.calledWith(messagesMock.findForUpdates, rid, { updatedAt: { $gt: lastUpdate }, minTs: undefined }, { sort: { ts: -1 } });
|
||||
sinon.assert.calledWith(
|
||||
messagesMock.trashFindDeletedAfter,
|
||||
lastUpdate,
|
||||
{ rid },
|
||||
{ projection: { _id: 1, _deletedAt: 1 }, sort: { ts: -1 } },
|
||||
);
|
||||
});
|
||||
|
||||
it('should bound both queries by `ts` when fromTs is provided', async () => {
|
||||
const rid = 'roomId';
|
||||
const lastUpdate = new Date('2024-01-02T00:00:00.000Z');
|
||||
const fromTs = new Date('2024-01-01T00:00:00.000Z');
|
||||
|
||||
messagesMock.findForUpdates.returns({ toArray: sinon.stub().resolves([]) });
|
||||
messagesMock.trashFindDeletedAfter.returns({ toArray: sinon.stub().resolves([]) });
|
||||
|
||||
await handleWithoutPagination(rid, lastUpdate, fromTs);
|
||||
|
||||
sinon.assert.calledWith(messagesMock.findForUpdates, rid, { updatedAt: { $gt: lastUpdate }, minTs: fromTs }, { sort: { ts: -1 } });
|
||||
sinon.assert.calledWith(
|
||||
messagesMock.trashFindDeletedAfter,
|
||||
lastUpdate,
|
||||
{ rid, ts: { $gte: fromTs } },
|
||||
{ projection: { _id: 1, _deletedAt: 1 }, sort: { ts: -1 } },
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty results', async () => {
|
||||
const rid = 'roomId';
|
||||
const lastUpdate = new Date();
|
||||
|
||||
messagesMock.findForUpdates.returns({ toArray: sinon.stub().resolves([]) });
|
||||
messagesMock.trashFindDeletedAfter.returns({ toArray: sinon.stub().resolves([]) });
|
||||
|
||||
const result = await handleWithoutPagination(rid, lastUpdate);
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
updated: [],
|
||||
deleted: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCursorPagination', () => {
|
||||
it('should return updated messages and cursor when type is UPDATED', async () => {
|
||||
const rid = 'roomId';
|
||||
const count = 10;
|
||||
const messages = [{ _id: 'msg1', _updatedAt: new Date('2024-01-01T00:00:00Z') }];
|
||||
messagesMock.findForUpdates.returns({ toArray: sinon.stub().resolves(messages) });
|
||||
|
||||
const result = await handleCursorPagination('UPDATED', rid, count);
|
||||
|
||||
expect(messagesMock.findForUpdates.calledOnce).to.be.true;
|
||||
// the cursor path has no `ts` bound to apply — it paginates on `_updatedAt` alone
|
||||
sinon.assert.calledWith(messagesMock.findForUpdates, rid, { updatedAt: { $gt: new Date(0) } }, { sort: { _updatedAt: 1 } });
|
||||
expect(result.updated).to.deep.equal(messages);
|
||||
expect(result.cursor).to.have.keys(['next', 'previous']);
|
||||
});
|
||||
|
||||
it('should return deleted messages and cursor when type is DELETED', async () => {
|
||||
const rid = 'roomId';
|
||||
const count = 10;
|
||||
const messages = [{ _id: 'msg1', _deletedAt: new Date() }];
|
||||
messagesMock.trashFind.returns({ toArray: sinon.stub().resolves(messages) });
|
||||
|
||||
const result = await handleCursorPagination('DELETED', rid, count);
|
||||
|
||||
expect(messagesMock.trashFind.calledOnce).to.be.true;
|
||||
expect(result.deleted).to.deep.equal(messages);
|
||||
expect(result.cursor).to.have.keys(['next', 'previous']);
|
||||
});
|
||||
|
||||
it('should handle empty response correctly', async () => {
|
||||
const rid = 'roomId';
|
||||
const count = 10;
|
||||
messagesMock.findForUpdates.returns({ toArray: sinon.stub().resolves([]) });
|
||||
|
||||
const result = await handleCursorPagination('UPDATED', rid, count);
|
||||
|
||||
expect(result.updated).to.deep.equal([]);
|
||||
expect(result.cursor).to.deep.equal({ next: null, previous: null });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessageHistory', () => {
|
||||
const rid = 'roomId';
|
||||
const fromId = 'userId';
|
||||
|
||||
const catchError = async (promise: Promise<unknown>): Promise<any> => {
|
||||
try {
|
||||
await promise;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
throw new Error('expected the call to be rejected');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
messagesMock.findForUpdates.reset();
|
||||
messagesMock.trashFindDeletedAfter.reset();
|
||||
messagesMock.trashFind.reset();
|
||||
getChannelHistoryStub.reset();
|
||||
});
|
||||
|
||||
it('should reject "fromTs" when combined with cursor pagination', async () => {
|
||||
const error = await catchError(getMessageHistory(rid, fromId, { fromTs: new Date(), type: 'UPDATED', next: '1704067200000' }));
|
||||
|
||||
expect(error).to.have.property('error', 'error-fromTs-requires-lastUpdate');
|
||||
sinon.assert.notCalled(messagesMock.findForUpdates);
|
||||
});
|
||||
|
||||
it('should reject "fromTs" when it would fall through to the channel history', async () => {
|
||||
const error = await catchError(getMessageHistory(rid, fromId, { fromTs: new Date() }));
|
||||
|
||||
expect(error).to.have.property('error', 'error-fromTs-requires-lastUpdate');
|
||||
sinon.assert.notCalled(getChannelHistoryStub);
|
||||
});
|
||||
|
||||
it('should bound the query by "fromTs" when "lastUpdate" is provided', async () => {
|
||||
const lastUpdate = new Date('2024-01-02T00:00:00.000Z');
|
||||
const fromTs = new Date('2024-01-01T00:00:00.000Z');
|
||||
|
||||
messagesMock.findForUpdates.returns({ toArray: sinon.stub().resolves([]) });
|
||||
messagesMock.trashFindDeletedAfter.returns({ toArray: sinon.stub().resolves([]) });
|
||||
|
||||
await getMessageHistory(rid, fromId, { lastUpdate, fromTs });
|
||||
|
||||
sinon.assert.calledWith(messagesMock.findForUpdates, rid, { updatedAt: { $gt: lastUpdate }, minTs: fromTs }, { sort: { ts: -1 } });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user