matrix-public-archive/test/e2e-tests.js

1228 lines
50 KiB
JavaScript
Raw Normal View History

2022-02-22 15:06:29 -07:00
'use strict';
process.env.NODE_ENV = 'test';
2022-02-22 15:06:29 -07:00
const assert = require('assert');
const path = require('path');
const http = require('http');
const urlJoin = require('url-join');
2022-02-24 02:27:53 -07:00
const escapeStringRegexp = require('escape-string-regexp');
const { parseHTML } = require('linkedom');
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,
getCanonicalAlias,
joinRoom,
sendEvent,
sendMessage,
createMessagesInRoom,
updateProfile,
uploadContent,
} = require('./lib/client-utils');
const TestError = require('./lib/test-error');
const testMatrixServerUrl1 = config.get('testMatrixServerUrl1');
const testMatrixServerUrl2 = config.get('testMatrixServerUrl2');
assert(testMatrixServerUrl1);
assert(testMatrixServerUrl2);
2022-02-24 02:27:53 -07:00
const basePath = config.get('basePath');
assert(basePath);
2022-02-24 02:27:53 -07:00
const interactive = config.get('interactive');
const matrixPublicArchiveURLCreator = new MatrixPublicArchiveURLCreator(basePath);
2022-02-22 15:06:29 -07:00
const HOMESERVER_URL_TO_PRETTY_NAME_MAP = {
[testMatrixServerUrl1]: 'hs1',
[testMatrixServerUrl2]: 'hs2',
};
2022-02-22 15:06:29 -07:00
describe('matrix-public-archive', () => {
2022-02-24 02:27:53 -07:00
let server;
before(() => {
// Start the archive server
server = require('../server/server');
});
after(() => {
2022-02-24 02:27:53 -07:00
if (!interactive) {
server.close();
}
});
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({
client: hs2Client,
roomId: hs2RoomId,
numMessages: 10,
prefix: HOMESERVER_URL_TO_PRETTY_NAME_MAP[hs2Client.homeserverUrl],
});
2022-02-22 19:55:42 -07:00
// Join hs1 to a room on hs2 (federation)
await joinRoom({
client: hs1Client,
roomId: hs2RoomId,
viaServers: 'hs2',
});
2022-02-22 19:55:42 -07:00
// From, hs1, make sure we can fetch messages from hs2
const messagesEndpoint = urlJoin(
hs1Client.homeserverUrl,
`_matrix/client/r0/rooms/${hs2RoomId}/messages?limit=5&dir=b&filter={"types":["m.room.message"]}`
);
const { data: messageResData } = await fetchEndpointAsJson(messagesEndpoint, {
accessToken: hs1Client.accessToken,
2022-02-22 19:55:42 -07:00
});
// Make sure it returned some messages
assert.strictEqual(messageResData.chunk.length, 5);
// Make sure all of the messages belong to the room
messageResData.chunk.map((event) => {
const isEventInRoomFromHs2 = room2EventIds.some((room2EventId) => {
return room2EventId === event.event_id;
});
// Make sure the message belongs to the room on hs2
assert.strictEqual(
isEventInRoomFromHs2,
true,
`Expected ${event.event_id} (${event.type}: "${
event.content.body
}") to be in room on hs2=${JSON.stringify(room2EventIds)}`
);
});
});
});
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.
// January 15th, 2022
const archiveDate = new Date(Date.UTC(2022, 0, 15));
let archiveUrl;
let numMessagesSent = 0;
afterEach(() => {
if (interactive) {
Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events (#43) Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events. Like if someone is replying to an event that was from long ago out of our range. In the case of missing relations, Hydrogen does `_loadContextEntryNotInTimeline` because it can't find the event locally which throws an `uncaughtException`. Before https://github.com/matrix-org/matrix-public-archive/pull/51, the `uncaughtException` killed the Hydrogen `child_process` before it could pass back the HTML. Now this PR mainly just adds a test to make sure it works. ``` TypeError: Cannot read properties of undefined (reading 'storeNames') at TimelineReader.readById (hydrogen-web\target\lib-build\hydrogen.cjs.js:12483:33) at Timeline._getEventFromStorage (hydrogen-web\target\lib-build\hydrogen.cjs.js:12762:46) at Timeline._loadContextEntryNotInTimeline (hydrogen-web\target\lib-build\hydrogen.cjs.js:12747:35) at Timeline._loadContextEntriesWhereNeeded (hydrogen-web\target\lib-build\hydrogen.cjs.js:12741:14) at Timeline.addEntries (hydrogen-web\target\lib-build\hydrogen.cjs.js:12699:10) at mountHydrogen (4-hydrogen-vm-render-script.js:204:12) at 4-hydrogen-vm-render-script.js:353:1 at Script.runInContext (node:vm:139:12) at _renderHydrogenToStringUnsafe (matrix-public-archive\server\hydrogen-render\3-render-hydrogen-to-string-unsafe.js:102:41) at async process.<anonymous> (matrix-public-archive\server\hydrogen-render\2-render-hydrogen-to-string-fork-script.js:18:27) ```
2022-08-29 18:42:18 -06:00
// eslint-disable-next-line no-console
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;
// Reset any custom modifications made for a particular test
config.reset();
});
// 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);
}
2022-02-22 19:55:42 -07:00
describe('Archive room view', () => {
it('shows all events in a given day', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
// 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.`,
`Amontons' Second Law: The force of friction is independent of the apparent area of contact.`,
// We're aiming for this to be the last message in the room
`Coulomb's Law of Friction: Kinetic friction is independent of the sliding velocity.`,
];
// TODO: Can we use `createMessagesInRoom` here instead?
const eventIds = [];
for (const messageText of messageTextList) {
const eventId = await sendMessageOnArchiveDate({
client,
roomId,
content: {
msgtype: 'm.text',
body: messageText,
},
});
eventIds.push(eventId);
}
// Sanity check that we actually sent some messages
assert.strictEqual(eventIds.length, 3);
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, archiveDate);
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
const dom = parseHTML(archivePageHtml);
// Make sure the messages are visible
for (let i = 0; i < eventIds.length; i++) {
const eventId = eventIds[i];
const eventText = messageTextList[i];
assert.match(
dom.document.querySelector(`[data-event-id="${eventId}"]`).outerHTML,
new RegExp(`.*${escapeStringRegexp(eventText)}.*`)
);
}
});
// eslint-disable-next-line max-statements
it('can render diverse messages', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
2022-02-24 12:06:19 -07:00
const userAvatarBuffer = Buffer.from(
// Purple PNG pixel
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mPsD9j0HwAFmQKScbjOAwAAAABJRU5ErkJggg==',
'base64'
);
const userAvatarMxcUri = await uploadContent({
client,
roomId,
data: userAvatarBuffer,
fileName: 'client user avatar',
});
const displayName = `${client.userId}-some-display-name`;
await updateProfile({
client,
displayName,
avatarUrl: userAvatarMxcUri,
});
2022-02-24 12:06:19 -07:00
// TODO: Set avatar of room
2022-02-24 12:06:19 -07:00
// Test image
// 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 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',
w: 640,
h: 312,
'xyz.amorgan.blurhash': 'LkR3G|IU?w%NbxbIemae_NxuD$M{',
},
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 sendMessageOnArchiveDate({
client,
roomId,
content: {
msgtype: 'm.text',
body: normalMessageText1,
},
2022-02-24 12:06:19 -07:00
});
// Another normal text message
const normalMessageText2 =
'The topography of the Moon has been measured with laser altimetry and stereo image analysis.';
const normalMessageEventId2 = await sendMessageOnArchiveDate({
client,
roomId,
content: {
msgtype: 'm.text',
body: normalMessageText2,
},
});
2022-02-24 12:06:19 -07:00
// 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 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 to make sure we can render the page when the reply is missing the
// event it's replying to (the relation).
const replyMissingRelationMessageText = `While the giant-impact theory explains many lines of evidence, some questions are still unresolved, most of which involve the Moon's composition.`;
const missingRelationEventId = '$someMissingEvent';
const replyMissingRelationMessageEventId = await sendMessageOnArchiveDate({
client,
roomId,
content: {
'org.matrix.msc1767.message': [
{
body: '> <@ericgittertester:my.synapse.server> some missing message',
mimetype: 'text/plain',
},
{
body: `<mx-reply><blockquote><a href="https://matrix.to/#/${roomId}/${missingRelationEventId}?via=my.synapse.server">In reply to</a> <a href="https://matrix.to/#/@ericgittertester:my.synapse.server">@ericgittertester:my.synapse.server</a><br>some missing message</blockquote></mx-reply>${replyMissingRelationMessageText}`,
mimetype: 'text/html',
},
],
body: `> <@ericgittertester:my.synapse.server> some missing message\n\n${replyMissingRelationMessageText}`,
msgtype: 'm.text',
format: 'org.matrix.custom.html',
formatted_body: `<mx-reply><blockquote><a href="https://matrix.to/#/${roomId}/${missingRelationEventId}?via=my.synapse.server">In reply to</a> <a href="https://matrix.to/#/@ericgittertester:my.synapse.server">@ericgittertester:my.synapse.server</a><br>some missing message</blockquote></mx-reply>${replyMissingRelationMessageText}`,
'm.relates_to': {
'm.in_reply_to': {
event_id: missingRelationEventId,
},
},
},
});
// Test reactions
const reactionText = '😅';
await sendEventOnArchiveDate({
client,
roomId,
eventType: 'm.reaction',
content: {
'm.relates_to': {
rel_type: 'm.annotation',
event_id: replyMessageEventId,
key: reactionText,
},
},
});
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, archiveDate);
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
2022-02-24 12:06:19 -07:00
const dom = parseHTML(archivePageHtml);
2022-02-24 12:06:19 -07:00
// Make sure the user display name is visible on the message
2022-02-24 12:06:19 -07:00
assert.match(
dom.document.querySelector(`[data-event-id="${imageEventId}"]`).outerHTML,
new RegExp(`.*${escapeStringRegexp(displayName)}.*`)
2022-02-24 12:06:19 -07:00
);
// Make sure the user avatar is visible on the message
const avatarImageElement = dom.document.querySelector(
`[data-event-id="${imageEventId}"] [data-testid="avatar"] img`
);
assert(avatarImageElement);
assert.match(avatarImageElement.getAttribute('src'), new RegExp(`^http://.*`));
// Make sure the image message is visible
const imageElement = dom.document.querySelector(
`[data-event-id="${imageEventId}"] [data-testid="media"] img`
);
assert(imageElement);
assert.match(imageElement.getAttribute('src'), new RegExp(`^http://.*`));
assert.strictEqual(imageElement.getAttribute('alt'), imageFileName);
// Make sure the normal message is visible
assert.match(
dom.document.querySelector(`[data-event-id="${normalMessageEventId1}"]`).outerHTML,
new RegExp(`.*${escapeStringRegexp(normalMessageText1)}.*`)
);
// Make sure the other normal message is visible
assert.match(
dom.document.querySelector(`[data-event-id="${normalMessageEventId2}"]`).outerHTML,
new RegExp(`.*${escapeStringRegexp(normalMessageText2)}.*`)
);
const replyMessageElement = dom.document.querySelector(
`[data-event-id="${replyMessageEventId}"]`
);
// Make sure the reply text is there
assert.match(
replyMessageElement.outerHTML,
new RegExp(`.*${escapeStringRegexp(replyMessageText)}.*`)
);
// Make sure it also includes the message we're replying to
assert.match(
replyMessageElement.outerHTML,
new RegExp(`.*${escapeStringRegexp(normalMessageEventId2)}.*`)
);
const replyMissingRelationMessageElement = dom.document.querySelector(
`[data-event-id="${replyMissingRelationMessageEventId}"]`
);
// Make sure the reply text is there.
// We don't care about the message we're replying to because it's missing on purpose.
assert.match(
replyMissingRelationMessageElement.outerHTML,
new RegExp(`.*${escapeStringRegexp(replyMissingRelationMessageText)}.*`)
);
// Make sure the reaction also exists
assert.match(
replyMessageElement.outerHTML,
new RegExp(`.*${escapeStringRegexp(reactionText)}.*`)
);
});
it(`can render day back in time from room on remote homeserver we haven't backfilled from`, async () => {
const hs2Client = await getTestClientForHs(testMatrixServerUrl2);
// Create a room on hs2
const hs2RoomId = await createTestRoom(hs2Client);
const room2EventIds = await createMessagesInRoom({
client: hs2Client,
roomId: hs2RoomId,
numMessages: 3,
prefix: HOMESERVER_URL_TO_PRETTY_NAME_MAP[hs2Client.homeserverUrl],
timestamp: archiveDate.getTime(),
});
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(hs2RoomId, archiveDate, {
// Since hs1 doesn't know about this room on hs2 yet, we have to provide
// a via server to ask through.
viaServers: [HOMESERVER_URL_TO_PRETTY_NAME_MAP[testMatrixServerUrl2]],
});
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
const dom = parseHTML(archivePageHtml);
// Make sure the messages are visible
assert.deepStrictEqual(
room2EventIds.map((eventId) => {
return dom.document
.querySelector(`[data-event-id="${eventId}"]`)
?.getAttribute('data-event-id');
}),
room2EventIds
);
});
it('redirects to last day with message history', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
// Send an event in the room so we have some day of history to redirect to
const eventId = await sendMessageOnArchiveDate({
client,
roomId,
content: {
msgtype: 'm.text',
body: 'some message in the history',
Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events (#43) Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events. Like if someone is replying to an event that was from long ago out of our range. In the case of missing relations, Hydrogen does `_loadContextEntryNotInTimeline` because it can't find the event locally which throws an `uncaughtException`. Before https://github.com/matrix-org/matrix-public-archive/pull/51, the `uncaughtException` killed the Hydrogen `child_process` before it could pass back the HTML. Now this PR mainly just adds a test to make sure it works. ``` TypeError: Cannot read properties of undefined (reading 'storeNames') at TimelineReader.readById (hydrogen-web\target\lib-build\hydrogen.cjs.js:12483:33) at Timeline._getEventFromStorage (hydrogen-web\target\lib-build\hydrogen.cjs.js:12762:46) at Timeline._loadContextEntryNotInTimeline (hydrogen-web\target\lib-build\hydrogen.cjs.js:12747:35) at Timeline._loadContextEntriesWhereNeeded (hydrogen-web\target\lib-build\hydrogen.cjs.js:12741:14) at Timeline.addEntries (hydrogen-web\target\lib-build\hydrogen.cjs.js:12699:10) at mountHydrogen (4-hydrogen-vm-render-script.js:204:12) at 4-hydrogen-vm-render-script.js:353:1 at Script.runInContext (node:vm:139:12) at _renderHydrogenToStringUnsafe (matrix-public-archive\server\hydrogen-render\3-render-hydrogen-to-string-unsafe.js:102:41) at async process.<anonymous> (matrix-public-archive\server\hydrogen-render\2-render-hydrogen-to-string-fork-script.js:18:27) ```
2022-08-29 18:42:18 -06:00
},
});
const expectedEventIdsOnDay = [eventId];
// Visit `/:roomIdOrAlias` and expect to be redirected to the last day with events
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForRoom(roomId);
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
const dom = parseHTML(archivePageHtml);
// Make sure the messages from the day we expect to get redirected to are visible
assert.deepStrictEqual(
expectedEventIdsOnDay.map((eventId) => {
return dom.document
.querySelector(`[data-event-id="${eventId}"]`)
?.getAttribute('data-event-id');
}),
expectedEventIdsOnDay
);
Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events (#43) Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events. Like if someone is replying to an event that was from long ago out of our range. In the case of missing relations, Hydrogen does `_loadContextEntryNotInTimeline` because it can't find the event locally which throws an `uncaughtException`. Before https://github.com/matrix-org/matrix-public-archive/pull/51, the `uncaughtException` killed the Hydrogen `child_process` before it could pass back the HTML. Now this PR mainly just adds a test to make sure it works. ``` TypeError: Cannot read properties of undefined (reading 'storeNames') at TimelineReader.readById (hydrogen-web\target\lib-build\hydrogen.cjs.js:12483:33) at Timeline._getEventFromStorage (hydrogen-web\target\lib-build\hydrogen.cjs.js:12762:46) at Timeline._loadContextEntryNotInTimeline (hydrogen-web\target\lib-build\hydrogen.cjs.js:12747:35) at Timeline._loadContextEntriesWhereNeeded (hydrogen-web\target\lib-build\hydrogen.cjs.js:12741:14) at Timeline.addEntries (hydrogen-web\target\lib-build\hydrogen.cjs.js:12699:10) at mountHydrogen (4-hydrogen-vm-render-script.js:204:12) at 4-hydrogen-vm-render-script.js:353:1 at Script.runInContext (node:vm:139:12) at _renderHydrogenToStringUnsafe (matrix-public-archive\server\hydrogen-render\3-render-hydrogen-to-string-unsafe.js:102:41) at async process.<anonymous> (matrix-public-archive\server\hydrogen-render\2-render-hydrogen-to-string-fork-script.js:18:27) ```
2022-08-29 18:42:18 -06:00
});
it('still shows surrounding messages on a day with no messages', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
// Send an event in the room so there is some history to display in the
// surroundings and everything doesn't just 404 because we can't find
// any event.
const eventId = await sendMessageOnArchiveDate({
client,
roomId,
content: {
msgtype: 'm.text',
body: 'some message in the history',
},
});
const expectedEventIdsToBeDisplayed = [eventId];
// Visit the archive on the day ahead of where there are messages
const visitArchiveDate = new Date(Date.UTC(2022, 0, 20));
assert(
visitArchiveDate > archiveDate,
'The date we visit the archive (`visitArchiveDate`) should be after where the messages were sent (`archiveDate`)'
);
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, visitArchiveDate);
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
const dom = parseHTML(archivePageHtml);
2022-02-24 02:27:53 -07:00
// Make sure the summary exists on the page
assert(
dom.document.querySelector(
`[data-testid="not-enough-events-summary-kind-no-events-in-day"]`
)
);
2022-02-24 02:27:53 -07:00
// Make sure the messages there are some messages from the surrounding days
assert.deepStrictEqual(
expectedEventIdsToBeDisplayed.map((eventId) => {
return dom.document
.querySelector(`[data-event-id="${eventId}"]`)
?.getAttribute('data-event-id');
}),
expectedEventIdsToBeDisplayed
);
});
it('shows no events summary when no messages at or before the given day', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
// We purposely send no events in the room
2022-02-24 02:27:53 -07:00
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, archiveDate);
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
2022-02-24 02:27:53 -07:00
const dom = parseHTML(archivePageHtml);
Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events (#43) Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events. Like if someone is replying to an event that was from long ago out of our range. In the case of missing relations, Hydrogen does `_loadContextEntryNotInTimeline` because it can't find the event locally which throws an `uncaughtException`. Before https://github.com/matrix-org/matrix-public-archive/pull/51, the `uncaughtException` killed the Hydrogen `child_process` before it could pass back the HTML. Now this PR mainly just adds a test to make sure it works. ``` TypeError: Cannot read properties of undefined (reading 'storeNames') at TimelineReader.readById (hydrogen-web\target\lib-build\hydrogen.cjs.js:12483:33) at Timeline._getEventFromStorage (hydrogen-web\target\lib-build\hydrogen.cjs.js:12762:46) at Timeline._loadContextEntryNotInTimeline (hydrogen-web\target\lib-build\hydrogen.cjs.js:12747:35) at Timeline._loadContextEntriesWhereNeeded (hydrogen-web\target\lib-build\hydrogen.cjs.js:12741:14) at Timeline.addEntries (hydrogen-web\target\lib-build\hydrogen.cjs.js:12699:10) at mountHydrogen (4-hydrogen-vm-render-script.js:204:12) at 4-hydrogen-vm-render-script.js:353:1 at Script.runInContext (node:vm:139:12) at _renderHydrogenToStringUnsafe (matrix-public-archive\server\hydrogen-render\3-render-hydrogen-to-string-unsafe.js:102:41) at async process.<anonymous> (matrix-public-archive\server\hydrogen-render\2-render-hydrogen-to-string-fork-script.js:18:27) ```
2022-08-29 18:42:18 -06:00
// Make sure the summary exists on the page
assert(
dom.document.querySelector(
`[data-testid="not-enough-events-summary-kind-no-events-at-all"]`
)
);
});
Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events (#43) Add test to make sure the archive doesn't fail when event for event relation is missing and not included in list of provided events. Like if someone is replying to an event that was from long ago out of our range. In the case of missing relations, Hydrogen does `_loadContextEntryNotInTimeline` because it can't find the event locally which throws an `uncaughtException`. Before https://github.com/matrix-org/matrix-public-archive/pull/51, the `uncaughtException` killed the Hydrogen `child_process` before it could pass back the HTML. Now this PR mainly just adds a test to make sure it works. ``` TypeError: Cannot read properties of undefined (reading 'storeNames') at TimelineReader.readById (hydrogen-web\target\lib-build\hydrogen.cjs.js:12483:33) at Timeline._getEventFromStorage (hydrogen-web\target\lib-build\hydrogen.cjs.js:12762:46) at Timeline._loadContextEntryNotInTimeline (hydrogen-web\target\lib-build\hydrogen.cjs.js:12747:35) at Timeline._loadContextEntriesWhereNeeded (hydrogen-web\target\lib-build\hydrogen.cjs.js:12741:14) at Timeline.addEntries (hydrogen-web\target\lib-build\hydrogen.cjs.js:12699:10) at mountHydrogen (4-hydrogen-vm-render-script.js:204:12) at 4-hydrogen-vm-render-script.js:353:1 at Script.runInContext (node:vm:139:12) at _renderHydrogenToStringUnsafe (matrix-public-archive\server\hydrogen-render\3-render-hydrogen-to-string-unsafe.js:102:41) at async process.<anonymous> (matrix-public-archive\server\hydrogen-render\2-render-hydrogen-to-string-fork-script.js:18:27) ```
2022-08-29 18:42:18 -06:00
it(`will redirect to hour pagination when there are too many messages on the same day`, async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
// Set this low so we can easily create more than the limit
config.set('archiveMessageLimit', 3);
2022-02-22 19:55:42 -07:00
// Create more messages than the limit
await createMessagesInRoom({
client,
roomId: roomId,
// This is larger than the `archiveMessageLimit` we set
numMessages: 5,
prefix: 'events in room',
timestamp: archiveDate.getTime(),
});
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, archiveDate);
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
assert.match(archivePageHtml, /TODO: Redirect user to smaller hour range/);
});
it(`will not redirect to hour pagination when there are too many messages from surrounding days`, async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
// Set this low so we can easily create more than the limit
config.set('archiveMessageLimit', 3);
// Create more messages than the limit on a previous day
const previousArchiveDate = new Date(Date.UTC(2022, 0, 2));
assert(
previousArchiveDate < archiveDate,
`The previousArchiveDate=${previousArchiveDate} should be before the archiveDate=${archiveDate}`
);
const surroundEventIds = await createMessagesInRoom({
client,
roomId: roomId,
numMessages: 2,
prefix: 'events in room',
timestamp: previousArchiveDate.getTime(),
});
// Create more messages than the limit
const eventIdsOnDay = await createMessagesInRoom({
client,
roomId: roomId,
numMessages: 2,
prefix: 'events in room',
timestamp: archiveDate.getTime(),
});
2022-02-22 19:55:42 -07:00
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(roomId, archiveDate);
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
2022-02-24 02:27:53 -07:00
const dom = parseHTML(archivePageHtml);
2022-02-24 02:27:53 -07:00
// Make sure the messages are displayed
const expectedEventIdsToBeDisplayed = [].concat(surroundEventIds).concat(eventIdsOnDay);
assert.deepStrictEqual(
expectedEventIdsToBeDisplayed.map((eventId) => {
return dom.document
.querySelector(`[data-event-id="${eventId}"]`)
?.getAttribute('data-event-id');
}),
expectedEventIdsToBeDisplayed
);
});
it('404 when trying to view a future day', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
try {
const TWO_DAY_MS = 2 * 24 * 60 * 60 * 1000;
await fetchEndpointAsText(
matrixPublicArchiveURLCreator.archiveUrlForDate(
roomId,
new Date(Date.now() + TWO_DAY_MS)
)
);
assert.fail(
new TestError(
`We expect the request to fail with a 404 since you can't view the future in the archive but it succeeded`
)
);
} catch (err) {
if (err instanceof TestError) {
throw err;
}
assert.strictEqual(
err?.response?.status,
404,
`Expected err.response.status=${err?.response?.status} to be 404 but error was: ${err.stack}`
);
}
});
describe('Jump forwards and backwards', () => {
let client;
let roomId;
let previousDayToEventMap;
beforeEach(async () => {
// Set this low so we can easily create more than the limit
config.set('archiveMessageLimit', 4);
client = await getTestClientForHs(testMatrixServerUrl1);
roomId = await createTestRoom(client);
// Create enough surround messages on previous days that overflow the page limit
// but don't overflow the limit on a single day basis.
//
// We create 4 days of messages so we can see a seamless continuation from
// page1 to page2. The page limit is 4 but each page will show up-to 5
// messages because we fetch one extra to determine overflow.
//
// 1 <-- 2 <-- 3 <-- 4 <-- 5 <-- 6 <-- 7 <-- 8 <-- 9 <-- 10 <-- 11 <-- 12
// [day 1 ] [day 2 ] [day 3 ] [day 4 ]
//
previousDayToEventMap = new Map();
for (let i = 1; i < 5; i++) {
// The date should be just past midnight so we don't run into inclusive
// bounds showing messages from more days than we expect in the tests.
const previousArchiveDate = new Date(Date.UTC(2022, 0, i, 1, 0, 0, 1));
assert(
previousArchiveDate < archiveDate,
`The previousArchiveDate=${previousArchiveDate} should be before the archiveDate=${archiveDate}`
);
const eventIds = await createMessagesInRoom({
client,
roomId,
numMessages: 3,
prefix: `day ${i} - events in room`,
timestamp: previousArchiveDate.getTime(),
});
previousDayToEventMap.set(previousArchiveDate, eventIds);
}
});
it('can jump forward to the next activity', async () => {
// Test to make sure we can jump from the 1st page to the 2nd page forwards.
//
// `previousDayToEventMap` maps each day to the events in that day (3 events
// per day). The page limit is 4 but each page will show 5 messages because we
// fetch one extra to determine overflow.
//
// In order to jump from the 1st page to the 2nd, we first jump forward 4
// messages, then back-track to the first date boundary which is day 3. We do
// this so that we don't start from day 4 backwards which would miss messages
// because there are more than 5 messages in between day 4 and day 2.
//
// Even though there is overlap between the pages, our scroll continues from
// the event where the 1st page starts.
//
// 1 <-- 2 <-- 3 <-- 4 <-- 5 <-- 6 <-- 7 <-- 8 <-- 9 <-- 10 <-- 11 <-- 12
// [day 1 ] [day 2 ] [day 3 ] [day 4 ]
// [1st page ]
// |--jump-fwd-4-messages-->|
// [2nd page ]
const previousArchiveDates = Array.from(previousDayToEventMap.keys());
assert.strictEqual(
previousArchiveDates.length,
4,
`This test expects to work with 4 days of history, each with 3 messages and a page limit of 4 messages previousArchiveDates=${previousArchiveDates}`
);
// Fetch messages for the 1st page (day 2 backwards)
const day2Date = previousArchiveDates[1];
const firstPageArchiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(
roomId,
day2Date
);
// Set this for debugging if the test fails here
archiveUrl = firstPageArchiveUrl;
const { data: firstPageArchivePageHtml } = await fetchEndpointAsText(firstPageArchiveUrl);
const firstPageDom = parseHTML(firstPageArchivePageHtml);
const eventIdsOnFirstPage = [...firstPageDom.document.querySelectorAll(`[data-event-id]`)]
.map((eventEl) => {
return eventEl.getAttribute('data-event-id');
})
.filter((eventId) => {
// Only return valid events. Filter out our `fake-event-id-xxx--x` events
return eventId.startsWith('$');
});
// Assert that the first page contains 5 events (day 2 and day 1)
assert.deepEqual(eventIdsOnFirstPage, [
// Some of day 1
...previousDayToEventMap.get(previousArchiveDates[0]).slice(-2),
// All of day 2
...previousDayToEventMap.get(previousArchiveDates[1]),
]);
// Follow the next activity link. Aka, fetch messages for the 2nd page
const nextActivityLinkEl = firstPageDom.document.querySelector(
'[data-testid="jump-to-next-activity-link"]'
);
const nextActivityLink = nextActivityLinkEl.getAttribute('href');
// Set this for debugging if the test fails here
archiveUrl = nextActivityLink;
const { data: nextActivityArchivePageHtml, res: nextActivityRes } =
await fetchEndpointAsText(nextActivityLink);
const nextActivityDom = parseHTML(nextActivityArchivePageHtml);
// Assert that it's a smooth continuation to more messages
//
// First by checking where the scroll is going to start from
const urlObj = new URL(nextActivityRes.url, basePath);
const qs = new URLSearchParams(urlObj.search);
assert.strictEqual(
qs.get('at'),
// Continuing from the first event of day 3
previousDayToEventMap.get(previousArchiveDates[2])[0]
);
// Then check the events are on the page correctly
const eventIdsOnNextDay = [
...nextActivityDom.document.querySelectorAll(`[data-event-id]`),
]
.map((eventEl) => {
return eventEl.getAttribute('data-event-id');
})
.filter((eventId) => {
// Only return valid events. Filter out our `fake-event-id-xxx--x` events
return eventId.startsWith('$');
});
// Assert that the 2nd page contains 5 events (day 3 and day 2)
assert.deepEqual(eventIdsOnNextDay, [
// Some of day 2
...previousDayToEventMap.get(previousArchiveDates[1]).slice(-2),
// All of day 3
...previousDayToEventMap.get(previousArchiveDates[2]),
]);
});
it('can jump backward to the previous activity', async () => {
// Test to make sure we can jump from the 1st page to the 2nd page backwards
//
// `previousDayToEventMap` maps each day to the events in that day (3 events
// per day). The page limit is 4 but each page will show 5 messages because we
// fetch one extra to determine overflow.
//
// The 2nd page continues from the *day* where the 1st page starts. Even
// though there is overlap between the pages, our scroll continues from the
// event where the 1st page starts.
//
// 1 <-- 2 <-- 3 <-- 4 <-- 5 <-- 6 <-- 7 <-- 8 <-- 9 <-- 10 <-- 11 <-- 12
// [day 1 ] [day 2 ] [day 3 ] [day 4 ]
// [1st page ]
// [2nd page ]
const previousArchiveDates = Array.from(previousDayToEventMap.keys());
assert.strictEqual(
previousArchiveDates.length,
4,
`This test expects to work with 4 days of history, each with 3 messages and a page limit of 4 messages previousArchiveDates=${previousArchiveDates}`
);
// Fetch messages for the 1st page (day 3 backwards)
const day3Date = previousArchiveDates[2];
const firstPageArchiveUrl = matrixPublicArchiveURLCreator.archiveUrlForDate(
roomId,
day3Date
);
// Set this for debugging if the test fails here
archiveUrl = firstPageArchiveUrl;
const { data: firstPageArchivePageHtml } = await fetchEndpointAsText(firstPageArchiveUrl);
const firstPageDom = parseHTML(firstPageArchivePageHtml);
const eventIdsOnFirstPage = [...firstPageDom.document.querySelectorAll(`[data-event-id]`)]
.map((eventEl) => {
return eventEl.getAttribute('data-event-id');
})
.filter((eventId) => {
// Only return valid events. Filter out our `fake-event-id-xxx--x` events
return eventId.startsWith('$');
});
// Assert that the first page contains 4 events (day 3 and day 2)
assert.deepEqual(eventIdsOnFirstPage, [
// Some of day 2
...previousDayToEventMap.get(previousArchiveDates[1]).slice(-2),
// All of day 3
...previousDayToEventMap.get(previousArchiveDates[2]),
]);
// Follow the previous activity link
const previousActivityLinkEl = firstPageDom.document.querySelector(
'[data-testid="jump-to-previous-activity-link"]'
);
const previousActivityLink = previousActivityLinkEl.getAttribute('href');
// Set this for debugging if the test fails here
archiveUrl = previousActivityLink;
const { data: previousActivityArchivePageHtml, res: previousActivityRes } =
await fetchEndpointAsText(previousActivityLink);
const previousActivityDom = parseHTML(previousActivityArchivePageHtml);
// Assert that it's a smooth continuation to more messages
//
// First by checking where the scroll is going to start from
const urlObj = new URL(previousActivityRes.url, basePath);
const qs = new URLSearchParams(urlObj.search);
assert.strictEqual(
qs.get('at'),
// Continuing from the first event of day 2
previousDayToEventMap.get(previousArchiveDates[1])[0]
);
// Then check the events are on the page correctly
const eventIdsOnPreviousDay = [
...previousActivityDom.document.querySelectorAll(`[data-event-id]`),
]
.map((eventEl) => {
return eventEl.getAttribute('data-event-id');
})
.filter((eventId) => {
// Only return valid events. Filter out our `fake-event-id-xxx--x` events
return eventId.startsWith('$');
});
// Assert that the 2nd page contains 4 events (day 2 and day 1)
assert.deepEqual(eventIdsOnPreviousDay, [
// All of day 1
...previousDayToEventMap.get(previousArchiveDates[0]).slice(-2),
// All of day 2
...previousDayToEventMap.get(previousArchiveDates[1]),
]);
});
});
});
describe('Room directory', () => {
it('room search narrows down results', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
// This is just an extra room to fill out the room directory and make sure
// that it does not appear when searching.
await createTestRoom(client);
// Then create two rooms we will find with search
//
// We use a `timeToken` so that we can namespace these rooms away from other
// test runs against the same homeserver
const timeToken = Date.now();
const roomPlanetPrefix = `planet-${timeToken}`;
const roomSaturnId = await createTestRoom(client, {
name: `${roomPlanetPrefix}-saturn`,
});
const roomMarsId = await createTestRoom(client, {
name: `${roomPlanetPrefix}-mars`,
});
// Browse the room directory without search to see many rooms
archiveUrl = matrixPublicArchiveURLCreator.roomDirectoryUrl();
const { data: roomDirectoryPageHtml } = await fetchEndpointAsText(archiveUrl);
const dom = parseHTML(roomDirectoryPageHtml);
const roomsOnPageWithoutSearch = [
...dom.document.querySelectorAll(`[data-testid="room-card"]`),
].map((roomCardEl) => {
return roomCardEl.getAttribute('data-room-id');
});
// Then browse the room directory again, this time with the search
// narrowing down results.
archiveUrl = matrixPublicArchiveURLCreator.roomDirectoryUrl({
searchTerm: roomPlanetPrefix,
});
const { data: roomDirectoryWithSearchPageHtml } = await fetchEndpointAsText(archiveUrl);
const domWithSearch = parseHTML(roomDirectoryWithSearchPageHtml);
const roomsOnPageWithSearch = [
...domWithSearch.document.querySelectorAll(`[data-testid="room-card"]`),
].map((roomCardEl) => {
return roomCardEl.getAttribute('data-room-id');
});
// Assert that the rooms we searched for are visible
assert.deepStrictEqual(roomsOnPageWithSearch.sort(), [roomSaturnId, roomMarsId].sort());
// Sanity check that search does something. Assert that it's not showing
// the same results as if we didn't make any search.
assert.notDeepStrictEqual(roomsOnPageWithSearch.sort(), roomsOnPageWithoutSearch.sort());
});
it('can show rooms from another remote server', async () => {
const hs2Client = await getTestClientForHs(testMatrixServerUrl2);
// Create some rooms on hs2
//
// We use a `timeToken` so that we can namespace these rooms away from other
// test runs against the same homeserver
const timeToken = Date.now();
const roomPlanetPrefix = `remote-planet-${timeToken}`;
const roomXId = await createTestRoom(hs2Client, {
name: `${roomPlanetPrefix}-x`,
});
const roomYId = await createTestRoom(hs2Client, {
name: `${roomPlanetPrefix}-y`,
});
archiveUrl = matrixPublicArchiveURLCreator.roomDirectoryUrl({
homeserver: HOMESERVER_URL_TO_PRETTY_NAME_MAP[testMatrixServerUrl2],
searchTerm: roomPlanetPrefix,
});
const { data: roomDirectoryWithSearchPageHtml } = await fetchEndpointAsText(archiveUrl);
const domWithSearch = parseHTML(roomDirectoryWithSearchPageHtml);
// Make sure the `?homserver` is selected in the homeserver selector `<select>`
const selectedHomeserverOptionElement = domWithSearch.document.querySelector(
`[data-testid="homeserver-select"] option[selected]`
);
assert.strictEqual(
selectedHomeserverOptionElement.getAttribute('value'),
HOMESERVER_URL_TO_PRETTY_NAME_MAP[testMatrixServerUrl2]
);
const roomsOnPageWithSearch = [
...domWithSearch.document.querySelectorAll(`[data-testid="room-card"]`),
].map((roomCardEl) => {
return roomCardEl.getAttribute('data-room-id');
});
// Assert that the rooms we searched for on remote hs2 are visible
assert.deepStrictEqual(roomsOnPageWithSearch.sort(), [roomXId, roomYId].sort());
});
});
describe('access controls', () => {
it('not allowed to view private room even when the archiver user is in the room', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client, {
preset: 'private_chat',
initial_state: [],
});
try {
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForRoom(roomId);
await fetchEndpointAsText(archiveUrl);
assert.fail(
new TestError(
'We expect the request to fail with a 403 since the archive should not be able to view a private room but it succeeded'
)
);
} catch (err) {
if (err instanceof TestError) {
throw err;
}
assert.strictEqual(
err.response.status,
403,
`Expected err.response.status=${err?.response?.status} to be 403 but error was: ${err.stack}`
);
}
});
it('search engines allowed to index `world_readable` room', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForRoom(roomId);
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
const dom = parseHTML(archivePageHtml);
// Make sure the `<meta name="robots" ...>` tag does NOT exist on the
// page telling search engines not to index it
assert.strictEqual(dom.document.querySelector(`meta[name="robots"]`), null);
});
it('search engines not allowed to index `public` room', async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client, {
// The default options for the test rooms adds a
// `m.room.history_visiblity` state event so we override that here so
// it's only a public room.
initial_state: [],
});
archiveUrl = matrixPublicArchiveURLCreator.archiveUrlForRoom(roomId);
const { data: archivePageHtml } = await fetchEndpointAsText(archiveUrl);
const dom = parseHTML(archivePageHtml);
// Make sure the `<meta name="robots" ...>` tag exists on the page
// telling search engines not to index it
assert.strictEqual(
dom.document.querySelector(`meta[name="robots"]`)?.getAttribute('content'),
'noindex, nofollow'
);
});
});
describe('redirects', () => {
const controller = new AbortController();
const { signal } = controller;
// We have to use this sometimes over `fetch` because `fetch` does not allow us to
// manually follow redirects and get the resultant URL, see
// https://github.com/whatwg/fetch/issues/763
function httpRequest(url) {
return new Promise((resolve, reject) => {
const req = http.request(url, { signal });
req.on('response', (res) => {
resolve(res);
});
req.on('error', (err) => {
reject(err);
});
// This must be called for the request to actually go out
req.end();
});
}
after(() => {
// Abort any in-flight request
controller.abort();
});
describe('general', () => {
let testRedirects = [];
before(async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
const roomIdWithoutSigil = roomId.replace(/^(#|!)/, '');
const canonicalAlias = await getCanonicalAlias({ client, roomId });
const canonicalAliasWithoutSigil = canonicalAlias.replace(/^(#|!)/, '');
const nowDate = new Date();
// Gives the date in YYYY/mm/dd format.
// date.toISOString() -> 2022-02-16T23:20:04.709Z
const urlDate = nowDate.toISOString().split('T')[0].replaceAll('-', '/');
// Warn if it's close to the end of the UTC day. This test could be a flakey
// and cause a failure if the room was created just before midnight (UTC) and
// this timestamp comes after midnight. The redirect would want to go to the
// day before when the latest event was created instead of the `nowDate` we
// expected in the URL's.
//
// We could lookup the date of the latest event to use the `origin_server_ts`
// from ourselves which may be less faff than this big warning but 🤷 - that's
// kinda like making sure `/timestamp_to_event` works by using
// `/timestamp_to_event`.
const utcMidnightOfNowDay = Date.UTC(
nowDate.getUTCFullYear(),
nowDate.getUTCMonth(),
nowDate.getUTCDate() + 1
);
if (utcMidnightOfNowDay - nowDate.getTime() < 30 * 1000 /* 30 seconds */) {
// eslint-disable-next-line no-console
console.warn(
`Test is being run at the end of the UTC day. This could result in a flakey ` +
`failure where the room was created before midnight (UTC) but the \`nowDate\` ` +
`was after midnight meaning our expected URL's would be a day ahead. Since ` +
`this is an e2e test we can't control the date/time exactly.`
);
}
testRedirects.push(
...[
{
description:
'Visiting via a room ID will keep the URL as a room ID (avoid the canonical alias taking precedence)',
from: urlJoin(basePath, `/roomid/${roomIdWithoutSigil}`),
to: urlJoin(basePath, `/roomid/${roomIdWithoutSigil}/date/${urlDate}`),
},
{
description: 'Visiting via a room alias will keep the URL as a room alias',
from: urlJoin(basePath, `/r/${canonicalAliasWithoutSigil}`),
to: urlJoin(basePath, `/r/${canonicalAliasWithoutSigil}/date/${urlDate}`),
},
{
description:
'Removes the `?via` query parameter after joining from hitting the first endpoint (via parameter only necessary for room IDs)',
from: urlJoin(
basePath,
`/roomid/${roomIdWithoutSigil}?via=${HOMESERVER_URL_TO_PRETTY_NAME_MAP[testMatrixServerUrl1]}`
),
to: urlJoin(basePath, `/roomid/${roomIdWithoutSigil}/date/${urlDate}`),
},
]
);
});
it('redirects people to the correct place (see internal test matrix)', async () => {
assert(testRedirects.length > 0, 'Expected more than 0 redirects to be tested');
for (const testRedirect of testRedirects) {
const res = await httpRequest(testRedirect.from);
// This should be a temporary redirect because the latest date will change
// as new events are sent for example.
assert.strictEqual(res.statusCode, 302, 'We expected a 302 temporary redirect');
// Make sure it redirected to the right location
assert.strictEqual(res.headers.location, testRedirect.to, testRedirect.description);
}
});
});
describe('fix honest mistakes and redirect to the correct place', () => {
let testRedirects = [];
before(async () => {
const client = await getTestClientForHs(testMatrixServerUrl1);
const roomId = await createTestRoom(client);
const roomIdWithoutSigil = roomId.replace(/^(#|!)/, '');
const canonicalAlias = await getCanonicalAlias({ client, roomId });
const canonicalAliasWithoutSigil = canonicalAlias.replace(/^(#|!)/, '');
testRedirects.push(
...[
{
description: 'Using a room ID with a prefix sigil (pasting a room ID)',
from: urlJoin(basePath, `/roomid/${roomId}`),
to: urlJoin(basePath, `/roomid/${roomIdWithoutSigil}`),
},
{
description: 'Using a room ID with a prefix sigil with extra path after',
from: urlJoin(basePath, `/roomid/${roomId}/date/2022/09/20?via=my.synapse.server`),
to: urlJoin(
basePath,
`/roomid/${roomIdWithoutSigil}/date/2022/09/20?via=my.synapse.server`
),
},
{
description: 'Pasting a room ID with a prefix sigil after the domain root',
from: urlJoin(basePath, `/${roomId}`),
to: urlJoin(basePath, `/roomid/${roomIdWithoutSigil}`),
},
{
description: 'URI encoded room ID',
from: urlJoin(basePath, `/${encodeURIComponent(roomId)}`),
to: urlJoin(basePath, `/roomid/${roomIdWithoutSigil}`),
},
{
description: 'URI encoded canonical alias',
from: urlJoin(basePath, `/${encodeURIComponent(canonicalAlias)}`),
to: urlJoin(basePath, `/r/${canonicalAliasWithoutSigil}`),
},
{
description:
'URI encoded canonical alias but on a visiting `/roomid/xxx` will redirect to alias `/r/xxx`',
from: urlJoin(basePath, `/roomid/${encodeURIComponent(canonicalAlias)}`),
to: urlJoin(basePath, `/r/${canonicalAliasWithoutSigil}`),
},
]
);
});
it('redirects people to the correct place (see internal test matrix)', async () => {
assert(testRedirects.length > 0, 'Expected more than 0 redirects to be tested');
for (const testRedirect of testRedirects) {
const res = await httpRequest(testRedirect.from);
assert.strictEqual(
res.statusCode,
301,
'We expected a 301 permanent redirect for mistakes'
);
// Make sure it redirected to the right location
assert.strictEqual(res.headers.location, testRedirect.to, testRedirect.description);
}
});
});
});
});
2022-02-22 15:06:29 -07:00
});