80 lines
3.0 KiB
PHP
80 lines
3.0 KiB
PHP
<?php
|
|
|
|
use App\Models\StripeAddonPrice;
|
|
use App\Services\Stripe\FakeStripeClient;
|
|
use App\Services\Stripe\StripeClient;
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
|
|
/**
|
|
* The orphans nobody can adopt.
|
|
*
|
|
* A Price at one of our Products that no row knows, and that AdoptStripePrice
|
|
* refuses — because nothing in its metadata proves it is ours, or because it
|
|
* charges in a way we never mint. The recognition step names it in the log and
|
|
* leaves it, deliberately: adopting a stranger's Price is worse than minting a
|
|
* second one. This is the other half of that decision, the one an operator can
|
|
* act with.
|
|
*/
|
|
beforeEach(function () {
|
|
$this->stripe = new FakeStripeClient;
|
|
app()->instance(StripeClient::class, $this->stripe);
|
|
app(Kernel::class)->call('stripe:sync-catalogue');
|
|
|
|
$this->product = (string) StripeAddonPrice::query()
|
|
->where('addon_key', 'priority_support')
|
|
->value('stripe_product_id');
|
|
|
|
$this->known = (string) StripeAddonPrice::query()
|
|
->where('addon_key', 'priority_support')
|
|
->where('interval', 'month')
|
|
->where('reverse_charge', false)
|
|
->value('stripe_price_id');
|
|
|
|
$this->stripe->plantPrice('price_orphan', $this->product, 9900, 'EUR', 'month', []);
|
|
});
|
|
|
|
it('reports the orphan and leaves it alone', function () {
|
|
$this->artisan('stripe:sweep-orphan-prices')
|
|
->expectsOutputToContain('price_orphan')
|
|
->assertSuccessful();
|
|
|
|
expect($this->stripe->archived)->toBe([]);
|
|
});
|
|
|
|
it('never reports a price a row knows', function () {
|
|
$this->artisan('stripe:sweep-orphan-prices')
|
|
->doesntExpectOutputToContain($this->known)
|
|
->assertSuccessful();
|
|
});
|
|
|
|
it('stops selling the orphan when asked to', function () {
|
|
$this->artisan('stripe:sweep-orphan-prices', ['--archive' => true])
|
|
->assertSuccessful();
|
|
|
|
// Archiving is harmless for the same reason it always is here: Stripe goes
|
|
// on billing every subscription already on an archived Price, it merely
|
|
// stops being sold.
|
|
expect($this->stripe->archived)->toBe(['price_orphan']);
|
|
});
|
|
|
|
it('touches nothing on a dry run, even when asked to archive', function () {
|
|
$this->artisan('stripe:sweep-orphan-prices', ['--archive' => true, '--dry-run' => true])
|
|
->expectsOutputToContain('price_orphan')
|
|
->assertSuccessful();
|
|
|
|
expect($this->stripe->archived)->toBe([]);
|
|
});
|
|
|
|
it('fails cleanly on a dry run when Stripe is not configured, rather than throwing', function () {
|
|
// Unlike stripe:sync-catalogue, this command has no local-only path: the
|
|
// report itself is a live activePricesFor() call, dry-run or not. A guard
|
|
// that only fired outside --dry-run would let this fall straight into
|
|
// HttpStripeClient's own "not configured" exception instead of the clean
|
|
// error below.
|
|
$this->stripe->configured = false;
|
|
|
|
$this->artisan('stripe:sweep-orphan-prices', ['--dry-run' => true])
|
|
->expectsOutputToContain('Stripe is not configured (STRIPE_SECRET is empty). Nothing was read.')
|
|
->assertFailed();
|
|
});
|