Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test for joining a new federated room #31

Merged
merged 15 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions server/ensure-room-joined.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict';

const assert = require('assert');
const { URLSearchParams } = require('url');
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()}`
);
console.log('joinEndpoint', joinEndpoint);
await fetchEndpointAsJson(joinEndpoint, {
method: 'POST',
accessToken,
});
}

module.exports = ensureRoomJoined;
10 changes: 0 additions & 10 deletions server/fetch-events-in-range.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,6 @@ async function fetchEventsFromTimestampBackwards(accessToken, roomId, ts, limit)
assert(ts);
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(
matrixServerUrl,
`_matrix/client/unstable/org.matrix.msc3030/rooms/${roomId}/timestamp_to_event?ts=${ts}&dir=b`
Expand Down
10 changes: 8 additions & 2 deletions server/routes/install-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const StatusError = require('../lib/status-error');

const fetchRoomData = require('../fetch-room-data');
const fetchEventsInRange = require('../fetch-events-in-range');
const ensureRoomJoined = require('../ensure-room-joined');
const renderHydrogenToString = require('../hydrogen-render/1-render-hydrogen-to-string');

const config = require('../lib/config');
Expand Down Expand Up @@ -117,22 +118,27 @@ function installRoutes(app) {
// the format isn't correct, redirect to the correct hour range
if (hourRange && toHour !== fromHour + 1) {
res.redirect(
urlJoin(
// FIXME: Can we use the matrixPublicArchiveURLCreator here?
`${urlJoin(
basePath,
roomIdOrAlias,
'date',
req.params.yyyy,
req.params.mm,
req.params.dd,
`${fromHour}-${fromHour + 1}`
)
)}?${new URLSearchParams(req.query).toString()}`
);
return;
}

// TODO: Highlight tile that matches ?at=$xxx
//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);
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved

// Do these in parallel to avoid the extra time in sequential round-trips
// (we want to display the archive page faster)
const [roomData, { events, stateEventMap }] = await Promise.all([
Expand Down
10 changes: 8 additions & 2 deletions shared/lib/url-creator.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
'use strict';

const urlJoin = require('url-join');
const { URLSearchParams } = require('url');

class URLCreator {
constructor(basePath) {
this._basePath = basePath;
}

archiveUrlForDate(roomId, date) {
archiveUrlForDate(roomId, date, { viaServers = [] } = {}) {
let qs = new URLSearchParams();
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
[].concat(viaServers).forEach((viaServer) => {
qs.append('via', viaServer);
});

// Gives the date in YYYY/mm/dd format.
// date.toISOString() -> 2022-02-16T23:20:04.709Z
const urlDate = date.toISOString().split('T')[0].replaceAll('-', '/');

return urlJoin(this._basePath, `${roomId}/date/${urlDate}`);
return `${urlJoin(this._basePath, `${roomId}/date/${urlDate}`)}?${qs.toString()}`;
}
}

Expand Down
13 changes: 13 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ $ npm run test-interactive

### Developer utility

macOS:

```sh
$ docker ps --all | grep test_hs
$ docker logs -f --tail 10 matrix_public_archive_test-hs1-1
$ docker logs -f --tail 10 matrix_public_archive_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
```
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved

Windows:

```sh
$ docker ps --all | grep test_hs
$ docker logs -f --tail 10 matrix_public_archive_test_hs1_1
Expand Down
103 changes: 71 additions & 32 deletions test/client-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,39 @@ const { fetchEndpointAsJson, fetchEndpoint } = require('../server/lib/fetch-endp
const config = require('../server/lib/config');
const matrixAccessToken = config.get('matrixAccessToken');
assert(matrixAccessToken);
const testMatrixServerUrl1 = config.get('testMatrixServerUrl1');
assert(testMatrixServerUrl1);

let txnCount = 0;
function getTxnId() {
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
// 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
Expand Down Expand Up @@ -92,13 +118,15 @@ async function joinRoom({ client, roomId, viaServers }) {
qs.append('user_id', client.applicationServiceUserIdOverride);
}

const joinRoomResponse = await fetchEndpointAsJson(
urlJoin(client.homeserverUrl, `/_matrix/client/v3/join/${roomId}?${qs.toString()}`),
{
method: 'POST',
accessToken: client.accessToken,
}
const joinRoomUrl = urlJoin(
client.homeserverUrl,
`/_matrix/client/v3/join/${roomId}?${qs.toString()}`
);
console.log('test client joinRoomUrl', joinRoomUrl);
const joinRoomResponse = await fetchEndpointAsJson(joinRoomUrl, {
method: 'POST',
accessToken: client.accessToken,
});

const joinedRoomId = joinRoomResponse['room_id'];
assert(joinedRoomId);
Expand Down Expand Up @@ -169,6 +197,9 @@ async function createMessagesInRoom({ client, roomId, numMessages, prefix, times
eventIds.push(eventId);
}

// Sanity check that we actually sent some messages
assert.strictEqual(eventIds.length, numMessages);

return eventIds;
}

Expand All @@ -178,33 +209,39 @@ async function updateProfile({ client, displayName, avatarUrl }) {
qs.append('user_id', client.applicationServiceUserIdOverride);
}

const updateDisplayNamePromise = fetchEndpointAsJson(
urlJoin(
client.homeserverUrl,
`/_matrix/client/v3/profile/${client.userId}/displayname?${qs.toString()}`
),
{
method: 'PUT',
body: {
displayname: displayName,
},
accessToken: client.accessToken,
}
);
let updateDisplayNamePromise = Promise.resolve();
if (displayName) {
updateDisplayNamePromise = fetchEndpointAsJson(
urlJoin(
client.homeserverUrl,
`/_matrix/client/v3/profile/${client.userId}/displayname?${qs.toString()}`
),
{
method: 'PUT',
body: {
displayname: displayName,
},
accessToken: client.accessToken,
}
);
}

const updateAvatarUrlPromise = fetchEndpointAsJson(
urlJoin(
client.homeserverUrl,
`/_matrix/client/v3/profile/${client.userId}/avatar_url?${qs.toString()}`
),
{
method: 'PUT',
body: {
avatar_url: avatarUrl,
},
accessToken: client.accessToken,
}
);
let updateAvatarUrlPromise = Promise.resolve();
if (avatarUrl) {
updateAvatarUrlPromise = fetchEndpointAsJson(
urlJoin(
client.homeserverUrl,
`/_matrix/client/v3/profile/${client.userId}/avatar_url?${qs.toString()}`
),
{
method: 'PUT',
body: {
avatar_url: avatarUrl,
},
accessToken: client.accessToken,
}
);
}

await Promise.all([updateDisplayNamePromise, updateAvatarUrlPromise]);

Expand Down Expand Up @@ -248,6 +285,8 @@ async function uploadContent({ client, roomId, data, fileName, contentType }) {
}

module.exports = {
ensureUserRegistered,
getTestClientForAs,
getTestClientForHs,
createTestRoom,
joinRoom,
Expand Down
50 changes: 49 additions & 1 deletion test/e2e-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const { fetchEndpointAsText, fetchEndpointAsJson } = require('../server/lib/fetc
const config = require('../server/lib/config');

const {
ensureUserRegistered,
getTestClientForAs,
getTestClientForHs,
createTestRoom,
joinRoom,
Expand Down Expand Up @@ -106,6 +108,20 @@ describe('matrix-public-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
// consistent. Otherwise, the tests could fail when they start close to
// midnight and it rolls over to the next day.
Expand Down Expand Up @@ -163,6 +179,7 @@ describe('matrix-public-archive', () => {
`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({
Expand Down Expand Up @@ -375,7 +392,38 @@ 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);
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved

const dom = parseHTML(archivePageHtml);

// Make sure the messages are visible
for (let i = 0; i < room2EventIds.length; i++) {
const eventId = room2EventIds[i];
assert.strictEqual(
dom.document.querySelectorAll(`[data-event-id="${eventId}"]`).length,
room2EventIds.length
);
}
});

it(`will redirect to hour pagination when there are too many messages`);

Expand Down