Remove `matrix-bot-sdk` usage in tests (#15)

Remove `matrix-bot-sdk` usage in tests because it didn't have timestamp massaging `?ts` and it's not really necessary to rely on since we can just call the API directly 🤷. `matrix-bot-sdk` is also very annoying having to build rust crypto packages.

We're now using direct `fetch` requests against the Matrix API and lightweight `client` object.

All 3 current tests pass 
This commit is contained in:
Eric Eastwood 2022-06-09 20:44:57 -05:00 committed by GitHub
parent 7dfe8cabc9
commit 9fc71a3412
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 467 additions and 1653 deletions

1524
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -31,8 +31,6 @@
"express": "^4.17.2",
"hydrogen-view-sdk": "^0.0.4",
"linkedom": "^0.14.1",
"matrix-bot-sdk": "^0.5.19",
"matrix-js-sdk": "^15.5.2",
"matrix-public-archive-shared": "file:./shared/",
"nconf": "^0.11.3",
"node-fetch": "^2.6.7",

View File

@ -16,7 +16,7 @@ assert(matrixServerUrl);
// Consider this scenario: dayStart(fromTs) <---- msg1 <- msg2 <-- msg3 <---- dayEnd(toTs)
// - ❌ If we start from dayStart and look backwards, we will find nothing.
// - ❌ If we start from dayStart and look forwards, we will find msg1, but federated backfill won't be able to paginate forwards
// - ✅ If we start from dayEnd and look backwards, we will msg3
// - ✅ If we start from dayEnd and look backwards, we will find msg3
// - ❌ If we start from dayEnd and look forwards, we will find nothing
//
// Returns events in reverse-chronological order.

View File

@ -20,7 +20,7 @@ function configureNodeEnv() {
}
const nodeEnv = configureNodeEnv();
console.log('nodeEnv', nodeEnv);
console.log(`Config is using nodeEnv=${nodeEnv}`);
const configDir = path.join(__dirname, '../../config');
nconf.argv().env('__');

View File

@ -33,6 +33,7 @@ async function fetchEndpoint(endpoint, options = {}) {
const res = await fetch(endpoint, {
method,
headers,
body: options.body,
});
await checkResponseStatus(res);
@ -49,11 +50,16 @@ async function fetchEndpointAsJson(endpoint, options) {
const opts = {
...options,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(options.headers || {}),
},
};
if (options.body) {
opts.body = JSON.stringify(options.body);
}
const res = await fetchEndpoint(endpoint, opts);
const data = await res.json();
return data;

View File

@ -174,7 +174,7 @@ async function mountHydrogen() {
const memberEvent = workingStateEventMap[event.user_id];
return makeEventEntryFromEventJson(event, memberEvent);
});
console.log('eventEntries', eventEntries.length);
//console.log('eventEntries', eventEntries.length);
// Map of `event_id` to `EventEntry`
const eventEntriesByEventId = eventEntries.reduce((currentMap, eventEntry) => {

View File

@ -1,17 +1,17 @@
```
```sh
$ docker pull matrixdotorg/synapse:latest
$ docker build -t matrix-public-archive-test-homeserver -f test/dockerfiles/Synapse.Dockerfile test/dockerfiles/
```
```
docker-compose -f test/docker-compose.yml up -d --no-recreate
```sh
$ docker-compose --project-name matrix_public_archive_test -f test/docker-compose.yml up -d --no-recreate
```
```
```sh
$ docker ps --all | grep test_hs
$ docker logs test_hs1_1
$ docker logs test_hs2_1
$ docker logs -f --tail 10 matrix_public_archive_test_hs1_1
$ docker logs -f --tail 10 matrix_public_archive_test_hs2_1
$ docker stop test_hs1_1 test_hs2_1
$ docker rm test_hs1_1 test_hs2_1
$ docker stop matrix_public_archive_test_hs1_1 matrix_public_archive_test_hs2_1
$ docker rm matrix_public_archive_test_hs1_1 matrix_public_archive_test_hs2_1
```

211
test/client-utils.js Normal file
View File

@ -0,0 +1,211 @@
'use strict';
const assert = require('assert');
const { URLSearchParams } = require('url');
const urlJoin = require('url-join');
const { fetchEndpointAsJson, fetchEndpoint } = require('../server/lib/fetch-endpoint');
const config = require('../server/lib/config');
const matrixAccessToken = config.get('matrixAccessToken');
assert(matrixAccessToken);
let txnCount = 0;
function getTxnId() {
txnCount++;
return `${new Date().getTime()}--${txnCount}`;
}
// Get client to act with for all of the client methods. This will use the
// application service access token and client methods will append `?user_id`
// for the specific user to act upon so we can use the `?ts` message timestamp
// massaging when sending.
async function getTestClientForHs(testMatrixServerUrl) {
// Register the virtual user
const username = `user-t${new Date().getTime()}-r${Math.floor(Math.random() * 1000000000)}`;
const registerResponse = await fetchEndpointAsJson(
urlJoin(testMatrixServerUrl, '/_matrix/client/v3/register'),
{
method: 'POST',
body: {
type: 'm.login.application_service',
username,
},
accessToken: matrixAccessToken,
}
);
const userId = registerResponse['user_id'];
assert(userId);
return {
homeserverUrl: testMatrixServerUrl,
// We use the application service AS token because we need to be able to use
// the `?ts` timestamp massaging when sending events
accessToken: matrixAccessToken,
userId: userId,
};
}
// Create a public room to test in
async function createTestRoom(client) {
let qs = new URLSearchParams();
if (client.userId) {
qs.append('user_id', client.userId);
}
const createRoomResponse = await fetchEndpointAsJson(
urlJoin(client.homeserverUrl, `/_matrix/client/v3/createRoom?${qs.toString()}`),
{
method: 'POST',
body: {
preset: 'public_chat',
name: 'the hangout spot',
initial_state: [
{
type: 'm.room.history_visibility',
state_key: '',
content: {
history_visibility: 'world_readable',
},
},
],
},
accessToken: client.accessToken,
}
);
const roomId = createRoomResponse['room_id'];
assert(roomId);
return roomId;
}
async function joinRoom({ client, roomId, viaServers }) {
let qs = new URLSearchParams();
if (viaServers) {
[].concat(viaServers).forEach((viaServer) => {
qs.append('server_name', viaServer);
});
}
if (client.userId) {
qs.append('user_id', client.userId);
}
const joinRoomResponse = await fetchEndpointAsJson(
urlJoin(client.homeserverUrl, `/_matrix/client/v3/join/${roomId}?${qs.toString()}`),
{
method: 'POST',
accessToken: client.accessToken,
}
);
const joinedRoomId = joinRoomResponse['room_id'];
assert(joinedRoomId);
return joinedRoomId;
}
async function sendEvent({ client, roomId, eventType, content, timestamp }) {
assert(client);
assert(roomId);
assert(content);
let qs = new URLSearchParams();
if (timestamp) {
assert(
timestamp && client.userId,
'We can only do `?ts` massaging from an application service access token. ' +
'Expected `client.userId` to be defined so we can act on behalf of that user'
);
qs.append('ts', timestamp);
}
if (client.userId) {
qs.append('user_id', client.userId);
}
const sendResponse = await fetchEndpointAsJson(
urlJoin(
client.homeserverUrl,
`/_matrix/client/v3/rooms/${roomId}/send/${eventType}/${getTxnId()}?${qs.toString()}`
),
{
method: 'PUT',
body: content,
accessToken: client.accessToken,
}
);
const eventId = sendResponse['event_id'];
assert(eventId);
return eventId;
}
async function sendMessage({ client, roomId, content, timestamp }) {
return sendEvent({ client, roomId, eventType: 'm.room.message', content, timestamp });
}
// Create a number of messages in the given room
async function createMessagesInRoom({ client, roomId, numMessages, prefix, timestamp }) {
let eventIds = [];
for (let i = 0; i < numMessages; i++) {
const eventId = await sendMessage({
client,
roomId,
content: {
msgtype: 'm.text',
body: `${prefix} - message${i}`,
},
timestamp,
});
eventIds.push(eventId);
}
return eventIds;
}
// Uploads the given data Buffer and returns the MXC URI of the uploaded content
async function uploadContent({ client, roomId, data, fileName, contentType }) {
assert(client);
assert(roomId);
assert(data);
let qs = new URLSearchParams();
if (client.userId) {
qs.append('user_id', client.userId);
}
if (fileName) {
qs.append('filename', fileName);
}
// We don't want to use `fetchEndpointAsJson` here because it will
// `JSON.stringify(...)` the body data
const uploadResponse = await fetchEndpoint(
urlJoin(client.homeserverUrl, `/_matrix/media/v3/upload`),
{
method: 'POST',
body: data,
headers: {
'Content-Type': contentType || 'application/octet-stream',
},
accessToken: client.accessToken,
}
);
const uploadResponseData = await uploadResponse.json();
const mxcUri = uploadResponseData['content_uri'];
assert(mxcUri);
return mxcUri;
}
module.exports = {
getTestClientForHs,
createTestRoom,
joinRoom,
sendEvent,
sendMessage,
createMessagesInRoom,
uploadContent,
};

View File

@ -24,7 +24,7 @@ COPY keys/* /ca/
RUN openssl genrsa -out /conf/server.tls.key 2048
# generate a signing key
RUN generate_signing_key.py -o /conf/server.signing.key
RUN generate_signing_key -o /conf/server.signing.key
WORKDIR /data

View File

@ -6,6 +6,7 @@ report_stats: False
signing_key_path: /conf/server.signing.key
trusted_key_servers: []
enable_registration: true
enable_registration_without_verification: true
## Listeners ##
@ -94,6 +95,13 @@ rc_joins:
federation_rr_transactions_per_room_per_second: 9999
## Media Store ##
# Whether to generate new thumbnails on the fly. This lets the image thumbnails
# load in the tests.
#
dynamic_thumbnails: true
## API Configuration ##
# A list of application service config files to use

View File

@ -3,16 +3,26 @@
process.env.NODE_ENV = 'test';
const assert = require('assert');
const path = require('path');
const urlJoin = require('url-join');
const escapeStringRegexp = require('escape-string-regexp');
const { MatrixAuth } = require('matrix-bot-sdk');
const { parseHTML } = require('linkedom');
const { fetchEndpointAsText, fetchEndpointAsJson } = require('../server/lib/fetch-endpoint');
const { readFile } = require('fs').promises;
const MatrixPublicArchiveURLCreator = require('matrix-public-archive-shared/lib/url-creator');
const { fetchEndpointAsText, fetchEndpointAsJson } = require('../server/lib/fetch-endpoint');
const config = require('../server/lib/config');
const {
getTestClientForHs,
createTestRoom,
joinRoom,
sendEvent,
sendMessage,
createMessagesInRoom,
uploadContent,
} = require('./client-utils');
const testMatrixServerUrl1 = config.get('testMatrixServerUrl1');
const testMatrixServerUrl2 = config.get('testMatrixServerUrl2');
assert(testMatrixServerUrl1);
@ -28,48 +38,6 @@ const HOMESERVER_URL_TO_PRETTY_NAME_MAP = {
[testMatrixServerUrl2]: 'hs2',
};
async function getTestClientForHs(testMatrixServerUrl) {
const auth = new MatrixAuth(testMatrixServerUrl);
const client = await auth.passwordRegister(
`user-t${new Date().getTime()}-r${Math.floor(Math.random() * 1000000000)}`,
'password'
);
return client;
}
async function createMessagesInRoom(client, roomId, numMessages, prefix) {
let eventIds = [];
for (let i = 0; i < numMessages; i++) {
const eventId = await client.sendMessage(roomId, {
msgtype: 'm.text',
body: `${prefix} - message${i}`,
});
eventIds.push(eventId);
}
return eventIds;
}
async function createTestRoom(client) {
const roomId = await client.createRoom({
preset: 'public_chat',
name: 'the hangout spot',
initial_state: [
{
type: 'm.room.history_visibility',
state_key: '',
content: {
history_visibility: 'world_readable',
},
},
],
});
return roomId;
}
describe('matrix-public-archive', () => {
let server;
before(() => {
@ -83,24 +51,28 @@ describe('matrix-public-archive', () => {
}
});
// Sanity check that our test homeservers can actually federate with each
// other. The rest of the tests won't work properly if this isn't working.
it('Test federation between fixture homeservers', async () => {
try {
describe('Test fixture homeservers', () => {
// Sanity check that our test homeservers can actually federate with each
// other. The rest of the tests won't work properly if this isn't working.
it('Test federation between fixture homeservers', async () => {
const hs1Client = await getTestClientForHs(testMatrixServerUrl1);
const hs2Client = await getTestClientForHs(testMatrixServerUrl2);
// Create a room on hs2
const hs2RoomId = await createTestRoom(hs2Client);
const room2EventIds = await createMessagesInRoom(
hs2Client,
hs2RoomId,
10,
HOMESERVER_URL_TO_PRETTY_NAME_MAP[hs2Client.homeserverUrl]
);
const room2EventIds = await createMessagesInRoom({
client: hs2Client,
roomId: hs2RoomId,
numMessages: 10,
prefix: HOMESERVER_URL_TO_PRETTY_NAME_MAP[hs2Client.homeserverUrl],
});
// Join hs1 to a room on hs2 (federation)
await hs1Client.joinRoom(hs2RoomId, 'hs2');
await joinRoom({
client: hs1Client,
roomId: hs2RoomId,
viaServers: 'hs2',
});
// From, hs1, make sure we can fetch messages from hs2
const messagesEndpoint = urlJoin(
@ -129,31 +101,59 @@ describe('matrix-public-archive', () => {
}") to be in room on hs2=${JSON.stringify(room2EventIds)}`
);
});
} catch (err) {
if (err.body) {
// FIXME: Remove this try/catch once the matrix-bot-sdk no longer throws
// huge response objects as errors, see
// https://github.com/turt2live/matrix-bot-sdk/pull/158
throw new Error(
`Error occured in matrix-bot-sdk (this new error is to stop it from logging the huge response) statusCode=${
err.statusCode
} body=${JSON.stringify(err.body)}`
);
}
throw err;
}
});
});
it('shows all events in a given day', async () => {
try {
describe('Archive', () => {
// Use a fixed date at the start of the UTC day so that the tests are
// consistent. Otherwise, the tests could fail when they start close to
// midnight and it rolls over to the next day.
const archiveDate = new Date(Date.UTC(2022, 0, 3));
let archiveUrl;
let numMessagesSent = 0;
afterEach(() => {
if (interactive) {
console.log('Interactive URL for test', archiveUrl);
}
// Reset `numMessagesSent` between tests so each test starts from the
// beginning of the day and we don't run out of minutes in the day to send
// messages in (we space messages out by a minute so the timestamp visibly
// changes in the UI).
numMessagesSent = 0;
});
// Sends a message and makes sure that a timestamp was provided
async function sendMessageOnArchiveDate(options) {
const minute = 1000 * 60;
// Adjust the timestamp by a minute each time so there is some visual difference.
options.timestamp = archiveDate.getTime() + minute * numMessagesSent;
numMessagesSent++;
return sendMessage(options);
}
// Sends a message and makes sure that a timestamp was provided
async function sendEventOnArchiveDate(options) {
const minute = 1000 * 60;
// Adjust the timestamp by a minute each time so there is some visual difference.
options.timestamp = archiveDate.getTime() + minute * numMessagesSent;
numMessagesSent++;
return sendEvent(options);
}
it('shows all events in a given day', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
const archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, new Date());
// Just render the page initially so that the archiver user is already joined to the page.
// We don't want their join event masking the one-off problem where we're missing the latest message in the room.
await fetchEndpointAsText(archiveUrl);
// Just render the page initially so that the archiver user is already
// joined to the page. We don't want their join event masking the one-off
// problem where we're missing the latest message in the room. We just use the date now
// because it will find whatever events backwards no matter when they were sent.
await fetchEndpointAsText(
matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, new Date())
);
const messageTextList = [
`Amontons' First Law: The force of friction is directly proportional to the applied load.`,
@ -164,9 +164,13 @@ describe('matrix-public-archive', () => {
const eventIds = [];
for (const messageText of messageTextList) {
const eventId = await client.sendMessage(roomId, {
msgtype: 'm.text',
body: messageText,
const eventId = await sendMessageOnArchiveDate({
client,
roomId,
content: {
msgtype: 'm.text',
body: messageText,
},
});
eventIds.push(eventId);
}
@ -174,10 +178,7 @@ describe('matrix-public-archive', () => {
// Sanity check that we actually sent some messages
assert.strictEqual(eventIds.length, 3);
if (interactive) {
console.log('Interactive URL for test', archiveUrl);
}
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, archiveDate);
const archivePageHtml = await fetchEndpointAsText(archiveUrl);
const dom = parseHTML(archivePageHtml);
@ -191,25 +192,10 @@ describe('matrix-public-archive', () => {
new RegExp(`.*${escapeStringRegexp(eventText)}.*`)
);
}
} catch (err) {
if (err.body) {
// FIXME: Remove this try/catch once the matrix-bot-sdk no longer throws
// huge response objects as errors, see
// https://github.com/turt2live/matrix-bot-sdk/pull/158
throw new Error(
`Error occured in matrix-bot-sdk (this new error is to stop it from logging the huge response) statusCode=${
err.statusCode
} body=${JSON.stringify(err.body)}`
);
}
});
throw err;
}
});
// eslint-disable-next-line max-statements
it('can render diverse messages', async () => {
try {
// eslint-disable-next-line max-statements
it('can render diverse messages', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
@ -218,84 +204,102 @@ describe('matrix-public-archive', () => {
// TODO: Set avatar of room
// Test image
const mxcUri = await client.uploadContentFromUrl(
'https://en.wikipedia.org/wiki/Friction#/media/File:Friction_between_surfaces.jpg'
// via https://en.wikipedia.org/wiki/Friction#/media/File:Friction_between_surfaces.jpg (CaoHao)
const imageBuffer = await readFile(
path.resolve(__dirname, './fixtures/friction_between_surfaces.jpg')
);
const imageEventId = await client.sendMessage(roomId, {
body: 'Friction_between_surfaces.jpeg',
info: {
size: 396644,
mimetype: 'image/jpeg',
thumbnail_info: {
w: 800,
h: 390,
const imageFileName = 'friction_between_surfaces.jpg';
const mxcUri = await uploadContent({
client,
roomId,
data: imageBuffer,
fileName: imageFileName,
});
const imageEventId = await sendMessageOnArchiveDate({
client,
roomId,
content: {
body: imageFileName,
info: {
size: 17471,
mimetype: 'image/jpeg',
size: 126496,
w: 640,
h: 312,
'xyz.amorgan.blurhash': 'LkR3G|IU?w%NbxbIemae_NxuD$M{',
},
w: 1894,
h: 925,
'xyz.amorgan.blurhash': 'LkR3G|IU?w%NbwbIemae_NxuD$M{',
// TODO: How to get a proper thumnail URL that will load?
thumbnail_url: mxcUri,
msgtype: 'm.image',
url: mxcUri,
},
msgtype: 'm.image',
url: mxcUri,
});
// A normal text message
const normalMessageText1 =
'^ Figure 1: Simulated blocks with fractal rough surfaces, exhibiting static frictional interactions';
const normalMessageEventId1 = await client.sendMessage(roomId, {
msgtype: 'm.text',
body: normalMessageText1,
const normalMessageEventId1 = await sendMessageOnArchiveDate({
client,
roomId,
content: {
msgtype: 'm.text',
body: normalMessageText1,
},
});
// Another normal text message
const normalMessageText2 =
'The topography of the Moon has been measured with laser altimetry and stereo image analysis.';
const normalMessageEventId2 = await client.sendMessage(roomId, {
msgtype: 'm.text',
body: normalMessageText2,
const normalMessageEventId2 = await sendMessageOnArchiveDate({
client,
roomId,
content: {
msgtype: 'm.text',
body: normalMessageText2,
},
});
// Test replies
const replyMessageText = `The concentration of maria on the near side likely reflects the substantially thicker crust of the highlands of the Far Side, which may have formed in a slow-velocity impact of a second moon of Earth a few tens of millions of years after the Moon's formation.`;
const replyMessageEventId = await client.sendMessage(roomId, {
'org.matrix.msc1767.message': [
{
body: '> <@ericgittertester:my.synapse.server> ${normalMessageText2}',
mimetype: 'text/plain',
},
{
body: `<mx-reply><blockquote><a href="https://matrix.to/#/${roomId}/${normalMessageEventId2}?via=my.synapse.server">In reply to</a> <a href="https://matrix.to/#/@ericgittertester:my.synapse.server">@ericgittertester:my.synapse.server</a><br>${normalMessageText2}</blockquote></mx-reply>${replyMessageText}`,
mimetype: 'text/html',
},
],
body: `> <@ericgittertester:my.synapse.server> ${normalMessageText2}\n\n${replyMessageText}`,
msgtype: 'm.text',
format: 'org.matrix.custom.html',
formatted_body: `<mx-reply><blockquote><a href="https://matrix.to/#/${roomId}/${normalMessageEventId2}?via=my.synapse.server">In reply to</a> <a href="https://matrix.to/#/@ericgittertester:my.synapse.server">@ericgittertester:my.synapse.server</a><br>${normalMessageText2}</blockquote></mx-reply>${replyMessageText}`,
'm.relates_to': {
'm.in_reply_to': {
event_id: normalMessageEventId2,
const replyMessageEventId = await sendMessageOnArchiveDate({
client,
roomId,
content: {
'org.matrix.msc1767.message': [
{
body: '> <@ericgittertester:my.synapse.server> ${normalMessageText2}',
mimetype: 'text/plain',
},
{
body: `<mx-reply><blockquote><a href="https://matrix.to/#/${roomId}/${normalMessageEventId2}?via=my.synapse.server">In reply to</a> <a href="https://matrix.to/#/@ericgittertester:my.synapse.server">@ericgittertester:my.synapse.server</a><br>${normalMessageText2}</blockquote></mx-reply>${replyMessageText}`,
mimetype: 'text/html',
},
],
body: `> <@ericgittertester:my.synapse.server> ${normalMessageText2}\n\n${replyMessageText}`,
msgtype: 'm.text',
format: 'org.matrix.custom.html',
formatted_body: `<mx-reply><blockquote><a href="https://matrix.to/#/${roomId}/${normalMessageEventId2}?via=my.synapse.server">In reply to</a> <a href="https://matrix.to/#/@ericgittertester:my.synapse.server">@ericgittertester:my.synapse.server</a><br>${normalMessageText2}</blockquote></mx-reply>${replyMessageText}`,
'm.relates_to': {
'm.in_reply_to': {
event_id: normalMessageEventId2,
},
},
},
});
// Test reactions
const reactionText = '😅';
await client.sendEvent(roomId, 'm.reaction', {
'm.relates_to': {
rel_type: 'm.annotation',
event_id: replyMessageEventId,
key: reactionText,
await sendEventOnArchiveDate({
client,
roomId,
eventType: 'm.reaction',
content: {
'm.relates_to': {
rel_type: 'm.annotation',
event_id: replyMessageEventId,
key: reactionText,
},
},
});
const archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, new Date());
if (interactive) {
console.log('Interactive URL for test', archiveUrl);
}
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, archiveDate);
const archivePageHtml = await fetchEndpointAsText(archiveUrl);
@ -305,7 +309,7 @@ describe('matrix-public-archive', () => {
const imageElement = dom.document.querySelector(`[data-event-id="${imageEventId}"] img`);
assert(imageElement);
assert.match(imageElement.getAttribute('src'), new RegExp(`^http://.*`));
assert.strictEqual(imageElement.getAttribute('alt'), 'Friction_between_surfaces.jpeg');
assert.strictEqual(imageElement.getAttribute('alt'), imageFileName);
// Make sure the normal message is visible
assert.match(
@ -337,29 +341,16 @@ describe('matrix-public-archive', () => {
replyMessageElement.outerHTML,
new RegExp(`.*${escapeStringRegexp(reactionText)}.*`)
);
} catch (err) {
if (err.body) {
// FIXME: Remove this try/catch once the matrix-bot-sdk no longer throws
// huge response objects as errors, see
// https://github.com/turt2live/matrix-bot-sdk/pull/158
throw new Error(
`Error occured in matrix-bot-sdk (this new error is to stop it from logging the huge response) statusCode=${
err.statusCode
} body=${JSON.stringify(err.body)}`
);
}
});
throw err;
}
it(`can render day back in time from room on remote homeserver we haven't backfilled from`);
it(`will redirect to hour pagination when there are too many messages`);
it(`will render a room with only a day of messages`);
it(
`will render a room with a sparse amount of messages (a few per day) with no contamination between days`
);
});
it(`can render day back in time from room on remote homeserver we haven't backfilled from`);
it(`will redirect to hour pagination when there are too many messages`);
it(`will render a room with only a day of messages`);
it(
`will render a room with a sparse amount of messages (a few per day) with no contamination between days`
);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB