Release 0.6.0
This commit is contained in:
1 parent
fd47910d09
commit
b80dded55d
40 files changed
+4080
-856
No files matched your search
@@ -0,0 +1,204 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import type { AuthUser } from './auth.js';
|
||||
import { finalizeShow, getCompletionSummary } from './completion.js';
|
||||
import { closeDatabase, getDatabase } from './database.js';
|
||||
import {
|
||||
addPieceUsage,
|
||||
addShowSection,
|
||||
createShow,
|
||||
duplicateCompletedShow,
|
||||
getPieceUsageContexts,
|
||||
getShow
|
||||
} from './shows.js';
|
||||
|
||||
let directory: string;
|
||||
const actor: AuthUser = {
|
||||
id: 'completion-actor',
|
||||
email: 'completion@example.test',
|
||||
displayName: 'Abschluss Test',
|
||||
role: 'barkeeper',
|
||||
lockedAtUtc: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
directory = mkdtempSync(path.join(tmpdir(), 'sendewerk-completion-test-'));
|
||||
process.env.SENDEWERK_DB_PATH = path.join(directory, 'test.sqlite');
|
||||
process.env.SENDEWERK_UPLOAD_DIR = path.join(directory, 'uploads');
|
||||
execFileSync(process.execPath, ['scripts/migrate.mjs'], { cwd: process.cwd(), env: process.env });
|
||||
execFileSync(process.execPath, ['scripts/seed.mjs'], { cwd: process.cwd(), env: process.env });
|
||||
getDatabase()
|
||||
.prepare(
|
||||
`INSERT INTO users (id, email, email_normalized, display_name, password_hash, role,
|
||||
password_changed_at_utc, created_at_utc, updated_at_utc)
|
||||
VALUES (?, ?, ?, ?, 'unused', 'barkeeper', ?, ?, ?)`
|
||||
)
|
||||
.run(
|
||||
actor.id,
|
||||
actor.email,
|
||||
actor.email,
|
||||
actor.displayName,
|
||||
...Array(3).fill(new Date().toISOString())
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
closeDatabase();
|
||||
delete process.env.SENDEWERK_DB_PATH;
|
||||
delete process.env.SENDEWERK_UPLOAD_DIR;
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function endedShow() {
|
||||
const created = createShow({ title: 'Finale Sendung', startsAtUtc: null, templateId: null });
|
||||
const section = addShowSection(created.id, created.revision, {
|
||||
name: 'Gesendeter Block',
|
||||
categoryId: 'cat-jc',
|
||||
explicitDurationSeconds: null
|
||||
});
|
||||
const done = addPieceUsage(created.id, section.revision, {
|
||||
pieceId: 'piece-console',
|
||||
sectionId: section.id
|
||||
});
|
||||
const skipped = addPieceUsage(created.id, done.revision, {
|
||||
pieceId: 'piece-console',
|
||||
sectionId: section.id
|
||||
});
|
||||
const database = getDatabase();
|
||||
database.prepare("UPDATE piece_usages SET status = 'done' WHERE id = ?").run(done.id);
|
||||
database.prepare("UPDATE piece_usages SET status = 'skipped' WHERE id = ?").run(skipped.id);
|
||||
database
|
||||
.prepare(
|
||||
`INSERT INTO show_live_states
|
||||
(show_id, revision, started_at_utc, ended_at_utc, presentation_revision, updated_at_utc)
|
||||
VALUES (?, 0, ?, ?, 0, ?)`
|
||||
)
|
||||
.run(
|
||||
created.id,
|
||||
'2026-10-25T18:00:00.000Z',
|
||||
'2026-10-25T19:00:00.000Z',
|
||||
'2026-10-25T19:00:00.000Z'
|
||||
);
|
||||
return { showId: created.id, doneUsageId: done.id, revision: skipped.revision };
|
||||
}
|
||||
|
||||
describe('show completion', () => {
|
||||
it('finalizes once, records only sent usages and archives only the explicit selection', () => {
|
||||
const prepared = endedShow();
|
||||
expect(getCompletionSummary(prepared.showId)).toMatchObject({
|
||||
doneCount: 1,
|
||||
skippedCount: 1,
|
||||
candidates: [{ pieceId: 'piece-console', usageCount: 1 }]
|
||||
});
|
||||
const first = finalizeShow({
|
||||
showId: prepared.showId,
|
||||
commandId: 'complete-once',
|
||||
expectedShowRevision: prepared.revision,
|
||||
expectedLiveRevision: 0,
|
||||
archivePieceIds: ['piece-console'],
|
||||
actor,
|
||||
now: new Date('2026-10-25T19:05:00.000Z')
|
||||
});
|
||||
expect(first.duplicate).toBe(false);
|
||||
expect(
|
||||
finalizeShow({
|
||||
showId: prepared.showId,
|
||||
commandId: 'complete-once',
|
||||
expectedShowRevision: prepared.revision,
|
||||
expectedLiveRevision: 0,
|
||||
archivePieceIds: ['piece-console'],
|
||||
actor
|
||||
}).duplicate
|
||||
).toBe(true);
|
||||
expect(() =>
|
||||
finalizeShow({
|
||||
showId: prepared.showId,
|
||||
commandId: 'different-command',
|
||||
expectedShowRevision: prepared.revision,
|
||||
expectedLiveRevision: 0,
|
||||
archivePieceIds: [],
|
||||
actor
|
||||
})
|
||||
).toThrow('SHOW_COMPLETED');
|
||||
const database = getDatabase();
|
||||
expect(
|
||||
(
|
||||
database
|
||||
.prepare('SELECT count(*) AS count FROM piece_usage_history WHERE show_id = ?')
|
||||
.get(prepared.showId) as { count: number }
|
||||
).count
|
||||
).toBe(1);
|
||||
expect(database.prepare('SELECT status FROM pieces WHERE id = ?').get('piece-console')).toEqual(
|
||||
{ status: 'archived' }
|
||||
);
|
||||
expect(getPieceUsageContexts(['piece-console'])['piece-console'].sentHistory).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('keeps historical snapshots stable and duplicates them into an editable clean run', () => {
|
||||
const prepared = endedShow();
|
||||
finalizeShow({
|
||||
showId: prepared.showId,
|
||||
commandId: 'freeze',
|
||||
expectedShowRevision: prepared.revision,
|
||||
expectedLiveRevision: 0,
|
||||
archivePieceIds: [],
|
||||
actor
|
||||
});
|
||||
const historical = getShow(prepared.showId)!;
|
||||
getDatabase()
|
||||
.prepare("UPDATE section_categories SET name = 'Später geändert' WHERE id = 'cat-jc'")
|
||||
.run();
|
||||
getDatabase()
|
||||
.prepare("UPDATE pieces SET title = 'Später geändert' WHERE id = 'piece-console'")
|
||||
.run();
|
||||
const unchanged = getShow(prepared.showId)!;
|
||||
expect(unchanged.sections[0].category).toBe(historical.sections[0].category);
|
||||
expect(unchanged.sections[0].usages[0].title).toBe(historical.sections[0].usages[0].title);
|
||||
|
||||
const copy = duplicateCompletedShow(prepared.showId, 'copy-once');
|
||||
expect(duplicateCompletedShow(prepared.showId, 'copy-once').id).toBe(copy.id);
|
||||
expect(getShow(copy.id)).toMatchObject({
|
||||
status: 'draft',
|
||||
startsAtUtc: null,
|
||||
sections: [
|
||||
{
|
||||
category: historical.sections[0].category,
|
||||
usages: [{ status: 'open' }, { status: 'open' }]
|
||||
}
|
||||
]
|
||||
});
|
||||
expect(
|
||||
getDatabase().prepare('SELECT 1 FROM show_finalizations WHERE show_id = ?').get(copy.id)
|
||||
).toBeUndefined();
|
||||
getDatabase()
|
||||
.prepare(
|
||||
`UPDATE piece_usages SET status = 'done'
|
||||
WHERE show_section_id IN (SELECT id FROM show_sections WHERE show_id = ?)`
|
||||
)
|
||||
.run(copy.id);
|
||||
getDatabase()
|
||||
.prepare(
|
||||
`INSERT INTO show_live_states
|
||||
(show_id, revision, started_at_utc, ended_at_utc, presentation_revision, updated_at_utc)
|
||||
VALUES (?, 0, ?, ?, 0, ?)`
|
||||
)
|
||||
.run(
|
||||
copy.id,
|
||||
'2026-10-26T18:00:00.000Z',
|
||||
'2026-10-26T19:00:00.000Z',
|
||||
'2026-10-26T19:00:00.000Z'
|
||||
);
|
||||
finalizeShow({
|
||||
showId: copy.id,
|
||||
commandId: 'finalize-copy',
|
||||
expectedShowRevision: 1,
|
||||
expectedLiveRevision: 0,
|
||||
archivePieceIds: [],
|
||||
actor
|
||||
});
|
||||
expect(getShow(copy.id)?.sections[0].category).toBe(historical.sections[0].category);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user