createMock(HandoverRevisionRepository::class); $handoverRepo = $handoverRepo ?? $this->createMock(HandoverRepository::class); return new HandoverRevisionService($revisionRepo, $handoverRepo); } // ── createRevision ─────────────────────────────────────────────── public function testCreateRevisionIncrementsNumber(): void { $revisionRepo = $this->createMock(HandoverRevisionRepository::class); $revisionRepo->method('getLatestRevisionNumber')->willReturn(3); $revisionRepo->expects($this->once()) ->method('insert') ->with($this->callback(function (array $data): bool { return $data['revision'] === 4; })) ->willReturn(100); $service = $this->createService($revisionRepo); $result = $service->createRevision(self::TENANT_ID, self::HANDOVER_ID, ['name' => 'Test'], 'draft', 1, self::USER_ID); $this->assertSame(100, $result); } public function testCreateRevisionStoresSnapshot(): void { $revisionRepo = $this->createMock(HandoverRevisionRepository::class); $revisionRepo->method('getLatestRevisionNumber')->willReturn(0); $revisionRepo->expects($this->once()) ->method('insert') ->with($this->callback(function (array $data): bool { return $data['status'] === 'in_progress' && $data['schema_version'] === 2 && $data['change_type'] === 'fields' && json_decode($data['field_values'], true) === ['name' => 'Acme']; })) ->willReturn(1); $service = $this->createService($revisionRepo); $service->createRevision(self::TENANT_ID, self::HANDOVER_ID, ['name' => 'Acme'], 'in_progress', 2, self::USER_ID); } // ── computeDiff ────────────────────────────────────────────────── public function testComputeDiffDetectsAddedFields(): void { $service = $this->createService(); $diff = $service->computeDiff([], ['name' => 'New']); $this->assertArrayHasKey('name', $diff); $this->assertSame('added', $diff['name']['type']); $this->assertNull($diff['name']['old']); $this->assertSame('New', $diff['name']['new']); } public function testComputeDiffDetectsRemovedFields(): void { $service = $this->createService(); $diff = $service->computeDiff(['name' => 'Old'], []); $this->assertArrayHasKey('name', $diff); $this->assertSame('removed', $diff['name']['type']); $this->assertSame('Old', $diff['name']['old']); $this->assertNull($diff['name']['new']); } public function testComputeDiffDetectsChangedFields(): void { $service = $this->createService(); $diff = $service->computeDiff(['name' => 'Old', 'priority' => 'low'], ['name' => 'New', 'priority' => 'low']); $this->assertArrayHasKey('name', $diff); $this->assertSame('changed', $diff['name']['type']); $this->assertSame('Old', $diff['name']['old']); $this->assertSame('New', $diff['name']['new']); $this->assertArrayNotHasKey('priority', $diff); } public function testComputeDiffHandlesEmptyArrays(): void { $service = $this->createService(); $diff = $service->computeDiff([], []); $this->assertSame([], $diff); } // ── restoreRevision ────────────────────────────────────────────── public function testRestoreRevisionRequiresManagePermission(): void { $service = $this->createService(); $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_CREATE); $this->assertFalse($result['ok']); $this->assertArrayHasKey('general', $result['errors']); } public function testRestoreRevisionRejectsInvalidRevision(): void { $revisionRepo = $this->createMock(HandoverRevisionRepository::class); $revisionRepo->method('findByRevision')->willReturn(null); $service = $this->createService($revisionRepo); $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 999, self::USER_ID, HandoverService::PERMISSION_MANAGE); $this->assertFalse($result['ok']); } public function testRestoreRevisionCreatesNewRevision(): void { $revisionRepo = $this->createMock(HandoverRevisionRepository::class); $revisionRepo->method('findByRevision')->willReturn([ 'id' => 1, 'revision' => 2, 'field_values' => '{"name":"Old value"}', 'status' => 'draft', ]); $revisionRepo->method('getLatestRevisionNumber')->willReturn(5); $revisionRepo->expects($this->once()) ->method('insert') ->with($this->callback(function (array $data): bool { return $data['revision'] === 6 && $data['change_type'] === 'restore' && json_decode($data['field_values'], true) === ['name' => 'Old value']; })) ->willReturn(200); $handoverRepo = $this->createMock(HandoverRepository::class); $handoverRepo->method('findById')->willReturn([ 'id' => self::HANDOVER_ID, 'status' => 'in_progress', 'field_values' => '{"name":"Current value"}', 'schema_snapshot' => '{"version":1,"fields":[]}', ]); $handoverRepo->expects($this->once())->method('beginTransaction'); $handoverRepo->expects($this->once())->method('commitTransaction'); $handoverRepo->expects($this->never())->method('rollbackTransaction'); $handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(true); $handoverRepo->expects($this->once())->method('updateStatus')->willReturn(true); $service = $this->createService($revisionRepo, $handoverRepo); $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 2, self::USER_ID, HandoverService::PERMISSION_MANAGE); $this->assertTrue($result['ok']); } public function testRestoreRevisionSkipsStatusUpdateWhenSame(): void { $revisionRepo = $this->createMock(HandoverRevisionRepository::class); $revisionRepo->method('findByRevision')->willReturn([ 'id' => 1, 'revision' => 1, 'field_values' => '{"name":"Old"}', 'status' => 'draft', ]); $revisionRepo->method('getLatestRevisionNumber')->willReturn(3); $revisionRepo->method('insert')->willReturn(100); $handoverRepo = $this->createMock(HandoverRepository::class); $handoverRepo->method('findById')->willReturn([ 'id' => self::HANDOVER_ID, 'status' => 'draft', 'field_values' => '{"name":"Current"}', 'schema_snapshot' => '{"version":1,"fields":[]}', ]); $handoverRepo->expects($this->once())->method('beginTransaction'); $handoverRepo->expects($this->once())->method('commitTransaction'); $handoverRepo->expects($this->never())->method('rollbackTransaction'); $handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(true); $handoverRepo->expects($this->never())->method('updateStatus'); $service = $this->createService($revisionRepo, $handoverRepo); $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_MANAGE); $this->assertTrue($result['ok']); } public function testRestoreRevisionRollsBackWhenFieldUpdateFails(): void { $revisionRepo = $this->createMock(HandoverRevisionRepository::class); $revisionRepo->method('findByRevision')->willReturn([ 'id' => 1, 'revision' => 1, 'field_values' => '{"name":"Old"}', 'status' => 'draft', ]); $revisionRepo->expects($this->never())->method('insert'); $handoverRepo = $this->createMock(HandoverRepository::class); $handoverRepo->method('findById')->willReturn([ 'id' => self::HANDOVER_ID, 'status' => 'draft', 'field_values' => '{"name":"Current"}', 'schema_snapshot' => '{"version":1,"fields":[]}', ]); $handoverRepo->expects($this->once())->method('beginTransaction'); $handoverRepo->expects($this->once())->method('rollbackTransaction'); $handoverRepo->expects($this->never())->method('commitTransaction'); $handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(false); $handoverRepo->expects($this->never())->method('updateStatus'); $service = $this->createService($revisionRepo, $handoverRepo); $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_MANAGE); $this->assertFalse($result['ok']); $this->assertArrayHasKey('general', $result['errors']); } public function testRestoreRevisionRollsBackWhenRevisionInsertFails(): void { $revisionRepo = $this->createMock(HandoverRevisionRepository::class); $revisionRepo->method('findByRevision')->willReturn([ 'id' => 1, 'revision' => 1, 'field_values' => '{"name":"Old"}', 'status' => 'draft', ]); $revisionRepo->method('getLatestRevisionNumber')->willReturn(3); $revisionRepo->expects($this->once())->method('insert')->willReturn(null); $handoverRepo = $this->createMock(HandoverRepository::class); $handoverRepo->method('findById')->willReturn([ 'id' => self::HANDOVER_ID, 'status' => 'draft', 'field_values' => '{"name":"Current"}', 'schema_snapshot' => '{"version":1,"fields":[]}', ]); $handoverRepo->expects($this->once())->method('beginTransaction'); $handoverRepo->expects($this->once())->method('rollbackTransaction'); $handoverRepo->expects($this->never())->method('commitTransaction'); $handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(true); $handoverRepo->expects($this->never())->method('updateStatus'); $service = $this->createService($revisionRepo, $handoverRepo); $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_MANAGE); $this->assertFalse($result['ok']); } }