Antraege vollstaendig erfassen und zum Forum verlinken
Threads ohne zuordenbaren Spieler wurden bisher verworfen. Von 502 Antraegen im Forum kamen dadurch nur die wenigen an, deren Ersteller schon als Spieler mit Forum-Namen erfasst war - der Rest war schlicht unsichtbar. Sie werden jetzt erfasst und stehen unter "Ohne zugeordneten Spieler", durchsuchbar nach Titel und Ersteller, aber ausserhalb der Arbeitsliste: ohne Spieler laesst sich nichts buchen. Wird spaeter ein Spieler mit passendem Forum-Namen angelegt, der Name nachgetragen oder ein frueheres Konto ergaenzt, wandern seine wartenden Antraege von selbst hinueber. Ein erneuter Abgleich ist dafuer nicht noetig, und eine bereits getroffene Zuordnung bleibt unberuehrt. Der Name eines CCs fuehrt auf der Spielerseite jetzt zum Forumsbeitrag, sofern einer verknuepft ist. Weitere Korrekturen am Abgleich: - Wer sein Forumskonto geloescht hat, stand ohne Profil-Link in der Zeile und hatte damit keinen Autor. Solche Antraege liessen sich niemandem zuordnen. - Bei einer abgewiesenen Anfrage brach das ganze Board ab; die bis dahin gelesenen Seiten gingen mit verloren. Jetzt wird die genannte Wartezeit eingehalten, das Tempo fuer den Rest des Laufs gedrosselt und die Seite erneut versucht. Was gelesen wurde, bleibt in jedem Fall erhalten. - Derselbe Schutz im GExtension-Abgleich, der sonst den Spieler uebersprungen haette. - Der Board-Filter fragte nach einem Feld "board", das es nicht gibt. Ein voller Lauf dauert rund eine halbe Minute und laenger, wenn das Forum bremst. Er laeuft deshalb im Hintergrund weiter und meldet seinen Fortschritt, statt an einer HTTP-Zeitgrenze zu sterben. Gelesen und geschrieben wird gebuendelt statt Thread fuer Thread.
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { after, before, describe, it } from 'node:test';
|
||||
import { startTestDatabase, type TestDatabase } from './harness.js';
|
||||
|
||||
type Forum = typeof import('../src/services/forum.js');
|
||||
type Players = typeof import('../src/services/players.js');
|
||||
type Characters = typeof import('../src/services/characters.js');
|
||||
type Db = typeof import('../src/db.js');
|
||||
|
||||
let database: TestDatabase;
|
||||
let forum: Forum;
|
||||
let players: Players;
|
||||
let characters: Characters;
|
||||
let db: Db;
|
||||
let ctx: { actorId: number; actorName: string };
|
||||
|
||||
before(async () => {
|
||||
database = await startTestDatabase(54337);
|
||||
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');
|
||||
forum = await import('../src/services/forum.js');
|
||||
players = await import('../src/services/players.js');
|
||||
characters = await import('../src/services/characters.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();
|
||||
});
|
||||
|
||||
/** Legt einen Thread an, wie ihn der Abgleich ohne Spieler vorfinden würde. */
|
||||
async function offenerThread(threadId: number, titel: string, autor: string) {
|
||||
return db.prisma.forumThread.create({
|
||||
data: {
|
||||
threadId,
|
||||
title: titel,
|
||||
url: `https://forum.example.org/index.php?thread/${threadId}-antrag/`,
|
||||
authorName: autor,
|
||||
currentBoard: 'CC_ARCHIV',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Anträge ohne zugeordneten Spieler', () => {
|
||||
it('hält sie aus der Arbeitsliste heraus, aber nicht aus der Datenbank', async () => {
|
||||
await offenerThread(9001, 'CC Antrag von einem Fremden', 'Unbekannter');
|
||||
|
||||
const arbeitsliste = await forum.listThreads({ onlyOpen: true });
|
||||
assert.equal(
|
||||
arbeitsliste.some((t) => t.threadId === 9001),
|
||||
false,
|
||||
'ohne Spieler ist nichts zu entscheiden',
|
||||
);
|
||||
|
||||
const wartend = await forum.listUnassignedThreads({});
|
||||
assert.equal(
|
||||
wartend.items.some((t) => t.threadId === 9001),
|
||||
true,
|
||||
'auffindbar bleibt er trotzdem',
|
||||
);
|
||||
});
|
||||
|
||||
it('findet sie über Titel und Ersteller', async () => {
|
||||
await offenerThread(9002, 'Feldagent Ersthelfer', 'Darkbrine44');
|
||||
|
||||
const nachTitel = await forum.listUnassignedThreads({ query: 'ersthelfer' });
|
||||
assert.deepEqual(
|
||||
nachTitel.items.map((t) => t.threadId),
|
||||
[9002],
|
||||
'Grossschreibung darf keine Rolle spielen',
|
||||
);
|
||||
|
||||
const nachAutor = await forum.listUnassignedThreads({ query: 'darkbrine' });
|
||||
assert.deepEqual(
|
||||
nachAutor.items.map((t) => t.threadId),
|
||||
[9002],
|
||||
);
|
||||
});
|
||||
|
||||
it('begrenzt die Ausgabe, nennt aber die volle Zahl', async () => {
|
||||
for (const id of [9010, 9011, 9012]) await offenerThread(id, `Antrag ${id}`, 'Serienbeantrager');
|
||||
|
||||
const seite = await forum.listUnassignedThreads({ query: 'Serienbeantrager', take: 2 });
|
||||
assert.equal(seite.items.length, 2);
|
||||
assert.equal(seite.total, 3, 'sonst wüsste niemand, dass etwas fehlt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Nachträgliche Zuordnung', () => {
|
||||
it('zieht wartende Anträge an einen neu angelegten Spieler', async () => {
|
||||
await offenerThread(9101, 'CC Antrag von Monox', 'Monox');
|
||||
await offenerThread(9102, 'Redesign von Monox', 'monox');
|
||||
|
||||
const spieler = await players.createPlayer(
|
||||
{ displayName: 'Monox', steamId: '76561198000002222', forumName: 'Monox' },
|
||||
ctx,
|
||||
);
|
||||
|
||||
const zugeordnet = await forum.listThreads({ playerId: spieler.id });
|
||||
assert.deepEqual(
|
||||
zugeordnet.map((t) => t.threadId).sort(),
|
||||
[9101, 9102],
|
||||
'auch die abweichende Schreibweise gehört dazu',
|
||||
);
|
||||
});
|
||||
|
||||
it('zieht sie auch nach, wenn der Forum-Name erst später eingetragen wird', async () => {
|
||||
await offenerThread(9201, 'Antrag eines Umbenannten', 'NeuerName');
|
||||
|
||||
const spieler = await players.createPlayer(
|
||||
{ displayName: 'Ohne Forum', steamId: '76561198000003333' },
|
||||
ctx,
|
||||
);
|
||||
assert.equal((await forum.listThreads({ playerId: spieler.id })).length, 0);
|
||||
|
||||
await players.updatePlayer(spieler.id, { forumName: 'NeuerName' }, ctx);
|
||||
|
||||
const zugeordnet = await forum.listThreads({ playerId: spieler.id });
|
||||
assert.deepEqual(
|
||||
zugeordnet.map((t) => t.threadId),
|
||||
[9201],
|
||||
);
|
||||
});
|
||||
|
||||
it('holt über ein früheres Konto auch die alten Anträge', async () => {
|
||||
await offenerThread(9301, 'Antrag unter dem alten Namen', 'AltesKonto');
|
||||
|
||||
const spieler = await players.createPlayer(
|
||||
{ displayName: 'Umzügler', steamId: '76561198000004444', forumName: 'NeuesKonto' },
|
||||
ctx,
|
||||
);
|
||||
await players.addAlias(spieler.id, 'FORUM_NAME', 'AltesKonto', 'Vor dem Wechsel', ctx);
|
||||
|
||||
const zugeordnet = await forum.listThreads({ playerId: spieler.id });
|
||||
assert.deepEqual(
|
||||
zugeordnet.map((t) => t.threadId),
|
||||
[9301],
|
||||
);
|
||||
});
|
||||
|
||||
it('nimmt einem Spieler nie einen bereits zugeordneten Antrag weg', async () => {
|
||||
const erster = await players.createPlayer(
|
||||
{ displayName: 'Erster', steamId: '76561198000005555', forumName: 'Doppelgänger' },
|
||||
ctx,
|
||||
);
|
||||
await offenerThread(9401, 'Strittiger Antrag', 'Doppelgänger');
|
||||
await forum.assignPlayer(9401, erster.id, ctx);
|
||||
|
||||
// Ein zweiter Spieler trägt denselben Namen als früheres Konto nach.
|
||||
const zweiter = await players.createPlayer(
|
||||
{ displayName: 'Zweiter', steamId: '76561198000006666' },
|
||||
ctx,
|
||||
);
|
||||
await players.updatePlayer(zweiter.id, { forumName: 'Doppelgänger' }, ctx);
|
||||
|
||||
const thread = await db.prisma.forumThread.findUniqueOrThrow({ where: { threadId: 9401 } });
|
||||
assert.equal(thread.playerId, erster.id, 'eine getroffene Entscheidung bleibt stehen');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Antrag am CC', () => {
|
||||
it('hängt den Thread an den CC, der aus ihm hervorgeht', async () => {
|
||||
const spieler = await players.createPlayer(
|
||||
{ displayName: 'Antragsteller', steamId: '76561198000007777', forumName: 'Antragsteller' },
|
||||
ctx,
|
||||
);
|
||||
await db.prisma.creditLedger.create({
|
||||
data: { playerId: spieler.id, kind: 'CC', amount: 1, source: 'CC_PACKAGE' },
|
||||
});
|
||||
|
||||
await offenerThread(9501, 'The Duke', 'Antragsteller');
|
||||
await forum.claimThreadsForPlayer(spieler.id, ['Antragsteller']);
|
||||
|
||||
const { characterId } = await forum.classifyThread(
|
||||
9501,
|
||||
{ classification: 'NEW_CC', characterName: 'The Duke' },
|
||||
ctx,
|
||||
);
|
||||
assert.ok(characterId);
|
||||
|
||||
const liste = await characters.listCharacters(spieler.id);
|
||||
const cc = liste.find((c) => c.id === characterId);
|
||||
assert.deepEqual(
|
||||
cc?.forumThreads.map((t) => t.threadId),
|
||||
[9501],
|
||||
'ohne das bliebe der Name auf der Spielerseite ein toter Text',
|
||||
);
|
||||
assert.match(cc?.forumThreads[0]?.url ?? '', /thread\/9501/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user