diff --git a/app/phpunit.xml.dist b/app/phpunit.xml.dist
index f8c384b..d9a247b 100644
--- a/app/phpunit.xml.dist
+++ b/app/phpunit.xml.dist
@@ -44,6 +44,7 @@
src/Serializable
src/DataFixtures
src/Entity
+ src/Enum
src/Kernel.php
diff --git a/app/src/Controller/Account/CreateAccountController.php b/app/src/Controller/Account/CreateAccountController.php
new file mode 100644
index 0000000..1a9222c
--- /dev/null
+++ b/app/src/Controller/Account/CreateAccountController.php
@@ -0,0 +1,50 @@
+successResponse(
+ data: $accountService->create($accountPayload),
+ groups: [SerializationGroups::ACCOUNT_CREATE],
+ status: Response::HTTP_CREATED,
+ );
+ }
+}
diff --git a/app/src/Controller/Account/DeleteAccountController.php b/app/src/Controller/Account/DeleteAccountController.php
new file mode 100644
index 0000000..03a3e7a
--- /dev/null
+++ b/app/src/Controller/Account/DeleteAccountController.php
@@ -0,0 +1,43 @@
+delete($account);
+
+ return $this->successResponse(data: [], status: Response::HTTP_NO_CONTENT);
+ }
+}
diff --git a/app/src/Controller/Account/GetAccountController.php b/app/src/Controller/Account/GetAccountController.php
new file mode 100644
index 0000000..49eb52e
--- /dev/null
+++ b/app/src/Controller/Account/GetAccountController.php
@@ -0,0 +1,41 @@
+successResponse(data: $accountService->get($id), groups: [SerializationGroups::ACCOUNT_GET]);
+ }
+}
diff --git a/app/src/Controller/Account/ListAccountController.php b/app/src/Controller/Account/ListAccountController.php
new file mode 100644
index 0000000..eeb496f
--- /dev/null
+++ b/app/src/Controller/Account/ListAccountController.php
@@ -0,0 +1,41 @@
+successResponse($accountService->list(), [SerializationGroups::ACCOUNT_LIST]);
+ }
+}
diff --git a/app/src/Controller/Account/UpdateAccountController.php b/app/src/Controller/Account/UpdateAccountController.php
new file mode 100644
index 0000000..2e7cd3e
--- /dev/null
+++ b/app/src/Controller/Account/UpdateAccountController.php
@@ -0,0 +1,50 @@
+successResponse(
+ data: $accountService->update($accountPayload, $account),
+ groups: [SerializationGroups::ACCOUNT_UPDATE]
+ );
+ }
+}
diff --git a/app/src/Controller/Budget/DeleteBudgetController.php b/app/src/Controller/Budget/DeleteBudgetController.php
index 7ba6ef8..4bea3cc 100644
--- a/app/src/Controller/Budget/DeleteBudgetController.php
+++ b/app/src/Controller/Budget/DeleteBudgetController.php
@@ -36,6 +36,8 @@ class DeleteBudgetController extends BaseRestController
#[Route('/{id}', name: 'api_budgets_delete', methods: Request::METHOD_DELETE)]
public function __invoke(BudgetService $budgetService, Budget $budget): JsonResponse
{
- return $this->successResponse(data: $budgetService->delete($budget), status: Response::HTTP_NO_CONTENT);
+ $budgetService->delete($budget);
+
+ return $this->successResponse(data: [], status: Response::HTTP_NO_CONTENT);
}
}
diff --git a/app/src/Controller/Budget/GetBudgetController.php b/app/src/Controller/Budget/GetBudgetController.php
index 9c4f19f..635bb35 100644
--- a/app/src/Controller/Budget/GetBudgetController.php
+++ b/app/src/Controller/Budget/GetBudgetController.php
@@ -25,9 +25,11 @@ class GetBudgetController extends BaseRestController
operationId: 'get_budget',
summary: 'get budget',
responses: [
- new SuccessResponse(responseClassFqcn: Budget::class, groups: [
- SerializationGroups::BUDGET_GET,
- ], description: 'Budget get'),
+ new SuccessResponse(
+ responseClassFqcn: Budget::class,
+ groups: [SerializationGroups::BUDGET_GET],
+ description: 'Budget get',
+ ),
new NotFoundResponse(description: 'Budget not found'),
],
)]
diff --git a/app/src/Dto/Account/Payload/AccountPayload.php b/app/src/Dto/Account/Payload/AccountPayload.php
new file mode 100644
index 0000000..95eac6f
--- /dev/null
+++ b/app/src/Dto/Account/Payload/AccountPayload.php
@@ -0,0 +1,15 @@
+
+ */
+ #[Serializer\Groups([SerializationGroups::ACCOUNT_GET])]
+ #[ORM\OneToMany(targetEntity: Transaction::class, mappedBy: 'account', orphanRemoval: true)]
+ private Collection $transactions;
+
+ #[ORM\ManyToOne(inversedBy: 'budgets')]
+ #[ORM\JoinColumn(nullable: false)]
+ private ?User $user = null;
+
+ public function __construct()
+ {
+ $this->transactions = new ArrayCollection();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function setId(?int $id): static
+ {
+ $this->id = $id;
+
+ return $this;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): static
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ public function getAmount(): float
+ {
+ return $this->amount;
+ }
+
+ public function setAmount(float $amount): static
+ {
+ $this->amount = $amount;
+
+ return $this;
+ }
+
+ public function getType(): AccountTypesEnum
+ {
+ return $this->type;
+ }
+
+ public function setType(AccountTypesEnum $type): static
+ {
+ $this->type = $type;
+
+ return $this;
+ }
+
+ /**
+ * @return Collection
+ */
+ public function getTransactions(): Collection
+ {
+ return $this->transactions;
+ }
+
+ public function addTransaction(Transaction $transaction): static
+ {
+ if (! $this->transactions->contains($transaction)) {
+ $this->transactions->add($transaction);
+ $transaction->setAccount($this);
+ }
+
+ return $this;
+ }
+
+ public function removeTransaction(Transaction $transaction): static
+ {
+ if ($this->transactions->removeElement($transaction) && $transaction->getAccount() === $this) {
+ $transaction->setAccount(null);
+ }
+
+ return $this;
+ }
+
+ public function getUser(): ?User
+ {
+ return $this->user;
+ }
+
+ public function setUser(?User $user): static
+ {
+ $this->user = $user;
+
+ return $this;
+ }
+}
diff --git a/app/src/Entity/Transaction.php b/app/src/Entity/Transaction.php
new file mode 100644
index 0000000..cbc7d1f
--- /dev/null
+++ b/app/src/Entity/Transaction.php
@@ -0,0 +1,118 @@
+ 'Y-m-d',
+ ],
+ denormalizationContext: [
+ DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
+ ],
+ )]
+ #[Assert\NotBlank]
+ #[Assert\Date]
+ #[ORM\Column(type: Types::DATE_MUTABLE)]
+ private \DateTimeInterface $date;
+
+ #[ORM\ManyToOne(inversedBy: 'transactions')]
+ #[ORM\JoinColumn(nullable: false)]
+ private ?Account $account = null;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getDescription(): string
+ {
+ return $this->description;
+ }
+
+ public function setDescription(string $description): static
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ public function getAmount(): ?float
+ {
+ return $this->amount;
+ }
+
+ public function setAmount(?float $amount): static
+ {
+ $this->amount = $amount;
+
+ return $this;
+ }
+
+ public function getType(): ?TransactionTypesEnum
+ {
+ return $this->type;
+ }
+
+ public function setType(TransactionTypesEnum $type): static
+ {
+ $this->type = $type;
+
+ return $this;
+ }
+
+ public function getDate(): \DateTimeInterface
+ {
+ return $this->date;
+ }
+
+ public function setDate(\DateTimeInterface $date): static
+ {
+ $this->date = $date;
+
+ return $this;
+ }
+
+ public function getAccount(): ?Account
+ {
+ return $this->account;
+ }
+
+ public function setAccount(?Account $account): static
+ {
+ $this->account = $account;
+
+ return $this;
+ }
+}
diff --git a/app/src/Entity/User.php b/app/src/Entity/User.php
index 0e240b8..b8a5941 100644
--- a/app/src/Entity/User.php
+++ b/app/src/Entity/User.php
@@ -57,13 +57,19 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
/**
* @var Collection
*/
- #[Serializer\Groups([SerializationGroups::USER_GET])]
#[ORM\OneToMany(targetEntity: Budget::class, mappedBy: 'user', orphanRemoval: true)]
private Collection $budgets;
+ /**
+ * @var Collection
+ */
+ #[ORM\OneToMany(targetEntity: Account::class, mappedBy: 'user', orphanRemoval: true)]
+ private Collection $accounts;
+
public function __construct()
{
$this->budgets = new ArrayCollection();
+ $this->accounts = new ArrayCollection();
}
public function getId(): int
@@ -171,7 +177,7 @@ public function getBudgets(): Collection
return $this->budgets;
}
- public function addBudget(Budget $budget): self
+ public function addBudget(Budget $budget): static
{
if (! $this->budgets->contains($budget)) {
$this->budgets->add($budget);
@@ -181,13 +187,39 @@ public function addBudget(Budget $budget): self
return $this;
}
- public function removeBudget(Budget $budget): self
+ public function removeBudget(Budget $budget): static
{
- // set the owning side to null (unless already changed)
if ($this->budgets->removeElement($budget) && $budget->getUser() === $this) {
$budget->setUser(null);
}
return $this;
}
+
+ /**
+ * @return Collection
+ */
+ public function getAccounts(): Collection
+ {
+ return $this->accounts;
+ }
+
+ public function addAccount(Account $account): static
+ {
+ if (! $this->accounts->contains($account)) {
+ $this->accounts->add($account);
+ $account->setUser($this);
+ }
+
+ return $this;
+ }
+
+ public function removeAccount(Account $account): static
+ {
+ if ($this->accounts->removeElement($account) && $account->getUser() === $this) {
+ $account->setUser(null);
+ }
+
+ return $this;
+ }
}
diff --git a/app/src/Enum/AccountTypesEnum.php b/app/src/Enum/AccountTypesEnum.php
new file mode 100644
index 0000000..ab841a8
--- /dev/null
+++ b/app/src/Enum/AccountTypesEnum.php
@@ -0,0 +1,10 @@
+
+ */
+class AccountRepository extends AbstractEntityRepository
+{
+ #[\Override]
+ public function getEntityClass(): string
+ {
+ return Account::class;
+ }
+}
diff --git a/app/src/Repository/TransactionRepository.php b/app/src/Repository/TransactionRepository.php
new file mode 100644
index 0000000..986f747
--- /dev/null
+++ b/app/src/Repository/TransactionRepository.php
@@ -0,0 +1,20 @@
+
+ */
+class TransactionRepository extends AbstractEntityRepository
+{
+ #[\Override]
+ public function getEntityClass(): string
+ {
+ return Transaction::class;
+ }
+}
diff --git a/app/src/Serializable/SerializationGroups.php b/app/src/Serializable/SerializationGroups.php
index 37d7684..da74bcd 100644
--- a/app/src/Serializable/SerializationGroups.php
+++ b/app/src/Serializable/SerializationGroups.php
@@ -14,6 +14,22 @@ final class SerializationGroups
public const string BUDGET_LIST = 'BUDGET_LIST';
+ public const string ACCOUNT_CREATE = 'ACCOUNT_CREATE';
+
+ public const string ACCOUNT_UPDATE = 'ACCOUNT_UPDATE';
+
+ public const string ACCOUNT_GET = 'ACCOUNT_GET';
+
+ public const string ACCOUNT_LIST = 'ACCOUNT_LIST';
+
+ public const string TRANSACTION_CREATE = 'TRANSACTION_CREATE';
+
+ public const string TRANSACTION_UPDATE = 'TRANSACTION_UPDATE';
+
+ public const string TRANSACTION_GET = 'TRANSACTION_GET';
+
+ public const string TRANSACTION_LIST = 'TRANSACTION_LIST';
+
public const string USER_CREATE = 'USER_CREATE';
public const string USER_GET = 'USER_GET';
diff --git a/app/src/Service/AccountService.php b/app/src/Service/AccountService.php
new file mode 100644
index 0000000..50588fe
--- /dev/null
+++ b/app/src/Service/AccountService.php
@@ -0,0 +1,86 @@
+accountRepository->find($id);
+
+ if ($account === null) {
+ throw new NotFoundHttpException('Account not found');
+ }
+
+ $this->checkAccess($account);
+
+ return $account;
+ }
+
+ public function create(AccountPayload $accountPayload): Account
+ {
+ /** @var User $user */
+ $user = $this->security->getUser();
+
+ $account = new Account();
+
+ $account->setName($accountPayload->name)
+ ->setUser($user)
+ ;
+
+ $this->accountRepository->save($account, true);
+
+ return $account;
+ }
+
+ public function update(AccountPayload $accountPayload, Account $account): Account
+ {
+ $this->checkAccess($account);
+
+ $account->setName($accountPayload->name);
+
+ $this->accountRepository->save($account, true);
+
+ return $account;
+ }
+
+ public function delete(Account $account): void
+ {
+ $this->checkAccess($account);
+
+ $this->accountRepository->delete($account, true);
+ }
+
+ /**
+ * @return list
+ */
+ public function list(): iterable
+ {
+ return $this->accountRepository->findBy([
+ 'user' => $this->security->getUser(),
+ ]);
+ }
+
+ private function checkAccess(Account $account): void
+ {
+ if ($this->security->getUser() !== $account->getUser()) {
+ throw new AccessDeniedHttpException('Access denied');
+ }
+ }
+}
diff --git a/app/src/Service/BudgetService.php b/app/src/Service/BudgetService.php
index 3acb561..d8243b6 100644
--- a/app/src/Service/BudgetService.php
+++ b/app/src/Service/BudgetService.php
@@ -83,13 +83,11 @@ private function createOrUpdateBudget(BudgetPayload $budgetPayload, Budget $budg
return $budget;
}
- public function delete(Budget $budget): Budget
+ public function delete(Budget $budget): void
{
$this->checkAccess($budget);
$this->budgetRepository->delete($budget, true);
-
- return $budget;
}
public function duplicate(?int $id = null): Budget
diff --git a/app/tests/Common/Factory/AccountFactory.php b/app/tests/Common/Factory/AccountFactory.php
new file mode 100644
index 0000000..8425a92
--- /dev/null
+++ b/app/tests/Common/Factory/AccountFactory.php
@@ -0,0 +1,46 @@
+
+ */
+final class AccountFactory extends PersistentProxyObjectFactory
+{
+ #[\Override]
+ public static function class(): string
+ {
+ return Account::class;
+ }
+
+ /**
+ * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
+ */
+ #[\Override]
+ protected function defaults(): array|callable
+ {
+ return [
+ 'id' => self::faker()->randomNumber(),
+ 'amount' => self::faker()->randomFloat(),
+ 'name' => self::faker()->text(255),
+ 'type' => self::faker()->randomElement(AccountTypesEnum::cases()),
+ 'user' => UserFactory::new(),
+ ];
+ }
+
+ /**
+ * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
+ */
+ #[\Override]
+ protected function initialize(): static
+ {
+ return $this;
+ // ->afterInstantiate(function(Account $account): void {})
+ }
+}
diff --git a/app/tests/Functional/Account/CreateAccountControllerTest.php b/app/tests/Functional/Account/CreateAccountControllerTest.php
new file mode 100644
index 0000000..de93747
--- /dev/null
+++ b/app/tests/Functional/Account/CreateAccountControllerTest.php
@@ -0,0 +1,46 @@
+_real();
+ $this->client->loginUser($user);
+
+ $accountPayload = [
+ 'name' => 'Livret',
+ ];
+
+ // ACT
+ $response = $this->clientRequest(Request::METHOD_POST, self::API_ENDPOINT, $accountPayload);
+ $responseData = $response['data'] ?? [];
+
+ // ASSERT
+ self::assertResponseIsSuccessful();
+ self::assertResponseFormatSame('json');
+ self::assertSame('Livret', $responseData['name']);
+ }
+}
diff --git a/app/tests/Functional/Account/DeleteAccountControllerTest.php b/app/tests/Functional/Account/DeleteAccountControllerTest.php
new file mode 100644
index 0000000..346fdcf
--- /dev/null
+++ b/app/tests/Functional/Account/DeleteAccountControllerTest.php
@@ -0,0 +1,46 @@
+_real();
+ $this->client->loginUser($user);
+
+ $account = AccountFactory::createOne([
+ 'user' => $user,
+ ]);
+
+ // ACT
+ $response = $this->clientRequest(Request::METHOD_DELETE, self::API_ENDPOINT . '/' . $account->getId());
+
+ // ASSERT
+ self::assertResponseIsSuccessful();
+ self::assertSame(Response::HTTP_NO_CONTENT, $response);
+ }
+}
diff --git a/app/tests/Functional/Account/GetAccountControllerTest.php b/app/tests/Functional/Account/GetAccountControllerTest.php
new file mode 100644
index 0000000..8b9cca0
--- /dev/null
+++ b/app/tests/Functional/Account/GetAccountControllerTest.php
@@ -0,0 +1,46 @@
+_real();
+ $this->client->loginUser($user);
+
+ $account = AccountFactory::createOne([
+ 'user' => $user,
+ ]);
+
+ // ACT
+ $response = $this->clientRequest(Request::METHOD_GET, self::API_ENDPOINT . '/' . $account->getId());
+ $responseData = $response['data'] ?? [];
+
+ // ASSERT
+ self::assertResponseIsSuccessful();
+ self::assertSame($account->getId(), $responseData['id']);
+ }
+}
diff --git a/app/tests/Functional/Account/ListAccountControllerTest.php b/app/tests/Functional/Account/ListAccountControllerTest.php
new file mode 100644
index 0000000..a3ac126
--- /dev/null
+++ b/app/tests/Functional/Account/ListAccountControllerTest.php
@@ -0,0 +1,46 @@
+_real();
+ $this->client->loginUser($user);
+
+ $accounts = AccountFactory::createMany(3, [
+ 'user' => $user,
+ ]);
+
+ // ACT
+ $response = $this->clientRequest(Request::METHOD_GET, self::API_ENDPOINT);
+ $responseData = $response['data'] ?? [];
+
+ // ASSERT
+ self::assertResponseIsSuccessful();
+ self::assertCount(\count($accounts), $responseData);
+ }
+}
diff --git a/app/tests/Functional/Account/UpdateAccountControllerTest.php b/app/tests/Functional/Account/UpdateAccountControllerTest.php
new file mode 100644
index 0000000..793b402
--- /dev/null
+++ b/app/tests/Functional/Account/UpdateAccountControllerTest.php
@@ -0,0 +1,55 @@
+_real();
+ $account = AccountFactory::createOne([
+ 'user' => $user,
+ ])->_real();
+
+ $this->client->loginUser($user);
+
+ $accountPayload = [
+ 'name' => 'Livret',
+ ];
+
+ // ACT
+ $response = $this->clientRequest(
+ Request::METHOD_PATCH,
+ self::API_ENDPOINT . '/' . $account->getId(),
+ $accountPayload
+ );
+ $responseData = $response['data'] ?? [];
+
+ // ASSERT
+ self::assertResponseIsSuccessful();
+ self::assertResponseFormatSame('json');
+ self::assertSame('Livret', $responseData['name']);
+ }
+}
diff --git a/app/tests/Integration/Repository/AccountRepositoryTest.php b/app/tests/Integration/Repository/AccountRepositoryTest.php
new file mode 100644
index 0000000..126d761
--- /dev/null
+++ b/app/tests/Integration/Repository/AccountRepositoryTest.php
@@ -0,0 +1,60 @@
+accountRepository = $container->get(AccountRepository::class);
+ }
+
+ #[TestDox('When you send an account and a user into findBy method, it should returns the users account list')]
+ #[Test]
+ public function findBy_WhenDataOk_ReturnsAccountList(): void
+ {
+ // ARRANGE
+ $user = UserFactory::createOne()->_real();
+
+ AccountFactory::createMany(3);
+ $accounts = AccountFactory::createMany(3, [
+ 'user' => $user,
+ ]);
+
+ // ACT
+ $accountResponse = $this->accountRepository->findBy([
+ 'user' => $user,
+ ]);
+
+ // ASSERT
+ self::assertCount(\count($accounts), $accountResponse);
+ }
+}
diff --git a/app/tests/Integration/Repository/BudgetRepositoryTest.php b/app/tests/Integration/Repository/BudgetRepositoryTest.php
index 2de0e9c..5353bd2 100644
--- a/app/tests/Integration/Repository/BudgetRepositoryTest.php
+++ b/app/tests/Integration/Repository/BudgetRepositoryTest.php
@@ -25,6 +25,7 @@ final class BudgetRepositoryTest extends KernelTestCase
{
use Factories;
use ResetDatabase;
+
private BudgetRepository $budgetRepository;
#[\Override]
diff --git a/app/tests/Integration/Repository/IncomeRepositoryTest.php b/app/tests/Integration/Repository/IncomeRepositoryTest.php
index 4572ef4..6e64c17 100644
--- a/app/tests/Integration/Repository/IncomeRepositoryTest.php
+++ b/app/tests/Integration/Repository/IncomeRepositoryTest.php
@@ -26,6 +26,7 @@ final class IncomeRepositoryTest extends KernelTestCase
{
use Factories;
use ResetDatabase;
+
private IncomeRepository $incomeRepository;
#[\Override]
diff --git a/app/tests/Integration/Repository/UserRepositoryTest.php b/app/tests/Integration/Repository/UserRepositoryTest.php
index b4ac186..7fb4e4f 100644
--- a/app/tests/Integration/Repository/UserRepositoryTest.php
+++ b/app/tests/Integration/Repository/UserRepositoryTest.php
@@ -24,6 +24,7 @@ final class UserRepositoryTest extends KernelTestCase
{
use Factories;
use ResetDatabase;
+
private UserRepository $userRepository;
#[\Override]
diff --git a/app/tests/Unit/Service/AccountServiceTest.php b/app/tests/Unit/Service/AccountServiceTest.php
new file mode 100644
index 0000000..f73fec4
--- /dev/null
+++ b/app/tests/Unit/Service/AccountServiceTest.php
@@ -0,0 +1,268 @@
+accountRepository = $this->createMock(AccountRepository::class);
+ $this->security = $this->createMock(Security::class);
+
+ $this->accountService = new AccountService(
+ accountRepository: $this->accountRepository,
+ security: $this->security,
+ );
+ }
+
+ #[TestDox('When calling create account, it should return the budget created')]
+ #[Test]
+ public function createAccountService_WhenDataOk_ReturnsBudgetCreated(): void
+ {
+ // ARRANGE
+ $account = AccountFactory::createOne([
+ 'id' => 1,
+ 'user' => $this->security->getUser(),
+ ]);
+
+ $accountPayload = (new AccountPayload());
+ $accountPayload->name = 'Livret';
+
+ $this->accountRepository->expects($this->once())
+ ->method('save')
+ ->willReturnCallback(static function (Account $account): void {
+ $account->setId(1)
+ ->setName('Livret')
+ ;
+ })
+ ;
+
+ // ACT
+ $accountResponse = $this->accountService->create($accountPayload);
+
+ // ASSERT
+ self::assertInstanceOf(Account::class, $account);
+ self::assertSame($account->getId(), $accountResponse->getId());
+ self::assertSame('Livret', $accountResponse->getName());
+ }
+
+ #[TestDox('When calling update account, it should update and return the account updated')]
+ #[Test]
+ public function updateAccountService_WhenDataOk_ReturnsAccountUpdated(): void
+ {
+ // ARRANGE
+ $account = AccountFactory::createOne([
+ 'id' => 1,
+ 'user' => $this->security->getUser(),
+ ]);
+
+ $accountPayload = (new AccountPayload());
+ $accountPayload->name = 'Livret updated';
+
+ $this->accountRepository->expects($this->once())
+ ->method('save')
+ ->willReturnCallback(static function (Account $account): void {
+ $account->setId(1)
+ ->setName('Livret updated')
+ ;
+ })
+ ;
+
+ // ACT
+ $accountResponse = $this->accountService->update($accountPayload, $account);
+
+ // ASSERT
+ self::assertInstanceOf(Account::class, $account);
+ self::assertSame($account->getId(), $accountResponse->getId());
+ self::assertSame('Livret updated', $accountResponse->getName());
+ }
+
+ #[TestDox('When calling update account with bad user, it should returns access denied exception')]
+ #[Test]
+ public function updateAccountService_WithBadUser_ReturnsAccessDeniedException(): void
+ {
+ // ASSERT
+ $this->expectException(AccessDeniedHttpException::class);
+
+ // ARRANGE
+ $account = AccountFactory::createOne([
+ 'id' => 1,
+ ]);
+
+ $accountPayload = (new AccountPayload());
+ $accountPayload->name = 'Livret updated';
+
+ // ACT
+ $this->accountService->update($accountPayload, $account);
+ }
+
+ #[TestDox('When calling get account, it should get the account')]
+ #[Test]
+ public function getAccountService_WhenDataOk_ReturnsAccount(): void
+ {
+ // ARRANGE
+ $account = AccountFactory::createOne([
+ 'id' => 1,
+ 'user' => $this->security->getUser(),
+ ]);
+
+ $this->accountRepository->expects($this->once())
+ ->method('find')
+ ->willReturn($account)
+ ;
+
+ // ACT
+ $accountResponse = $this->accountService->get($account->getId());
+
+ // ASSERT
+ self::assertInstanceOf(Account::class, $account);
+ self::assertSame($account->getId(), $accountResponse->getId());
+ }
+
+ #[TestDox('When calling get account with bad id, it should throw not found exception')]
+ #[Test]
+ public function getAccountService_WithBadId_ReturnsNotFoundException(): void
+ {
+ // ASSERT
+ $this->expectException(NotFoundHttpException::class);
+
+ // ACT
+ $this->accountService->get(999);
+ }
+
+ #[TestDox('When calling get account for another user, it should throw access denied exception')]
+ #[Test]
+ public function getAccountService_WithBadUser_ReturnsAccessDeniedException(): void
+ {
+ // ASSERT
+ $this->expectException(AccessDeniedHttpException::class);
+
+ // ARRANGE
+ $account = AccountFactory::new()->withoutPersisting()->create();
+
+ $this->accountRepository->expects($this->once())
+ ->method('find')
+ ->willReturn($account)
+ ;
+
+ // ACT
+ $this->accountService->get($account->getId());
+ }
+
+ #[TestDox('When calling delete account, it should delete the account')]
+ #[Test]
+ public function deleteAccountService_WhenDataOk_ReturnsNoContent(): void
+ {
+ // ARRANGE
+ $account = AccountFactory::createOne([
+ 'user' => $this->security->getUser(),
+ ]);
+
+ $this->accountRepository->expects($this->once())
+ ->method('delete')
+ ->with($account, true)
+ ;
+
+ // ACT
+ $this->accountService->delete($account);
+
+ // ASSERT
+ self::assertInstanceOf(Account::class, $account);
+ }
+
+ #[TestDox('When calling delete account with bad user, it should returns access denied exception')]
+ #[Test]
+ public function deleteAccountService_WithBadUser_ReturnsAccessDeniedException(): void
+ {
+ // ASSERT
+ $this->expectException(AccessDeniedHttpException::class);
+
+ // ARRANGE
+ $account = AccountFactory::createOne();
+
+ // ACT
+ $this->accountService->delete($account);
+ }
+
+ #[TestDox('When you call list, it should return the accounts list')]
+ #[Test]
+ public function listAccountService_WhenDataOk_ReturnsAccountsList(): void
+ {
+ // ARRANGE
+ AccountFactory::createMany(3);
+
+ $accounts = AccountFactory::createMany(3, [
+ 'user' => $this->security->getUser(),
+ ]);
+
+ $this->accountRepository->method('findBy')
+ ->willReturn($accounts)
+ ;
+
+ // ACT
+ $accountsResponse = $this->accountService->list();
+
+ // ASSERT
+ self::assertCount(\count($accounts), $accountsResponse);
+ }
+
+ #[TestDox('When calling checkAccess, it should returns an AccessDeniedException')]
+ #[Test]
+ public function checkAccessBudgetService_WhenBadData_ReturnsAccessDeniedException(): void
+ {
+ // ASSERT
+ $this->expectException(AccessDeniedHttpException::class);
+
+ // ARRANGE PRIVATE METHOD TEST
+ $accountService = new AccountService($this->accountRepository, $this->security);
+
+ $method = $this->getPrivateMethod(AccountService::class, 'checkAccess');
+
+ // ARRANGE
+ $account = AccountFactory::createOne([
+ 'id' => 1,
+ ]);
+
+ // ACT
+ $method->invoke($accountService, $account);
+ }
+
+ private function getPrivateMethod(string $className, string $methodName): \ReflectionMethod
+ {
+ return (new \ReflectionClass($className))->getMethod($methodName);
+ }
+}
diff --git a/app/tests/Unit/Service/BudgetServiceTest.php b/app/tests/Unit/Service/BudgetServiceTest.php
index 231d6b0..0d34153 100644
--- a/app/tests/Unit/Service/BudgetServiceTest.php
+++ b/app/tests/Unit/Service/BudgetServiceTest.php
@@ -62,7 +62,7 @@ protected function setUp(): void
);
}
- #[TestDox('When calling create budget, it should update and return the budget created')]
+ #[TestDox('When calling create budget, it should return the budget created')]
#[Test]
public function createBudgetService_WhenDataOk_ReturnsBudgetCreated(): void
{
@@ -72,10 +72,10 @@ public function createBudgetService_WhenDataOk_ReturnsBudgetCreated(): void
'user' => $this->security->getUser(),
]);
- $BudgetPayload = (new BudgetPayload());
- $BudgetPayload->date = Carbon::parse('2022-03');
- $BudgetPayload->incomes = [];
- $BudgetPayload->expenses = [];
+ $budgetPayload = (new BudgetPayload());
+ $budgetPayload->date = Carbon::parse('2022-03');
+ $budgetPayload->incomes = [];
+ $budgetPayload->expenses = [];
$this->budgetRepository->expects($this->once())
->method('save')
@@ -88,7 +88,7 @@ public function createBudgetService_WhenDataOk_ReturnsBudgetCreated(): void
;
// ACT
- $budgetResponse = $this->budgetService->create($BudgetPayload);
+ $budgetResponse = $this->budgetService->create($budgetPayload);
// ASSERT
self::assertInstanceOf(Budget::class, $budget);
@@ -212,12 +212,16 @@ public function deleteBudgetService_WhenDataOk_ReturnsNoContent(): void
'user' => $this->security->getUser(),
]);
+ $this->budgetRepository->expects($this->once())
+ ->method('delete')
+ ->with($budget, true)
+ ;
+
// ACT
- $budgetResponse = $this->budgetService->delete($budget);
+ $this->budgetService->delete($budget);
// ASSERT
self::assertInstanceOf(Budget::class, $budget);
- self::assertSame($budget->getId(), $budgetResponse->getId());
}
#[TestDox('When calling delete budget with bad user, it should returns access denied exception')]