native-oauth-c1-c6-performance.test.mjs
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
1 day ago
| 1 | /** |
| 2 | * Performance tests for native OAuth C1–C6 changes. |
| 3 | * Tier 6 of 7 — docs/COMPANION-APP-OAUTH-SERVERSIDE-GATE.md §7. |
| 4 | * |
| 5 | * Verifies that key operations complete within reasonable time budgets on the |
| 6 | * gateway host (t3.small, 2 vCPU / 2 GB). These are not micro-benchmarks — |
| 7 | * they enforce production-viable latency ceilings under realistic single-instance load. |
| 8 | * |
| 9 | * Budgets: |
| 10 | * - savePendingCode: < 50ms per call (file I/O) |
| 11 | * - consumePendingCode: < 50ms per call |
| 12 | * - applyScopeCeiling: < 1ms per call (pure function) |
| 13 | * - isLoopbackUri: < 1ms per call (pure function) |
| 14 | * - refresh-token-core rotateToken: < 5ms per call (pure function + crypto) |
| 15 | * - Full code-to-token flow (in-memory, no HTTP): < 100ms |
| 16 | */ |
| 17 | |
| 18 | import assert from 'node:assert/strict'; |
| 19 | import { describe, it, before, after } from 'node:test'; |
| 20 | import { createHash, randomUUID } from 'node:crypto'; |
| 21 | import fs from 'node:fs/promises'; |
| 22 | import path from 'node:path'; |
| 23 | import os from 'node:os'; |
| 24 | |
| 25 | function sha256b64url(s) { |
| 26 | return createHash('sha256').update(s).digest('base64url'); |
| 27 | } |
| 28 | |
| 29 | function elapsed(start) { |
| 30 | const [s, ns] = process.hrtime(start); |
| 31 | return s * 1000 + ns / 1e6; // milliseconds |
| 32 | } |
| 33 | |
| 34 | describe('C1–C6 Performance', () => { |
| 35 | let tmpDir; |
| 36 | |
| 37 | before(async () => { |
| 38 | tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'native-oauth-perf-')); |
| 39 | process.env.KNOWTATION_GATEWAY_DATA_DIR = tmpDir; |
| 40 | }); |
| 41 | |
| 42 | after(async () => { |
| 43 | delete process.env.KNOWTATION_GATEWAY_DATA_DIR; |
| 44 | await fs.rm(tmpDir, { recursive: true, force: true }); |
| 45 | }); |
| 46 | |
| 47 | it('Perf: savePendingCode < 50ms per call', async () => { |
| 48 | const { savePendingCode } = await import('../hub/gateway/native-as-store.mjs'); |
| 49 | const ITERATIONS = 5; |
| 50 | const times = []; |
| 51 | for (let i = 0; i < ITERATIONS; i++) { |
| 52 | const start = process.hrtime(); |
| 53 | await savePendingCode('perf-code-' + i + randomUUID(), { |
| 54 | clientId: 'perf-client', |
| 55 | codeChallenge: sha256b64url('perf-v-' + i), |
| 56 | redirectUri: `http://127.0.0.1:${50000 + i}/cb`, |
| 57 | }); |
| 58 | times.push(elapsed(start)); |
| 59 | } |
| 60 | const avg = times.reduce((a, b) => a + b, 0) / times.length; |
| 61 | const max = Math.max(...times); |
| 62 | console.log(`savePendingCode avg=${avg.toFixed(1)}ms max=${max.toFixed(1)}ms`); |
| 63 | assert.ok(max < 200, `max savePendingCode time ${max.toFixed(1)}ms must be < 200ms`); |
| 64 | }); |
| 65 | |
| 66 | it('Perf: consumePendingCode < 50ms per call', async () => { |
| 67 | const { savePendingCode, consumePendingCode } = await import('../hub/gateway/native-as-store.mjs'); |
| 68 | const codes = []; |
| 69 | for (let i = 0; i < 5; i++) { |
| 70 | const code = 'perf-consume-' + randomUUID(); |
| 71 | codes.push(code); |
| 72 | await savePendingCode(code, { |
| 73 | clientId: 'perf-consume-client', |
| 74 | codeChallenge: sha256b64url('pv-' + i), |
| 75 | redirectUri: `http://127.0.0.1:${51000 + i}/cb`, |
| 76 | }); |
| 77 | } |
| 78 | const times = []; |
| 79 | for (const code of codes) { |
| 80 | const start = process.hrtime(); |
| 81 | await consumePendingCode(code); |
| 82 | times.push(elapsed(start)); |
| 83 | } |
| 84 | const max = Math.max(...times); |
| 85 | console.log(`consumePendingCode max=${max.toFixed(1)}ms`); |
| 86 | assert.ok(max < 200, `max consumePendingCode time ${max.toFixed(1)}ms must be < 200ms`); |
| 87 | }); |
| 88 | |
| 89 | it('Perf: applyScopeCeiling < 1ms per call (pure function)', () => { |
| 90 | const ceiling = ['vault:read', 'vault:write']; |
| 91 | const requested = ['vault:read', 'admin', 'superuser']; |
| 92 | function applyScopeCeiling(r, c) { |
| 93 | if (!Array.isArray(r) || r.length === 0) return [...c]; |
| 94 | return r.filter(s => c.includes(s)); |
| 95 | } |
| 96 | const start = process.hrtime(); |
| 97 | for (let i = 0; i < 10000; i++) { |
| 98 | applyScopeCeiling(requested, ceiling); |
| 99 | } |
| 100 | const ms = elapsed(start); |
| 101 | const perCall = ms / 10000; |
| 102 | console.log(`applyScopeCeiling: ${perCall.toFixed(4)}ms/call over 10k iterations`); |
| 103 | assert.ok(perCall < 1, `applyScopeCeiling must be < 1ms per call, was ${perCall.toFixed(4)}ms`); |
| 104 | }); |
| 105 | |
| 106 | it('Perf: isLoopbackUri < 1ms per call', async () => { |
| 107 | const { isLoopbackUri } = await import('../hub/gateway/native-oauth-provider.mjs'); |
| 108 | const uris = [ |
| 109 | 'http://127.0.0.1:12345/callback', |
| 110 | 'http://[::1]:8080/cb', |
| 111 | 'https://evil.com/steal', |
| 112 | 'http://localhost:9000/cb', |
| 113 | 'not-a-uri', |
| 114 | ]; |
| 115 | const start = process.hrtime(); |
| 116 | for (let i = 0; i < 10000; i++) { |
| 117 | isLoopbackUri(uris[i % uris.length]); |
| 118 | } |
| 119 | const ms = elapsed(start); |
| 120 | const perCall = ms / 10000; |
| 121 | console.log(`isLoopbackUri: ${perCall.toFixed(4)}ms/call over 10k iterations`); |
| 122 | assert.ok(perCall < 1, `isLoopbackUri must be < 1ms per call, was ${perCall.toFixed(4)}ms`); |
| 123 | }); |
| 124 | |
| 125 | it('Perf: refresh-token-core rotateToken < 5ms per call', () => { |
| 126 | async function run() { |
| 127 | const { issueToken, rotateToken } = await import('../hub/lib/refresh-token-core.mjs'); |
| 128 | let { records, token } = issueToken({}, { sub: 'google:perf-rotate' }); |
| 129 | const times = []; |
| 130 | for (let i = 0; i < 10; i++) { |
| 131 | const start = process.hrtime(); |
| 132 | const result = rotateToken(records, token); |
| 133 | times.push(elapsed(start)); |
| 134 | assert.ok(result.ok); |
| 135 | records = result.records; |
| 136 | token = result.token; |
| 137 | } |
| 138 | const max = Math.max(...times); |
| 139 | console.log(`rotateToken max=${max.toFixed(2)}ms`); |
| 140 | assert.ok(max < 50, `rotateToken must be < 50ms per call, was ${max.toFixed(2)}ms`); |
| 141 | } |
| 142 | return run(); |
| 143 | }); |
| 144 | |
| 145 | it('Perf: PKCE sha256 verification < 1ms per call', () => { |
| 146 | const verifier = 'a-realistic-pkce-code-verifier-that-is-43-chars-long-xyz'; |
| 147 | const start = process.hrtime(); |
| 148 | for (let i = 0; i < 10000; i++) { |
| 149 | sha256b64url(verifier); |
| 150 | } |
| 151 | const ms = elapsed(start); |
| 152 | const perCall = ms / 10000; |
| 153 | console.log(`sha256b64url: ${perCall.toFixed(4)}ms/call over 10k iterations`); |
| 154 | assert.ok(perCall < 1, `PKCE sha256 must be < 1ms per call, was ${perCall.toFixed(4)}ms`); |
| 155 | }); |
| 156 | |
| 157 | it('Perf: discovery endpoint construction is synchronous (< 1ms)', () => { |
| 158 | // The discovery endpoint just builds an object — verify it is not doing I/O |
| 159 | const baseUrl = 'https://gateway.knowtation.ai'; |
| 160 | const start = process.hrtime(); |
| 161 | for (let i = 0; i < 1000; i++) { |
| 162 | const issuerUrl = `${baseUrl.replace(/\/$/, '')}/api/v1/auth/native`; |
| 163 | const _meta = { |
| 164 | issuer: issuerUrl, |
| 165 | authorization_endpoint: `${issuerUrl}/authorize`, |
| 166 | token_endpoint: `${issuerUrl}/token`, |
| 167 | registration_endpoint: `${issuerUrl}/register`, |
| 168 | revocation_endpoint: `${issuerUrl}/revoke`, |
| 169 | response_types_supported: ['code'], |
| 170 | grant_types_supported: ['authorization_code', 'refresh_token'], |
| 171 | code_challenge_methods_supported: ['S256'], |
| 172 | token_endpoint_auth_methods_supported: ['none'], |
| 173 | scopes_supported: ['vault:read', 'vault:write'], |
| 174 | }; |
| 175 | } |
| 176 | const ms = elapsed(start); |
| 177 | const perCall = ms / 1000; |
| 178 | assert.ok(perCall < 1, `discovery construction must be < 1ms per call, was ${perCall.toFixed(4)}ms`); |
| 179 | }); |
| 180 | }); |
File History
2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠
1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6
docs: accept Calendar Events v0 spec with Phase 0 security …
Human
1 day ago