@build-astron-co/nimbus 0.4.1 → 0.4.3
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/CHANGELOG.md +268 -89
- package/README.md +26 -567
- package/dist/src/agent/compaction-agent.js +24 -12
- package/dist/src/agent/context-manager.js +2 -1
- package/dist/src/agent/expand-files.js +2 -1
- package/dist/src/agent/loop.js +71 -33
- package/dist/src/agent/permissions.js +4 -2
- package/dist/src/agent/system-prompt.js +34 -17
- package/dist/src/app.js +1 -1
- package/dist/src/auth/keychain.js +8 -4
- package/dist/src/auth/store.js +70 -107
- package/dist/src/cli/init.js +35 -19
- package/dist/src/cli/run.js +18 -10
- package/dist/src/cli/serve.js +4 -2
- package/dist/src/cli.js +52 -11
- package/dist/src/commands/alias.js +5 -3
- package/dist/src/commands/audit/index.js +2 -1
- package/dist/src/commands/aws-terraform.js +36 -18
- package/dist/src/commands/completions.js +1 -1
- package/dist/src/commands/config.js +3 -2
- package/dist/src/commands/connect-github.js +92 -0
- package/dist/src/commands/cost/index.js +3 -2
- package/dist/src/commands/deploy.js +15 -10
- package/dist/src/commands/doctor.js +9 -6
- package/dist/src/commands/drift/index.js +2 -1
- package/dist/src/commands/export.js +5 -3
- package/dist/src/commands/generate-terraform.js +110 -2
- package/dist/src/commands/import.js +3 -3
- package/dist/src/commands/incident.js +10 -5
- package/dist/src/commands/login.js +8 -93
- package/dist/src/commands/logs.js +16 -8
- package/dist/src/commands/onboarding.js +6 -4
- package/dist/src/commands/pipeline.js +6 -3
- package/dist/src/commands/plugin.js +3 -2
- package/dist/src/commands/profile.js +27 -14
- package/dist/src/commands/questionnaire.js +1 -1
- package/dist/src/commands/rollback.js +3 -2
- package/dist/src/commands/rollout.js +5 -3
- package/dist/src/commands/runbook.js +17 -10
- package/dist/src/commands/schedule.js +10 -5
- package/dist/src/commands/status.js +2 -1
- package/dist/src/commands/team-context.js +12 -7
- package/dist/src/commands/template.js +1 -1
- package/dist/src/commands/tf/index.js +6 -3
- package/dist/src/commands/upgrade.js +5 -3
- package/dist/src/commands/version.js +6 -3
- package/dist/src/commands/watch.js +6 -3
- package/dist/src/compat/sqlite.js +5 -3
- package/dist/src/config/mode-store.js +2 -1
- package/dist/src/config/profiles.js +4 -2
- package/dist/src/config/types.js +2 -1
- package/dist/src/engine/executor.js +8 -4
- package/dist/src/engine/planner.js +9 -5
- package/dist/src/llm/providers/anthropic.js +6 -3
- package/dist/src/llm/providers/ollama.js +1 -1
- package/dist/src/llm/router.js +22 -7
- package/dist/src/nimbus.js +1 -0
- package/dist/src/sessions/manager.js +6 -3
- package/dist/src/sharing/viewer.js +2 -1
- package/dist/src/tools/file-ops.js +1 -2
- package/dist/src/tools/schemas/devops.js +197 -108
- package/dist/src/tools/schemas/standard.js +1 -1
- package/dist/src/ui/App.js +25 -13
- package/dist/src/ui/FileDiffModal.js +22 -11
- package/dist/src/ui/HelpModal.js +2 -1
- package/dist/src/ui/InputBox.js +6 -3
- package/dist/src/ui/MessageList.js +40 -20
- package/dist/src/ui/TerminalPane.js +2 -1
- package/dist/src/ui/ToolCallDisplay.js +12 -6
- package/dist/src/ui/TreePane.js +2 -1
- package/dist/src/ui/ink/index.js +37 -21
- package/dist/src/version.js +1 -1
- package/dist/src/watcher/index.js +8 -4
- package/package.json +3 -5
- package/src/__tests__/alias.test.ts +0 -133
- package/src/__tests__/app.test.ts +0 -76
- package/src/__tests__/audit.test.ts +0 -877
- package/src/__tests__/circuit-breaker.test.ts +0 -116
- package/src/__tests__/cli-run.test.ts +0 -351
- package/src/__tests__/compat-sqlite.test.ts +0 -68
- package/src/__tests__/context-manager.test.ts +0 -632
- package/src/__tests__/context.test.ts +0 -242
- package/src/__tests__/devops-terminal-gaps.test.ts +0 -718
- package/src/__tests__/doctor.test.ts +0 -48
- package/src/__tests__/enterprise.test.ts +0 -401
- package/src/__tests__/export.test.ts +0 -236
- package/src/__tests__/gap-11-18-20.test.ts +0 -958
- package/src/__tests__/generator.test.ts +0 -433
- package/src/__tests__/helm-streaming.test.ts +0 -127
- package/src/__tests__/hooks.test.ts +0 -582
- package/src/__tests__/incident.test.ts +0 -179
- package/src/__tests__/init.test.ts +0 -487
- package/src/__tests__/intent-parser.test.ts +0 -229
- package/src/__tests__/llm-router.test.ts +0 -209
- package/src/__tests__/logs.test.ts +0 -107
- package/src/__tests__/loop-errors.test.ts +0 -244
- package/src/__tests__/lsp.test.ts +0 -293
- package/src/__tests__/modes.test.ts +0 -336
- package/src/__tests__/perf-optimizations.test.ts +0 -847
- package/src/__tests__/permissions.test.ts +0 -338
- package/src/__tests__/pipeline.test.ts +0 -50
- package/src/__tests__/polish-phase3.test.ts +0 -340
- package/src/__tests__/profile.test.ts +0 -237
- package/src/__tests__/rollback.test.ts +0 -83
- package/src/__tests__/runbook.test.ts +0 -219
- package/src/__tests__/schedule.test.ts +0 -206
- package/src/__tests__/serve.test.ts +0 -275
- package/src/__tests__/sessions.test.ts +0 -322
- package/src/__tests__/sharing.test.ts +0 -340
- package/src/__tests__/snapshots.test.ts +0 -581
- package/src/__tests__/standalone-migration.test.ts +0 -199
- package/src/__tests__/state-db.test.ts +0 -334
- package/src/__tests__/status.test.ts +0 -158
- package/src/__tests__/stream-with-tools.test.ts +0 -778
- package/src/__tests__/subagents.test.ts +0 -176
- package/src/__tests__/system-prompt.test.ts +0 -248
- package/src/__tests__/terminal-gap-v2.test.ts +0 -395
- package/src/__tests__/terminal-parity.test.ts +0 -393
- package/src/__tests__/tf-apply.test.ts +0 -187
- package/src/__tests__/tool-converter.test.ts +0 -256
- package/src/__tests__/tool-schemas.test.ts +0 -602
- package/src/__tests__/tools.test.ts +0 -144
- package/src/__tests__/version-json.test.ts +0 -184
- package/src/__tests__/version.test.ts +0 -49
- package/src/__tests__/watch.test.ts +0 -129
- package/src/agent/compaction-agent.ts +0 -266
- package/src/agent/context-manager.ts +0 -499
- package/src/agent/context.ts +0 -427
- package/src/agent/deploy-preview.ts +0 -487
- package/src/agent/expand-files.ts +0 -108
- package/src/agent/index.ts +0 -68
- package/src/agent/loop.ts +0 -1998
- package/src/agent/modes.ts +0 -429
- package/src/agent/permissions.ts +0 -513
- package/src/agent/subagents/base.ts +0 -116
- package/src/agent/subagents/cost.ts +0 -51
- package/src/agent/subagents/explore.ts +0 -42
- package/src/agent/subagents/general.ts +0 -54
- package/src/agent/subagents/index.ts +0 -102
- package/src/agent/subagents/infra.ts +0 -59
- package/src/agent/subagents/security.ts +0 -69
- package/src/agent/system-prompt.ts +0 -990
- package/src/app.ts +0 -180
- package/src/audit/activity-log.ts +0 -290
- package/src/audit/compliance-checker.ts +0 -540
- package/src/audit/cost-tracker.ts +0 -318
- package/src/audit/index.ts +0 -23
- package/src/audit/security-scanner.ts +0 -641
- package/src/auth/guard.ts +0 -75
- package/src/auth/index.ts +0 -56
- package/src/auth/keychain.ts +0 -82
- package/src/auth/oauth.ts +0 -465
- package/src/auth/providers.ts +0 -470
- package/src/auth/sso.ts +0 -113
- package/src/auth/store.ts +0 -505
- package/src/auth/types.ts +0 -187
- package/src/build.ts +0 -141
- package/src/cli/index.ts +0 -16
- package/src/cli/init.ts +0 -1227
- package/src/cli/openapi-spec.ts +0 -356
- package/src/cli/run.ts +0 -628
- package/src/cli/serve-auth.ts +0 -80
- package/src/cli/serve.ts +0 -539
- package/src/cli/web.ts +0 -71
- package/src/cli.ts +0 -1728
- package/src/clients/core-engine-client.ts +0 -227
- package/src/clients/enterprise-client.ts +0 -334
- package/src/clients/generator-client.ts +0 -351
- package/src/clients/git-client.ts +0 -627
- package/src/clients/github-client.ts +0 -410
- package/src/clients/helm-client.ts +0 -504
- package/src/clients/index.ts +0 -80
- package/src/clients/k8s-client.ts +0 -497
- package/src/clients/llm-client.ts +0 -161
- package/src/clients/rest-client.ts +0 -130
- package/src/clients/service-discovery.ts +0 -38
- package/src/clients/terraform-client.ts +0 -482
- package/src/clients/tools-client.ts +0 -1843
- package/src/clients/ws-client.ts +0 -115
- package/src/commands/alias.ts +0 -100
- package/src/commands/analyze/index.ts +0 -352
- package/src/commands/apply/helm.ts +0 -473
- package/src/commands/apply/index.ts +0 -213
- package/src/commands/apply/k8s.ts +0 -454
- package/src/commands/apply/terraform.ts +0 -582
- package/src/commands/ask.ts +0 -167
- package/src/commands/audit/index.ts +0 -357
- package/src/commands/auth-cloud.ts +0 -407
- package/src/commands/auth-list.ts +0 -134
- package/src/commands/auth-profile.ts +0 -121
- package/src/commands/auth-refresh.ts +0 -187
- package/src/commands/auth-status.ts +0 -141
- package/src/commands/aws/ec2.ts +0 -501
- package/src/commands/aws/iam.ts +0 -397
- package/src/commands/aws/index.ts +0 -133
- package/src/commands/aws/lambda.ts +0 -396
- package/src/commands/aws/rds.ts +0 -439
- package/src/commands/aws/s3.ts +0 -439
- package/src/commands/aws/vpc.ts +0 -393
- package/src/commands/aws-discover.ts +0 -542
- package/src/commands/aws-terraform.ts +0 -755
- package/src/commands/azure/aks.ts +0 -376
- package/src/commands/azure/functions.ts +0 -253
- package/src/commands/azure/index.ts +0 -116
- package/src/commands/azure/storage.ts +0 -478
- package/src/commands/azure/vm.ts +0 -355
- package/src/commands/billing/index.ts +0 -256
- package/src/commands/chat.ts +0 -320
- package/src/commands/completions.ts +0 -268
- package/src/commands/config.ts +0 -372
- package/src/commands/cost/cloud-cost-estimator.ts +0 -266
- package/src/commands/cost/estimator.ts +0 -79
- package/src/commands/cost/index.ts +0 -810
- package/src/commands/cost/parsers/terraform.ts +0 -273
- package/src/commands/cost/parsers/types.ts +0 -25
- package/src/commands/cost/pricing/aws.ts +0 -544
- package/src/commands/cost/pricing/azure.ts +0 -499
- package/src/commands/cost/pricing/gcp.ts +0 -396
- package/src/commands/cost/pricing/index.ts +0 -40
- package/src/commands/demo.ts +0 -250
- package/src/commands/deploy.ts +0 -260
- package/src/commands/doctor.ts +0 -1386
- package/src/commands/drift/index.ts +0 -787
- package/src/commands/explain.ts +0 -277
- package/src/commands/export.ts +0 -146
- package/src/commands/feedback.ts +0 -389
- package/src/commands/fix.ts +0 -324
- package/src/commands/fs/index.ts +0 -402
- package/src/commands/gcp/compute.ts +0 -325
- package/src/commands/gcp/functions.ts +0 -271
- package/src/commands/gcp/gke.ts +0 -438
- package/src/commands/gcp/iam.ts +0 -344
- package/src/commands/gcp/index.ts +0 -129
- package/src/commands/gcp/storage.ts +0 -284
- package/src/commands/generate-helm.ts +0 -1249
- package/src/commands/generate-k8s.ts +0 -1508
- package/src/commands/generate-terraform.ts +0 -1202
- package/src/commands/gh/index.ts +0 -863
- package/src/commands/git/index.ts +0 -1343
- package/src/commands/helm/index.ts +0 -1126
- package/src/commands/help.ts +0 -715
- package/src/commands/history.ts +0 -149
- package/src/commands/import.ts +0 -868
- package/src/commands/incident.ts +0 -166
- package/src/commands/index.ts +0 -367
- package/src/commands/init.ts +0 -1051
- package/src/commands/k8s/index.ts +0 -1137
- package/src/commands/login.ts +0 -716
- package/src/commands/logout.ts +0 -83
- package/src/commands/logs.ts +0 -167
- package/src/commands/onboarding.ts +0 -405
- package/src/commands/pipeline.ts +0 -186
- package/src/commands/plan/display.ts +0 -279
- package/src/commands/plan/index.ts +0 -599
- package/src/commands/plugin.ts +0 -398
- package/src/commands/preview.ts +0 -452
- package/src/commands/profile.ts +0 -342
- package/src/commands/questionnaire.ts +0 -1172
- package/src/commands/resume.ts +0 -47
- package/src/commands/rollback.ts +0 -315
- package/src/commands/rollout.ts +0 -88
- package/src/commands/runbook.ts +0 -346
- package/src/commands/schedule.ts +0 -236
- package/src/commands/status.ts +0 -252
- package/src/commands/team/index.ts +0 -346
- package/src/commands/team-context.ts +0 -220
- package/src/commands/template.ts +0 -233
- package/src/commands/tf/index.ts +0 -1093
- package/src/commands/upgrade.ts +0 -607
- package/src/commands/usage/index.ts +0 -134
- package/src/commands/version.ts +0 -174
- package/src/commands/watch.ts +0 -153
- package/src/compat/index.ts +0 -2
- package/src/compat/runtime.ts +0 -12
- package/src/compat/sqlite.ts +0 -177
- package/src/config/index.ts +0 -17
- package/src/config/manager.ts +0 -530
- package/src/config/mode-store.ts +0 -62
- package/src/config/profiles.ts +0 -84
- package/src/config/safety-policy.ts +0 -358
- package/src/config/schema.ts +0 -125
- package/src/config/types.ts +0 -609
- package/src/config/workspace-state.ts +0 -53
- package/src/context/context-db.ts +0 -199
- package/src/demo/index.ts +0 -349
- package/src/demo/scenarios/full-journey.ts +0 -229
- package/src/demo/scenarios/getting-started.ts +0 -127
- package/src/demo/scenarios/helm-release.ts +0 -341
- package/src/demo/scenarios/k8s-deployment.ts +0 -194
- package/src/demo/scenarios/terraform-vpc.ts +0 -170
- package/src/demo/types.ts +0 -92
- package/src/engine/cost-estimator.ts +0 -480
- package/src/engine/diagram-generator.ts +0 -256
- package/src/engine/drift-detector.ts +0 -902
- package/src/engine/executor.ts +0 -1066
- package/src/engine/index.ts +0 -76
- package/src/engine/orchestrator.ts +0 -636
- package/src/engine/planner.ts +0 -787
- package/src/engine/safety.ts +0 -743
- package/src/engine/verifier.ts +0 -770
- package/src/enterprise/audit.ts +0 -348
- package/src/enterprise/auth.ts +0 -270
- package/src/enterprise/billing.ts +0 -822
- package/src/enterprise/index.ts +0 -17
- package/src/enterprise/teams.ts +0 -443
- package/src/generator/best-practices.ts +0 -1608
- package/src/generator/helm.ts +0 -630
- package/src/generator/index.ts +0 -37
- package/src/generator/intent-parser.ts +0 -514
- package/src/generator/kubernetes.ts +0 -976
- package/src/generator/terraform.ts +0 -1875
- package/src/history/index.ts +0 -8
- package/src/history/manager.ts +0 -250
- package/src/history/types.ts +0 -34
- package/src/hooks/config.ts +0 -432
- package/src/hooks/engine.ts +0 -392
- package/src/hooks/index.ts +0 -4
- package/src/llm/auth-bridge.ts +0 -198
- package/src/llm/circuit-breaker.ts +0 -140
- package/src/llm/config-loader.ts +0 -201
- package/src/llm/cost-calculator.ts +0 -171
- package/src/llm/index.ts +0 -8
- package/src/llm/model-aliases.ts +0 -115
- package/src/llm/provider-registry.ts +0 -63
- package/src/llm/providers/anthropic.ts +0 -462
- package/src/llm/providers/bedrock.ts +0 -477
- package/src/llm/providers/google.ts +0 -405
- package/src/llm/providers/ollama.ts +0 -767
- package/src/llm/providers/openai-compatible.ts +0 -340
- package/src/llm/providers/openai.ts +0 -328
- package/src/llm/providers/openrouter.ts +0 -338
- package/src/llm/router.ts +0 -1104
- package/src/llm/types.ts +0 -232
- package/src/lsp/client.ts +0 -298
- package/src/lsp/languages.ts +0 -119
- package/src/lsp/manager.ts +0 -294
- package/src/mcp/client.ts +0 -402
- package/src/mcp/index.ts +0 -5
- package/src/mcp/manager.ts +0 -133
- package/src/nimbus.ts +0 -233
- package/src/plugins/index.ts +0 -27
- package/src/plugins/loader.ts +0 -334
- package/src/plugins/manager.ts +0 -376
- package/src/plugins/types.ts +0 -284
- package/src/scanners/cicd-scanner.ts +0 -258
- package/src/scanners/cloud-scanner.ts +0 -466
- package/src/scanners/framework-scanner.ts +0 -469
- package/src/scanners/iac-scanner.ts +0 -388
- package/src/scanners/index.ts +0 -539
- package/src/scanners/language-scanner.ts +0 -276
- package/src/scanners/package-manager-scanner.ts +0 -277
- package/src/scanners/types.ts +0 -172
- package/src/sessions/manager.ts +0 -472
- package/src/sessions/types.ts +0 -44
- package/src/sharing/sync.ts +0 -300
- package/src/sharing/viewer.ts +0 -163
- package/src/snapshots/index.ts +0 -2
- package/src/snapshots/manager.ts +0 -530
- package/src/state/artifacts.ts +0 -147
- package/src/state/audit.ts +0 -137
- package/src/state/billing.ts +0 -240
- package/src/state/checkpoints.ts +0 -117
- package/src/state/config.ts +0 -67
- package/src/state/conversations.ts +0 -14
- package/src/state/credentials.ts +0 -154
- package/src/state/db.ts +0 -58
- package/src/state/index.ts +0 -26
- package/src/state/messages.ts +0 -115
- package/src/state/projects.ts +0 -123
- package/src/state/schema.ts +0 -236
- package/src/state/sessions.ts +0 -147
- package/src/state/teams.ts +0 -200
- package/src/telemetry.ts +0 -108
- package/src/tools/aws-ops.ts +0 -952
- package/src/tools/azure-ops.ts +0 -579
- package/src/tools/file-ops.ts +0 -615
- package/src/tools/gcp-ops.ts +0 -625
- package/src/tools/git-ops.ts +0 -773
- package/src/tools/github-ops.ts +0 -799
- package/src/tools/helm-ops.ts +0 -943
- package/src/tools/index.ts +0 -17
- package/src/tools/k8s-ops.ts +0 -819
- package/src/tools/schemas/converter.ts +0 -184
- package/src/tools/schemas/devops.ts +0 -3502
- package/src/tools/schemas/index.ts +0 -73
- package/src/tools/schemas/standard.ts +0 -1148
- package/src/tools/schemas/types.ts +0 -735
- package/src/tools/spawn-exec.ts +0 -148
- package/src/tools/terraform-ops.ts +0 -862
- package/src/types/ambient.d.ts +0 -193
- package/src/types/config.ts +0 -83
- package/src/types/drift.ts +0 -116
- package/src/types/enterprise.ts +0 -335
- package/src/types/index.ts +0 -20
- package/src/types/plan.ts +0 -44
- package/src/types/request.ts +0 -65
- package/src/types/response.ts +0 -54
- package/src/types/service.ts +0 -51
- package/src/ui/App.tsx +0 -2114
- package/src/ui/DeployPreview.tsx +0 -174
- package/src/ui/FileDiffModal.tsx +0 -162
- package/src/ui/Header.tsx +0 -131
- package/src/ui/HelpModal.tsx +0 -57
- package/src/ui/InputBox.tsx +0 -503
- package/src/ui/MessageList.tsx +0 -1032
- package/src/ui/PermissionPrompt.tsx +0 -163
- package/src/ui/StatusBar.tsx +0 -277
- package/src/ui/TerminalPane.tsx +0 -84
- package/src/ui/ToolCallDisplay.tsx +0 -643
- package/src/ui/TreePane.tsx +0 -132
- package/src/ui/chat-ui.ts +0 -850
- package/src/ui/index.ts +0 -33
- package/src/ui/ink/index.ts +0 -1444
- package/src/ui/streaming.ts +0 -176
- package/src/ui/theme.ts +0 -104
- package/src/ui/types.ts +0 -75
- package/src/utils/analytics.ts +0 -72
- package/src/utils/cost-warning.ts +0 -27
- package/src/utils/env.ts +0 -46
- package/src/utils/errors.ts +0 -69
- package/src/utils/event-bus.ts +0 -38
- package/src/utils/index.ts +0 -24
- package/src/utils/logger.ts +0 -171
- package/src/utils/rate-limiter.ts +0 -121
- package/src/utils/service-auth.ts +0 -49
- package/src/utils/validation.ts +0 -53
- package/src/version.ts +0 -4
- package/src/watcher/index.ts +0 -214
- package/src/wizard/approval.ts +0 -383
- package/src/wizard/index.ts +0 -25
- package/src/wizard/prompts.ts +0 -338
- package/src/wizard/types.ts +0 -172
- package/src/wizard/ui.ts +0 -556
- package/src/wizard/wizard.ts +0 -304
- package/tsconfig.json +0 -24
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Multi-Session Manager
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
-
import { Database } from '../compat/sqlite';
|
|
6
|
-
import { SessionManager } from '../sessions/manager';
|
|
7
|
-
import type { SessionEvent } from '../sessions/types';
|
|
8
|
-
|
|
9
|
-
describe('SessionManager', () => {
|
|
10
|
-
let db: Database;
|
|
11
|
-
let manager: SessionManager;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
db = new Database(':memory:');
|
|
15
|
-
db.exec('PRAGMA journal_mode=WAL');
|
|
16
|
-
db.exec('PRAGMA foreign_keys=ON');
|
|
17
|
-
SessionManager.resetInstance();
|
|
18
|
-
manager = new SessionManager(db);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
db.close();
|
|
23
|
-
SessionManager.resetInstance();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('create', () => {
|
|
27
|
-
it('should create a new session with defaults', () => {
|
|
28
|
-
const session = manager.create({ name: 'Test Session' });
|
|
29
|
-
expect(session.id).toBeTruthy();
|
|
30
|
-
expect(session.name).toBe('Test Session');
|
|
31
|
-
expect(session.status).toBe('active');
|
|
32
|
-
expect(session.mode).toBe('plan');
|
|
33
|
-
expect(session.tokenCount).toBe(0);
|
|
34
|
-
expect(session.costUSD).toBe(0);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should create a session with custom options', () => {
|
|
38
|
-
const session = manager.create({
|
|
39
|
-
name: 'Deploy Session',
|
|
40
|
-
mode: 'deploy',
|
|
41
|
-
model: 'claude-sonnet',
|
|
42
|
-
cwd: '/tmp/project',
|
|
43
|
-
});
|
|
44
|
-
expect(session.mode).toBe('deploy');
|
|
45
|
-
expect(session.model).toBe('claude-sonnet');
|
|
46
|
-
expect(session.cwd).toBe('/tmp/project');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should emit a created event', () => {
|
|
50
|
-
const events: SessionEvent[] = [];
|
|
51
|
-
manager.onEvent(e => events.push(e));
|
|
52
|
-
manager.create({ name: 'Test' });
|
|
53
|
-
expect(events).toHaveLength(1);
|
|
54
|
-
expect(events[0].type).toBe('created');
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('list', () => {
|
|
59
|
-
it('should list all sessions', () => {
|
|
60
|
-
manager.create({ name: 'Session 1' });
|
|
61
|
-
manager.create({ name: 'Session 2' });
|
|
62
|
-
const sessions = manager.list();
|
|
63
|
-
expect(sessions).toHaveLength(2);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should filter by status', () => {
|
|
67
|
-
const _s1 = manager.create({ name: 'Active' });
|
|
68
|
-
const s2 = manager.create({ name: 'Completed' });
|
|
69
|
-
manager.complete(s2.id);
|
|
70
|
-
|
|
71
|
-
const active = manager.list('active');
|
|
72
|
-
expect(active).toHaveLength(1);
|
|
73
|
-
expect(active[0].name).toBe('Active');
|
|
74
|
-
|
|
75
|
-
const completed = manager.list('completed');
|
|
76
|
-
expect(completed).toHaveLength(1);
|
|
77
|
-
expect(completed[0].name).toBe('Completed');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('get', () => {
|
|
82
|
-
it('should return a session by ID', () => {
|
|
83
|
-
const created = manager.create({ name: 'Lookup Test' });
|
|
84
|
-
const fetched = manager.get(created.id);
|
|
85
|
-
expect(fetched).not.toBeNull();
|
|
86
|
-
expect(fetched!.name).toBe('Lookup Test');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should return null for non-existent session', () => {
|
|
90
|
-
expect(manager.get('nonexistent')).toBeNull();
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('switchTo', () => {
|
|
95
|
-
it('should switch active session', () => {
|
|
96
|
-
const s1 = manager.create({ name: 'Session 1' });
|
|
97
|
-
const s2 = manager.create({ name: 'Session 2' });
|
|
98
|
-
manager.switchTo(s1.id);
|
|
99
|
-
expect(manager.getActiveSessionId()).toBe(s1.id);
|
|
100
|
-
|
|
101
|
-
manager.switchTo(s2.id);
|
|
102
|
-
expect(manager.getActiveSessionId()).toBe(s2.id);
|
|
103
|
-
|
|
104
|
-
// s1 should be suspended
|
|
105
|
-
const s1Updated = manager.get(s1.id);
|
|
106
|
-
expect(s1Updated!.status).toBe('suspended');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should return null for non-existent session', () => {
|
|
110
|
-
expect(manager.switchTo('nonexistent')).toBeNull();
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('suspend and resume', () => {
|
|
115
|
-
it('should suspend an active session', () => {
|
|
116
|
-
const session = manager.create({ name: 'Test' });
|
|
117
|
-
manager.switchTo(session.id);
|
|
118
|
-
manager.suspend(session.id);
|
|
119
|
-
|
|
120
|
-
const updated = manager.get(session.id);
|
|
121
|
-
expect(updated!.status).toBe('suspended');
|
|
122
|
-
expect(manager.getActiveSessionId()).toBeNull();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should resume a suspended session', () => {
|
|
126
|
-
const session = manager.create({ name: 'Test' });
|
|
127
|
-
manager.suspend(session.id);
|
|
128
|
-
manager.resume(session.id);
|
|
129
|
-
|
|
130
|
-
const updated = manager.get(session.id);
|
|
131
|
-
expect(updated!.status).toBe('active');
|
|
132
|
-
expect(manager.getActiveSessionId()).toBe(session.id);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should not resume a completed session', () => {
|
|
136
|
-
const session = manager.create({ name: 'Test' });
|
|
137
|
-
manager.complete(session.id);
|
|
138
|
-
const result = manager.resume(session.id);
|
|
139
|
-
expect(result).toBeNull();
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
describe('complete', () => {
|
|
144
|
-
it('should mark session as completed', () => {
|
|
145
|
-
const session = manager.create({ name: 'Test' });
|
|
146
|
-
manager.switchTo(session.id);
|
|
147
|
-
manager.complete(session.id);
|
|
148
|
-
|
|
149
|
-
const updated = manager.get(session.id);
|
|
150
|
-
expect(updated!.status).toBe('completed');
|
|
151
|
-
expect(manager.getActiveSessionId()).toBeNull();
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe('destroy', () => {
|
|
156
|
-
it('should remove session from database', () => {
|
|
157
|
-
const session = manager.create({ name: 'Test' });
|
|
158
|
-
manager.destroy(session.id);
|
|
159
|
-
expect(manager.get(session.id)).toBeNull();
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
describe('updateSession', () => {
|
|
164
|
-
it('should update session fields', () => {
|
|
165
|
-
const session = manager.create({ name: 'Test' });
|
|
166
|
-
manager.updateSession(session.id, {
|
|
167
|
-
tokenCount: 5000,
|
|
168
|
-
costUSD: 0.05,
|
|
169
|
-
mode: 'build',
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const updated = manager.get(session.id);
|
|
173
|
-
expect(updated!.tokenCount).toBe(5000);
|
|
174
|
-
expect(updated!.costUSD).toBe(0.05);
|
|
175
|
-
expect(updated!.mode).toBe('build');
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
describe('file conflict detection', () => {
|
|
180
|
-
it('should detect when two sessions edit the same file', () => {
|
|
181
|
-
const s1 = manager.create({ name: 'Session 1' });
|
|
182
|
-
const s2 = manager.create({ name: 'Session 2' });
|
|
183
|
-
|
|
184
|
-
manager.recordFileEdit(s1.id, '/src/index.ts');
|
|
185
|
-
const conflicts = manager.recordFileEdit(s2.id, '/src/index.ts');
|
|
186
|
-
|
|
187
|
-
expect(conflicts).toContain(s1.id);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should not detect conflicts for the same session', () => {
|
|
191
|
-
const s1 = manager.create({ name: 'Session 1' });
|
|
192
|
-
|
|
193
|
-
manager.recordFileEdit(s1.id, '/src/index.ts');
|
|
194
|
-
const conflicts = manager.recordFileEdit(s1.id, '/src/index.ts');
|
|
195
|
-
|
|
196
|
-
expect(conflicts).toHaveLength(0);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('should emit file_conflict event', () => {
|
|
200
|
-
const events: SessionEvent[] = [];
|
|
201
|
-
manager.onEvent(e => events.push(e));
|
|
202
|
-
|
|
203
|
-
const s1 = manager.create({ name: 'S1' });
|
|
204
|
-
const s2 = manager.create({ name: 'S2' });
|
|
205
|
-
|
|
206
|
-
manager.recordFileEdit(s1.id, '/src/index.ts');
|
|
207
|
-
manager.recordFileEdit(s2.id, '/src/index.ts');
|
|
208
|
-
|
|
209
|
-
const conflictEvents = events.filter(e => e.type === 'file_conflict');
|
|
210
|
-
expect(conflictEvents).toHaveLength(1);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe('event listeners', () => {
|
|
215
|
-
it('should support removing listeners', () => {
|
|
216
|
-
const events: SessionEvent[] = [];
|
|
217
|
-
const unsub = manager.onEvent(e => events.push(e));
|
|
218
|
-
|
|
219
|
-
manager.create({ name: 'Before' });
|
|
220
|
-
expect(events).toHaveLength(1);
|
|
221
|
-
|
|
222
|
-
unsub();
|
|
223
|
-
manager.create({ name: 'After' });
|
|
224
|
-
expect(events).toHaveLength(1); // No new events after unsubscribe
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// -------------------------------------------------------------------------
|
|
229
|
-
// PERF-2a: Debounced SQLite write batching
|
|
230
|
-
// -------------------------------------------------------------------------
|
|
231
|
-
describe('PERF-2a: debounced SQLite flush (saveConversationAndStats)', () => {
|
|
232
|
-
let fastManager: SessionManager;
|
|
233
|
-
|
|
234
|
-
beforeEach(() => {
|
|
235
|
-
// flushDebounceMs=0 triggers immediate flush for deterministic tests
|
|
236
|
-
fastManager = new SessionManager(db, { flushDebounceMs: 0 });
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('saveConversationAndStats flushes immediately when flushDebounceMs=0', () => {
|
|
240
|
-
const session = fastManager.create({ name: 'Flush Test' });
|
|
241
|
-
const msgs = [{ role: 'user' as const, content: 'hello' }];
|
|
242
|
-
fastManager.saveConversationAndStats(session.id, msgs, { tokenCount: 10 });
|
|
243
|
-
|
|
244
|
-
const loaded = fastManager.loadConversation(session.id);
|
|
245
|
-
expect(loaded).toHaveLength(1);
|
|
246
|
-
expect(loaded[0].content).toBe('hello');
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('multiple saveConversationAndStats calls merge stats before flush', () => {
|
|
250
|
-
const session = fastManager.create({ name: 'Merge Test' });
|
|
251
|
-
const msgs = [{ role: 'user' as const, content: 'first' }];
|
|
252
|
-
// First call (with debounce=0, flushes immediately per-call)
|
|
253
|
-
fastManager.saveConversationAndStats(session.id, msgs, { tokenCount: 5 });
|
|
254
|
-
const loaded = fastManager.loadConversation(session.id);
|
|
255
|
-
expect(loaded[0].content).toBe('first');
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('flushAll() persists pending writes when called explicitly', () => {
|
|
259
|
-
// Use a high debounce so writes are pending
|
|
260
|
-
const deferredManager = new SessionManager(db, { flushDebounceMs: 60_000 });
|
|
261
|
-
const session = deferredManager.create({ name: 'Deferred' });
|
|
262
|
-
const msgs = [{ role: 'assistant' as const, content: 'deferred write' }];
|
|
263
|
-
deferredManager.saveConversationAndStats(session.id, msgs, {});
|
|
264
|
-
|
|
265
|
-
// Without flushAll, nothing is written yet
|
|
266
|
-
// (flushDebounceMs=0 manager can't verify this without timing, but we can
|
|
267
|
-
// verify flushAll itself writes correctly)
|
|
268
|
-
deferredManager.flushAll();
|
|
269
|
-
|
|
270
|
-
const loaded = deferredManager.loadConversation(session.id);
|
|
271
|
-
expect(loaded[0].content).toBe('deferred write');
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('flushAll() is a no-op when nothing is pending', () => {
|
|
275
|
-
// Should not throw
|
|
276
|
-
expect(() => fastManager.flushAll()).not.toThrow();
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('constructor accepts flushDebounceMs option', () => {
|
|
280
|
-
const m = new SessionManager(db, { flushDebounceMs: 1000 });
|
|
281
|
-
expect(m).toBeInstanceOf(SessionManager);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// ===========================================================================
|
|
287
|
-
// M2: Session rename
|
|
288
|
-
// ===========================================================================
|
|
289
|
-
|
|
290
|
-
describe('SessionManager.rename (M2)', () => {
|
|
291
|
-
let renameDb: Database;
|
|
292
|
-
let renameManager: SessionManager;
|
|
293
|
-
|
|
294
|
-
beforeEach(() => {
|
|
295
|
-
renameDb = new Database(':memory:');
|
|
296
|
-
renameDb.exec('PRAGMA journal_mode=WAL');
|
|
297
|
-
renameDb.exec('PRAGMA foreign_keys=ON');
|
|
298
|
-
SessionManager.resetInstance();
|
|
299
|
-
renameManager = new SessionManager(renameDb);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
afterEach(() => {
|
|
303
|
-
renameDb.close();
|
|
304
|
-
SessionManager.resetInstance();
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it('rename updates the session name', () => {
|
|
308
|
-
const session = renameManager.create({ name: 'chat-2026-01-01T10:00' });
|
|
309
|
-
renameManager.rename(session.id, 'deploy-staging-app');
|
|
310
|
-
const updated = renameManager.get(session.id);
|
|
311
|
-
expect(updated?.name).toBe('deploy-staging-app');
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('rename is a no-op for non-existent session (no throw)', () => {
|
|
315
|
-
expect(() => renameManager.rename('non-existent-id', 'new-name')).not.toThrow();
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it('rename accepts empty string', () => {
|
|
319
|
-
const session = renameManager.create({ name: 'original' });
|
|
320
|
-
expect(() => renameManager.rename(session.id, '')).not.toThrow();
|
|
321
|
-
});
|
|
322
|
-
});
|
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Session Sharing
|
|
3
|
-
*/
|
|
4
|
-
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
-
import {
|
|
6
|
-
shareSession,
|
|
7
|
-
getSharedSession,
|
|
8
|
-
listShares,
|
|
9
|
-
deleteShare,
|
|
10
|
-
getShareUrl,
|
|
11
|
-
cleanupExpiredShares,
|
|
12
|
-
_deps,
|
|
13
|
-
type SharedSession,
|
|
14
|
-
} from '../sharing/sync';
|
|
15
|
-
import { generateShareViewer } from '../sharing/viewer';
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Mock data
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
const mockSession = {
|
|
22
|
-
id: 'test-session-id',
|
|
23
|
-
name: 'Test Session',
|
|
24
|
-
mode: 'build',
|
|
25
|
-
model: 'claude-sonnet',
|
|
26
|
-
status: 'active',
|
|
27
|
-
costUSD: 0.05,
|
|
28
|
-
tokenCount: 5000,
|
|
29
|
-
createdAt: new Date().toISOString(),
|
|
30
|
-
updatedAt: new Date().toISOString(),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const mockMessages = [
|
|
34
|
-
{ role: 'user' as const, content: 'Hello' },
|
|
35
|
-
{ role: 'assistant' as const, content: 'Hi there!' },
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Inject test dependencies (no module-level mocks — avoids cross-file leaks)
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
/** Minimal in-memory DB that mimics the SQLite API used by sharing/sync.ts */
|
|
43
|
-
function makeInMemoryDb() {
|
|
44
|
-
const rows = new Map<string, any>();
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
run(sql: string, params: any[]) {
|
|
48
|
-
if (/INSERT INTO shares/.test(sql)) {
|
|
49
|
-
const [id, session_id, name, messages, model, mode, cost_usd, token_count, is_live, write_token, created_at, expires_at] = params;
|
|
50
|
-
rows.set(id, { id, session_id, name, messages, model, mode, cost_usd, token_count, is_live, write_token, created_at, expires_at });
|
|
51
|
-
return { changes: 1 };
|
|
52
|
-
} else if (/UPDATE shares SET messages/.test(sql)) {
|
|
53
|
-
const [messages, id] = params;
|
|
54
|
-
const row = rows.get(id);
|
|
55
|
-
if (row) { row.messages = messages; return { changes: 1 }; }
|
|
56
|
-
return { changes: 0 };
|
|
57
|
-
} else if (/DELETE FROM shares WHERE expires_at/.test(sql)) {
|
|
58
|
-
const [now] = params;
|
|
59
|
-
let count = 0;
|
|
60
|
-
for (const [id, row] of rows) {
|
|
61
|
-
if (row.expires_at <= now) { rows.delete(id); count++; }
|
|
62
|
-
}
|
|
63
|
-
return { changes: count };
|
|
64
|
-
} else if (/DELETE FROM shares WHERE id/.test(sql)) {
|
|
65
|
-
const [id] = params;
|
|
66
|
-
const existed = rows.has(id);
|
|
67
|
-
rows.delete(id);
|
|
68
|
-
return { changes: existed ? 1 : 0 };
|
|
69
|
-
}
|
|
70
|
-
return { changes: 0 };
|
|
71
|
-
},
|
|
72
|
-
query(sql: string) {
|
|
73
|
-
return {
|
|
74
|
-
get(...params: any[]) {
|
|
75
|
-
if (/WHERE id = \? AND expires_at/.test(sql)) {
|
|
76
|
-
const [id, now] = params;
|
|
77
|
-
const row = rows.get(id);
|
|
78
|
-
return row && row.expires_at > now ? row : undefined;
|
|
79
|
-
}
|
|
80
|
-
return undefined;
|
|
81
|
-
},
|
|
82
|
-
all(..._params: any[]) {
|
|
83
|
-
// listShares uses DELETE first then SELECT with ORDER BY (no WHERE params)
|
|
84
|
-
return [...rows.values()];
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
beforeEach(() => {
|
|
92
|
-
const db = makeInMemoryDb();
|
|
93
|
-
_deps.getDb = () => db;
|
|
94
|
-
_deps.getConversation = (id: string) =>
|
|
95
|
-
id === 'test-session-id' ? { messages: mockMessages } : null;
|
|
96
|
-
_deps.getSessionManager = () => ({
|
|
97
|
-
get: (id: string) => (id === 'test-session-id' ? mockSession : null),
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
afterEach(() => {
|
|
102
|
-
_deps.getDb = undefined;
|
|
103
|
-
_deps.getConversation = undefined;
|
|
104
|
-
_deps.getSessionManager = undefined;
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
// shareSession
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
describe('shareSession', () => {
|
|
112
|
-
test('creates a share for a valid session', () => {
|
|
113
|
-
const shared = shareSession('test-session-id');
|
|
114
|
-
expect(shared).not.toBeNull();
|
|
115
|
-
expect(shared!.id).toBeTruthy();
|
|
116
|
-
expect(shared!.sessionId).toBe('test-session-id');
|
|
117
|
-
expect(shared!.name).toBe('Test Session');
|
|
118
|
-
expect(shared!.messages).toEqual(mockMessages);
|
|
119
|
-
expect(shared!.model).toBe('claude-sonnet');
|
|
120
|
-
expect(shared!.mode).toBe('build');
|
|
121
|
-
expect(shared!.costUSD).toBe(0.05);
|
|
122
|
-
expect(shared!.tokenCount).toBe(5000);
|
|
123
|
-
expect(shared!.isLive).toBe(false);
|
|
124
|
-
expect(shared!.expiresAt).toBeTruthy();
|
|
125
|
-
expect(shared!.writeToken).toBeTruthy();
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('returns null for non-existent session', () => {
|
|
129
|
-
const shared = shareSession('nonexistent-id');
|
|
130
|
-
expect(shared).toBeNull();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('respects isLive option', () => {
|
|
134
|
-
const shared = shareSession('test-session-id', { isLive: true });
|
|
135
|
-
expect(shared).not.toBeNull();
|
|
136
|
-
expect(shared!.isLive).toBe(true);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('respects custom ttlDays', () => {
|
|
140
|
-
const shared = shareSession('test-session-id', { ttlDays: 7 });
|
|
141
|
-
expect(shared).not.toBeNull();
|
|
142
|
-
const expiresAt = new Date(shared!.expiresAt);
|
|
143
|
-
const now = new Date();
|
|
144
|
-
const diffDays = (expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);
|
|
145
|
-
// Should expire in approximately 7 days (allow some tolerance for execution time)
|
|
146
|
-
expect(diffDays).toBeGreaterThan(6.9);
|
|
147
|
-
expect(diffDays).toBeLessThan(7.1);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
// getSharedSession
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
|
-
|
|
155
|
-
describe('getSharedSession', () => {
|
|
156
|
-
test('returns the shared session without writeToken', () => {
|
|
157
|
-
const created = shareSession('test-session-id');
|
|
158
|
-
expect(created).not.toBeNull();
|
|
159
|
-
|
|
160
|
-
const retrieved = getSharedSession(created!.id);
|
|
161
|
-
expect(retrieved).not.toBeNull();
|
|
162
|
-
expect(retrieved!.id).toBe(created!.id);
|
|
163
|
-
expect(retrieved!.sessionId).toBe('test-session-id');
|
|
164
|
-
expect(retrieved!.name).toBe('Test Session');
|
|
165
|
-
// The writeToken should NOT be present in the public view
|
|
166
|
-
expect(retrieved!.writeToken).toBeUndefined();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test('returns null for non-existent share', () => {
|
|
170
|
-
const result = getSharedSession('does-not-exist');
|
|
171
|
-
expect(result).toBeNull();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test('returns null for expired shares', () => {
|
|
175
|
-
// Create a share with a very short TTL, then manipulate its expiry
|
|
176
|
-
const created = shareSession('test-session-id', { ttlDays: 0 });
|
|
177
|
-
expect(created).not.toBeNull();
|
|
178
|
-
|
|
179
|
-
// The share was created with ttlDays: 0, which means it expires immediately
|
|
180
|
-
// Access the internal store to verify expiry behavior
|
|
181
|
-
// Since ttlDays is 0, expiry = now + 0 days = now, which is already expired
|
|
182
|
-
const result = getSharedSession(created!.id);
|
|
183
|
-
expect(result).toBeNull();
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
|
-
// listShares
|
|
189
|
-
// ---------------------------------------------------------------------------
|
|
190
|
-
|
|
191
|
-
describe('listShares', () => {
|
|
192
|
-
test('returns an array of shares', () => {
|
|
193
|
-
const shares = listShares();
|
|
194
|
-
expect(Array.isArray(shares)).toBe(true);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test('filters out expired shares', () => {
|
|
198
|
-
// Create a valid share
|
|
199
|
-
const valid = shareSession('test-session-id', { ttlDays: 30 });
|
|
200
|
-
expect(valid).not.toBeNull();
|
|
201
|
-
|
|
202
|
-
// Create a share that expires immediately
|
|
203
|
-
const expired = shareSession('test-session-id', { ttlDays: 0 });
|
|
204
|
-
expect(expired).not.toBeNull();
|
|
205
|
-
|
|
206
|
-
const shares = listShares();
|
|
207
|
-
const ids = shares.map(s => s.id);
|
|
208
|
-
|
|
209
|
-
// The valid share should be in the list
|
|
210
|
-
expect(ids).toContain(valid!.id);
|
|
211
|
-
// The expired share should NOT be in the list
|
|
212
|
-
expect(ids).not.toContain(expired!.id);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// ---------------------------------------------------------------------------
|
|
217
|
-
// deleteShare
|
|
218
|
-
// ---------------------------------------------------------------------------
|
|
219
|
-
|
|
220
|
-
describe('deleteShare', () => {
|
|
221
|
-
test('removes a share and returns true', () => {
|
|
222
|
-
const created = shareSession('test-session-id');
|
|
223
|
-
expect(created).not.toBeNull();
|
|
224
|
-
|
|
225
|
-
const deleted = deleteShare(created!.id);
|
|
226
|
-
expect(deleted).toBe(true);
|
|
227
|
-
|
|
228
|
-
// Verify it is gone
|
|
229
|
-
const result = getSharedSession(created!.id);
|
|
230
|
-
expect(result).toBeNull();
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test('returns false for non-existent share', () => {
|
|
234
|
-
expect(deleteShare('nonexistent')).toBe(false);
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// ---------------------------------------------------------------------------
|
|
239
|
-
// cleanupExpiredShares
|
|
240
|
-
// ---------------------------------------------------------------------------
|
|
241
|
-
|
|
242
|
-
describe('cleanupExpiredShares', () => {
|
|
243
|
-
test('removes expired shares and returns count', () => {
|
|
244
|
-
// Create an immediately-expiring share
|
|
245
|
-
shareSession('test-session-id', { ttlDays: 0 });
|
|
246
|
-
|
|
247
|
-
const cleaned = cleanupExpiredShares();
|
|
248
|
-
expect(typeof cleaned).toBe('number');
|
|
249
|
-
expect(cleaned).toBeGreaterThanOrEqual(0);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// ---------------------------------------------------------------------------
|
|
254
|
-
// getShareUrl
|
|
255
|
-
// ---------------------------------------------------------------------------
|
|
256
|
-
|
|
257
|
-
describe('getShareUrl', () => {
|
|
258
|
-
test('generates URL with default base', () => {
|
|
259
|
-
const url = getShareUrl('abc123');
|
|
260
|
-
expect(url).toBe('http://localhost:6001/nimbus/share/abc123');
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
test('generates URL with custom base', () => {
|
|
264
|
-
const url = getShareUrl('abc123', 'https://astron.dev');
|
|
265
|
-
expect(url).toBe('https://astron.dev/nimbus/share/abc123');
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// ---------------------------------------------------------------------------
|
|
270
|
-
// generateShareViewer
|
|
271
|
-
// ---------------------------------------------------------------------------
|
|
272
|
-
|
|
273
|
-
describe('generateShareViewer', () => {
|
|
274
|
-
const mockSharedSession: SharedSession = {
|
|
275
|
-
id: 'test-share-id',
|
|
276
|
-
sessionId: 'session-123',
|
|
277
|
-
name: 'Test Session',
|
|
278
|
-
messages: [
|
|
279
|
-
{ role: 'user', content: 'Hello' },
|
|
280
|
-
{ role: 'assistant', content: 'Hi there! How can I help?' },
|
|
281
|
-
{ role: 'user', content: 'Deploy my app' },
|
|
282
|
-
{ role: 'assistant', content: 'I will help you deploy your application.' },
|
|
283
|
-
],
|
|
284
|
-
model: 'claude-sonnet',
|
|
285
|
-
mode: 'build',
|
|
286
|
-
costUSD: 0.0042,
|
|
287
|
-
tokenCount: 1500,
|
|
288
|
-
createdAt: new Date().toISOString(),
|
|
289
|
-
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
290
|
-
isLive: false,
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
test('generates valid HTML', () => {
|
|
294
|
-
const html = generateShareViewer(mockSharedSession);
|
|
295
|
-
expect(html).toContain('<!DOCTYPE html>');
|
|
296
|
-
expect(html).toContain('Nimbus');
|
|
297
|
-
expect(html).toContain('Test Session');
|
|
298
|
-
expect(html).toContain('claude-sonnet');
|
|
299
|
-
expect(html).toContain('build');
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
test('escapes HTML entities', () => {
|
|
303
|
-
const session: SharedSession = {
|
|
304
|
-
...mockSharedSession,
|
|
305
|
-
messages: [
|
|
306
|
-
{ role: 'user', content: '<script>alert("xss")</script>' },
|
|
307
|
-
{ role: 'assistant', content: 'Safe response & "quotes"' },
|
|
308
|
-
],
|
|
309
|
-
};
|
|
310
|
-
const html = generateShareViewer(session);
|
|
311
|
-
expect(html).not.toContain('<script>alert');
|
|
312
|
-
expect(html).toContain('<script>');
|
|
313
|
-
expect(html).toContain('&');
|
|
314
|
-
expect(html).toContain('"quotes"');
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test('shows LIVE badge when isLive=true', () => {
|
|
318
|
-
const liveSession = { ...mockSharedSession, isLive: true };
|
|
319
|
-
const html = generateShareViewer(liveSession);
|
|
320
|
-
expect(html).toContain('LIVE');
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test('does not show LIVE badge when isLive=false', () => {
|
|
324
|
-
const html = generateShareViewer(mockSharedSession);
|
|
325
|
-
expect(html).not.toContain('LIVE');
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test('includes expiry information', () => {
|
|
329
|
-
const html = generateShareViewer(mockSharedSession);
|
|
330
|
-
expect(html).toContain('Expires');
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
test('includes all user and assistant messages', () => {
|
|
334
|
-
const html = generateShareViewer(mockSharedSession);
|
|
335
|
-
expect(html).toContain('Hello');
|
|
336
|
-
expect(html).toContain('Hi there! How can I help?');
|
|
337
|
-
expect(html).toContain('Deploy my app');
|
|
338
|
-
expect(html).toContain('I will help you deploy your application.');
|
|
339
|
-
});
|
|
340
|
-
});
|