11import { wait } from "@stackframe/stack-shared/dist/utils/promises" ;
2- import { it } from "../../../../helpers" ;
2+ import { expect } from "vitest" ;
3+ import { NiceResponse , it } from "../../../../helpers" ;
34import { Auth , InternalApiKey , Project , backendContext , createMailbox , niceBackendFetch } from "../../../backend-helpers" ;
45
6+ async function ensureAnonymousUsersAreStillExcluded ( metricsResponse : NiceResponse ) {
7+ for ( let i = 0 ; i < 2 ; i ++ ) {
8+ await Auth . Anonymous . signUp ( ) ;
9+ }
10+ await wait ( 1000 ) ;
11+ const response = await niceBackendFetch ( "/api/v1/internal/metrics" , { accessType : 'admin' } ) ;
12+ expect ( response . body ) . toEqual ( metricsResponse . body ) ;
13+ }
14+
515it ( "should return metrics data" , async ( { expect } ) => {
616 await Project . createAndSwitch ( {
717 config : {
@@ -13,6 +23,8 @@ it("should return metrics data", async ({ expect }) => {
1323
1424 const response = await niceBackendFetch ( "/api/v1/internal/metrics" , { accessType : 'admin' } ) ;
1525 expect ( response ) . toMatchSnapshot ( `metrics_result_no_users` ) ;
26+
27+ await ensureAnonymousUsersAreStillExcluded ( response ) ;
1628} ) ;
1729
1830it ( "should return metrics data with users" , async ( { expect } ) => {
@@ -54,6 +66,8 @@ it("should return metrics data with users", async ({ expect }) => {
5466
5567 const response = await niceBackendFetch ( "/api/v1/internal/metrics" , { accessType : 'admin' } ) ;
5668 expect ( response ) . toMatchSnapshot ( ) ;
69+
70+ await ensureAnonymousUsersAreStillExcluded ( response ) ;
5771} , {
5872 timeout : 120_000 ,
5973} ) ;
@@ -87,4 +101,134 @@ it("should not work for non-admins", async ({ expect }) => {
87101 },
88102 }
89103 ` ) ;
104+
105+ await ensureAnonymousUsersAreStillExcluded ( response ) ;
106+ } ) ;
107+
108+ it ( "should exclude anonymous users from metrics" , async ( { expect } ) => {
109+ await Project . createAndSwitch ( {
110+ config : {
111+ magic_link_enabled : true ,
112+ }
113+ } ) ;
114+
115+ await InternalApiKey . createAndSetProjectKeys ( ) ;
116+
117+ // Create 1 regular user
118+ backendContext . set ( { mailbox : createMailbox ( ) , ipData : { country : "US" , ipAddress : "127.0.0.1" , city : "New York" , region : "NY" , latitude : 40.7128 , longitude : - 74.0060 , tzIdentifier : "America/New_York" } } ) ;
119+ await Auth . Otp . signIn ( ) ;
120+
121+ // Store metrics so we can compare them later
122+ const beforeMetrics = await niceBackendFetch ( "/api/v1/internal/metrics" , { accessType : 'admin' } ) ;
123+
124+ // Create 2 anonymous users
125+ for ( let i = 0 ; i < 2 ; i ++ ) {
126+ await Auth . Anonymous . signUp ( ) ;
127+ }
128+
129+ await wait ( 1000 ) ; // the event log is async, so let's give it some time to be written to the DB
130+
131+ const response = await niceBackendFetch ( "/api/v1/internal/metrics" , { accessType : 'admin' } ) ;
132+ expect ( beforeMetrics . body ) . toEqual ( response . body ) ;
133+
134+ // Verify that total_users only counts the 1 regular user, not the anonymous ones
135+ expect ( response . body . total_users ) . toBe ( 1 ) ;
136+
137+ // Verify anonymous users don't appear in recently_registered
138+ expect ( response . body . recently_registered . length ) . toBe ( 1 ) ;
139+ expect ( response . body . recently_registered . every ( ( user : any ) => ! user . is_anonymous ) ) . toBe ( true ) ;
140+
141+ // Verify anonymous users don't appear in recently_active
142+ expect ( response . body . recently_active . every ( ( user : any ) => ! user . is_anonymous ) ) . toBe ( true ) ;
143+
144+ // Verify anonymous users aren't counted in daily_users
145+ const lastDayUsers = response . body . daily_users [ response . body . daily_users . length - 1 ] ;
146+ expect ( lastDayUsers . activity ) . toBe ( 1 ) ;
147+
148+ // Verify users_by_country only includes regular users
149+ expect ( response . body . users_by_country [ "US" ] ) . toBe ( 1 ) ;
150+
151+ await ensureAnonymousUsersAreStillExcluded ( response ) ;
152+ } ) ;
153+
154+ it ( "should handle anonymous users with activity correctly" , async ( { expect } ) => {
155+ await Project . createAndSwitch ( {
156+ config : {
157+ magic_link_enabled : true ,
158+ }
159+ } ) ;
160+
161+ await InternalApiKey . createAndSetProjectKeys ( ) ;
162+
163+ // Create 1 regular user with activity
164+ const regularMailbox = createMailbox ( ) ;
165+ backendContext . set ( { mailbox : regularMailbox , ipData : { country : "CA" , ipAddress : "127.0.0.1" , city : "Toronto" , region : "ON" , latitude : 43.6532 , longitude : - 79.3832 , tzIdentifier : "America/Toronto" } } ) ;
166+ await Auth . Otp . signIn ( ) ;
167+
168+ // Generate some activity for regular user
169+ await niceBackendFetch ( "/api/v1/users/me" , { accessType : 'client' } ) ;
170+
171+ // Create 3 anonymous users with activity
172+ for ( let i = 0 ; i < 3 ; i ++ ) {
173+ await Auth . Anonymous . signUp ( ) ;
174+ }
175+
176+ await wait ( 3000 ) ; // the event log is async, so let's give it some time to be written to the DB
177+
178+ const response = await niceBackendFetch ( "/api/v1/internal/metrics" , { accessType : 'admin' } ) ;
179+
180+ // Should only count 1 regular user
181+ expect ( response . body . total_users ) . toBe ( 1 ) ;
182+
183+ // Daily active users should only count regular users
184+ const todayDAU = response . body . daily_active_users [ response . body . daily_active_users . length - 1 ] ;
185+ expect ( todayDAU . activity ) . toBe ( 1 ) ;
186+
187+ // Users by country should only count regular users
188+ expect ( response . body . users_by_country [ "CA" ] ) . toBe ( 1 ) ;
189+ expect ( response . body . users_by_country [ "US" ] ) . toBeUndefined ( ) ;
190+
191+ await ensureAnonymousUsersAreStillExcluded ( response ) ;
192+ } ) ;
193+
194+ it ( "should handle mixed auth methods excluding anonymous users" , async ( { expect } ) => {
195+ await Project . createAndSwitch ( {
196+ config : {
197+ magic_link_enabled : true ,
198+ credential_enabled : true ,
199+ }
200+ } ) ;
201+
202+ await InternalApiKey . createAndSetProjectKeys ( ) ;
203+
204+ // Create users with different auth methods
205+ const regularMailbox = createMailbox ( ) ;
206+
207+ // Regular user with OTP
208+ backendContext . set ( { mailbox : regularMailbox } ) ;
209+ await Auth . Otp . signIn ( ) ;
210+
211+ // Regular user with password
212+ const passwordMailbox = createMailbox ( ) ;
213+ backendContext . set ( { mailbox : passwordMailbox } ) ;
214+ await Auth . Password . signUpWithEmail ( { password : "test1234" } ) ;
215+
216+ // Anonymous users (should not be counted)
217+ for ( let i = 0 ; i < 5 ; i ++ ) {
218+ await Auth . Anonymous . signUp ( ) ;
219+ }
220+
221+ await wait ( 3000 ) ;
222+
223+ const response = await niceBackendFetch ( "/api/v1/internal/metrics" , { accessType : 'admin' } ) ;
224+
225+ // Should only count 2 regular users
226+ expect ( response . body . total_users ) . toBe ( 2 ) ;
227+
228+ // Login methods should only count regular users' methods
229+ const loginMethods = response . body . login_methods ;
230+ const totalMethodCount = loginMethods . reduce ( ( sum : number , method : any ) => sum + method . count , 0 ) ;
231+ expect ( totalMethodCount ) . toBe ( 2 ) ; // 1 OTP + 1 password, no anonymous
232+
233+ await ensureAnonymousUsersAreStillExcluded ( response ) ;
90234} ) ;
0 commit comments