2022-02-22 15:06:29 -07:00
'use strict' ;
2022-02-23 20:25:05 -07:00
process . env . NODE _ENV = 'test' ;
2022-02-22 15:06:29 -07:00
const assert = require ( 'assert' ) ;
2022-06-09 19:44:57 -06:00
const path = require ( 'path' ) ;
2022-10-27 00:09:13 -06:00
const http = require ( 'http' ) ;
2022-02-22 19:25:24 -07:00
const urlJoin = require ( 'url-join' ) ;
2022-02-24 02:27:53 -07:00
const escapeStringRegexp = require ( 'escape-string-regexp' ) ;
const { parseHTML } = require ( 'linkedom' ) ;
2022-06-09 19:44:57 -06:00
const { readFile } = require ( 'fs' ) . promises ;
2022-02-22 19:25:24 -07:00
2022-06-09 19:44:57 -06:00
const MatrixPublicArchiveURLCreator = require ( 'matrix-public-archive-shared/lib/url-creator' ) ;
2022-02-23 20:25:05 -07:00
const { fetchEndpointAsText , fetchEndpointAsJson } = require ( '../server/lib/fetch-endpoint' ) ;
2022-06-09 19:44:57 -06:00
const config = require ( '../server/lib/config' ) ;
2022-02-23 20:25:05 -07:00
2022-06-09 19:44:57 -06:00
const {
getTestClientForHs ,
createTestRoom ,
2022-10-27 00:09:13 -06:00
getCanonicalAlias ,
2022-06-09 19:44:57 -06:00
joinRoom ,
sendEvent ,
sendMessage ,
createMessagesInRoom ,
2022-06-29 05:56:13 -06:00
updateProfile ,
2022-06-09 19:44:57 -06:00
uploadContent ,
} = require ( './client-utils' ) ;
2022-02-23 20:25:05 -07:00
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' ) ;
2022-02-23 20:25:05 -07:00
assert ( basePath ) ;
2022-02-24 02:27:53 -07:00
const interactive = config . get ( 'interactive' ) ;
2022-02-23 20:25:05 -07:00
const matrixPublicArchiveURLCreator = new MatrixPublicArchiveURLCreator ( basePath ) ;
2022-02-22 15:06:29 -07:00
2022-02-23 20:25:05 -07:00
const HOMESERVER _URL _TO _PRETTY _NAME _MAP = {
[ testMatrixServerUrl1 ] : 'hs1' ,
[ testMatrixServerUrl2 ] : 'hs2' ,
} ;
2022-02-22 19:25:24 -07:00
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' ) ;
} ) ;
2022-02-23 20:25:05 -07:00
after ( ( ) => {
2022-02-24 02:27:53 -07:00
if ( ! interactive ) {
server . close ( ) ;
}
2022-02-23 20:25:05 -07:00
} ) ;
2022-06-09 19:44:57 -06:00
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 ( ) => {
2022-02-23 20:25:05 -07:00
const hs1Client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const hs2Client = await getTestClientForHs ( testMatrixServerUrl2 ) ;
// Create a room on hs2
const hs2RoomId = await createTestRoom ( hs2Client ) ;
2022-06-09 19:44:57 -06:00
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
2022-02-23 20:25:05 -07:00
// Join hs1 to a room on hs2 (federation)
2022-06-09 19:44:57 -06:00
await joinRoom ( {
client : hs1Client ,
roomId : hs2RoomId ,
viaServers : 'hs2' ,
} ) ;
2022-02-22 19:55:42 -07:00
2022-02-23 20:25:05 -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 messageResData = await fetchEndpointAsJson ( messagesEndpoint , {
accessToken : hs1Client . accessToken ,
2022-02-22 19:55:42 -07:00
} ) ;
2022-02-23 20:25:05 -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 ) } `
) ;
} ) ;
2022-06-09 19:44:57 -06:00
} ) ;
} ) ;
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 ) {
2022-08-29 18:42:18 -06:00
// eslint-disable-next-line no-console
2022-06-09 19:44:57 -06:00
console . log ( 'Interactive URL for test' , archiveUrl ) ;
2022-02-23 20:25:05 -07:00
}
2022-06-09 19:44:57 -06:00
// 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 ;
2022-09-20 15:02:09 -06:00
// Reset any custom modifications made for a particular test
config . reset ( ) ;
2022-06-09 19:44:57 -06:00
} ) ;
// 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-23 20:25:05 -07:00
}
2022-02-22 19:55:42 -07:00
2022-09-20 15:02:09 -06: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 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 ) } .* ` )
) ;
}
2022-09-08 01:18:18 -06:00
} ) ;
2022-09-20 15:02:09 -06:00
// 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
2022-09-20 15:02:09 -06: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
2022-09-20 15:02:09 -06:00
// TODO: Set avatar of room
2022-02-24 12:06:19 -07:00
2022-09-20 15:02:09 -06: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 ( {
2022-06-09 19:44:57 -06:00
client ,
roomId ,
content : {
msgtype : 'm.text' ,
2022-09-20 15:02:09 -06:00
body : normalMessageText1 ,
2022-06-09 19:44:57 -06:00
} ,
2022-02-24 12:06:19 -07:00
} ) ;
2022-09-20 15:02:09 -06: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
2022-09-20 15:02:09 -06: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 archivePageHtml = await fetchEndpointAsText ( archiveUrl ) ;
2022-02-24 12:06:19 -07:00
2022-09-20 15:02:09 -06:00
const dom = parseHTML ( archivePageHtml ) ;
2022-02-24 12:06:19 -07:00
2022-09-20 15:02:09 -06:00
// Make sure the user display name is visible on the message
2022-02-24 12:06:19 -07:00
assert . match (
2022-09-20 15:02:09 -06:00
dom . document . querySelector ( ` [data-event-id=" ${ imageEventId } "] ` ) . outerHTML ,
new RegExp ( ` .* ${ escapeStringRegexp ( displayName ) } .* ` )
2022-02-24 12:06:19 -07:00
) ;
2022-09-20 15:02:09 -06: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://.* ` ) ) ;
2022-02-23 20:25:05 -07:00
2022-09-20 15:02:09 -06:00
// 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 ) ;
2022-02-23 20:25:05 -07:00
2022-09-20 15:02:09 -06:00
// Make sure the normal message is visible
assert . match (
dom . document . querySelector ( ` [data-event-id=" ${ normalMessageEventId1 } "] ` ) . outerHTML ,
new RegExp ( ` .* ${ escapeStringRegexp ( normalMessageText1 ) } .* ` )
) ;
2022-02-23 20:25:05 -07:00
2022-09-20 15:02:09 -06:00
// Make sure the other normal message is visible
assert . match (
dom . document . querySelector ( ` [data-event-id=" ${ normalMessageEventId2 } "] ` ) . outerHTML ,
new RegExp ( ` .* ${ escapeStringRegexp ( normalMessageText2 ) } .* ` )
) ;
2022-02-23 20:25:05 -07:00
2022-09-20 15:02:09 -06:00
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 ) } .* ` )
) ;
2022-02-23 20:25:05 -07:00
2022-09-20 15:02:09 -06:00
// Make sure the reaction also exists
assert . match (
replyMessageElement . outerHTML ,
new RegExp ( ` .* ${ escapeStringRegexp ( reactionText ) } .* ` )
) ;
2022-02-23 20:25:05 -07:00
} ) ;
2022-09-20 15:02:09 -06:00
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.
2022-10-20 01:06:43 -06:00
viaServers : [ HOMESERVER _URL _TO _PRETTY _NAME _MAP [ testMatrixServerUrl2 ] ] ,
2022-09-20 15:02:09 -06:00
} ) ;
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
) ;
2022-02-23 20:25:05 -07:00
} ) ;
2022-09-20 15:02:09 -06:00
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' ,
2022-08-29 18:42:18 -06:00
} ,
2022-09-20 15:02:09 -06:00
} ) ;
const expectedEventIdsOnDay = [ eventId ] ;
// Visit `/:roomIdOrAlias` and expect to be redirected to the last day with events
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForRoom ( roomId ) ;
const 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
) ;
2022-08-29 18:42:18 -06:00
} ) ;
2022-09-20 15:02:09 -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' ,
2022-06-09 19:44:57 -06:00
} ,
2022-09-20 15:02:09 -06:00
} ) ;
const expectedEventIdsToBeDisplayed = [ eventId ] ;
2022-02-23 20:25:05 -07:00
2022-09-20 15:02:09 -06:00
// Visit the archive on the day ahead of where there are messages
const visitArchiveDate = new Date ( Date . UTC ( 2022 , 0 , 5 ) ) ;
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 archivePageHtml = await fetchEndpointAsText ( archiveUrl ) ;
2022-02-23 20:25:05 -07:00
2022-09-20 15:02:09 -06:00
const dom = parseHTML ( archivePageHtml ) ;
2022-02-24 02:27:53 -07:00
2022-09-20 15:02:09 -06: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
2022-09-20 15:02:09 -06: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
) ;
} ) ;
2022-06-29 05:56:13 -06:00
2022-09-20 15:02:09 -06:00
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 ) ;
2022-06-29 05:56:13 -06:00
2022-09-20 15:02:09 -06:00
// We purposely send no events in the room
2022-02-24 02:27:53 -07:00
2022-09-20 15:02:09 -06:00
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , archiveDate ) ;
const archivePageHtml = await fetchEndpointAsText ( archiveUrl ) ;
2022-02-24 02:27:53 -07:00
2022-09-20 15:02:09 -06:00
const dom = parseHTML ( archivePageHtml ) ;
2022-08-29 18:42:18 -06:00
2022-09-20 15:02:09 -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"] `
)
) ;
} ) ;
2022-08-29 18:42:18 -06:00
2022-09-20 15:02:09 -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
2022-09-20 15:02:09 -06: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 ( ) ,
} ) ;
2022-08-29 17:56:31 -06:00
2022-09-20 15:02:09 -06:00
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , archiveDate ) ;
const archivePageHtml = await fetchEndpointAsText ( archiveUrl ) ;
2022-08-29 17:56:31 -06:00
2022-09-20 15:02:09 -06:00
assert . match ( archivePageHtml , /TODO: Redirect user to smaller hour range/ ) ;
2022-08-29 17:56:31 -06:00
} ) ;
2022-09-20 15:02:09 -06:00
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 ,
// This is larger than the `archiveMessageLimit` we set
numMessages : 2 ,
prefix : 'events in room' ,
timestamp : previousArchiveDate . getTime ( ) ,
} ) ;
2022-08-29 17:56:31 -06:00
2022-09-20 15:02:09 -06:00
// Create more messages than the limit
const eventIdsOnDay = await createMessagesInRoom ( {
client ,
roomId : roomId ,
// This is larger than the `archiveMessageLimit` we set
numMessages : 2 ,
prefix : 'events in room' ,
timestamp : archiveDate . getTime ( ) ,
} ) ;
2022-02-22 19:55:42 -07:00
2022-09-20 15:02:09 -06:00
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , archiveDate ) ;
const archivePageHtml = await fetchEndpointAsText ( archiveUrl ) ;
2022-02-24 02:27:53 -07:00
2022-09-20 15:02:09 -06:00
const dom = parseHTML ( archivePageHtml ) ;
2022-02-24 02:27:53 -07:00
2022-09-20 15:02:09 -06: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
) ;
} ) ;
} ) ;
2022-09-08 18:15:07 -06:00
2022-09-15 19:41:55 -06:00
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
2022-10-20 01:06:43 -06:00
//
// We use a `timeToken` so that we can namespace these rooms away from other
// test runs against the same homeserver
2022-09-15 19:41:55 -06:00
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 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 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 ( ) ) ;
} ) ;
2022-10-20 01:06:43 -06:00
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 roomDirectoryWithSearchPageHtml = await fetchEndpointAsText ( archiveUrl ) ;
const domWithSearch = parseHTML ( roomDirectoryWithSearchPageHtml ) ;
2022-10-21 01:09:26 -06:00
// 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 ]
) ;
2022-10-20 01:06:43 -06:00
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 ( ) ) ;
} ) ;
2022-09-15 19:41:55 -06:00
} ) ;
2022-09-08 18:15:07 -06:00
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 (
'We expect the request to fail with a 403 since the archive should not be able to view a private room'
) ;
} catch ( err ) {
assert . strictEqual ( err . response . status , 403 ) ;
}
} ) ;
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 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 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'
) ;
} ) ;
} ) ;
2022-10-27 00:09:13 -06:00
describe ( 'redirects' , ( ) => {
const controller = new AbortController ( ) ;
const { signal } = controller ;
// We have to use this 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-06-09 19:44:57 -06:00
} ) ;
2022-02-22 15:06:29 -07:00
} ) ;