Add test for joining a new federated room (#31)
Add test for joining a new federated room and making sure the messages are available (homeserver should backfill). Synapse changes: https://github.com/matrix-org/synapse/pull/13205, https://github.com/matrix-org/synapse/pull/13320
This commit is contained in:
parent
b5b79b94f2
commit
e9d13db911
|
@ -0,0 +1,32 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const urlJoin = require('url-join');
|
||||||
|
|
||||||
|
const { fetchEndpointAsJson } = require('./lib/fetch-endpoint');
|
||||||
|
|
||||||
|
const config = require('./lib/config');
|
||||||
|
const matrixServerUrl = config.get('matrixServerUrl');
|
||||||
|
assert(matrixServerUrl);
|
||||||
|
|
||||||
|
async function ensureRoomJoined(accessToken, roomId, viaServers = []) {
|
||||||
|
let qs = new URLSearchParams();
|
||||||
|
[].concat(viaServers).forEach((viaServer) => {
|
||||||
|
qs.append('server_name', viaServer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Only join world_readable rooms. Perhaps we want to serve public rooms
|
||||||
|
// where we have been invited. GET
|
||||||
|
// /_matrix/client/v3/directory/list/room/{roomId} (Gets the visibility of a
|
||||||
|
// given room on the server’s public room directory.)
|
||||||
|
const joinEndpoint = urlJoin(
|
||||||
|
matrixServerUrl,
|
||||||
|
`_matrix/client/r0/join/${roomId}?${qs.toString()}`
|
||||||
|
);
|
||||||
|
await fetchEndpointAsJson(joinEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
accessToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ensureRoomJoined;
|
|
@ -27,16 +27,6 @@ async function fetchEventsFromTimestampBackwards(accessToken, roomId, ts, limit)
|
||||||
assert(ts);
|
assert(ts);
|
||||||
assert(limit);
|
assert(limit);
|
||||||
|
|
||||||
// TODO: Only join world_readable rooms. Perhaps we want to serve public rooms
|
|
||||||
// where we have been invited. GET
|
|
||||||
// /_matrix/client/v3/directory/list/room/{roomId} (Gets the visibility of a
|
|
||||||
// given room on the server’s public room directory.)
|
|
||||||
const joinEndpoint = urlJoin(matrixServerUrl, `_matrix/client/r0/join/${roomId}`);
|
|
||||||
await fetchEndpointAsJson(joinEndpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
accessToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
const timestampToEventEndpoint = urlJoin(
|
const timestampToEventEndpoint = urlJoin(
|
||||||
matrixServerUrl,
|
matrixServerUrl,
|
||||||
`_matrix/client/unstable/org.matrix.msc3030/rooms/${roomId}/timestamp_to_event?ts=${ts}&dir=b`
|
`_matrix/client/unstable/org.matrix.msc3030/rooms/${roomId}/timestamp_to_event?ts=${ts}&dir=b`
|
||||||
|
|
|
@ -12,6 +12,7 @@ const timeoutMiddleware = require('./timeout-middleware');
|
||||||
|
|
||||||
const fetchRoomData = require('../fetch-room-data');
|
const fetchRoomData = require('../fetch-room-data');
|
||||||
const fetchEventsInRange = require('../fetch-events-in-range');
|
const fetchEventsInRange = require('../fetch-events-in-range');
|
||||||
|
const ensureRoomJoined = require('../ensure-room-joined');
|
||||||
const renderHydrogenToString = require('../hydrogen-render/1-render-hydrogen-to-string');
|
const renderHydrogenToString = require('../hydrogen-render/1-render-hydrogen-to-string');
|
||||||
const sanitizeHtml = require('../lib/sanitize-html');
|
const sanitizeHtml = require('../lib/sanitize-html');
|
||||||
const safeJson = require('../lib/safe-json');
|
const safeJson = require('../lib/safe-json');
|
||||||
|
@ -136,8 +137,15 @@ function installRoutes(app) {
|
||||||
// If the hourRange is defined, we force the range to always be 1 hour. If
|
// If the hourRange is defined, we force the range to always be 1 hour. If
|
||||||
// the format isn't correct, redirect to the correct hour range
|
// the format isn't correct, redirect to the correct hour range
|
||||||
if (hourRange && toHour !== fromHour + 1) {
|
if (hourRange && toHour !== fromHour + 1) {
|
||||||
|
// Pass through the query parameters
|
||||||
|
let queryParamterUrlPiece = '';
|
||||||
|
if (req.query) {
|
||||||
|
queryParamterUrlPiece = `?${new URLSearchParams(req.query).toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
res.redirect(
|
res.redirect(
|
||||||
urlJoin(
|
// FIXME: Can we use the matrixPublicArchiveURLCreator here?
|
||||||
|
`${urlJoin(
|
||||||
basePath,
|
basePath,
|
||||||
roomIdOrAlias,
|
roomIdOrAlias,
|
||||||
'date',
|
'date',
|
||||||
|
@ -145,7 +153,7 @@ function installRoutes(app) {
|
||||||
req.params.mm,
|
req.params.mm,
|
||||||
req.params.dd,
|
req.params.dd,
|
||||||
`${fromHour}-${fromHour + 1}`
|
`${fromHour}-${fromHour + 1}`
|
||||||
)
|
)}${queryParamterUrlPiece}`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -153,6 +161,10 @@ function installRoutes(app) {
|
||||||
// TODO: Highlight tile that matches ?at=$xxx
|
// TODO: Highlight tile that matches ?at=$xxx
|
||||||
//const aroundId = req.query.at;
|
//const aroundId = req.query.at;
|
||||||
|
|
||||||
|
// We have to wait for the room join to happen first before we can fetch
|
||||||
|
// any of the additional room info or messages.
|
||||||
|
await ensureRoomJoined(matrixAccessToken, roomIdOrAlias, req.query.via);
|
||||||
|
|
||||||
// Do these in parallel to avoid the extra time in sequential round-trips
|
// Do these in parallel to avoid the extra time in sequential round-trips
|
||||||
// (we want to display the archive page faster)
|
// (we want to display the archive page faster)
|
||||||
const [roomData, { events, stateEventMap }] = await Promise.all([
|
const [roomData, { events, stateEventMap }] = await Promise.all([
|
||||||
|
|
|
@ -7,12 +7,17 @@ class URLCreator {
|
||||||
this._basePath = basePath;
|
this._basePath = basePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
archiveUrlForDate(roomId, date) {
|
archiveUrlForDate(roomId, date, { viaServers = [] } = {}) {
|
||||||
|
let qs = new URLSearchParams();
|
||||||
|
[].concat(viaServers).forEach((viaServer) => {
|
||||||
|
qs.append('via', viaServer);
|
||||||
|
});
|
||||||
|
|
||||||
// Gives the date in YYYY/mm/dd format.
|
// Gives the date in YYYY/mm/dd format.
|
||||||
// date.toISOString() -> 2022-02-16T23:20:04.709Z
|
// date.toISOString() -> 2022-02-16T23:20:04.709Z
|
||||||
const urlDate = date.toISOString().split('T')[0].replaceAll('-', '/');
|
const urlDate = date.toISOString().split('T')[0].replaceAll('-', '/');
|
||||||
|
|
||||||
return urlJoin(this._basePath, `${roomId}/date/${urlDate}`);
|
return `${urlJoin(this._basePath, `${roomId}/date/${urlDate}`)}?${qs.toString()}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const { URLSearchParams } = require('url');
|
|
||||||
const urlJoin = require('url-join');
|
const urlJoin = require('url-join');
|
||||||
const { fetchEndpointAsJson, fetchEndpoint } = require('../server/lib/fetch-endpoint');
|
const { fetchEndpointAsJson, fetchEndpoint } = require('../server/lib/fetch-endpoint');
|
||||||
|
|
||||||
const config = require('../server/lib/config');
|
const config = require('../server/lib/config');
|
||||||
const matrixAccessToken = config.get('matrixAccessToken');
|
const matrixAccessToken = config.get('matrixAccessToken');
|
||||||
assert(matrixAccessToken);
|
assert(matrixAccessToken);
|
||||||
|
const testMatrixServerUrl1 = config.get('testMatrixServerUrl1');
|
||||||
|
assert(testMatrixServerUrl1);
|
||||||
|
|
||||||
let txnCount = 0;
|
let txnCount = 0;
|
||||||
function getTxnId() {
|
function getTxnId() {
|
||||||
|
@ -15,6 +16,30 @@ function getTxnId() {
|
||||||
return `${new Date().getTime()}--${txnCount}`;
|
return `${new Date().getTime()}--${txnCount}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureUserRegistered({ matrixServerUrl, username }) {
|
||||||
|
const registerResponse = await fetchEndpointAsJson(
|
||||||
|
urlJoin(matrixServerUrl, '/_matrix/client/v3/register'),
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
type: 'm.login.dummy',
|
||||||
|
username,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const userId = registerResponse['user_id'];
|
||||||
|
assert(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTestClientForAs() {
|
||||||
|
return {
|
||||||
|
homeserverUrl: testMatrixServerUrl1,
|
||||||
|
accessToken: matrixAccessToken,
|
||||||
|
userId: '@archiver:hs1',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Get client to act with for all of the client methods. This will use the
|
// 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`
|
// 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
|
// for the specific user to act upon so we can use the `?ts` message timestamp
|
||||||
|
@ -92,13 +117,15 @@ async function joinRoom({ client, roomId, viaServers }) {
|
||||||
qs.append('user_id', client.applicationServiceUserIdOverride);
|
qs.append('user_id', client.applicationServiceUserIdOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
const joinRoomResponse = await fetchEndpointAsJson(
|
const joinRoomUrl = urlJoin(
|
||||||
urlJoin(client.homeserverUrl, `/_matrix/client/v3/join/${roomId}?${qs.toString()}`),
|
client.homeserverUrl,
|
||||||
{
|
`/_matrix/client/v3/join/${roomId}?${qs.toString()}`
|
||||||
|
);
|
||||||
|
console.log('test client joinRoomUrl', joinRoomUrl);
|
||||||
|
const joinRoomResponse = await fetchEndpointAsJson(joinRoomUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
accessToken: client.accessToken,
|
accessToken: client.accessToken,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const joinedRoomId = joinRoomResponse['room_id'];
|
const joinedRoomId = joinRoomResponse['room_id'];
|
||||||
assert(joinedRoomId);
|
assert(joinedRoomId);
|
||||||
|
@ -164,11 +191,19 @@ async function createMessagesInRoom({ client, roomId, numMessages, prefix, times
|
||||||
msgtype: 'm.text',
|
msgtype: 'm.text',
|
||||||
body: `${prefix} - message${i}`,
|
body: `${prefix} - message${i}`,
|
||||||
},
|
},
|
||||||
timestamp,
|
// We can't use the exact same timestamp for every message in the tests
|
||||||
|
// otherwise it's a toss up which event will be returned as the closest
|
||||||
|
// for `/timestamp_to_event`. As a note, we don't have to do this after
|
||||||
|
// https://github.com/matrix-org/synapse/pull/13658 merges but it still
|
||||||
|
// seems like a good idea to make the tests more clear.
|
||||||
|
timestamp: timestamp + i,
|
||||||
});
|
});
|
||||||
eventIds.push(eventId);
|
eventIds.push(eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sanity check that we actually sent some messages
|
||||||
|
assert.strictEqual(eventIds.length, numMessages);
|
||||||
|
|
||||||
return eventIds;
|
return eventIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +213,9 @@ async function updateProfile({ client, displayName, avatarUrl }) {
|
||||||
qs.append('user_id', client.applicationServiceUserIdOverride);
|
qs.append('user_id', client.applicationServiceUserIdOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateDisplayNamePromise = fetchEndpointAsJson(
|
let updateDisplayNamePromise = Promise.resolve();
|
||||||
|
if (displayName) {
|
||||||
|
updateDisplayNamePromise = fetchEndpointAsJson(
|
||||||
urlJoin(
|
urlJoin(
|
||||||
client.homeserverUrl,
|
client.homeserverUrl,
|
||||||
`/_matrix/client/v3/profile/${client.userId}/displayname?${qs.toString()}`
|
`/_matrix/client/v3/profile/${client.userId}/displayname?${qs.toString()}`
|
||||||
|
@ -191,8 +228,11 @@ async function updateProfile({ client, displayName, avatarUrl }) {
|
||||||
accessToken: client.accessToken,
|
accessToken: client.accessToken,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const updateAvatarUrlPromise = fetchEndpointAsJson(
|
let updateAvatarUrlPromise = Promise.resolve();
|
||||||
|
if (avatarUrl) {
|
||||||
|
updateAvatarUrlPromise = fetchEndpointAsJson(
|
||||||
urlJoin(
|
urlJoin(
|
||||||
client.homeserverUrl,
|
client.homeserverUrl,
|
||||||
`/_matrix/client/v3/profile/${client.userId}/avatar_url?${qs.toString()}`
|
`/_matrix/client/v3/profile/${client.userId}/avatar_url?${qs.toString()}`
|
||||||
|
@ -205,6 +245,7 @@ async function updateProfile({ client, displayName, avatarUrl }) {
|
||||||
accessToken: client.accessToken,
|
accessToken: client.accessToken,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all([updateDisplayNamePromise, updateAvatarUrlPromise]);
|
await Promise.all([updateDisplayNamePromise, updateAvatarUrlPromise]);
|
||||||
|
|
||||||
|
@ -248,6 +289,8 @@ async function uploadContent({ client, roomId, data, fileName, contentType }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
ensureUserRegistered,
|
||||||
|
getTestClientForAs,
|
||||||
getTestClientForHs,
|
getTestClientForHs,
|
||||||
createTestRoom,
|
createTestRoom,
|
||||||
joinRoom,
|
joinRoom,
|
||||||
|
|
|
@ -14,6 +14,7 @@ const { fetchEndpointAsText, fetchEndpointAsJson } = require('../server/lib/fetc
|
||||||
const config = require('../server/lib/config');
|
const config = require('../server/lib/config');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
getTestClientForAs,
|
||||||
getTestClientForHs,
|
getTestClientForHs,
|
||||||
createTestRoom,
|
createTestRoom,
|
||||||
joinRoom,
|
joinRoom,
|
||||||
|
@ -106,6 +107,20 @@ describe('matrix-public-archive', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Archive', () => {
|
describe('Archive', () => {
|
||||||
|
before(async () => {
|
||||||
|
// Make sure the application service archiver user itself has a profile
|
||||||
|
// set otherwise we run into 404, `Profile was not found` errors when
|
||||||
|
// joining a remote federated room from the archiver user, see
|
||||||
|
// https://github.com/matrix-org/synapse/issues/4778
|
||||||
|
//
|
||||||
|
// FIXME: Remove after https://github.com/matrix-org/synapse/issues/4778 is resolved
|
||||||
|
const asClient = await getTestClientForAs();
|
||||||
|
await updateProfile({
|
||||||
|
client: asClient,
|
||||||
|
displayName: 'Archiver',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Use a fixed date at the start of the UTC day so that the tests are
|
// 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
|
// consistent. Otherwise, the tests could fail when they start close to
|
||||||
// midnight and it rolls over to the next day.
|
// midnight and it rolls over to the next day.
|
||||||
|
@ -163,6 +178,7 @@ describe('matrix-public-archive', () => {
|
||||||
`Coulomb's Law of Friction: Kinetic friction is independent of the sliding velocity.`,
|
`Coulomb's Law of Friction: Kinetic friction is independent of the sliding velocity.`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// TODO: Can we use `createMessagesInRoom` here instead?
|
||||||
const eventIds = [];
|
const eventIds = [];
|
||||||
for (const messageText of messageTextList) {
|
for (const messageText of messageTextList) {
|
||||||
const eventId = await sendMessageOnArchiveDate({
|
const eventId = await sendMessageOnArchiveDate({
|
||||||
|
@ -375,7 +391,39 @@ describe('matrix-public-archive', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`can render day back in time from room on remote homeserver we haven't backfilled from`);
|
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: ['hs2'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const 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(`will redirect to hour pagination when there are too many messages`);
|
it(`will redirect to hour pagination when there are too many messages`);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue