Rollen neu geschnitten, Seitenzahl korrigiert, frühere Konten

Der Forums-Abgleich las von 25 Archivseiten nur zwei. WoltLab verlinkt in der
Blätterleiste nur die Nachbarseiten und führt die Gesamtzahl in einem eigenen
Element; ausgewertet wurden aber die Links. Alles jenseits von Seite zwei war
damit unsichtbar - etwa ein Antrag auf Seite vier, der deshalb nie ankam.

Die Rollen verlaufen jetzt zwischen erfassen und gewähren statt zwischen
Alltag und Verwaltung: Editoren erfassen Käufe und führen Datei-Import,
GExtension- und Forums-Abgleich samt Entscheidung über Anträge durch. Kulanz
zu vergeben bleibt Administratoren, ebenso das Auflösen unklarer Pakete, weil
dabei Kulanz entstehen kann.

Dazu lassen sich frühere Steam- und Forumskonten am Spieler hinterlegen.
Abgleich und Import prüfen sie mit, sodass ein Kontowechsel die Zuordnung
nicht abreißen lässt. Steam-Kennungen werden dabei auf dieselbe Form gebracht
wie die Hauptangabe, und eine Kennung kann nicht zwei Spielern gehören.
This commit is contained in:
2026-09-18 20:07:56 +02:00
parent 3344592c6e
commit 6c50d32d2b
24 changed files with 639 additions and 62 deletions
+102
View File
@@ -0,0 +1,102 @@
import assert from 'node:assert/strict';
import { after, before, describe, it } from 'node:test';
import { startTestDatabase, type TestDatabase } from './harness.js';
type Players = typeof import('../src/services/players.js');
type Scraper = typeof import('../src/services/scraper.js');
type Db = typeof import('../src/db.js');
let database: TestDatabase;
let players: Players;
let scraper: Scraper;
let db: Db;
let ctx: { actorId: number; actorName: string };
before(async () => {
database = await startTestDatabase(54335);
process.env.DATABASE_URL = database.url;
process.env.SESSION_SECRET = 'test-secret-that-is-long-enough-000000';
process.env.NODE_ENV = 'test';
db = await import('../src/db.js');
players = await import('../src/services/players.js');
scraper = await import('../src/services/scraper.js');
const user = await db.prisma.user.create({
data: { username: 'pfleger', passwordHash: 'x', role: 'ADMIN' },
});
ctx = { actorId: user.id, actorName: user.username };
});
after(async () => {
await db.prisma.$disconnect();
await database.stop();
});
describe('Frühere Konten', () => {
it('bringt eine nachgetragene SteamID auf dieselbe Form wie die Hauptangabe', async () => {
const player = await db.prisma.player.create({
data: { displayName: 'Umzügler', steamId: '76561198000001111' },
});
// In der Legacy-Schreibweise eingegeben, gespeichert wird die 64-Bit-Form.
await players.addAlias(player.id, 'STEAM_ID', 'STEAM_0:1:19867635', 'Alter Account', ctx);
const alias = await db.prisma.playerAlias.findFirstOrThrow({ where: { playerId: player.id } });
assert.equal(alias.value, '76561198000000999');
});
it('klappert beim Abgleich auch die früheren Konten ab', async () => {
const ids = await scraper.knownSteamIds();
const fuerUmzuegler = ids.filter((e) => e.steamId.startsWith('765611980000'));
assert.ok(
fuerUmzuegler.some((e) => e.steamId === '76561198000001111'),
'die aktuelle Kennung',
);
assert.ok(
fuerUmzuegler.some((e) => e.steamId === '76561198000000999'),
'und die frühere',
);
});
it('nimmt dieselbe Kennung nicht zweimal in den Lauf', async () => {
const ids = await scraper.knownSteamIds();
const eindeutig = new Set(ids.map((e) => e.steamId));
assert.equal(ids.length, eindeutig.size);
});
it('lässt ein Konto nicht zwei Spielern gehören', async () => {
const anderer = await db.prisma.player.create({ data: { displayName: 'Fremder' } });
await assert.rejects(
() => players.addAlias(anderer.id, 'STEAM_ID', '76561198000000999', undefined, ctx),
(error: Error & { code?: string }) => error.code === 'ALIAS_TAKEN',
);
});
it('weist die eigene Hauptangabe als Alias ab', async () => {
const player = await db.prisma.player.findFirstOrThrow({ where: { displayName: 'Umzügler' } });
await assert.rejects(
() => players.addAlias(player.id, 'STEAM_ID', '76561198000001111', undefined, ctx),
(error: Error & { code?: string }) => error.code === 'IS_PRIMARY',
);
});
it('weist Unsinn als SteamID ab', async () => {
const player = await db.prisma.player.findFirstOrThrow({ where: { displayName: 'Umzügler' } });
await assert.rejects(
() => players.addAlias(player.id, 'STEAM_ID', 'kein-account', undefined, ctx),
(error: Error & { code?: string }) => error.code === 'INVALID_STEAMID',
);
});
it('nimmt Forum-Namen ohne Umrechnung', async () => {
const player = await db.prisma.player.findFirstOrThrow({ where: { displayName: 'Umzügler' } });
const alias = await players.addAlias(player.id, 'FORUM_NAME', 'AlterName2019', undefined, ctx);
assert.equal(alias.value, 'AlterName2019');
});
});