luca 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yaml +1 -0
- package/CLAUDE.md +10 -2
- package/README.md +130 -112
- package/assistants/codingAssistant/CORE.md +6 -1
- package/assistants/codingAssistant/hooks.ts +1 -1
- package/assistants/inkbot/hooks.ts +1 -1
- package/assistants/inkbot/tools.ts +1 -1
- package/bun.lock +264 -321
- package/commands/audit-docs.ts +2 -2
- package/commands/build-bootstrap.ts +2 -3
- package/commands/build-python-bridge.ts +2 -3
- package/commands/build-scaffolds.ts +2 -3
- package/commands/bundle-consumer-project.ts +521 -0
- package/commands/generate-api-docs.ts +2 -2
- package/commands/inkbot.ts +2 -2
- package/commands/release.ts +2 -2
- package/commands/social.ts +137 -0
- package/commands/try-all-challenges.ts +3 -3
- package/commands/try-challenge.ts +3 -3
- package/datasets/lora/agentic-loop-session-candidates.jsonl +91 -0
- package/datasets/lora/agentic-loop-session-curation-summary.json +123 -0
- package/datasets/lora/luca-session-candidates.jsonl +29 -0
- package/datasets/lora/luca-session-curation-summary.json +121 -0
- package/datasets/lora/review-batch-1.jsonl +30 -0
- package/datasets/lora/review-manifest.json +41 -0
- package/datasets/lora/review-queue.jsonl +120 -0
- package/datasets/lora/review-schema.json +134 -0
- package/datasets/lora/review-template.jsonl +2 -0
- package/datasets/lora/review-ui.html +725 -0
- package/dist/agi/container.server.d.ts +2 -2
- package/dist/agi/features/assistant.d.ts +2 -2
- package/dist/agi/features/assistants-manager.d.ts +1 -1
- package/dist/agi/features/autonomous-assistant.d.ts +1 -1
- package/dist/agi/features/browser-use.d.ts +1 -1
- package/dist/agi/features/claude-code.d.ts +1 -1
- package/dist/agi/features/conversation-history.d.ts +2 -2
- package/dist/agi/features/conversation.d.ts +1 -1
- package/dist/agi/features/docs-reader.d.ts +1 -1
- package/dist/agi/features/file-tools.d.ts +1 -1
- package/dist/agi/features/luca-coder.d.ts +1 -1
- package/dist/agi/features/openai-codex.d.ts +1 -1
- package/dist/agi/features/skills-library.d.ts +1 -1
- package/dist/clients/civitai/index.d.ts +4 -4
- package/dist/clients/client-template.d.ts +4 -4
- package/dist/clients/comfyui/index.d.ts +2 -2
- package/dist/clients/elevenlabs/index.d.ts +2 -2
- package/dist/clients/openai/index.d.ts +2 -2
- package/dist/clients/supabase/index.d.ts +3 -3
- package/dist/command.d.ts +1 -1
- package/dist/node/container.d.ts +1 -1
- package/dist/node/features/helpers.d.ts +3 -3
- package/dist/node/features/semantic-search.d.ts +1 -1
- package/dist/node/features/vm.d.ts +3 -3
- package/dist/node.d.ts +1 -1
- package/dist/scaffolds/generated.d.ts +1 -1
- package/dist/selector.d.ts +1 -1
- package/features/cipher-social.ts +493 -0
- package/index.html +217 -190
- package/luca.console.ts +1 -1
- package/package.json +7 -2
- package/public/index.html +217 -190
- package/public/slides-ai-native.html +1 -1
- package/public/slides-intro.html +2 -2
- package/scripts/curate-claude-sessions.ts +561 -0
- package/scripts/examples/ask-luca-expert.ts +1 -1
- package/scripts/examples/assistant-questions.ts +1 -1
- package/scripts/examples/excalidraw-expert.ts +1 -1
- package/scripts/examples/file-manager.ts +1 -1
- package/scripts/examples/ideas.ts +1 -1
- package/scripts/examples/interactive-chat.ts +1 -1
- package/scripts/examples/opening-a-web-browser.ts +1 -1
- package/scripts/examples/telegram-bot.ts +1 -1
- package/scripts/examples/using-assistant-with-mcp.ts +1 -1
- package/scripts/examples/using-claude-code.ts +1 -1
- package/scripts/examples/using-contentdb.ts +2 -2
- package/scripts/examples/using-conversations.ts +1 -1
- package/scripts/examples/using-disk-cache.ts +1 -1
- package/scripts/examples/using-docker-shell.ts +1 -1
- package/scripts/examples/using-elevenlabs.ts +1 -1
- package/scripts/examples/using-google-calendar.ts +1 -1
- package/scripts/examples/using-google-docs.ts +1 -1
- package/scripts/examples/using-google-drive.ts +1 -1
- package/scripts/examples/using-google-sheets.ts +1 -1
- package/scripts/examples/using-nlp.ts +1 -1
- package/scripts/examples/using-ollama.ts +1 -1
- package/scripts/examples/using-postgres.ts +1 -1
- package/scripts/examples/using-runpod.ts +1 -1
- package/scripts/examples/using-tts.ts +1 -1
- package/scripts/scaffold.ts +5 -5
- package/scripts/scratch.ts +1 -1
- package/scripts/test-assistant-hooks.ts +1 -1
- package/scripts/test-docs-reader.ts +1 -1
- package/src/agi/container.server.ts +6 -2
- package/src/agi/features/agent-memory.ts +25 -25
- package/src/agi/features/assistant.ts +34 -5
- package/src/agi/features/assistants-manager.ts +122 -6
- package/src/agi/features/autonomous-assistant.ts +1 -1
- package/src/agi/features/browser-use.ts +20 -1
- package/src/agi/features/claude-code.ts +51 -5
- package/src/agi/features/coding-tools.ts +1 -1
- package/src/agi/features/conversation-history.ts +181 -4
- package/src/agi/features/conversation.ts +186 -15
- package/src/agi/features/docs-reader.ts +2 -2
- package/src/agi/features/file-tools.ts +49 -2
- package/src/agi/features/luca-coder.ts +7 -5
- package/src/agi/features/mcp-bridge.ts +532 -0
- package/src/agi/features/openai-codex.ts +2 -2
- package/src/agi/features/skills-library.ts +131 -52
- package/src/agi/lib/token-counter.ts +80 -0
- package/src/bootstrap/generated.ts +56 -57
- package/src/browser.ts +1 -1
- package/src/cli/build-info.ts +2 -2
- package/src/cli/cli.ts +2 -2
- package/src/clients/civitai/index.ts +5 -5
- package/src/clients/client-template.ts +4 -4
- package/src/clients/comfyui/index.ts +4 -4
- package/src/clients/elevenlabs/index.ts +4 -4
- package/src/clients/openai/index.ts +7 -7
- package/src/clients/supabase/index.ts +4 -4
- package/src/clients/voicebox/index.ts +4 -4
- package/src/command.ts +2 -1
- package/src/commands/chat.ts +1 -0
- package/src/commands/eval.ts +2 -56
- package/src/commands/introspect.ts +1 -1
- package/src/commands/prompt.ts +41 -9
- package/src/container-describer.ts +8 -1
- package/src/container.ts +13 -0
- package/src/entity.ts +2 -2
- package/src/helper.ts +1 -1
- package/src/introspection/generated.agi.ts +29596 -27654
- package/src/introspection/generated.node.ts +20284 -19247
- package/src/introspection/generated.web.ts +605 -584
- package/src/introspection/scan.ts +11 -6
- package/src/node/container.ts +9 -1
- package/src/node/features/content-db.ts +39 -2
- package/src/node/features/display-result.ts +57 -0
- package/src/node/features/helpers.ts +46 -7
- package/src/node/features/python.ts +25 -19
- package/src/node/features/repl.ts +1 -1
- package/src/node/features/secure-shell.ts +11 -17
- package/src/node/features/semantic-search.ts +2 -2
- package/src/node/features/socket-repl.ts +336 -0
- package/src/node/features/telnyx-assistant-connector.ts +1206 -0
- package/src/node/features/transpiler.ts +2 -3
- package/src/node/features/ui.ts +5 -0
- package/src/node/features/vm.ts +20 -3
- package/src/node.ts +3 -3
- package/src/python/generated.ts +0 -1
- package/src/scaffolds/generated.ts +82 -83
- package/src/selector.ts +1 -1
- package/src/servers/express.ts +1 -1
- package/src/web/features/helpers.ts +22 -0
- package/tsconfig.json +12 -12
- package/docs/CLI.md +0 -335
- package/docs/CNAME +0 -1
- package/docs/README.md +0 -60
- package/docs/TABLE-OF-CONTENTS.md +0 -183
- package/docs/apis/clients/elevenlabs.md +0 -308
- package/docs/apis/clients/graph.md +0 -107
- package/docs/apis/clients/openai.md +0 -429
- package/docs/apis/clients/rest.md +0 -161
- package/docs/apis/clients/websocket.md +0 -174
- package/docs/apis/features/agi/assistant.md +0 -625
- package/docs/apis/features/agi/assistants-manager.md +0 -282
- package/docs/apis/features/agi/auto-assistant.md +0 -279
- package/docs/apis/features/agi/browser-use.md +0 -802
- package/docs/apis/features/agi/claude-code.md +0 -884
- package/docs/apis/features/agi/conversation-history.md +0 -364
- package/docs/apis/features/agi/conversation.md +0 -548
- package/docs/apis/features/agi/docs-reader.md +0 -99
- package/docs/apis/features/agi/file-tools.md +0 -163
- package/docs/apis/features/agi/luca-coder.md +0 -407
- package/docs/apis/features/agi/openai-codex.md +0 -396
- package/docs/apis/features/agi/openapi.md +0 -138
- package/docs/apis/features/agi/semantic-search.md +0 -387
- package/docs/apis/features/agi/skills-library.md +0 -239
- package/docs/apis/features/node/container-link.md +0 -192
- package/docs/apis/features/node/content-db.md +0 -450
- package/docs/apis/features/node/disk-cache.md +0 -379
- package/docs/apis/features/node/dns.md +0 -652
- package/docs/apis/features/node/docker.md +0 -706
- package/docs/apis/features/node/downloader.md +0 -81
- package/docs/apis/features/node/esbuild.md +0 -60
- package/docs/apis/features/node/file-manager.md +0 -191
- package/docs/apis/features/node/fs.md +0 -1217
- package/docs/apis/features/node/git.md +0 -371
- package/docs/apis/features/node/google-auth.md +0 -193
- package/docs/apis/features/node/google-calendar.md +0 -202
- package/docs/apis/features/node/google-docs.md +0 -173
- package/docs/apis/features/node/google-drive.md +0 -246
- package/docs/apis/features/node/google-mail.md +0 -214
- package/docs/apis/features/node/google-sheets.md +0 -194
- package/docs/apis/features/node/grep.md +0 -292
- package/docs/apis/features/node/helpers.md +0 -164
- package/docs/apis/features/node/ink.md +0 -334
- package/docs/apis/features/node/ipc-socket.md +0 -249
- package/docs/apis/features/node/json-tree.md +0 -86
- package/docs/apis/features/node/networking.md +0 -316
- package/docs/apis/features/node/nlp.md +0 -133
- package/docs/apis/features/node/opener.md +0 -97
- package/docs/apis/features/node/os.md +0 -146
- package/docs/apis/features/node/package-finder.md +0 -392
- package/docs/apis/features/node/postgres.md +0 -234
- package/docs/apis/features/node/proc.md +0 -399
- package/docs/apis/features/node/process-manager.md +0 -305
- package/docs/apis/features/node/python.md +0 -604
- package/docs/apis/features/node/redis.md +0 -380
- package/docs/apis/features/node/repl.md +0 -88
- package/docs/apis/features/node/runpod.md +0 -674
- package/docs/apis/features/node/secure-shell.md +0 -176
- package/docs/apis/features/node/semantic-search.md +0 -408
- package/docs/apis/features/node/sqlite.md +0 -233
- package/docs/apis/features/node/telegram.md +0 -279
- package/docs/apis/features/node/transpiler.md +0 -74
- package/docs/apis/features/node/tts.md +0 -133
- package/docs/apis/features/node/ui.md +0 -701
- package/docs/apis/features/node/vault.md +0 -59
- package/docs/apis/features/node/vm.md +0 -75
- package/docs/apis/features/node/yaml-tree.md +0 -85
- package/docs/apis/features/node/yaml.md +0 -176
- package/docs/apis/features/web/asset-loader.md +0 -59
- package/docs/apis/features/web/container-link.md +0 -192
- package/docs/apis/features/web/esbuild.md +0 -54
- package/docs/apis/features/web/helpers.md +0 -164
- package/docs/apis/features/web/network.md +0 -44
- package/docs/apis/features/web/speech.md +0 -69
- package/docs/apis/features/web/vault.md +0 -59
- package/docs/apis/features/web/vm.md +0 -75
- package/docs/apis/features/web/voice.md +0 -84
- package/docs/apis/servers/express.md +0 -171
- package/docs/apis/servers/mcp.md +0 -238
- package/docs/apis/servers/websocket.md +0 -170
- package/docs/bootstrap/CLAUDE.md +0 -101
- package/docs/bootstrap/SKILL.md +0 -341
- package/docs/bootstrap/templates/about-command.ts +0 -41
- package/docs/bootstrap/templates/docs-models.ts +0 -22
- package/docs/bootstrap/templates/docs-readme.md +0 -43
- package/docs/bootstrap/templates/example-feature.ts +0 -53
- package/docs/bootstrap/templates/health-endpoint.ts +0 -15
- package/docs/bootstrap/templates/luca-cli.ts +0 -30
- package/docs/bootstrap/templates/runme.md +0 -54
- package/docs/challenges/caching-proxy.md +0 -16
- package/docs/challenges/content-db-round-trip.md +0 -14
- package/docs/challenges/custom-command.md +0 -9
- package/docs/challenges/file-watcher-pipeline.md +0 -11
- package/docs/challenges/grep-audit-report.md +0 -15
- package/docs/challenges/multi-feature-dashboard.md +0 -14
- package/docs/challenges/process-orchestrator.md +0 -17
- package/docs/challenges/rest-api-server-with-client.md +0 -12
- package/docs/challenges/script-runner-with-vm.md +0 -11
- package/docs/challenges/simple-rest-api.md +0 -15
- package/docs/challenges/websocket-serve-and-client.md +0 -11
- package/docs/challenges/yaml-config-system.md +0 -14
- package/docs/command-system-overhaul.md +0 -94
- package/docs/documentation-audit.md +0 -134
- package/docs/examples/assistant/CORE.md +0 -18
- package/docs/examples/assistant/hooks.ts +0 -3
- package/docs/examples/assistant/tools.ts +0 -10
- package/docs/examples/assistant-hooks-reference.ts +0 -171
- package/docs/examples/assistant-with-process-manager.md +0 -84
- package/docs/examples/content-db.md +0 -77
- package/docs/examples/disk-cache.md +0 -83
- package/docs/examples/docker.md +0 -101
- package/docs/examples/downloader.md +0 -70
- package/docs/examples/entity.md +0 -124
- package/docs/examples/esbuild.md +0 -80
- package/docs/examples/feature-as-tool-provider.md +0 -143
- package/docs/examples/file-manager.md +0 -82
- package/docs/examples/fs.md +0 -83
- package/docs/examples/git.md +0 -85
- package/docs/examples/google-auth.md +0 -88
- package/docs/examples/google-calendar.md +0 -94
- package/docs/examples/google-docs.md +0 -82
- package/docs/examples/google-drive.md +0 -96
- package/docs/examples/google-sheets.md +0 -95
- package/docs/examples/grep.md +0 -85
- package/docs/examples/ink-blocks.md +0 -75
- package/docs/examples/ink-renderer.md +0 -41
- package/docs/examples/ink.md +0 -103
- package/docs/examples/ipc-socket.md +0 -103
- package/docs/examples/json-tree.md +0 -91
- package/docs/examples/networking.md +0 -58
- package/docs/examples/nlp.md +0 -91
- package/docs/examples/opener.md +0 -78
- package/docs/examples/os.md +0 -72
- package/docs/examples/package-finder.md +0 -89
- package/docs/examples/postgres.md +0 -91
- package/docs/examples/proc.md +0 -81
- package/docs/examples/process-manager.md +0 -79
- package/docs/examples/python.md +0 -132
- package/docs/examples/repl.md +0 -93
- package/docs/examples/runpod.md +0 -119
- package/docs/examples/secure-shell.md +0 -92
- package/docs/examples/sqlite.md +0 -86
- package/docs/examples/structured-output-with-assistants.md +0 -144
- package/docs/examples/telegram.md +0 -77
- package/docs/examples/tts.md +0 -86
- package/docs/examples/ui.md +0 -80
- package/docs/examples/vault.md +0 -70
- package/docs/examples/vm.md +0 -86
- package/docs/examples/websocket-ask-and-reply-example.md +0 -128
- package/docs/examples/yaml-tree.md +0 -93
- package/docs/examples/yaml.md +0 -104
- package/docs/ideas/assistant-factory-pattern.md +0 -142
- package/docs/in-memory-fs.md +0 -4
- package/docs/introspection-audit.md +0 -49
- package/docs/introspection.md +0 -164
- package/docs/mcp/readme.md +0 -162
- package/docs/models.ts +0 -41
- package/docs/philosophy.md +0 -86
- package/docs/principles.md +0 -7
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +0 -34
- package/docs/prompts/check-for-undocumented-features.md +0 -27
- package/docs/prompts/mcp-test-easy-command.md +0 -27
- package/docs/scaffolds/client.md +0 -149
- package/docs/scaffolds/command.md +0 -120
- package/docs/scaffolds/endpoint.md +0 -171
- package/docs/scaffolds/feature.md +0 -158
- package/docs/scaffolds/selector.md +0 -91
- package/docs/scaffolds/server.md +0 -196
- package/docs/selectors.md +0 -115
- package/docs/sessions/custom-command/attempt-log-2.md +0 -195
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +0 -728
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +0 -555
- package/docs/sessions/grep-audit-report/attempt-log-1.md +0 -289
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +0 -679
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +0 -1
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +0 -920
- package/docs/sessions/simple-rest-api/attempt-log-1.md +0 -593
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +0 -995
- package/docs/tutorials/00-bootstrap.md +0 -166
- package/docs/tutorials/01-getting-started.md +0 -106
- package/docs/tutorials/02-container.md +0 -210
- package/docs/tutorials/03-scripts.md +0 -194
- package/docs/tutorials/04-features-overview.md +0 -196
- package/docs/tutorials/05-state-and-events.md +0 -171
- package/docs/tutorials/06-servers.md +0 -157
- package/docs/tutorials/07-endpoints.md +0 -198
- package/docs/tutorials/08-commands.md +0 -252
- package/docs/tutorials/09-clients.md +0 -162
- package/docs/tutorials/10-creating-features.md +0 -203
- package/docs/tutorials/11-contentbase.md +0 -191
- package/docs/tutorials/12-assistants.md +0 -215
- package/docs/tutorials/13-introspection.md +0 -157
- package/docs/tutorials/14-type-system.md +0 -174
- package/docs/tutorials/15-project-patterns.md +0 -222
- package/docs/tutorials/16-google-features.md +0 -534
- package/docs/tutorials/17-tui-blocks.md +0 -530
- package/docs/tutorials/18-semantic-search.md +0 -334
- package/docs/tutorials/19-python-sessions.md +0 -401
- package/docs/tutorials/20-browser-esm.md +0 -234
- package/index.ts +0 -1
- package/src/agi/endpoints/ask.ts +0 -60
- package/src/agi/endpoints/conversations/[id].ts +0 -45
- package/src/agi/endpoints/conversations.ts +0 -31
- package/src/agi/endpoints/experts.ts +0 -37
- package/test/assistant-hooks.test.ts +0 -306
- package/test/assistant.test.ts +0 -81
- package/test/bus.test.ts +0 -134
- package/test/clients-servers.test.ts +0 -217
- package/test/command.test.ts +0 -267
- package/test/container-link.test.ts +0 -274
- package/test/conversation.test.ts +0 -220
- package/test/features.test.ts +0 -160
- package/test/fork-and-research.test.ts +0 -450
- package/test/integration.test.ts +0 -787
- package/test/interceptor-chain.test.ts +0 -61
- package/test/node-container.test.ts +0 -121
- package/test/python-session.test.ts +0 -105
- package/test/rate-limit.test.ts +0 -272
- package/test/semantic-search.test.ts +0 -550
- package/test/state.test.ts +0 -121
- package/test/vm-context.test.ts +0 -146
- package/test/vm-loadmodule.test.ts +0 -213
- package/test/websocket-ask.test.ts +0 -101
- package/test-integration/assistant.test.ts +0 -138
- package/test-integration/assistants-manager.test.ts +0 -113
- package/test-integration/claude-code.test.ts +0 -98
- package/test-integration/conversation-history.test.ts +0 -205
- package/test-integration/conversation.test.ts +0 -137
- package/test-integration/elevenlabs.test.ts +0 -55
- package/test-integration/google-services.test.ts +0 -80
- package/test-integration/helpers.ts +0 -89
- package/test-integration/memory.test.ts +0 -204
- package/test-integration/openai-codex.test.ts +0 -93
- package/test-integration/runpod.test.ts +0 -58
- package/test-integration/server-endpoints.test.ts +0 -97
- package/test-integration/telegram.test.ts +0 -46
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'bun:test'
|
|
2
|
-
import { InterceptorChain } from '../src/agi/lib/interceptor-chain'
|
|
3
|
-
|
|
4
|
-
describe('InterceptorChain', () => {
|
|
5
|
-
it('runs interceptors in order then the final', async () => {
|
|
6
|
-
const chain = new InterceptorChain<{ log: string[] }>()
|
|
7
|
-
chain.add(async (ctx, next) => { ctx.log.push('a'); await next() })
|
|
8
|
-
chain.add(async (ctx, next) => { ctx.log.push('b'); await next() })
|
|
9
|
-
|
|
10
|
-
const ctx = { log: [] as string[] }
|
|
11
|
-
await chain.run(ctx, async () => { ctx.log.push('final') })
|
|
12
|
-
|
|
13
|
-
expect(ctx.log).toEqual(['a', 'b', 'final'])
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('skips the final when an interceptor does not call next', async () => {
|
|
17
|
-
const chain = new InterceptorChain<{ log: string[] }>()
|
|
18
|
-
chain.add(async (ctx, _next) => { ctx.log.push('blocker') })
|
|
19
|
-
chain.add(async (ctx, next) => { ctx.log.push('never'); await next() })
|
|
20
|
-
|
|
21
|
-
const ctx = { log: [] as string[] }
|
|
22
|
-
await chain.run(ctx, async () => { ctx.log.push('final') })
|
|
23
|
-
|
|
24
|
-
expect(ctx.log).toEqual(['blocker'])
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('allows interceptors to mutate ctx before and after next', async () => {
|
|
28
|
-
const chain = new InterceptorChain<{ value: number }>()
|
|
29
|
-
chain.add(async (ctx, next) => {
|
|
30
|
-
ctx.value *= 2
|
|
31
|
-
await next()
|
|
32
|
-
ctx.value += 100
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const ctx = { value: 5 }
|
|
36
|
-
await chain.run(ctx, async () => { ctx.value += 1 })
|
|
37
|
-
|
|
38
|
-
expect(ctx.value).toBe(111) // (5*2)=10, final +1=11, after +100=111
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('reports hasInterceptors and size', () => {
|
|
42
|
-
const chain = new InterceptorChain<{}>()
|
|
43
|
-
expect(chain.hasInterceptors).toBe(false)
|
|
44
|
-
expect(chain.size).toBe(0)
|
|
45
|
-
|
|
46
|
-
const fn = async (_ctx: {}, next: () => Promise<void>) => { await next() }
|
|
47
|
-
chain.add(fn)
|
|
48
|
-
expect(chain.hasInterceptors).toBe(true)
|
|
49
|
-
expect(chain.size).toBe(1)
|
|
50
|
-
|
|
51
|
-
chain.remove(fn)
|
|
52
|
-
expect(chain.hasInterceptors).toBe(false)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('runs just the final when no interceptors are registered', async () => {
|
|
56
|
-
const chain = new InterceptorChain<{ ran: boolean }>()
|
|
57
|
-
const ctx = { ran: false }
|
|
58
|
-
await chain.run(ctx, async () => { ctx.ran = true })
|
|
59
|
-
expect(ctx.ran).toBe(true)
|
|
60
|
-
})
|
|
61
|
-
})
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, mock } from 'bun:test'
|
|
2
|
-
import { NodeContainer } from '../src/node/container'
|
|
3
|
-
|
|
4
|
-
describe('NodeContainer', () => {
|
|
5
|
-
it('creates a container with a uuid', () => {
|
|
6
|
-
const c = new NodeContainer()
|
|
7
|
-
expect(c.uuid).toBeDefined()
|
|
8
|
-
expect(typeof c.uuid).toBe('string')
|
|
9
|
-
expect(c.uuid.length).toBeGreaterThan(0)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('has a cwd', () => {
|
|
13
|
-
const c = new NodeContainer()
|
|
14
|
-
expect(c.cwd).toBe(process.cwd())
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('accepts a custom cwd', () => {
|
|
18
|
-
const c = new NodeContainer({ cwd: '/tmp' })
|
|
19
|
-
expect(c.cwd).toBe('/tmp')
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('detects node environment', () => {
|
|
23
|
-
const c = new NodeContainer()
|
|
24
|
-
expect(c.isNode).toBe(true)
|
|
25
|
-
expect(c.isBrowser).toBe(false)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('has state with started=false initially', () => {
|
|
29
|
-
const c = new NodeContainer()
|
|
30
|
-
expect(c.currentState.started).toBe(false)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('start() sets started state and emits event', async () => {
|
|
34
|
-
const c = new NodeContainer()
|
|
35
|
-
const listener = mock()
|
|
36
|
-
c.on('started', listener)
|
|
37
|
-
await c.start()
|
|
38
|
-
expect(c.currentState.started).toBe(true)
|
|
39
|
-
expect(listener).toHaveBeenCalled()
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('has utils with hashObject, stringUtils, uuid, lodash', () => {
|
|
43
|
-
const c = new NodeContainer()
|
|
44
|
-
expect(typeof c.utils.hashObject).toBe('function')
|
|
45
|
-
expect(typeof c.utils.stringUtils.camelCase).toBe('function')
|
|
46
|
-
expect(typeof c.utils.stringUtils.kebabCase).toBe('function')
|
|
47
|
-
expect(typeof c.utils.uuid).toBe('function')
|
|
48
|
-
expect(typeof c.utils.lodash.uniq).toBe('function')
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('utils.hashObject produces deterministic results', () => {
|
|
52
|
-
const c = new NodeContainer()
|
|
53
|
-
const a = c.utils.hashObject({ x: 1, y: 2 })
|
|
54
|
-
const b = c.utils.hashObject({ x: 1, y: 2 })
|
|
55
|
-
expect(a).toBe(b)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('paths.resolve works relative to cwd', () => {
|
|
59
|
-
const c = new NodeContainer({ cwd: '/tmp' })
|
|
60
|
-
expect(c.paths.resolve('foo')).toBe('/tmp/foo')
|
|
61
|
-
expect(c.paths.resolve('foo', 'bar')).toBe('/tmp/foo/bar')
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('paths.join works relative to cwd', () => {
|
|
65
|
-
const c = new NodeContainer({ cwd: '/tmp' })
|
|
66
|
-
expect(c.paths.join('a', 'b')).toBe('/tmp/a/b')
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('bus() creates a new Bus instance', () => {
|
|
70
|
-
const c = new NodeContainer()
|
|
71
|
-
const bus = c.bus()
|
|
72
|
-
expect(typeof bus.on).toBe('function')
|
|
73
|
-
expect(typeof bus.emit).toBe('function')
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('newState() creates a new State instance', () => {
|
|
77
|
-
const c = new NodeContainer()
|
|
78
|
-
const state = c.newState({ count: 0 })
|
|
79
|
-
expect(state.get('count')).toBe(0)
|
|
80
|
-
state.set('count', 5)
|
|
81
|
-
expect(state.get('count')).toBe(5)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('event system works (on, emit, off)', () => {
|
|
85
|
-
const c = new NodeContainer()
|
|
86
|
-
const listener = mock()
|
|
87
|
-
c.on('custom', listener)
|
|
88
|
-
c.emit('custom', 'hello')
|
|
89
|
-
expect(listener).toHaveBeenCalledWith('hello')
|
|
90
|
-
c.off('custom', listener)
|
|
91
|
-
c.emit('custom', 'world')
|
|
92
|
-
expect(listener).toHaveBeenCalledTimes(1)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('waitFor resolves when event fires', async () => {
|
|
96
|
-
const c = new NodeContainer()
|
|
97
|
-
const promise = c.waitFor('ready')
|
|
98
|
-
c.emit('ready', 42)
|
|
99
|
-
expect(await promise).toBe(42)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('each container gets a unique uuid', () => {
|
|
103
|
-
const a = new NodeContainer()
|
|
104
|
-
const b = new NodeContainer()
|
|
105
|
-
expect(a.uuid).not.toBe(b.uuid)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
describe('plugin system', () => {
|
|
109
|
-
it('use() with a plugin object calls attach', () => {
|
|
110
|
-
const c = new NodeContainer()
|
|
111
|
-
const plugin = { attach: mock() }
|
|
112
|
-
c.use(plugin)
|
|
113
|
-
expect(plugin.attach).toHaveBeenCalledWith(c, {})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('use() with an unknown feature string throws', () => {
|
|
117
|
-
const c = new NodeContainer()
|
|
118
|
-
expect(() => c.use('nonexistent_feature_xyz' as any)).toThrow()
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
})
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from 'bun:test'
|
|
2
|
-
|
|
3
|
-
describe('Python Persistent Session', () => {
|
|
4
|
-
let container: any
|
|
5
|
-
let python: any
|
|
6
|
-
|
|
7
|
-
beforeAll(async () => {
|
|
8
|
-
const mod = await import('../src/node.js')
|
|
9
|
-
container = mod.default
|
|
10
|
-
python = container.feature('python', { dir: container.cwd })
|
|
11
|
-
await python.enable()
|
|
12
|
-
await python.startSession()
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
afterAll(async () => {
|
|
16
|
-
if (python?.state?.get('sessionActive')) {
|
|
17
|
-
await python.stopSession()
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('session is active after start', () => {
|
|
22
|
-
expect(python.state.get('sessionActive')).toBe(true)
|
|
23
|
-
expect(python.state.get('sessionId')).toBeTruthy()
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('run() executes code and captures stdout', async () => {
|
|
27
|
-
const result = await python.run('print("hello from bridge")')
|
|
28
|
-
expect(result.ok).toBe(true)
|
|
29
|
-
expect(result.stdout).toContain('hello from bridge')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('state persists across calls', async () => {
|
|
33
|
-
await python.run('x = 42')
|
|
34
|
-
const result = await python.eval('x + 1')
|
|
35
|
-
expect(result).toBe(43)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('variables are injected', async () => {
|
|
39
|
-
const result = await python.run('print(name)', { name: 'luca' })
|
|
40
|
-
expect(result.ok).toBe(true)
|
|
41
|
-
expect(result.stdout).toContain('luca')
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('importModule() works', async () => {
|
|
45
|
-
await python.importModule('json')
|
|
46
|
-
const result = await python.eval('json.dumps({"a": 1})')
|
|
47
|
-
expect(result).toBe('{"a": 1}')
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('call() invokes functions', async () => {
|
|
51
|
-
await python.run('def add(a, b): return a + b')
|
|
52
|
-
const result = await python.call('add', [3, 4])
|
|
53
|
-
expect(result).toBe(7)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('getLocals() returns namespace', async () => {
|
|
57
|
-
await python.run('y = 99')
|
|
58
|
-
const locals = await python.getLocals()
|
|
59
|
-
expect(locals.y).toBe(99)
|
|
60
|
-
expect(locals.x).toBe(42)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('handles errors gracefully without crashing the session', async () => {
|
|
64
|
-
const result = await python.run('raise ValueError("test error")')
|
|
65
|
-
expect(result.ok).toBe(false)
|
|
66
|
-
expect(result.error).toContain('test error')
|
|
67
|
-
// Session should still be alive
|
|
68
|
-
const check = await python.run('print("still alive")')
|
|
69
|
-
expect(check.ok).toBe(true)
|
|
70
|
-
expect(check.stdout).toContain('still alive')
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('resetSession() clears state', async () => {
|
|
74
|
-
await python.run('z = 123')
|
|
75
|
-
await python.resetSession()
|
|
76
|
-
const result = await python.run('print(z)')
|
|
77
|
-
expect(result.ok).toBe(false)
|
|
78
|
-
expect(result.error).toContain('is not defined')
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('stopSession() cleans up', async () => {
|
|
82
|
-
await python.stopSession()
|
|
83
|
-
expect(python.state.get('sessionActive')).toBe(false)
|
|
84
|
-
expect(python.state.get('sessionId')).toBeNull()
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('can start a new session after stopping', async () => {
|
|
88
|
-
await python.startSession()
|
|
89
|
-
expect(python.state.get('sessionActive')).toBe(true)
|
|
90
|
-
const result = await python.run('print("fresh session")')
|
|
91
|
-
expect(result.ok).toBe(true)
|
|
92
|
-
expect(result.stdout).toContain('fresh session')
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
describe('Python backward compatibility', () => {
|
|
97
|
-
it('execute() still works without a session', async () => {
|
|
98
|
-
const mod = await import('../src/node.js')
|
|
99
|
-
const container = mod.default
|
|
100
|
-
const python = container.feature('python', { dir: container.cwd })
|
|
101
|
-
await python.enable()
|
|
102
|
-
const result = await python.execute('print("stateless")')
|
|
103
|
-
expect(result.stdout).toContain('stateless')
|
|
104
|
-
})
|
|
105
|
-
})
|
package/test/rate-limit.test.ts
DELETED
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import { NodeContainer } from '../src/node/container'
|
|
2
|
-
import { Endpoint, type EndpointModule } from '../src/endpoint'
|
|
3
|
-
|
|
4
|
-
/** Creates a minimal mock Express app that records registered routes and lets us fire requests. */
|
|
5
|
-
function createMockApp() {
|
|
6
|
-
const routes: Record<string, Function> = {}
|
|
7
|
-
|
|
8
|
-
const app: any = {}
|
|
9
|
-
for (const method of ['get', 'post', 'put', 'patch', 'delete']) {
|
|
10
|
-
app[method] = (path: string, handler: Function) => {
|
|
11
|
-
routes[`${method}:${path}`] = handler
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Simulate a request hitting a registered route */
|
|
16
|
-
async function request(method: string, path: string, options: { ip?: string; query?: any; body?: any; params?: any } = {}) {
|
|
17
|
-
const handler = routes[`${method}:${path}`]
|
|
18
|
-
if (!handler) throw new Error(`No route for ${method} ${path}`)
|
|
19
|
-
|
|
20
|
-
let statusCode = 200
|
|
21
|
-
let responseBody: any = null
|
|
22
|
-
let headersSent = false
|
|
23
|
-
|
|
24
|
-
const req = {
|
|
25
|
-
ip: options.ip || '127.0.0.1',
|
|
26
|
-
socket: { remoteAddress: options.ip || '127.0.0.1' },
|
|
27
|
-
query: options.query || {},
|
|
28
|
-
body: options.body || {},
|
|
29
|
-
params: options.params || {},
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const res = {
|
|
33
|
-
status(code: number) {
|
|
34
|
-
statusCode = code
|
|
35
|
-
return res
|
|
36
|
-
},
|
|
37
|
-
json(body: any) {
|
|
38
|
-
responseBody = body
|
|
39
|
-
headersSent = true
|
|
40
|
-
},
|
|
41
|
-
get headersSent() {
|
|
42
|
-
return headersSent
|
|
43
|
-
},
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
await handler(req, res)
|
|
47
|
-
return { status: statusCode, body: responseBody }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return { app, routes, request }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function createEndpoint(mod: EndpointModule) {
|
|
54
|
-
const c = new NodeContainer()
|
|
55
|
-
const endpoint = new Endpoint({ path: mod.path }, c.context)
|
|
56
|
-
endpoint.load(mod)
|
|
57
|
-
return endpoint
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
describe('Endpoint Rate Limiting', () => {
|
|
61
|
-
it('allows requests under the limit', async () => {
|
|
62
|
-
const { app, request } = createMockApp()
|
|
63
|
-
const endpoint = createEndpoint({
|
|
64
|
-
path: '/api/test',
|
|
65
|
-
rateLimit: { maxRequests: 5 },
|
|
66
|
-
get: async () => ({ ok: true }),
|
|
67
|
-
})
|
|
68
|
-
endpoint.mount(app)
|
|
69
|
-
|
|
70
|
-
for (let i = 0; i < 5; i++) {
|
|
71
|
-
const res = await request('get', '/api/test')
|
|
72
|
-
expect(res.status).toBe(200)
|
|
73
|
-
expect(res.body).toEqual({ ok: true })
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('returns 429 when rate limit is exceeded', async () => {
|
|
78
|
-
const { app, request } = createMockApp()
|
|
79
|
-
const endpoint = createEndpoint({
|
|
80
|
-
path: '/api/test',
|
|
81
|
-
rateLimit: { maxRequests: 3 },
|
|
82
|
-
get: async () => ({ ok: true }),
|
|
83
|
-
})
|
|
84
|
-
endpoint.mount(app)
|
|
85
|
-
|
|
86
|
-
// First 3 should succeed
|
|
87
|
-
for (let i = 0; i < 3; i++) {
|
|
88
|
-
const res = await request('get', '/api/test')
|
|
89
|
-
expect(res.status).toBe(200)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 4th should be rate limited
|
|
93
|
-
const res = await request('get', '/api/test')
|
|
94
|
-
expect(res.status).toBe(429)
|
|
95
|
-
expect(res.body).toEqual({ error: 'Too Many Requests' })
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('emits error event when rate limited', async () => {
|
|
99
|
-
const { app, request } = createMockApp()
|
|
100
|
-
const endpoint = createEndpoint({
|
|
101
|
-
path: '/api/test',
|
|
102
|
-
rateLimit: { maxRequests: 1 },
|
|
103
|
-
get: async () => ({ ok: true }),
|
|
104
|
-
})
|
|
105
|
-
endpoint.mount(app)
|
|
106
|
-
|
|
107
|
-
const errors: any[] = []
|
|
108
|
-
endpoint.on('error', (err: any) => errors.push(err))
|
|
109
|
-
|
|
110
|
-
await request('get', '/api/test') // allowed
|
|
111
|
-
await request('get', '/api/test') // rate limited
|
|
112
|
-
|
|
113
|
-
expect(errors).toHaveLength(1)
|
|
114
|
-
expect(errors[0].message).toContain('Rate limit exceeded')
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('tracks rate limits per IP address', async () => {
|
|
118
|
-
const { app, request } = createMockApp()
|
|
119
|
-
const endpoint = createEndpoint({
|
|
120
|
-
path: '/api/test',
|
|
121
|
-
rateLimit: { maxRequests: 2 },
|
|
122
|
-
get: async () => ({ ok: true }),
|
|
123
|
-
})
|
|
124
|
-
endpoint.mount(app)
|
|
125
|
-
|
|
126
|
-
// IP A uses its 2 requests
|
|
127
|
-
await request('get', '/api/test', { ip: '10.0.0.1' })
|
|
128
|
-
await request('get', '/api/test', { ip: '10.0.0.1' })
|
|
129
|
-
const limitedA = await request('get', '/api/test', { ip: '10.0.0.1' })
|
|
130
|
-
expect(limitedA.status).toBe(429)
|
|
131
|
-
|
|
132
|
-
// IP B should still be allowed
|
|
133
|
-
const okB = await request('get', '/api/test', { ip: '10.0.0.2' })
|
|
134
|
-
expect(okB.status).toBe(200)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('supports per-method rate limits that override endpoint-level', async () => {
|
|
138
|
-
const { app, request } = createMockApp()
|
|
139
|
-
const endpoint = createEndpoint({
|
|
140
|
-
path: '/api/test',
|
|
141
|
-
rateLimit: { maxRequests: 10 }, // general: 10 rps
|
|
142
|
-
getRateLimit: { maxRequests: 2 }, // GET: 2 rps
|
|
143
|
-
get: async () => ({ method: 'get' }),
|
|
144
|
-
post: async () => ({ method: 'post' }),
|
|
145
|
-
})
|
|
146
|
-
endpoint.mount(app)
|
|
147
|
-
|
|
148
|
-
// GET should be limited at 2
|
|
149
|
-
await request('get', '/api/test')
|
|
150
|
-
await request('get', '/api/test')
|
|
151
|
-
const limited = await request('get', '/api/test')
|
|
152
|
-
expect(limited.status).toBe(429)
|
|
153
|
-
|
|
154
|
-
// POST should still use the general limit of 10
|
|
155
|
-
for (let i = 0; i < 10; i++) {
|
|
156
|
-
const res = await request('post', '/api/test')
|
|
157
|
-
expect(res.status).toBe(200)
|
|
158
|
-
}
|
|
159
|
-
const postLimited = await request('post', '/api/test')
|
|
160
|
-
expect(postLimited.status).toBe(429)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
it('does not rate limit when no rateLimit is set', async () => {
|
|
164
|
-
const { app, request } = createMockApp()
|
|
165
|
-
const endpoint = createEndpoint({
|
|
166
|
-
path: '/api/test',
|
|
167
|
-
get: async () => ({ ok: true }),
|
|
168
|
-
})
|
|
169
|
-
endpoint.mount(app)
|
|
170
|
-
|
|
171
|
-
// Should handle many requests without 429
|
|
172
|
-
for (let i = 0; i < 100; i++) {
|
|
173
|
-
const res = await request('get', '/api/test')
|
|
174
|
-
expect(res.status).toBe(200)
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
it('resets the window after time passes', async () => {
|
|
179
|
-
const { app, request } = createMockApp()
|
|
180
|
-
const endpoint = createEndpoint({
|
|
181
|
-
path: '/api/test',
|
|
182
|
-
rateLimit: { maxRequests: 2, windowSeconds: 0.1 },
|
|
183
|
-
get: async () => ({ ok: true }),
|
|
184
|
-
})
|
|
185
|
-
endpoint.mount(app)
|
|
186
|
-
|
|
187
|
-
await request('get', '/api/test')
|
|
188
|
-
await request('get', '/api/test')
|
|
189
|
-
const limited = await request('get', '/api/test')
|
|
190
|
-
expect(limited.status).toBe(429)
|
|
191
|
-
|
|
192
|
-
// Wait for the 100ms window to expire
|
|
193
|
-
await new Promise((resolve) => setTimeout(resolve, 150))
|
|
194
|
-
|
|
195
|
-
const allowed = await request('get', '/api/test')
|
|
196
|
-
expect(allowed.status).toBe(200)
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
it('rateLimitFor returns correct config', () => {
|
|
200
|
-
const endpoint = createEndpoint({
|
|
201
|
-
path: '/api/test',
|
|
202
|
-
rateLimit: { maxRequests: 10 },
|
|
203
|
-
postRateLimit: { maxRequests: 2 },
|
|
204
|
-
get: async () => ({}),
|
|
205
|
-
post: async () => ({}),
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
// GET falls back to endpoint-level rateLimit
|
|
209
|
-
expect(endpoint.rateLimitFor('get')).toEqual({ maxRequests: 10 })
|
|
210
|
-
// POST uses its own override
|
|
211
|
-
expect(endpoint.rateLimitFor('post')).toEqual({ maxRequests: 2 })
|
|
212
|
-
// DELETE has no handler but we can still query the config
|
|
213
|
-
expect(endpoint.rateLimitFor('delete')).toEqual({ maxRequests: 10 })
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
it('rateLimiter.reset() clears all tracking', async () => {
|
|
217
|
-
const { app, request } = createMockApp()
|
|
218
|
-
const endpoint = createEndpoint({
|
|
219
|
-
path: '/api/test',
|
|
220
|
-
rateLimit: { maxRequests: 1 },
|
|
221
|
-
get: async () => ({ ok: true }),
|
|
222
|
-
})
|
|
223
|
-
endpoint.mount(app)
|
|
224
|
-
|
|
225
|
-
await request('get', '/api/test')
|
|
226
|
-
const limited = await request('get', '/api/test')
|
|
227
|
-
expect(limited.status).toBe(429)
|
|
228
|
-
|
|
229
|
-
// Reset the limiter
|
|
230
|
-
endpoint.rateLimiter.reset()
|
|
231
|
-
|
|
232
|
-
const allowed = await request('get', '/api/test')
|
|
233
|
-
expect(allowed.status).toBe(200)
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
it('does not increment requestCount on rate-limited requests', async () => {
|
|
237
|
-
const { app, request } = createMockApp()
|
|
238
|
-
const endpoint = createEndpoint({
|
|
239
|
-
path: '/api/test',
|
|
240
|
-
rateLimit: { maxRequests: 2 },
|
|
241
|
-
get: async () => ({ ok: true }),
|
|
242
|
-
})
|
|
243
|
-
endpoint.mount(app)
|
|
244
|
-
|
|
245
|
-
await request('get', '/api/test')
|
|
246
|
-
await request('get', '/api/test')
|
|
247
|
-
await request('get', '/api/test') // rate limited
|
|
248
|
-
|
|
249
|
-
expect(endpoint.state.get('requestCount')).toBe(2)
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
it('includes 429 in OpenAPI spec when rate limit is configured', () => {
|
|
253
|
-
const endpoint = createEndpoint({
|
|
254
|
-
path: '/api/test',
|
|
255
|
-
rateLimit: { maxRequests: 5 },
|
|
256
|
-
get: async () => ({}),
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
const spec = endpoint.toOpenAPIPathItem()
|
|
260
|
-
expect(spec.get.responses['429']).toEqual({ description: 'Rate limit exceeded' })
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
it('omits 429 from OpenAPI spec when no rate limit is configured', () => {
|
|
264
|
-
const endpoint = createEndpoint({
|
|
265
|
-
path: '/api/test',
|
|
266
|
-
get: async () => ({}),
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
const spec = endpoint.toOpenAPIPathItem()
|
|
270
|
-
expect(spec.get.responses['429']).toBeUndefined()
|
|
271
|
-
})
|
|
272
|
-
})
|