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-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 ,
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 ) {
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 ;
} ) ;
// 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-06-09 19:44:57 -06:00
it ( 'shows all events in a given day' , async ( ) => {
2022-02-24 12:06:19 -07:00
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const roomId = await createTestRoom ( client ) ;
2022-06-09 19:44:57 -06:00
// 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 ( ) )
) ;
2022-02-24 12:06:19 -07:00
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. ` ,
] ;
const eventIds = [ ] ;
for ( const messageText of messageTextList ) {
2022-06-09 19:44:57 -06:00
const eventId = await sendMessageOnArchiveDate ( {
client ,
roomId ,
content : {
msgtype : 'm.text' ,
body : messageText ,
} ,
2022-02-24 12:06:19 -07:00
} ) ;
eventIds . push ( eventId ) ;
}
// Sanity check that we actually sent some messages
assert . strictEqual ( eventIds . length , 3 ) ;
2022-06-09 19:44:57 -06:00
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , archiveDate ) ;
2022-02-24 12:06:19 -07:00
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-06-09 19:44:57 -06:00
} ) ;
2022-02-24 12:06:19 -07:00
2022-06-09 19:44:57 -06:00
// eslint-disable-next-line max-statements
it ( 'can render diverse messages' , async ( ) => {
2022-02-23 20:25:05 -07:00
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const roomId = await createTestRoom ( client ) ;
2022-06-29 05:56:13 -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-23 20:25:05 -07:00
// TODO: Set avatar of room
// Test image
2022-06-09 19:44:57 -06:00
// 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' )
2022-02-23 20:25:05 -07:00
) ;
2022-06-09 19:44:57 -06:00
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 ,
2022-02-24 02:27:53 -07:00
mimetype : 'image/jpeg' ,
2022-06-09 19:44:57 -06:00
w : 640 ,
h : 312 ,
'xyz.amorgan.blurhash' : 'LkR3G|IU?w%NbxbIemae_NxuD$M{' ,
2022-02-24 02:27:53 -07:00
} ,
2022-06-09 19:44:57 -06:00
msgtype : 'm.image' ,
url : mxcUri ,
2022-02-23 20:25:05 -07:00
} ,
} ) ;
// A normal text message
2022-02-24 02:27:53 -07:00
const normalMessageText1 =
'^ Figure 1: Simulated blocks with fractal rough surfaces, exhibiting static frictional interactions' ;
2022-06-09 19:44:57 -06:00
const normalMessageEventId1 = await sendMessageOnArchiveDate ( {
client ,
roomId ,
content : {
msgtype : 'm.text' ,
body : normalMessageText1 ,
} ,
2022-02-23 20:25:05 -07:00
} ) ;
2022-02-24 02:27:53 -07:00
// Another normal text message
const normalMessageText2 =
'The topography of the Moon has been measured with laser altimetry and stereo image analysis.' ;
2022-06-09 19:44:57 -06:00
const normalMessageEventId2 = await sendMessageOnArchiveDate ( {
client ,
roomId ,
content : {
msgtype : 'm.text' ,
body : normalMessageText2 ,
} ,
2022-02-23 20:25:05 -07:00
} ) ;
// Test replies
2022-02-24 02:27:53 -07:00
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. ` ;
2022-06-09 19:44:57 -06:00
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 ,
} ,
2022-02-23 20:25:05 -07:00
} ,
} ,
} ) ;
// Test reactions
2022-02-24 02:27:53 -07:00
const reactionText = '😅' ;
2022-06-09 19:44:57 -06:00
await sendEventOnArchiveDate ( {
client ,
roomId ,
eventType : 'm.reaction' ,
content : {
'm.relates_to' : {
rel _type : 'm.annotation' ,
event _id : replyMessageEventId ,
key : reactionText ,
} ,
2022-02-23 20:25:05 -07:00
} ,
} ) ;
2022-06-09 19:44:57 -06:00
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , archiveDate ) ;
2022-02-23 20:25:05 -07:00
const archivePageHtml = await fetchEndpointAsText ( archiveUrl ) ;
2022-02-24 02:27:53 -07:00
const dom = parseHTML ( archivePageHtml ) ;
2022-06-29 05:56:13 -06:00
// Make sure the user display name is visible on the message
assert . match (
dom . document . querySelector ( ` [data-event-id=" ${ imageEventId } "] ` ) . outerHTML ,
new RegExp ( ` .* ${ escapeStringRegexp ( displayName ) } .* ` )
) ;
// Make sure the user avatar is visible on the message
const avatarImageElement = dom . document . querySelector (
2022-07-05 05:23:47 -06:00
` [data-event-id=" ${ imageEventId } "] [data-testid="avatar"] img `
2022-06-29 05:56:13 -06:00
) ;
assert ( avatarImageElement ) ;
assert . match ( avatarImageElement . getAttribute ( 'src' ) , new RegExp ( ` ^http://.* ` ) ) ;
2022-02-24 02:27:53 -07:00
// Make sure the image message is visible
2022-06-29 05:56:13 -06:00
const imageElement = dom . document . querySelector (
2022-07-05 05:23:47 -06:00
` [data-event-id=" ${ imageEventId } "] [data-testid="media"] img `
2022-06-29 05:56:13 -06:00
) ;
2022-02-24 02:27:53 -07:00
assert ( imageElement ) ;
assert . match ( imageElement . getAttribute ( 'src' ) , new RegExp ( ` ^http://.* ` ) ) ;
2022-06-09 19:44:57 -06:00
assert . strictEqual ( imageElement . getAttribute ( 'alt' ) , imageFileName ) ;
2022-02-24 02:27:53 -07:00
// 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 ) } .* ` )
) ;
// Make sure the reaction also exists
assert . match (
replyMessageElement . outerHTML ,
new RegExp ( ` .* ${ escapeStringRegexp ( reactionText ) } .* ` )
) ;
2022-06-09 19:44:57 -06:00
} ) ;
2022-02-22 19:55:42 -07:00
2022-06-09 19:44:57 -06:00
it ( ` can render day back in time from room on remote homeserver we haven't backfilled from ` ) ;
2022-02-22 19:55:42 -07:00
2022-06-09 19:44:57 -06:00
it ( ` will redirect to hour pagination when there are too many messages ` ) ;
2022-02-24 02:27:53 -07:00
2022-06-09 19:44:57 -06:00
it ( ` will render a room with only a day of messages ` ) ;
2022-02-24 02:27:53 -07:00
2022-06-09 19:44:57 -06:00
it (
` will render a room with a sparse amount of messages (a few per day) with no contamination between days `
) ;
} ) ;
2022-02-22 15:06:29 -07:00
} ) ;