feat: implement save mechanisim

Signed-off-by: Sebastian <krupinski01@gmail.com>
This commit is contained in:
2026-08-18 22:30:42 -04:00
parent 67024b5bfd
commit 684d50062c
8 changed files with 359 additions and 15 deletions
@@ -154,6 +154,109 @@ final class CompositionSynchronizationTest extends TestCase {
$this->assertNull($snapshot['remote']['entity']);
}
public function testInlineSaveSynchronizesAndPurgesLocalComposition(): void {
$this->stageComposition();
$properties = $this->createStub(MessagePropertiesMutableInterface::class);
$service = $this->mutableService(['draftTarget' => 'drafts'], $properties);
$created = $this->entity('provider', 'service', 'drafts', 'created-inline');
$mailManager = $this->createMock(\KTXM\MailManager\Manager::class);
$mailManager->method('serviceFindByAddress')->willReturn($service);
$mailManager->expects($this->once())
->method('entityCreate')
->willReturn($created);
$result = $this->compositionManager($mailManager)->save(
self::TENANT_ID,
self::USER_ID,
self::COMPOSITION_ID,
[
'revision' => 2,
'sender' => ['address' => 'sender@example.com'],
'message' => ['subject' => 'Saved inline'],
],
);
$this->assertSame('saved', $result['disposition']);
$this->assertNull($this->compositionStore->compositionFetch(
self::TENANT_ID,
self::USER_ID,
self::COMPOSITION_ID,
));
}
public function testInlineSaveFailureKeepsLocalComposition(): void {
$this->stageComposition();
$mailManager = $this->createStub(\KTXM\MailManager\Manager::class);
$mailManager->method('serviceFindByAddress')->willReturn(null);
$result = $this->compositionManager($mailManager)->save(
self::TENANT_ID,
self::USER_ID,
self::COMPOSITION_ID,
[
'revision' => 2,
'sender' => ['address' => 'sender@example.com'],
'message' => ['subject' => 'Keep this draft'],
],
);
$snapshot = $this->compositionStore->compositionFetch(self::TENANT_ID, self::USER_ID, self::COMPOSITION_ID);
$this->assertSame('error', $result['disposition']);
$this->assertSame('composition_save_failed', $result['error']['type']);
$this->assertSame('Keep this draft', $snapshot['message']['subject']);
$this->assertSame('failed', $snapshot['remote']['status']);
}
public function testDiscardDeletesRemoteDraftBeforePurgingLocalComposition(): void {
$remote = 'provider:service:drafts:remote-1';
$this->stageComposition($remote);
$mailManager = $this->createMock(\KTXM\MailManager\Manager::class);
$mailManager->expects($this->once())
->method('entityDelete')
->with(
self::TENANT_ID,
self::USER_ID,
$this->callback(static fn(EntityIdentifier $target): bool => (string)$target === $remote),
)
->willReturn([$remote => ['disposition' => 'deleted']]);
$result = $this->compositionManager($mailManager)->discard(
self::TENANT_ID,
self::USER_ID,
self::COMPOSITION_ID,
);
$this->assertSame('discarded', $result['disposition']);
$this->assertNull($this->compositionStore->compositionFetch(
self::TENANT_ID,
self::USER_ID,
self::COMPOSITION_ID,
));
}
public function testRemoteDiscardFailureKeepsLocalComposition(): void {
$remote = 'provider:service:drafts:remote-1';
$this->stageComposition($remote);
$mailManager = $this->createMock(\KTXM\MailManager\Manager::class);
$mailManager->method('entityDelete')
->willReturn([$remote => ['disposition' => 'error', 'error' => 'Provider unavailable']]);
$result = $this->compositionManager($mailManager)->discard(
self::TENANT_ID,
self::USER_ID,
self::COMPOSITION_ID,
);
$this->assertSame('error', $result['disposition']);
$this->assertSame('Provider unavailable', $result['error']['message']);
$this->assertNotNull($this->compositionStore->compositionFetch(
self::TENANT_ID,
self::USER_ID,
self::COMPOSITION_ID,
));
}
private function stageComposition(?string $remoteEntity = null): void {
$saveComposition = static fn(): array => [
'identifier' => self::COMPOSITION_ID,