Loading files
442335385This file is pinned to the selected release. Download the original to open it in your design tool.
import { beforeEach, describe, expect, it, vi } from 'vitest'
const db = vi.hoisted(() => ({
userPresenceUpsert: vi.fn(),
machinePresenceUpsert: vi.fn(),
agentPresenceUpsert: vi.fn(),
}))
vi.mock('./prisma.js', () => ({
prisma: {
userDailyPresence: { upsert: db.userPresenceUpsert },
machineDailyPresence: { upsert: db.machinePresenceUpsert },
agentDailyPresence: { upsert: db.agentPresenceUpsert },
},
}))
import { presenceWriteDue, recordTurnStarted, touchMachineOnlineDay, touchUserOnlineDay } from './dailyTracking.js'
import { utcDayStart } from '../types/analytics.js'
describe('touchUserOnlineDay', () => {
beforeEach(() => {
vi.clearAllMocks()
db.userPresenceUpsert.mockResolvedValue({})
})
it('an app `open` upserts the (user, UTC day) row and bumps connections', async () => {
const now = new Date('2026-09-16T10:15:30.000Z')
await touchUserOnlineDay('user-1', now, { isNewConnection: true })
expect(db.userPresenceUpsert).toHaveBeenCalledTimes(1)
const call = db.userPresenceUpsert.mock.calls[0][0]
expect(call.where).toEqual({ userId_dayUtc: { userId: 'user-1', dayUtc: utcDayStart(now) } })
expect(call.create).toEqual({
userId: 'user-1', dayUtc: utcDayStart(now), connections: 1, firstSeenAt: now, lastSeenAt: now,
})
expect(call.update).toEqual({ lastSeenAt: now, connections: { increment: 1 } })
})
it('an app `ping` only touches lastSeenAt — a session spanning midnight opens no connection that day', async () => {
const now = new Date('2026-09-16T00:00:20.000Z')
await touchUserOnlineDay('user-1', now, { isNewConnection: false })
const call = db.userPresenceUpsert.mock.calls[0][0]
expect(call.update).toEqual({ lastSeenAt: now })
expect(call.create.connections).toBe(0)
})
})
describe('touchMachineOnlineDay', () => {
beforeEach(() => {
vi.clearAllMocks()
db.machinePresenceUpsert.mockResolvedValue({})
})
it('upserts the (machine, UTC day) row and bumps connections on a new connection', async () => {
const now = new Date('2026-09-16T10:15:30.000Z')
await touchMachineOnlineDay('user-1', 'machine-a', now, { isNewConnection: true })
expect(db.machinePresenceUpsert).toHaveBeenCalledTimes(1)
const call = db.machinePresenceUpsert.mock.calls[0][0]
expect(call.where).toEqual({ machineId_dayUtc: { machineId: 'machine-a', dayUtc: utcDayStart(now) } })
expect(call.create).toEqual({
machineId: 'machine-a', userId: 'user-1', dayUtc: utcDayStart(now),
connections: 1, firstSeenAt: now, lastSeenAt: now,
})
expect(call.update).toEqual({ lastSeenAt: now, connections: { increment: 1 } })
})
it('only touches lastSeenAt on a heartbeat/close (no connections increment)', async () => {
const now = new Date('2026-09-16T23:59:59.000Z')
await touchMachineOnlineDay('user-1', 'machine-a', now, { isNewConnection: false })
const call = db.machinePresenceUpsert.mock.calls[0][0]
expect(call.update).toEqual({ lastSeenAt: now })
// First write of a new UTC day from a session that spans midnight: the row exists, but no
// connection was opened on that day.
expect(call.create.connections).toBe(0)
})
it('propagates a DB failure so the caller can log it and keep its guard unchanged', async () => {
db.machinePresenceUpsert.mockRejectedValueOnce(new Error('mongo down'))
await expect(
touchMachineOnlineDay('user-1', 'machine-a', new Date(), { isNewConnection: true }),
).rejects.toThrow('mongo down')
})
})
describe('recordTurnStarted', () => {
const now = new Date('2026-09-16T10:15:30.000Z')
beforeEach(() => {
vi.clearAllMocks()
db.machinePresenceUpsert.mockResolvedValue({})
db.agentPresenceUpsert.mockResolvedValue({})
})
it('bumps turnsStarted on the machine row without counting a connection', async () => {
await recordTurnStarted('user-1', 'machine-a', 'agent-x', now)
expect(db.machinePresenceUpsert).toHaveBeenCalledTimes(1)
const call = db.machinePresenceUpsert.mock.calls[0][0]
expect(call.where).toEqual({ machineId_dayUtc: { machineId: 'machine-a', dayUtc: utcDayStart(now) } })
expect(call.create).toEqual({
machineId: 'machine-a', userId: 'user-1', dayUtc: utcDayStart(now),
connections: 0, turnsStarted: 1, firstSeenAt: now, lastSeenAt: now,
})
expect(call.update).toEqual({ lastSeenAt: now, turnsStarted: { increment: 1 } })
})
it('bumps the (machine, agent, day) row', async () => {
await recordTurnStarted('user-1', 'machine-a', 'agent-x', now)
expect(db.agentPresenceUpsert).toHaveBeenCalledTimes(1)
const call = db.agentPresenceUpsert.mock.calls[0][0]
expect(call.where).toEqual({
machineId_agentId_dayUtc: { machineId: 'machine-a', agentId: 'agent-x', dayUtc: utcDayStart(now) },
})
expect(call.create).toEqual({
machineId: 'machine-a', agentId: 'agent-x', userId: 'user-1', dayUtc: utcDayStart(now),
turnsStarted: 1, firstSeenAt: now, lastSeenAt: now,
})
expect(call.update).toEqual({ lastSeenAt: now, turnsStarted: { increment: 1 } })
})
it('propagates a failure from either upsert', async () => {
db.agentPresenceUpsert.mockRejectedValueOnce(new Error('agent row failed'))
await expect(recordTurnStarted('user-1', 'machine-a', 'agent-x', now)).rejects.toThrow('agent row failed')
// The machine write was still attempted — the two are independent.
expect(db.machinePresenceUpsert).toHaveBeenCalledTimes(1)
})
})
describe('presenceWriteDue', () => {
const FIVE_MIN = 5 * 60_000
const t0 = new Date('2026-09-16T10:00:00.000Z')
it('is due before anything has been written', () => {
expect(presenceWriteDue({ dayKey: null, wroteAt: 0 }, t0, FIVE_MIN)).toBe(true)
})
it('is not due within the interval on the same UTC day', () => {
const last = { dayKey: '2026-09-16', wroteAt: t0.getTime() }
expect(presenceWriteDue(last, new Date(t0.getTime() + 15_000), FIVE_MIN)).toBe(false)
expect(presenceWriteDue(last, new Date(t0.getTime() + FIVE_MIN - 1), FIVE_MIN)).toBe(false)
})
it('is due once the interval has elapsed', () => {
const last = { dayKey: '2026-09-16', wroteAt: t0.getTime() }
expect(presenceWriteDue(last, new Date(t0.getTime() + FIVE_MIN), FIVE_MIN)).toBe(true)
})
it('is due when the UTC day rolled over, even inside the interval', () => {
const lateNight = new Date('2026-09-16T23:59:50.000Z')
const last = { dayKey: '2026-09-16', wroteAt: lateNight.getTime() }
expect(presenceWriteDue(last, new Date('2026-09-17T00:00:05.000Z'), FIVE_MIN)).toBe(true)
})
})