website = Website::factory()->create(['domain' => 'example.com']); } private function validPayload(array $overrides = []): array { return array_merge([ 'tid' => $this->website->tracking_id, 'vid' => 'visitor-abc-123', 'sid' => 'session-abc-123', 'type' => 'pageview', 'url' => 'https://example.com/hello', 'path' => '/hello', 'title' => 'Hello Page', ], $overrides); } // -- Store endpoint -- public function test_valid_pageview_payload_dispatches_job(): void { Bus::fake(); $response = $this->postJson('/api/collect', $this->validPayload()); $response->assertOk()->assertJson(['ok' => true]); Bus::assertDispatched(ProcessTrackingPayload::class); } public function test_valid_event_payload_dispatches_job(): void { Bus::fake(); $response = $this->postJson('/api/collect', $this->validPayload([ 'type' => 'event', 'event_name' => 'signup', 'event_category' => 'conversion', ])); $response->assertOk()->assertJson(['ok' => true]); Bus::assertDispatched(ProcessTrackingPayload::class); } public function test_missing_required_fields_returns_validation_error(): void { $response = $this->postJson('/api/collect', []); $response->assertUnprocessable(); $response->assertJsonValidationErrors(['tid', 'vid', 'sid', 'type']); } public function test_invalid_type_returns_validation_error(): void { $response = $this->postJson('/api/collect', $this->validPayload([ 'type' => 'invalid_type', ])); $response->assertUnprocessable(); $response->assertJsonValidationErrors(['type']); } public function test_domain_mismatch_returns_error(): void { Bus::fake(); $response = $this->postJson('/api/collect', $this->validPayload([ 'url' => 'https://evil-site.com/phishing', ])); $response->assertStatus(400); $response->assertJson(['ok' => false, 'reason' => 'domain_mismatch']); Bus::assertNotDispatched(ProcessTrackingPayload::class); } public function test_subdomain_is_allowed(): void { Bus::fake(); $response = $this->postJson('/api/collect', $this->validPayload([ 'url' => 'https://www.example.com/page', ])); $response->assertOk()->assertJson(['ok' => true]); Bus::assertDispatched(ProcessTrackingPayload::class); } public function test_unknown_tracking_id_still_succeeds(): void { Bus::fake(); $response = $this->postJson('/api/collect', $this->validPayload([ 'tid' => 'TA-NONEXISTENT', 'url' => null, ])); $response->assertOk()->assertJson(['ok' => true]); Bus::assertDispatched(ProcessTrackingPayload::class); } public function test_bot_requests_are_rejected(): void { Bus::fake(); $response = $this->postJson('/api/collect', $this->validPayload(), [ 'User-Agent' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', ]); $response->assertOk()->assertJson(['ok' => false, 'reason' => 'bot']); Bus::assertNotDispatched(ProcessTrackingPayload::class); } public function test_payload_with_utm_parameters(): void { Bus::fake(); $response = $this->postJson('/api/collect', $this->validPayload([ 'utm_source' => 'google', 'utm_medium' => 'cpc', 'utm_campaign' => 'spring_sale', 'utm_term' => 'shoes', 'utm_content' => 'ad_v1', ])); $response->assertOk()->assertJson(['ok' => true]); Bus::assertDispatched(ProcessTrackingPayload::class); } // -- Pixel endpoint -- public function test_pixel_returns_gif(): void { Bus::fake(); $response = $this->get('/api/pixel?tid='.$this->website->tracking_id); $response->assertOk(); $response->assertHeader('Content-Type', 'image/gif'); Bus::assertDispatched(ProcessTrackingPayload::class); } public function test_pixel_without_tid_returns_gif_without_dispatching(): void { Bus::fake(); $response = $this->get('/api/pixel'); $response->assertOk(); $response->assertHeader('Content-Type', 'image/gif'); Bus::assertNotDispatched(ProcessTrackingPayload::class); } public function test_pixel_rejects_bots(): void { Bus::fake(); $response = $this->get('/api/pixel?tid='.$this->website->tracking_id, [ 'User-Agent' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', ]); $response->assertOk(); $response->assertHeader('Content-Type', 'image/gif'); Bus::assertNotDispatched(ProcessTrackingPayload::class); } }