126 lines
4.5 KiB
PHP
126 lines
4.5 KiB
PHP
<?php
|
|
|
|
use App\Actions\GrantSubscription;
|
|
use App\Models\Customer;
|
|
use App\Models\Operator;
|
|
use App\Models\Order;
|
|
use App\Models\ProvisioningRun;
|
|
use App\Models\Subscription;
|
|
use App\Provisioning\Jobs\AdvanceRunJob;
|
|
use App\Services\Billing\PlanCatalogue;
|
|
use Illuminate\Support\Facades\Queue;
|
|
|
|
/**
|
|
* A grant is an order without money, not a second code path: it must produce
|
|
* the same rows — order, provisioning run, subscription — a paid purchase
|
|
* does, with provenance recorded and price_cents set to what the customer
|
|
* actually pays.
|
|
*/
|
|
it('opens a subscription with no money charged, and provenance recorded', function () {
|
|
Queue::fake();
|
|
|
|
$customer = Customer::factory()->create();
|
|
$owner = Operator::factory()->create();
|
|
|
|
$subscription = app(GrantSubscription::class)(
|
|
customer: $customer,
|
|
grantedBy: $owner,
|
|
plan: 'team',
|
|
term: Subscription::TERM_MONTHLY,
|
|
datacenter: 'fsn',
|
|
priceCents: 0,
|
|
note: 'Freund des Hauses',
|
|
);
|
|
|
|
$order = Order::query()->where('customer_id', $customer->id)->sole();
|
|
|
|
expect($order->amount_cents)->toBe(0)
|
|
->and($order->stripe_event_id)->toBeNull()
|
|
->and($subscription->price_cents)->toBe(0)
|
|
->and($subscription->plan)->toBe('team')
|
|
// The customer is owed the real machine, so the snapshot is the
|
|
// catalogue's — only the price and the provenance were overridden.
|
|
->and($subscription->ram_mb)->toBeGreaterThan(0)
|
|
->and($subscription->isGranted())->toBeTrue()
|
|
->and($subscription->isFreeGrant())->toBeTrue()
|
|
->and($subscription->granted_by)->toBe($owner->id)
|
|
->and($subscription->grant_note)->toBe('Freund des Hauses')
|
|
->and($subscription->granted_until)->toBeNull()
|
|
->and($subscription->catalogue_price_cents)->toBeGreaterThan(0);
|
|
|
|
// Same downstream pipeline as a paid order: a run was opened and dispatched.
|
|
$run = ProvisioningRun::query()->where('subject_id', $order->id)->sole();
|
|
expect($run->pipeline)->toBe('customer');
|
|
Queue::assertPushed(AdvanceRunJob::class, fn ($job) => $job->runUuid === $run->uuid);
|
|
});
|
|
|
|
it('records a discount as what it is: a price below the catalogue, not a free plan', function () {
|
|
$customer = Customer::factory()->create();
|
|
$owner = Operator::factory()->create();
|
|
|
|
$subscription = app(GrantSubscription::class)(
|
|
customer: $customer,
|
|
grantedBy: $owner,
|
|
plan: 'team',
|
|
term: Subscription::TERM_MONTHLY,
|
|
datacenter: 'fsn',
|
|
priceCents: 5000,
|
|
);
|
|
|
|
expect($subscription->price_cents)->toBe(5000)
|
|
->and($subscription->catalogue_price_cents)->toBeGreaterThan(5000)
|
|
->and($subscription->isFreeGrant())->toBeFalse()
|
|
->and($subscription->isGranted())->toBeTrue();
|
|
});
|
|
|
|
it('grants a quota bonus beyond the plan, baked into the frozen snapshot', function () {
|
|
$customer = Customer::factory()->create();
|
|
$owner = Operator::factory()->create();
|
|
$plainTeam = app(PlanCatalogue::class)->currentVersion('team');
|
|
|
|
$subscription = app(GrantSubscription::class)(
|
|
customer: $customer,
|
|
grantedBy: $owner,
|
|
plan: 'team',
|
|
term: Subscription::TERM_MONTHLY,
|
|
datacenter: 'fsn',
|
|
priceCents: 0,
|
|
bonus: ['quota_gb' => $plainTeam->quota_gb + 250],
|
|
);
|
|
|
|
expect($subscription->quota_gb)->toBe($plainTeam->quota_gb + 250);
|
|
});
|
|
|
|
it('refuses to charge more for a grant than the plan actually costs', function () {
|
|
$customer = Customer::factory()->create();
|
|
$owner = Operator::factory()->create();
|
|
$price = app(PlanCatalogue::class)->currentVersion('team')->priceFor('monthly')->amount_cents;
|
|
|
|
expect(fn () => app(GrantSubscription::class)(
|
|
customer: $customer,
|
|
grantedBy: $owner,
|
|
plan: 'team',
|
|
term: Subscription::TERM_MONTHLY,
|
|
datacenter: 'fsn',
|
|
priceCents: $price + 1,
|
|
))->toThrow(RuntimeException::class);
|
|
});
|
|
|
|
it('remembers until when a dated grant runs, and leaves it unlimited otherwise', function () {
|
|
$customer = Customer::factory()->create();
|
|
$owner = Operator::factory()->create();
|
|
$until = now()->addMonths(6);
|
|
|
|
$subscription = app(GrantSubscription::class)(
|
|
customer: $customer,
|
|
grantedBy: $owner,
|
|
plan: 'team',
|
|
term: Subscription::TERM_MONTHLY,
|
|
datacenter: 'fsn',
|
|
priceCents: 0,
|
|
until: $until,
|
|
);
|
|
|
|
expect($subscription->granted_until->toDateString())->toBe($until->toDateString());
|
|
});
|