Files
orca/cloud/apps/push/src/host-session-store.test.ts
Jinwoo Hong eb2f2d52ae feat(cloud): native push gateway and dedicated infrastructure (1/3) (#19912)
* refactor(cloud): share PostgreSQL schema startup between services

* feat(cloud): add durable native push notification gateway

* infra(push): define dedicated gateway resources and operational checks

* fix(push): bound cross-host admission and simplify gateway configuration

* fix(push): validate deploy configuration and preserve topic-error registrations
2026-09-10 17:59:46 -04:00

71 lines
2.7 KiB
TypeScript

import { PUSH_LIMITS } from '@orca-cloud/push-contract'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { PushHostSessionStore } from './host-session-store.js'
import { openInMemoryPushDatabase, type PushDatabase } from './push-database.js'
const HOST = 'abcdefghijklmnop'
describe('push host session store', () => {
let database: PushDatabase
let clock = 1_700_000_000_000
let sessions: PushHostSessionStore
beforeEach(async () => {
database = await openInMemoryPushDatabase()
clock = 1_700_000_000_000
sessions = new PushHostSessionStore(database, () => clock)
})
afterEach(async () => {
await database.close()
})
it('mints a 24 hour session and stores only its hash', async () => {
const session = await sessions.create(HOST)
expect(session.expiresAt).toBe(clock + PUSH_LIMITS.sessionTtlMs)
expect(Buffer.from(session.sessionToken, 'base64url').byteLength).toBe(32)
const [row] = await database.query('SELECT token_hash FROM push_sessions')
expect(String(row?.token_hash)).not.toBe(session.sessionToken)
await expect(sessions.resolve(session.sessionToken)).resolves.toMatchObject({
ok: true,
hostFingerprint: HOST
})
})
it('reports expiry separately from an unknown token', async () => {
const session = await sessions.create(HOST)
clock += PUSH_LIMITS.sessionTtlMs + 1
await expect(sessions.resolve(session.sessionToken)).resolves.toEqual({
ok: false,
reason: 'session_expired'
})
await expect(sessions.resolve('not-a-session')).resolves.toEqual({
ok: false,
reason: 'unknown_session'
})
})
it('accepts a session on its final millisecond', async () => {
const session = await sessions.create(HOST)
clock += PUSH_LIMITS.sessionTtlMs
await expect(sessions.resolve(session.sessionToken)).resolves.toMatchObject({ ok: true })
})
it('keeps one live session per host and prunes it once expired', async () => {
const first = await sessions.create(HOST)
const second = await sessions.create(HOST)
// The earlier session is gone the moment its host proves again, so a flood
// of proofs leaves one row per host rather than one per proof.
await expect(sessions.resolve(first.sessionToken)).resolves.toEqual({
ok: false,
reason: 'unknown_session'
})
await expect(sessions.resolve(second.sessionToken)).resolves.toMatchObject({ ok: true })
const other = await sessions.create('ponmlkjihgfedcba')
await expect(sessions.resolve(second.sessionToken)).resolves.toMatchObject({ ok: true })
clock += PUSH_LIMITS.sessionTtlMs + 1
expect(await sessions.pruneExpired()).toBe(2)
await expect(sessions.resolve(other.sessionToken)).resolves.toMatchObject({ ok: false })
})
})