From 0d15865bcd0432b3573991f6f0ac9bf2a25c3d89 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 9 Jan 2026 02:07:08 +1300 Subject: [PATCH] kstate fixes and code coverage --- src/db/migrations/0029-force-guild-ids.js | 2 + src/matrix/kstate.js | 17 +- src/matrix/kstate.test.js | 184 +++++++++++++++++++++- 3 files changed, 192 insertions(+), 11 deletions(-) diff --git a/src/db/migrations/0029-force-guild-ids.js b/src/db/migrations/0029-force-guild-ids.js index e575783..354bc6b 100644 --- a/src/db/migrations/0029-force-guild-ids.js +++ b/src/db/migrations/0029-force-guild-ids.js @@ -14,6 +14,8 @@ const {discord} = require("../../passthrough") const ones = "₀₁₂₃₄₅₆₇₈₉" const tens = "0123456789" +/* c8 ignore start */ + module.exports = async function(db) { /** @type {{name: string, channel_id: string, thread_parent: string | null}[]} */ const rows = db.prepare("SELECT name, channel_id, thread_parent FROM channel_room WHERE guild_id IS NULL").all() diff --git a/src/matrix/kstate.js b/src/matrix/kstate.js index 85a2838..37eed39 100644 --- a/src/matrix/kstate.js +++ b/src/matrix/kstate.js @@ -53,11 +53,11 @@ async function kstateToState(kstate) { kstateStripConditionals(kstate) await kstateUploadMxc(kstate) for (const [k, content] of Object.entries(kstate)) { - if (k === "m.room.create/") continue const slashIndex = k.indexOf("/") assert(slashIndex > 0) const type = k.slice(0, slashIndex) const state_key = k.slice(slashIndex + 1) + if (type === "m.room.create") continue events.push({type, state_key, content}) } return events @@ -94,15 +94,16 @@ function diffKState(actual, target) { if (key === "m.room.power_levels/") { // Special handling for power levels, we want to deep merge the actual and target into the final state. if (!(key in actual)) throw new Error(`want to apply a power levels diff, but original power level data is missing\nstarted with: ${JSON.stringify(actual)}\nwant to apply: ${JSON.stringify(target)}`) - const mixedTarget = mixin({}, actual[key], target[key]) + // if the diff includes users, it needs to be cleaned wrt room version 12 + const cleanedTarget = mixin({}, target[key]) + if (target[key].users && Object.keys(target[key].users).length > 0) { + assert("m.room.create/" in actual, `want to apply a power levels diff, but original m.room.create/ is missing\nstarted with: ${JSON.stringify(actual)}\nwant to apply: ${JSON.stringify(target)}`) + assert("m.room.create/outer" in actual, `want to apply a power levels diff, but original m.room.create/outer is missing\nstarted with: ${JSON.stringify(actual)}\nwant to apply: ${JSON.stringify(target)}`) + utils.removeCreatorsFromPowerLevels(actual["m.room.create/outer"], cleanedTarget) + } + const mixedTarget = mixin({}, actual[key], cleanedTarget) if (!isDeepStrictEqual(actual[key], mixedTarget)) { // they differ. use the newly prepared object as the diff. - // if the diff includes users, it needs to be cleaned wrt room version 12 - if (target[key].users && Object.keys(target[key].users).length > 0) { - if (!("m.room.create/" in actual)) throw new Error(`want to apply a power levels diff, but original m.room.create/ is missing\nstarted with: ${JSON.stringify(actual)}\nwant to apply: ${JSON.stringify(target)}`) - if (!("m.room.create/outer" in actual)) throw new Error(`want to apply a power levels diff, but original m.room.create/outer is missing\nstarted with: ${JSON.stringify(actual)}\nwant to apply: ${JSON.stringify(target)}`) - utils.removeCreatorsFromPowerLevels(actual["m.room.create/outer"], mixedTarget) - } diff[key] = mixedTarget } diff --git a/src/matrix/kstate.test.js b/src/matrix/kstate.test.js index ff65e9c..b67a725 100644 --- a/src/matrix/kstate.test.js +++ b/src/matrix/kstate.test.js @@ -1,5 +1,5 @@ const assert = require("assert") -const {kstateToState, stateToKState, diffKState, kstateStripConditionals, kstateUploadMxc} = require("./kstate") +const {kstateToState, stateToKState, diffKState, kstateStripConditionals, kstateUploadMxc, kstateToCreationContent} = require("./kstate") const {test} = require("supertape") test("kstate strip: strips false conditions", t => { @@ -68,6 +68,8 @@ test("kstateUploadMxc and strip: work together", async t => { test("kstate2state: general", async t => { t.deepEqual(await kstateToState({ + "m.room.create/": {bogus: true}, + "m.room.create/outer": {bogus: true}, "m.room.name/": {name: "test name"}, "m.room.member/@cadence:cadence.moe": {membership: "join"}, "uk.half-shot.bridge/org.matrix.appservice-irc://irc/epicord.net/#general": {creator: "@cadence:cadence.moe"} @@ -98,6 +100,14 @@ test("kstate2state: general", async t => { test("state2kstate: general", t => { t.deepEqual(stateToKState([ + { + type: "m.room.create", + state_key: "", + sender: "@example:matrix.org", + content: { + room_version: "12" + } + }, { type: "m.room.name", state_key: "", @@ -122,7 +132,9 @@ test("state2kstate: general", t => { ]), { "m.room.name/": {name: "test name"}, "m.room.member/@cadence:cadence.moe": {membership: "join"}, - "uk.half-shot.bridge/org.matrix.appservice-irc://irc/epicord.net/#general": {creator: "@cadence:cadence.moe"} + "uk.half-shot.bridge/org.matrix.appservice-irc://irc/epicord.net/#general": {creator: "@cadence:cadence.moe"}, + "m.room.create/": {room_version: "12"}, + "m.room.create/outer": {type: "m.room.create", state_key: "", sender: "@example:matrix.org", content: {room_version: "12"}} }) }) @@ -157,6 +169,17 @@ test("diffKState: detects new properties", t => { test("diffKState: power levels are mixed together", t => { const original = { + "m.room.create/outer": { + type: "m.room.create", + state_key: "", + sender: "@example:matrix.org", + content: { + room_version: "11" + } + }, + "m.room.create/": { + room_version: "11" + }, "m.room.power_levels/": { "ban": 50, "events": { @@ -181,6 +204,9 @@ test("diffKState: power levels are mixed together", t => { "m.room.power_levels/": { "events": { "m.room.avatar": 0 + }, + users: { + "@example:matrix.org": 100 } } }) @@ -201,7 +227,8 @@ test("diffKState: power levels are mixed together", t => { "redact": 50, "state_default": 50, "users": { - "@example:localhost": 100 + "@example:localhost": 100, + "@example:matrix.org": 100 }, "users_default": 0 } @@ -270,3 +297,154 @@ test("diffKState: topic changes if the topic key has changed", t => { } }) }) + +test("diffKState: room v12 creators cannot be introduced into power levels", t => { + const original = { + "m.room.create/outer": { + type: "m.room.create", + state_key: "", + sender: "@example1:matrix.org", + content: { + additional_creators: ["@example2:matrix.org"], + room_version: "12" + } + }, + "m.room.create/": { + room_version: "12" + }, + "m.room.power_levels/": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100 + }, + "events_default": 0, + "invite": 50, + "kick": 50, + "notifications": { + "room": 20 + }, + "redact": 50, + "state_default": 50, + "users": { + "@example:localhost": 100 + }, + "users_default": 0 + } + } + const result = diffKState(original, { + "m.room.create/": { + bogus: true + }, + "m.room.power_levels/": { + events: { + "m.room.avatar": 0 + }, + users: { + "@example1:matrix.org": 100, + "@example2:matrix.org": 100, + "@example3:matrix.org": 100 + } + } + }) + t.deepEqual(result, { + "m.room.power_levels/": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100, + "m.room.avatar": 0 + }, + "events_default": 0, + "invite": 50, + "kick": 50, + "notifications": { + "room": 20 + }, + "redact": 50, + "state_default": 50, + "users": { + "@example:localhost": 100, + "@example3:matrix.org": 100 + }, + "users_default": 0 + } + }) + t.notDeepEqual(original, result) +}) + +test("diffKState: room v12 creators cannot be introduced into power levels - no diff if no changes", t => { + const original = { + "m.room.create/outer": { + type: "m.room.create", + state_key: "", + sender: "@example1:matrix.org", + content: { + additional_creators: ["@example2:matrix.org"], + room_version: "12" + } + }, + "m.room.create/": { + additional_creators: ["@example2:matrix.org"], + room_version: "12" + }, + "m.room.power_levels/": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100 + }, + "events_default": 0, + "invite": 50, + "kick": 50, + "notifications": { + "room": 20 + }, + "redact": 50, + "state_default": 50, + "users": { + "@example:localhost": 100 + }, + "users_default": 0 + } + } + const result = diffKState(original, { + "m.room.power_levels/": { + users: { + "@example1:matrix.org": 100, + "@example2:matrix.org": 100 + } + } + }) + t.deepEqual(result, {}) + t.notDeepEqual(original, result) +}) + +test("kstateToCreationContent: works", t => { + const original = { + "m.room.create/outer": { + type: "m.room.create", + state_key: "", + sender: "@example1:matrix.org", + content: { + additional_creators: ["@example2:matrix.org"], + room_version: "12", + type: "m.space" + } + }, + "m.room.create/": { + additional_creators: ["@example2:matrix.org"], + room_version: "12", + type: "m.space" + } + } + t.deepEqual(kstateToCreationContent(original), { + additional_creators: ["@example2:matrix.org"], + room_version: "12", + type: "m.space" + }) +}) + +test("kstateToCreationContent: works if empty", t => { + t.deepEqual(kstateToCreationContent({}), {}) +})