Improve test coverage

This commit is contained in:
Cadence Ember 2024-02-02 15:55:02 +13:00
parent 69922c4a14
commit c7fb6fd52e
10 changed files with 374 additions and 33 deletions

View file

@ -39,6 +39,7 @@ async function runSingleTest(t, url, totalSize) {
res.body.emit("end")
})
t.equal(result.subarray(1, 4).toString("ascii"), "PNG", `result was not a PNG file: ${result.toString("base64")}`)
/* c8 ignore next 5 */
if (meter.bytes < totalSize / 4) { // should download less than 25% of each file
t.pass("intentionally read partial file")
} else {

View file

@ -5,6 +5,7 @@ const DiscordTypes = require("discord-api-types/v10")
const {Readable} = require("stream")
const chunk = require("chunk-text")
const TurndownService = require("turndown")
const domino = require("domino")
const assert = require("assert").strict
const entities = require("entities")
@ -38,7 +39,7 @@ const turndownService = new TurndownService({
hr: "----",
headingStyle: "atx",
preformattedCode: true,
codeBlockStyle: "fenced",
codeBlockStyle: "fenced"
})
/**
@ -339,6 +340,33 @@ async function handleRoomOrMessageLinks(input, di) {
return input
}
/**
* @param {string} content
* @param {DiscordTypes.APIGuild} guild
* @param {{api: import("../../matrix/api"), snow: import("snowtransfer").SnowTransfer, fetch: import("node-fetch")["default"]}} di
*/
async function checkWrittenMentions(content, guild, di) {
let writtenMentionMatch = content.match(/(?:^|[^"[<>/A-Za-z0-9])@([A-Za-z][A-Za-z0-9._\[\]\(\)-]+):?/d) // /d flag for indices requires node.js 16+
if (writtenMentionMatch) {
const results = await di.snow.guild.searchGuildMembers(guild.id, {query: writtenMentionMatch[1]})
if (results[0]) {
assert(results[0].user)
return {
// @ts-ignore - typescript doesn't know about indices yet
content: content.slice(0, writtenMentionMatch.indices[1][0]-1) + `<@${results[0].user.id}>` + content.slice(writtenMentionMatch.indices[1][1]),
ensureJoined: results[0].user
}
}
}
}
const attachmentEmojis = new Map([
["m.image", "🖼️"],
["m.video", "🎞️"],
["m.audio", "🎶"],
["m.file", "📄"]
])
/**
* @param {Ty.Event.Outer_M_Room_Message | Ty.Event.Outer_M_Room_Message_File | Ty.Event.Outer_M_Sticker | Ty.Event.Outer_M_Room_Message_Encrypted_File} event
* @param {import("discord-api-types/v10").APIGuild} guild
@ -380,12 +408,10 @@ async function eventToMessage(event, guild, di) {
// Handling edits. If the edit was an edit of a reply, edits do not include the reply reference, so we need to fetch up to 2 more events.
// this event ---is an edit of--> original event ---is a reply to--> past event
await (async () => {
if (!event.content["m.new_content"]) return
// Check if there is an edit
const relatesTo = event.content["m.relates_to"]
if (!relatesTo) return
if (!event.content["m.new_content"] || !relatesTo || relatesTo.rel_type !== "m.replace") return
// Check if we have a pointer to what was edited
const relType = relatesTo.rel_type
if (relType !== "m.replace") return
const originalEventId = relatesTo.event_id
if (!originalEventId) return
messageIDsToEdit = select("event_message", "message_id", {event_id: originalEventId}, "ORDER BY part").pluck().all()
@ -480,12 +506,7 @@ async function eventToMessage(event, guild, di) {
repliedToEvent.content = repliedToEvent.content["m.new_content"]
}
let contentPreview
const fileReplyContentAlternative =
( repliedToEvent.content.msgtype === "m.image" ? "🖼️"
: repliedToEvent.content.msgtype === "m.video" ? "🎞️"
: repliedToEvent.content.msgtype === "m.audio" ? "🎶"
: repliedToEvent.content.msgtype === "m.file" ? "📄"
: null)
const fileReplyContentAlternative = attachmentEmojis.get(repliedToEvent.content.msgtype)
if (fileReplyContentAlternative) {
contentPreview = " " + fileReplyContentAlternative
} else {
@ -574,8 +595,35 @@ async function eventToMessage(event, guild, di) {
last = match.index
}
// Handling written @mentions: we need to look for candidate Discord members to join to the room
// This shouldn't apply to code blocks, links, or inside attributes. So editing the HTML tree instead of regular expressions is a sensible choice here.
// We're using the domino parser because Turndown uses the same and can reuse this tree.
const doc = domino.createDocument(
// DOM parsers arrange elements in the <head> and <body>. Wrapping in a custom element ensures elements are reliably arranged in a single element.
'<x-turndown id="turndown-root">' + input + '</x-turndown>'
);
const root = doc.getElementById("turndown-root");
async function forEachNode(node) {
for (; node; node = node.nextSibling) {
if (node.nodeType === 3 && node.nodeValue.includes("@")) {
const result = await checkWrittenMentions(node.nodeValue, guild, di)
if (result) {
node.nodeValue = result.content
ensureJoined.push(result.ensureJoined)
}
}
if (node.nodeType === 1 && ["CODE", "PRE", "A"].includes(node.tagName)) {
// don't recurse into code or links
} else {
// do recurse into everything else
await forEachNode(node.firstChild)
}
}
}
await forEachNode(root)
// @ts-ignore bad type from turndown
content = turndownService.turndown(input)
content = turndownService.turndown(root)
// It's designed for commonmark, we need to replace the space-space-newline with just newline
content = content.replace(/ \n/g, "\n")
@ -592,6 +640,12 @@ async function eventToMessage(event, guild, di) {
content = await handleRoomOrMessageLinks(content, di)
const result = await checkWrittenMentions(content, guild, di)
if (result) {
content = result.content
ensureJoined.push(result.ensureJoined)
}
// Markdown needs to be escaped, though take care not to escape the middle of links
// @ts-ignore bad type from turndown
content = turndownService.escape(content)
@ -640,18 +694,6 @@ async function eventToMessage(event, guild, di) {
content = displayNameRunoff + replyLine + content
// Handling written @mentions: we need to look for candidate Discord members to join to the room
let writtenMentionMatch = content.match(/(?:^|[^"[<>/A-Za-z0-9])@([A-Za-z][A-Za-z0-9._\[\]\(\)-]+):?/d) // /d flag for indices requires node.js 16+
if (writtenMentionMatch) {
const results = await di.snow.guild.searchGuildMembers(guild.id, {query: writtenMentionMatch[1]})
if (results[0]) {
assert(results[0].user)
// @ts-ignore - typescript doesn't know about indices yet
content = content.slice(0, writtenMentionMatch.indices[1][0]-1) + `<@${results[0].user.id}>` + content.slice(writtenMentionMatch.indices[1][1])
ensureJoined.push(results[0].user)
}
}
// Split into 2000 character chunks
const chunks = chunk(content, 2000)
messages = messages.concat(chunks.map(content => ({

View file

@ -1924,7 +1924,7 @@ test("event2message: mentioning PK discord users works", async t => {
messagesToEdit: [],
messagesToSend: [{
username: "cadence [they]",
content: "I'm just <@196188877885538304> testing mentions",
content: "I'm just **@Azalea &flwr; 🌺** (<@196188877885538304>) testing mentions",
avatar_url: undefined
}]
}
@ -2845,7 +2845,7 @@ test("event2message: unknown emojis in the middle are linked", async t => {
)
})
test("event2message: guessed @mentions may join members to mention", async t => {
test("event2message: guessed @mentions in plaintext may join members to mention", async t => {
let called = 0
const subtext = {
user: {
@ -2893,6 +2893,56 @@ test("event2message: guessed @mentions may join members to mention", async t =>
t.equal(called, 1, "searchGuildMembers should be called once")
})
test("event2message: guessed @mentions in formatted body may join members to mention", async t => {
let called = 0
const subtext = {
user: {
id: "321876634777218072",
username: "subtextual",
global_name: "subtext",
discriminator: "0"
}
}
t.deepEqual(
await eventToMessage({
type: "m.room.message",
sender: "@cadence:cadence.moe",
content: {
msgtype: "m.text",
body: "wrong body",
format: "org.matrix.custom.html",
formatted_body: "<strong><em>HEY @SUBTEXT, WHAT FOOD WOULD YOU LIKE TO ORDER??</em></strong>"
},
event_id: "$u5gSwSzv_ZQS3eM00mnTBCor8nx_A_AwuQz7e59PZk8",
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
}, {
id: "112760669178241024"
}, {
snow: {
guild: {
async searchGuildMembers(guildID, options) {
called++
t.equal(guildID, "112760669178241024")
t.deepEqual(options, {query: "SUBTEXT"})
return [subtext]
}
}
}
}),
{
messagesToDelete: [],
messagesToEdit: [],
messagesToSend: [{
username: "cadence [they]",
content: "**_HEY <@321876634777218072>, WHAT FOOD WOULD YOU LIKE TO ORDER??_**",
avatar_url: undefined
}],
ensureJoined: [subtext.user]
}
)
t.equal(called, 1, "searchGuildMembers should be called once")
})
test("event2message: guessed @mentions work with other matrix bridge old users", async t => {
t.deepEqual(
await eventToMessage({