486 lines
21 KiB
PHP
486 lines
21 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace MintyPHP\Tests\Service\User;
|
||
|
|
|
||
|
|
use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
||
|
|
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
||
|
|
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
||
|
|
use MintyPHP\Service\Audit\UserLifecycleAuditService;
|
||
|
|
use MintyPHP\Service\User\UserLifecycleRestoreService;
|
||
|
|
use MintyPHP\Service\User\UserSettingsGateway;
|
||
|
|
use PHPUnit\Framework\TestCase;
|
||
|
|
|
||
|
|
class UserLifecycleRestoreServiceTest extends TestCase
|
||
|
|
{
|
||
|
|
// ── Full success path ───────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreFullSuccessPath(): void
|
||
|
|
{
|
||
|
|
$event = [
|
||
|
|
'id' => 1,
|
||
|
|
'restored_at' => null,
|
||
|
|
'snapshot_enc' => 'enc-data',
|
||
|
|
'policy_deactivate_days' => 30,
|
||
|
|
'policy_delete_days' => 90,
|
||
|
|
];
|
||
|
|
|
||
|
|
$snapshot = [
|
||
|
|
'uuid' => 'restored-uuid',
|
||
|
|
'email' => 'restored@example.com',
|
||
|
|
'first_name' => 'Max',
|
||
|
|
'last_name' => 'Mustermann',
|
||
|
|
'theme' => 'dark',
|
||
|
|
];
|
||
|
|
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->expects($this->once())
|
||
|
|
->method('findDeleteEventForRestore')
|
||
|
|
->with(1, true)
|
||
|
|
->willReturn($event);
|
||
|
|
$auditService->expects($this->once())
|
||
|
|
->method('decryptSnapshot')
|
||
|
|
->with($event)
|
||
|
|
->willReturn($snapshot);
|
||
|
|
$auditService->expects($this->once())
|
||
|
|
->method('markDeleteEventRestored')
|
||
|
|
->with(1, 99, 77)
|
||
|
|
->willReturn(true);
|
||
|
|
$auditService->expects($this->once())
|
||
|
|
->method('logRestore')
|
||
|
|
->with(
|
||
|
|
$this->isString(),
|
||
|
|
'manual',
|
||
|
|
$this->callback(static fn (array $policy): bool => $policy['deactivate_days'] === 30 && $policy['delete_days'] === 90),
|
||
|
|
99,
|
||
|
|
$this->callback(static fn (array $user): bool => $user['id'] === 77 && $user['uuid'] === 'restored-uuid' && $user['email'] === 'restored@example.com'),
|
||
|
|
'success',
|
||
|
|
null
|
||
|
|
);
|
||
|
|
|
||
|
|
$userReadRepo = $this->createMock(UserReadRepositoryInterface::class);
|
||
|
|
$userReadRepo->expects($this->once())->method('findByUuid')->with('restored-uuid')->willReturn(null);
|
||
|
|
$userReadRepo->expects($this->once())->method('findByEmail')->with('restored@example.com')->willReturn(null);
|
||
|
|
|
||
|
|
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||
|
|
$userWriteRepo->expects($this->once())
|
||
|
|
->method('create')
|
||
|
|
->with($this->callback(static function (array $data): bool {
|
||
|
|
return $data['uuid'] === 'restored-uuid'
|
||
|
|
&& $data['email'] === 'restored@example.com'
|
||
|
|
&& $data['first_name'] === 'Max'
|
||
|
|
&& $data['last_name'] === 'Mustermann'
|
||
|
|
&& $data['active'] === 0
|
||
|
|
&& $data['created_by'] === 99
|
||
|
|
&& $data['primary_tenant_id'] === null
|
||
|
|
&& $data['current_tenant_id'] === null
|
||
|
|
&& $data['totp_secret'] === '';
|
||
|
|
}))
|
||
|
|
->willReturn(77);
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('beginTransaction');
|
||
|
|
$dbSession->expects($this->once())->method('commitTransaction');
|
||
|
|
$dbSession->expects($this->never())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$settingsGateway = $this->createMock(UserSettingsGateway::class);
|
||
|
|
$settingsGateway->method('normalizeTheme')->willReturn('dark');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userReadRepository: $userReadRepo,
|
||
|
|
userWriteRepository: $userWriteRepo,
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession,
|
||
|
|
userSettingsGateway: $settingsGateway
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertTrue($result['ok']);
|
||
|
|
$this->assertSame(77, $result['restored_user_id']);
|
||
|
|
$this->assertSame('restored-uuid', $result['restored_user_uuid']);
|
||
|
|
$this->assertSame(1, $result['audit_id']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── invalid_request ─────────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreReturnsInvalidRequestWhenAuditIdIsZero(): void
|
||
|
|
{
|
||
|
|
$service = $this->newService();
|
||
|
|
$result = $service->restoreFromLifecycleAudit(0, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('invalid_request', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testRestoreReturnsInvalidRequestWhenAuditIdIsNegative(): void
|
||
|
|
{
|
||
|
|
$service = $this->newService();
|
||
|
|
$result = $service->restoreFromLifecycleAudit(-1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('invalid_request', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testRestoreReturnsInvalidRequestWhenActorUserIdIsZero(): void
|
||
|
|
{
|
||
|
|
$service = $this->newService();
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 0);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('invalid_request', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testRestoreReturnsInvalidRequestWhenActorUserIdIsNegative(): void
|
||
|
|
{
|
||
|
|
$service = $this->newService();
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, -5);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('invalid_request', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── audit_event_not_found ───────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreReturnsAuditEventNotFoundWhenEventIsNull(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->expects($this->once())
|
||
|
|
->method('findDeleteEventForRestore')
|
||
|
|
->with(1, true)
|
||
|
|
->willReturn(null);
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('beginTransaction');
|
||
|
|
$dbSession->expects($this->never())->method('commitTransaction');
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('audit_event_not_found', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── audit_event_already_restored ────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreReturnsAlreadyRestoredWhenEventHasRestoredAt(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')->willReturn([
|
||
|
|
'id' => 1,
|
||
|
|
'restored_at' => '2025-01-01 00:00:00',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('audit_event_already_restored', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── snapshot_unavailable ────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreReturnsSnapshotUnavailableWhenDecryptReturnsNull(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')->willReturn([
|
||
|
|
'id' => 1,
|
||
|
|
'restored_at' => null,
|
||
|
|
]);
|
||
|
|
$auditService->method('decryptSnapshot')->willReturn(null);
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('snapshot_unavailable', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── snapshot_invalid ────────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreReturnsSnapshotInvalidWhenUuidIsMissing(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')->willReturn(['id' => 1, 'restored_at' => null]);
|
||
|
|
$auditService->method('decryptSnapshot')->willReturn(['uuid' => '', 'email' => 'a@b.com']);
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('snapshot_invalid', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testRestoreReturnsSnapshotInvalidWhenEmailIsMissing(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')->willReturn(['id' => 1, 'restored_at' => null]);
|
||
|
|
$auditService->method('decryptSnapshot')->willReturn(['uuid' => 'u1', 'email' => '']);
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('snapshot_invalid', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── restore_uuid_exists ─────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreReturnsUuidExistsWhenUserWithUuidAlreadyExists(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')->willReturn(['id' => 1, 'restored_at' => null]);
|
||
|
|
$auditService->method('decryptSnapshot')->willReturn(['uuid' => 'existing-uuid', 'email' => 'new@example.com']);
|
||
|
|
|
||
|
|
$userReadRepo = $this->createMock(UserReadRepositoryInterface::class);
|
||
|
|
$userReadRepo->expects($this->once())->method('findByUuid')->with('existing-uuid')
|
||
|
|
->willReturn(['id' => 50, 'uuid' => 'existing-uuid']);
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userReadRepository: $userReadRepo,
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('restore_uuid_exists', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── restore_email_exists ────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreReturnsEmailExistsWhenUserWithEmailAlreadyExists(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')->willReturn(['id' => 1, 'restored_at' => null]);
|
||
|
|
$auditService->method('decryptSnapshot')->willReturn(['uuid' => 'new-uuid', 'email' => 'existing@example.com']);
|
||
|
|
|
||
|
|
$userReadRepo = $this->createMock(UserReadRepositoryInterface::class);
|
||
|
|
$userReadRepo->method('findByUuid')->willReturn(null);
|
||
|
|
$userReadRepo->expects($this->once())->method('findByEmail')->with('existing@example.com')
|
||
|
|
->willReturn(['id' => 60, 'email' => 'existing@example.com']);
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userReadRepository: $userReadRepo,
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('restore_email_exists', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── restore_create_failed ───────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreReturnsCreateFailedWhenWriteRepoReturnsZero(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')->willReturn(['id' => 1, 'restored_at' => null]);
|
||
|
|
$auditService->method('decryptSnapshot')->willReturn(['uuid' => 'u1', 'email' => 'e@x.com']);
|
||
|
|
|
||
|
|
$userReadRepo = $this->createMock(UserReadRepositoryInterface::class);
|
||
|
|
$userReadRepo->method('findByUuid')->willReturn(null);
|
||
|
|
$userReadRepo->method('findByEmail')->willReturn(null);
|
||
|
|
|
||
|
|
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||
|
|
$userWriteRepo->expects($this->once())->method('create')->willReturn(0);
|
||
|
|
|
||
|
|
$settingsGateway = $this->createMock(UserSettingsGateway::class);
|
||
|
|
$settingsGateway->method('normalizeTheme')->willReturn('light');
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userReadRepository: $userReadRepo,
|
||
|
|
userWriteRepository: $userWriteRepo,
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession,
|
||
|
|
userSettingsGateway: $settingsGateway
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('restore_create_failed', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testRestoreReturnsCreateFailedWhenWriteRepoReturnsFalse(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')->willReturn(['id' => 1, 'restored_at' => null]);
|
||
|
|
$auditService->method('decryptSnapshot')->willReturn(['uuid' => 'u1', 'email' => 'e@x.com']);
|
||
|
|
|
||
|
|
$userReadRepo = $this->createMock(UserReadRepositoryInterface::class);
|
||
|
|
$userReadRepo->method('findByUuid')->willReturn(null);
|
||
|
|
$userReadRepo->method('findByEmail')->willReturn(null);
|
||
|
|
|
||
|
|
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||
|
|
$userWriteRepo->expects($this->once())->method('create')->willReturn(false);
|
||
|
|
|
||
|
|
$settingsGateway = $this->createMock(UserSettingsGateway::class);
|
||
|
|
$settingsGateway->method('normalizeTheme')->willReturn('light');
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userReadRepository: $userReadRepo,
|
||
|
|
userWriteRepository: $userWriteRepo,
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession,
|
||
|
|
userSettingsGateway: $settingsGateway
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('restore_create_failed', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── restore_mark_failed ─────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreReturnsMarkFailedWhenMarkRestoredReturnsFalse(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')->willReturn(['id' => 1, 'restored_at' => null]);
|
||
|
|
$auditService->method('decryptSnapshot')->willReturn(['uuid' => 'u1', 'email' => 'e@x.com']);
|
||
|
|
$auditService->expects($this->once())
|
||
|
|
->method('markDeleteEventRestored')
|
||
|
|
->with(1, 99, 77)
|
||
|
|
->willReturn(false);
|
||
|
|
|
||
|
|
$userReadRepo = $this->createMock(UserReadRepositoryInterface::class);
|
||
|
|
$userReadRepo->method('findByUuid')->willReturn(null);
|
||
|
|
$userReadRepo->method('findByEmail')->willReturn(null);
|
||
|
|
|
||
|
|
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||
|
|
$userWriteRepo->method('create')->willReturn(77);
|
||
|
|
|
||
|
|
$settingsGateway = $this->createMock(UserSettingsGateway::class);
|
||
|
|
$settingsGateway->method('normalizeTheme')->willReturn('light');
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
$dbSession->expects($this->never())->method('commitTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userReadRepository: $userReadRepo,
|
||
|
|
userWriteRepository: $userWriteRepo,
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession,
|
||
|
|
userSettingsGateway: $settingsGateway
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('restore_mark_failed', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Transaction rollback on exception ───────────────────────────────
|
||
|
|
|
||
|
|
public function testRestoreRollsBackOnUnexpectedException(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')
|
||
|
|
->willThrowException(new \RuntimeException('unexpected'));
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->expects($this->once())->method('beginTransaction');
|
||
|
|
$dbSession->expects($this->never())->method('commitTransaction');
|
||
|
|
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession
|
||
|
|
);
|
||
|
|
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('restore_unexpected_error', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testRestoreRollbackQuietlySwallowsRollbackException(): void
|
||
|
|
{
|
||
|
|
$auditService = $this->createMock(UserLifecycleAuditService::class);
|
||
|
|
$auditService->method('findDeleteEventForRestore')
|
||
|
|
->willThrowException(new \RuntimeException('db error'));
|
||
|
|
|
||
|
|
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||
|
|
$dbSession->method('rollbackTransaction')
|
||
|
|
->willThrowException(new \RuntimeException('rollback also failed'));
|
||
|
|
|
||
|
|
$service = $this->newService(
|
||
|
|
userLifecycleAuditService: $auditService,
|
||
|
|
databaseSessionRepository: $dbSession
|
||
|
|
);
|
||
|
|
|
||
|
|
// Should not throw, even though rollback itself throws
|
||
|
|
$result = $service->restoreFromLifecycleAudit(1, 99);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertSame('restore_unexpected_error', $result['error']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Helper ──────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
private function newService(
|
||
|
|
?UserReadRepositoryInterface $userReadRepository = null,
|
||
|
|
?UserWriteRepositoryInterface $userWriteRepository = null,
|
||
|
|
?UserLifecycleAuditService $userLifecycleAuditService = null,
|
||
|
|
?DatabaseSessionRepository $databaseSessionRepository = null,
|
||
|
|
?UserSettingsGateway $userSettingsGateway = null,
|
||
|
|
): UserLifecycleRestoreService {
|
||
|
|
return new UserLifecycleRestoreService(
|
||
|
|
$userReadRepository ?? $this->createMock(UserReadRepositoryInterface::class),
|
||
|
|
$userWriteRepository ?? $this->createMock(UserWriteRepositoryInterface::class),
|
||
|
|
$userLifecycleAuditService ?? $this->createMock(UserLifecycleAuditService::class),
|
||
|
|
$databaseSessionRepository ?? $this->createMock(DatabaseSessionRepository::class),
|
||
|
|
$userSettingsGateway ?? $this->createMock(UserSettingsGateway::class),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|