Pin a restore point and keep a note on it

Both live in the catalog on the location, so a new server sees them; the database holds
a local copy for the lists. The retention policy leaves a pinned restore point alone and
does not count it. Available on the restore page and as occ backup:pin and backup:note.
This commit is contained in:
2026-11-06 23:49:50 +01:00
parent 6540a9314a
commit 193f65940b
23 changed files with 464 additions and 9 deletions
@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace OCA\NgBackup\Tests\Unit\Repository;
use OCA\NgBackup\Backend\LocalBackend;
use OCA\NgBackup\Crypto\KeyRing;
use OCA\NgBackup\Repository\Repository;
use OCA\NgBackup\Tests\Unit\TempDirTrait;
use PHPUnit\Framework\TestCase;
/** A pin and a note on a snapshot live in the catalog on the location, so a new server sees them too. */
class SnapshotMarksTest extends TestCase {
use TempDirTrait;
private LocalBackend $backend;
private KeyRing $keys;
private string $snapshot;
protected function setUp(): void {
$this->backend = new LocalBackend($this->tempDir());
$this->keys = KeyRing::generate();
$src = $this->tempDir();
file_put_contents("$src/a", 'x');
$this->snapshot = Repository::initWithKey($this->backend, $this->keys, $this->keys->wrap('pw'))->backupDirectory($src)['snapshot'];
}
private function repo(): Repository {
return Repository::openWithKey($this->backend, $this->keys);
}
public function testTheyAreThereAfterOpeningAgain(): void {
$this->repo()->setPinned($this->snapshot, true);
$this->repo()->setSnapshotNote($this->snapshot, 'before the upgrade');
$this->assertSame([$this->snapshot], $this->repo()->pinnedSnapshots());
$this->assertSame([$this->snapshot => 'before the upgrade'], $this->repo()->snapshotNotes());
}
public function testTheyCanBeTakenOffAgain(): void {
$this->repo()->setPinned($this->snapshot, true);
$this->repo()->setSnapshotNote($this->snapshot, 'x');
$this->repo()->setPinned($this->snapshot, false);
$this->repo()->setSnapshotNote($this->snapshot, '');
$this->assertSame([], $this->repo()->pinnedSnapshots());
$this->assertSame([], $this->repo()->snapshotNotes());
}
public function testNoChangeWritesNoCatalog(): void {
$repo = $this->repo();
$repo->setPinned($this->snapshot, true);
$generation = $repo->catalogGeneration();
$repo->setPinned($this->snapshot, true);
$repo->setSnapshotNote($this->snapshot, '');
$this->assertSame($generation, $repo->catalogGeneration());
}
public function testASnapshotThatIsNotThereIsRefused(): void {
$this->expectException(\InvalidArgumentException::class);
$this->repo()->setPinned('0123456789abcdef0123456789abcdef', true);
}
public function testATrashedSnapshotLosesItsPinAndKeepsItsNote(): void {
$repo = $this->repo();
$repo->setPinned($this->snapshot, true);
$repo->setSnapshotNote($this->snapshot, 'kept');
$repo->forget($this->snapshot);
$this->assertSame([], $this->repo()->pinnedSnapshots());
$this->assertSame([$this->snapshot => 'kept'], $this->repo()->snapshotNotes());
$repo = $this->repo();
$repo->untrash($this->snapshot);
$this->assertSame([], $this->repo()->pinnedSnapshots(), 'back in the list, as a normal one');
$this->assertSame([$this->snapshot => 'kept'], $this->repo()->snapshotNotes());
}
public function testThePurgeTakesTheNoteAlong(): void {
$repo = $this->repo();
$repo->setSnapshotNote($this->snapshot, 'kept');
$repo->forget($this->snapshot);
$repo->purgeTrash(0);
$this->assertSame([], $this->repo()->snapshotNotes());
}
public function testACatalogFromBeforeThisVersionReadsAsNone(): void {
$this->assertSame([], $this->repo()->pinnedSnapshots());
$this->assertSame([], $this->repo()->snapshotNotes());
}
}
@@ -25,6 +25,17 @@ class RetentionPolicyTest extends TestCase {
$this->assertCount(3, $months);
}
public function testAPinnedSnapshotStaysAndDoesNotCount(): void {
$policy = new RetentionPolicy(1, 0, 0, 0);
$snaps = ['old' => 100, 'pinned' => 300, 'new' => 200];
$this->assertSame(['pinned'], array_keys($policy->keep($snaps)), 'without the pin it would take the place of "last"');
$keep = $policy->keepWithPinned($snaps, ['pinned', 'gone-already']);
$this->assertSame(['pinned'], $keep['pinned']);
$this->assertSame(['last'], $keep['new'], 'the newest one that is not pinned');
$this->assertArrayNotHasKey('old', $keep);
$this->assertArrayNotHasKey('gone-already', $keep);
}
public function testKeepsEverythingWhenFewSnapshots(): void {
$keep = (new RetentionPolicy())->keep(['a' => 100, 'b' => 200]);
$this->assertSame(['a', 'b'], array_values(array_intersect(['a', 'b'], array_keys($keep))));