diff --git a/database/factories/TransactionFactory.php b/database/factories/TransactionFactory.php
index a0c3cdc..27c8b2f 100644
--- a/database/factories/TransactionFactory.php
+++ b/database/factories/TransactionFactory.php
@@ -19,14 +19,18 @@ class TransactionFactory extends Factory
*/
public function definition(): array
{
+ $transaction_type = $this->faker->randomElement(['BUY', 'SELL']);
+
return [
- 'symbol' => $this->faker->randomElement(['AAPL', 'GOOGL', 'AMZN']),
- 'transaction_type' => static::$transaction_type = $this->faker->randomElement(['BUY', 'SELL']),
- 'portfolio_id' => Portfolio::factory()->create(),
+ 'symbol' => $this->faker->randomElement(['AAPL', 'GOOG', 'AMZN']),
+ 'transaction_type' => $transaction_type,
+ 'portfolio_id' => Portfolio::factory()->create()->id,
'date' => $this->faker->date('Y-m-d'),
- 'quantity' => $this->faker->randomFloat(2, 1, 100),
- 'cost_basis' => $this->faker->randomFloat(2, 10, 500),
- 'sale_price' => static::$transaction_type == 'SELL'
+ 'quantity' => 1,
+ 'cost_basis' => $transaction_type == 'BUY'
+ ? $this->faker->randomFloat(2, 10, 500)
+ : null,
+ 'sale_price' => $transaction_type == 'SELL'
? $this->faker->randomFloat(2, 10, 500)
: null,
];
@@ -39,7 +43,7 @@ class TransactionFactory extends Factory
]);
}
- public function portfolios($portfolio_id): static
+ public function portfolio($portfolio_id): static
{
return $this->state(fn (array $attributes) => [
'portfolio_id' => $portfolio_id,
@@ -50,6 +54,8 @@ class TransactionFactory extends Factory
{
return $this->state(fn (array $attributes) => [
'transaction_type' => 'BUY',
+ 'cost_basis' => $this->faker->randomFloat(2, 10, 500),
+ 'sale_price' => null
]);
}
@@ -57,6 +63,8 @@ class TransactionFactory extends Factory
{
return $this->state(fn (array $attributes) => [
'transaction_type' => 'SELL',
+ 'sale_price' => $this->faker->randomFloat(2, 10, 500),
+ 'cost_basis' => null,
]);
}
}
diff --git a/phpunit.xml b/phpunit.xml
index 6dd89fc..ea39f6e 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -26,5 +26,6 @@
+
diff --git a/tests/CaptureDailyChangeTest.php b/tests/CaptureDailyChangeTest.php
new file mode 100644
index 0000000..527bbbe
--- /dev/null
+++ b/tests/CaptureDailyChangeTest.php
@@ -0,0 +1,56 @@
+actingAs($user = User::factory()->create());
+
+ $this->portfolio = Portfolio::factory()->create();
+ Transaction::factory(5)->buy()->portfolio($this->portfolio->id)->symbol('AAPL')->create();
+ $this->transaction = Transaction::factory()->sell()->portfolio($this->portfolio->id)->symbol('AAPL')->create();
+ }
+
+ /**
+ */
+ public function test_daily_change_for_portfolios()
+ {
+ // Run the command
+ Artisan::call('daily-change:capture');
+
+ // Assert the daily change was captured for the portfolio
+ $this->assertDatabaseHas('daily_change', [
+ 'portfolio_id' => $this->portfolio->id,
+ ]);
+
+ $output = Artisan::output();
+ $this->assertStringContainsString('Capturing daily change for', $output);
+
+ $daily_change = DailyChange::where([
+ 'portfolio_id' => $this->portfolio->id,
+ ])->get();
+
+ $this->assertCount(1, $daily_change);
+
+ $this->assertEqualsWithDelta(
+ $this->transaction->sale_price - $this->transaction->cost_basis,
+ $daily_change->first()->realized_gains,
+ 0.01
+ );
+
+ }
+}
diff --git a/tests/DashboardTest.php b/tests/DashboardTest.php
new file mode 100644
index 0000000..e675b0a
--- /dev/null
+++ b/tests/DashboardTest.php
@@ -0,0 +1,77 @@
+create();
+ $this->actingAs($user);
+
+ Portfolio::factory(5)->create();
+
+ $this->assertCount(5, $user->portfolios);
+ }
+
+ /**
+ */
+ public function test_user_has_transactions(): void
+ {
+ $user = User::factory()->create();
+ $this->actingAs($user);
+
+ Transaction::factory(10)->create();
+
+ $this->assertCount(10, $user->transactions);
+ }
+
+ /**
+ */
+ public function test_user_has_holdings(): void
+ {
+ $user = User::factory()->create();
+ $this->actingAs($user);
+
+ $portfolio = Portfolio::factory()->create();
+
+ Transaction::factory(5)->symbol('AAPL')->portfolio($portfolio->id)->create();
+
+ $this->assertCount(1, $user->holdings);
+ }
+
+ /**
+ */
+ public function test_user_has_dashboard_metrics(): void
+ {
+ $user = User::factory()->create();
+ $this->actingAs($user);
+
+ $portfolio = Portfolio::factory()->create();
+
+ Transaction::factory(5)->buy()->portfolio($portfolio->id)->symbol('AAPL')->create();
+ $transaction = Transaction::factory()->sell()->portfolio($portfolio->id)->symbol('AAPL')->create();
+
+ $metrics = Holding::query()
+ ->myHoldings()
+ ->withPortfolioMetrics()
+ ->first();
+
+ $this->assertEqualsWithDelta(
+ $transaction->sale_price - $transaction->cost_basis,
+ $metrics->realized_gain_dollars,
+ 0.01
+ );
+ }
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index a169a5a..02df1c3 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -11,7 +11,7 @@ abstract class TestCase extends BaseTestCase
{
parent::setUp();
- Artisan::call('migrate');
+ //
}
protected function tearDown(): void
diff --git a/tests/TransactionsTest.php b/tests/TransactionsTest.php
index 5beb9a2..98fc924 100644
--- a/tests/TransactionsTest.php
+++ b/tests/TransactionsTest.php
@@ -2,72 +2,78 @@
namespace Tests;
+use Tests\TestCase;
use App\Models\User;
+use App\Models\Holding;
use App\Models\Portfolio;
use App\Models\Transaction;
-use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class TransactionsTest extends TestCase
{
use RefreshDatabase;
+
/**
- * A basic test example.
*/
public function test_can_create_a_transaction(): void
{
- $user = User::factory()->create();
- $this->actingAs($user);
+ $this->actingAs($user = User::factory()->create());
- $transactions = Transaction::factory()->create();
+ $transaction = Transaction::factory()->create();
+
+ $this->assertNotNull($transaction);
+ }
+
+ /**
+ */
+ public function test_sales_calculate_cost_basis(): void
+ {
+ $this->actingAs($user = User::factory()->create());
+
+ Transaction::factory(5)->buy()->symbol('AAPL')->create();
+
+ $transaction = Transaction::factory()->sell()->symbol('AAPL')->create();
+
+ $this->assertNotNull($transaction->cost_basis);
+ }
+
+ /**
+ */
+ public function test_purchases_dont_have_sale_price(): void
+ {
+ $this->actingAs($user = User::factory()->create());
+
+ $transaction = Transaction::factory()->buy()->create();
+
+ $this->assertNull($transaction->sale_price);
+ }
+
+ /**
+ */
+ public function test_transaction_synced_to_holding(): void
+ {
+ $this->actingAs($user = User::factory()->create());
+
+ $portfolio = Portfolio::factory()->create();
+
+ Transaction::factory(5)->buy()->portfolio($portfolio->id)->symbol('AAPL')->create();
+ $transaction = Transaction::factory()->sell()->portfolio($portfolio->id)->symbol('AAPL')->create();
+
+ $this->assertDatabaseHas('holdings', [
+ 'portfolio_id' => $portfolio->id,
+ 'symbol' => 'AAPL',
+ 'quantity' => 4
+ ]);
+
+ $holding = Holding::where([
+ 'portfolio_id' => $portfolio->id,
+ 'symbol' => 'AAPL'
+ ])->first();
+
+ $this->assertEqualsWithDelta(
+ $holding->realized_gain_dollars,
+ $transaction->sale_price - $transaction->cost_basis,
+ 0.01
+ );
}
}
-
-
-
-
- // static::saving(function ($transaction) {
-
- // if ($transaction->transaction_type == 'SELL') {
-
- // $transaction->ensureCostBasisIsAddedToSale();
- // }
- // });
-
- // static::saved(function ($transaction) {
-
- // $transaction->syncToHolding();
-
- // $transaction->refreshMarketData();
-
- // cache()->tags(['metrics', $transaction->portfolio_id])->flush();
- // });
-
- // public function update()
- // {
-
- // $this->transaction->update($this->validate());
- // // $this->transaction->owner_id = auth()->user()->id;
- // $this->transaction->save();
-
- // $this->success(__('Transaction updated'));
-
- // $this->dispatch('toggle-manage-transaction');
- // $this->dispatch('transaction-updated');
- // }
-
- // public function save()
- // {
- // $validated = $this->validate();
-
- // if (!isset($this->portfolio)) {
- // $this->portfolio = Portfolio::find($this->portfolio_id);
- // }
-
- // $transaction = $this->portfolio->transactions()->create($validated);
- // $transaction->save();
-
- // $this->dispatch('transaction-saved');
-
- // $this->success(__('Transaction created'), redirectTo: route('holding.show', ['portfolio' => $this->portfolio->id, 'symbol' => $this->symbol]));
- // }