service = new GoalEvaluationService; $this->website = Website::factory()->create(); $this->visitor = Visitor::factory()->for($this->website)->create(); $this->session = AnalyticsSession::factory()->for($this->website)->create([ 'visitor_id' => $this->visitor->id, 'duration' => 120, 'pages_count' => 5, ]); } // -- URL Destination -- public function test_url_destination_exact_match(): void { $goal = Goal::factory()->for($this->website)->urlDestination('/thank-you')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/thank-you', ], Carbon::now()); $this->assertDatabaseHas('goal_conversions', [ 'goal_id' => $goal->id, 'visitor_id' => $this->visitor->id, ]); } public function test_url_destination_exact_mismatch(): void { Goal::factory()->for($this->website)->urlDestination('/thank-you')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/other-page', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 0); } public function test_url_destination_starts_with(): void { $goal = Goal::factory()->for($this->website)->urlDestination('/checkout', 'starts_with')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/checkout/step-2', ], Carbon::now()); $this->assertDatabaseHas('goal_conversions', ['goal_id' => $goal->id]); } public function test_url_destination_contains(): void { $goal = Goal::factory()->for($this->website)->urlDestination('success', 'contains')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/order/success/123', ], Carbon::now()); $this->assertDatabaseHas('goal_conversions', ['goal_id' => $goal->id]); } public function test_url_destination_regex(): void { $goal = Goal::factory()->for($this->website)->urlDestination('/^\\/order\\/\\d+$/', 'regex')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/order/42', ], Carbon::now()); $this->assertDatabaseHas('goal_conversions', ['goal_id' => $goal->id]); } // -- Event Match -- public function test_event_match_by_name(): void { $goal = Goal::factory()->for($this->website)->eventMatch('purchase')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'event', 'event_name' => 'purchase', ], Carbon::now()); $this->assertDatabaseHas('goal_conversions', ['goal_id' => $goal->id]); } public function test_event_match_wrong_name_does_not_convert(): void { Goal::factory()->for($this->website)->eventMatch('purchase')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'event', 'event_name' => 'signup', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 0); } public function test_event_match_with_category(): void { $goal = Goal::factory()->for($this->website)->eventMatch('click', 'cta')->create(); // wrong category $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'event', 'event_name' => 'click', 'event_category' => 'navigation', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 0); // right category $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'event', 'event_name' => 'click', 'event_category' => 'cta', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 1); } public function test_event_match_requires_event_type(): void { Goal::factory()->for($this->website)->eventMatch('purchase')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'event_name' => 'purchase', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 0); } // -- Duration -- public function test_duration_goal_converts_when_threshold_met(): void { $goal = Goal::factory()->for($this->website)->duration(60)->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/', ], Carbon::now()); $this->assertDatabaseHas('goal_conversions', ['goal_id' => $goal->id]); } public function test_duration_goal_does_not_convert_below_threshold(): void { Goal::factory()->for($this->website)->duration(300)->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 0); } // -- Pages per session -- public function test_pages_per_session_goal_converts(): void { $goal = Goal::factory()->for($this->website)->pagesPerSession(3)->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/', ], Carbon::now()); $this->assertDatabaseHas('goal_conversions', ['goal_id' => $goal->id]); } public function test_pages_per_session_goal_does_not_convert_below_threshold(): void { Goal::factory()->for($this->website)->pagesPerSession(10)->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 0); } // -- De-duplication -- public function test_does_not_duplicate_conversion_within_same_session(): void { $goal = Goal::factory()->for($this->website)->urlDestination('/thank-you')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/thank-you', ], Carbon::now()); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/thank-you', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 1); } // -- Inactive goals -- public function test_inactive_goals_are_skipped(): void { Goal::factory()->for($this->website)->urlDestination('/thank-you')->inactive()->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/thank-you', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 0); } // -- Conversion value -- public function test_conversion_stores_goal_default_value(): void { $goal = Goal::factory()->for($this->website)->urlDestination('/buy')->create([ 'default_value' => 49.99, ]); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/buy', ], Carbon::now()); $conversion = GoalConversion::where('goal_id', $goal->id)->first(); $this->assertNotNull($conversion); $this->assertEquals(49.99, (float) $conversion->value); } // -- Multiple goals -- public function test_evaluates_multiple_goals_independently(): void { $goal1 = Goal::factory()->for($this->website)->urlDestination('/thank-you')->create(); $goal2 = Goal::factory()->for($this->website)->duration(60)->create(); Goal::factory()->for($this->website)->urlDestination('/other-page')->create(); $this->service->evaluate($this->website, $this->visitor, $this->session, [ 'type' => 'pageview', 'path' => '/thank-you', ], Carbon::now()); $this->assertDatabaseCount('goal_conversions', 2); $this->assertDatabaseHas('goal_conversions', ['goal_id' => $goal1->id]); $this->assertDatabaseHas('goal_conversions', ['goal_id' => $goal2->id]); } }