add rollback tests and update test descriptions #3

Merged
maxp merged 5 commits from test/fail-write-and-rollback into main 2025-05-30 11:21:41 +02:00
Showing only changes of commit c4f4614a2d - Show all commits

View File

@@ -9,7 +9,7 @@ import { join } from 'https://deno.land/std@0.224.0/path/mod.ts';
import { resolveUnitTargetPath, writeUnitFiles } from '../mod.ts'; import { resolveUnitTargetPath, writeUnitFiles } from '../mod.ts';
import { TimerOptions } from '../../types/options.ts'; import { TimerOptions } from '../../types/options.ts';
Deno.test('writeUnitFiles schreibt .service und .timer korrekt', async () => { Deno.test('writeUnitFiles: writes .service and .timer files correctly', async () => {
const tmp = await Deno.makeTempDir({ prefix: 'test-units-' }); const tmp = await Deno.makeTempDir({ prefix: 'test-units-' });
const options = { const options = {
@@ -28,15 +28,15 @@ Deno.test('writeUnitFiles schreibt .service und .timer korrekt', async () => {
options as TimerOptions, options as TimerOptions,
) as { servicePath: string; timerPath: string }; ) as { servicePath: string; timerPath: string };
// Überprüfe Pfade // Check file paths
assertEquals(servicePath, join(tmp, 'test-backup.service')); assertEquals(servicePath, join(tmp, 'test-backup.service'));
assertEquals(timerPath, join(tmp, 'test-backup.timer')); assertEquals(timerPath, join(tmp, 'test-backup.timer'));
// Existieren Dateien? // Check if files exist
assertExists(await Deno.stat(servicePath)); assertExists(await Deno.stat(servicePath));
assertExists(await Deno.stat(timerPath)); assertExists(await Deno.stat(timerPath));
// Enthält die Datei den erwarteten Inhalt? // Check if file contents match expectations
const readService = await Deno.readTextFile(servicePath); const readService = await Deno.readTextFile(servicePath);
const readTimer = await Deno.readTextFile(timerPath); const readTimer = await Deno.readTextFile(timerPath);
@@ -44,23 +44,23 @@ Deno.test('writeUnitFiles schreibt .service und .timer korrekt', async () => {
assertStringIncludes(readTimer, 'OnCalendar=daily'); assertStringIncludes(readTimer, 'OnCalendar=daily');
}); });
Deno.test('resolveUnitTargetPath mit --output', () => { Deno.test('resolveUnitTargetPath: with --output', () => {
const result = resolveUnitTargetPath({ output: '/tmp/units', user: false }); const result = resolveUnitTargetPath({ output: '/tmp/units', user: false });
assertEquals(result, '/tmp/units'); assertEquals(result, '/tmp/units');
}); });
Deno.test('resolveUnitTargetPath mit --user ohne output', () => { Deno.test('resolveUnitTargetPath: with --user and no output', () => {
Deno.env.set('HOME', '/home/maxp'); Deno.env.set('HOME', '/home/maxp');
const result = resolveUnitTargetPath({ output: undefined, user: true }); const result = resolveUnitTargetPath({ output: undefined, user: true });
assertEquals(result, '/home/maxp/.config/systemd/user'); assertEquals(result, '/home/maxp/.config/systemd/user');
}); });
Deno.test('resolveUnitTargetPath ohne output und ohne user', () => { Deno.test('resolveUnitTargetPath: with no output and no user', () => {
const result = resolveUnitTargetPath({ output: undefined, user: false }); const result = resolveUnitTargetPath({ output: undefined, user: false });
assertEquals(result, '/etc/systemd/system'); assertEquals(result, '/etc/systemd/system');
}); });
Deno.test('writeUnitFiles: Fehler beim Schreiben der .timer-Datei führt zu Rollback', async () => { Deno.test('writeUnitFiles: error writing .timer file triggers rollback', async () => {
const tmp = await Deno.makeTempDir(); const tmp = await Deno.makeTempDir();
const options: TimerOptions = { const options: TimerOptions = {
@@ -77,14 +77,14 @@ Deno.test('writeUnitFiles: Fehler beim Schreiben der .timer-Datei führt zu Roll
const servicePath = join(tmp, `${name}.service`); const servicePath = join(tmp, `${name}.service`);
const timerPath = join(tmp, `${name}.timer`); const timerPath = join(tmp, `${name}.timer`);
// Simuliere: Schreiben der .timer-Datei schlägt fehl // Simulate: writing the .timer file fails
const originalWrite = Deno.writeTextFile; const originalWrite = Deno.writeTextFile;
const writeStub = stub( const writeStub = stub(
Deno, Deno,
'writeTextFile', 'writeTextFile',
async (path: string | URL, data: string | ReadableStream<string>) => { async (path: string | URL, data: string | ReadableStream<string>) => {
if (typeof path === 'string' && path.endsWith('.timer')) { if (typeof path === 'string' && path.endsWith('.timer')) {
throw new Error('Simulierter Schreibfehler'); throw new Error('Simulated write error');
} else { } else {
return await originalWrite(path, data); return await originalWrite(path, data);
} }
@@ -98,17 +98,17 @@ Deno.test('writeUnitFiles: Fehler beim Schreiben der .timer-Datei führt zu Roll
options, options,
); );
// Erwartung: Funktion gibt undefined zurück // Expect: function returns undefined
assertEquals(result, undefined); assertEquals(result, undefined);
// Erwartung: Beide Dateien wurden gelöscht (Rollback) // Expect: both files have been deleted (rollback)
assertEquals(await exists(servicePath), false); assertEquals(await exists(servicePath), false);
assertEquals(await exists(timerPath), false); assertEquals(await exists(timerPath), false);
writeStub.restore(); writeStub.restore();
}); });
Deno.test('writeUnitFiles: Fehler beim Schreiben der .service-Datei verhindert Folgeaktionen', async () => { Deno.test('writeUnitFiles: error writing .service file prevents further actions', async () => {
const tmp = await Deno.makeTempDir(); const tmp = await Deno.makeTempDir();
const options: TimerOptions = { const options: TimerOptions = {
@@ -125,13 +125,13 @@ Deno.test('writeUnitFiles: Fehler beim Schreiben der .service-Datei verhindert F
const servicePath = join(tmp, `${name}.service`); const servicePath = join(tmp, `${name}.service`);
const timerPath = join(tmp, `${name}.timer`); const timerPath = join(tmp, `${name}.timer`);
// Simuliere: Fehler beim Schreiben der .service-Datei // Simulate: error writing the .service file
const writeStub = stub( const writeStub = stub(
Deno, Deno,
'writeTextFile', 'writeTextFile',
(path: string | URL, _data: string | ReadableStream<string>) => { (path: string | URL, _data: string | ReadableStream<string>) => {
if (typeof path === 'string' && path.endsWith('.service')) { if (typeof path === 'string' && path.endsWith('.service')) {
throw new Error('Simulierter Service-Schreibfehler'); throw new Error('Simulated service write error');
} }
return Promise.resolve(); return Promise.resolve();
}, },
@@ -144,17 +144,17 @@ Deno.test('writeUnitFiles: Fehler beim Schreiben der .service-Datei verhindert F
options, options,
); );
// Erwartung: Funktion gibt undefined zurück // Expect: function returns undefined
assertEquals(result, undefined); assertEquals(result, undefined);
// Erwartung: Es wurden keine Dateien angelegt // Expect: no files were created
assertEquals(await exists(servicePath), false); assertEquals(await exists(servicePath), false);
assertEquals(await exists(timerPath), false); assertEquals(await exists(timerPath), false);
writeStub.restore(); writeStub.restore();
}); });
Deno.test('writeUnitFiles: beide Dateien geschrieben, danach Fehler → vollständiger Rollback', async () => { Deno.test('writeUnitFiles: both files written, then error → full rollback', async () => {
const tmp = await Deno.makeTempDir(); const tmp = await Deno.makeTempDir();
const options: TimerOptions = { const options: TimerOptions = {
@@ -179,18 +179,16 @@ Deno.test('writeUnitFiles: beide Dateien geschrieben, danach Fehler → vollstä
'writeTextFile', 'writeTextFile',
async (path: string | URL, data: string | ReadableStream<string>) => { async (path: string | URL, data: string | ReadableStream<string>) => {
if (typeof path !== 'string') { if (typeof path !== 'string') {
throw new Error('Unerwarteter Pfadtyp'); throw new Error('Unexpected path type');
} }
// Simuliere beide Schreibvorgänge, aber wirf nach dem zweiten eine Exception // Simulate both writes, then throw an error after the second
writeCount++; writeCount++;
await originalWriteTextFile(path, data); // wirklich schreiben await originalWriteTextFile(path, data);
if (writeCount === 2) { if (writeCount === 2) {
throw new Error( throw new Error('Simulated error after full write');
'Simulierter Fehler nach vollständigem Schreiben',
);
} }
}, },
); );
@@ -202,17 +200,17 @@ Deno.test('writeUnitFiles: beide Dateien geschrieben, danach Fehler → vollstä
options, options,
); );
// Erwartung: Funktion gibt undefined zurück // Expect: function returns undefined
assertEquals(result, undefined); assertEquals(result, undefined);
// Erwartung: Beide Dateien wurden wieder entfernt // Expect: both files were removed
assertEquals(await exists(servicePath), false); assertEquals(await exists(servicePath), false);
assertEquals(await exists(timerPath), false); assertEquals(await exists(timerPath), false);
writeStub.restore(); writeStub.restore();
}); });
Deno.test('writeUnitFiles: Rollback schlägt fehl, wenn Dateien nicht gelöscht werden können', async () => { Deno.test('writeUnitFiles: rollback fails if files cannot be deleted', async () => {
const tmp = await Deno.makeTempDir(); const tmp = await Deno.makeTempDir();
const options: TimerOptions = { const options: TimerOptions = {
@@ -229,9 +227,8 @@ Deno.test('writeUnitFiles: Rollback schlägt fehl, wenn Dateien nicht gelöscht
const servicePath = join(tmp, `${name}.service`); const servicePath = join(tmp, `${name}.service`);
const timerPath = join(tmp, `${name}.timer`); const timerPath = join(tmp, `${name}.timer`);
// Originale Methoden sichern
const originalWriteTextFile = Deno.writeTextFile; const originalWriteTextFile = Deno.writeTextFile;
const originalRemove = Deno.remove; const _originalRemove = Deno.remove;
let writeCount = 0; let writeCount = 0;
@@ -242,7 +239,7 @@ Deno.test('writeUnitFiles: Rollback schlägt fehl, wenn Dateien nicht gelöscht
writeCount++; writeCount++;
await originalWriteTextFile(path, data); await originalWriteTextFile(path, data);
if (writeCount === 2) { if (writeCount === 2) {
throw new Error('Fehler nach vollständigem Schreiben'); throw new Error('Error after full write');
} }
}, },
); );
@@ -252,11 +249,10 @@ Deno.test('writeUnitFiles: Rollback schlägt fehl, wenn Dateien nicht gelöscht
'remove', 'remove',
// deno-lint-ignore require-await // deno-lint-ignore require-await
async (_path: string | URL, _opts?: Deno.RemoveOptions) => { async (_path: string | URL, _opts?: Deno.RemoveOptions) => {
throw new Error('Löschen verboten!'); throw new Error('Deletion forbidden!');
}, },
); );
// capture console output
const logs: string[] = []; const logs: string[] = [];
const consoleStub = stub(console, 'error', (...args) => { const consoleStub = stub(console, 'error', (...args) => {
logs.push(args.map((a) => String(a)).join(' ')); logs.push(args.map((a) => String(a)).join(' '));
@@ -270,15 +266,14 @@ Deno.test('writeUnitFiles: Rollback schlägt fehl, wenn Dateien nicht gelöscht
); );
assertEquals(result, undefined); assertEquals(result, undefined);
// Dateien existieren noch, weil löschen fehlschlug // Files still exist because deletion failed
assertEquals(await exists(servicePath), true); assertEquals(await exists(servicePath), true);
assertEquals(await exists(timerPath), true); assertEquals(await exists(timerPath), true);
// Fehlerausgabe enthält "rollback_failed" // Error output contains "rollback_failed"
const combinedLogs = logs.join('\n'); const combinedLogs = logs.join('\n');
assertStringIncludes(combinedLogs, 'rollback_failed'); assertStringIncludes(combinedLogs, 'rollback_failed');
// Cleanup
writeStub.restore(); writeStub.restore();
removeStub.restore(); removeStub.restore();
consoleStub.restore(); consoleStub.restore();