wip
This commit is contained in:
@@ -5,6 +5,8 @@ namespace App\Http\ApiControllers;
|
|||||||
use App\Models\Holding;
|
use App\Models\Holding;
|
||||||
use App\Models\Portfolio;
|
use App\Models\Portfolio;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
use App\Http\Requests\HoldingRequest;
|
||||||
use App\Http\Resources\HoldingResource;
|
use App\Http\Resources\HoldingResource;
|
||||||
use HackerEsq\FilterModels\FilterModels;
|
use HackerEsq\FilterModels\FilterModels;
|
||||||
use App\Http\ApiControllers\Controller as ApiController;
|
use App\Http\ApiControllers\Controller as ApiController;
|
||||||
@@ -25,12 +27,22 @@ class HoldingController extends ApiController
|
|||||||
public function show(Portfolio $portfolio, string $symbol)
|
public function show(Portfolio $portfolio, string $symbol)
|
||||||
{
|
{
|
||||||
|
|
||||||
//
|
Gate::authorize('readOnly', $portfolio);
|
||||||
|
|
||||||
|
$holding = $portfolio->holdings()->symbol($symbol)->firstOrFail();
|
||||||
|
|
||||||
|
return HoldingResource::make($holding);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function put(FilterModels $filters)
|
public function update(HoldingRequest $request, Portfolio $portfolio, string $symbol)
|
||||||
{
|
{
|
||||||
|
|
||||||
//
|
Gate::authorize('fullAccess', $portfolio);
|
||||||
|
|
||||||
|
$holding = $portfolio->holdings()->symbol($symbol)->firstOrFail();
|
||||||
|
|
||||||
|
$holding->update($request->validated());
|
||||||
|
|
||||||
|
return HoldingResource::make($holding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Http\Requests\FormRequest;
|
||||||
|
|
||||||
|
class HoldingRequest extends FormRequest
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'reinvest_dividends' => ['sometimes', 'boolean']
|
||||||
|
];
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ Route::middleware(['auth:sanctum'])->name('api.')->group(function () {
|
|||||||
|
|
||||||
// holding
|
// holding
|
||||||
Route::get('/holding', [HoldingController::class, 'index'])->name('holding.index');
|
Route::get('/holding', [HoldingController::class, 'index'])->name('holding.index');
|
||||||
|
Route::get('/holding/{portfolio}/{symbol}', [HoldingController::class, 'show'])->name('holding.show')->scopeBindings();
|
||||||
|
Route::put('/holding/{portfolio}/{symbol}', [HoldingController::class, 'update'])->name('holding.update')->scopeBindings();
|
||||||
|
|
||||||
// market data
|
// market data
|
||||||
Route::get('/market-data/{symbol}', [MarketDataController::class, 'show'])->name('market-data.show');
|
Route::get('/market-data/{symbol}', [MarketDataController::class, 'show'])->name('market-data.show');
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Api;
|
||||||
|
|
||||||
|
use Tests\TestCase;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Holding;
|
||||||
|
use App\Models\Portfolio;
|
||||||
|
use App\Models\Transaction;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
class HoldingsTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected User $user;
|
||||||
|
protected Portfolio $portfolio;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
// make user
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_can_list_holdings()
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
Transaction::factory(10)->create();
|
||||||
|
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->getJson(route('api.holding.index', ['page' => 1, 'itemsPerPage' => 5]))
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonStructure([
|
||||||
|
'data' => [['id', 'symbol', 'portfolio_id', 'total_market_value', 'dividends_earned']],
|
||||||
|
'meta' => ['current_page', 'last_page', 'total'],
|
||||||
|
'links' => ['first', 'last', 'prev', 'next']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_cannot_list_others_holdings()
|
||||||
|
{
|
||||||
|
// create transactions with existing user
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
Transaction::factory(10)->create();
|
||||||
|
|
||||||
|
// Create a new user
|
||||||
|
$this->actingAs($user = User::factory()->create());
|
||||||
|
Transaction::factory(1)->create();
|
||||||
|
$this->actingAs($user)
|
||||||
|
->getJson(route('api.holding.index', ['page' => 1, 'itemsPerPage' => 5]))
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonCount(1, 'data');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_cannot_access_holdings_when_unauthenticated()
|
||||||
|
{
|
||||||
|
$this->getJson(route('api.holding.index'))->assertUnauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_can_show_a_holding()
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
$transaction = Transaction::factory()->create();
|
||||||
|
|
||||||
|
$holding = Holding::where(['portfolio_id' => $transaction->portfolio->id, 'symbol' => $transaction->symbol])->firstOrFail();
|
||||||
|
|
||||||
|
$this->getJson(route('api.holding.show', ['portfolio' => $transaction->portfolio_id, 'symbol' => $transaction->symbol]))
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonFragment([
|
||||||
|
'id' => $holding->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_cannot_show_nonexistent_holdings()
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->getJson(route('api.holding.show', ['portfolio' => 'abc-123-foo-BAR', 'symbol' => 'AAPL']))
|
||||||
|
->assertNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_can_update_holding_options()
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
$transaction = Transaction::factory()->create();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'reinvest_dividends' => true
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->putJson(route('api.holding.update', ['portfolio' => $transaction->portfolio_id, 'symbol' => $transaction->symbol]), $data)
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonFragment([
|
||||||
|
'reinvest_dividends' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_cannot_update_holding_without_permission()
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
$transaction = Transaction::factory()->create();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'reinvest_dividends' => true
|
||||||
|
];
|
||||||
|
|
||||||
|
$otherUser = User::factory()->create();
|
||||||
|
$this->actingAs($otherUser)
|
||||||
|
->putJson(route('api.holding.update', ['portfolio' => $transaction->portfolio_id, 'symbol' => $transaction->symbol]), $data)
|
||||||
|
->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user