@alumbwe/anvil 1.0.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/README.md +84 -0
- package/bin/anvil +2 -0
- package/bin/tree-sitter.wasm +0 -0
- package/package.json +67 -0
- package/src/__tests__/README.md +256 -0
- package/src/__tests__/bash-mode.test.ts +442 -0
- package/src/__tests__/cli-args.test.ts +181 -0
- package/src/__tests__/cli-harness.test.ts +52 -0
- package/src/__tests__/e2e/first-time-login.test.ts +173 -0
- package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
- package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
- package/src/__tests__/e2e-cli.test.ts +183 -0
- package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
- package/src/__tests__/helpers/mock-api-client.ts +64 -0
- package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
- package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
- package/src/__tests__/home-directory-detection.test.ts +213 -0
- package/src/__tests__/integration/api-integration.test.ts +288 -0
- package/src/__tests__/integration/credentials-storage.test.ts +486 -0
- package/src/__tests__/integration/local-agents.test.ts +1187 -0
- package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
- package/src/__tests__/integration/login-polling-working.test.ts +285 -0
- package/src/__tests__/integration/queue-editing.test.tsx +261 -0
- package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
- package/src/__tests__/integration-tmux.test.ts +188 -0
- package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
- package/src/__tests__/launcher-disconnect.test.ts +104 -0
- package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
- package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
- package/src/__tests__/path-completion.test.ts +243 -0
- package/src/__tests__/project-files-chat-id.test.ts +34 -0
- package/src/__tests__/release/proxy-http-get.test.ts +667 -0
- package/src/__tests__/release/wrapper-safety.test.ts +412 -0
- package/src/__tests__/renderer-cleanup.test.ts +174 -0
- package/src/__tests__/rerender-perf.integration.test.ts +283 -0
- package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
- package/src/__tests__/terminal-watchdog.test.ts +388 -0
- package/src/__tests__/test-utils.ts +271 -0
- package/src/__tests__/tmux-poc.ts +150 -0
- package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
- package/src/__tests__/unit/copy-button.test.ts +157 -0
- package/src/__tests__/unit/create-run-config.test.ts +66 -0
- package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
- package/src/__tests__/unit/segmented-control.test.ts +152 -0
- package/src/__tests__/utils/env.test.ts +169 -0
- package/src/__tests__/utils/project-picker.test.ts +39 -0
- package/src/agents/bundled-agents.generated.d.ts +14 -0
- package/src/agents/bundled-agents.generated.ts +3692 -0
- package/src/app.tsx +399 -0
- package/src/chat.tsx +1698 -0
- package/src/cli-args.ts +134 -0
- package/src/commands/__tests__/bash-command.test.ts +445 -0
- package/src/commands/__tests__/command-args.test.ts +349 -0
- package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
- package/src/commands/__tests__/copy-conversation.test.ts +226 -0
- package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
- package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
- package/src/commands/__tests__/image.test.ts +99 -0
- package/src/commands/__tests__/init.test.ts +456 -0
- package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
- package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
- package/src/commands/__tests__/prompt-builders.test.ts +28 -0
- package/src/commands/__tests__/router-input.test.ts +298 -0
- package/src/commands/ads.ts +44 -0
- package/src/commands/command-registry.ts +726 -0
- package/src/commands/copy-conversation.ts +392 -0
- package/src/commands/help.ts +13 -0
- package/src/commands/image.ts +20 -0
- package/src/commands/init.ts +121 -0
- package/src/commands/process-diagnostics.ts +120 -0
- package/src/commands/prompt-builders.ts +100 -0
- package/src/commands/publish.ts +203 -0
- package/src/commands/router-utils.ts +80 -0
- package/src/commands/router.ts +487 -0
- package/src/commands/usage.ts +26 -0
- package/src/components/__tests__/ad-banner.test.tsx +83 -0
- package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
- package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
- package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
- package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
- package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
- package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
- package/src/components/__tests__/grid-layout.test.tsx +1051 -0
- package/src/components/__tests__/message-block.completion.test.tsx +104 -0
- package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
- package/src/components/__tests__/message-with-agents.test.tsx +579 -0
- package/src/components/__tests__/multiline-input.test.tsx +1132 -0
- package/src/components/__tests__/queue-panel.test.tsx +398 -0
- package/src/components/__tests__/selectable-list.test.ts +232 -0
- package/src/components/__tests__/status-bar.test.tsx +44 -0
- package/src/components/__tests__/status-indicator.test.tsx +204 -0
- package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
- package/src/components/__tests__/user-error-banner.test.tsx +102 -0
- package/src/components/ad-banner.tsx +323 -0
- package/src/components/agent-checklist.tsx +350 -0
- package/src/components/agent-mode-toggle.tsx +241 -0
- package/src/components/anvil-mark.tsx +52 -0
- package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
- package/src/components/ask-user/__tests__/validation.test.ts +213 -0
- package/src/components/ask-user/components/accordion-question.tsx +153 -0
- package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
- package/src/components/ask-user/components/options-list.tsx +133 -0
- package/src/components/ask-user/components/question-header.tsx +80 -0
- package/src/components/ask-user/components/question-option.tsx +89 -0
- package/src/components/ask-user/constants.ts +38 -0
- package/src/components/ask-user/index.tsx +631 -0
- package/src/components/ask-user/utils/validation.ts +85 -0
- package/src/components/attachment-card.tsx +65 -0
- package/src/components/blocks/agent-block-grid.tsx +58 -0
- package/src/components/blocks/agent-branch-item.tsx +311 -0
- package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
- package/src/components/blocks/agent-list-branch.tsx +77 -0
- package/src/components/blocks/ask-user-branch.tsx +81 -0
- package/src/components/blocks/block-helpers.ts +11 -0
- package/src/components/blocks/blocks-renderer.tsx +289 -0
- package/src/components/blocks/content-with-markdown.tsx +33 -0
- package/src/components/blocks/image-block.tsx +128 -0
- package/src/components/blocks/implementor-row.tsx +429 -0
- package/src/components/blocks/single-block.tsx +201 -0
- package/src/components/blocks/thinking-block.tsx +67 -0
- package/src/components/blocks/tool-block-group.tsx +57 -0
- package/src/components/blocks/tool-branch.tsx +206 -0
- package/src/components/blocks/user-content-copy.tsx +157 -0
- package/src/components/bottom-banner.tsx +136 -0
- package/src/components/build-mode-buttons.tsx +110 -0
- package/src/components/button.tsx +66 -0
- package/src/components/chat-header.tsx +77 -0
- package/src/components/chat-history-screen.tsx +413 -0
- package/src/components/chat-input-bar.tsx +523 -0
- package/src/components/clickable-title-box.tsx +66 -0
- package/src/components/clickable.tsx +119 -0
- package/src/components/collapse-button.tsx +34 -0
- package/src/components/copy-button.tsx +203 -0
- package/src/components/elapsed-timer.tsx +55 -0
- package/src/components/error-boundary.tsx +55 -0
- package/src/components/feedback-container.tsx +181 -0
- package/src/components/feedback-icon-button.tsx +89 -0
- package/src/components/feedback-input-mode.tsx +344 -0
- package/src/components/file-attachment-card.tsx +98 -0
- package/src/components/freebuff-active-session-summary.tsx +63 -0
- package/src/components/freebuff-landing-screen.tsx +865 -0
- package/src/components/freebuff-model-selector.tsx +1087 -0
- package/src/components/freebuff-referral-banner.tsx +604 -0
- package/src/components/freebuff-superseded-screen.tsx +62 -0
- package/src/components/grid-layout.tsx +80 -0
- package/src/components/help-banner.tsx +127 -0
- package/src/components/highlighted-text.tsx +52 -0
- package/src/components/image-card.tsx +167 -0
- package/src/components/image-thumbnail.tsx +114 -0
- package/src/components/input-cursor.tsx +77 -0
- package/src/components/input-mode-banner.tsx +57 -0
- package/src/components/load-previous-button.tsx +48 -0
- package/src/components/login-modal.tsx +475 -0
- package/src/components/message-block.tsx +346 -0
- package/src/components/message-footer.tsx +258 -0
- package/src/components/message-with-agents.tsx +506 -0
- package/src/components/mode-divider.tsx +43 -0
- package/src/components/multiline-input.tsx +1241 -0
- package/src/components/out-of-credits-banner.tsx +160 -0
- package/src/components/pending-attachments-banner.tsx +116 -0
- package/src/components/pending-bash-message.tsx +59 -0
- package/src/components/progress-bar.tsx +81 -0
- package/src/components/project-picker-screen.tsx +485 -0
- package/src/components/publish-confirmation.tsx +493 -0
- package/src/components/publish-container.tsx +687 -0
- package/src/components/queue-panel.tsx +310 -0
- package/src/components/raised-pill.tsx +95 -0
- package/src/components/renderers/plan-box.tsx +59 -0
- package/src/components/review-screen.tsx +112 -0
- package/src/components/scroll-to-bottom-button.tsx +34 -0
- package/src/components/segmented-control.tsx +187 -0
- package/src/components/selectable-list.tsx +248 -0
- package/src/components/selected-chips.tsx +80 -0
- package/src/components/separator.tsx +40 -0
- package/src/components/session-ended-banner.tsx +226 -0
- package/src/components/shimmer-text.tsx +248 -0
- package/src/components/status-bar.tsx +313 -0
- package/src/components/subscription-limit-banner.tsx +206 -0
- package/src/components/suggested-prompts.tsx +184 -0
- package/src/components/suggestion-menu.tsx +215 -0
- package/src/components/terminal-command-display.tsx +151 -0
- package/src/components/terminal-link.tsx +97 -0
- package/src/components/terminal-thinking-orb.tsx +95 -0
- package/src/components/text-attachment-card.tsx +77 -0
- package/src/components/thinking.tsx +109 -0
- package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
- package/src/components/tools/__tests__/code-search.test.tsx +45 -0
- package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
- package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
- package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
- package/src/components/tools/apply-patch.tsx +95 -0
- package/src/components/tools/code-search.tsx +45 -0
- package/src/components/tools/composio.tsx +152 -0
- package/src/components/tools/diff-viewer.tsx +76 -0
- package/src/components/tools/glob.tsx +61 -0
- package/src/components/tools/gravity-index.tsx +77 -0
- package/src/components/tools/list-directory.tsx +61 -0
- package/src/components/tools/read-docs.tsx +37 -0
- package/src/components/tools/read-files.tsx +91 -0
- package/src/components/tools/read-subtree.tsx +44 -0
- package/src/components/tools/read-url.tsx +33 -0
- package/src/components/tools/registry.ts +126 -0
- package/src/components/tools/render-ui.tsx +122 -0
- package/src/components/tools/run-terminal-command.tsx +79 -0
- package/src/components/tools/skill.tsx +29 -0
- package/src/components/tools/str-replace.tsx +77 -0
- package/src/components/tools/suggest-followups.tsx +369 -0
- package/src/components/tools/task-completed.tsx +16 -0
- package/src/components/tools/tool-call-item.tsx +273 -0
- package/src/components/tools/types.ts +55 -0
- package/src/components/tools/web-search.tsx +33 -0
- package/src/components/tools/write-file.tsx +16 -0
- package/src/components/tools/write-todos.tsx +95 -0
- package/src/components/top-banner.tsx +178 -0
- package/src/components/usage-banner.tsx +244 -0
- package/src/components/user-error-banner.tsx +56 -0
- package/src/components/validation-error-popover.tsx +192 -0
- package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
- package/src/contexts/chat-runtime-context.tsx +240 -0
- package/src/data/slash-commands.test.ts +24 -0
- package/src/data/slash-commands.ts +249 -0
- package/src/entry.ts +12 -0
- package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
- package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
- package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
- package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
- package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
- package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
- package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
- package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
- package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
- package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
- package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
- package/src/hooks/__tests__/use-input-history.test.ts +699 -0
- package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
- package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
- package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
- package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
- package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
- package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
- package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
- package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
- package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
- package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
- package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
- package/src/hooks/__tests__/use-timeout.test.ts +330 -0
- package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
- package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
- package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
- package/src/hooks/helpers/send-message.ts +654 -0
- package/src/hooks/stream-state.ts +83 -0
- package/src/hooks/use-activity-query.ts +668 -0
- package/src/hooks/use-agent-validation.ts +74 -0
- package/src/hooks/use-ask-user-bridge.ts +92 -0
- package/src/hooks/use-auth-query.ts +253 -0
- package/src/hooks/use-auth-state.ts +152 -0
- package/src/hooks/use-chat-input.ts +105 -0
- package/src/hooks/use-chat-keyboard.ts +321 -0
- package/src/hooks/use-chat-messages.ts +254 -0
- package/src/hooks/use-chat-state.ts +165 -0
- package/src/hooks/use-chat-streaming.ts +180 -0
- package/src/hooks/use-chat-ui.ts +131 -0
- package/src/hooks/use-clipboard.ts +151 -0
- package/src/hooks/use-connection-status.ts +142 -0
- package/src/hooks/use-directory-browser.ts +88 -0
- package/src/hooks/use-elapsed-time.ts +116 -0
- package/src/hooks/use-event.ts +34 -0
- package/src/hooks/use-exit-handler.ts +66 -0
- package/src/hooks/use-fetch-login-url.ts +65 -0
- package/src/hooks/use-fingerprint.ts +61 -0
- package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
- package/src/hooks/use-freebuff-session-progress.ts +34 -0
- package/src/hooks/use-freebuff-session.ts +806 -0
- package/src/hooks/use-freebuff-streak-query.ts +61 -0
- package/src/hooks/use-gravity-ad.ts +656 -0
- package/src/hooks/use-grid-layout.ts +73 -0
- package/src/hooks/use-input-history.ts +173 -0
- package/src/hooks/use-login-keyboard-handlers.ts +75 -0
- package/src/hooks/use-login-polling.ts +114 -0
- package/src/hooks/use-logo.tsx +161 -0
- package/src/hooks/use-message-queue.ts +387 -0
- package/src/hooks/use-now.ts +20 -0
- package/src/hooks/use-path-tab-completion.ts +88 -0
- package/src/hooks/use-publish-mutation.ts +60 -0
- package/src/hooks/use-queue-controls.ts +68 -0
- package/src/hooks/use-queue-ui.ts +74 -0
- package/src/hooks/use-scroll-management.ts +184 -0
- package/src/hooks/use-searchable-list.ts +98 -0
- package/src/hooks/use-send-message.ts +773 -0
- package/src/hooks/use-sheen-animation.tsx +88 -0
- package/src/hooks/use-subscription-query.ts +71 -0
- package/src/hooks/use-suggestion-engine.ts +783 -0
- package/src/hooks/use-terminal-breakpoints.ts +77 -0
- package/src/hooks/use-terminal-dimensions.ts +46 -0
- package/src/hooks/use-terminal-focus.ts +29 -0
- package/src/hooks/use-terminal-layout.ts +189 -0
- package/src/hooks/use-theme.tsx +140 -0
- package/src/hooks/use-timeout.ts +85 -0
- package/src/hooks/use-update-preference.ts +66 -0
- package/src/hooks/use-usage-monitor.ts +55 -0
- package/src/hooks/use-usage-query.ts +124 -0
- package/src/hooks/use-user-details-query.ts +93 -0
- package/src/hooks/use-why-did-you-update.ts +177 -0
- package/src/index.tsx +441 -0
- package/src/init/__tests__/init-direnv.test.ts +526 -0
- package/src/init/init-app.ts +34 -0
- package/src/init/init-direnv.ts +133 -0
- package/src/login/constants.ts +60 -0
- package/src/login/login-flow.ts +204 -0
- package/src/login/plain-login.ts +108 -0
- package/src/login/utils.ts +183 -0
- package/src/native/ripgrep.ts +80 -0
- package/src/polyfills/bun-strip-ansi.ts +12 -0
- package/src/pre-init/tree-sitter-wasm.ts +96 -0
- package/src/project-files.ts +102 -0
- package/src/smoke/terminal-command-broker.ts +439 -0
- package/src/state/__tests__/chat-store-focus.test.ts +22 -0
- package/src/state/__tests__/feedback-store.test.ts +257 -0
- package/src/state/chat-history-store.ts +30 -0
- package/src/state/chat-store.ts +544 -0
- package/src/state/feedback-store.ts +160 -0
- package/src/state/freebuff-model-store.ts +42 -0
- package/src/state/freebuff-session-store.ts +53 -0
- package/src/state/login-store.ts +150 -0
- package/src/state/message-block-store.ts +149 -0
- package/src/state/publish-store.ts +146 -0
- package/src/state/queue-panel-store.ts +26 -0
- package/src/state/review-store.ts +24 -0
- package/src/testing/env.ts +46 -0
- package/src/types/chat-state.ts +15 -0
- package/src/types/chat.ts +241 -0
- package/src/types/contracts/send-message.ts +12 -0
- package/src/types/env.ts +98 -0
- package/src/types/freebuff-session.ts +17 -0
- package/src/types/function-params.ts +28 -0
- package/src/types/react19-compat.d.ts +19 -0
- package/src/types/store.ts +111 -0
- package/src/types/theme-system.ts +144 -0
- package/src/types/utils.ts +3 -0
- package/src/utils/__tests__/active-run.test.ts +140 -0
- package/src/utils/__tests__/activity-tracker.test.ts +440 -0
- package/src/utils/__tests__/agent-display.test.ts +139 -0
- package/src/utils/__tests__/analytics-client.test.ts +262 -0
- package/src/utils/__tests__/anonymous-id.test.ts +74 -0
- package/src/utils/__tests__/anvil-api.test.ts +578 -0
- package/src/utils/__tests__/arrays.test.ts +264 -0
- package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
- package/src/utils/__tests__/block-processor.test.ts +810 -0
- package/src/utils/__tests__/chat-history.test.ts +203 -0
- package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
- package/src/utils/__tests__/chat-meta.test.ts +130 -0
- package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
- package/src/utils/__tests__/clipboard.test.ts +910 -0
- package/src/utils/__tests__/code-search-summary.test.ts +84 -0
- package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
- package/src/utils/__tests__/error-handling.test.ts +658 -0
- package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
- package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
- package/src/utils/__tests__/feedback-submission.test.ts +26 -0
- package/src/utils/__tests__/fetch-usage.test.ts +355 -0
- package/src/utils/__tests__/fingerprint.test.ts +144 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
- package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
- package/src/utils/__tests__/format-timeout.test.ts +87 -0
- package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
- package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
- package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
- package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
- package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
- package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
- package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
- package/src/utils/__tests__/image-dimensions.test.ts +249 -0
- package/src/utils/__tests__/image-processor.test.ts +209 -0
- package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
- package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
- package/src/utils/__tests__/layout-helpers.test.ts +36 -0
- package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
- package/src/utils/__tests__/log-shipper.test.ts +53 -0
- package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
- package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
- package/src/utils/__tests__/message-updater.test.ts +645 -0
- package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
- package/src/utils/__tests__/pending-attachments.test.ts +277 -0
- package/src/utils/__tests__/polling-backoff.test.ts +51 -0
- package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
- package/src/utils/__tests__/run-state-storage.test.ts +919 -0
- package/src/utils/__tests__/safe-json.test.ts +77 -0
- package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
- package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
- package/src/utils/__tests__/send-message-timer.test.ts +64 -0
- package/src/utils/__tests__/settings.test.ts +99 -0
- package/src/utils/__tests__/strings.test.ts +192 -0
- package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
- package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
- package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
- package/src/utils/__tests__/terminal-io.test.ts +137 -0
- package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
- package/src/utils/__tests__/text-layout.test.ts +82 -0
- package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
- package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
- package/src/utils/__tests__/trace-writer.test.ts +223 -0
- package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
- package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
- package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
- package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
- package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
- package/src/utils/active-run.ts +131 -0
- package/src/utils/activity-tracker.ts +66 -0
- package/src/utils/agent-display.ts +87 -0
- package/src/utils/agent-helpers.ts +27 -0
- package/src/utils/agent-id-utils.ts +14 -0
- package/src/utils/ai-message-id.ts +12 -0
- package/src/utils/analytics.ts +361 -0
- package/src/utils/anonymous-id.ts +87 -0
- package/src/utils/anvil-api.ts +612 -0
- package/src/utils/anvil-client.ts +217 -0
- package/src/utils/arrays.ts +117 -0
- package/src/utils/auth.ts +258 -0
- package/src/utils/bash-context-processor.ts +47 -0
- package/src/utils/bash-messages.ts +108 -0
- package/src/utils/block-margins.ts +35 -0
- package/src/utils/block-operations.ts +523 -0
- package/src/utils/block-processor.ts +213 -0
- package/src/utils/chat-history.ts +253 -0
- package/src/utils/chat-input-key-intercept.ts +52 -0
- package/src/utils/chat-meta.ts +90 -0
- package/src/utils/chat-scroll-accel.ts +151 -0
- package/src/utils/clipboard-image.ts +588 -0
- package/src/utils/clipboard.ts +397 -0
- package/src/utils/code-search-summary.ts +70 -0
- package/src/utils/collapse-helpers.ts +256 -0
- package/src/utils/config-dir.ts +24 -0
- package/src/utils/constants.ts +190 -0
- package/src/utils/create-event-handler-state.ts +73 -0
- package/src/utils/create-run-config.ts +121 -0
- package/src/utils/detect-shell.ts +116 -0
- package/src/utils/directory-browser.ts +72 -0
- package/src/utils/engagement.ts +55 -0
- package/src/utils/env.ts +92 -0
- package/src/utils/error-handling.ts +255 -0
- package/src/utils/error-messages.ts +62 -0
- package/src/utils/exit-cleanly.ts +86 -0
- package/src/utils/feedback-helpers.ts +103 -0
- package/src/utils/feedback-submission.ts +22 -0
- package/src/utils/fetch-usage.ts +76 -0
- package/src/utils/fingerprint.ts +245 -0
- package/src/utils/format-elapsed-time.ts +33 -0
- package/src/utils/format-session-units.ts +6 -0
- package/src/utils/format-timeout.ts +28 -0
- package/src/utils/format-validation-errors-for-message.ts +84 -0
- package/src/utils/freebuff-agent-selection.ts +33 -0
- package/src/utils/freebuff-instance-owner.ts +66 -0
- package/src/utils/freebuff-model-navigation.ts +50 -0
- package/src/utils/freebuff-premium-reset.ts +55 -0
- package/src/utils/freebuff-referral-cache.ts +51 -0
- package/src/utils/freebuff-session-api.ts +243 -0
- package/src/utils/freebuff-session-display.ts +21 -0
- package/src/utils/freebuff-streak-line.ts +93 -0
- package/src/utils/git.ts +17 -0
- package/src/utils/helpers.ts +47 -0
- package/src/utils/image-display.ts +67 -0
- package/src/utils/image-handler.ts +325 -0
- package/src/utils/image-processor.ts +133 -0
- package/src/utils/image-thumbnail.ts +78 -0
- package/src/utils/implementor-helpers.ts +787 -0
- package/src/utils/input-modes.ts +174 -0
- package/src/utils/keyboard-actions.ts +392 -0
- package/src/utils/keypad-keys.ts +47 -0
- package/src/utils/layout-helpers.ts +33 -0
- package/src/utils/lazy-response-ads.ts +1 -0
- package/src/utils/local-agent-registry.ts +474 -0
- package/src/utils/log-shipper.ts +126 -0
- package/src/utils/logger.ts +327 -0
- package/src/utils/markdown-renderer.tsx +1090 -0
- package/src/utils/math.ts +3 -0
- package/src/utils/message-block-helpers.ts +684 -0
- package/src/utils/message-history.ts +143 -0
- package/src/utils/message-tree-utils.ts +67 -0
- package/src/utils/message-updater.ts +279 -0
- package/src/utils/open-file.ts +146 -0
- package/src/utils/open-url.ts +63 -0
- package/src/utils/path-completion.ts +111 -0
- package/src/utils/path-helpers.ts +38 -0
- package/src/utils/pending-attachments.ts +360 -0
- package/src/utils/polling-backoff.ts +59 -0
- package/src/utils/project-picker.ts +12 -0
- package/src/utils/queue-panel-actions.ts +70 -0
- package/src/utils/recent-projects.ts +156 -0
- package/src/utils/renderer-cleanup.ts +233 -0
- package/src/utils/response-ad-positions.ts +1 -0
- package/src/utils/run-state-storage.ts +603 -0
- package/src/utils/safe-json.ts +153 -0
- package/src/utils/sdk-event-handlers.ts +571 -0
- package/src/utils/send-message-helpers.ts +210 -0
- package/src/utils/send-message-timer.ts +110 -0
- package/src/utils/settings.ts +226 -0
- package/src/utils/skill-registry.ts +94 -0
- package/src/utils/spawn-agent-matcher.ts +55 -0
- package/src/utils/status-indicator-state.ts +107 -0
- package/src/utils/stream-chunk-processor.ts +57 -0
- package/src/utils/strings.ts +271 -0
- package/src/utils/subscription.ts +31 -0
- package/src/utils/syntax-highlighter.tsx +19 -0
- package/src/utils/terminal-color-detection.ts +474 -0
- package/src/utils/terminal-command-broker.ts +467 -0
- package/src/utils/terminal-enter-detection.ts +63 -0
- package/src/utils/terminal-images.ts +229 -0
- package/src/utils/terminal-io.ts +85 -0
- package/src/utils/terminal-protocol-controller.ts +173 -0
- package/src/utils/terminal-reset-sequences.ts +33 -0
- package/src/utils/terminal-title.ts +84 -0
- package/src/utils/terminal-watchdog.ts +366 -0
- package/src/utils/text-layout.ts +149 -0
- package/src/utils/theme-config.ts +141 -0
- package/src/utils/theme-system.ts +1206 -0
- package/src/utils/think-tag-parser.ts +95 -0
- package/src/utils/time-format.ts +21 -0
- package/src/utils/trace-writer.ts +162 -0
- package/src/utils/ui-constants.ts +69 -0
- package/src/utils/update-checker.ts +110 -0
- package/src/utils/usage-banner-state.ts +146 -0
- package/src/utils/validation-error-formatting.ts +86 -0
- package/src/utils/validation-error-helpers.ts +19 -0
- package/src/utils/windows-terminal-health.ts +124 -0
- package/src/utils/word-wrap-utils.ts +53 -0
- package/src/utils/write-file-atomic.ts +54 -0
- package/src/utils/yield-to-event-loop.ts +9 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import os from 'os'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
clearMockedModules,
|
|
7
|
+
mockModule,
|
|
8
|
+
} from '@anvil/common/testing/mock-modules'
|
|
9
|
+
import {
|
|
10
|
+
describe,
|
|
11
|
+
test,
|
|
12
|
+
expect,
|
|
13
|
+
beforeEach,
|
|
14
|
+
afterEach,
|
|
15
|
+
mock,
|
|
16
|
+
spyOn,
|
|
17
|
+
} from 'bun:test'
|
|
18
|
+
|
|
19
|
+
import { setProjectRoot } from '../../project-files'
|
|
20
|
+
import * as authModule from '../../utils/auth'
|
|
21
|
+
import { saveUserCredentials, getUserCredentials } from '../../utils/auth'
|
|
22
|
+
|
|
23
|
+
import type { User } from '../../utils/auth'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Integration tests for credential storage and retrieval
|
|
27
|
+
*
|
|
28
|
+
* These tests verify the complete flow of saving, loading, and managing
|
|
29
|
+
* user credentials on the file system. Credentials are stored in:
|
|
30
|
+
* - Dev: ~/.config/manicode-dev/credentials.json
|
|
31
|
+
* - Prod: ~/.config/manicode/credentials.json
|
|
32
|
+
*
|
|
33
|
+
* Tests ensure:
|
|
34
|
+
* - Directories are created if missing
|
|
35
|
+
* - JSON format is correct and parseable
|
|
36
|
+
* - Credentials persist across CLI restarts
|
|
37
|
+
* - File operations are atomic (no partial writes)
|
|
38
|
+
* - Environment variable detection works (dev vs prod)
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const TEST_USER: User = {
|
|
42
|
+
id: 'test-user-123',
|
|
43
|
+
name: 'Test User',
|
|
44
|
+
email: 'test@example.com',
|
|
45
|
+
authToken: 'test-session-token-abc',
|
|
46
|
+
fingerprintId: 'test-fingerprint',
|
|
47
|
+
fingerprintHash: 'test-hash',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('Credentials Storage Integration', () => {
|
|
51
|
+
let tempConfigDir: string
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
// Create temporary config directory for tests
|
|
55
|
+
tempConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), 'manicode-test-'))
|
|
56
|
+
|
|
57
|
+
// Set project root to avoid "Project root not set" error in logger
|
|
58
|
+
setProjectRoot(tempConfigDir)
|
|
59
|
+
|
|
60
|
+
// Mock getConfigDir to use temp directory
|
|
61
|
+
spyOn(authModule, 'getConfigDir').mockReturnValue(tempConfigDir)
|
|
62
|
+
spyOn(authModule, 'getCredentialsPath').mockReturnValue(
|
|
63
|
+
path.join(tempConfigDir, 'credentials.json'),
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
// Clean up temp directory
|
|
69
|
+
if (fs.existsSync(tempConfigDir)) {
|
|
70
|
+
fs.rmSync(tempConfigDir, { recursive: true, force: true })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
mock.restore()
|
|
74
|
+
clearMockedModules()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('P0: File System Operations', () => {
|
|
78
|
+
test('should create config directory if it does not exist', () => {
|
|
79
|
+
// Ensure directory doesn't exist initially
|
|
80
|
+
if (fs.existsSync(tempConfigDir)) {
|
|
81
|
+
fs.rmSync(tempConfigDir, { recursive: true })
|
|
82
|
+
}
|
|
83
|
+
expect(fs.existsSync(tempConfigDir)).toBe(false)
|
|
84
|
+
|
|
85
|
+
// Call saveUserCredentials - should create directory
|
|
86
|
+
saveUserCredentials(TEST_USER)
|
|
87
|
+
|
|
88
|
+
// Verify directory was created
|
|
89
|
+
expect(fs.existsSync(tempConfigDir)).toBe(true)
|
|
90
|
+
|
|
91
|
+
// Verify it's a directory
|
|
92
|
+
const stats = fs.statSync(tempConfigDir)
|
|
93
|
+
expect(stats.isDirectory()).toBe(true)
|
|
94
|
+
|
|
95
|
+
// Verify credentials file was created in the directory
|
|
96
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
97
|
+
expect(fs.existsSync(credentialsPath)).toBe(true)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('should write credentials.json with correct JSON format', () => {
|
|
101
|
+
// Call saveUserCredentials
|
|
102
|
+
saveUserCredentials(TEST_USER)
|
|
103
|
+
|
|
104
|
+
// Read the credentials file
|
|
105
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
106
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8')
|
|
107
|
+
|
|
108
|
+
// Parse JSON
|
|
109
|
+
const parsed = JSON.parse(fileContent)
|
|
110
|
+
|
|
111
|
+
// Verify structure is { default: { ...user fields } }
|
|
112
|
+
expect(parsed).toHaveProperty('default')
|
|
113
|
+
expect(typeof parsed.default).toBe('object')
|
|
114
|
+
|
|
115
|
+
// Verify all user fields are present
|
|
116
|
+
expect(parsed.default.id).toBe(TEST_USER.id)
|
|
117
|
+
expect(parsed.default.name).toBe(TEST_USER.name)
|
|
118
|
+
expect(parsed.default.email).toBe(TEST_USER.email)
|
|
119
|
+
expect(parsed.default.authToken).toBe(TEST_USER.authToken)
|
|
120
|
+
expect(parsed.default.fingerprintId).toBe(TEST_USER.fingerprintId)
|
|
121
|
+
expect(parsed.default.fingerprintHash).toBe(TEST_USER.fingerprintHash)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('should overwrite existing credentials when saving new ones', () => {
|
|
125
|
+
// Save initial credentials
|
|
126
|
+
saveUserCredentials(TEST_USER)
|
|
127
|
+
|
|
128
|
+
// Read and verify first credentials
|
|
129
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
130
|
+
let fileContent = fs.readFileSync(credentialsPath, 'utf8')
|
|
131
|
+
let parsed = JSON.parse(fileContent)
|
|
132
|
+
expect(parsed.default.id).toBe(TEST_USER.id)
|
|
133
|
+
|
|
134
|
+
// Save different credentials
|
|
135
|
+
const newUser: User = {
|
|
136
|
+
id: 'different-user-456',
|
|
137
|
+
name: 'Different User',
|
|
138
|
+
email: 'different@example.com',
|
|
139
|
+
authToken: 'different-token',
|
|
140
|
+
fingerprintId: 'different-fingerprint',
|
|
141
|
+
fingerprintHash: 'different-hash',
|
|
142
|
+
}
|
|
143
|
+
saveUserCredentials(newUser)
|
|
144
|
+
|
|
145
|
+
// Read again
|
|
146
|
+
fileContent = fs.readFileSync(credentialsPath, 'utf8')
|
|
147
|
+
parsed = JSON.parse(fileContent)
|
|
148
|
+
|
|
149
|
+
// Verify new credentials replaced old ones
|
|
150
|
+
expect(parsed.default.id).toBe(newUser.id)
|
|
151
|
+
expect(parsed.default.name).toBe(newUser.name)
|
|
152
|
+
expect(parsed.default.email).toBe(newUser.email)
|
|
153
|
+
expect(parsed.default.authToken).toBe(newUser.authToken)
|
|
154
|
+
|
|
155
|
+
// Verify only one 'default' entry exists
|
|
156
|
+
const keys = Object.keys(parsed)
|
|
157
|
+
expect(keys.length).toBe(1)
|
|
158
|
+
expect(keys[0]).toBe('default')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('should use manicode-test directory in test environment', async () => {
|
|
162
|
+
// Restore getConfigDir to use real implementation for this test
|
|
163
|
+
mock.restore()
|
|
164
|
+
|
|
165
|
+
await mockModule('@anvil/common/env', () => ({
|
|
166
|
+
env: { NEXT_PUBLIC_CB_ENVIRONMENT: 'test' },
|
|
167
|
+
}))
|
|
168
|
+
|
|
169
|
+
// Call real getConfigDir to verify it includes '-dev'
|
|
170
|
+
const configDir = authModule.getConfigDir()
|
|
171
|
+
expect(configDir).toEqual(
|
|
172
|
+
path.join(os.homedir(), '.config', 'manicode-test'),
|
|
173
|
+
)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('should use manicode-dev directory in development environment', async () => {
|
|
177
|
+
// Restore getConfigDir to use real implementation for this test
|
|
178
|
+
mock.restore()
|
|
179
|
+
|
|
180
|
+
await mockModule('@anvil/common/env', () => ({
|
|
181
|
+
env: { NEXT_PUBLIC_CB_ENVIRONMENT: 'dev' },
|
|
182
|
+
}))
|
|
183
|
+
|
|
184
|
+
// Call real getConfigDir to verify it includes '-dev'
|
|
185
|
+
const configDir = authModule.getConfigDir()
|
|
186
|
+
expect(configDir).toEqual(
|
|
187
|
+
path.join(os.homedir(), '.config', 'manicode-dev'),
|
|
188
|
+
)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('should use manicode directory in production environment', async () => {
|
|
192
|
+
// Restore getConfigDir to use real implementation
|
|
193
|
+
mock.restore()
|
|
194
|
+
|
|
195
|
+
// Set environment to prod (or unset it)
|
|
196
|
+
await mockModule('@anvil/common/env', () => ({
|
|
197
|
+
env: { NEXT_PUBLIC_CB_ENVIRONMENT: 'prod' },
|
|
198
|
+
}))
|
|
199
|
+
|
|
200
|
+
// Call real getConfigDir to verify it doesn't include '-dev'
|
|
201
|
+
const configDir = authModule.getConfigDir()
|
|
202
|
+
expect(configDir).toEqual(path.join(os.homedir(), '.config', 'manicode'))
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('should allow credentials to persist across simulated CLI restarts', () => {
|
|
206
|
+
// Save credentials
|
|
207
|
+
saveUserCredentials(TEST_USER)
|
|
208
|
+
|
|
209
|
+
// Simulate CLI restart by calling getUserCredentials
|
|
210
|
+
// (simulates reading from disk on fresh startup)
|
|
211
|
+
const loadedCredentials = getUserCredentials()
|
|
212
|
+
|
|
213
|
+
// Verify credentials are loaded from file
|
|
214
|
+
expect(loadedCredentials).not.toBeNull()
|
|
215
|
+
expect(loadedCredentials).toBeDefined()
|
|
216
|
+
|
|
217
|
+
// Verify all fields match what was saved
|
|
218
|
+
expect(loadedCredentials!.id).toBe(TEST_USER.id)
|
|
219
|
+
expect(loadedCredentials!.name).toBe(TEST_USER.name)
|
|
220
|
+
expect(loadedCredentials!.email).toBe(TEST_USER.email)
|
|
221
|
+
expect(loadedCredentials!.authToken).toBe(TEST_USER.authToken)
|
|
222
|
+
expect(loadedCredentials!.fingerprintId).toBe(TEST_USER.fingerprintId)
|
|
223
|
+
expect(loadedCredentials!.fingerprintHash).toBe(TEST_USER.fingerprintHash)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test('drops the removed ChatGPT integration token on the next write', () => {
|
|
227
|
+
// Anyone who ran /connect-chatgpt still has an OAuth access + refresh
|
|
228
|
+
// token for their own ChatGPT account sitting in this file. Nothing reads
|
|
229
|
+
// it now and the command that managed it is gone, so it must not survive
|
|
230
|
+
// a rewrite — otherwise it is orphaned on disk with no way to clear it.
|
|
231
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
232
|
+
fs.writeFileSync(
|
|
233
|
+
credentialsPath,
|
|
234
|
+
JSON.stringify({
|
|
235
|
+
default: { ...TEST_USER, authToken: 'stale' },
|
|
236
|
+
chatgptOAuth: {
|
|
237
|
+
accessToken: 'a',
|
|
238
|
+
refreshToken: 'r',
|
|
239
|
+
expiresAt: 1,
|
|
240
|
+
connectedAt: 1,
|
|
241
|
+
},
|
|
242
|
+
someOtherKey: 'kept',
|
|
243
|
+
}),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
saveUserCredentials(TEST_USER)
|
|
247
|
+
|
|
248
|
+
const parsed = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'))
|
|
249
|
+
expect(parsed.chatgptOAuth).toBeUndefined()
|
|
250
|
+
// Only that one key goes: unrelated entries and the login still stand.
|
|
251
|
+
expect(parsed.someOtherKey).toBe('kept')
|
|
252
|
+
expect(parsed.default.authToken).toBe(TEST_USER.authToken)
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
describe('P0: Credential Format Validation', () => {
|
|
257
|
+
test('should save user ID in credentials', () => {
|
|
258
|
+
saveUserCredentials(TEST_USER)
|
|
259
|
+
|
|
260
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
261
|
+
const parsed = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'))
|
|
262
|
+
|
|
263
|
+
expect(parsed.default.id).toBe(TEST_USER.id)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test('should save user name in credentials', () => {
|
|
267
|
+
saveUserCredentials(TEST_USER)
|
|
268
|
+
|
|
269
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
270
|
+
const parsed = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'))
|
|
271
|
+
|
|
272
|
+
expect(parsed.default.name).toBe(TEST_USER.name)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test('should save user email in credentials', () => {
|
|
276
|
+
saveUserCredentials(TEST_USER)
|
|
277
|
+
|
|
278
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
279
|
+
const parsed = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'))
|
|
280
|
+
|
|
281
|
+
expect(parsed.default.email).toBe(TEST_USER.email)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test('should save authToken (session token) in credentials', () => {
|
|
285
|
+
saveUserCredentials(TEST_USER)
|
|
286
|
+
|
|
287
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
288
|
+
const parsed = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'))
|
|
289
|
+
|
|
290
|
+
// authToken is the most critical field for authentication
|
|
291
|
+
expect(parsed.default.authToken).toBe(TEST_USER.authToken)
|
|
292
|
+
expect(parsed.default.authToken).toBeTruthy()
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test('should save fingerprintId in credentials', () => {
|
|
296
|
+
saveUserCredentials(TEST_USER)
|
|
297
|
+
|
|
298
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
299
|
+
const parsed = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'))
|
|
300
|
+
|
|
301
|
+
expect(parsed.default.fingerprintId).toBe(TEST_USER.fingerprintId)
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
test('should save fingerprintHash in credentials', () => {
|
|
305
|
+
saveUserCredentials(TEST_USER)
|
|
306
|
+
|
|
307
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
308
|
+
const parsed = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'))
|
|
309
|
+
|
|
310
|
+
expect(parsed.default.fingerprintHash).toBe(TEST_USER.fingerprintHash)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
test('should produce valid, parseable JSON', () => {
|
|
314
|
+
saveUserCredentials(TEST_USER)
|
|
315
|
+
|
|
316
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
317
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8')
|
|
318
|
+
|
|
319
|
+
// Verify JSON.parse doesn't throw
|
|
320
|
+
let parsed: any
|
|
321
|
+
expect(() => {
|
|
322
|
+
parsed = JSON.parse(fileContent)
|
|
323
|
+
}).not.toThrow()
|
|
324
|
+
|
|
325
|
+
// Verify parsed object has expected structure
|
|
326
|
+
expect(parsed).toHaveProperty('default')
|
|
327
|
+
expect(typeof parsed.default).toBe('object')
|
|
328
|
+
expect(parsed.default).toHaveProperty('id')
|
|
329
|
+
expect(parsed.default).toHaveProperty('authToken')
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
describe('P2: File System Edge Cases', () => {
|
|
334
|
+
test('should preserve file permissions when writing credentials', () => {
|
|
335
|
+
// Save credentials
|
|
336
|
+
saveUserCredentials(TEST_USER)
|
|
337
|
+
|
|
338
|
+
// Check file permissions
|
|
339
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
340
|
+
const stats = fs.statSync(credentialsPath)
|
|
341
|
+
const mode = stats.mode
|
|
342
|
+
|
|
343
|
+
// On Unix systems, check permissions
|
|
344
|
+
if (process.platform !== 'win32') {
|
|
345
|
+
// File should be readable by user
|
|
346
|
+
expect((mode & 0o400) !== 0).toBe(true)
|
|
347
|
+
|
|
348
|
+
// File should be writable by user
|
|
349
|
+
expect((mode & 0o200) !== 0).toBe(true)
|
|
350
|
+
} else {
|
|
351
|
+
// On Windows, just verify file exists and is accessible
|
|
352
|
+
expect(fs.existsSync(credentialsPath)).toBe(true)
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
test('should handle write permission errors gracefully', () => {
|
|
357
|
+
// Mock fs.writeFileSync to throw EACCES error
|
|
358
|
+
const writeError = new Error(
|
|
359
|
+
'EACCES: permission denied',
|
|
360
|
+
) as NodeJS.ErrnoException
|
|
361
|
+
writeError.code = 'EACCES'
|
|
362
|
+
|
|
363
|
+
const writeFileSyncSpy = spyOn(fs, 'writeFileSync').mockImplementation(
|
|
364
|
+
() => {
|
|
365
|
+
throw writeError
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
// Attempt to save credentials - should throw since we're not catching in saveUserCredentials
|
|
370
|
+
expect(() => {
|
|
371
|
+
saveUserCredentials(TEST_USER)
|
|
372
|
+
}).toThrow('EACCES')
|
|
373
|
+
|
|
374
|
+
// Verify writeFileSync was attempted
|
|
375
|
+
expect(writeFileSyncSpy).toHaveBeenCalled()
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
test('should show clear error message on permission denial', () => {
|
|
379
|
+
// This test verifies that when permission is denied, the error is logged
|
|
380
|
+
const writeError = new Error(
|
|
381
|
+
"EACCES: permission denied, open '/test/credentials.json'",
|
|
382
|
+
) as NodeJS.ErrnoException
|
|
383
|
+
writeError.code = 'EACCES'
|
|
384
|
+
|
|
385
|
+
spyOn(fs, 'writeFileSync').mockImplementation(() => {
|
|
386
|
+
throw writeError
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
// Attempt to save credentials - will throw and get logged
|
|
390
|
+
expect(() => {
|
|
391
|
+
saveUserCredentials(TEST_USER)
|
|
392
|
+
}).toThrow()
|
|
393
|
+
|
|
394
|
+
// The actual error logging happens in saveUserCredentials via logger.error
|
|
395
|
+
// The error message includes the error details which would help users diagnose
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
test('should gracefully degrade if credentials cannot be written', () => {
|
|
399
|
+
// This tests that the error is thrown (not silently swallowed)
|
|
400
|
+
// The caller (login mutation) is responsible for handling the error gracefully
|
|
401
|
+
const writeError = new Error(
|
|
402
|
+
'ENOSPC: no space left on device',
|
|
403
|
+
) as NodeJS.ErrnoException
|
|
404
|
+
writeError.code = 'ENOSPC'
|
|
405
|
+
|
|
406
|
+
spyOn(fs, 'writeFileSync').mockImplementation(() => {
|
|
407
|
+
throw writeError
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
// Attempt to save - should throw to caller who can handle it
|
|
411
|
+
expect(() => {
|
|
412
|
+
saveUserCredentials(TEST_USER)
|
|
413
|
+
}).toThrow('ENOSPC')
|
|
414
|
+
|
|
415
|
+
// The login mutation's onError handler would catch this and allow
|
|
416
|
+
// the user to continue with in-memory credentials
|
|
417
|
+
})
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
describe('P2: Concurrent Operations', () => {
|
|
421
|
+
test('should handle rapid saves without race conditions', () => {
|
|
422
|
+
// Create different user objects for rapid saves
|
|
423
|
+
const users: User[] = []
|
|
424
|
+
for (let i = 0; i < 5; i++) {
|
|
425
|
+
users.push({
|
|
426
|
+
id: `user-${i}`,
|
|
427
|
+
name: `User ${i}`,
|
|
428
|
+
email: `user${i}@example.com`,
|
|
429
|
+
authToken: `token-${i}`,
|
|
430
|
+
fingerprintId: `fingerprint-${i}`,
|
|
431
|
+
fingerprintHash: `hash-${i}`,
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Call saveUserCredentials 5 times rapidly
|
|
436
|
+
users.forEach((user) => saveUserCredentials(user))
|
|
437
|
+
|
|
438
|
+
// Read final credentials
|
|
439
|
+
const credentialsPath = path.join(tempConfigDir, 'credentials.json')
|
|
440
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8')
|
|
441
|
+
const parsed = JSON.parse(fileContent)
|
|
442
|
+
|
|
443
|
+
// Verify file contains the last saved data (user-4)
|
|
444
|
+
expect(parsed.default.id).toBe('user-4')
|
|
445
|
+
expect(parsed.default.name).toBe('User 4')
|
|
446
|
+
|
|
447
|
+
// Verify no corrupted/partial data - JSON should be valid
|
|
448
|
+
expect(parsed).toHaveProperty('default')
|
|
449
|
+
expect(typeof parsed.default.authToken).toBe('string')
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
test('should handle read during write without corruption', () => {
|
|
453
|
+
// Since fs.writeFileSync is synchronous, there's no actual concurrency
|
|
454
|
+
// But we can verify that writes are atomic (no partial data)
|
|
455
|
+
|
|
456
|
+
// Save initial credentials
|
|
457
|
+
saveUserCredentials(TEST_USER)
|
|
458
|
+
|
|
459
|
+
// Read credentials
|
|
460
|
+
const loadedBefore = getUserCredentials()
|
|
461
|
+
expect(loadedBefore).not.toBeNull()
|
|
462
|
+
expect(loadedBefore!.id).toBe(TEST_USER.id)
|
|
463
|
+
|
|
464
|
+
// Save new credentials
|
|
465
|
+
const newUser: User = {
|
|
466
|
+
id: 'new-user-789',
|
|
467
|
+
name: 'New User',
|
|
468
|
+
email: 'new@example.com',
|
|
469
|
+
authToken: 'new-token',
|
|
470
|
+
fingerprintId: 'new-fingerprint',
|
|
471
|
+
fingerprintHash: 'new-hash',
|
|
472
|
+
}
|
|
473
|
+
saveUserCredentials(newUser)
|
|
474
|
+
|
|
475
|
+
// Read again - should get complete new data (not partial/mixed)
|
|
476
|
+
const loadedAfter = getUserCredentials()
|
|
477
|
+
expect(loadedAfter).not.toBeNull()
|
|
478
|
+
expect(loadedAfter!.id).toBe(newUser.id)
|
|
479
|
+
expect(loadedAfter!.name).toBe(newUser.name)
|
|
480
|
+
expect(loadedAfter!.authToken).toBe(newUser.authToken)
|
|
481
|
+
|
|
482
|
+
// Verify NOT corrupted (not mixed old and new data)
|
|
483
|
+
expect(loadedAfter!.id).not.toBe(TEST_USER.id)
|
|
484
|
+
})
|
|
485
|
+
})
|
|
486
|
+
})
|