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
2023-04-05 03:25:31 -06:00
const RethrownError = require ( '../server/lib/rethrown-error' ) ;
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' ) ;
2023-04-05 03:25:31 -06:00
const {
MS _LOOKUP ,
TIME _PRECISION _VALUES ,
DIRECTION ,
} = require ( 'matrix-public-archive-shared/lib/reference-values' ) ;
const { ONE _DAY _IN _MS , ONE _HOUR _IN _MS , ONE _MINUTE _IN _MS , ONE _SECOND _IN _MS } = MS _LOOKUP ;
2022-02-23 20:25:05 -07:00
2022-06-09 19:44:57 -06:00
const {
2023-04-05 03:25:31 -06:00
getTestClientForAs ,
2022-06-09 19:44:57 -06:00
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 ,
2023-04-05 03:25:31 -06:00
} = require ( './test-utils/client-utils' ) ;
const TestError = require ( './test-utils/test-error' ) ;
2023-04-07 11:52:41 -06:00
const parseRoomDayMessageStructure = require ( './test-utils/parse-room-day-message-structure' ) ;
const parseArchiveUrlForRoom = require ( './test-utils/parse-archive-url-for-room' ) ;
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' ,
} ;
2023-04-05 03:25:31 -06:00
function assertExpectedTimePrecisionAgainstUrl ( expectedTimePrecision , url ) {
const urlObj = new URL ( url , basePath ) ;
const urlPathname = urlObj . pathname ;
// First check the URL has the appropriate time precision
if ( expectedTimePrecision === null ) {
assert . doesNotMatch (
urlPathname ,
/T[\d:]*?$/ ,
` Expected the URL to *not* have any time precision but saw ${ urlPathname } `
) ;
} else if ( expectedTimePrecision === TIME _PRECISION _VALUES . minutes ) {
assert . match ( urlPathname , /T\d\d:\d\d$/ ) ;
} else if ( expectedTimePrecision === TIME _PRECISION _VALUES . seconds ) {
assert . match ( urlPathname , /T\d\d:\d\d:\d\d$/ ) ;
} else {
throw new Error (
` \` expectedTimePrecision \` was an unexpected value ${ expectedTimePrecision } which we don't know how to assert here `
) ;
}
}
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 ) ;
2023-04-05 03:25:31 -06:00
const { eventIds : room2EventIds } = await createMessagesInRoom ( {
2022-06-09 19:44:57 -06:00
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"]} `
) ;
2022-11-03 03:12:00 -06:00
const { data : messageResData } = await fetchEndpointAsJson ( messagesEndpoint , {
2022-02-23 20:25:05 -07:00
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.
2022-11-02 03:27:30 -06:00
// January 15th, 2022
const archiveDate = new Date ( Date . UTC ( 2022 , 0 , 15 ) ) ;
2022-06-09 19:44:57 -06:00
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 ) ;
2022-11-03 03:12:00 -06:00
const { data : archivePageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
2022-09-20 15:02:09 -06:00
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 ) ;
2022-11-03 03:12:00 -06:00
const { data : 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 ) ;
2023-04-05 03:25:31 -06:00
const { eventIds : room2EventIds } = await createMessagesInRoom ( {
2022-09-20 15:02:09 -06:00
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
} ) ;
2022-11-03 03:12:00 -06:00
const { data : archivePageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
2022-09-20 15:02:09 -06:00
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
} ) ;
2023-04-05 03:25:31 -06:00
it ( 'redirects to most recent day with message history' , async ( ) => {
2022-09-20 15:02:09 -06:00
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 ] ;
2023-04-05 03:25:31 -06:00
// Visit `/:roomIdOrAlias` and expect to be redirected to the most recent day with events
2022-09-20 15:02:09 -06:00
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForRoom ( roomId ) ;
2022-11-03 03:12:00 -06:00
const { data : archivePageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
2022-09-20 15:02:09 -06:00
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
2022-11-02 03:27:30 -06:00
const visitArchiveDate = new Date ( Date . UTC ( 2022 , 0 , 20 ) ) ;
2022-09-20 15:02:09 -06:00
assert (
visitArchiveDate > archiveDate ,
'The date we visit the archive (`visitArchiveDate`) should be after where the messages were sent (`archiveDate`)'
) ;
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , visitArchiveDate ) ;
2022-11-03 03:12:00 -06:00
const { data : 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
2023-04-05 03:25:31 -06:00
it ( 'shows no events summary when no messages at or before the given day (empty view)' , async ( ) => {
2022-09-20 15:02:09 -06:00
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 ) ;
2022-11-03 03:12:00 -06:00
const { data : 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-11-02 03:27:30 -06:00
it ( '404 when trying to view a future day' , async ( ) => {
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const roomId = await createTestRoom ( client ) ;
try {
2023-04-05 03:25:31 -06:00
const TWO _DAYS _IN _MS = 2 * ONE _DAY _IN _MS ;
2022-11-02 03:27:30 -06:00
await fetchEndpointAsText (
matrixPublicArchiveURLCreator . archiveUrlForDate (
roomId ,
2023-04-05 03:25:31 -06:00
new Date ( Date . now ( ) + TWO _DAYS _IN _MS )
2022-11-02 03:27:30 -06:00
)
) ;
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 } `
) ;
}
} ) ;
2023-04-05 03:25:31 -06:00
describe ( 'time selector' , ( ) => {
it ( 'shows time selector when there are too many messages from the same day' , async ( ) => {
// Set this low so it's easy to hit the limit
config . set ( 'archiveMessageLimit' , 3 ) ;
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const roomId = await createTestRoom ( client ) ;
await createMessagesInRoom ( {
client ,
roomId ,
// This should be greater than the `archiveMessageLimit`
numMessages : 10 ,
prefix : ` foo ` ,
timestamp : archiveDate . getTime ( ) ,
// Just spread things out a bit so the event times are more obvious
// and stand out from each other while debugging
increment : ONE _HOUR _IN _MS ,
} ) ;
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , archiveDate ) ;
const { data : archivePageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
const dom = parseHTML ( archivePageHtml ) ;
// Make sure the time selector is visible
const timeSelectorElement = dom . document . querySelector ( ` [data-testid="time-selector"] ` ) ;
assert ( timeSelectorElement ) ;
2022-11-02 03:27:30 -06:00
} ) ;
2023-04-05 03:25:31 -06:00
it ( 'shows time selector when there are too many messages from the same day but paginated forward into days with no messages' , async ( ) => {
// Set this low so it's easy to hit the limit
config . set ( 'archiveMessageLimit' , 3 ) ;
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const roomId = await createTestRoom ( client ) ;
2022-11-02 03:27:30 -06:00
2023-04-05 03:25:31 -06:00
await createMessagesInRoom ( {
client ,
2022-11-02 03:27:30 -06:00
roomId ,
2023-04-05 03:25:31 -06:00
// This should be greater than the `archiveMessageLimit`
numMessages : 10 ,
prefix : ` foo ` ,
timestamp : archiveDate . getTime ( ) ,
// Just spread things out a bit so the event times are more obvious
// and stand out from each other while debugging
increment : ONE _HOUR _IN _MS ,
} ) ;
2022-11-02 03:27:30 -06:00
2023-04-05 03:25:31 -06:00
// Visit a day after when the messages were sent but there weren't
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`)'
2022-11-02 03:27:30 -06:00
) ;
2023-04-05 03:25:31 -06:00
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , visitArchiveDate ) ;
const { data : archivePageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
const dom = parseHTML ( archivePageHtml ) ;
// Make sure the time selector is visible
const timeSelectorElement = dom . document . querySelector ( ` [data-testid="time-selector"] ` ) ;
assert ( timeSelectorElement ) ;
} ) ;
it ( 'does not show time selector when all events from the same day but not over the limit' , async ( ) => {
// Set this low so we don't have to deal with many messages in the tests
config . set ( 'archiveMessageLimit' , 5 ) ;
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const roomId = await createTestRoom ( client ) ;
await createMessagesInRoom ( {
client ,
roomId ,
// This should be lesser than the `archiveMessageLimit`
numMessages : 3 ,
prefix : ` foo ` ,
timestamp : archiveDate . getTime ( ) ,
// Just spread things out a bit so the event times are more obvious
// and stand out from each other while debugging
increment : ONE _HOUR _IN _MS ,
} ) ;
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , archiveDate ) ;
const { data : archivePageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
const dom = parseHTML ( archivePageHtml ) ;
// Make sure the time selector is *NOT* visible
const timeSelectorElement = dom . document . querySelector ( ` [data-testid="time-selector"] ` ) ;
assert . strictEqual ( timeSelectorElement , null ) ;
} ) ;
it ( 'does not show time selector when showing events from multiple days' , async ( ) => {
// Set this low so we don't have to deal with many messages in the tests
config . set ( 'archiveMessageLimit' , 5 ) ;
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const roomId = await createTestRoom ( client ) ;
// Create more messages than the archiveMessageLimit across many days but we
// should not go over the limit on a daily basis
const dayBeforeArchiveDateTs = Date . UTC (
archiveDate . getUTCFullYear ( ) ,
archiveDate . getUTCMonth ( ) ,
archiveDate . getUTCDate ( ) - 1
2022-11-03 04:06:53 -06:00
) ;
2023-04-05 03:25:31 -06:00
await createMessagesInRoom ( {
client ,
roomId ,
// This should be lesser than the `archiveMessageLimit`
numMessages : 3 ,
prefix : ` foo ` ,
timestamp : dayBeforeArchiveDateTs ,
// Just spread things out a bit so the event times are more obvious
// and stand out from each other while debugging
increment : ONE _HOUR _IN _MS ,
} ) ;
await createMessagesInRoom ( {
client ,
roomId ,
// This should be lesser than the `archiveMessageLimit`
numMessages : 3 ,
prefix : ` foo ` ,
timestamp : archiveDate . getTime ( ) ,
// Just spread things out a bit so the event times are more obvious
// and stand out from each other while debugging
increment : ONE _HOUR _IN _MS ,
} ) ;
2022-11-03 04:06:53 -06:00
2023-04-05 03:25:31 -06:00
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForDate ( roomId , archiveDate ) ;
const { data : archivePageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
const dom = parseHTML ( archivePageHtml ) ;
2022-11-02 03:27:30 -06:00
2023-04-05 03:25:31 -06:00
// Make sure the time selector is *NOT* visible
const timeSelectorElement = dom . document . querySelector ( ` [data-testid="time-selector"] ` ) ;
assert . strictEqual ( timeSelectorElement , null ) ;
2022-11-02 03:27:30 -06:00
} ) ;
2023-04-05 03:25:31 -06:00
} ) ;
2022-11-02 03:27:30 -06:00
2023-04-05 03:25:31 -06:00
describe ( 'Jump forwards and backwards' , ( ) => {
const jumpTestCases = [
{
// 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 day3. We do
// this so that we don't start from day4 backwards which would miss messages
// because there are more than 5 messages in between day4 and day2.
//
// Even though there is overlap between the pages, our scroll continues from
// the event where the 1st page starts.
testName : 'can jump forward to the next activity' ,
// Create enough surround messages on nearby 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.
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12
[ day1 ] [ day2 ] [ day3 ] [ day4 ]
[ page1 ]
| -- jump - fwd - 4 - messages -- > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03?at=$event7' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// This test is just slightly different and jumps further into day4 (just a
// slight variation to make sure it still does the correct thing)
//
// 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 day3. There
// is exactly 5 messages between day4 and day2 which would be a perfect next
// page but because we hit the middle of day4, we have no idea how many more
// messages are in day4.
//
// Even though there is overlap between the pages, our scroll continues from
// the event where the 1st page starts.
testName : 'can jump forward to the next activity2' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11
[ day1 ] [ day2 ] [ day3 ] [ day4 ]
[ page1 ]
| -- jump - fwd - 4 - messages -- > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03?at=$event7' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// In order to jump from the 1st page to the 2nd, we first "jump" forward 4
// messages by paginating `/messages?limit=4` but it only returns 2x
// messages (event11 and event12) which is less than our limit of 4, so we
// know we reached the end and can simply display the day that the latest
// event occured on.
testName : 'can jump forward to the latest activity in the room (same day)' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12
[ day1 ] [ day2 ] [ day3 ] [ day4 ]
[ page1 ]
| -- jump - fwd - 4 - messages -- > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/04T01:00' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/04T01:00' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/04?at=$event11' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// In order to jump from the 1st page to the 2nd, we first "jump" forward 4
// messages by paginating `/messages?limit=4` but it only returns 3x
// messages (event10, event11, event12) which is less than our limit of 4,
// so we know we reached the end and can simply display the day that the
// latest event occured on.
testName : 'can jump forward to the latest activity in the room (different day)' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12
[ day1 ] [ day2 ] [ day3 ] [ day4 ]
[ page1 ]
| -- - jump - fwd - 4 - messages -- - > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/03' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/04?at=$event10' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
// This test currently doesn't work because it finds the primordial room
// creation events which are created in now time vs the timestamp massaging we
// do for the message fixtures. We can uncomment this once Synapse supports
// timestamp massaging for `/createRoom`, see
// https://github.com/matrix-org/synapse/issues/15346
2022-11-03 04:06:53 -06:00
//
2023-04-05 03:25:31 -06:00
// {
// // In order to jump from the 1st page to the 2nd, we first "jump" forward 4
// // messages by paginating `/messages?limit=4` but it returns no messages
// // which is less than our limit of 4, so we know we reached the end and can
// // simply TODO
// testName:
// 'can jump forward to the latest activity in the room (when already viewing the latest activity)',
2023-04-07 11:52:41 -06:00
// roomDayMessageStructureString: `
// [room1 ]
// 1 <-- 2 <-- 3 <-- 4 <-- 5 <-- 6 <-- 7 <-- 8 <-- 9 <-- 10 <-- 11 <-- 12
// [day1 ] [day2 ] [day3 ] [day4 ]
// [page1 ]
// |---jump-fwd-4-messages--->|
// [page2 ]
// `,
// startUrl: '/r/room1/date/2022/01/04',
2023-04-05 03:25:31 -06:00
// page1: {
2023-04-07 11:52:41 -06:00
// url: '/r/room1/date/2022/01/04',
2023-04-05 03:25:31 -06:00
// action: 'next',
// },
// page2: {
// // If we can't find any more messages to paginate to, we just progress the
// // date forward by a day so we can display the empty view for that day.
2023-04-07 11:52:41 -06:00
// //
// // TODO: This page probably doesn't need a `?at=` continue event
// url: '/r/room1/date/2022/01/05?at=TODO',
2023-04-05 03:25:31 -06:00
// action: null,
// },
// },
{
// Test to make sure we can jump from the 1st page to the 2nd page forwards
// even when it exactly paginates to the last message of the next day. We're
// testing this specifically to ensure that you actually jump to the next
// day (previously failed with naive flawed code).
//
// In order to jump from the 1st page to the 2nd, we first jump forward 3
// messages, then back-track to the first date boundary which is the nearest
// hour backwards from event9. We use the nearest hour because there is
// less than a day of gap between event6 and event9 and we fallback from
// nearest day to hour boundary.
//
testName :
'can jump forward to the next activity even when it exactly goes to the end of the next day' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12
[ day1 ] [ day2 ] [ day3 ] [ day4 ]
[ page1 ]
| - jump - fwd - 3 - msg - > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
// We expect the URL to look like `T02:00` because we're rendering part way
// through day3 and while we could get away with just hour precision, the
// default precision has hours and minutes.
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03T02:00?at=$event7' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// From the first page with too many messages, starting at event5(page1
// rangeStart), we look backwards for the closest event. Because we find
// event4 as the closest, which is from a different day from event9(page1
// rangeEnd), we can just display the day where event5 resides.
//
// Even though there is overlap between
// the pages, our scroll continues from the event where the 1st page starts.
testName : 'can jump backward to the previous activity' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12
[ day1 ] [ day2 ] [ day3 ] [ day4 ]
[ page1 ]
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/03' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03' ,
2023-04-05 03:25:31 -06:00
action : 'previous' ,
} ,
page2 : {
// Continuing from the first event of day2 since we already saw the rest
// of day2 in the first page
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02?at=$event4' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// In order to jump from the 1st page to the 2nd, we first jump forward 8
// messages, then back-track to the first date boundary which is the nearest
// day backwards from event20. We use the nearest day because there is more
// than a day of gap between event12 and event20.
testName :
'can jump forward over many quiet days without losing any messages in the gap' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14 < -- 15 < -- 16 < -- 17 < -- 18 < -- 19 < -- 20 < -- 21
[ day1 ] [ day2 ] [ day3 ] [ day4 ] [ day5 ] [ day6 ] [ day7 ] [ day8 ]
[ page1 ]
| -- -- -- -- -- -- -- -- -- jump - fwd - 8 - msg -- -- -- -- -- -- -- -- -- -- -- > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/04' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/04' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/07?at=$event13' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// From the first page with too many messages, starting at event13 (page1
// rangeStart), we look backwards for the closest event. Because we find
// event12 as the closest, which is from the a different day from event21
// (page1 rangeEnd), we can just display the day where event12 resides.
testName : 'can jump backward to the previous activity with many small quiet days' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14 < -- 15 < -- 16 < -- 17 < -- 18 < -- 19 < -- 20 < -- 21
[ day1 ] [ day2 ] [ day3 ] [ day4 ] [ day5 ] [ day6 ] [ day7 ] [ day8 ] [ day9 ]
[ page1 ]
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/09' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/09' ,
2023-04-05 03:25:31 -06:00
action : 'previous' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/06?at=$event12' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// Test to make sure we can jump forwards from the 1st page to the 2nd page
// with too many messages to display on a single day.
//
// We jump forward 4 messages (`archiveMessageLimit`), then back-track to
// the nearest hour which starts us from event9, and then we display 5
// messages because we fetch one more than `archiveMessageLimit` to
// determine overflow.
testName : 'can jump forward to the next activity and land in too many messages' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14 < -- 15
[ day1 ] [ day2 ] [ day3 ] [ day4 ]
[ page1 ]
| -- jump - fwd - 4 - messages -- > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03T03:00?at=$event7' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// Test to make sure we can jump backwards from the 1st page to the 2nd page
// with too many messages to display on a single day.
//
// From the first page with too many messages, starting at event10 (page1
// rangeStart), we look backwards for the closest event. Because we find
// event9 as the closest, which is from the a different day from event14
// (page1 rangeEnd), we can just display the day where event9 resides.
testName : 'can jump backward to the previous activity and land in too many messages' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14
[ day1 ] [ day2 ] [ day3 ] [ day4 ]
[ page1 ]
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/04' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/04' ,
2023-04-05 03:25:31 -06:00
action : 'previous' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02?at=$event9' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// We jump forward 4 messages (`archiveMessageLimit`) to event12, then
// back-track to the nearest hour which starts off at event11 and render the
// page with 5 messages because we fetch one more than `archiveMessageLimit`
// to determine overflow.
testName :
'can jump forward from one day with too many messages into the next day with too many messages' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14
[ day1 ] [ day2 ] [ day3 ]
[ page1 ]
| -- - jump - fwd - 4 - messages -- - > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03T03:00?at=$event9' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// From the first page with too many messages, starting at event10 (page1
// rangeStart), we look backwards for the closest event. Because we find
// event9 as the closest, which is from the same day as event14 (page1
// rangeEnd), we round up to the nearest hour so that the URL encompasses it
// when looking backwards.
testName :
'can jump backward from one day with too many messages into the previous day with too many messages' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14
[ day1 ] [ day2 ] [ day3 ]
[ page1 ]
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/03' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03' ,
2023-04-05 03:25:31 -06:00
action : 'previous' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03T01:00?at=$event9' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// We jump forward 4 messages (`archiveMessageLimit`) to event12, then
// back-track to the nearest hour which starts off at event11 and render the
// page with 5 messages because we fetch one more than `archiveMessageLimit`
// to determine overflow.
testName :
'can jump forward from one day with too many messages into the same day with too many messages' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14
[ day1 ] [ day2 ]
[ page1 ]
| -- - jump - fwd - 4 - messages -- - > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/02T6:00' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02T6:00' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02T09:00?at=$event9' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// From the first page with too many messages, starting at event10 (page1
// rangeStart), we look backwards for the closest event. Because we find
// event9 as the closest, which is from the same day as event14 (page1
// rangeEnd), we round up to the nearest hour so that the URL encompasses it
// when looking backwards.
testName :
'can jump backward from one day with too many messages into the same day with too many messages' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14
[ day1 ] [ day2 ]
[ page1 ]
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/02T11:00' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02T11:00' ,
2023-04-05 03:25:31 -06:00
action : 'previous' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02T06:00?at=$event8' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// We jump forward 4 messages (`archiveMessageLimit`) to event12, then
// back-track to the nearest hour which starts off at event11 and render the
// page with 5 messages because we fetch one more than `archiveMessageLimit`
// to determine overflow.
testName :
'can jump forward from the middle of one day with too many messages into the next day with too many messages' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14
[ day1 ] [ day2 ] [ day3 ]
[ page1 ]
| -- - jump - fwd - 4 - messages -- - > |
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/02T06:00' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02T06:00' ,
2023-04-05 03:25:31 -06:00
action : 'next' ,
} ,
page2 : {
// Continuing from the unseen event in day2
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03T02:00?at=$event9' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// From the first page with too many messages, starting at event10 (page1
// rangeStart), we look backwards for the closest event. Because we find
// event9 as the closest, which is from the same day as event14 (page1
// rangeEnd), we round up to the nearest hour so that the URL encompasses it
// when looking backwards.
testName :
'can jump backward from the middle of one day with too many messages into the previous day with too many messages' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14
[ day1 ] [ day2 ] [ day3 ]
[ page1 ]
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/03T06:00' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03T06:00' ,
2023-04-05 03:25:31 -06:00
action : 'previous' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03T01:00?at=$event8' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
{
// From the first page with too many messages, starting at event8 (page1
// rangeStart), we look backwards for the closest event. Because we find
// event7 as the closest, which is from a different day than event12 (page1
// rangeEnd), we can just display the day where event7 resides.
//
testName :
'can jump backward from the start of one day with too many messages into the previous day with exactly the limit' ,
2023-04-07 11:52:41 -06:00
roomDayMessageStructureString : `
[ room1 ]
1 < -- 2 < -- 3 < -- 4 < -- 5 < -- 6 < -- 7 < -- 8 < -- 9 < -- 10 < -- 11 < -- 12 < -- 13 < -- 14
[ day1 ] [ day2 ] [ day3 ]
[ page1 ]
[ page2 ]
` ,
startUrl : '/r/room1/date/2022/01/03T05:00' ,
2023-04-05 03:25:31 -06:00
page1 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/03T05:00' ,
2023-04-05 03:25:31 -06:00
action : 'previous' ,
} ,
page2 : {
2023-04-07 11:52:41 -06:00
url : '/r/room1/date/2022/01/02?at=$event7' ,
2023-04-05 03:25:31 -06:00
action : null ,
} ,
} ,
] ;
2022-11-02 03:27:30 -06:00
2023-04-05 03:25:31 -06:00
jumpTestCases . forEach ( ( testCase ) => {
2023-04-07 11:52:41 -06:00
// eslint-disable-next-line max-statements, complexity
2023-04-05 03:25:31 -06:00
it ( testCase . testName , async ( ) => {
// Setup
// --------------------------------------
// --------------------------------------
2023-04-07 11:52:41 -06:00
const eventMap = new Map ( ) ;
const fancyIdentifierToEventIdMap = new Map ( ) ;
const eventIdToFancyIdentifierMap = new Map ( ) ;
2023-04-05 03:25:31 -06:00
function convertFancyIdentifierListToDebugEventIds ( fancyEventIdentifiers ) {
// eslint-disable-next-line max-nested-callbacks
return fancyEventIdentifiers . map ( ( fancyId ) => {
2023-04-07 11:52:41 -06:00
const eventId = fancyIdentifierToEventIdMap . get ( fancyId ) ;
2023-04-05 03:25:31 -06:00
if ( ! eventId ) {
throw new Error (
` Unable to find ${ fancyId } in the fancyIdentifierToEventMap= ${ JSON . stringify (
2023-04-07 11:52:41 -06:00
Object . fromEntries ( fancyIdentifierToEventIdMap . entries ( ) ) ,
2023-04-05 03:25:31 -06:00
null ,
2
) } `
) ;
}
2023-04-07 11:52:41 -06:00
const ts = eventMap . get ( eventId ) ? . originServerTs ;
2023-04-05 03:25:31 -06:00
const tsDebugString = ts && ` ${ new Date ( ts ) . toISOString ( ) } ( ${ ts } ) ` ;
return ` ${ eventId } ( ${ fancyId } ) - ${ tsDebugString } ` ;
} ) ;
}
function convertEventIdsToDebugEventIds ( eventIds ) {
// eslint-disable-next-line max-nested-callbacks
return eventIds . map ( ( eventId ) => {
2023-04-07 11:52:41 -06:00
const fancyEventId = eventIdToFancyIdentifierMap . get ( eventId ) ;
if ( ! fancyEventId ) {
2023-04-05 03:25:31 -06:00
throw new Error (
2023-04-07 11:52:41 -06:00
` Unable to find ${ eventId } in the eventIdToFancyIdentifierMap= ${ JSON . stringify (
Object . fromEntries ( eventIdToFancyIdentifierMap . entries ( ) ) ,
2023-04-05 03:25:31 -06:00
null ,
2
) } `
) ;
}
2023-04-07 11:52:41 -06:00
const ts = eventMap . get ( eventId ) ? . originServerTs ;
2023-04-05 03:25:31 -06:00
const tsDebugString = ts && ` ${ new Date ( ts ) . toISOString ( ) } ( ${ ts } ) ` ;
2023-04-07 11:52:41 -06:00
return ` ${ eventId } ( ${ fancyEventId } ) - ${ tsDebugString } ` ;
2023-04-05 03:25:31 -06:00
} ) ;
}
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
2023-04-07 11:52:41 -06:00
const { rooms , archiveMessageLimit , pages } = parseRoomDayMessageStructure (
testCase . roomDayMessageStructureString
) ;
const fancyIdentifierToRoomIdMap = new Map ( ) ;
const roomIdToFancyIdentifierMap = new Map ( ) ;
for ( const [ roomIndex , room ] of rooms . entries ( ) ) {
// TODO: upgradeRoom to link one room to another
const roomId = await createTestRoom ( client ) ;
const fancyRoomId = ` #room ${ roomIndex + 1 } ` ;
fancyIdentifierToRoomIdMap . set ( fancyRoomId , roomId ) ;
roomIdToFancyIdentifierMap . set ( roomId , fancyRoomId ) ;
// Join the archive user to the room before we create the test messages to
// avoid problems jumping to the latest activity since we can't control the
// timestamp of the membership event.
const archiveAppServiceUserClient = await getTestClientForAs ( ) ;
await joinRoom ( {
client : archiveAppServiceUserClient ,
roomId : roomId ,
} ) ;
2023-04-05 03:25:31 -06:00
2023-04-07 11:52:41 -06:00
// Just spread things out a bit so the event times are more obvious
// and stand out from each other while debugging and so we just have
// to deal with hour time slicing
const eventSendTimeIncrement = ONE _HOUR _IN _MS ;
for ( const eventMeta of room . events ) {
const archiveDate = new Date ( Date . UTC ( 2022 , 0 , eventMeta . dayNumber , 0 , 0 , 0 , 1 ) ) ;
const originServerTs =
archiveDate . getTime ( ) + eventMeta . eventIndexInDay * eventSendTimeIncrement ;
const content = {
msgtype : 'm.text' ,
body : ` event ${ eventMeta . eventNumber } - day ${ eventMeta . dayNumber } . ${ eventMeta . eventIndexInDay } ` ,
} ;
const eventId = await sendMessage ( {
2023-04-05 03:25:31 -06:00
client ,
roomId ,
2023-04-07 11:52:41 -06:00
content ,
// Technically, we don't have to set the timestamp to be unique or sequential but
// it still seems like a good idea to make the tests more clear.
timestamp : originServerTs ,
2023-04-05 03:25:31 -06:00
} ) ;
2023-04-07 11:52:41 -06:00
eventMap . set ( eventId , {
roomId ,
originServerTs ,
content ,
} ) ;
const fancyEventId = ` $ event ${ eventMeta . eventNumber } ` ;
fancyIdentifierToEventIdMap . set ( fancyEventId , eventId ) ;
eventIdToFancyIdentifierMap . set ( eventId , fancyEventId ) ;
}
2023-04-05 03:25:31 -06:00
}
// Now Test
// --------------------------------------
// --------------------------------------
// Make sure the archive is configured as the test expects
2023-04-07 11:52:41 -06:00
config . set ( 'archiveMessageLimit' , archiveMessageLimit ) ;
2023-04-05 03:25:31 -06:00
// eslint-disable-next-line max-nested-callbacks
const pagesKeyList = Object . keys ( testCase ) . filter ( ( key ) => {
const isPageKey = key . startsWith ( 'page' ) ;
if ( isPageKey ) {
assert . match ( key , /page\d+/ ) ;
return true ;
}
2022-11-03 04:06:53 -06:00
2023-04-05 03:25:31 -06:00
return false ;
2022-11-02 03:27:30 -06:00
} ) ;
2023-04-05 03:25:31 -06:00
assert (
pagesKeyList . length > 0 ,
'You must have at least one `pageX` of expectations in your jump test case'
) ;
// Make sure the page are in order
// eslint-disable-next-line max-nested-callbacks
pagesKeyList . reduce ( ( prevPageCount , currentPageKey ) => {
const pageNumber = parseInt ( currentPageKey . match ( /\d+$/ ) [ 0 ] , 10 ) ;
assert (
prevPageCount + 1 === pageNumber ,
` Page numbers must be sorted in each test case but found ` +
` ${ pageNumber } after ${ prevPageCount } - pagesList= ${ pagesKeyList } `
) ;
return pageNumber ;
} , 0 ) ;
// Get the URL for the first page to fetch
//
// Set the `archiveUrl` for debugging if the test fails here
2023-04-07 11:52:41 -06:00
const { roomIdOrAlias : startRoomFancyKey , urlDateTime : startUrlDateTime } =
parseArchiveUrlForRoom ( urlJoin ( 'https://example.com' , testCase . startUrl ) ) ;
const startRoomIdOrAlias = fancyIdentifierToRoomIdMap . get ( startRoomFancyKey ) ;
2023-04-05 03:25:31 -06:00
assert (
2023-04-07 11:52:41 -06:00
startRoomIdOrAlias ,
` Could not find room ID for ${ startRoomFancyKey } in our list of known rooms ${ JSON . stringify (
Object . fromEntries ( fancyIdentifierToRoomIdMap . entries ( ) )
2023-04-05 03:25:31 -06:00
) } `
) ;
archiveUrl = ` ${ matrixPublicArchiveURLCreator . archiveUrlForRoom (
2023-04-07 11:52:41 -06:00
startRoomIdOrAlias
) } / date / $ { startUrlDateTime } ` ;
2023-04-05 03:25:31 -06:00
// Loop through all of the pages of the test and ensure expectations
let alreadyEncounteredLastPage = false ;
for ( const pageKey of pagesKeyList ) {
try {
if ( alreadyEncounteredLastPage ) {
assert . fail (
'We should not see any more pages after we already saw a page without an action ' +
` which signals the end of expecations. Encountered ${ pageKey } in ${ pagesKeyList } ` +
'after we already thought we were done'
) ;
}
const pageTestMeta = testCase [ pageKey ] ;
2023-04-07 11:52:41 -06:00
const {
roomIdOrAlias : expectedRoomFancyId ,
//urlDateTime: expectedUrlDateTime,
continueAtEvent : expectedContinueAtEvent ,
} = parseArchiveUrlForRoom ( urlJoin ( 'https://example.com' , pageTestMeta . url ) ) ;
const expectedRoomId = fancyIdentifierToRoomIdMap . get ( expectedRoomFancyId ) ;
assert (
expectedRoomId ,
` Could not find room ID for ${ expectedRoomFancyId } in our list of known rooms ${ JSON . stringify (
Object . fromEntries ( fancyIdentifierToRoomIdMap . entries ( ) )
) } `
) ;
2023-04-05 03:25:31 -06:00
// Fetch the given page.
const { data : archivePageHtml , res : pageRes } = await fetchEndpointAsText (
archiveUrl
) ;
const pageDom = parseHTML ( archivePageHtml ) ;
2023-04-07 11:52:41 -06:00
const {
roomIdOrAliasUrlPart : actualRoomIdOrAliasUrlPart ,
roomIdOrAlias : actualRoomId ,
//urlDateTime: actualUrlDateTime,
continueAtEvent : actualContinueAtEventId ,
} = parseArchiveUrlForRoom ( pageRes . url ) ;
const actualRoomFancyId = roomIdToFancyIdentifierMap . get ( actualRoomId ) ;
assert (
actualRoomFancyId ,
` Could not find room ID for ${ actualRoomId } in our list of known rooms ${ JSON . stringify (
Object . fromEntries ( roomIdToFancyIdentifierMap . entries ( ) )
) } `
) ;
let actualContinueAtEventFancyId ;
if ( actualContinueAtEventId ) {
actualContinueAtEventFancyId =
eventIdToFancyIdentifierMap . get ( actualContinueAtEventId ) ;
assert (
actualContinueAtEventFancyId ,
` Could not find event ID for ${ actualContinueAtEventId } in our list of known events ${ JSON . stringify (
Object . fromEntries ( eventIdToFancyIdentifierMap . entries ( ) )
) } `
) ;
}
2023-04-05 03:25:31 -06:00
2023-04-07 11:52:41 -06:00
// Replace messy room ID's and event ID's that change with every test
// run with their fancy ID's which correlate with the test meta so it's
// easier to reason about things when the assertion fails.
let actualUrlWithFancyIdentifies = pageRes . url
. replace (
` /roomid/ ${ actualRoomIdOrAliasUrlPart } ` ,
// Slice to remove the sigil
` /r/ ${ actualRoomFancyId . slice ( 1 ) } `
)
. replace ( actualContinueAtEventId , actualContinueAtEventFancyId ) ;
// Assert the correct room and time precision in the URL
assert . match (
actualUrlWithFancyIdentifies ,
new RegExp ( ` ${ escapeStringRegexp ( pageTestMeta . url ) } $ ` )
) ;
2023-04-05 03:25:31 -06:00
// If provided, assert that it's a smooth continuation to more messages.
// First by checking where the scroll is going to start from
2023-04-07 11:52:41 -06:00
if ( expectedContinueAtEvent ) {
2023-04-05 03:25:31 -06:00
const [ expectedContinuationDebugEventId ] =
2023-04-07 11:52:41 -06:00
convertFancyIdentifierListToDebugEventIds ( [ expectedContinueAtEvent ] ) ;
2023-04-05 03:25:31 -06:00
const urlObj = new URL ( pageRes . url , basePath ) ;
const qs = new URLSearchParams ( urlObj . search ) ;
const continuationEventId = qs . get ( 'at' ) ;
if ( ! continuationEventId ) {
throw new Error (
` Expected ?at= $ xxx query parameter to be defined in the URL= ${ pageRes . url } but it was ${ continuationEventId } . We expect it to match ${ expectedContinuationDebugEventId } `
) ;
}
const [ continationDebugEventId ] = convertEventIdsToDebugEventIds ( [
continuationEventId ,
] ) ;
assert . strictEqual ( continationDebugEventId , expectedContinuationDebugEventId ) ;
}
const eventIdsOnPage = [ ... pageDom . document . querySelectorAll ( ` [data-event-id] ` ) ]
// eslint-disable-next-line max-nested-callbacks
. map ( ( eventEl ) => {
return eventEl . getAttribute ( 'data-event-id' ) ;
} ) ;
2023-04-07 11:52:41 -06:00
const pageNumber = pageKey . replace ( 'page' , '' ) ;
const page = pages [ pageNumber - 1 ] ;
const expectedEventsOnPage = page . events ;
const expectedFancyIdsOnPage = expectedEventsOnPage . map (
// eslint-disable-next-line max-nested-callbacks
( event ) => ` $ event ${ event . eventNumber } `
) ;
2023-04-05 03:25:31 -06:00
// Assert that the page contains all expected events
assert . deepEqual (
convertEventIdsToDebugEventIds ( eventIdsOnPage ) ,
2023-04-07 11:52:41 -06:00
convertFancyIdentifierListToDebugEventIds ( expectedFancyIdsOnPage ) ,
2023-04-05 03:25:31 -06:00
` Events on ${ pageKey } should be as expected `
) ;
// Follow the next activity link. Aka, fetch messages for the 2nd page
let actionLinkSelector ;
if ( pageTestMeta . action === 'next' ) {
actionLinkSelector = '[data-testid="jump-to-next-activity-link"]' ;
} else if ( pageTestMeta . action === 'previous' ) {
actionLinkSelector = '[data-testid="jump-to-previous-activity-link"]' ;
} else if ( pageTestMeta . action === null ) {
// No more pages to test ✅, move on
alreadyEncounteredLastPage = true ;
continue ;
} else {
throw new Error (
` Unexpected value for ${ pageKey } .action= ${ pageTestMeta . action } that we don't know what to do with `
) ;
}
const jumpToActivityLinkEl = pageDom . document . querySelector ( actionLinkSelector ) ;
const jumpToActivityLinkHref = jumpToActivityLinkEl . getAttribute ( 'href' ) ;
// Move to the next iteration of the loop
//
// Set this for debugging if the test fails here
archiveUrl = jumpToActivityLinkHref ;
} catch ( err ) {
const errorWithContext = new RethrownError (
` Encountered error while asserting ${ pageKey } : ${ err . message } ` ,
err
) ;
// Copy these over so mocha generates a nice diff for us
if ( err instanceof assert . AssertionError ) {
errorWithContext . actual = err . actual ;
errorWithContext . expected = err . expected ;
}
throw errorWithContext ;
}
}
} ) ;
} ) ;
2022-11-02 03:27:30 -06:00
2023-04-05 03:25:31 -06:00
const jumpPrecisionTestCases = [
{
durationMinLabel : 'day' ,
durationMinMs : ONE _DAY _IN _MS ,
durationMaxLabel : 'multiple days' ,
durationMaxMs : 5 * ONE _DAY _IN _MS ,
expectedTimePrecision : TIME _PRECISION _VALUES . none ,
} ,
{
durationMinLabel : 'hour' ,
durationMinMs : ONE _HOUR _IN _MS ,
durationMaxLabel : 'day' ,
durationMaxMs : ONE _DAY _IN _MS ,
expectedTimePrecision : TIME _PRECISION _VALUES . minutes ,
} ,
{
durationMinLabel : 'minute' ,
durationMinMs : ONE _MINUTE _IN _MS ,
durationMaxLabel : 'hour' ,
durationMaxMs : ONE _HOUR _IN _MS ,
expectedTimePrecision : TIME _PRECISION _VALUES . minutes ,
} ,
{
durationMinLabel : 'second' ,
durationMinMs : ONE _SECOND _IN _MS ,
durationMaxLabel : 'minute' ,
durationMaxMs : ONE _MINUTE _IN _MS ,
expectedTimePrecision : TIME _PRECISION _VALUES . seconds ,
} ,
// This one is expected to fail but we could support it (#support-ms-time-slice)
// {
// durationMinLabel: 'millisecond',
// durationMinMs: 1,
// durationMaxLabel: 'second',
// durationMaxMs: ONE_SECOND_IN_MS,
// // #support-ms-time-slice
// expectedTimePrecision: TIME_PRECISION_VALUES.milliseconds,
// },
] ;
[
{
directionLabel : 'backward' ,
directionValue : DIRECTION . backward ,
} ,
{
directionLabel : 'forward' ,
directionValue : DIRECTION . forward ,
} ,
] . forEach ( ( { directionLabel , directionValue } ) => {
describe ( ` /jump redirects to \` /date/... \` URL that encompasses closest event when looking ${ directionLabel } ` , ( ) => {
// eslint-disable-next-line max-nested-callbacks
jumpPrecisionTestCases . forEach ( ( testCase ) => {
let testTitle ;
if ( directionValue === DIRECTION . backward ) {
testTitle = ` will jump to the nearest ${ testCase . durationMinLabel } rounded up when the closest event is from the same ${ testCase . durationMaxLabel } (but further than a ${ testCase . durationMinLabel } away) as our currently displayed range of events ` ;
} else if ( directionValue === DIRECTION . forward ) {
testTitle = ` will jump to the nearest ${ testCase . durationMinLabel } rounded down when the last event in the next range is more than a ${ testCase . durationMinLabel } away from our currently displayed range of events ` ;
}
// eslint-disable-next-line max-nested-callbacks
it ( testTitle , async ( ) => {
// The date should be just past midnight so we don't run into inclusive
// bounds leaking messages from one day into another.
const archiveDate = new Date ( Date . UTC ( 2022 , 0 , 1 , 0 , 0 , 0 , 1 ) ) ;
config . set ( 'archiveMessageLimit' , 3 ) ;
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const roomId = await createTestRoom ( client ) ;
const { eventIds , eventMap } = await createMessagesInRoom ( {
client ,
roomId ,
// Make sure there is enough space before and after the selected range
// for another page of history
numMessages : 10 ,
prefix : ` foo ` ,
timestamp : archiveDate . getTime ( ) ,
increment : testCase . durationMinMs ,
} ) ;
const fourthEvent = eventMap . get ( eventIds [ 3 ] ) ;
const sixthEvent = eventMap . get ( eventIds [ 5 ] ) ;
const jumpUrl = ` ${ matrixPublicArchiveURLCreator . archiveJumpUrlForRoom ( roomId , {
dir : directionValue ,
currentRangeStartTs : fourthEvent . originServerTs ,
currentRangeEndTs : sixthEvent . originServerTs ,
} ) } ` ;
// Fetch the given page.
const { res } = await fetchEndpointAsText ( jumpUrl ) ;
// Assert the correct time precision in the URL
assertExpectedTimePrecisionAgainstUrl ( testCase . expectedTimePrecision , res . url ) ;
} ) ;
} ) ;
} ) ;
2022-11-02 03:27:30 -06:00
} ) ;
} ) ;
2022-09-20 15:02:09 -06:00
} ) ;
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 ( ) ;
2022-11-03 03:12:00 -06:00
const { data : roomDirectoryPageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
2022-09-15 19:41:55 -06:00
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 ,
} ) ;
2022-11-03 03:12:00 -06:00
const { data : roomDirectoryWithSearchPageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
2022-09-15 19:41:55 -06:00
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 ,
} ) ;
2022-11-03 03:12:00 -06:00
const { data : roomDirectoryWithSearchPageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
2022-10-20 01:06:43 -06:00
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 (
2022-11-02 03:27:30 -06:00
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'
)
2022-09-08 18:15:07 -06:00
) ;
} catch ( err ) {
2022-11-02 03:27:30 -06:00
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 } `
) ;
2022-09-08 18:15:07 -06:00
}
} ) ;
it ( 'search engines allowed to index `world_readable` room' , async ( ) => {
const client = await getTestClientForHs ( testMatrixServerUrl1 ) ;
const roomId = await createTestRoom ( client ) ;
archiveUrl = matrixPublicArchiveURLCreator . archiveUrlForRoom ( roomId ) ;
2022-11-03 03:12:00 -06:00
const { data : archivePageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
2022-09-08 18:15:07 -06:00
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 ) ;
2022-11-03 03:12:00 -06:00
const { data : archivePageHtml } = await fetchEndpointAsText ( archiveUrl ) ;
2022-09-08 18:15:07 -06:00
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 ;
2022-11-02 03:27:30 -06:00
// We have to use this sometimes over `fetch` because `fetch` does not allow us to
// manually follow redirects and get the resultant URL, see
2022-10-27 00:09:13 -06:00
// 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
} ) ;