seed(RoleAndPermissionSeeder::class); Cache::flush(); $this->admin = User::factory()->admin()->create(); $this->adminUrl = 'http://'.config('app.domains.admin'); }); describe('Customer count and delta', function (): void { it('returns correct totalCustomers and newCustomersThisMonth', function (): void { // Customer from last month User::factory()->customer()->create([ 'created_at' => now()->subMonth(), ]); // Two customers this month User::factory()->customer()->count(2)->create([ 'created_at' => now(), ]); $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('totalCustomers', 3) ->where('newCustomersThisMonth', 2) ); }); }); describe('MRR normalization', function (): void { it('normalizes MRR across billing cycles', function (): void { $user = User::factory()->customer()->create(); // Monthly plan at $30/mo → contributes $30 MRR $monthlyPlan = Plan::factory()->create(['billing_cycle' => 'monthly', 'price' => 30]); PlanPrice::create([ 'plan_id' => $monthlyPlan->id, 'billing_cycle' => 'monthly', 'price' => 30.00, ]); Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_monthly_'.uniqid(), 'stripe_status' => 'active', 'stripe_price' => 'price_monthly', 'plan_id' => $monthlyPlan->id, 'billing_cycle' => 'monthly', ]); // Quarterly plan at $90/quarter → contributes $30 MRR $quarterlyPlan = Plan::factory()->create(['billing_cycle' => 'quarterly', 'price' => 90]); PlanPrice::create([ 'plan_id' => $quarterlyPlan->id, 'billing_cycle' => 'quarterly', 'price' => 90.00, ]); Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_quarterly_'.uniqid(), 'stripe_status' => 'active', 'stripe_price' => 'price_quarterly', 'plan_id' => $quarterlyPlan->id, 'billing_cycle' => 'quarterly', ]); // Annual plan at $120/year → contributes $10 MRR $annualPlan = Plan::factory()->create(['billing_cycle' => 'annual', 'price' => 120]); PlanPrice::create([ 'plan_id' => $annualPlan->id, 'billing_cycle' => 'annual', 'price' => 120.00, ]); Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_annual_'.uniqid(), 'stripe_status' => 'active', 'stripe_price' => 'price_annual', 'plan_id' => $annualPlan->id, 'billing_cycle' => 'annual', ]); // Expected MRR: $30 + $30 + $10 = $70 $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('mrr', fn ($mrr) => (float) $mrr === 70.0) ->where('arr', fn ($arr) => (float) $arr === 840.0) ); }); }); describe('MRR month-over-month change', function (): void { it('calculates mrrChangePercent when previous month data exists', function (): void { $user = User::factory()->customer()->create(); // Subscription created before last month (will count for both current and previous MRR) $plan = Plan::factory()->create(['billing_cycle' => 'monthly', 'price' => 50]); PlanPrice::create([ 'plan_id' => $plan->id, 'billing_cycle' => 'monthly', 'price' => 50.00, ]); Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_old_'.uniqid(), 'stripe_status' => 'active', 'stripe_price' => 'price_old', 'plan_id' => $plan->id, 'billing_cycle' => 'monthly', 'created_at' => now()->subMonths(2), ]); // New subscription this month (only counts for current MRR) $plan2 = Plan::factory()->create(['billing_cycle' => 'monthly', 'price' => 50]); PlanPrice::create([ 'plan_id' => $plan2->id, 'billing_cycle' => 'monthly', 'price' => 50.00, ]); Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_new_'.uniqid(), 'stripe_status' => 'active', 'stripe_price' => 'price_new', 'plan_id' => $plan2->id, 'billing_cycle' => 'monthly', 'created_at' => now(), ]); // Current MRR: $100, Previous MRR: $50 → change: +100% $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('mrr', fn ($mrr) => (float) $mrr === 100.0) ->where('mrrChangePercent', fn ($v) => (float) $v === 100.0) ); }); it('returns null mrrChangePercent when no previous month data', function (): void { $user = User::factory()->customer()->create(); // Subscription created this month only $plan = Plan::factory()->create(['billing_cycle' => 'monthly', 'price' => 50]); PlanPrice::create([ 'plan_id' => $plan->id, 'billing_cycle' => 'monthly', 'price' => 50.00, ]); Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_new_'.uniqid(), 'stripe_status' => 'active', 'stripe_price' => 'price_new', 'plan_id' => $plan->id, 'billing_cycle' => 'monthly', 'created_at' => now(), ]); $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('mrrChangePercent', null) ); }); }); describe('Fee estimation by gateway', function (): void { it('estimates fees correctly for Stripe and PayPal transactions', function (): void { $user = User::factory()->customer()->create(); // Stripe: $100 → fee = ($100 * 0.029) + $0.30 = $3.20 PaymentTransaction::create([ 'user_id' => $user->id, 'gateway' => 'stripe', 'gateway_transaction_id' => 'txn_stripe_'.uniqid(), 'amount' => 100.00, 'currency' => 'USD', 'status' => 'succeeded', 'payment_method' => 'card', 'description' => 'Test stripe payment', ]); // PayPal: $200 → fee = ($200 * 0.0349) + $0.49 = $7.47 PaymentTransaction::create([ 'user_id' => $user->id, 'gateway' => 'paypal', 'gateway_transaction_id' => 'txn_paypal_'.uniqid(), 'amount' => 200.00, 'currency' => 'USD', 'status' => 'succeeded', 'payment_method' => 'paypal', 'description' => 'Test paypal payment', ]); // Total fees: $3.20 + $7.47 = $10.67 $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('totalTransactionRevenue', fn ($v) => (float) $v === 300.0) ->where('estimatedFees', 10.67) ); }); }); describe('Net revenue', function (): void { it('calculates net revenue as total minus fees', function (): void { $user = User::factory()->customer()->create(); PaymentTransaction::create([ 'user_id' => $user->id, 'gateway' => 'stripe', 'gateway_transaction_id' => 'txn_'.uniqid(), 'amount' => 100.00, 'currency' => 'USD', 'status' => 'succeeded', 'payment_method' => 'card', 'description' => 'Payment', ]); // Fee: ($100 * 0.029) + $0.30 = $3.20 // Net: $100 - $3.20 = $96.80 $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('netRevenue', fn ($v) => (float) $v === 96.8) ); }); }); describe('Service breakdown', function (): void { it('returns correct service counts by type', function (): void { $customer = User::factory()->customer()->create(); Service::factory()->count(3)->create([ 'user_id' => $customer->id, 'service_type' => 'vps', 'status' => 'active', ]); Service::factory()->count(2)->create([ 'user_id' => $customer->id, 'service_type' => 'dedicated', 'status' => 'active', ]); // Suspended service should not appear Service::factory()->create([ 'user_id' => $customer->id, 'service_type' => 'vps', 'status' => 'suspended', ]); $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('activeServices', 5) ->where('serviceBreakdown.vps', 3) ->where('serviceBreakdown.dedicated', 2) ); }); }); describe('Overdue tracking', function (): void { it('returns correct overdue count and amount', function (): void { $customer = User::factory()->customer()->create(); Invoice::factory()->count(2)->create([ 'user_id' => $customer->id, 'status' => 'overdue', 'total' => 50.00, ]); // Paid invoice should not count Invoice::factory()->create([ 'user_id' => $customer->id, 'status' => 'paid', 'total' => 100.00, ]); $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('overdueCount', 2) ->where('overdueAmount', fn ($v) => (float) $v === 100.0) ); }); }); describe('Churn health status', function (): void { it('returns healthy when churn rate is below 3%', function (): void { // No subscriptions → 0% churn → healthy $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('currentChurnRate', 0) ->where('churnHealthStatus', 'healthy') ); }); it('returns watch when churn rate is between 3% and 7%', function (): void { $user = User::factory()->customer()->create(); // Create 20 subscriptions before this month for ($i = 0; $i < 20; $i++) { Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_base_'.$i.'_'.uniqid(), 'stripe_status' => 'active', 'stripe_price' => 'price_base', 'created_at' => now()->subMonths(2), ]); } // Cancel 1 this month → 5% churn → watch Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_cancelled_'.uniqid(), 'stripe_status' => 'canceled', 'stripe_price' => 'price_cancelled', 'created_at' => now()->subMonths(2), 'cancelled_at' => now(), ]); $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('churnHealthStatus', 'watch') ); }); it('returns high when churn rate exceeds 7%', function (): void { $user = User::factory()->customer()->create(); // Create 10 subscriptions before this month for ($i = 0; $i < 10; $i++) { Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_base_'.$i.'_'.uniqid(), 'stripe_status' => 'active', 'stripe_price' => 'price_base', 'created_at' => now()->subMonths(2), ]); } // Cancel 1 this month → ~9.1% churn (1/11) → high Subscription::create([ 'user_id' => $user->id, 'type' => 'default', 'stripe_id' => 'sub_cancelled_'.uniqid(), 'stripe_status' => 'canceled', 'stripe_price' => 'price_cancelled', 'created_at' => now()->subMonths(2), 'cancelled_at' => now(), ]); $this->actingAs($this->admin) ->get($this->adminUrl.'/dashboard') ->assertOk() ->assertInertia(fn ($page) => $page ->where('churnHealthStatus', 'high') ); }); });