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')]