shadow-claw 1.23.3 → 1.24.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 +42 -5
- package/bin/assert-version-bump.mjs +60 -38
- package/bin/assert-version-bump.test.mjs +74 -11
- package/bin/cli.mjs +166 -0
- package/bin/cli.test.mjs +60 -0
- package/bin/commands/backup.mjs +70 -0
- package/bin/commands/clients.mjs +41 -0
- package/bin/commands/peer-id.mjs +76 -0
- package/bin/commands/peer-id.test.mjs +104 -0
- package/bin/commands/send.mjs +51 -0
- package/bin/commands/tasks.mjs +59 -0
- package/bin/commands/webrtc-listen.mjs +254 -0
- package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
- package/bin/rolldown.config.test.mjs +401 -8
- package/bin/utils/control-client.mjs +243 -0
- package/bin/utils/control-client.test.mjs +83 -0
- package/bin/utils/ulid.mjs +45 -0
- package/bin/utils/webrtc-control-client.mjs +699 -0
- package/bin/utils/webrtc-control-client.test.mjs +318 -0
- package/dist/electron/main.cjs +1 -1
- package/dist/public/AGENTS.md +5 -3
- package/dist/public/README.md +42 -5
- package/dist/public/agent.worker.js +6 -6
- package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.js} +1 -1
- package/dist/public/backup-controller-C_s9in2t.js +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
- package/dist/public/components/settings/shadow-claw-storage/shadow-claw-storage.html +1 -1
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.css +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.html +1 -1
- package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
- package/dist/public/config-CrHchneq.js +1 -0
- package/dist/public/{configurePeerJs-B7kNU8AQ.js → configurePeerJs-CxpZeDJJ.js} +1 -1
- package/dist/public/{connections-xd_66YVU.js → connections-DvbgdkaB.js} +1 -1
- package/dist/public/{custom-element-security-CZ92anB8.js → custom-element-security-NCqQ5lmC.js} +1 -1
- package/dist/public/docs/README.md +3 -1
- package/dist/public/docs/architecture/overview.md +12 -12
- package/dist/public/docs/architecture/storage.md +5 -2
- package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
- package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +6 -3
- package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
- package/dist/public/docs/example/article/index.html +2 -2
- package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
- package/dist/public/docs/guides/server-development-configuration.md +24 -0
- package/dist/public/docs/publishing/index.html +19 -76
- package/dist/public/docs/skill-creator/index.html +2 -2
- package/dist/public/docs/subsystems/backup.md +56 -0
- package/dist/public/docs/subsystems/cli.md +124 -0
- package/dist/public/docs/subsystems/control-plane.md +158 -0
- package/dist/public/docs/subsystems/notifications.md +54 -2
- package/dist/public/docs/subsystems/reactive-ui.md +1 -0
- package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
- package/dist/public/e2e/README.md +5 -1
- package/dist/public/{e2e-bridge-DkS5xFGX.js → e2e-bridge-IGWsRl4i.js} +1 -1
- package/dist/public/file-viewer-D0eflgt6.js +1 -0
- package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
- package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
- package/dist/public/{git-BkjrsRXX.js → git-7Vq_SJSp.js} +2 -2
- package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-BslPXAXS.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
- package/dist/public/initControlPlane-C9jpnWev.js +1 -0
- package/dist/public/llms.txt +1 -1
- package/dist/public/main/index.html +2 -2
- package/dist/public/main/memory/index.html +2 -2
- package/dist/public/{markdown-6MBrIo-S.js → markdown-BnpKxSxl.js} +1 -1
- package/dist/public/mcp-reconnect-9emsqGRe.js +1 -0
- package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-C0KvLNUp.js} +1 -1
- package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-3h9xxHUm.js} +676 -676
- package/dist/public/orchestrator-nJj59RqD.js +15 -0
- package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-BP4laMXj.js} +1 -1
- package/dist/public/{push-client-Dqq06wbB.js → push-client-CrjEXK3U.js} +1 -1
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker/push-handler.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-Bd1rfuVC.js → shadow-claw-CbwETBWf.js} +2 -2
- package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-B4sBTQEj.js} +3 -3
- package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
- package/dist/public/{shadow-claw-chat-NXg6zz_l.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
- package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
- package/dist/public/{shadow-claw-conversations-C8eqGpP9.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
- package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
- package/dist/public/shadow-claw-file-viewer-Blf6yGVD.js +577 -0
- package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
- package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-v8S0JjuR.js} +3 -3
- package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
- package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
- package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-qHbP95z9.js} +1 -1
- package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
- package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
- package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DFZHhzFZ.js} +3 -3
- package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
- package/dist/public/shadow-claw-storage-BFynbP0j.js +219 -0
- package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
- package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Caxc9atL.js} +4 -4
- package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
- package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-BfJx3A9W.js} +2 -2
- package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
- package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/static-main-manifest.json +1 -1
- package/dist/public/{syncWebMcpRegistration-DfpvASnh.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
- package/dist/server.js +286 -189
- package/package.json +2 -1
- package/src/__mocks__/jszip.cjs +15 -5
- package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
- package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
- package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
- package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
- package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
- package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
- package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.html +17 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +120 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
- package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
- package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
- package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
- package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
- package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
- package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
- package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
- package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
- package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +378 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
- package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
- package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +627 -1
- package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
- package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
- package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
- package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
- package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
- package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
- package/src/config/config.test.ts +9 -0
- package/src/config/config.ts +15 -0
- package/src/core/app-routes.test.ts +56 -0
- package/src/core/app-routes.ts +8 -1
- package/src/core/backup-controller.test.ts +101 -0
- package/src/core/backup-controller.ts +137 -0
- package/src/core/control-plane-client.test.ts +408 -0
- package/src/core/control-plane-client.ts +425 -0
- package/src/core/orchestrator/orchestrator.ts +4 -0
- package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
- package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
- package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
- package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
- package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
- package/src/core/utils/initControlPlane.test.ts +276 -0
- package/src/core/utils/initControlPlane.ts +437 -0
- package/src/core/utils/initializeApp.ts +18 -0
- package/src/db/getSession.test.ts +13 -1
- package/src/db/openDatabase.test.ts +10 -1
- package/src/db/openDatabase.ts +3 -1
- package/src/server/app.test.ts +12 -0
- package/src/server/app.ts +18 -8
- package/src/server/client-registry.test.ts +250 -0
- package/src/server/client-registry.ts +477 -0
- package/src/server/config.ts +15 -0
- package/src/server/control-plane-types.ts +120 -0
- package/src/server/control-plane.test.ts +472 -0
- package/src/server/control-plane.ts +581 -0
- package/src/server/routes/backup.test.ts +263 -0
- package/src/server/routes/backup.ts +262 -0
- package/src/server/routes/integrations-email.test.ts +532 -0
- package/src/server/routes/llamafile.test.ts +172 -48
- package/src/server/routes/mesh-llm.test.ts +192 -0
- package/src/server/routes/ollama.test.ts +49 -0
- package/src/server/server-peer.test.ts +86 -0
- package/src/server/server-peer.ts +330 -0
- package/src/server/server.ts +35 -0
- package/src/server/services/llamafile-manager.test.ts +230 -6
- package/src/server/services/transformers-runtime.test.ts +186 -18
- package/src/server/webrtc.test.ts +21 -0
- package/src/server/webrtc.ts +72 -0
- package/src/service-worker/fetch-proxy.test.ts +64 -1
- package/src/service-worker/push-handler.ts +39 -0
- package/src/shell/vm.test.ts +744 -12
- package/src/storage/ensureMainGroupIndex.test.ts +153 -0
- package/src/storage/getGroupFile.test.ts +66 -0
- package/src/storage/memoryStorage.test.ts +82 -2
- package/src/storage/migrateLegacyOpfs.test.ts +327 -0
- package/src/storage/migrateLegacyOpfs.ts +117 -0
- package/src/storage/writeGroupFileBytes.test.ts +143 -0
- package/src/stores/chat-ui.test.ts +20 -0
- package/src/stores/orchestrator.test.ts +134 -0
- package/src/stores/toast.test.ts +50 -0
- package/src/stores/tools.test.ts +147 -10
- package/src/subsystems/channels/imessage.test.ts +59 -0
- package/src/subsystems/channels/peerjs.test.ts +500 -62
- package/src/subsystems/channels/peerjs.ts +71 -4
- package/src/subsystems/channels/telegram.test.ts +177 -0
- package/src/subsystems/email/catalog.test.ts +1 -0
- package/src/subsystems/email/connections.test.ts +39 -1
- package/src/subsystems/git/credentials.test.ts +160 -825
- package/src/subsystems/git/git.test.ts +463 -0
- package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
- package/src/subsystems/notifications/push-routes.test.ts +33 -0
- package/src/subsystems/notifications/push-routes.ts +23 -0
- package/src/subsystems/notifications/push-store.ts +1 -1
- package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
- package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
- package/src/subsystems/providers/model-registry.test.ts +115 -0
- package/src/subsystems/providers/providers.test.ts +94 -1
- package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
- package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
- package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
- package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
- package/src/subsystems/skills/activateSkill.test.ts +101 -0
- package/src/subsystems/skills/discoverSkills.test.ts +251 -0
- package/src/subsystems/tools/cron.test.ts +64 -0
- package/src/subsystems/tools/declarative.test.ts +255 -89
- package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
- package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
- package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
- package/src/testing/jest-setup.ts +328 -40
- package/src/testing/mock-v86.mjs +49 -0
- package/src/ui/a2ui/types.ts +4 -3
- package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
- package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
- package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
- package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
- package/src/worker/tools/email/email.test.ts +193 -8
- package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
- package/src/worker/tools/git/git.test.ts +214 -3
- package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
- package/src/worker/tools/rooms/leave-room.test.ts +40 -0
- package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
- package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
- package/src/worker/tools/ui/ask-user.test.ts +50 -0
- package/src/worker/tools/ui/clear-chat.test.ts +19 -0
- package/src/worker/tools/ui/get-current-time.test.ts +19 -0
- package/src/worker/tools/ui/javascript.test.ts +116 -0
- package/src/worker/tools/ui/list-components.test.ts +10 -0
- package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
- package/src/worker/tools/ui/manage-tools.test.ts +34 -0
- package/src/worker/tools/ui/render-component.test.ts +346 -0
- package/src/worker/tools/ui/send-notification.test.ts +40 -0
- package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
- package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
- package/src/worker/tools/workspace/attach-file.test.ts +84 -0
- package/src/worker/tools/workspace/copy-file.test.ts +69 -0
- package/src/worker/tools/workspace/create-directory.test.ts +40 -0
- package/src/worker/tools/workspace/delete-file.test.ts +73 -0
- package/src/worker/tools/workspace/diff-files.test.ts +94 -0
- package/src/worker/tools/workspace/list-files.test.ts +36 -0
- package/src/worker/tools/workspace/move-file.test.ts +66 -0
- package/src/worker/tools/workspace/open-file.test.ts +57 -0
- package/src/worker/tools/workspace/patch-file.test.ts +81 -0
- package/src/worker/tools/workspace/read-file.test.ts +116 -0
- package/src/worker/tools/workspace/send-file.test.ts +82 -0
- package/src/worker/tools/workspace/update-memory.test.ts +29 -0
- package/src/worker/tools/workspace/write-file.test.ts +28 -0
- package/src/worker/utils/rate-limit.test.ts +199 -0
- package/src/worker/utils/tool-state.test.ts +29 -0
- package/src/worker/utils/transformers-js-utils.test.ts +59 -0
- package/dist/public/config-AL77QAx4.js +0 -1
- package/dist/public/downloadGroupFile-BABv_1mJ.js +0 -1
- package/dist/public/file-viewer-CCSABMsE.js +0 -1
- package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
- package/dist/public/orchestrator-4su7iyWj.js +0 -15
- package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
- package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
- package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
- package/dist/public/shadow-claw-storage-Cf31np81.js +0 -202
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../../storage/deleteGroupFile.js", () => ({
|
|
4
|
+
deleteGroupFile: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
jest.unstable_mockModule("../../../storage/deleteGroupDirectory.js", () => ({
|
|
8
|
+
deleteGroupDirectory: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const { executeDeleteFile } = await import("./delete-file.js");
|
|
12
|
+
const { deleteGroupFile } = await import("../../../storage/deleteGroupFile.js");
|
|
13
|
+
const { deleteGroupDirectory } =
|
|
14
|
+
await import("../../../storage/deleteGroupDirectory.js");
|
|
15
|
+
|
|
16
|
+
describe("executeDeleteFile", () => {
|
|
17
|
+
const mockDb: any = {};
|
|
18
|
+
const groupId = "test-group";
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("throws error when path parameter is missing", async () => {
|
|
25
|
+
await expect(executeDeleteFile(mockDb, {}, groupId)).rejects.toThrow(
|
|
26
|
+
"Missing path parameter",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("deletes a file when deleteGroupFile succeeds", async () => {
|
|
31
|
+
(deleteGroupFile as jest.Mock<any>).mockResolvedValue(undefined);
|
|
32
|
+
|
|
33
|
+
const result = await executeDeleteFile(
|
|
34
|
+
mockDb,
|
|
35
|
+
{ path: "file.txt" },
|
|
36
|
+
groupId,
|
|
37
|
+
);
|
|
38
|
+
expect(result).toBe("Deleted file: file.txt");
|
|
39
|
+
expect(deleteGroupFile).toHaveBeenCalledWith(mockDb, groupId, "file.txt");
|
|
40
|
+
expect(deleteGroupDirectory).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("falls back to deleteGroupDirectory when deleteGroupFile fails", async () => {
|
|
44
|
+
(deleteGroupFile as jest.Mock<any>).mockRejectedValueOnce(
|
|
45
|
+
new Error("Is a directory"),
|
|
46
|
+
);
|
|
47
|
+
(deleteGroupDirectory as jest.Mock<any>).mockResolvedValueOnce(undefined);
|
|
48
|
+
|
|
49
|
+
const result = await executeDeleteFile(
|
|
50
|
+
mockDb,
|
|
51
|
+
{ path: "some-dir" },
|
|
52
|
+
groupId,
|
|
53
|
+
);
|
|
54
|
+
expect(result).toBe("Deleted directory: some-dir");
|
|
55
|
+
expect(deleteGroupDirectory).toHaveBeenCalledWith(
|
|
56
|
+
mockDb,
|
|
57
|
+
groupId,
|
|
58
|
+
"some-dir",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("re-throws original error if both file and directory delete fail", async () => {
|
|
63
|
+
const fileError = new Error("File delete error");
|
|
64
|
+
(deleteGroupFile as jest.Mock<any>).mockRejectedValueOnce(fileError);
|
|
65
|
+
(deleteGroupDirectory as jest.Mock<any>).mockRejectedValueOnce(
|
|
66
|
+
new Error("Dir error"),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
await expect(
|
|
70
|
+
executeDeleteFile(mockDb, { path: "bad-path" }, groupId),
|
|
71
|
+
).rejects.toThrow("File delete error");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../../storage/readGroupFile.js", () => ({
|
|
4
|
+
readGroupFile: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
const { executeDiffFiles } = await import("./diff-files.js");
|
|
8
|
+
const { readGroupFile } = await import("../../../storage/readGroupFile.js");
|
|
9
|
+
|
|
10
|
+
describe("executeDiffFiles", () => {
|
|
11
|
+
const mockDb: any = {};
|
|
12
|
+
const groupId = "test-group";
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("returns error if path_a or path_b is missing", async () => {
|
|
19
|
+
const result1 = await executeDiffFiles(
|
|
20
|
+
mockDb,
|
|
21
|
+
{ path_a: "a.txt" },
|
|
22
|
+
groupId,
|
|
23
|
+
);
|
|
24
|
+
expect(result1).toBe("Error: path_a and path_b are required.");
|
|
25
|
+
|
|
26
|
+
const result2 = await executeDiffFiles(
|
|
27
|
+
mockDb,
|
|
28
|
+
{ path_b: "b.txt" },
|
|
29
|
+
groupId,
|
|
30
|
+
);
|
|
31
|
+
expect(result2).toBe("Error: path_a and path_b are required.");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns error when reading path_a fails", async () => {
|
|
35
|
+
(readGroupFile as jest.Mock<any>).mockRejectedValueOnce(
|
|
36
|
+
new Error("File A not found"),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const result = await executeDiffFiles(
|
|
40
|
+
mockDb,
|
|
41
|
+
{ path_a: "missing_a.txt", path_b: "b.txt" },
|
|
42
|
+
groupId,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(result).toContain(
|
|
46
|
+
"Error reading missing_a.txt: Error: File A not found",
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns error when reading path_b fails", async () => {
|
|
51
|
+
(readGroupFile as jest.Mock<any>)
|
|
52
|
+
.mockResolvedValueOnce("content a")
|
|
53
|
+
.mockRejectedValueOnce(new Error("File B not found"));
|
|
54
|
+
|
|
55
|
+
const result = await executeDiffFiles(
|
|
56
|
+
mockDb,
|
|
57
|
+
{ path_a: "a.txt", path_b: "missing_b.txt" },
|
|
58
|
+
groupId,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(result).toContain(
|
|
62
|
+
"Error reading missing_b.txt: Error: File B not found",
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns identical message when files match exactly", async () => {
|
|
67
|
+
(readGroupFile as jest.Mock<any>)
|
|
68
|
+
.mockResolvedValueOnce("line 1\nline 2")
|
|
69
|
+
.mockResolvedValueOnce("line 1\nline 2");
|
|
70
|
+
|
|
71
|
+
const result = await executeDiffFiles(
|
|
72
|
+
mockDb,
|
|
73
|
+
{ path_a: "a.txt", path_b: "b.txt" },
|
|
74
|
+
groupId,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(result).toBe("Files are identical.");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("produces unified-like line diff when files differ", async () => {
|
|
81
|
+
(readGroupFile as jest.Mock<any>)
|
|
82
|
+
.mockResolvedValueOnce("line 1\nline 2 original\nline 3")
|
|
83
|
+
.mockResolvedValueOnce("line 1\nline 2 modified\nline 3");
|
|
84
|
+
|
|
85
|
+
const result = await executeDiffFiles(
|
|
86
|
+
mockDb,
|
|
87
|
+
{ path_a: "a.txt", path_b: "b.txt" },
|
|
88
|
+
groupId,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(result).toContain("- [Line 2] line 2 original");
|
|
92
|
+
expect(result).toContain("+ [Line 2] line 2 modified");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../../storage/listGroupFiles.js", () => ({
|
|
4
|
+
listGroupFiles: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
const { executeListFiles } = await import("./list-files.js");
|
|
8
|
+
const { listGroupFiles } = await import("../../../storage/listGroupFiles.js");
|
|
9
|
+
|
|
10
|
+
describe("executeListFiles", () => {
|
|
11
|
+
const mockDb: any = {};
|
|
12
|
+
const groupId = "test-group";
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("lists entries when directory has files", async () => {
|
|
19
|
+
(listGroupFiles as jest.Mock<any>).mockResolvedValue([
|
|
20
|
+
"index.ts",
|
|
21
|
+
"utils/",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const result = await executeListFiles(mockDb, { path: "src" }, groupId);
|
|
25
|
+
expect(result).toBe("index.ts\nutils/");
|
|
26
|
+
expect(listGroupFiles).toHaveBeenCalledWith(mockDb, groupId, "src");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("defaults path to '.' and returns empty directory message when no files", async () => {
|
|
30
|
+
(listGroupFiles as jest.Mock<any>).mockResolvedValue([]);
|
|
31
|
+
|
|
32
|
+
const result = await executeListFiles(mockDb, {}, groupId);
|
|
33
|
+
expect(result).toBe("(empty directory)");
|
|
34
|
+
expect(listGroupFiles).toHaveBeenCalledWith(mockDb, groupId, ".");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../../storage/moveGroupEntry.js", () => ({
|
|
4
|
+
moveGroupEntry: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
const { executeMoveFile } = await import("./move-file.js");
|
|
8
|
+
const { moveGroupEntry } = await import("../../../storage/moveGroupEntry.js");
|
|
9
|
+
|
|
10
|
+
describe("executeMoveFile", () => {
|
|
11
|
+
const mockDb: any = {};
|
|
12
|
+
const groupId = "test-group";
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("throws error when source_path or target_path is missing", async () => {
|
|
19
|
+
await expect(
|
|
20
|
+
executeMoveFile(mockDb, { source_path: "a" }, groupId),
|
|
21
|
+
).rejects.toThrow("Missing source_path or target_path parameter");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("moves entry within same group", async () => {
|
|
25
|
+
(moveGroupEntry as jest.Mock<any>).mockResolvedValue(undefined);
|
|
26
|
+
|
|
27
|
+
const result = await executeMoveFile(
|
|
28
|
+
mockDb,
|
|
29
|
+
{ source_path: "old.md", target_path: "new.md" },
|
|
30
|
+
groupId,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(result).toBe("Moved old.md to new.md");
|
|
34
|
+
expect(moveGroupEntry).toHaveBeenCalledWith(
|
|
35
|
+
mockDb,
|
|
36
|
+
groupId,
|
|
37
|
+
groupId,
|
|
38
|
+
"old.md",
|
|
39
|
+
"new.md",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("moves entry across different groups", async () => {
|
|
44
|
+
(moveGroupEntry as jest.Mock<any>).mockResolvedValue(undefined);
|
|
45
|
+
|
|
46
|
+
const result = await executeMoveFile(
|
|
47
|
+
mockDb,
|
|
48
|
+
{
|
|
49
|
+
source_path: "old.md",
|
|
50
|
+
target_path: "new.md",
|
|
51
|
+
source_group_id: "group-a",
|
|
52
|
+
target_group_id: "group-b",
|
|
53
|
+
},
|
|
54
|
+
groupId,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(result).toBe("Moved old.md to new.md (from group-a to group-b)");
|
|
58
|
+
expect(moveGroupEntry).toHaveBeenCalledWith(
|
|
59
|
+
mockDb,
|
|
60
|
+
"group-a",
|
|
61
|
+
"group-b",
|
|
62
|
+
"old.md",
|
|
63
|
+
"new.md",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../../storage/groupFileExists.js", () => ({
|
|
4
|
+
groupFileExists: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
jest.unstable_mockModule("../../utils/post.js", () => ({
|
|
8
|
+
post: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const { executeOpenFile } = await import("./open-file.js");
|
|
12
|
+
const { groupFileExists } = await import("../../../storage/groupFileExists.js");
|
|
13
|
+
const { post } = await import("../../utils/post.js");
|
|
14
|
+
|
|
15
|
+
describe("executeOpenFile", () => {
|
|
16
|
+
const mockDb: any = {};
|
|
17
|
+
const groupId = "test-group";
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns error if path is missing or invalid", async () => {
|
|
24
|
+
const result1 = await executeOpenFile(mockDb, {}, groupId);
|
|
25
|
+
expect(result1).toBe("Error: open_file requires a valid path string.");
|
|
26
|
+
|
|
27
|
+
const result2 = await executeOpenFile(mockDb, { path: 123 }, groupId);
|
|
28
|
+
expect(result2).toBe("Error: open_file requires a valid path string.");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns error if file does not exist", async () => {
|
|
32
|
+
(groupFileExists as jest.Mock<any>).mockResolvedValue(false);
|
|
33
|
+
|
|
34
|
+
const result = await executeOpenFile(
|
|
35
|
+
mockDb,
|
|
36
|
+
{ path: "missing.txt" },
|
|
37
|
+
groupId,
|
|
38
|
+
);
|
|
39
|
+
expect(result).toBe("Error: file not found: missing.txt");
|
|
40
|
+
expect(post).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("posts open-file event and returns success when file exists", async () => {
|
|
44
|
+
(groupFileExists as jest.Mock<any>).mockResolvedValue(true);
|
|
45
|
+
|
|
46
|
+
const result = await executeOpenFile(
|
|
47
|
+
mockDb,
|
|
48
|
+
{ path: "document.pdf" },
|
|
49
|
+
groupId,
|
|
50
|
+
);
|
|
51
|
+
expect(result).toBe("Opening file in viewer: document.pdf");
|
|
52
|
+
expect(post).toHaveBeenCalledWith({
|
|
53
|
+
type: "open-file",
|
|
54
|
+
payload: { groupId, path: "document.pdf" },
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../../storage/readGroupFile.js", () => ({
|
|
4
|
+
readGroupFile: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
jest.unstable_mockModule("../../../storage/writeGroupFile.js", () => ({
|
|
8
|
+
writeGroupFile: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const { executePatchFile } = await import("./patch-file.js");
|
|
12
|
+
const { readGroupFile } = await import("../../../storage/readGroupFile.js");
|
|
13
|
+
const { writeGroupFile } = await import("../../../storage/writeGroupFile.js");
|
|
14
|
+
|
|
15
|
+
describe("executePatchFile", () => {
|
|
16
|
+
const mockDb: any = {};
|
|
17
|
+
const groupId = "test-group";
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("patches file successfully when match is unique", async () => {
|
|
24
|
+
(readGroupFile as jest.Mock<any>).mockResolvedValue(
|
|
25
|
+
"const x = 1;\nconst y = 2;\n",
|
|
26
|
+
);
|
|
27
|
+
(writeGroupFile as jest.Mock<any>).mockResolvedValue(undefined);
|
|
28
|
+
|
|
29
|
+
const result = await executePatchFile(
|
|
30
|
+
mockDb,
|
|
31
|
+
{
|
|
32
|
+
path: "test.ts",
|
|
33
|
+
old_string: "const x = 1;",
|
|
34
|
+
new_string: "const x = 42;",
|
|
35
|
+
},
|
|
36
|
+
groupId,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(result).toBe("Patched test.ts (12 chars replaced with 13 chars)");
|
|
40
|
+
expect(writeGroupFile).toHaveBeenCalledWith(
|
|
41
|
+
mockDb,
|
|
42
|
+
groupId,
|
|
43
|
+
"test.ts",
|
|
44
|
+
"const x = 42;\nconst y = 2;\n",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns error when old_string is not found", async () => {
|
|
49
|
+
(readGroupFile as jest.Mock<any>).mockResolvedValue("const a = 1;");
|
|
50
|
+
|
|
51
|
+
const result = await executePatchFile(
|
|
52
|
+
mockDb,
|
|
53
|
+
{
|
|
54
|
+
path: "test.ts",
|
|
55
|
+
old_string: "const missing = true;",
|
|
56
|
+
new_string: "const fixed = true;",
|
|
57
|
+
},
|
|
58
|
+
groupId,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(result).toBe("patch_file failed: old_string not found in test.ts");
|
|
62
|
+
expect(writeGroupFile).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("returns error when old_string matches multiple locations", async () => {
|
|
66
|
+
(readGroupFile as jest.Mock<any>).mockResolvedValue("foo bar foo baz");
|
|
67
|
+
|
|
68
|
+
const result = await executePatchFile(
|
|
69
|
+
mockDb,
|
|
70
|
+
{
|
|
71
|
+
path: "test.txt",
|
|
72
|
+
old_string: "foo",
|
|
73
|
+
new_string: "qux",
|
|
74
|
+
},
|
|
75
|
+
groupId,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect(result).toContain("matches multiple locations in test.txt");
|
|
79
|
+
expect(writeGroupFile).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../../storage/readGroupFileBytes.js", () => ({
|
|
4
|
+
readGroupFileBytes: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
const { executeReadFile } = await import("./read-file.js");
|
|
8
|
+
const { readGroupFileBytes } =
|
|
9
|
+
await import("../../../storage/readGroupFileBytes.js");
|
|
10
|
+
|
|
11
|
+
describe("executeReadFile", () => {
|
|
12
|
+
const mockDb: any = {};
|
|
13
|
+
const groupId = "test-group";
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns error when neither path nor paths is provided", async () => {
|
|
20
|
+
const result = await executeReadFile(mockDb, {}, groupId);
|
|
21
|
+
expect(result).toBe("Error: read_file requires path or paths.");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("reads a single text file successfully", async () => {
|
|
25
|
+
(readGroupFileBytes as jest.Mock<any>).mockResolvedValue(
|
|
26
|
+
new TextEncoder().encode("Hello from file!"),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const result = await executeReadFile(
|
|
30
|
+
mockDb,
|
|
31
|
+
{ path: "hello.txt" },
|
|
32
|
+
groupId,
|
|
33
|
+
);
|
|
34
|
+
expect(result).toBe("Hello from file!");
|
|
35
|
+
expect(readGroupFileBytes).toHaveBeenCalledWith(
|
|
36
|
+
mockDb,
|
|
37
|
+
groupId,
|
|
38
|
+
"hello.txt",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("handles single text file read error", async () => {
|
|
43
|
+
(readGroupFileBytes as jest.Mock<any>).mockRejectedValue(
|
|
44
|
+
new Error("File not found"),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const result = await executeReadFile(
|
|
48
|
+
mockDb,
|
|
49
|
+
{ path: "missing.txt" },
|
|
50
|
+
groupId,
|
|
51
|
+
);
|
|
52
|
+
expect(result).toBe("Error reading missing.txt: File not found");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns error for single binary content", async () => {
|
|
56
|
+
(readGroupFileBytes as jest.Mock<any>).mockResolvedValue(
|
|
57
|
+
new Uint8Array([0x00, 0x01, 0x02, 0x00, 0xff]),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const result = await executeReadFile(
|
|
61
|
+
mockDb,
|
|
62
|
+
{ path: "program.exe" },
|
|
63
|
+
groupId,
|
|
64
|
+
);
|
|
65
|
+
expect(result).toContain(
|
|
66
|
+
"is a binary file and cannot be displayed as text",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("reads a single image file as multimodal content blocks", async () => {
|
|
71
|
+
const imageBytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
72
|
+
(readGroupFileBytes as jest.Mock<any>).mockResolvedValue(imageBytes);
|
|
73
|
+
|
|
74
|
+
const result = await executeReadFile(
|
|
75
|
+
mockDb,
|
|
76
|
+
{ path: "photo.png" },
|
|
77
|
+
groupId,
|
|
78
|
+
);
|
|
79
|
+
expect(Array.isArray(result)).toBe(true);
|
|
80
|
+
const blocks = result as any[];
|
|
81
|
+
expect(blocks).toHaveLength(2);
|
|
82
|
+
expect(blocks[0].type).toBe("text");
|
|
83
|
+
expect(blocks[1].type).toBe("image");
|
|
84
|
+
expect(blocks[1].media_type).toBe("image/png");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("handles multiple files across text, image, binary, and errors", async () => {
|
|
88
|
+
(readGroupFileBytes as jest.Mock<any>).mockImplementation(
|
|
89
|
+
async (_db, _gid, path) => {
|
|
90
|
+
if (path === "file1.txt") return new TextEncoder().encode("Content 1");
|
|
91
|
+
if (path === "file2.bin")
|
|
92
|
+
return new Uint8Array([0x00, 0x01, 0x02, 0x00]);
|
|
93
|
+
throw new Error("Disk error");
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const result = await executeReadFile(
|
|
98
|
+
mockDb,
|
|
99
|
+
{ paths: ["file1.txt", "file2.bin", "image.jpg", "error.txt"] },
|
|
100
|
+
groupId,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(typeof result).toBe("string");
|
|
104
|
+
const text = result as string;
|
|
105
|
+
expect(text).toContain("--- file1.txt ---\nContent 1");
|
|
106
|
+
expect(text).toContain(
|
|
107
|
+
"--- file2.bin ---\n[Binary file: cannot display as text]",
|
|
108
|
+
);
|
|
109
|
+
expect(text).toContain(
|
|
110
|
+
"--- image.jpg ---\n[Image file: use read_file with a single path to see this image]",
|
|
111
|
+
);
|
|
112
|
+
expect(text).toContain(
|
|
113
|
+
"--- error.txt ---\nError reading error.txt: Disk error",
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
const mockGroupFileExists = jest.fn() as any;
|
|
4
|
+
const mockPost = jest.fn() as any;
|
|
5
|
+
|
|
6
|
+
jest.unstable_mockModule("../../../storage/groupFileExists.js", () => ({
|
|
7
|
+
groupFileExists: mockGroupFileExists,
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.unstable_mockModule("../../utils/post.js", () => ({
|
|
11
|
+
post: mockPost,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const { executeSendFile } = await import("./send-file.js");
|
|
15
|
+
|
|
16
|
+
describe("executeSendFile", () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns error if path is missing or not a string", async () => {
|
|
22
|
+
const res1 = await executeSendFile({} as any, {}, "peer:abc");
|
|
23
|
+
expect(res1).toBe("Error: send_file requires a valid path string.");
|
|
24
|
+
|
|
25
|
+
const res2 = await executeSendFile({} as any, { path: 123 }, "peer:abc");
|
|
26
|
+
expect(res2).toBe("Error: send_file requires a valid path string.");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("returns error if groupId is not a peer conversation", async () => {
|
|
30
|
+
const res = await executeSendFile({} as any, { path: "doc.txt" }, "main");
|
|
31
|
+
expect(res).toContain("Error: send_file only works in peer conversations");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns error on empty path after normalization", async () => {
|
|
35
|
+
const res = await executeSendFile({} as any, { path: " " }, "peer:user1");
|
|
36
|
+
expect(res).toBe("Error: send_file received an empty file path.");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns error if path contains path traversal segments", async () => {
|
|
40
|
+
const res = await executeSendFile(
|
|
41
|
+
{} as any,
|
|
42
|
+
{ path: "../secret.txt" },
|
|
43
|
+
"peer:user1",
|
|
44
|
+
);
|
|
45
|
+
expect(res).toBe("Error: send_file path cannot contain '..' segments.");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns error if file does not exist in the workspace", async () => {
|
|
49
|
+
mockGroupFileExists.mockResolvedValue(false);
|
|
50
|
+
|
|
51
|
+
const res = await executeSendFile(
|
|
52
|
+
{} as any,
|
|
53
|
+
{ path: "photos/pic.png" },
|
|
54
|
+
"peer:user1",
|
|
55
|
+
);
|
|
56
|
+
expect(res).toBe(
|
|
57
|
+
"Error: send_file could not find photos/pic.png in the workspace.",
|
|
58
|
+
);
|
|
59
|
+
expect(mockPost).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("posts send-file message and returns success response on valid peer transfer", async () => {
|
|
63
|
+
mockGroupFileExists.mockResolvedValue(true);
|
|
64
|
+
|
|
65
|
+
const res = await executeSendFile(
|
|
66
|
+
{} as any,
|
|
67
|
+
{ path: "shared/report.pdf" },
|
|
68
|
+
"peer:node-42",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(mockPost).toHaveBeenCalledWith({
|
|
72
|
+
type: "send-file",
|
|
73
|
+
payload: {
|
|
74
|
+
groupId: "peer:node-42",
|
|
75
|
+
path: "shared/report.pdf",
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
expect(res).toBe(
|
|
79
|
+
"Sending file to peer: shared/report.pdf. The transfer will proceed in the background — you can continue chatting.",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../../storage/writeGroupFile.js", () => ({
|
|
4
|
+
writeGroupFile: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
const { executeUpdateMemory } = await import("./update-memory.js");
|
|
8
|
+
const { writeGroupFile } = await import("../../../storage/writeGroupFile.js");
|
|
9
|
+
|
|
10
|
+
describe("executeUpdateMemory", () => {
|
|
11
|
+
it("writes memory content to MEMORY.md and returns confirmation", async () => {
|
|
12
|
+
(writeGroupFile as jest.Mock<any>).mockResolvedValue(undefined);
|
|
13
|
+
const mockDb: any = {};
|
|
14
|
+
|
|
15
|
+
const result = await executeUpdateMemory(
|
|
16
|
+
mockDb,
|
|
17
|
+
{ content: "# User Preferences\n- Prefers dark mode" },
|
|
18
|
+
"test-group",
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(result).toBe("Memory updated successfully.");
|
|
22
|
+
expect(writeGroupFile).toHaveBeenCalledWith(
|
|
23
|
+
mockDb,
|
|
24
|
+
"test-group",
|
|
25
|
+
"MEMORY.md",
|
|
26
|
+
"# User Preferences\n- Prefers dark mode",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../../storage/writeGroupFile.js", () => ({
|
|
4
|
+
writeGroupFile: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
const { executeWriteFile } = await import("./write-file.js");
|
|
8
|
+
const { writeGroupFile } = await import("../../../storage/writeGroupFile.js");
|
|
9
|
+
|
|
10
|
+
describe("executeWriteFile", () => {
|
|
11
|
+
it("writes file content and returns summary", async () => {
|
|
12
|
+
(writeGroupFile as jest.Mock<any>).mockResolvedValue(undefined);
|
|
13
|
+
const mockDb: any = {};
|
|
14
|
+
const result = await executeWriteFile(
|
|
15
|
+
mockDb,
|
|
16
|
+
{ path: "src/main.ts", content: "console.log('hi');" },
|
|
17
|
+
"test-group",
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(result).toBe("Written 18 bytes to src/main.ts");
|
|
21
|
+
expect(writeGroupFile).toHaveBeenCalledWith(
|
|
22
|
+
mockDb,
|
|
23
|
+
"test-group",
|
|
24
|
+
"src/main.ts",
|
|
25
|
+
"console.log('hi');",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
});
|