trypost/tests/Unit/Policies/AccountPolicyTest.php
Paulo Castellano 0619f2a0f4 refactor: address code review for per-workspace pricing
Bug fix
- Surface the localized "network already connected" message on the
  Facebook/Instagram/InstagramFacebook/LinkedInPage/Threads/YouTube OAuth
  callbacks: catch NetworkAlreadyConnectedException before the generic catch
  so the conflict no longer falls through to a generic error + Log::error.

Scope / dead code
- Remove the orphaned BillingController::checkout() + app.billing.checkout route
  (onboarding starts checkout directly); delete the now-dead DiscordWidget and
  useFeatureAccess composable; prune orphaned i18n keys left by removing the
  plan picker / upgrade dialog / count limits (billing.subscribe.*,
  accounts.limit_reached, workspaces.limit_reached, common.discord.*).
- Drop the unused `plan` prop from the usage page and the unused `label` from
  the onboarding persona payload (labels come from i18n); remove
  Persona::options()/label().

Conventions
- declare(strict_types=1) on the two new migrations.
- Extract autofill validation into AutofillBrandRequest (FormRequest).
- Drop the unused $plan param from AccountPolicy::swapPlan.
- CreateUser: drop the stale config('cashier.trial_days', 7) fallback (now 8).
- Rename LimitEnforcementTest to InvitePermissionTest; use Pest mock() helper in
  WorkspaceQuantitySyncTest; move shared test helpers into Pest.php.
- Memoize BillingCycle window() + subscription lookup.
2026-06-21 21:28:47 -03:00

90 lines
2.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Enums\Plan\Slug;
use App\Models\Account;
use App\Models\AiUsageLog;
use App\Models\Plan;
use App\Models\User;
use App\Models\Workspace;
use App\Policies\AccountPolicy;
beforeEach(function () {
$this->policy = new AccountPolicy;
$this->account = Account::factory()->create([
'plan_id' => Plan::where('slug', Slug::Workspace)->value('id'),
]);
$this->owner = User::factory()->create(['account_id' => $this->account->id]);
$this->account->update(['owner_id' => $this->owner->id]);
});
test('swapPlan allows the account owner', function () {
$response = $this->policy->swapPlan($this->owner, $this->account);
expect($response->allowed())->toBeTrue();
});
test('swapPlan denies a non-owner', function () {
$member = User::factory()->create(['account_id' => $this->account->id]);
$response = $this->policy->swapPlan($member, $this->account);
expect($response->denied())->toBeTrue();
expect($response->message())->toBe(__('billing.flash.cannot_manage'));
});
test('useAi allows when subscribed and credits remain', function () {
config()->set('trypost.self_hosted', false);
Workspace::factory()->create([
'account_id' => $this->account->id,
'user_id' => $this->owner->id,
]);
subscribeAccount($this->account);
$response = $this->policy->useAi($this->owner, $this->account->fresh());
expect($response->allowed())->toBeTrue();
});
test('useAi denies when there is no active subscription', function () {
config()->set('trypost.self_hosted', false);
Workspace::factory()->create([
'account_id' => $this->account->id,
'user_id' => $this->owner->id,
]);
$response = $this->policy->useAi($this->owner, $this->account->fresh());
expect($response->denied())->toBeTrue();
expect($response->message())->toBe(__('billing.flash.subscription_required'));
});
test('useAi denies when monthly credits are exhausted', function () {
config()->set('trypost.self_hosted', false);
$workspace = Workspace::factory()->create([
'account_id' => $this->account->id,
'user_id' => $this->owner->id,
]);
subscribeAccount($this->account);
AiUsageLog::factory()->text(credits: 2500)->create([
'account_id' => $this->account->id,
'workspace_id' => $workspace->id,
]);
$response = $this->policy->useAi($this->owner, $this->account->fresh());
expect($response->denied())->toBeTrue();
expect($response->message())->toBe(__('billing.flash.credits_exhausted', [
'limit' => '2500',
]));
});
test('useAi always allows when self-hosted', function () {
config()->set('trypost.self_hosted', true);
$response = $this->policy->useAi($this->owner, $this->account);
expect($response->allowed())->toBeTrue();
});