travelTo(Carbon::parse('2026-09-22 12:00:00', 'UTC')); } protected function tearDown(): void { Cache::flush(); $this->travelBack(); parent::tearDown(); } public function test_it_reuses_the_signed_url_and_its_original_expiration(): void { $disk = Mockery::mock(); Storage::shouldReceive('disk') ->once() ->with('s3') ->andReturn($disk); $disk->shouldReceive('temporaryUrl') ->once() ->with( 'images/product.png', Mockery::on(fn ($expiration): bool => $expiration->equalTo(now()->addMinutes(10))), ['ResponseCacheControl' => 'private, max-age=600'], ) ->andReturn('https://s3.example.test/product.png?signed=first'); $service = app(TemporaryUrlService::class); $first = $service->generate('images/product.png', 10); $this->travel(5)->minutes(); $second = $service->generate('images/product.png', 10); $this->assertSame($first, $second); $this->assertSame('https://s3.example.test/product.png?signed=first', $second['temporary_url']); $this->assertSame('2026-09-22T12:10:00+00:00', $second['temporary_url_expires_at']); } public function test_it_refreshes_the_signed_url_before_it_expires(): void { $disk = Mockery::mock(); Storage::shouldReceive('disk') ->twice() ->with('s3') ->andReturn($disk); $disk->shouldReceive('temporaryUrl') ->twice() ->andReturn( 'https://s3.example.test/product.png?signed=first', 'https://s3.example.test/product.png?signed=second', ); $service = app(TemporaryUrlService::class); $first = $service->generate('images/product.png', 10); $this->travel(9)->minutes(); $this->travel(1)->seconds(); $second = $service->generate('images/product.png', 10); $this->assertNotSame($first['temporary_url'], $second['temporary_url']); $this->assertSame('2026-09-22T12:19:01+00:00', $second['temporary_url_expires_at']); } }