@@ -4,15 +4,16 @@ import { drizzle } from 'drizzle-orm/postgres-js'
44import postgres from 'postgres'
55import { afterAll , beforeAll , describe , expect , it , vi } from 'vitest'
66
7- const { select } = vi . hoisted ( ( ) => ( { select : vi . fn ( ) } ) )
7+ const { select, transaction } = vi . hoisted ( ( ) => ( { select : vi . fn ( ) , transaction : vi . fn ( ) } ) )
88const databaseUrl = readTestDatabaseUrl ( )
9- vi . mock ( '@sim/db' , ( ) => ( { db : { select } } ) )
9+ vi . mock ( '@sim/db' , ( ) => ( { db : { select, transaction } } ) )
1010
1111import {
12- type DashboardLedgerWindow ,
13- readDashboardHistoricalActorUsage ,
14- readDashboardLedgerUsage ,
15- readDashboardLedgerUsageByUser ,
12+ organizationWindow ,
13+ readDashboardHistoricalActorSpend ,
14+ readDashboardLedgerSpend ,
15+ readDashboardLedgerSpendByUser ,
16+ readDashboardOrganizationWorkflowRuns ,
1617} from '@/lib/admin/dashboard-usage'
1718import type { ResolvedUsagePeriod } from '@/lib/billing/core/reporting-period'
1819
@@ -30,11 +31,6 @@ const reportingPeriod: ResolvedUsagePeriod = {
3031 anchorDate : '2026-01-15' ,
3132 interval : 'month' ,
3233}
33- const organizationWindow = ( id : string , period : ResolvedUsagePeriod ) : DashboardLedgerWindow => ( {
34- entity : { type : 'organization' , id } ,
35- period,
36- } )
37-
3834const P = `'2026-08-01', '2026-09-01'`
3935const PREVIOUS = `'2026-07-01', '2026-08-01'`
4036
@@ -90,6 +86,9 @@ beforeAll(async () => {
9086 ('u3', 'model', 'copilot', 0.1, 'organization', 'org-a', ${ P } , NULL, '2026-08-07'),
9187 ('u4', 'model', 'copilot', 0.2, 'organization', 'org-a', ${ P } , NULL, '2026-08-07'),
9288 ('u5', 'model', 'workflow', 0.3, 'organization', 'org-a', ${ P } , 'e11', '2026-08-07'),
89+ -- one run shared by two former actors
90+ ('u3', 'model', 'workflow', 0.1, 'organization', 'org-a', ${ P } , 'e12', '2026-08-08'),
91+ ('u5', 'model', 'workflow', 0.1, 'organization', 'org-a', ${ P } , 'e12', '2026-08-08'),
9392
9493 -- org-b, reporting period: matched on created_at, start inclusive and end exclusive,
9594 -- whatever the rows are stamped with.
@@ -106,6 +105,7 @@ beforeAll(async () => {
106105 ` )
107106 const database = drizzle ( connection )
108107 select . mockImplementation ( ( fields ) => database . select ( fields ) )
108+ transaction . mockImplementation ( ( run , config ) => database . transaction ( run , config ) )
109109} )
110110
111111afterAll ( async ( ) => {
@@ -114,63 +114,98 @@ afterAll(async () => {
114114} )
115115
116116describe ( 'dashboard ledger usage' , ( ) => {
117- it ( 'sums spend and counts distinct workflow runs per entity over each period' , async ( ) => {
118- const usage = await readDashboardLedgerUsage ( [
117+ it ( 'sums spend per entity over each period' , async ( ) => {
118+ const spend = await readDashboardLedgerSpend ( [
119119 organizationWindow ( 'org-a' , stampedPeriod ) ,
120120 organizationWindow ( 'org-b' , reportingPeriod ) ,
121121 organizationWindow ( 'org-c' , stampedPeriod ) ,
122122 { entity : { type : 'user' , id : 'p1' } , period : stampedPeriod } ,
123123 ] )
124124
125- expect ( Object . fromEntries ( usage ) ) . toEqual ( {
126- 'org-a' : { cost : 3.38 , workflowRuns : 5 } ,
127- 'org-b' : { cost : 1.75 , workflowRuns : 3 } ,
128- p1 : { cost : 0.6 , workflowRuns : 1 } ,
129- } )
125+ expect ( Object . fromEntries ( spend ) ) . toEqual ( { 'org-a' : 3.58 , 'org-b' : 1.75 , p1 : 0.6 } )
130126 } )
131127
132128 it ( 'reads an entity once however many times it is asked for' , async ( ) => {
133- const usage = await readDashboardLedgerUsage ( [
129+ const spend = await readDashboardLedgerSpend ( [
134130 organizationWindow ( 'org-a' , stampedPeriod ) ,
135131 organizationWindow ( 'org-a' , stampedPeriod ) ,
136132 ] )
137- expect ( usage . get ( 'org-a' ) ) . toEqual ( { cost : 3.38 , workflowRuns : 5 } )
133+ expect ( spend . get ( 'org-a' ) ) . toBe ( 3.58 )
138134 } )
139135
140- it ( 'splits spend and runs by acting user, counting a shared run for each actor ' , async ( ) => {
141- const usage = await readDashboardLedgerUsageByUser ( [
136+ it ( 'splits spend by acting user' , async ( ) => {
137+ const spend = await readDashboardLedgerSpendByUser ( [
142138 organizationWindow ( 'org-a' , stampedPeriod ) ,
143139 organizationWindow ( 'org-b' , reportingPeriod ) ,
144140 ] )
145141
146- expect ( Object . fromEntries ( usage . get ( 'org-a' ) ?? [ ] ) ) . toEqual ( {
147- u1 : { cost : 1.51 , workflowRuns : 2 } ,
148- u2 : { cost : 0.37 , workflowRuns : 2 } ,
149- u3 : { cost : 1 , workflowRuns : 1 } ,
150- u4 : { cost : 0.2 , workflowRuns : 0 } ,
151- u5 : { cost : 0.3 , workflowRuns : 1 } ,
152- } )
153- expect ( Object . fromEntries ( usage . get ( 'org-b' ) ?? [ ] ) ) . toEqual ( {
154- u6 : { cost : 1.75 , workflowRuns : 3 } ,
142+ expect ( Object . fromEntries ( spend . get ( 'org-a' ) ?? [ ] ) ) . toEqual ( {
143+ u1 : 1.51 ,
144+ u2 : 0.37 ,
145+ u3 : 1.1 ,
146+ u4 : 0.2 ,
147+ u5 : 0.4 ,
155148 } )
149+ expect ( Object . fromEntries ( spend . get ( 'org-b' ) ?? [ ] ) ) . toEqual ( { u6 : 1.75 } )
156150 } )
157151
158152 it ( 'narrows the per-user split to the requested users' , async ( ) => {
159153 const window = organizationWindow ( 'org-a' , stampedPeriod )
160154
161- const usage = await readDashboardLedgerUsageByUser ( [ window ] , [ 'u2' , 'u4' ] )
162- expect ( Object . fromEntries ( usage . get ( 'org-a' ) ?? [ ] ) ) . toEqual ( {
163- u2 : { cost : 0.37 , workflowRuns : 2 } ,
164- u4 : { cost : 0.2 , workflowRuns : 0 } ,
165- } )
166- expect ( ( await readDashboardLedgerUsageByUser ( [ window ] , [ ] ) ) . size ) . toBe ( 0 )
155+ const spend = await readDashboardLedgerSpendByUser ( [ window ] , [ 'u2' , 'u4' ] )
156+ expect ( Object . fromEntries ( spend . get ( 'org-a' ) ?? [ ] ) ) . toEqual ( { u2 : 0.37 , u4 : 0.2 } )
157+ expect ( ( await readDashboardLedgerSpendByUser ( [ window ] , [ ] ) ) . size ) . toBe ( 0 )
167158 } )
168159
169- it ( 'counts only actors who are neither members nor collaborators of the organization' , async ( ) => {
170- expect ( await readDashboardHistoricalActorUsage ( 'org-a' , stampedPeriod ) ) . toEqual ( {
171- usedDollars : 1.5 ,
172- workflowRuns : 2 ,
173- actorCount : 3 ,
160+ it ( 'counts distinct workflow runs in total, for each actor of a shared run, and once across former actors' , async ( ) => {
161+ const orgA = await readDashboardOrganizationWorkflowRuns ( 'org-a' , stampedPeriod )
162+ expect ( orgA ?. total ) . toBe ( 6 )
163+ expect ( Object . fromEntries ( orgA ?. byUser ?? [ ] ) ) . toEqual ( { u1 : 2 , u2 : 2 , u3 : 2 , u5 : 2 } )
164+ expect ( orgA ?. formerActors ) . toBe ( 3 )
165+
166+ const orgB = await readDashboardOrganizationWorkflowRuns ( 'org-b' , reportingPeriod )
167+ expect ( orgB ?. total ) . toBe ( 3 )
168+ expect ( Object . fromEntries ( orgB ?. byUser ?? [ ] ) ) . toEqual ( { u6 : 3 } )
169+ expect ( orgB ?. formerActors ) . toBe ( 3 )
170+
171+ const empty = await readDashboardOrganizationWorkflowRuns ( 'org-c' , stampedPeriod )
172+ expect ( empty ?. total ) . toBe ( 0 )
173+ expect ( empty ?. byUser . size ) . toBe ( 0 )
174+ expect ( empty ?. formerActors ) . toBe ( 0 )
175+ } )
176+
177+ it ( 'reports runs as unknown when the count outlasts its budget' , async ( ) => {
178+ const locker = postgres ( databaseUrl , {
179+ max : 1 ,
180+ prepare : false ,
181+ connection : { search_path : schemaName } ,
182+ onnotice : ( ) => undefined ,
183+ } )
184+ let release : ( ) => void = ( ) => undefined
185+ const released = new Promise < void > ( ( resolve ) => {
186+ release = resolve
187+ } )
188+ let markLocked : ( ) => void = ( ) => undefined
189+ const locked = new Promise < void > ( ( resolve ) => {
190+ markLocked = resolve
191+ } )
192+ const holder = locker . begin ( async ( tx ) => {
193+ await tx . unsafe ( 'LOCK TABLE usage_log IN ACCESS EXCLUSIVE MODE' )
194+ markLocked ( )
195+ await released
174196 } )
197+ try {
198+ await locked
199+ expect ( await readDashboardOrganizationWorkflowRuns ( 'org-a' , stampedPeriod ) ) . toBeNull ( )
200+ } finally {
201+ release ( )
202+ await holder
203+ await locker . end ( )
204+ }
205+ } , 20_000 )
206+
207+ it ( 'spends only for actors who are neither members nor collaborators of the organization' , async ( ) => {
208+ const spend = await readDashboardHistoricalActorSpend ( 'org-a' , stampedPeriod )
209+ expect ( Object . fromEntries ( spend ) ) . toEqual ( { u3 : 1.1 , u4 : 0.2 , u5 : 0.4 } )
175210 } )
176211} )
0 commit comments