From d6fdec2a8ba41824e6d83127eff9b40cf709dba8 Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Sun, 14 Apr 2024 18:23:14 +0200
Subject: [PATCH] TASK-37: Add ability to accept new clients from CRM

---
 features/crm-user-sync.feature                | 34 ++++++++
 src/Controller/ClientController.php           | 40 +++++++++
 src/Entity/Client.php                         | 14 ++++
 .../ClientAlreadyExistsException.php          |  7 ++
 src/Repository/ClientRepository.php           | 29 ++-----
 src/Service/ClientService.php                 | 28 +++++++
 tests/Behat/ClientsContext.php                | 82 +++++++++++++++++++
 7 files changed, 211 insertions(+), 23 deletions(-)
 create mode 100644 features/crm-user-sync.feature
 create mode 100644 src/Controller/ClientController.php
 create mode 100644 src/Exception/ClientAlreadyExistsException.php
 create mode 100644 src/Service/ClientService.php
 create mode 100644 tests/Behat/ClientsContext.php

diff --git a/features/crm-user-sync.feature b/features/crm-user-sync.feature
new file mode 100644
index 0000000..95d4aea
--- /dev/null
+++ b/features/crm-user-sync.feature
@@ -0,0 +1,34 @@
+Feature:
+  The CRM system is able to send information about new Clients to the service
+
+  Background:
+    Given there exist following clients:
+      | clientId                             | name   | initialBalance | currentBalance |
+      | 018edd2e-894a-78d7-b10c-16e05ca933a3 | Kacper | 10000          | 5000           |
+
+  Scenario: CRM is able to create new clients
+    Given the request has the following body:
+      """
+      {
+        "clientId": "018edd37-c145-7143-ba91-c191084e4fba",
+        "name": "Jan",
+        "balance": 100000
+      }
+      """
+    When I send a POST request to "/clients"
+    Then the response status should be 201
+    And client with id "018edd37-c145-7143-ba91-c191084e4fba" should exist
+    And client with id "018edd37-c145-7143-ba91-c191084e4fba" should have balance of 100000
+
+  Scenario: CRM should not be able to override existing user
+    Given the request has the following body:
+      """
+      {
+        "clientId": "018edd2e-894a-78d7-b10c-16e05ca933a3",
+        "name": "Kacper",
+        "balance": 100000
+      }
+      """
+    When I send a POST request to "/clients"
+    Then the response status should be 409
+    And client with id "018edd2e-894a-78d7-b10c-16e05ca933a3" should exist
diff --git a/src/Controller/ClientController.php b/src/Controller/ClientController.php
new file mode 100644
index 0000000..16cdd3c
--- /dev/null
+++ b/src/Controller/ClientController.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Controller;
+
+use App\Contract\ClientDto;
+use App\Entity\Client;
+use App\Exception\ClientAlreadyExistsException;
+use App\Service\ClientService;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Attribute\AsController;
+use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
+use Symfony\Component\Routing\Attribute\Route;
+
+#[Route(path: '/clients', name: 'clients_')]
+#[AsController]
+readonly class ClientController
+{
+    public function __construct(
+        private ClientService $clientService,
+    ) {
+    }
+
+    #[Route(name: 'create', methods: ['POST'])]
+    public function create(#[MapRequestPayload] ClientDto $clientDto): Response
+    {
+        $client = new Client(
+            clientId: $clientDto->getClientId(),
+            name: $clientDto->getName(),
+            initialBalance: $clientDto->getBalance(),
+        );
+
+        try {
+            $this->clientService->createNewClient($client);
+        } catch (ClientAlreadyExistsException) {
+            return new Response(status: Response::HTTP_CONFLICT);
+        }
+
+        return new Response(status: Response::HTTP_CREATED);
+    }
+}
diff --git a/src/Entity/Client.php b/src/Entity/Client.php
index c657ae3..1e6d81f 100644
--- a/src/Entity/Client.php
+++ b/src/Entity/Client.php
@@ -18,9 +18,13 @@ class Client
         #[ORM\Column]
         private ?string $name = null,
 
+        #[ORM\Column]
+        private ?int $initialBalance = null,
+
         #[ORM\Column]
         private ?int $currentBalance = null,
     ) {
+        $this->currentBalance ??= $this->initialBalance;
     }
 
     public function getClientId(): ?Uuid
@@ -38,6 +42,16 @@ class Client
         $this->name = $name;
     }
 
+    public function getInitialBalance(): ?int
+    {
+        return $this->initialBalance;
+    }
+
+    public function setInitialBalance(?int $initialBalance): void
+    {
+        $this->initialBalance = $initialBalance;
+    }
+
     public function getBalance(): ?int
     {
         return $this->currentBalance;
diff --git a/src/Exception/ClientAlreadyExistsException.php b/src/Exception/ClientAlreadyExistsException.php
new file mode 100644
index 0000000..aa0c6b8
--- /dev/null
+++ b/src/Exception/ClientAlreadyExistsException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace App\Exception;
+
+class ClientAlreadyExistsException extends \LogicException
+{
+}
diff --git a/src/Repository/ClientRepository.php b/src/Repository/ClientRepository.php
index d9d20a0..8609770 100644
--- a/src/Repository/ClientRepository.php
+++ b/src/Repository/ClientRepository.php
@@ -21,28 +21,11 @@ class ClientRepository extends ServiceEntityRepository
         parent::__construct($registry, Client::class);
     }
 
-    //    /**
-    //     * @return Client[] Returns an array of Client objects
-    //     */
-    //    public function findByExampleField($value): array
-    //    {
-    //        return $this->createQueryBuilder('c')
-    //            ->andWhere('c.exampleField = :val')
-    //            ->setParameter('val', $value)
-    //            ->orderBy('c.id', 'ASC')
-    //            ->setMaxResults(10)
-    //            ->getQuery()
-    //            ->getResult()
-    //        ;
-    //    }
+    public function save(Client $client): Client
+    {
+        $this->getEntityManager()->persist($client);
+        $this->getEntityManager()->flush();
 
-    //    public function findOneBySomeField($value): ?Client
-    //    {
-    //        return $this->createQueryBuilder('c')
-    //            ->andWhere('c.exampleField = :val')
-    //            ->setParameter('val', $value)
-    //            ->getQuery()
-    //            ->getOneOrNullResult()
-    //        ;
-    //    }
+        return $client;
+    }
 }
diff --git a/src/Service/ClientService.php b/src/Service/ClientService.php
new file mode 100644
index 0000000..c2beaf8
--- /dev/null
+++ b/src/Service/ClientService.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Service;
+
+use App\Entity\Client;
+use App\Exception\ClientAlreadyExistsException;
+use App\Repository\ClientRepository;
+
+class ClientService
+{
+    public function __construct(
+        public readonly ClientRepository $clientRepository
+    ) {
+    }
+
+    public function createNewClient(Client $client): Client
+    {
+        $existing = $this->clientRepository->find($client->getClientId());
+
+        if ($existing !== null) {
+            throw new ClientAlreadyExistsException(
+                message: sprintf('Client with id "%s" already exists.', $client->getClientId()->toRfc4122())
+            );
+        }
+
+        return $this->clientRepository->save($client);
+    }
+}
diff --git a/tests/Behat/ClientsContext.php b/tests/Behat/ClientsContext.php
new file mode 100644
index 0000000..4916768
--- /dev/null
+++ b/tests/Behat/ClientsContext.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace App\Tests\Behat;
+
+use App\Entity\Client;
+use App\Repository\ClientRepository;
+use Behat\Behat\Context\Context;
+use Behat\Behat\Tester\Exception\PendingException;
+use Behat\Gherkin\Node\TableNode;
+use Doctrine\ORM\EntityManagerInterface;
+use PHPUnit\Framework\Assert;
+use Symfony\Component\Uid\Uuid;
+
+class ClientsContext implements Context
+{
+    private array $clients;
+
+    public function __construct(
+        protected readonly ClientRepository $clientRepository,
+    ) {}
+
+    /**
+     * @BeforeScenario
+     */
+    public function clean()
+    {
+        $this->clientRepository
+            ->createQueryBuilder('c')
+            ->delete()
+            ->getQuery()
+            ->execute();
+
+        $this->clients = [];
+    }
+
+    /**
+     * @Given /^there exist following clients:$/
+     */
+    public function thereExistFollowingClients(TableNode $table)
+    {
+        foreach ($table as $row) {
+            $client = $this->getClient($row['clientId']);
+
+            if (!$client) {
+                $client = new Client(
+                    clientId: Uuid::fromRfc4122($row['clientId']),
+                    name: $row['name'],
+                    initialBalance: intval($row['initialBalance']),
+                    currentBalance: intval($row['currentBalance']),
+                );
+
+                $this->clientRepository->save($client);
+            }
+
+            $this->clients[$client->getClientId()->toRfc4122()] = $client;
+        }
+    }
+
+    public function getClient(string $id): ?Client
+    {
+        return $this->clients[$id] ??= $this->clientRepository->find(Uuid::fromRfc4122($id));
+    }
+
+    /**
+     * @Given /^client with id "([^"]+)" should exist$/
+     */
+    public function clientWithIdShouldExist(string $clientId)
+    {
+        Assert::assertNotNull($this->getClient($clientId));
+    }
+
+    /**
+     * @Given /^client with id "([^"]+)" should have balance of (\d+)$/
+     */
+    public function clientWithIdShouldHaveBalanceOf(string $clientId, int $balance)
+    {
+        $client = $this->getClient($clientId);
+
+        Assert::assertEquals($client->getBalance(), $balance);
+    }
+
+}