Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/Models/PolydockAppInstance.php
Original file line number Diff line number Diff line change
Expand Up @@ -1110,8 +1110,9 @@ public function calculateAndSetTrialDates(?int $overrideDurationDays = null, ?bo
*/
public function calculateAndSetTrialDatesFromEndDate($trialEndDateTime, bool $saveModel = false): self
{
// Calculate days between now and end date
$durationDays = now()->diffInDays($trialEndDateTime);
// diffInDays() returns a float in Carbon 3; round up so a partial day still
// grants a full trial day rather than being truncated toward zero.
$durationDays = (int) ceil((float) now()->diffInDays($trialEndDateTime));

return $this->calculateAndSetTrialDates($durationDays, $saveModel);
}
Expand Down
118 changes: 118 additions & 0 deletions tests/Unit/Models/TrialDateCalculationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace Tests\Unit\Models;

use App\Events\PolydockAppInstanceCreatedWithNewStatus;
use App\Events\PolydockAppInstanceStatusChanged;
use App\Models\PolydockAppInstance;
use App\Models\PolydockStore;
use App\Models\PolydockStoreApp;
use App\Models\UserGroup;
use App\Polydock\Apps\Generic\PolydockAiApp;
use App\Polydock\Core\Enums\PolydockAppInstanceStatus;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

class TrialDateCalculationTest extends TestCase
{
use RefreshDatabase;

private PolydockStoreApp $storeApp;

private UserGroup $group;

protected function setUp(): void
{
parent::setUp();

$store = PolydockStore::factory()->create();
$this->storeApp = PolydockStoreApp::factory()->create([
'polydock_store_id' => $store->id,
'trial_duration_days' => 7,
'send_midtrial_email' => true,
'send_one_day_left_email' => true,
'send_trial_complete_email' => true,
]);
$this->group = UserGroup::factory()->create();

Event::fake([
PolydockAppInstanceCreatedWithNewStatus::class,
PolydockAppInstanceStatusChanged::class,
]);
}

protected function tearDown(): void
{
Carbon::setTestNow();

parent::tearDown();
}

private function makeInstance(): PolydockAppInstance
{
$instance = new PolydockAppInstance;
$instance->polydock_store_app_id = $this->storeApp->id;
$instance->user_group_id = $this->group->id;
$instance->name = 'trial-date-test';
$instance->app_type = PolydockAiApp::class;
$instance->status = PolydockAppInstanceStatus::NEW;
$instance->save();

return $instance;
}

public function test_whole_days_in_future_sets_trial_ends_at_exactly_that_many_days_out(): void
{
Carbon::setTestNow('2026-07-01 12:00:00');

$instance = $this->makeInstance();
$endDate = now()->copy()->addDays(5); // exactly 5 whole days

$instance->calculateAndSetTrialDatesFromEndDate($endDate);

$this->assertNotNull($instance->trial_ends_at);
$this->assertSame(
now()->copy()->addDays(5)->toDateTimeString(),
$instance->trial_ends_at->toDateTimeString()
);
}

public function test_partial_day_rounds_up_so_trial_is_not_short_changed(): void
{
// Regression: Carbon 3 diffInDays() returns a float (e.g. 5.5); truncation
// toward zero would leave the trial ~1 day short of the requested end.
Carbon::setTestNow('2026-07-01 12:00:00');

$instance = $this->makeInstance();
$endDate = now()->copy()->addDays(5)->addHours(13); // 5 days + 13h => rounds up to 6

$instance->calculateAndSetTrialDatesFromEndDate($endDate);

$this->assertNotNull($instance->trial_ends_at);
// Rounds up to 6 whole days rather than truncating to 5.
$this->assertSame(
now()->copy()->addDays(6)->toDateTimeString(),
$instance->trial_ends_at->toDateTimeString()
);
// And the trial never ends before the requested end date.
$this->assertTrue(
$instance->trial_ends_at->greaterThanOrEqualTo($endDate),
'trial_ends_at should be on or after the requested end date'
);
}

public function test_end_date_in_the_past_leaves_trial_ends_at_null(): void
{
Carbon::setTestNow('2026-07-01 12:00:00');

$instance = $this->makeInstance();
$endDate = now()->copy()->subDays(3); // in the past

$instance->calculateAndSetTrialDatesFromEndDate($endDate);

// Guard ($durationDays > 0) means no negative trial is set.
$this->assertNull($instance->trial_ends_at);
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}