Jede Liste gehoert einem Spieler, CC-Guthaben wird umgewandelt
Ein Gruppen-CC wird fuer jeden Nutzer einzeln gerechnet: fuer den einen kann er der gekaufte Platz sein, fuer den anderen ein Redesign seines vorherigen CCs. Dafuer haengt die Liste jetzt an der Mitgliedschaft statt am CC, und verschieben ordnet nur die eigene Liste. Ein Redesign wird nie aus dem CC-Topf bezahlt. Wer sein CC-Guthaben dafuer einsetzt, wandelt es um: ein Platz weniger, ein bezahltes Redesign mehr. Damit entfaellt die Ausnahme, die sich bisher durch Slot-Pruefung und Kettenrechnung zog. Beim Start werden Buchungen einmalig an die Listen angeglichen.
This commit is contained in:
@@ -769,3 +769,106 @@ describe('Der eigene CC bleibt der eigene', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ein Gruppen-CC zählt für jeden Nutzer einzeln', () => {
|
||||
/// Womit ein bestimmter Spieler diesen CC bezahlt hat.
|
||||
async function bezahltMit(characterId: number, playerId: number): Promise<string | undefined> {
|
||||
const buchung = await db.prisma.creditLedger.findFirst({
|
||||
where: { characterId, playerId, source: 'CONSUME', reversedBy: null },
|
||||
orderBy: { id: 'desc' },
|
||||
});
|
||||
return buchung?.kind;
|
||||
}
|
||||
|
||||
/// Die Liste dieses Spielers, von unten nach oben.
|
||||
async function liste(playerId: number): Promise<string[]> {
|
||||
const eigene = await db.prisma.characterMember.findMany({
|
||||
where: { playerId },
|
||||
orderBy: [{ lineageId: 'asc' }, { chainPosition: 'asc' }],
|
||||
select: { lineageId: true, character: { select: { name: true } } },
|
||||
});
|
||||
return eigene.map((e) => `${e.lineageId.slice(0, 4)}:${e.character.name}`);
|
||||
}
|
||||
|
||||
it('ist für den einen ein Platz und für den anderen ein Redesign', async () => {
|
||||
const wirt = await spielerMitPaket('Zaehlwirt');
|
||||
const gast = await spielerMitPaket('Zaehlgast');
|
||||
const gruppe = await characters.createCharacter(wirt, { name: 'Gemeinsames Werk' }, ctx);
|
||||
const eigener = await characters.createCharacter(gast, { name: 'Sein Erster' }, ctx);
|
||||
|
||||
await credits.bookRedesignPackage(gast, ctx);
|
||||
await characters.addMember(
|
||||
gruppe.character.id,
|
||||
gast,
|
||||
{ useKind: 'REDESIGN_PAID', predecessorId: eigener.character.id },
|
||||
ctx,
|
||||
);
|
||||
|
||||
assert.equal(await bezahltMit(gruppe.character.id, wirt), 'CC', 'für den Wirt sein Platz');
|
||||
assert.equal(
|
||||
await bezahltMit(gruppe.character.id, gast),
|
||||
'REDESIGN_PAID',
|
||||
'für den Gast das Redesign seines ersten CCs',
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await characters.richteBuchungenAus(),
|
||||
0,
|
||||
'beide Sichten sind in sich stimmig und bleiben unangetastet',
|
||||
);
|
||||
|
||||
// Die Rechnung des Gastes: eine Liste mit zwei Einträgen.
|
||||
const stand = await credits.getBalances(gast);
|
||||
assert.equal(stand.CC, 0, 'ein Platz, eine Liste');
|
||||
assert.equal(stand.REDESIGN_PAID, 1, 'von zwei Redesigns eines verbraucht');
|
||||
assert.deepEqual(
|
||||
(await liste(gast)).map((e) => e.split(':')[1]),
|
||||
['Sein Erster', 'Gemeinsames Werk'],
|
||||
'in seiner Liste steht der Gruppen-CC über seinem ersten',
|
||||
);
|
||||
});
|
||||
|
||||
it('lässt jeden für sich verschieben, ohne den anderen anzufassen', async () => {
|
||||
const wirt = await spielerMitPaket('Schiebewirt', 2);
|
||||
const gast = await spielerMitPaket('Schiebegast', 2);
|
||||
const gruppe = await characters.createCharacter(wirt, { name: 'Die Bude' }, ctx);
|
||||
const eigener = await characters.createCharacter(gast, { name: 'Gasts Erster' }, ctx);
|
||||
|
||||
await characters.addMember(gruppe.character.id, gast, { useKind: 'CC' }, ctx);
|
||||
assert.equal(await bezahltMit(gruppe.character.id, gast), 'CC', 'erst ein eigener Platz');
|
||||
|
||||
// Der Gast reiht den Gruppen-CC in seine Liste ein: für ihn wird daraus
|
||||
// ein Redesign seines ersten CCs.
|
||||
await credits.bookRedesignPackage(gast, ctx);
|
||||
await characters.moveInChain(
|
||||
gruppe.character.id,
|
||||
{
|
||||
lineageId: (
|
||||
await db.prisma.characterMember.findFirstOrThrow({
|
||||
where: { playerId: gast, characterId: eigener.character.id },
|
||||
})
|
||||
).lineageId,
|
||||
afterId: eigener.character.id,
|
||||
forPlayerId: gast,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await bezahltMit(gruppe.character.id, gast),
|
||||
'REDESIGN_PAID',
|
||||
'in seiner Liste ist es jetzt ein Redesign',
|
||||
);
|
||||
assert.equal(
|
||||
await bezahltMit(gruppe.character.id, wirt),
|
||||
'CC',
|
||||
'beim Wirt ändert sich dadurch nichts',
|
||||
);
|
||||
|
||||
// Und der CC selbst bleibt aktiv - der Wirt spielt ihn weiter.
|
||||
const danach = await db.prisma.character.findUniqueOrThrow({
|
||||
where: { id: gruppe.character.id },
|
||||
});
|
||||
assert.equal(danach.status, 'ACTIVE');
|
||||
});
|
||||
});
|
||||
|
||||
+291
-32
@@ -1,5 +1,8 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { after, before, describe, it } from 'node:test';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { startTestDatabase, type TestDatabase } from './harness.js';
|
||||
|
||||
type Characters = typeof import('../src/services/characters.js');
|
||||
@@ -43,13 +46,23 @@ async function spielerMitPaketen(name: string, pakete = 1) {
|
||||
* Eine Kette, wie sie angezeigt würde. Massgeblich ist die Linie, nicht der
|
||||
* Besitzer: ein Gruppen-CC gehört einem anderen und steht trotzdem darin.
|
||||
*/
|
||||
async function kette(_playerId: number, lineageId: string) {
|
||||
const glieder = await db.prisma.character.findMany({
|
||||
where: { lineageId },
|
||||
async function kette(playerId: number, lineageId: string) {
|
||||
const glieder = await db.prisma.characterMember.findMany({
|
||||
where: { playerId, lineageId },
|
||||
orderBy: [{ chainPosition: 'asc' }, { id: 'asc' }],
|
||||
select: { id: true, name: true, predecessorId: true, chainPosition: true },
|
||||
select: {
|
||||
characterId: true,
|
||||
chainPosition: true,
|
||||
predecessorId: true,
|
||||
character: { select: { name: true } },
|
||||
},
|
||||
});
|
||||
return glieder;
|
||||
return glieder.map((glied) => ({
|
||||
id: glied.characterId,
|
||||
name: glied.character.name,
|
||||
predecessorId: glied.predecessorId,
|
||||
chainPosition: glied.chainPosition,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Prüft, dass Positionen und Vorgänger-Verweise dasselbe sagen. */
|
||||
@@ -493,22 +506,22 @@ describe('Redesign nachträglich umbuchen', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('stellt einen gekauften CC auf Redesign um', async () => {
|
||||
// Genau der Korrekturfall: ein Eintrag war gar nicht der Anfang einer
|
||||
// Kette, sondern ging aus einem anderen hervor.
|
||||
it('macht aus einem Kettenanfang kein Redesign', async () => {
|
||||
// Ob ein Eintrag ein Redesign ist, entscheidet seine Stelle in der Kette.
|
||||
// Sonst ginge die Rechnung "eine Kette = ein CC-Paket" nicht mehr auf.
|
||||
const spieler = await spielerMitPaketen('Umsteller', 1);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'Direkt aus dem Paket' }, ctx);
|
||||
await credits.bookRedesignPackage(spieler, ctx);
|
||||
|
||||
const danach = await characters.changeFunding(eins.character.id, 'REDESIGN_PAID', ctx);
|
||||
assert.equal(danach.balances.CC, 1, 'das CC-Guthaben kommt zurück');
|
||||
assert.equal(danach.balances.REDESIGN_PAID, 1, 'dafür geht ein Redesign weg');
|
||||
|
||||
await assert.rejects(
|
||||
() => characters.changeFunding(eins.character.id, 'REDESIGN_PAID', ctx),
|
||||
(error: Error & { code?: string }) => error.code === 'SAME_KIND',
|
||||
'ein CC aus dem Paket ist schon als CC gebucht',
|
||||
(error: Error & { code?: string }) => error.code === 'CHAIN_START',
|
||||
'dafür wird er eingereiht, nicht umgebucht',
|
||||
);
|
||||
|
||||
const stand = await credits.getBalances(spieler);
|
||||
assert.equal(stand.CC, 0, 'der Platz bleibt gebucht');
|
||||
assert.equal(stand.REDESIGN_PAID, 2, 'und das Redesign unangetastet');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1045,8 +1058,16 @@ describe('Kulanz gegen ein Minus verrechnen', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ein Redesign ausnahmsweise aus dem CC-Topf', () => {
|
||||
it('nimmt ein CC-Guthaben an', async () => {
|
||||
describe('Ein CC-Guthaben für ein Redesign einsetzen', () => {
|
||||
async function verbrauchtAls(characterId: number): Promise<string | undefined> {
|
||||
const buchung = await db.prisma.creditLedger.findFirst({
|
||||
where: { characterId, source: 'CONSUME', reversedBy: null },
|
||||
orderBy: { id: 'desc' },
|
||||
});
|
||||
return buchung?.kind;
|
||||
}
|
||||
|
||||
it('wandelt es um, statt eine Ausnahme zu buchen', async () => {
|
||||
const spieler = await spielerMitPaketen('Ausnahmefall', 2);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'Basis' }, ctx);
|
||||
|
||||
@@ -1057,13 +1078,26 @@ describe('Ein Redesign ausnahmsweise aus dem CC-Topf', () => {
|
||||
ctx,
|
||||
);
|
||||
|
||||
assert.equal(zwei.balances.CC, vorher.CC - 1, 'bezahlt aus dem CC-Topf');
|
||||
assert.equal(zwei.balances.REDESIGN_PAID, vorher.REDESIGN_PAID, 'die Redesigns bleiben');
|
||||
assert.equal(zwei.balances.CC, vorher.CC - 1, 'der Platz ist weg');
|
||||
assert.equal(
|
||||
zwei.balances.REDESIGN_PAID,
|
||||
vorher.REDESIGN_PAID,
|
||||
'das eingetauschte Redesign wird sofort wieder verbraucht',
|
||||
);
|
||||
assert.equal(
|
||||
await verbrauchtAls(zwei.character.id),
|
||||
'REDESIGN_PAID',
|
||||
'der Eintrag selbst ist ein Redesign wie jedes andere',
|
||||
);
|
||||
assert.equal(
|
||||
zwei.balances.granted.REDESIGN_PAID,
|
||||
vorher.granted.REDESIGN_PAID + 1,
|
||||
'und das umgewandelte Guthaben ist als erhalten ausgewiesen',
|
||||
);
|
||||
assert.equal(zwei.balances.granted.CC, vorher.granted.CC, 'die Käufe bleiben, wie sie waren');
|
||||
});
|
||||
|
||||
it('lässt sich vom Ausrichten an den Ketten nicht überschreiben', async () => {
|
||||
// Ohne Merker würde die Automatik beim nächsten Umsortieren daraus
|
||||
// wieder ein gewöhnliches Redesign machen.
|
||||
it('bleibt beim Ausrichten an den Ketten stehen', async () => {
|
||||
const spieler = await spielerMitPaketen('Bleibt so', 2);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'Erstes' }, ctx);
|
||||
const zwei = await characters.redesignCharacter(
|
||||
@@ -1072,16 +1106,32 @@ describe('Ein Redesign ausnahmsweise aus dem CC-Topf', () => {
|
||||
ctx,
|
||||
);
|
||||
|
||||
await characters.rebuildFunding(spieler, ctx);
|
||||
const vorher = await credits.getBalances(spieler);
|
||||
const lauf = await characters.rebuildFunding(spieler, ctx);
|
||||
|
||||
const buchung = await db.prisma.creditLedger.findFirst({
|
||||
where: { characterId: zwei.character.id, source: 'CONSUME', reversedBy: null },
|
||||
orderBy: { id: 'desc' },
|
||||
});
|
||||
assert.equal(buchung?.kind, 'CC', 'die bewusste Entscheidung bleibt stehen');
|
||||
assert.equal(lauf.umgebucht, 0, 'es passt bereits alles');
|
||||
assert.equal(await verbrauchtAls(zwei.character.id), 'REDESIGN_PAID');
|
||||
assert.deepEqual(lauf.balances, vorher, 'und am Guthaben ändert sich nichts');
|
||||
});
|
||||
|
||||
it('merkt sich auch eine von Hand geänderte Bezahlart', async () => {
|
||||
it('gibt den Tausch zurück, wenn der Eintrag wieder allein steht', async () => {
|
||||
const spieler = await spielerMitPaketen('Rueckzieher', 2);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'A' }, ctx);
|
||||
const zwei = await characters.redesignCharacter(
|
||||
eins.character.id,
|
||||
{ name: 'B', useKind: 'CC' },
|
||||
ctx,
|
||||
);
|
||||
|
||||
await characters.detachFromChain(zwei.character.id, ctx);
|
||||
|
||||
assert.equal(await verbrauchtAls(zwei.character.id), 'CC', 'allein steht er für sich');
|
||||
const stand = await credits.getBalances(spieler);
|
||||
assert.equal(stand.CC, 0, 'beide Plätze sind belegt');
|
||||
assert.equal(stand.REDESIGN_PAID, 2, 'die Redesigns sind wieder frei');
|
||||
});
|
||||
|
||||
it('lässt sich auch nachträglich einsetzen', async () => {
|
||||
const spieler = await spielerMitPaketen('Von Hand', 2);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'A' }, ctx);
|
||||
const zwei = await characters.redesignCharacter(
|
||||
@@ -1090,13 +1140,222 @@ describe('Ein Redesign ausnahmsweise aus dem CC-Topf', () => {
|
||||
ctx,
|
||||
);
|
||||
|
||||
await characters.changeFunding(zwei.character.id, 'CC', ctx);
|
||||
await characters.rebuildFunding(spieler, ctx);
|
||||
const danach = await characters.changeFunding(zwei.character.id, 'CC', ctx);
|
||||
|
||||
assert.equal(await verbrauchtAls(zwei.character.id), 'REDESIGN_PAID');
|
||||
assert.equal(danach.balances.CC, 0, 'der Platz ist eingetauscht');
|
||||
assert.equal(danach.balances.REDESIGN_PAID, 2, 'das Redesign dafür wieder frei');
|
||||
|
||||
// Und zurück: der Tausch wird rückgängig gemacht.
|
||||
const zurueck = await characters.changeFunding(zwei.character.id, 'REDESIGN_PAID', ctx);
|
||||
assert.equal(zurueck.balances.CC, 1, 'der Platz kommt zurück');
|
||||
assert.equal(zurueck.balances.REDESIGN_PAID, 1, 'und das Redesign wird wieder verbraucht');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Buchungen an die Ketten angleichen', () => {
|
||||
it('bucht den Anfang einer Kette auf einen Platz', async () => {
|
||||
// Der Fall aus dem Bestand: drei Ketten, aber nur zwei gebuchte Plätze -
|
||||
// bei einer war der unterste Eintrag als Redesign gebucht.
|
||||
const spieler = await spielerMitPaketen('Schieflage', 2);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'Kettenfuss' }, ctx);
|
||||
await db.prisma.creditLedger.updateMany({
|
||||
where: { characterId: eins.character.id, source: 'CONSUME' },
|
||||
data: { kind: 'REDESIGN_PAID' },
|
||||
});
|
||||
|
||||
const abweichend = await db.prisma.creditLedger.findMany({
|
||||
where: { source: 'CONSUME', reversedBy: null },
|
||||
select: { kind: true, playerId: true, character: { select: { name: true, playerId: true, predecessorId: true } } },
|
||||
});
|
||||
for (const e of abweichend) {
|
||||
const cc = e.character;
|
||||
if (!cc) continue;
|
||||
const soll = cc.playerId === e.playerId ? cc.predecessorId === null : null;
|
||||
if (soll !== null && (e.kind === 'CC') !== soll) {
|
||||
console.log('ABWEICHUNG', cc.name, e.kind, 'predecessor', cc.predecessorId, 'spieler', e.playerId, 'besitzer', cc.playerId);
|
||||
}
|
||||
}
|
||||
const angeglichen = await characters.richteBuchungenAus();
|
||||
assert.equal(angeglichen, 1, 'genau diese eine Buchung passte nicht');
|
||||
|
||||
const buchung = await db.prisma.creditLedger.findFirst({
|
||||
where: { characterId: zwei.character.id, source: 'CONSUME', reversedBy: null },
|
||||
where: { characterId: eins.character.id, source: 'CONSUME', reversedBy: null },
|
||||
orderBy: { id: 'desc' },
|
||||
});
|
||||
assert.equal(buchung?.kind, 'CC', 'wer unten steht, hat einen Platz gekauft');
|
||||
|
||||
const stand = await credits.getBalances(spieler);
|
||||
assert.equal(stand.CC, 1, 'ein Platz belegt, einer frei');
|
||||
assert.equal(stand.REDESIGN_PAID, 2, 'das Redesign ist wieder frei');
|
||||
});
|
||||
|
||||
it('lässt den zweiten Lauf alles, wie es ist', async () => {
|
||||
const spieler = await spielerMitPaketen('Ruhig', 1);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'Unten' }, ctx);
|
||||
await characters.redesignCharacter(
|
||||
eins.character.id,
|
||||
{ name: 'Oben', useKind: 'REDESIGN_PAID' },
|
||||
ctx,
|
||||
);
|
||||
|
||||
assert.equal(await characters.richteBuchungenAus(), 0, 'es passt bereits alles');
|
||||
const vorher = await db.prisma.creditLedger.count({ where: { playerId: spieler } });
|
||||
await characters.richteBuchungenAus();
|
||||
assert.equal(
|
||||
await db.prisma.creditLedger.count({ where: { playerId: spieler } }),
|
||||
vorher,
|
||||
'und es entstehen keine Zeilen aus dem Nichts',
|
||||
);
|
||||
});
|
||||
|
||||
it('erkennt den aufgegebenen CC eines Mitglieds', async () => {
|
||||
// Für ein Mitglied steht der Vorgänger an der Mitgliedschaft, nicht am
|
||||
// Gruppen-CC. Sonst würde sein Redesign hier zu einem Platz.
|
||||
const gruender = await spielerMitPaketen('Wirt', 1);
|
||||
const gast = await spielerMitPaketen('Gast', 1);
|
||||
const gruppe = await characters.createCharacter(gruender, { name: 'Stammtisch' }, ctx);
|
||||
const eigener = await characters.createCharacter(gast, { name: 'Eigener' }, ctx);
|
||||
|
||||
await credits.bookRedesignPackage(gast, ctx);
|
||||
await characters.addMember(
|
||||
gruppe.character.id,
|
||||
gast,
|
||||
{ useKind: 'REDESIGN_PAID', predecessorId: eigener.character.id },
|
||||
ctx,
|
||||
);
|
||||
|
||||
const vorher = await credits.getBalances(gast);
|
||||
assert.equal(await characters.richteBuchungenAus(), 0, 'sein Beitritt war ein Redesign');
|
||||
assert.deepEqual(await credits.getBalances(gast), vorher);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Die Umstellung auf Listen je Nutzer', () => {
|
||||
const MIGRATION = join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'..',
|
||||
'prisma',
|
||||
'migrations',
|
||||
'20250117000000_kette_je_nutzer',
|
||||
'migration.sql',
|
||||
);
|
||||
|
||||
/**
|
||||
* Die Migration noch einmal anwenden.
|
||||
*
|
||||
* Jeder Schritt greift nur, wo noch keine Liste steht - so lässt sich der
|
||||
* Weg nachstellen, den der Bestand beim Aktualisieren nimmt. Zerlegt wird
|
||||
* an Semikolons, wobei ein DO-Block am Stück bleibt.
|
||||
*/
|
||||
async function migrationAusfuehren(): Promise<void> {
|
||||
const sql = readFileSync(MIGRATION, 'utf8');
|
||||
let puffer = '';
|
||||
for (const teil of sql.split(';')) {
|
||||
puffer += `${teil};`;
|
||||
if ((puffer.match(/\$\$/g) ?? []).length % 2 !== 0) continue;
|
||||
if (puffer.replace(/;/g, '').trim()) await db.prisma.$executeRawUnsafe(puffer);
|
||||
puffer = '';
|
||||
}
|
||||
}
|
||||
|
||||
it('ordnet einen Bestand ohne Listen wieder richtig zu', async () => {
|
||||
const wirt = await spielerMitPaketen('Altbestand-Wirt', 1);
|
||||
const gast = await spielerMitPaketen('Altbestand-Gast', 2);
|
||||
|
||||
// Eine Kette des Wirts, in der der Gast ab dem zweiten Glied mitzieht.
|
||||
const eins = await characters.createCharacter(wirt, { name: 'Alt-Eins' }, ctx);
|
||||
const zwei = await characters.redesignCharacter(
|
||||
eins.character.id,
|
||||
{ name: 'Alt-Zwei', useKind: 'REDESIGN_PAID' },
|
||||
ctx,
|
||||
);
|
||||
const drei = await characters.redesignCharacter(
|
||||
zwei.character.id,
|
||||
{ name: 'Alt-Drei', useKind: 'REDESIGN_PAID' },
|
||||
ctx,
|
||||
);
|
||||
|
||||
const eigener = await characters.createCharacter(gast, { name: 'Gasts Alter' }, ctx);
|
||||
await credits.bookRedesignPackage(gast, ctx);
|
||||
await characters.addMemberToChain(
|
||||
zwei.character.id,
|
||||
gast,
|
||||
{ useKind: 'REDESIGN_PAID', predecessorId: eigener.character.id },
|
||||
ctx,
|
||||
);
|
||||
|
||||
const vorher = new Map(
|
||||
(
|
||||
await db.prisma.characterMember.findMany({
|
||||
select: { id: true, lineageId: true, chainPosition: true },
|
||||
})
|
||||
).map((m) => [m.id, m]),
|
||||
);
|
||||
|
||||
// Zurück auf den Stand vor der Umstellung.
|
||||
await db.prisma.$executeRawUnsafe(
|
||||
'ALTER TABLE "CharacterMember" ALTER COLUMN "lineageId" DROP NOT NULL',
|
||||
);
|
||||
await db.prisma.$executeRawUnsafe(
|
||||
'UPDATE "CharacterMember" SET "lineageId" = NULL, "chainPosition" = 0',
|
||||
);
|
||||
|
||||
await migrationAusfuehren();
|
||||
|
||||
const danach = await db.prisma.characterMember.findMany({
|
||||
where: { playerId: gast },
|
||||
orderBy: { chainPosition: 'asc' },
|
||||
select: { characterId: true, lineageId: true, character: { select: { name: true } } },
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
danach.map((m) => m.character.name),
|
||||
['Gasts Alter', 'Alt-Zwei', 'Alt-Drei'],
|
||||
'der Gast führt seinen alten CC und die Glieder, ab denen er dabei war',
|
||||
);
|
||||
assert.equal(
|
||||
new Set(danach.map((m) => m.lineageId)).size,
|
||||
1,
|
||||
'und zwar in einer einzigen Liste',
|
||||
);
|
||||
|
||||
// Für den Wirt steht die Kette wieder so, wie sie war.
|
||||
const wirtDanach = await db.prisma.characterMember.findMany({
|
||||
where: { playerId: wirt },
|
||||
orderBy: { chainPosition: 'asc' },
|
||||
select: { id: true, lineageId: true, chainPosition: true },
|
||||
});
|
||||
assert.equal(buchung?.kind, 'CC');
|
||||
for (const m of wirtDanach) {
|
||||
assert.deepEqual(
|
||||
{ lineageId: m.lineageId, chainPosition: m.chainPosition },
|
||||
{
|
||||
lineageId: vorher.get(m.id)?.lineageId,
|
||||
chainPosition: vorher.get(m.id)?.chainPosition,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
await characters.richteBuchungenAus(),
|
||||
0,
|
||||
'und die Buchungen passen danach zu den Listen',
|
||||
);
|
||||
|
||||
// Der Gast zahlt für seinen Einstieg ein Redesign, nicht einen Platz.
|
||||
assert.equal(
|
||||
(
|
||||
await db.prisma.creditLedger.findFirst({
|
||||
where: {
|
||||
characterId: zwei.character.id,
|
||||
playerId: gast,
|
||||
source: 'CONSUME',
|
||||
reversedBy: null,
|
||||
},
|
||||
})
|
||||
)?.kind,
|
||||
'REDESIGN_PAID',
|
||||
);
|
||||
assert.ok(drei.character.id > 0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user