From ea22c27710cc51c1e39c855fcd53f8dae7e6c69b Mon Sep 17 00:00:00 2001 From: hackerESQ Date: Mon, 27 Jan 2025 20:04:03 -0600 Subject: [PATCH] wip --- app/Http/ApiControllers/HoldingController.php | 13 ++ .../ApiControllers/TransactionController.php | 10 +- app/Http/Requests/FormRequest.php | 14 ++ app/Http/Requests/PortfolioRequest.php | 2 +- app/Http/Requests/TransactionRequest.php | 47 +++- app/Models/Portfolio.php | 43 ++++ app/Rules/QuantityValidationRule.php | 13 +- database/factories/PortfolioFactory.php | 2 +- .../livewire/share-portfolio-form.blade.php | 22 +- routes/api.php | 10 +- tests/Api/PortfoliosTest.php | 202 ++++++++++++++++++ tests/Api/TransactionsTest.php | 200 +++++++++++++++++ tests/ApiTokenPermissionsTest.php | 72 +++---- 13 files changed, 569 insertions(+), 81 deletions(-) create mode 100644 app/Http/Requests/FormRequest.php create mode 100644 tests/Api/PortfoliosTest.php create mode 100644 tests/Api/TransactionsTest.php diff --git a/app/Http/ApiControllers/HoldingController.php b/app/Http/ApiControllers/HoldingController.php index 15330f0..ee345df 100644 --- a/app/Http/ApiControllers/HoldingController.php +++ b/app/Http/ApiControllers/HoldingController.php @@ -3,6 +3,7 @@ namespace App\Http\ApiControllers; use App\Models\Holding; +use App\Models\Portfolio; use Illuminate\Http\Request; use App\Http\Resources\HoldingResource; use HackerEsq\FilterModels\FilterModels; @@ -20,4 +21,16 @@ class HoldingController extends ApiController return HoldingResource::collection($filters->paginated()); } + + public function show(Portfolio $portfolio, string $symbol) + { + + // + } + + public function put(FilterModels $filters) + { + + // + } } \ No newline at end of file diff --git a/app/Http/ApiControllers/TransactionController.php b/app/Http/ApiControllers/TransactionController.php index 3441df0..64924bb 100644 --- a/app/Http/ApiControllers/TransactionController.php +++ b/app/Http/ApiControllers/TransactionController.php @@ -23,7 +23,9 @@ class TransactionController extends ApiController } public function store(TransactionRequest $request) - { + { + Gate::authorize('fullAccess', $request->portfolio); + $transaction = Transaction::create($request->validated()); return TransactionResource::make($transaction); @@ -31,14 +33,14 @@ class TransactionController extends ApiController public function show(Transaction $transaction) { - Gate::authorize('readOnly', $transaction); + Gate::authorize('readOnly', $transaction->portfolio); return TransactionResource::make($transaction); } public function update(TransactionRequest $request, Transaction $transaction) { - Gate::authorize('fullAccess', $transaction); + Gate::authorize('fullAccess', $transaction->portfolio); $transaction->update($request->validated()); @@ -47,7 +49,7 @@ class TransactionController extends ApiController public function destroy(Transaction $transaction) { - Gate::authorize('fullAccess', $transaction); + Gate::authorize('fullAccess', $transaction->portfolio); $transaction->delete(); diff --git a/app/Http/Requests/FormRequest.php b/app/Http/Requests/FormRequest.php new file mode 100644 index 0000000..d0f8334 --- /dev/null +++ b/app/Http/Requests/FormRequest.php @@ -0,0 +1,14 @@ +request->get($key) ?? $this->{$model}?->{$key}; + } +} \ No newline at end of file diff --git a/app/Http/Requests/PortfolioRequest.php b/app/Http/Requests/PortfolioRequest.php index 772c6b2..ab94472 100644 --- a/app/Http/Requests/PortfolioRequest.php +++ b/app/Http/Requests/PortfolioRequest.php @@ -2,7 +2,7 @@ namespace App\Http\Requests; -use Illuminate\Foundation\Http\FormRequest; +use App\Http\Requests\FormRequest; class PortfolioRequest extends FormRequest { diff --git a/app/Http/Requests/TransactionRequest.php b/app/Http/Requests/TransactionRequest.php index 2d7d43c..c0412d3 100644 --- a/app/Http/Requests/TransactionRequest.php +++ b/app/Http/Requests/TransactionRequest.php @@ -2,11 +2,16 @@ namespace App\Http\Requests; -use Illuminate\Foundation\Http\FormRequest; +use App\Models\Portfolio; +use App\Http\Requests\FormRequest; +use App\Rules\SymbolValidationRule; +use App\Rules\QuantityValidationRule; class TransactionRequest extends FormRequest { + public ?Portfolio $portfolio; + /** * Get the validation rules that apply to the request. * @@ -14,15 +19,45 @@ class TransactionRequest extends FormRequest */ public function rules(): array { + $this->portfolio = Portfolio::findOrFail($this->requestOrModelValue('portfolio_id', 'transaction')); $rules = [ - 'title' => ['required', 'string', 'min:5', 'max:255'], - 'notes' => ['sometimes', 'nullable', 'string'], - 'wishlist' => ['sometimes', 'nullable', 'boolean'], + 'portfolio_id' => [], // validated by findOrFail() above + 'symbol' => ['required', 'string', new SymbolValidationRule], + 'transaction_type' => ['required', 'string', 'in:BUY,SELL'], + 'date' => ['required', 'date_format:Y-m-d', 'before_or_equal:' . now()->format('Y-m-d')], + 'quantity' => [ + 'required', + 'numeric', + 'min:0', + new QuantityValidationRule( + $this->portfolio, + $this->requestOrModelValue('symbol', 'transaction'), + $this->requestOrModelValue('transaction_type', 'transaction'), + $this->requestOrModelValue('date', 'transaction') + ) + ], + 'cost_basis' => ['exclude_if:transaction_type,SELL', 'min:0', 'numeric'], + 'sale_price' => ['exclude_if:transaction_type,BUY', 'min:0', 'numeric'], ]; - if (!is_null($this->portfolio)) { - $rules['title'][0] = 'sometimes'; + if (!is_null($this->transaction)) { + $rules['symbol'][0] = 'sometimes'; + $rules['transaction_type'][0] = 'sometimes'; + $rules['date'][0] = 'sometimes'; + $rules['quantity'][0] = 'sometimes'; + + if ( + $this->requestOrModelValue('transaction_type', 'transaction') == 'SELL' + && $this->requestOrModelValue('sale_price', 'transaction') == null + ) { + $rules['sale_price'][0] = 'required'; + } elseif ( + $this->requestOrModelValue('transaction_type', 'transaction') == 'BUY' + && $this->requestOrModelValue('cost_basis', 'transaction') == null + ) { + $rules['cost_basis'][0] = 'required'; + } } return $rules; diff --git a/app/Models/Portfolio.php b/app/Models/Portfolio.php index 0660745..2195fdf 100644 --- a/app/Models/Portfolio.php +++ b/app/Models/Portfolio.php @@ -5,11 +5,13 @@ namespace App\Models; use App\Models\AiChat; use Carbon\CarbonPeriod; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Model; use App\Interfaces\MarketData\MarketDataInterface; use Illuminate\Database\Eloquent\Concerns\HasUuids; +use App\Notifications\InvitedOnboardingNotification; use Illuminate\Database\Eloquent\Factories\HasFactory; class Portfolio extends Model @@ -129,6 +131,7 @@ class Portfolio extends Model // save $portfolio->users()->sync($owner); + static::$owner_id = null; } } @@ -253,4 +256,44 @@ class Portfolio extends Model } return $formattedHoldings; } + + /** + * Share a portfolio with a user + * + * @param string $email + * @param boolean $fullAccess + * @return void + */ + public function share(string $email, bool $fullAccess = false): void + { + $user = User::firstOrCreate([ + 'email' => $email + ], [ + 'name' => Str::title(Str::before($email, '@')) + ]); + + $permissions[$user->id] = [ + 'full_access' => $fullAccess + ]; + + $sync = $this->users()->syncWithoutDetaching($permissions); + + if (!empty($sync['attached'])) { + + foreach($sync['attached'] as $newUserId) { + User::find($newUserId)->notify(new InvitedOnboardingNotification($this, auth()->user())); + }; + } + } + + /** + * Un-share a portfolio + * + * @param string $userId + * @return void + */ + public function unShare(string $userId): void + { + $this->users()->detach($userId); + } } diff --git a/app/Rules/QuantityValidationRule.php b/app/Rules/QuantityValidationRule.php index 0dea105..74027bc 100644 --- a/app/Rules/QuantityValidationRule.php +++ b/app/Rules/QuantityValidationRule.php @@ -13,10 +13,10 @@ class QuantityValidationRule implements ValidationRule * @return void */ public function __construct( - protected Portfolio $portfolio, - protected string $symbol, - protected string $transactionType, - protected string $date + protected ?Portfolio $portfolio, + protected ?string $symbol, + protected ?string $transactionType, + protected ?string $date ) { $this->portfolio = $portfolio; $this->symbol = $symbol; @@ -34,6 +34,11 @@ class QuantityValidationRule implements ValidationRule */ public function validate(string $attribute, mixed $value, \Closure $fail): void { + if (is_null($this->portfolio) || is_null($this->symbol) || is_null($this->transactionType) || is_null($this->date)) { + // + $fail(__('The quantity must not be greater than the available quantity.')); + } + if ($this->transactionType == 'SELL') { $purchase_qty = $this->portfolio->transactions() diff --git a/database/factories/PortfolioFactory.php b/database/factories/PortfolioFactory.php index 85ac7bd..175631d 100644 --- a/database/factories/PortfolioFactory.php +++ b/database/factories/PortfolioFactory.php @@ -17,7 +17,7 @@ class PortfolioFactory extends Factory public function definition(): array { return [ - 'title' => $this->faker->word, + 'title' => $this->faker->words(4, true), 'created_at' => now(), 'updated_at' => now(), ]; diff --git a/resources/views/livewire/share-portfolio-form.blade.php b/resources/views/livewire/share-portfolio-form.blade.php index f80b021..6f2a55c 100644 --- a/resources/views/livewire/share-portfolio-form.blade.php +++ b/resources/views/livewire/share-portfolio-form.blade.php @@ -7,7 +7,6 @@ use Livewire\Attributes\Rule; use Livewire\Volt\Component; use Illuminate\Support\Collection; use Mary\Traits\Toast; -use App\Notifications\InvitedOnboardingNotification; new class extends Component { @@ -75,7 +74,7 @@ new class extends Component { unset($this->permissions[$userId]); - $this->portfolio->users()->sync($this->permissions); + $this->portfolio->unShare($userId); $this->portfolio->refresh(); @@ -92,24 +91,7 @@ new class extends Component { $this->validate(); - $user = User::firstOrCreate([ - 'email' => $this->emailAddress - ], [ - 'name' => Str::title(Str::before($this->emailAddress, '@')) - ]); - - $this->permissions[$user->id] = [ - 'full_access' => $this->fullAccess - ]; - - $sync = $this->portfolio->users()->sync($this->permissions); - - if (!empty($sync['attached'])) { - - foreach($sync['attached'] as $newUserId) { - User::find($newUserId)->notify(new InvitedOnboardingNotification($this->portfolio, auth()->user())); - }; - } + $this->portfolio->share($this->emailAddress, $this->fullAccess); $this->success(__('Shared portfolio with user')); $this->portfolio->refresh(); diff --git a/routes/api.php b/routes/api.php index 6249abb..949c05b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,20 +7,20 @@ use App\Http\ApiControllers\PortfolioController; use App\Http\ApiControllers\MarketDataController; use App\Http\ApiControllers\TransactionController; -Route::middleware(['auth:sanctum'])->group(function () { +Route::middleware(['auth:sanctum'])->name('api.')->group(function () { // user - Route::get('/me', [UserController::class, 'me']); + Route::get('/me', [UserController::class, 'me'])->name('me'); // portfolio Route::apiResource('/portfolio', PortfolioController::class); // transaction - Route::get('/transaction', [TransactionController::class, 'index']); + Route::apiResource('/transaction', TransactionController::class); // holding - Route::get('/holding', [HoldingController::class, 'index']); + Route::get('/holding', [HoldingController::class, 'index'])->name('holding.index'); // market data - Route::get('/market-data/{symbol}', [MarketDataController::class, 'show']); + Route::get('/market-data/{symbol}', [MarketDataController::class, 'show'])->name('market-data.show'); }); \ No newline at end of file diff --git a/tests/Api/PortfoliosTest.php b/tests/Api/PortfoliosTest.php new file mode 100644 index 0000000..44dfd5b --- /dev/null +++ b/tests/Api/PortfoliosTest.php @@ -0,0 +1,202 @@ +user = User::factory()->create(); + } + + public function test_can_list_own_portfolios_with_pagination() + { + $this->actingAs($this->user); + + Portfolio::factory(10)->create(); + + $this->actingAs($this->user) + ->getJson(route('api.portfolio.index', ['page' => 1, 'itemsPerPage' => 5])) + ->assertOk() + ->assertJsonStructure([ + 'data' => [['id', 'title', 'owner', 'holdings', 'transactions']], + 'meta' => ['current_page', 'last_page', 'total'], + 'links' => ['first', 'last', 'prev', 'next'] + ]); + } + + public function test_cannot_list_others_portfolios() + { + // create portfolios with existing user + $this->actingAs($this->user); + Portfolio::factory(10)->create(); + + // Create a new user + $this->actingAs($user = User::factory()->create()); + Portfolio::factory(1)->create(); + $this->actingAs($user) + ->getJson(route('api.portfolio.index', ['page' => 1, 'itemsPerPage' => 5])) + ->assertOk() + ->assertJsonCount(1, 'data'); + } + + public function test_cannot_access_portfolios_when_unauthenticated() + { + $this->getJson(route('api.portfolio.index'))->assertUnauthorized(); + } + + public function test_can_create_a_portfolio() + { + $data = Portfolio::factory()->make()->toArray(); + + $this->actingAs($this->user) + ->postJson(route('api.portfolio.store'), $data) + ->assertCreated() + ->assertJsonStructure(['id', 'title', 'owner']); + + $this->assertDatabaseHas('portfolios', ['title' => $data['title']]); + } + + public function test_cannot_create_portfolio_without_required_fields() + { + $this->actingAs($this->user) + ->postJson(route('api.portfolio.store'), []) + ->assertUnprocessable() + ->assertJsonValidationErrors(['title']); + } + + public function test_can_show_a_portfolio() + { + $this->actingAs($this->user); + $portfolio = Portfolio::factory()->create(); + + $this->actingAs($this->user) + ->getJson(route('api.portfolio.show', $portfolio)) + ->assertOk() + ->assertJsonStructure(['id', 'title', 'owner']); + } + + public function test_cannot_show_nonexistent_portfolio() + { + $this->actingAs($this->user) + ->getJson(route('api.portfolio.show', ['portfolio' => 999])) + ->assertNotFound(); + } + + public function test_can_update_a_portfolio() + { + $updatedData = ['title' => 'Updated Portfolio Title']; + + $this->actingAs($this->user); + $portfolio = Portfolio::factory()->create(); + + $this->actingAs($this->user) + ->putJson(route('api.portfolio.update', $portfolio), $updatedData) + ->assertOk() + ->assertJson($updatedData); + + $this->assertDatabaseHas('portfolios', $updatedData); + } + + public function test_shared_user_can_update_portfolio() + { + // create portfolio + $this->actingAs($this->user); + $portfolio = Portfolio::factory()->create(); + + // share it + $otherUser = User::factory()->create(); + $portfolio->share($otherUser->email, true); + + // shared user tries to update it + $this->actingAs($otherUser) + ->putJson(route('api.portfolio.update', $portfolio), ['title' => 'A brand new updated title']) + ->assertOk() + ->assertJsonFragment([ + 'title' => 'A brand new updated title' + ]); + } + + public function test_removed_user_cannot_update_portfolio() + { + // create portfolio + $this->actingAs($this->user); + $portfolio = Portfolio::factory()->create(); + + // share it + $otherUser = User::factory()->create(); + $portfolio->share($otherUser->email, true); + + // unshare it + $otherUser = User::factory()->create(); + $portfolio->unShare($otherUser->id); + + // shared user tries to update it + $this->actingAs($otherUser) + ->putJson(route('api.portfolio.update', $portfolio), ['Title' => 'A brand new updated title']) + ->assertForbidden(); + } + + public function test_read_only_user_cannot_update_portfolio() + { + // create portfolio + $this->actingAs($this->user); + $portfolio = Portfolio::factory()->create(); + + // share it + $otherUser = User::factory()->create(); + $portfolio->share($otherUser->email, false); + + // shared user tries to update it + $this->actingAs($otherUser) + ->putJson(route('api.portfolio.update', $portfolio), ['Title' => 'A brand new updated title']) + ->assertForbidden(); + } + + public function test_cannot_update_portfolio_without_permission() + { + $this->actingAs($this->user); + $portfolio = Portfolio::factory()->create(); + + $otherUser = User::factory()->create(); + $this->actingAs($otherUser) + ->putJson(route('api.portfolio.update', $portfolio), ['title' => 'New Title']) + ->assertForbidden(); + } + + public function test_can_delete_a_portfolio() + { + $this->actingAs($this->user); + $portfolio = Portfolio::factory()->create(); + + $this->actingAs($this->user) + ->deleteJson(route('api.portfolio.destroy', $portfolio)) + ->assertNoContent(); + + $this->assertDatabaseMissing('portfolios', ['id' => $portfolio->id]); + } + + public function test_cannot_delete_portfolio_without_permission() + { + $this->actingAs($this->user); + $portfolio = Portfolio::factory()->create(); + + $otherUser = User::factory()->create(); + $this->actingAs($otherUser) + ->deleteJson(route('api.portfolio.destroy', $portfolio)) + ->assertForbidden(); + } +} \ No newline at end of file diff --git a/tests/Api/TransactionsTest.php b/tests/Api/TransactionsTest.php new file mode 100644 index 0000000..c0cfbba --- /dev/null +++ b/tests/Api/TransactionsTest.php @@ -0,0 +1,200 @@ +user = User::factory()->create(); + + // make portfolio + $this->portfolio = Portfolio::factory()->makeOne(); + $this->portfolio->setOwnerIdAttribute($this->user->id); + $this->portfolio->save(); + } + + public function test_can_list_transactions() + { + $this->actingAs($this->user); + + Transaction::factory(10)->create(); + + $this->actingAs($this->user) + ->getJson(route('api.transaction.index', ['page' => 1, 'itemsPerPage' => 5])) + ->assertOk() + ->assertJsonStructure([ + 'data' => [['id', 'symbol', 'transaction_type', 'portfolio_id', 'date']], + 'meta' => ['current_page', 'last_page', 'total'], + 'links' => ['first', 'last', 'prev', 'next'] + ]); + } + + public function test_cannot_list_others_transactions() + { + // 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.transaction.index', ['page' => 1, 'itemsPerPage' => 5])) + ->assertOk() + ->assertJsonCount(1, 'data'); + } + + public function test_cannot_access_transactions_when_unauthenticated() + { + $this->getJson(route('api.transaction.index'))->assertUnauthorized(); + } + + public function test_can_create_transaction() + { + $this->actingAs($this->user); + + $data = [ + 'symbol' => 'AAPL', + 'portfolio_id' => $this->portfolio->id, + 'transaction_type' => 'BUY', + 'quantity' => 10, + 'date' => now()->toDateString(), + 'cost_basis' => 150, + ]; + + $this->postJson(route('api.transaction.store'), $data) + ->assertCreated() + ->assertJsonStructure([ + 'id', + 'symbol', + 'portfolio_id', + 'transaction_type', + 'quantity', + 'date', + 'cost_basis', + 'sale_price' + ]); + } + + public function test_cannot_create_transaction_without_required_fields() + { + $this->actingAs($this->user) + ->postJson(route('api.transaction.store'), [ + 'portfolio_id' => $this->portfolio->id, + 'symbol' => null + ]) + ->assertUnprocessable() + ->assertJsonValidationErrors(['symbol']); + } + + public function test_can_show_a_transaction() + { + $this->actingAs($this->user); + + $transaction = Transaction::factory()->create(); + + $this->getJson(route('api.transaction.show', $transaction)) + ->assertOk() + ->assertJsonFragment([ + 'id' => $transaction->id, + ]); + } + + public function test_cannot_show_nonexistent_transactions() + { + $this->actingAs($this->user) + ->getJson(route('api.transaction.show', ['transaction' => 999])) + ->assertNotFound(); + } + + public function test_can_update_a_transaction() + { + $this->actingAs($this->user); + + $transaction = Transaction::factory()->create(); + + $data = [ + 'symbol' => 'ZZZ', + 'transaction_type' => 'BUY', + 'cost_basis' => 200.19, + 'quantity' => 5 + ]; + + $this->actingAs($this->user) + ->putJson(route('api.transaction.update', $transaction), $data) + ->assertOk() + ->assertJsonFragment([ + 'symbol' => 'ZZZ', + 'transaction_type' => 'BUY', + 'cost_basis' => 200.19, + 'quantity' => 5, + ]); + } + + public function test_shared_user_can_update_transaction() + { + // create transaction (and portfolio) + $this->actingAs($this->user); + $transaction = Transaction::factory()->create(); + + // share it + $otherUser = User::factory()->create(); + $transaction->portfolio->share($otherUser->email, true); + + // shared user tries to update it + $this->actingAs($otherUser) + ->putJson(route('api.transaction.update', $transaction), ['symbol' => 'ZZZ']) + ->assertOk() + ->assertJsonFragment([ + 'symbol' => 'ZZZ' + ]); + } + + public function test_cannot_update_transaction_without_permission() + { + $this->actingAs($this->user); + $transaction = Transaction::factory()->create(); + + $otherUser = User::factory()->create(); + $this->actingAs($otherUser) + ->putJson(route('api.transaction.update', $transaction), ['symbol' => 'AAPL']) + ->assertForbidden(); + } + + public function test_can_delete_a_transaction() + { + $this->actingAs($this->user); + $transaction = Transaction::factory()->create(); + + $this->deleteJson(route('api.transaction.destroy', $transaction)) + ->assertNoContent(); + + $this->assertDatabaseMissing('transactions', ['id' => $transaction->id]); + } + + public function test_cannot_delete_transaction_without_permission() + { + $this->actingAs($this->user); + $transaction = Transaction::factory()->create(); + + $otherUser = User::factory()->create(); + $this->actingAs($otherUser) + ->deleteJson(route('api.transaction.destroy', $transaction)) + ->assertForbidden(); + } +} \ No newline at end of file diff --git a/tests/ApiTokenPermissionsTest.php b/tests/ApiTokenPermissionsTest.php index 70ac458..9afd10d 100644 --- a/tests/ApiTokenPermissionsTest.php +++ b/tests/ApiTokenPermissionsTest.php @@ -14,50 +14,45 @@ class ApiTokenPermissionsTest extends TestCase { use RefreshDatabase; - // public function test_api_tokens_can_be_deleted(): void - // { - // if (! Features::hasApiFeatures()) { - // $this->markTestSkipped('API support is not enabled.'); - // } + public function test_api_tokens_can_be_deleted(): void + { + if (! Features::hasApiFeatures()) { + $this->markTestSkipped('API support is not enabled.'); + } - // $this->actingAs($user = User::factory()->create()); + $this->actingAs($user = User::factory()->create()); - // $token = $user->tokens()->create([ - // 'name' => 'Test Token', - // 'token' => Str::random(40), - // 'abilities' => ['create', 'read'], - // ]); + $token = $user->tokens()->create([ + 'name' => 'Test Token', + 'token' => Str::random(40), + 'abilities' => [], + ]); - // Livewire::test(ApiTokenManager::class) - // ->set(['apiTokenIdBeingDeleted' => $token->id]) - // ->call('deleteApiToken'); + Livewire::test(ApiTokenManager::class) + ->set(['apiTokenIdBeingDeleted' => $token->id]) + ->call('deleteApiToken'); - // $this->assertCount(0, $user->fresh()->tokens); - // } + $this->assertCount(0, $user->fresh()->tokens); + } - // public function test_api_tokens_can_be_created(): void - // { - // if (! Features::hasApiFeatures()) { - // $this->markTestSkipped('API support is not enabled.'); - // } + public function test_api_tokens_can_be_created(): void + { + if (! Features::hasApiFeatures()) { + $this->markTestSkipped('API support is not enabled.'); + } - // $this->actingAs($user = User::factory()->create()); + $this->actingAs($user = User::factory()->create()); - // Livewire::test(ApiTokenManager::class) - // ->set(['createApiTokenForm' => [ - // 'name' => 'Test Token', - // 'permissions' => [ - // 'read', - // 'update', - // ], - // ]]) - // ->call('createApiToken'); + Livewire::test(ApiTokenManager::class) + ->set(['createApiTokenForm' => [ + 'name' => 'Test Token', + 'permissions' => [], + ]]) + ->call('createApiToken'); - // $this->assertCount(1, $user->fresh()->tokens); - // $this->assertEquals('Test Token', $user->fresh()->tokens->first()->name); - // $this->assertTrue($user->fresh()->tokens->first()->can('read')); - // $this->assertFalse($user->fresh()->tokens->first()->can('delete')); - // } + $this->assertCount(1, $user->fresh()->tokens); + $this->assertEquals('Test Token', $user->fresh()->tokens->first()->name); + } // public function test_api_token_permissions_can_be_updated(): void // { @@ -76,10 +71,7 @@ class ApiTokenPermissionsTest extends TestCase // Livewire::test(ApiTokenManager::class) // ->set(['managingPermissionsFor' => $token]) // ->set(['updateApiTokenForm' => [ - // 'permissions' => [ - // 'delete', - // 'missing-permission', - // ], + // 'permissions' => [], // ]]) // ->call('updateApiToken');