discord:setup: der zweite Lauf schreibt wirklich nichts mehr

Der Bauplan verspricht, dass ein zweiter Lauf nichts aendert. Zwei Schritte
haben das gebrochen, weil sie ohne Vergleich geschrieben haben: die
Servereinstellungen und die Rangfolge der Rollen.

Aufgefallen beim Einrichten des Technik-Webhooks - der Lauf meldete "GESETZT
abdulive_" und "GESETZT Rangfolge", obwohl die Vorschau zuvor "steht" gesagt
hatte. Vorschau und echter Lauf sagten also Verschiedenes.

Folgenlos war es nicht: jeder Lauf kostete zwei Aufrufe und hinterliess zwei
Eintraege im Audit-Log des Servers, einen davon eine Rollen-Umsortierung.

Jetzt wird beides vorher verglichen. ApiGuild traegt dafuer die vier Felder
aus GUILD_SETTINGS. Ein Test haelt es fest: nach einem Lauf darf der zweite
weder PATCH /guilds/{id} noch PATCH /guilds/{id}/roles schicken.

Am echten Server nachgeprueft - der dritte Lauf meldet keine Aenderung mehr.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZeasyAlexandClaude Opus 5 committed 2026-09-18 00:12:58 +02:00
1 parent c4dc6ee04f
commit e18336c57f
3 files changed
+64 -10

No files matched your search

+33 -6
View File
@@ -109,7 +109,13 @@ class FakeDiscord implements Rest {
async patch<T>(path: string, body: unknown): Promise<T> {
this.calls.push(`PATCH ${path}`);
if (path === `/guilds/${GUILD}`) return {} as T;
if (path === `/guilds/${GUILD}`) {
// Wie der echte Server: gesetzte Einstellungen stehen danach auch drin.
// Ohne das liesse sich nicht pruefen, ob ein zweiter Lauf sie in Ruhe
// laesst.
Object.assign(this.guild, body as Record<string, unknown>);
return structuredClone(this.guild) as T;
}
if (path === `/guilds/${GUILD}/roles`) { // Rangfolge
for (const e of body as Array<{ id: string; position: number }>) {
const r = this.guild.roles.find((x) => x.id === e.id);
@@ -187,7 +193,7 @@ test('der erste Lauf legt Rollen, Kategorien, Kanaele und Webhooks an', async ()
for (const c of CHANNELS) {
assert.ok(api.channels.find((x) => x.name === c.name), `Kanal ${c.name} fehlt`);
}
assert.equal(res.webhooks, 3);
assert.equal(res.webhooks, 4);
assert.equal(api.automod.length, 2);
assert.equal(counts(res.steps)['uebersprungen'] ?? 0, 0);
});
@@ -259,7 +265,7 @@ test('der zweite Lauf aendert nichts', async () => {
`unerwartete Schreibzugriffe: ${schreibend.join(', ')}`);
assert.equal(counts(res.steps)['erstellt'] ?? 0, 0);
assert.equal(res.webhooks, 3, 'die bestehenden Webhook-URLs muessen wiedergefunden werden');
assert.equal(res.webhooks, 4, 'die bestehenden Webhook-URLs muessen wiedergefunden werden');
});
test('die Begruessung wird nicht ein zweites Mal gepostet', async () => {
@@ -320,7 +326,7 @@ test('ein von Hand angelegter Webhook wird gemeldet, nicht ueberschrieben', asyn
const step = res.steps.find((s) => s.kind === 'Webhook' && s.name.startsWith('Go-Live'));
assert.equal(step?.change, 'uebersprungen');
assert.match(step?.note ?? '', /nicht auslesbar/);
assert.equal(res.webhooks, 2);
assert.equal(res.webhooks, 3, 'die drei uebrigen muessen trotzdem durchkommen');
});
test('eine von Discord verwaltete Rolle wird in Ruhe gelassen', async () => {
@@ -375,7 +381,7 @@ test('Bot-Rolle ganz unten: Rangfolge wird gemeldet statt zu scheitern', async (
assert.match(step?.note ?? '', /nach oben ziehen/);
// Alles andere muss trotzdem stehen.
assert.equal(api.channels.length, 3 + CHANNELS.length);
assert.equal(res.webhooks, 3);
assert.equal(res.webhooks, 4);
});
// ---------------------------------------------------------------- Umbenennen
@@ -421,10 +427,31 @@ test('haengt Discord beim Umbenennen im Rate Limit, laeuft der Rest weiter', asy
assert.equal(step?.change, 'uebersprungen');
assert.match(step?.note ?? '', /530 s/);
// Die uebrigen Kanaele und die Webhooks muessen trotzdem entstehen.
assert.equal(res.webhooks, 3);
assert.equal(res.webhooks, 4);
assert.equal(api.automod.length, 2);
});
test('der zweite Lauf schreibt nichts mehr - auch nicht Einstellungen und Rangfolge', async () => {
// Der Bauplan verspricht, dass ein zweiter Lauf nichts aendert. Zwei
// Schritte haben das lange gebrochen, weil sie ohne Vergleich geschrieben
// haben: die Servereinstellungen und die Rangfolge der Rollen. Folgenlos
// war das nicht - jeder Lauf hinterliess zwei Eintraege im Audit-Log und
// liess den Bericht so aussehen, als sei etwas passiert.
const api = new FakeDiscord();
await run(api);
api.calls.length = 0;
const res = await run(api);
assert.equal(api.calls.some((c) => c === `PATCH /guilds/${GUILD}`), false,
'Servereinstellungen standen schon');
assert.equal(api.calls.some((c) => c === `PATCH /guilds/${GUILD}/roles`), false,
'die Rangfolge stand schon');
assert.equal(res.steps.find((s) => s.kind === 'Server')?.change, 'unveraendert');
assert.equal(res.steps.find((s) => s.name === 'Rangfolge')?.change, 'unveraendert');
assert.equal(res.steps.some((s) => s.change === 'erstellt'), false, 'nichts neu angelegt');
});
test('ein geaenderter Blocktext wird auf eine bestehende Regel nachgezogen', async () => {
const api = new FakeDiscord();
await run(api);