fix: write reference sources before fingerprints.json, atomic on failure
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -172,13 +172,21 @@ final class DiviCheckCommand
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
DiviReference::writeExpected(
|
|
||||||
array_map(static fn (SourceFingerprint $print): string => $print->hash, $prints),
|
|
||||||
$this->expectedFile
|
|
||||||
);
|
|
||||||
$sourceDir = ($this->sourceDir ?? DiviReference::sourceDir(...))();
|
$sourceDir = ($this->sourceDir ?? DiviReference::sourceDir(...))();
|
||||||
foreach ($sourceDir === null ? [] : $prints as $id => $print) {
|
try {
|
||||||
DiviReference::writeSource($id, $print->source, $sourceDir);
|
// Sources first, fingerprints.json last: the fingerprints decide the release gate, so a write that
|
||||||
|
// fails before this last one leaves the gate exactly as it was.
|
||||||
|
foreach ($sourceDir === null ? [] : $prints as $id => $print) {
|
||||||
|
DiviReference::writeSource($id, $print->source, $sourceDir);
|
||||||
|
}
|
||||||
|
DiviReference::writeExpected(
|
||||||
|
array_map(static fn (SourceFingerprint $print): string => $print->hash, $prints),
|
||||||
|
$this->expectedFile
|
||||||
|
);
|
||||||
|
} catch (RuntimeException $e) {
|
||||||
|
($this->out)('Referenz nicht vollständig aktualisiert: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
delete_transient(ReleaseResolver::CACHE);
|
delete_transient(ReleaseResolver::CACHE);
|
||||||
ReleaseGate::forget();
|
ReleaseGate::forget();
|
||||||
|
|||||||
@@ -44,8 +44,12 @@ final class DiviCheckCommandTest extends TestCase
|
|||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param array<string, list<string>> $problems Step key => problems. */
|
/**
|
||||||
private function command(array $problems = []): DiviCheckCommand
|
* @param array<string, list<string>> $problems Step key => problems.
|
||||||
|
* @param bool $expectSave True asserts save() is called exactly once (the green happy path); otherwise it is
|
||||||
|
* merely allowed, since some tests (e.g. no admin) never reach the save() call.
|
||||||
|
*/
|
||||||
|
private function command(array $problems = [], bool $expectSave = false): DiviCheckCommand
|
||||||
{
|
{
|
||||||
$steps = array_map(
|
$steps = array_map(
|
||||||
static fn (string $key): CheckStep => new CheckStep($key, $problems[$key] ?? []),
|
static fn (string $key): CheckStep => new CheckStep($key, $problems[$key] ?? []),
|
||||||
@@ -54,7 +58,11 @@ final class DiviCheckCommandTest extends TestCase
|
|||||||
$check = Mockery::mock(CompatibilityCheck::class);
|
$check = Mockery::mock(CompatibilityCheck::class);
|
||||||
$check->allows('run')->andReturn(new CheckReport('r1', 'd', 1, '5.3.0', '0.6.0', 'k', $steps));
|
$check->allows('run')->andReturn(new CheckReport('r1', 'd', 1, '5.3.0', '0.6.0', 'k', $steps));
|
||||||
$reports = Mockery::mock(CheckReportStore::class);
|
$reports = Mockery::mock(CheckReportStore::class);
|
||||||
$reports->allows('save');
|
if ($expectSave) {
|
||||||
|
$reports->expects('save')->once();
|
||||||
|
} else {
|
||||||
|
$reports->allows('save');
|
||||||
|
}
|
||||||
|
|
||||||
return new DiviCheckCommand(
|
return new DiviCheckCommand(
|
||||||
static fn () => $check,
|
static fn () => $check,
|
||||||
@@ -78,7 +86,7 @@ final class DiviCheckCommandTest extends TestCase
|
|||||||
|
|
||||||
public function testPrintsTheStepsAndSucceedsWhenGreen(): void
|
public function testPrintsTheStepsAndSucceedsWhenGreen(): void
|
||||||
{
|
{
|
||||||
self::assertSame(0, $this->command()->run([]));
|
self::assertSame(0, $this->command(expectSave: true)->run([]));
|
||||||
self::assertContains('✓ Schreibtest auf einer Wegwerfseite', $this->lines);
|
self::assertContains('✓ Schreibtest auf einer Wegwerfseite', $this->lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +142,25 @@ final class DiviCheckCommandTest extends TestCase
|
|||||||
self::assertFileDoesNotExist($this->expectedFile, 'all or nothing');
|
self::assertFileDoesNotExist($this->expectedFile, 'all or nothing');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testUpdateReferenceWritesNothingWhenASourceIsUnwritable(): void
|
||||||
|
{
|
||||||
|
$this->writeDivi();
|
||||||
|
chmod($this->sourceDir, 0500);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$code = $this->command([CheckStep::MIRRORS => ['geändert']])->run(['update-reference' => true]);
|
||||||
|
|
||||||
|
self::assertSame(1, $code);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
'Referenz nicht vollständig aktualisiert:',
|
||||||
|
implode("\n", $this->lines)
|
||||||
|
);
|
||||||
|
self::assertFileDoesNotExist($this->expectedFile, 'sources are written before fingerprints.json');
|
||||||
|
} finally {
|
||||||
|
chmod($this->sourceDir, 0777);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testUpdateReferenceWritesFingerprintsAndSources(): void
|
public function testUpdateReferenceWritesFingerprintsAndSources(): void
|
||||||
{
|
{
|
||||||
$this->writeDivi();
|
$this->writeDivi();
|
||||||
|
|||||||
Reference in New Issue
Block a user