get('/dashboard')->assertRedirect('/login'); }); it('shows the dashboard to authenticated users', function () { $user = User::factory()->create(); $this->actingAs($user)->get('/dashboard') ->assertOk() ->assertSee('CluPilot') ->assertSee(__('dashboard.title')); }); it('shows no next invoice once the customer has given notice', function () { // Cancelling leaves the subscription active until the term runs out, so // `current_period_end` becomes the day service ENDS rather than the day the // next charge falls. Billing a renewal that will never happen, on the sheet // a customer checks their invoices against, is the worst line this page // could carry. $user = App\Models\User::factory()->create(); $customer = App\Models\Customer::factory()->create(['user_id' => $user->id]); $instance = App\Models\Instance::factory()->create([ 'customer_id' => $customer->id, 'status' => 'cancellation_scheduled', 'cancel_requested_at' => now(), 'service_ends_at' => now()->addMonth(), ]); App\Models\Subscription::factory()->create([ 'customer_id' => $customer->id, 'instance_id' => $instance->id, 'status' => 'active', 'price_cents' => 17900, 'currency' => 'EUR', 'term' => 'monthly', 'current_period_end' => now()->addMonth(), ]); Livewire::actingAs($user) ->test(App\Livewire\Dashboard::class) ->assertViewHas('nextInvoice', null) // The price is still on the master record — they pay it until the end. ->assertViewHas('planPrice', fn (?array $p) => $p !== null && $p['cents'] === 17900) ->assertDontSee(__('dashboard.invoice.label')); }); it('shows the credentials card, and lets acknowledging delete the password rather than just hide it', function () { $user = User::factory()->create(['email' => 'ack@cred.test']); $customer = Customer::factory()->create(['email' => 'ack@cred.test', 'user_id' => $user->id]); $order = Order::factory()->create(['customer_id' => $customer->id]); $instance = Instance::factory()->create([ 'customer_id' => $customer->id, 'order_id' => $order->id, 'status' => 'active', 'nc_admin_ref' => 'admin', 'admin_password' => 'my-secret-pw', ]); Livewire::actingAs($user) ->test(Dashboard::class) ->assertSee(__('dashboard.credentials.title')) ->assertSee('my-secret-pw') ->call('acknowledgeCredentials', $instance->uuid) ->assertDontSee('my-secret-pw') ->assertDontSee(__('dashboard.credentials.title')); // Not merely hidden from the card — actually gone, at the raw stored // column too, so a mutant that only toggled a "hide the card" flag // without clearing admin_password is caught here. expect($instance->fresh()->admin_password)->toBeNull() ->and($instance->fresh()->credentials_acknowledged_at)->not->toBeNull(); $raw = DB::table('instances')->where('id', $instance->id)->value('admin_password'); expect($raw)->toBeNull(); }); it("refuses to acknowledge — or leak — another customer's credentials", function () { $userA = User::factory()->create(['email' => 'a@cred.test']); $customerA = Customer::factory()->create(['email' => 'a@cred.test', 'user_id' => $userA->id]); $orderA = Order::factory()->create(['customer_id' => $customerA->id]); $instanceA = Instance::factory()->create([ 'customer_id' => $customerA->id, 'order_id' => $orderA->id, 'status' => 'active', 'nc_admin_ref' => 'admin', 'admin_password' => 'a-secret-pw', ]); $userB = User::factory()->create(['email' => 'b@cred.test']); $customerB = Customer::factory()->create(['email' => 'b@cred.test', 'user_id' => $userB->id]); $orderB = Order::factory()->create(['customer_id' => $customerB->id]); Instance::factory()->create([ 'customer_id' => $customerB->id, 'order_id' => $orderB->id, 'status' => 'active', ]); // B's own dashboard render must not show A's password — render() scopes // $instance to $customer's own instances, so this should hold already. Livewire::actingAs($userB) ->test(Dashboard::class) ->assertDontSee('a-secret-pw') // B posting A's instance uuid directly — exactly what an unchecked // Livewire action allows, since the card that would normally supply // this uuid is never rendered for B. acknowledgeCredentials() must // re-resolve the instance from B's own customer, not trust the uuid. ->call('acknowledgeCredentials', $instanceA->uuid); expect($instanceA->fresh()->admin_password)->not->toBeNull() ->and($instanceA->fresh()->credentials_acknowledged_at)->toBeNull(); });