Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 9a733a6

Browse files
author
Kerry
authored
Apply strictNullChecks to src/utils/exportUtils (#10379)
* Apply `strictNullChecks` to `src/utils/exportUtils` * strict fix * test coverage * lint * test coverage * one more test
1 parent 1447829 commit 9a733a6

File tree

3 files changed

+178
-9
lines changed

3 files changed

+178
-9
lines changed

src/utils/DecryptFile.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ export class DecryptError extends Error {
4747
* @param {IMediaEventInfo} info The info parameter taken from the matrix event.
4848
* @returns {Promise<Blob>} Resolves to a Blob of the file.
4949
*/
50-
export async function decryptFile(file: IEncryptedFile, info?: IMediaEventInfo): Promise<Blob> {
50+
export async function decryptFile(file?: IEncryptedFile, info?: IMediaEventInfo): Promise<Blob> {
51+
// throws if file is falsy
5152
const media = mediaFromContent({ file });
5253

5354
let responseData: ArrayBuffer;
@@ -64,7 +65,7 @@ export async function decryptFile(file: IEncryptedFile, info?: IMediaEventInfo):
6465

6566
try {
6667
// Decrypt the array buffer using the information taken from the event content.
67-
const dataArray = await encrypt.decryptAttachment(responseData, file);
68+
const dataArray = await encrypt.decryptAttachment(responseData, file!);
6869
// Turn the array into a Blob and give it the correct MIME-type.
6970

7071
// IMPORTANT: we must not allow scriptable mime-types into Blobs otherwise

src/utils/exportUtils/Exporter.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export default abstract class Exporter {
5151
if (
5252
exportOptions.maxSize < 1 * 1024 * 1024 || // Less than 1 MB
5353
exportOptions.maxSize > 8000 * 1024 * 1024 || // More than 8 GB
54-
exportOptions.numberOfMessages > 10 ** 8
54+
(!!exportOptions.numberOfMessages && exportOptions.numberOfMessages > 10 ** 8) ||
55+
(exportType === ExportType.LastNMessages && !exportOptions.numberOfMessages)
5556
) {
5657
throw new Error("Invalid export options");
5758
}
@@ -123,10 +124,11 @@ export default abstract class Exporter {
123124
}
124125

125126
protected setEventMetadata(event: MatrixEvent): MatrixEvent {
126-
const roomState = this.client.getRoom(this.room.roomId).currentState;
127-
event.sender = roomState.getSentinelMember(event.getSender());
127+
const roomState = this.client.getRoom(this.room.roomId)?.currentState;
128+
const sender = event.getSender();
129+
event.sender = (!!sender && roomState?.getSentinelMember(sender)) || null;
128130
if (event.getType() === "m.room.member") {
129-
event.target = roomState.getSentinelMember(event.getStateKey());
131+
event.target = roomState?.getSentinelMember(event.getStateKey()!) ?? null;
130132
}
131133
return event;
132134
}
@@ -135,6 +137,8 @@ export default abstract class Exporter {
135137
let limit: number;
136138
switch (this.exportType) {
137139
case ExportType.LastNMessages:
140+
// validated in constructor that numberOfMessages is defined
141+
// when export type is LastNMessages
138142
limit = this.exportOptions.numberOfMessages!;
139143
break;
140144
case ExportType.Timeline:
@@ -221,8 +225,14 @@ export default abstract class Exporter {
221225
return events;
222226
}
223227

224-
protected async getMediaBlob(event: MatrixEvent): Promise<Blob | undefined> {
225-
let blob: Blob | undefined;
228+
/**
229+
* Decrypts if necessary, and fetches media from a matrix event
230+
* @param event - matrix event with media event content
231+
* @resolves when media has been fetched
232+
* @throws if media was unable to be fetched
233+
*/
234+
protected async getMediaBlob(event: MatrixEvent): Promise<Blob> {
235+
let blob: Blob | undefined = undefined;
226236
try {
227237
const isEncrypted = event.isEncrypted();
228238
const content = event.getContent<IMediaEventContent>();
@@ -231,12 +241,18 @@ export default abstract class Exporter {
231241
blob = await decryptFile(content.file);
232242
} else {
233243
const media = mediaFromContent(content);
244+
if (!media.srcHttp) {
245+
throw new Error("Cannot fetch without srcHttp");
246+
}
234247
const image = await fetch(media.srcHttp);
235248
blob = await image.blob();
236249
}
237250
} catch (err) {
238251
logger.log("Error decrypting media");
239252
}
253+
if (!blob) {
254+
throw new Error("Unable to fetch file");
255+
}
240256
return blob;
241257
}
242258

test/utils/exportUtils/HTMLExport-test.ts

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,16 @@ See the License for the specific language governing permissions and
1414
limitations 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";
1827
import fetchMock from "fetch-mock-jest";
1928

2029
import { 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+
5477
describe("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

Comments
 (0)