mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 16:02:41 +00:00
* 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
71 lines
2.7 KiB
TypeScript
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 })
|
|
})
|
|
})
|