11defmodule Plausible.Stats.SamplingTest do
2- use Plausible.DataCase , async: true
2+ use Plausible.DataCase , async: false
33
44 on_ee do
55 import Plausible.Stats.Sampling , only: [ fractional_sample_rate: 2 ]
66 alias Plausible.Stats . { Query , DateTimeRange }
77
8- describe "&fractional_sample_rate/2" do
9- @ threshold Plausible.Stats.Sampling . default_sample_threshold ( )
8+ @ threshold Plausible.Stats.Sampling . default_sample_threshold ( )
109
10+ describe "&fractional_sample_rate/2 for date ranges of different lengths" do
1111 test "no traffic estimate" do
1212 assert fractional_sample_rate ( nil , query ( 30 ) ) == :no_sampling
1313 end
@@ -47,8 +47,7 @@ defmodule Plausible.Stats.SamplingTest do
4747 end
4848
4949 test "short durations" do
50- assert fractional_sample_rate ( @ threshold * 15 , query ( 1 , unit: :hour ) ) ==
51- :no_sampling
50+ assert fractional_sample_rate ( @ threshold * 15 , query ( 1 , unit: :hour ) ) == :no_sampling
5251 end
5352
5453 test "very low sampling rate" do
@@ -58,24 +57,56 @@ defmodule Plausible.Stats.SamplingTest do
5857 @ filter [ "is" , "event:name" , [ "pageview" ] ]
5958 test "scales sampling rate according to query filters (when sampling adjustments are enabled)" do
6059 assert fractional_sample_rate ( @ threshold * 50 , query ( 30 , filters: [ ] ) ) == 0.02
60+ assert fractional_sample_rate ( @ threshold * 50 , query ( 30 , filters: [ @ filter ] ) ) == 0.08
6161
62- assert fractional_sample_rate ( @ threshold * 50 , query ( 30 , filters: [ @ filter ] ) ) ==
63- 0.08
64-
65- assert fractional_sample_rate (
66- @ threshold * 50 ,
67- query ( 30 , filters: [ @ filter , @ filter ] )
68- ) == 0.32
62+ assert fractional_sample_rate ( @ threshold * 50 , query ( 30 , filters: [ @ filter , @ filter ] ) ) ==
63+ 0.32
6964
7065 assert fractional_sample_rate (
7166 @ threshold * 50 ,
7267 query ( 30 , filters: [ @ filter , @ filter , @ filter ] )
7368 ) == 0.32
7469
75- assert fractional_sample_rate (
76- @ threshold * 10 ,
77- query ( 30 , filters: [ @ filter , @ filter ] )
78- ) == :no_sampling
70+ assert fractional_sample_rate ( @ threshold * 10 , query ( 30 , filters: [ @ filter , @ filter ] ) ) ==
71+ :no_sampling
72+ end
73+ end
74+
75+ describe "&fractional_sample_rate/2 clamps the range to the site's stats start" do
76+ test "a range with no known stats start uses the full requested range" do
77+ # `site_native_stats_start_at` is only nil in tests / for sites without
78+ # stats; nothing gets clamped, matching a stats start before the range.
79+ without_start = range_query ( ~D[ 2025-01-01] , ~D[ 2026-07-15] , nil )
80+ early_start = range_query ( ~D[ 2025-01-01] , ~D[ 2026-07-15] , ~N[ 1900-01-01 00:00:00] )
81+
82+ assert fractional_sample_rate ( @ threshold , without_start ) ==
83+ fractional_sample_rate ( @ threshold , early_start )
84+ end
85+
86+ test "a range starting after stats begin is unaffected by the clamp" do
87+ after_start = range_query ( ~D[ 2026-04-01] , ~D[ 2026-07-15] , ~N[ 2026-01-01 00:00:00] )
88+ no_start = range_query ( ~D[ 2026-04-01] , ~D[ 2026-07-15] , nil )
89+
90+ assert fractional_sample_rate ( @ threshold , after_start ) ==
91+ fractional_sample_rate ( @ threshold , no_start )
92+ end
93+
94+ test "a range starting before stats begin is treated as starting when stats begin" do
95+ stats_start = ~N[ 2026-01-01 00:00:00]
96+ range_end = ~D[ 2026-07-15]
97+
98+ from_stats_start =
99+ fractional_sample_rate ( @ threshold , range_query ( ~D[ 2026-01-01] , range_end , stats_start ) )
100+
101+ from_1970 =
102+ fractional_sample_rate ( @ threshold , range_query ( ~D[ 1970-01-01] , range_end , stats_start ) )
103+
104+ from_2025 =
105+ fractional_sample_rate ( @ threshold , range_query ( ~D[ 2025-01-01] , range_end , stats_start ) )
106+
107+ assert from_stats_start == 0.15
108+ assert from_1970 == from_stats_start
109+ assert from_2025 == from_stats_start
79110 end
80111 end
81112
@@ -92,5 +123,17 @@ defmodule Plausible.Stats.SamplingTest do
92123 filters: filters
93124 }
94125 end
126+
127+ defp range_query ( first_date , last_date , native_stats_start_at ) do
128+ first = DateTime . new! ( first_date , ~T[ 00:00:00] , "Etc/UTC" )
129+ last = DateTime . new! ( last_date , ~T[ 00:00:00] , "Etc/UTC" )
130+
131+ % Query {
132+ utc_time_range: DateTimeRange . new! ( first , last ) ,
133+ timezone: "UTC" ,
134+ filters: [ ] ,
135+ site_native_stats_start_at: native_stats_start_at
136+ }
137+ end
95138 end
96139end
0 commit comments