tokenService = app(EventJoinTokenService::class); } protected function tearDown(): void { Mockery::close(); parent::tearDown(); } private function createPublishedEvent(): Event { return Event::factory()->create([ 'status' => 'published', ]); } public function test_guest_can_access_stats_using_join_token(): void { $event = $this->createPublishedEvent(); Photo::factory()->count(3)->create([ 'event_id' => $event->id, 'guest_name' => 'device-stats', ]); $token = $this->tokenService->createToken($event); $response = $this->getJson("/api/v1/events/{$token->token}/stats"); $response->assertOk() ->assertJsonStructure([ 'online_guests', 'tasks_solved', 'latest_photo_at', ]); } public function test_guest_can_upload_photo_with_join_token(): void { Storage::fake('public'); $event = $this->createPublishedEvent(); $token = $this->tokenService->createToken($event); Mockery::mock('alias:App\Support\ImageHelper') ->shouldReceive('makeThumbnailOnDisk') ->andReturn("events/{$event->id}/photos/thumbs/generated_thumb.jpg"); $file = UploadedFile::fake()->image('example.jpg', 1200, 800); $response = $this->withHeader('X-Device-Id', 'token-device') ->postJson("/api/v1/events/{$token->token}/upload", [ 'photo' => $file, ]); $response->assertCreated() ->assertJsonStructure(['id', 'file_path', 'thumbnail_path']); $this->assertDatabaseCount('photos', 1); $saved = Photo::first(); $this->assertNotNull($saved); $this->assertEquals($event->id, $saved->event_id); $storedPath = $saved->file_path ? ltrim(str_replace('/storage/', '', $saved->file_path), '/') : null; if ($storedPath) { $this->assertTrue( Storage::disk('public')->exists($storedPath), sprintf('Uploaded file [%s] was not stored on the public disk.', $storedPath) ); } } public function test_guest_can_like_photo_after_joining_with_token(): void { $event = $this->createPublishedEvent(); $token = $this->tokenService->createToken($event); $photo = Photo::factory()->create([ 'event_id' => $event->id, 'likes_count' => 0, ]); $this->getJson("/api/v1/events/{$token->token}"); $response = $this->withHeader('X-Device-Id', 'device-like') ->postJson("/api/v1/photos/{$photo->id}/like"); $response->assertOk() ->assertJson([ 'liked' => true, ]); $this->assertDatabaseHas('photo_likes', [ 'photo_id' => $photo->id, 'guest_name' => 'device-like', ]); $this->assertEquals(1, $photo->fresh()->likes_count); } public function test_guest_cannot_access_event_with_expired_token(): void { $event = $this->createPublishedEvent(); $token = $this->tokenService->createToken($event, [ 'expires_at' => now()->subDay(), ]); $response = $this->getJson("/api/v1/events/{$token->token}"); $response->assertStatus(410) ->assertJsonPath('error.code', 'token_expired'); } public function test_guest_cannot_access_event_with_revoked_token(): void { $event = $this->createPublishedEvent(); $token = $this->tokenService->createToken($event); $this->tokenService->revoke($token, 'revoked for test'); $response = $this->getJson("/api/v1/events/{$token->token}"); $response->assertStatus(410) ->assertJsonPath('error.code', 'token_revoked'); } }