From 9fa8fc50549664f6c325abfe94047f308d3bae55 Mon Sep 17 00:00:00 2001 From: Shift Date: Sat, 2 Mar 2024 13:27:55 +0000 Subject: [PATCH 01/16] Bump dependencies for Laravel 11 --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index effadcb..71fd05e 100644 --- a/composer.json +++ b/composer.json @@ -18,10 +18,10 @@ ], "require": { "php": "^8.0|^8.1|^8.2", - "illuminate/contracts": "^8.0|^9.0|^10.0" + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0" }, "require-dev": { - "orchestra/testbench": "^6.22|^7.0|^8.0", + "orchestra/testbench": "^6.22|^7.0|^8.0|^9.0", "phpunit/phpunit": "^9.5|^10.0", "squizlabs/php_codesniffer": "*" }, @@ -52,7 +52,7 @@ "providers": [ "LucasDotVin\\Soulbscription\\SoulbscriptionServiceProvider" ], - "aliases": {} + "aliases": [] } }, "minimum-stability": "stable", From 2f7d01f7cc722a51bd0b786613214fdc9990c5a7 Mon Sep 17 00:00:00 2001 From: Shift Date: Sat, 2 Mar 2024 13:27:55 +0000 Subject: [PATCH 02/16] Update GitHub Actions for Laravel 11 --- .github/workflows/run-tests.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 19730f9..eaadfee 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -3,25 +3,35 @@ name: run-tests on: workflow_dispatch: push: - branches: [main, develop] + branches: + - main + - develop pull_request: - branches: [main, develop] + branches: + - main + - develop jobs: test: runs-on: ${{ matrix.os }} + strategy: fail-fast: true matrix: os: [ubuntu-latest] php: [8.1, 8.2] - laravel: [9.*, 10.*] + laravel: ['9.*', '10.*', '11.*'] stability: [prefer-stable] include: - laravel: 9.* testbench: ^7.10 - laravel: 10.* testbench: ^8.0 + - laravel: 11.* + testbench: ^9.0 + exclude: + - laravel: 11.* + php: 8.1 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} @@ -50,6 +60,4 @@ jobs: run: vendor/bin/phpunit - name: Codecov - # You may pin to the exact commit or the version. - # uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 uses: codecov/codecov-action@v3.1.4 From 3662a781045874328d042a8ca5c97a3e594c13a9 Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Mon, 8 Apr 2024 00:37:08 -0300 Subject: [PATCH 03/16] fix: Replace unsignedInteger and unsignedDecimal calls --- database/migrations/2022_02_01_233701_create_plans_table.php | 2 +- database/migrations/2022_02_01_235540_create_features_table.php | 2 +- .../2022_02_02_000527_create_feature_consumptions_table.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/database/migrations/2022_02_01_233701_create_plans_table.php b/database/migrations/2022_02_01_233701_create_plans_table.php index 5b4cc97..14ce62f 100644 --- a/database/migrations/2022_02_01_233701_create_plans_table.php +++ b/database/migrations/2022_02_01_233701_create_plans_table.php @@ -16,7 +16,7 @@ public function up() $table->id(); $table->integer('grace_days')->default(0); $table->string('name'); - $table->unsignedInteger('periodicity')->nullable(); + $table->integer('periodicity')->unsigned()->nullable(); $table->string('periodicity_type')->nullable(); $table->softDeletes(); $table->timestamps(); diff --git a/database/migrations/2022_02_01_235540_create_features_table.php b/database/migrations/2022_02_01_235540_create_features_table.php index 5745c77..273b0a9 100644 --- a/database/migrations/2022_02_01_235540_create_features_table.php +++ b/database/migrations/2022_02_01_235540_create_features_table.php @@ -18,7 +18,7 @@ public function up() $table->boolean('consumable'); $table->boolean('quota')->default(false); $table->boolean('postpaid')->default(false); - $table->unsignedInteger('periodicity')->nullable(); + $table->integer('periodicity')->unsigned()->nullable(); $table->string('periodicity_type')->nullable(); $table->softDeletes(); $table->timestamps(); diff --git a/database/migrations/2022_02_02_000527_create_feature_consumptions_table.php b/database/migrations/2022_02_02_000527_create_feature_consumptions_table.php index 6660ae4..526eb92 100644 --- a/database/migrations/2022_02_02_000527_create_feature_consumptions_table.php +++ b/database/migrations/2022_02_02_000527_create_feature_consumptions_table.php @@ -14,7 +14,7 @@ public function up() { Schema::create('feature_consumptions', function (Blueprint $table) { $table->id(); - $table->unsignedDecimal('consumption')->nullable(); + $table->decimal('consumption')->unsigned()->nullable(); $table->timestamp('expired_at')->nullable(); $table->foreignIdFor(\LucasDotVin\Soulbscription\Models\Feature::class)->constrained()->cascadeOnDelete(); $table->timestamps(); From 02707b35fc76f59b77ee431c9508ad1e1b1d4e5f Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Mon, 8 Apr 2024 00:37:14 -0300 Subject: [PATCH 04/16] feat: Set up tests with PHP 8.3 --- .github/workflows/run-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index eaadfee..f0d05dd 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -19,7 +19,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest] - php: [8.1, 8.2] + php: [8.1, 8.2, 8.3] laravel: ['9.*', '10.*', '11.*'] stability: [prefer-stable] include: @@ -30,6 +30,8 @@ jobs: - laravel: 11.* testbench: ^9.0 exclude: + - laravel: 9.* + php: 8.3 - laravel: 11.* php: 8.1 From c31adf3744075cd9960d67a74be4bb874a338df9 Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Mon, 8 Apr 2024 00:49:14 -0300 Subject: [PATCH 05/16] fix: Fix date difference parameters order --- src/Models/Concerns/HandlesRecurrence.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Concerns/HandlesRecurrence.php b/src/Models/Concerns/HandlesRecurrence.php index 3d449e0..690e82c 100644 --- a/src/Models/Concerns/HandlesRecurrence.php +++ b/src/Models/Concerns/HandlesRecurrence.php @@ -17,7 +17,7 @@ public function calculateNextRecurrenceEnd(Carbon|string $start = null): Carbon $start = Carbon::parse($start); } - $recurrences = PeriodicityType::getDateDifference(from: now(), to: $start, unit: $this->periodicity_type); + $recurrences = PeriodicityType::getDateDifference(from: $start, to: now(), unit: $this->periodicity_type); $expirationDate = $start->copy()->add($this->periodicity_type, $this->periodicity + $recurrences); return $expirationDate; From e7815d345aebffb2f41287eeb041c93a906f62ae Mon Sep 17 00:00:00 2001 From: Shift Date: Sun, 16 Feb 2025 21:20:29 +0000 Subject: [PATCH 06/16] Bump dependencies for Laravel 12 --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 71fd05e..716403d 100644 --- a/composer.json +++ b/composer.json @@ -18,11 +18,11 @@ ], "require": { "php": "^8.0|^8.1|^8.2", - "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0" + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0" }, "require-dev": { - "orchestra/testbench": "^6.22|^7.0|^8.0|^9.0", - "phpunit/phpunit": "^9.5|^10.0", + "orchestra/testbench": "^6.22|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.5|^10.0|^11.5.3", "squizlabs/php_codesniffer": "*" }, "autoload": { From 7841ac9981d382a62292cc41a6f76ba768c0e6ee Mon Sep 17 00:00:00 2001 From: Shift Date: Sun, 16 Feb 2025 21:20:29 +0000 Subject: [PATCH 07/16] Update GitHub Actions for Laravel 12 --- .github/workflows/run-tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f0d05dd..0e58d36 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -20,7 +20,7 @@ jobs: matrix: os: [ubuntu-latest] php: [8.1, 8.2, 8.3] - laravel: ['9.*', '10.*', '11.*'] + laravel: ['9.*', '10.*', '11.*', '12.*'] stability: [prefer-stable] include: - laravel: 9.* @@ -29,11 +29,15 @@ jobs: testbench: ^8.0 - laravel: 11.* testbench: ^9.0 + - laravel: 12.* + testbench: ^10.0 exclude: - laravel: 9.* php: 8.3 - laravel: 11.* php: 8.1 + - laravel: 12.* + php: 8.1 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} From 63567d900cb79f05bf8d5ab382b2db93089562b6 Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Thu, 20 Mar 2025 00:17:58 -0300 Subject: [PATCH 08/16] test: Cover grace days update --- tests/Models/SubscriptionTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Models/SubscriptionTest.php b/tests/Models/SubscriptionTest.php index e2cc4bd..9b2a5f4 100644 --- a/tests/Models/SubscriptionTest.php +++ b/tests/Models/SubscriptionTest.php @@ -343,4 +343,21 @@ public function testModelReturnsOnlyNotCanceledSubscriptionsWithTheScope() fn ($subscription) => $this->assertContains($subscription->id, $returnedSubscriptions->pluck('id')) ); } + + public function testModelUpdatesGraceDaysEndedAtWhenRenewing() + { + $subscriber = User::factory()->create(); + $subscription = Subscription::factory() + ->for($subscriber, 'subscriber') + ->create([ + 'grace_days_ended_at' => now()->subDay(), + ]); + + $subscription->renew(); + + $this->assertDatabaseHas('subscriptions', [ + 'id' => $subscription->id, + 'grace_days_ended_at' => $subscription->expired_at->addDays($subscription->plan->grace_days), + ]); + } } From 9ee55b5dc0f77c99919361c6bca158ed3844dda9 Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Thu, 20 Mar 2025 00:18:04 -0300 Subject: [PATCH 09/16] feat: Update grace days when renewing --- src/Models/Subscription.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Models/Subscription.php b/src/Models/Subscription.php index 0a27907..09f5bd4 100644 --- a/src/Models/Subscription.php +++ b/src/Models/Subscription.php @@ -113,6 +113,7 @@ public function renew(?Carbon $expirationDate = null): self $this->update([ 'expired_at' => $expirationDate, + 'grace_days_ended_at' => $expirationDate->addDays($this->plan->grace_days), ]); event(new SubscriptionRenewed($this)); From 6334cc7ce872159eb3b1f226e085bedc9506f53b Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Thu, 20 Mar 2025 00:34:26 -0300 Subject: [PATCH 10/16] test: Cover new renewing-related cases --- .../Models/Concerns/HandlesRecurrenceTest.php | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/Models/Concerns/HandlesRecurrenceTest.php b/tests/Models/Concerns/HandlesRecurrenceTest.php index 3c299b9..65f60e4 100644 --- a/tests/Models/Concerns/HandlesRecurrenceTest.php +++ b/tests/Models/Concerns/HandlesRecurrenceTest.php @@ -100,4 +100,60 @@ public function testModelCalculateExpirationWithADifferentStartAsString() $plan->calculateNextRecurrenceEnd($start->toDateTimeString()), ); } + + public function testModelCalculateExpirationWithRenewalAfterOneMonth() + { + Carbon::setTestNow('2021-02-18'); + + $plan = self::MODEL::factory()->create([ + 'periodicity_type' => PeriodicityType::Month, + 'periodicity' => 1, + ]); + + $start = '2021-02-20'; + + $this->assertEquals('2021-03-20', $plan->calculateNextRecurrenceEnd($start)->toDateString()); + } + + public function testModelCalculateExpirationWithTwoRenewalsInOneMonth() + { + Carbon::setTestNow('2021-02-19'); + + $plan = self::MODEL::factory()->create([ + 'periodicity_type' => PeriodicityType::Month, + 'periodicity' => 1, + ]); + + $start = '2021-03-20'; + + $this->assertEquals('2021-04-20', $plan->calculateNextRecurrenceEnd($start)->toDateString()); + } + + public function testModelCalculateExpirationWithThreeRenewalsInOneMonth() + { + Carbon::setTestNow('2021-02-20'); + + $plan = self::MODEL::factory()->create([ + 'periodicity_type' => PeriodicityType::Month, + 'periodicity' => 1, + ]); + + $start = '2021-04-20'; + + $this->assertEquals('2021-05-20', $plan->calculateNextRecurrenceEnd($start)->toDateString()); + } + + public function testModelCalculateExpirationWithRenewalAfterExpiration() + { + Carbon::setTestNow('2021-02-21'); + + $plan = self::MODEL::factory()->create([ + 'periodicity_type' => PeriodicityType::Month, + 'periodicity' => 1, + ]); + + $start = '2021-02-20'; + + $this->assertEquals('2021-03-20', $plan->calculateNextRecurrenceEnd($start)->toDateString()); + } } From 4d19c530ad3af0520dbf858dba0c0d221dd6744d Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Thu, 20 Mar 2025 00:34:53 -0300 Subject: [PATCH 11/16] fix: Get at least one recurrence --- src/Models/Concerns/HandlesRecurrence.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Models/Concerns/HandlesRecurrence.php b/src/Models/Concerns/HandlesRecurrence.php index 690e82c..8de439d 100644 --- a/src/Models/Concerns/HandlesRecurrence.php +++ b/src/Models/Concerns/HandlesRecurrence.php @@ -17,7 +17,11 @@ public function calculateNextRecurrenceEnd(Carbon|string $start = null): Carbon $start = Carbon::parse($start); } - $recurrences = PeriodicityType::getDateDifference(from: $start, to: now(), unit: $this->periodicity_type); + $recurrences = max( + PeriodicityType::getDateDifference(from: $start, to: now(), unit: $this->periodicity_type), + 0, + ); + $expirationDate = $start->copy()->add($this->periodicity_type, $this->periodicity + $recurrences); return $expirationDate; From 7ef10f08ec5fd48579154eb39eec03b9d08dc7b6 Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Thu, 20 Mar 2025 00:39:12 -0300 Subject: [PATCH 12/16] test: Cover plans without grace days --- tests/Models/SubscriptionTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Models/SubscriptionTest.php b/tests/Models/SubscriptionTest.php index 9b2a5f4..3dd905e 100644 --- a/tests/Models/SubscriptionTest.php +++ b/tests/Models/SubscriptionTest.php @@ -360,4 +360,23 @@ public function testModelUpdatesGraceDaysEndedAtWhenRenewing() 'grace_days_ended_at' => $subscription->expired_at->addDays($subscription->plan->grace_days), ]); } + + public function testModelLeavesGraceDaysEmptyWhenRenewingIfPlanDoesNotHaveIt() + { + $subscriber = User::factory()->create(); + $subscription = Subscription::factory() + ->for($subscriber, 'subscriber') + ->create([ + 'grace_days_ended_at' => null, + ]); + + $subscription->plan->update(['grace_days' => 0]); + + $subscription->renew(); + + $this->assertDatabaseHas('subscriptions', [ + 'id' => $subscription->id, + 'grace_days_ended_at' => null, + ]); + } } From 05ffe80a54cd26d4337a589818dd64bf9ecd555e Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Thu, 20 Mar 2025 00:39:45 -0300 Subject: [PATCH 13/16] feat: Leave grace days empty when necessary --- src/Models/Subscription.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Models/Subscription.php b/src/Models/Subscription.php index 09f5bd4..a4eac57 100644 --- a/src/Models/Subscription.php +++ b/src/Models/Subscription.php @@ -110,10 +110,15 @@ public function renew(?Carbon $expirationDate = null): self ]); $expirationDate = $this->getRenewedExpiration($expirationDate); + $graceDaysEndedAt = null; + + if ($this->plan->grace_days) { + $graceDaysEndedAt = $expirationDate->addDays($this->plan->grace_days); + } $this->update([ 'expired_at' => $expirationDate, - 'grace_days_ended_at' => $expirationDate->addDays($this->plan->grace_days), + 'grace_days_ended_at' => $graceDaysEndedAt, ]); event(new SubscriptionRenewed($this)); From bcde5fe56aa2bc94cef4459642053269fc9b8d8c Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Thu, 20 Mar 2025 00:43:40 -0300 Subject: [PATCH 14/16] refactor: Use factories for set grace days --- src/Models/Subscription.php | 2 +- tests/Models/SubscriptionTest.php | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Models/Subscription.php b/src/Models/Subscription.php index a4eac57..3d88ba5 100644 --- a/src/Models/Subscription.php +++ b/src/Models/Subscription.php @@ -113,7 +113,7 @@ public function renew(?Carbon $expirationDate = null): self $graceDaysEndedAt = null; if ($this->plan->grace_days) { - $graceDaysEndedAt = $expirationDate->addDays($this->plan->grace_days); + $graceDaysEndedAt = $expirationDate->copy()->addDays($this->plan->grace_days); } $this->update([ diff --git a/tests/Models/SubscriptionTest.php b/tests/Models/SubscriptionTest.php index 3dd905e..e9344a1 100644 --- a/tests/Models/SubscriptionTest.php +++ b/tests/Models/SubscriptionTest.php @@ -347,7 +347,12 @@ public function testModelReturnsOnlyNotCanceledSubscriptionsWithTheScope() public function testModelUpdatesGraceDaysEndedAtWhenRenewing() { $subscriber = User::factory()->create(); + $plan = Plan::factory()->create([ + 'grace_days' => $graceDays = $this->faker()->randomDigitNotNull(), + ]); + $subscription = Subscription::factory() + ->for($plan) ->for($subscriber, 'subscriber') ->create([ 'grace_days_ended_at' => now()->subDay(), @@ -357,21 +362,24 @@ public function testModelUpdatesGraceDaysEndedAtWhenRenewing() $this->assertDatabaseHas('subscriptions', [ 'id' => $subscription->id, - 'grace_days_ended_at' => $subscription->expired_at->addDays($subscription->plan->grace_days), + 'grace_days_ended_at' => $subscription->expired_at->addDays($graceDays), ]); } public function testModelLeavesGraceDaysEmptyWhenRenewingIfPlanDoesNotHaveIt() { $subscriber = User::factory()->create(); + $plan = Plan::factory()->create([ + 'grace_days' => 0, + ]); + $subscription = Subscription::factory() + ->for($plan) ->for($subscriber, 'subscriber') ->create([ 'grace_days_ended_at' => null, ]); - $subscription->plan->update(['grace_days' => 0]); - $subscription->renew(); $this->assertDatabaseHas('subscriptions', [ From 1697c441a7ebdb09c2cc9364fd78c579f03b7032 Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Thu, 20 Mar 2025 19:48:07 -0300 Subject: [PATCH 15/16] feat: Normalize recurrence delta manually --- src/Enums/PeriodicityType.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Enums/PeriodicityType.php b/src/Enums/PeriodicityType.php index 83a86dd..ceaa682 100644 --- a/src/Enums/PeriodicityType.php +++ b/src/Enums/PeriodicityType.php @@ -17,10 +17,17 @@ class PeriodicityType public static function getDateDifference(Carbon $from, Carbon $to, string $unit): int { + if ($from->isAfter($to)) { + $delta = -1; + } else { + $delta = 1; + } + $unitInPlural = Str::plural($unit); $differenceMethodName = 'diffIn' . $unitInPlural; + $difference = abs($from->{$differenceMethodName}($to)); - return $from->{$differenceMethodName}($to); + return $difference * $delta; } } From e4aa9accddfe08da17228e0687435117d2f6d947 Mon Sep 17 00:00:00 2001 From: Lucas Vinicius Date: Thu, 20 Mar 2025 22:26:13 -0300 Subject: [PATCH 16/16] test: Max test coverage --- src/Models/Concerns/HasSubscriptions.php | 2 +- .../Models/Concerns/HasSubscriptionsTest.php | 155 +++++++++++++++++- tests/Models/SubscriptionTest.php | 22 +++ 3 files changed, 177 insertions(+), 2 deletions(-) diff --git a/src/Models/Concerns/HasSubscriptions.php b/src/Models/Concerns/HasSubscriptions.php index 14b3fe1..cd7e68a 100644 --- a/src/Models/Concerns/HasSubscriptions.php +++ b/src/Models/Concerns/HasSubscriptions.php @@ -106,7 +106,7 @@ public function setConsumedQuota($featureName, float $consumption) ->whereFeatureId($feature->id) ->firstOrNew(); - if ($featureConsumption->consumption === $consumption) { + if ($featureConsumption->consumption == $consumption) { return; } diff --git a/tests/Models/Concerns/HasSubscriptionsTest.php b/tests/Models/Concerns/HasSubscriptionsTest.php index 29cc427..ad72a18 100644 --- a/tests/Models/Concerns/HasSubscriptionsTest.php +++ b/tests/Models/Concerns/HasSubscriptionsTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Models\Concerns; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; @@ -21,6 +22,7 @@ use LucasDotVin\Soulbscription\Models\SubscriptionRenewal; use OutOfBoundsException; use OverflowException; +use ReflectionClass; use Tests\Mocks\Models\User; use Tests\TestCase; @@ -427,6 +429,42 @@ public function testModelGetFeaturesFromTickets() ); } + public function testModelGetFeaturesFromPreviouslyLoadedTickets() + { + $feature = Feature::factory()->createOne(); + $subscriber = User::factory()->createOne(); + + $reflection = new ReflectionClass($subscriber); + $property = $reflection->getProperty('loadedTicketFeatures'); + $property->setAccessible(true); + $property->setValue($subscriber, Collection::make([$feature])); + + config()->set('soulbscription.feature_tickets', true); + + $features = $subscriber->getFeaturesAttribute(); + + $this->assertCount(1, $features); + $this->assertTrue($features->contains($feature)); + } + + public function testModelGetFeaturesFromPreviouslyLoadedSubscription() + { + $feature = Feature::factory()->createOne(); + $subscriber = User::factory()->createOne(); + + $reflection = new ReflectionClass($subscriber); + $property = $reflection->getProperty('loadedSubscriptionFeatures'); + $property->setAccessible(true); + $property->setValue($subscriber, Collection::make([$feature])); + + config()->set('soulbscription.feature_tickets', true); + + $features = $subscriber->getFeaturesAttribute(); + + $this->assertCount(1, $features); + $this->assertTrue($features->contains($feature)); + } + public function testModelGetFeaturesFromNonExpirableTickets() { $feature = Feature::factory()->consumable()->createOne(); @@ -805,6 +843,31 @@ public function testItCanSetQuotaFeatureConsumption() ]); } + public function testItDoesNothingWhileSettingQuotaIfTheGivenAmountIsTheSameAsTheBalance() + { + $charges = $this->faker->numberBetween(5, 10); + $consumption = $this->faker->numberBetween(1, $charges); + + $plan = Plan::factory()->createOne(); + $feature = Feature::factory()->quota()->createOne(); + $feature->plans()->attach($plan, [ + 'charges' => $charges, + ]); + + $subscriber = User::factory()->createOne(); + $subscriber->subscribeTo($plan); + + $subscriber->consume($feature->name, $consumption); + $subscriber->setConsumedQuota($feature->name, $consumption); + + $this->assertDatabaseHas('feature_consumptions', [ + 'consumption' => $consumption, + 'feature_id' => $feature->id, + 'subscriber_id' => $subscriber->id, + 'expired_at' => null, + ]); + } + public function testItRaisesAnExceptionWhenSettingConsumedQuotaForANotQuotaFeature() { $charges = $this->faker->numberBetween(5, 10); @@ -908,7 +971,7 @@ public function testItDoesNotReturnNegativeChargesForFeatures() $this->assertEquals(0, $subscriber->getRemainingCharges($feature->name)); } - public function testItReturnsNegativeBalanceForFeatures() + public function testItReturnsNegativeBalanceForPostpaidFeatures() { $charges = $this->faker->numberBetween(5, 10); $consumption = $this->faker->numberBetween($charges + 1, $charges * 2); @@ -927,6 +990,16 @@ public function testItReturnsNegativeBalanceForFeatures() $this->assertLessThan(0, $subscriber->balance($feature->name)); } + public function testItReturnsZeroForUnavailableFeatures() + { + $feature = Feature::factory()->createOne(); + $subscriber = User::factory()->createOne(); + + $remainingCharges = $subscriber->getRemainingCharges($feature->name); + + $this->assertEquals(0, $remainingCharges); + } + public function testItReturnsRemainingChargesOnlyForTheGivenUser() { config(['soulbscription.feature_tickets' => true]); @@ -943,4 +1016,84 @@ public function testItReturnsRemainingChargesOnlyForTheGivenUser() $this->assertEquals($charges, $subscriber->getRemainingCharges($feature->name)); } + + public function testItCanCheckIfSubscriberHasFeature() + { + $plan = Plan::factory()->createOne(); + $feature = Feature::factory()->createOne(); + $feature->plans()->attach($plan); + + $subscriber = User::factory()->createOne(); + $subscriber->subscribeTo($plan); + + $this->assertTrue($subscriber->hasFeature($feature->name)); + } + + public function testItCanCheckIfSubscriberDoesNotHaveFeature() + { + $plan = Plan::factory()->createOne(); + $feature = Feature::factory()->createOne(); + + $subscriber = User::factory()->createOne(); + $subscriber->subscribeTo($plan); + + $this->assertFalse($subscriber->hasFeature($feature->name)); + } + + public function testItCanAlwaysConsumeAPostpaidFeature() + { + $charges = $this->faker->numberBetween(5, 10); + $consumption = $this->faker->numberBetween($charges + 1, $charges * 2); + + $plan = Plan::factory()->createOne(); + $feature = Feature::factory()->postpaid()->createOne(); + $feature->plans()->attach($plan, [ + 'charges' => $charges, + ]); + + $subscriber = User::factory()->createOne(); + $subscriber->subscribeTo($plan); + + $this->assertTrue($subscriber->canConsume($feature->name, $consumption)); + + $subscriber->consume($feature->name, $consumption); + + $this->assertDatabaseHas('feature_consumptions', [ + 'consumption' => $consumption, + 'feature_id' => $feature->id, + 'subscriber_id' => $subscriber->id, + ]); + } + + public function testItSetsAnEmptyExpirationIfThePlanHasNoPeriodicity() + { + $plan = Plan::factory()->createOne([ + 'periodicity' => null, + ]); + + $subscriber = User::factory()->createOne(); + $subscription = $subscriber->subscribeTo($plan); + + $this->assertNull($subscription->expired_at); + } + + public function testItReturnsZeroForCurrentConsumptionWhenSubscriberDoesNotHaveFeature() + { + $feature = Feature::factory()->createOne(); + $subscriber = User::factory()->createOne(); + + $currentConsumption = $subscriber->getCurrentConsumption($feature->name); + + $this->assertEquals(0, $currentConsumption); + } + + public function testItReturnsZeroForTotalChargesWhenSubscriberDoesNotHaveFeature() + { + $feature = Feature::factory()->createOne(); + $subscriber = User::factory()->createOne(); + + $totalCharges = $subscriber->getTotalCharges($feature->name); + + $this->assertEquals(0, $totalCharges); + } } diff --git a/tests/Models/SubscriptionTest.php b/tests/Models/SubscriptionTest.php index e9344a1..730d586 100644 --- a/tests/Models/SubscriptionTest.php +++ b/tests/Models/SubscriptionTest.php @@ -387,4 +387,26 @@ public function testModelLeavesGraceDaysEmptyWhenRenewingIfPlanDoesNotHaveIt() 'grace_days_ended_at' => null, ]); } + + public function testModelUsesProvidedExpirationAtRenewing() + { + $subscriber = User::factory()->create(); + $plan = Plan::factory()->create(); + + $subscription = Subscription::factory() + ->for($plan) + ->for($subscriber, 'subscriber') + ->create([ + 'expired_at' => now()->subDay(), + ]); + + $expectedExpiredAt = now()->addDays($days = $this->faker()->randomDigitNotNull())->toDateTimeString(); + + $subscription->renew(now()->addDays($days)); + + $this->assertDatabaseHas('subscriptions', [ + 'id' => $subscription->id, + 'expired_at' => $expectedExpiredAt, + ]); + } }