@@ -14,7 +14,16 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- import { EventType , IRoomEvent , MatrixClient , MatrixEvent , MsgType , Room , RoomMember } from "matrix-js-sdk/src/matrix" ;
17+ import {
18+ EventType ,
19+ IRoomEvent ,
20+ MatrixClient ,
21+ MatrixEvent ,
22+ MsgType ,
23+ Room ,
24+ RoomMember ,
25+ RoomState ,
26+ } from "matrix-js-sdk/src/matrix" ;
1827import fetchMock from "fetch-mock-jest" ;
1928
2029import { filterConsole , mkStubRoom , REPEATABLE_DATE , stubClient } from "../../test-utils" ;
@@ -51,6 +60,20 @@ const EVENT_ATTACHMENT: IRoomEvent = {
5160 } ,
5261} ;
5362
63+ const EVENT_ATTACHMENT_MALFORMED : IRoomEvent = {
64+ event_id : "$2" ,
65+ type : EventType . RoomMessage ,
66+ sender : "@alice:example.com" ,
67+ origin_server_ts : 1 ,
68+ content : {
69+ msgtype : MsgType . File ,
70+ body : "hello.txt" ,
71+ file : {
72+ url : undefined ,
73+ } ,
74+ } ,
75+ } ;
76+
5477describe ( "HTMLExport" , ( ) => {
5578 let client : jest . Mocked < MatrixClient > ;
5679 let room : Room ;
@@ -107,6 +130,22 @@ describe("HTMLExport", () => {
107130 fetchMock . get ( media . srcHttp ! , body ) ;
108131 }
109132
133+ it ( "should throw when created with invalid config for LastNMessages" , async ( ) => {
134+ expect (
135+ ( ) =>
136+ new HTMLExporter (
137+ room ,
138+ ExportType . LastNMessages ,
139+ {
140+ attachmentsIncluded : false ,
141+ maxSize : 1_024 * 1_024 ,
142+ numberOfMessages : undefined ,
143+ } ,
144+ ( ) => { } ,
145+ ) ,
146+ ) . toThrow ( "Invalid export options" ) ;
147+ } ) ;
148+
110149 it ( "should have an SDK-branded destination file name" , ( ) => {
111150 const roomName = "My / Test / Room: Welcome" ;
112151 const stubOptions : IExportOptions = {
@@ -266,6 +305,56 @@ describe("HTMLExport", () => {
266305 expect ( await file . text ( ) ) . toBe ( avatarContent ) ;
267306 } ) ;
268307
308+ it ( "should handle when an event has no sender" , async ( ) => {
309+ const EVENT_MESSAGE_NO_SENDER : IRoomEvent = {
310+ event_id : "$1" ,
311+ type : EventType . RoomMessage ,
312+ sender : "" ,
313+ origin_server_ts : 0 ,
314+ content : {
315+ msgtype : "m.text" ,
316+ body : "Message with no sender" ,
317+ } ,
318+ } ;
319+ mockMessages ( EVENT_MESSAGE_NO_SENDER ) ;
320+
321+ const exporter = new HTMLExporter (
322+ room ,
323+ ExportType . Timeline ,
324+ {
325+ attachmentsIncluded : false ,
326+ maxSize : 1_024 * 1_024 ,
327+ } ,
328+ ( ) => { } ,
329+ ) ;
330+
331+ await exporter . export ( ) ;
332+
333+ const file = getMessageFile ( exporter ) ;
334+ expect ( await file . text ( ) ) . toContain ( EVENT_MESSAGE_NO_SENDER . content . body ) ;
335+ } ) ;
336+
337+ it ( "should handle when events sender cannot be found in room state" , async ( ) => {
338+ mockMessages ( EVENT_MESSAGE ) ;
339+
340+ jest . spyOn ( RoomState . prototype , "getSentinelMember" ) . mockReturnValue ( null ) ;
341+
342+ const exporter = new HTMLExporter (
343+ room ,
344+ ExportType . Timeline ,
345+ {
346+ attachmentsIncluded : false ,
347+ maxSize : 1_024 * 1_024 ,
348+ } ,
349+ ( ) => { } ,
350+ ) ;
351+
352+ await exporter . export ( ) ;
353+
354+ const file = getMessageFile ( exporter ) ;
355+ expect ( await file . text ( ) ) . toContain ( EVENT_MESSAGE . content . body ) ;
356+ } ) ;
357+
269358 it ( "should include attachments" , async ( ) => {
270359 mockMessages ( EVENT_MESSAGE , EVENT_ATTACHMENT ) ;
271360 const attachmentBody = "Lorem ipsum dolor sit amet" ;
@@ -294,6 +383,68 @@ describe("HTMLExport", () => {
294383 expect ( text ) . toBe ( attachmentBody ) ;
295384 } ) ;
296385
386+ it ( "should handle when attachment cannot be fetched" , async ( ) => {
387+ mockMessages ( EVENT_MESSAGE , EVENT_ATTACHMENT_MALFORMED , EVENT_ATTACHMENT ) ;
388+ const attachmentBody = "Lorem ipsum dolor sit amet" ;
389+
390+ mockMxc ( "mxc://example.org/test-id" , attachmentBody ) ;
391+
392+ const exporter = new HTMLExporter (
393+ room ,
394+ ExportType . Timeline ,
395+ {
396+ attachmentsIncluded : true ,
397+ maxSize : 1_024 * 1_024 ,
398+ } ,
399+ ( ) => { } ,
400+ ) ;
401+
402+ await exporter . export ( ) ;
403+
404+ // good attachment present
405+ const files = getFiles ( exporter ) ;
406+ const file = files [ Object . keys ( files ) . find ( ( k ) => k . endsWith ( ".txt" ) ) ! ] ;
407+ expect ( file ) . not . toBeUndefined ( ) ;
408+
409+ // Ensure that the attachment has the expected content
410+ const text = await file . text ( ) ;
411+ expect ( text ) . toBe ( attachmentBody ) ;
412+
413+ // messages export still successful
414+ const messagesFile = getMessageFile ( exporter ) ;
415+ expect ( await messagesFile . text ( ) ) . toBeTruthy ( ) ;
416+ } ) ;
417+
418+ it ( "should handle when attachment srcHttp is falsy" , async ( ) => {
419+ mockMessages ( EVENT_MESSAGE , EVENT_ATTACHMENT ) ;
420+ const attachmentBody = "Lorem ipsum dolor sit amet" ;
421+
422+ mockMxc ( "mxc://example.org/test-id" , attachmentBody ) ;
423+
424+ jest . spyOn ( client , "mxcUrlToHttp" ) . mockReturnValue ( null ) ;
425+
426+ const exporter = new HTMLExporter (
427+ room ,
428+ ExportType . Timeline ,
429+ {
430+ attachmentsIncluded : true ,
431+ maxSize : 1_024 * 1_024 ,
432+ } ,
433+ ( ) => { } ,
434+ ) ;
435+
436+ await exporter . export ( ) ;
437+
438+ // attachment not present
439+ const files = getFiles ( exporter ) ;
440+ const file = files [ Object . keys ( files ) . find ( ( k ) => k . endsWith ( ".txt" ) ) ! ] ;
441+ expect ( file ) . toBeUndefined ( ) ;
442+
443+ // messages export still successful
444+ const messagesFile = getMessageFile ( exporter ) ;
445+ expect ( await messagesFile . text ( ) ) . toBeTruthy ( ) ;
446+ } ) ;
447+
297448 it ( "should omit attachments" , async ( ) => {
298449 mockMessages ( EVENT_MESSAGE , EVENT_ATTACHMENT ) ;
299450
@@ -323,6 +474,7 @@ describe("HTMLExport", () => {
323474 {
324475 attachmentsIncluded : false ,
325476 maxSize : 1_024 * 1_024 ,
477+ numberOfMessages : 5000 ,
326478 } ,
327479 ( ) => { } ,
328480 ) ;
0 commit comments