diff --git a/app/Api/V1/Controllers/TransactionController.php b/app/Api/V1/Controllers/TransactionController.php index 0acf72e1f7..3f5b4220f0 100644 --- a/app/Api/V1/Controllers/TransactionController.php +++ b/app/Api/V1/Controllers/TransactionController.php @@ -29,10 +29,8 @@ use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Helpers\Filter\NegativeAmountFilter; use FireflyIII\Helpers\Filter\PositiveAmountFilter; use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Services\Internal\Update\JournalUpdateService; use FireflyIII\Transformers\TransactionTransformer; use Illuminate\Http\Request; use Illuminate\Support\Collection; @@ -41,6 +39,7 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; +use Log; use Preferences; /** @@ -176,7 +175,6 @@ class TransactionController extends Controller * @param TransactionRequest $request * * @return \Illuminate\Http\JsonResponse - * @throws \FireflyIII\Exceptions\FireflyException */ public function store(TransactionRequest $request, JournalRepositoryInterface $repository) { @@ -216,19 +214,20 @@ class TransactionController extends Controller /** - * @param TransactionRequest $request - * @param TransactionJournal $journal + * @param TransactionRequest $request + * @param JournalRepositoryInterface $repository + * @param Transaction $transaction * * @return \Illuminate\Http\JsonResponse */ - public function update(TransactionRequest $request, Transaction $transaction) + public function update(TransactionRequest $request, JournalRepositoryInterface $repository, Transaction $transaction) { $data = $request->getAll(); $data['user'] = auth()->user()->id; - /** @var JournalUpdateService $service */ - $service = app(JournalUpdateService::class); - $journal = $service->update($transaction->transactionJournal, $data); + Log::debug('Inside transaction update'); + + $journal = $repository->update($transaction->transactionJournal, $data); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; diff --git a/app/Api/V1/Controllers/UserController.php b/app/Api/V1/Controllers/UserController.php index f827ec1e6c..642c7c01c4 100644 --- a/app/Api/V1/Controllers/UserController.php +++ b/app/Api/V1/Controllers/UserController.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; +use FireflyIII\Api\V1\Requests\UserRequest; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Transformers\UserTransformer; use FireflyIII\User; @@ -89,21 +90,28 @@ class UserController extends Controller */ public function index(Request $request) { - $pageSize = intval(Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data); + // user preferences + $pageSize = intval(Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data); + + // make manager + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + // build collection $collection = $this->repository->all(); $count = $collection->count(); $users = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); // make paginator: $paginator = new LengthAwarePaginator($users, $count, $pageSize, $this->parameters->get('page')); - $manager = new Manager(); - $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; - $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $paginator->setPath(route('api.v1.users.index') . $this->buildParams()); + // make resource $resource = new FractalCollection($users, new UserTransformer($this->parameters), 'users'); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } /** @@ -114,35 +122,70 @@ class UserController extends Controller */ public function show(Request $request, User $user) { - + // make manager $manager = new Manager(); - //$manager->parseIncludes(['attachments', 'journals', 'user']); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + // make resource $resource = new Item($user, new UserTransformer($this->parameters), 'users'); - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } /** - * @param AccountRequest $request + * @param UserRequest $request * * @return \Illuminate\Http\JsonResponse */ - public function store(AccountRequest $request) + public function store(UserRequest $request) { + $data = $request->getAll(); + $user = $this->repository->store($data); + // make manager + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + // make resource + $resource = new Item($user, new UserTransformer($this->parameters), 'users'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } /** - * @param AccountRequest $request - * @param Account $account + * @param UserRequest $request + * @param User $user * * @return \Illuminate\Http\JsonResponse */ - public function update(AccountRequest $request, Account $account) + public function update(UserRequest $request, User $user) { + $data = $request->getAll(); + $user = $this->repository->update($user, $data); + // make manager + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + // make resource + $resource = new Item($user, new UserTransformer($this->parameters), 'users'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } diff --git a/app/Api/V1/Requests/TransactionRequest.php b/app/Api/V1/Requests/TransactionRequest.php index a724caede3..17a46633c7 100644 --- a/app/Api/V1/Requests/TransactionRequest.php +++ b/app/Api/V1/Requests/TransactionRequest.php @@ -334,7 +334,7 @@ class TransactionRequest extends Request } /** - * Throws an error when the given opping account (of type $type) is invalid. + * Throws an error when the given opposing account (of type $type) is invalid. * Empty data is allowed, system will default to cash. * * @param Validator $validator @@ -342,32 +342,32 @@ class TransactionRequest extends Request * @param int|null $accountId * @param null|string $accountName * @param string $idField - * @param string $nameField */ - protected function opposingAccountExists(Validator $validator, string $type, ?int $accountId, ?string $accountName, string $idField, string $nameField - ): void { + protected function opposingAccountExists(Validator $validator, string $type, ?int $accountId, ?string $accountName, string $idField): void { $accountId = intval($accountId); $accountName = strval($accountName); // both empty? done! if ($accountId < 1 && strlen($accountName) === 0) { return; } - // ID belongs to user and is $type account: - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser(auth()->user()); - $set = $repository->getAccountsById([$accountId]); - if ($set->count() === 1) { - /** @var Account $first */ - $first = $set->first(); - if ($first->accountType->type !== $type) { - $validator->errors()->add($idField, trans('validation.belongs_user')); + if ($accountId !== 0) { + // ID belongs to user and is $type account: + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $repository->setUser(auth()->user()); + $set = $repository->getAccountsById([$accountId]); + if ($set->count() === 1) { + /** @var Account $first */ + $first = $set->first(); + if ($first->accountType->type !== $type) { + $validator->errors()->add($idField, trans('validation.belongs_user')); + return; + } + + // we ignore the account name at this point. return; } - - // we ignore the account name at this point. - return; } // not having an opposing account by this name is NOT a problem. @@ -390,7 +390,7 @@ class TransactionRequest extends Request /** @var Transaction $transaction */ $transaction = $this->route()->parameter('transaction'); if (is_null($transaction)) { - return; + return; // @codeCoverageIgnore } $data['type'] = strtolower($transaction->transactionJournal->transactionType->type); } @@ -407,13 +407,11 @@ class TransactionRequest extends Request $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); $idField = 'transactions.' . $index . '.destination_id'; - $nameField = 'transactions.' . $index . '.destination_name'; - $this->opposingAccountExists($validator, AccountType::EXPENSE, $destinationId, $destinationName, $idField, $nameField); + $this->opposingAccountExists($validator, AccountType::EXPENSE, $destinationId, $destinationName, $idField); break; case 'deposit': $idField = 'transactions.' . $index . '.source_id'; - $nameField = 'transactions.' . $index . '.source_name'; - $this->opposingAccountExists($validator, AccountType::REVENUE, $sourceId, $sourceName, $idField, $nameField); + $this->opposingAccountExists($validator, AccountType::REVENUE, $sourceId, $sourceName, $idField); $idField = 'transactions.' . $index . '.destination_id'; $nameField = 'transactions.' . $index . '.destination_name'; @@ -447,7 +445,8 @@ class TransactionRequest extends Request if ($count < 2) { return; } - + // this is pretty much impossible: + // @codeCoverageIgnoreStart if (!isset($data['type'])) { // the journal may exist in the request: /** @var Transaction $transaction */ @@ -457,6 +456,7 @@ class TransactionRequest extends Request } $data['type'] = strtolower($transaction->transactionJournal->transactionType->type); } + // @codeCoverageIgnoreEnd // collect all source ID's and destination ID's, if present: $sources = []; @@ -487,7 +487,11 @@ class TransactionRequest extends Request } break; default: - throw new FireflyException(sprintf('The validator cannot handle transaction type "%s" in validateSplitAccounts().', $data['type'])); + // @codeCoverageIgnoreStart + throw new FireflyException( + sprintf('The validator cannot handle transaction type "%s" in validateSplitAccounts().', $data['type']) + ); + // @codeCoverageIgnoreEnd } return; diff --git a/app/Api/V1/Requests/UserRequest.php b/app/Api/V1/Requests/UserRequest.php new file mode 100644 index 0000000000..81c7a54703 --- /dev/null +++ b/app/Api/V1/Requests/UserRequest.php @@ -0,0 +1,89 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\User; + + +/** + * Class UserRequest + */ +class UserRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + if (!auth()->check()) { + return false; + } + /** @var User $user */ + $user = auth()->user(); + if (!$user->hasRole('owner')) { + return false; + } + + return true; + } + + /** + * @return array + */ + public function getAll(): array + { + $data = [ + 'email' => $this->string('email'), + 'blocked' => $this->boolean('blocked'), + 'blocked_code' => $this->string('blocked_code'), + ]; + + return $data; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'email' => 'required|email|unique:users,email,', + 'blocked' => 'required|boolean', + 'blocked_code' => 'in:email_changed', + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + $user = $this->route()->parameter('user'); + $rules['email'] = 'required|email|unique:users,email,' . $user->id; + break; + } + + return $rules; + } + +} \ No newline at end of file diff --git a/app/Http/Middleware/Sandstorm.php b/app/Http/Middleware/Sandstorm.php index c119efcc97..5dce1f605a 100644 --- a/app/Http/Middleware/Sandstorm.php +++ b/app/Http/Middleware/Sandstorm.php @@ -95,8 +95,9 @@ class Sandstorm /** @var User $user */ $user = $repository->store( [ + 'blocked' => false, + 'blocked_code' => null, 'email' => $email, - 'password' => str_random(16), ] ); Auth::guard($guard)->login($user); diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php index b69f13381d..4f664f4570 100644 --- a/app/Repositories/User/UserRepository.php +++ b/app/Repositories/User/UserRepository.php @@ -264,12 +264,12 @@ class UserRepository implements UserRepositoryInterface */ public function store(array $data): User { - $password = bcrypt($data['password'] ?? app('str')->random(16)); - return User::create( [ - 'email' => $data['email'], - 'password' => $password, + 'blocked' => $data['blocked'] ?? false, + 'blocked_code' => $data['blocked_code'] ?? null, + 'email' => $data['email'], + 'password' => str_random(24), ] ); } @@ -286,6 +286,24 @@ class UserRepository implements UserRepositoryInterface return; } + /** + * Update user info. + * + * @param User $user + * @param array $data + * + * @return User + */ + public function update(User $user, array $data): User + { + $this->updateEmail($user, $data['email']); + $user->blocked = $data['blocked'] ?? false; + $user->blocked_code = $data['blocked_code'] ?? null; + $user->save(); + + return $user; + } + /** * This updates the users email address. Same as changeEmail just without most logging. This makes sure that the undo/confirm routine can't catch this one. * The user is NOT blocked. diff --git a/app/Repositories/User/UserRepositoryInterface.php b/app/Repositories/User/UserRepositoryInterface.php index 01810c451c..46d261d006 100644 --- a/app/Repositories/User/UserRepositoryInterface.php +++ b/app/Repositories/User/UserRepositoryInterface.php @@ -31,7 +31,6 @@ use Illuminate\Support\Collection; */ interface UserRepositoryInterface { - /** * Returns a collection of all users. * @@ -159,6 +158,16 @@ interface UserRepositoryInterface */ public function unblockUser(User $user): void; + /** + * Update user info. + * + * @param User $user + * @param array $data + * + * @return User + */ + public function update(User $user, array $data): User; + /** * This updates the users email address. Same as changeEmail just without most logging. This makes sure that the undo/confirm routine can't catch this one. * The user is NOT blocked. diff --git a/app/Transformers/UserTransformer.php b/app/Transformers/UserTransformer.php index e07fb51908..66e5c835e5 100644 --- a/app/Transformers/UserTransformer.php +++ b/app/Transformers/UserTransformer.php @@ -26,6 +26,7 @@ namespace FireflyIII\Transformers; use FireflyIII\Models\Role; use FireflyIII\User; +use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\TransformerAbstract; use Symfony\Component\HttpFoundation\ParameterBag; @@ -34,6 +35,19 @@ use Symfony\Component\HttpFoundation\ParameterBag; */ class UserTransformer extends TransformerAbstract { + /** + * List of resources possible to include. + * + * @var array + */ + protected $availableIncludes = ['accounts', 'attachments', 'bills', 'budgets', 'categories', 'piggy_banks', 'tags', 'transactions']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + /** @var ParameterBag */ protected $parameters; @@ -49,6 +63,116 @@ class UserTransformer extends TransformerAbstract $this->parameters = $parameters; } + /** + * Include accounts. + * + * @codeCoverageIgnore + * + * @param User $user + * + * @return FractalCollection + */ + public function includeAccounts(User $user): FractalCollection + { + return $this->collection($user->accounts, new AccountTransformer($this->parameters), 'accounts'); + } + + /** + * Include attachments. + * + * @codeCoverageIgnore + * + * @param User $user + * + * @return FractalCollection + */ + public function includeAttachments(User $user): FractalCollection + { + return $this->collection($user->attachments, new AttachmentTransformer($this->parameters), 'attachments'); + } + + /** + * @codeCoverageIgnore + * + * @param User $user + * + * @return FractalCollection + */ + public function includeBills(User $user): FractalCollection + { + return $this->collection($user->bills, new BillTransformer($this->parameters), 'bills'); + } + + /** + * Include budgets. + * + * @codeCoverageIgnore + * + * @param User $user + * + * @return FractalCollection + */ + public function includeBudgets(User $user): FractalCollection + { + return $this->collection($user->budgets, new BudgetTransformer($this->parameters), 'budgets'); + } + + /** + * Include categories. + * + * @codeCoverageIgnore + * + * @param User $user + * + * @return FractalCollection + */ + public function includeCategories(User $user): FractalCollection + { + return $this->collection($user->categories, new CategoryTransformer($this->parameters), 'categories'); + } + + /** + * Include piggy banks. + * + * @codeCoverageIgnore + * + * @param User $user + * + * @return FractalCollection + */ + public function includePiggyBanks(User $user): FractalCollection + { + return $this->collection($user->piggyBanks, new PiggyBankTransformer($this->parameters), 'piggy_banks'); + } + + /** + * Include tags. + * + * @codeCoverageIgnore + * + * @param User $user + * + * @return FractalCollection + */ + public function includeTags(User $user): FractalCollection + { + return $this->collection($user->tags, new TagTransformer($this->parameters), 'tags'); + } + + /** + * Include transactions. + * + * @codeCoverageIgnore + * + * @param User $user + * + * @return FractalCollection + */ + public function includeTransactions(User $user): FractalCollection + { + return $this->collection($user->transactions, new TransactionTransformer($this->parameters), 'transactions'); + } + /** * Transform user. * diff --git a/app/User.php b/app/User.php index 7b9c6c2336..d8d76af5e7 100644 --- a/app/User.php +++ b/app/User.php @@ -42,13 +42,23 @@ class User extends Authenticatable { use Notifiable, HasApiTokens; + /** + * The attributes that should be casted to native types. + * + * @var array + */ + protected $casts + = [ + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'blocked' => 'boolean', + ]; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['email', 'password', 'blocked', 'blocked_code']; - /** * The attributes excluded from the model's JSON form. * @@ -63,7 +73,6 @@ class User extends Authenticatable protected $table = 'users'; /** - * @param $guard * @param string $value * * @return User diff --git a/routes/api.php b/routes/api.php index 9115da7cbf..53e6fb1ab5 100644 --- a/routes/api.php +++ b/routes/api.php @@ -61,7 +61,7 @@ Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transactions', 'as' => 'api.v1.transactions.'], function () { - // Users API routes: + // Transaction API routes: Route::get('', ['uses' => 'TransactionController@index', 'as' => 'index']); Route::post('', ['uses' => 'TransactionController@store', 'as' => 'store']); Route::get('{transaction}', ['uses' => 'TransactionController@show', 'as' => 'show']); diff --git a/tests/Api/V1/Controllers/TransactionControllerTest.php b/tests/Api/V1/Controllers/TransactionControllerTest.php index 1864e3b3e6..1378e28d44 100644 --- a/tests/Api/V1/Controllers/TransactionControllerTest.php +++ b/tests/Api/V1/Controllers/TransactionControllerTest.php @@ -37,7 +37,6 @@ use Log; use Tests\TestCase; /** - * todo test fire of rules with parameter * Class TransactionControllerTest */ class TransactionControllerTest extends TestCase @@ -1526,6 +1525,46 @@ class TransactionControllerTest extends TestCase $response->assertStatus(200); } + /** + * Add opposing account by a new name. + * + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store + * @covers \FireflyIII\Api\V1\Requests\TransactionRequest + */ + public function testSuccessNewStoreOpposingName() + { + $journal = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first(); + $account = $this->user()->accounts()->where('account_type_id', 3)->first(); + $journalRepos = $this->mock(JournalRepositoryInterface::class)->makePartial(); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $journalRepos->shouldReceive('setUser')->once(); + $accountRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('getAccountsById')->andReturn(new Collection([$account])); + $journalRepos->shouldReceive('store')->andReturn($journal)->once(); + + $data = [ + 'description' => 'Some transaction #' . rand(1, 1000), + 'date' => '2018-01-01', + 'type' => 'withdrawal', + 'transactions' => [ + [ + 'amount' => '10', + 'currency_id' => 1, + 'source_id' => $account->id, + 'destination_name' => 'New expense account #' . rand(1, 1000), + ], + + + ], + ]; + + // test API + $response = $this->post('/api/v1/transactions', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + + } + /** * Submit the minimum amount of data required to create a withdrawal. * @@ -1574,7 +1613,7 @@ class TransactionControllerTest extends TestCase public function testSuccessStoreBasic() { // default journal: - $journal = $this->user()->transactionJournals()->first(); + $journal = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first(); $account = $this->user()->accounts()->where('account_type_id', 3)->first(); $journalRepos = $this->mock(JournalRepositoryInterface::class)->makePartial(); $accountRepos = $this->mock(AccountRepositoryInterface::class); @@ -1613,7 +1652,7 @@ class TransactionControllerTest extends TestCase public function testSuccessStoreBasicDeposit() { // default journal: - $journal = $this->user()->transactionJournals()->first(); + $journal = $this->user()->transactionJournals()->where('transaction_type_id', 2)->first(); $account = $this->user()->accounts()->where('account_type_id', 3)->first(); $journalRepos = $this->mock(JournalRepositoryInterface::class)->makePartial(); $accountRepos = $this->mock(AccountRepositoryInterface::class); @@ -2330,4 +2369,78 @@ class TransactionControllerTest extends TestCase $response = $this->post('/api/v1/transactions?include=tags', $data, ['Accept' => 'application/json']); $response->assertStatus(200); } + + /** + * Fire enough to trigger an update. Since the create code already fires on the Request, no + * need to verify all of that. + * + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::update + * @covers \FireflyIII\Api\V1\Requests\TransactionRequest + */ + public function testUpdateBasicDeposit() + { + $account = $this->user()->accounts()->where('account_type_id', 3)->first(); + $repository = $this->mock(JournalRepositoryInterface::class); + $data = [ + 'description' => 'Some deposit #' . rand(1, 1000), + 'date' => '2018-01-01', + 'transactions' => [ + [ + 'amount' => '10', + 'currency_id' => 1, + 'destination_id' => $account->id, + ], + ], + ]; + do { + /** @var TransactionJournal $deposit */ + $deposit = $this->user()->transactionJournals()->where('transaction_type_id', 2)->first(); + $count = $deposit->transactions()->count(); + } while ($count !== 2); + + $transaction = $deposit->transactions()->first(); + $repository->shouldReceive('setUser'); + $repository->shouldReceive('update')->andReturn($deposit)->once(); + + // call API + $response = $this->put('/api/v1/transactions/' . $transaction->id, $data); + $response->assertStatus(200); + } + + /** + * Fire enough to trigger an update. Since the create code already fires on the Request, no + * need to verify all of that. + * + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::update + * @covers \FireflyIII\Api\V1\Requests\TransactionRequest + */ + public function testUpdateBasicWithdrawal() + { + $account = $this->user()->accounts()->where('account_type_id', 3)->first(); + $repository = $this->mock(JournalRepositoryInterface::class); + $data = [ + 'description' => 'Some transaction #' . rand(1, 1000), + 'date' => '2018-01-01', + 'transactions' => [ + [ + 'amount' => '10', + 'currency_id' => 1, + 'source_id' => $account->id, + ], + ], + ]; + do { + /** @var TransactionJournal $withdrawal */ + $withdrawal = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first(); + $count = $withdrawal->transactions()->count(); + } while ($count !== 2); + + $transaction = $withdrawal->transactions()->first(); + $repository->shouldReceive('setUser'); + $repository->shouldReceive('update')->andReturn($withdrawal)->once(); + + // call API + $response = $this->put('/api/v1/transactions/' . $transaction->id, $data); + $response->assertStatus(200); + } } \ No newline at end of file diff --git a/tests/Api/V1/Controllers/UserControllerTest.php b/tests/Api/V1/Controllers/UserControllerTest.php new file mode 100644 index 0000000000..da0ee82353 --- /dev/null +++ b/tests/Api/V1/Controllers/UserControllerTest.php @@ -0,0 +1,35 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + + +use Tests\TestCase; + +/** + * Class UserControllerTest + */ +class UserControllerTest extends TestCase +{ + +} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index cf3b6e7297..29cd8605d6 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -40,6 +40,9 @@ use Mockery; */ abstract class TestCase extends BaseTestCase { + + + /** * @param User $user * @param string $range