'Subscription Product', 'sku' => 'SUB-'.uniqid(), 'type' => ProductType::SIMPLE, 'status' => ProductStatus::PUBLISHED, 'manage_stock' => false, ]); } private function actionFor(Product $product, string $event = 'subscription.started'): void { ProductAction::create([ 'product_id' => $product->id, 'events' => [$event], 'class' => RecordingSubscriptionAction::class, 'method' => 'handle', 'defer' => false, 'active' => true, ]); } private function subscriptionFor(Product $product, User $user): Subscription { return Subscription::create([ 'user_id' => $user->id, 'product_id' => $product->id, 'type' => 'default', 'stripe_id' => 'sub_'.uniqid(), 'stripe_status' => 'active', 'stripe_price' => 'price_x', 'quantity' => 1, ]); } #[Test] public function the_product_relation_and_resolver_link_subscription_to_product(): void { $user = User::factory()->create(); $product = $this->product(); $sub = $this->subscriptionFor($product, $user); $this->assertTrue($sub->product->is($product)); $this->assertTrue($sub->resolveProduct()->is($product)); } #[Test] public function call_product_actions_runs_actions_with_subscription_context(): void { $user = User::factory()->create(); $product = $this->product(); $this->actionFor($product, 'subscription.started'); $sub = $this->subscriptionFor($product, $user); $sub->callProductActions(); $this->assertCount(1, RecordingSubscriptionAction::$calls); $args = RecordingSubscriptionAction::$calls[0]; $this->assertSame('subscription.started', $args['event']); $this->assertInstanceOf(Subscription::class, $args['subscription']); $this->assertTrue($args['subscription']->is($sub)); $this->assertArrayHasKey('expiresAtOverride', $args); } #[Test] public function record_started_fires_event_and_runs_actions(): void { $user = User::factory()->create(); $product = $this->product(); $this->actionFor($product, 'subscription.started'); $sub = $this->subscriptionFor($product, $user); Event::fake([SubscriptionStarted::class]); $sub->recordStarted(); $this->assertCount(1, RecordingSubscriptionAction::$calls); Event::assertDispatched(SubscriptionStarted::class, fn (SubscriptionStarted $e) => $e->subscription->is($sub)); } #[Test] public function record_renewed_fires_event_and_runs_renewal_actions(): void { $user = User::factory()->create(); $product = $this->product(); $this->actionFor($product, 'subscription.renewed'); $sub = $this->subscriptionFor($product, $user); Event::fake([SubscriptionRenewed::class]); $sub->recordRenewed(); $this->assertCount(1, RecordingSubscriptionAction::$calls); $this->assertSame('subscription.renewed', RecordingSubscriptionAction::$calls[0]['event']); Event::assertDispatched(SubscriptionRenewed::class, fn (SubscriptionRenewed $e) => $e->subscription->is($sub)); } #[Test] public function record_canceled_fires_event(): void { $user = User::factory()->create(); $product = $this->product(); $sub = $this->subscriptionFor($product, $user); Event::fake([SubscriptionCanceled::class]); $sub->recordCanceled(); Event::assertDispatched(SubscriptionCanceled::class, fn (SubscriptionCanceled $e) => $e->subscription->is($sub)); } } /** * Test fulfillment handler — records each invocation's (named) arguments so the * test can assert the subscription context was passed through. */ class RecordingSubscriptionAction { /** @var array> */ public static array $calls = []; public static function handle(...$args): void { self::$calls[] = $args; } }