shadow-claw 1.23.4 → 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/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 +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-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-settings/shadow-claw-settings.html +1 -1
- package/dist/public/config-CrHchneq.js +1 -0
- package/dist/public/{configurePeerJs-B4Ym9VMj.js → configurePeerJs-CxpZeDJJ.js} +1 -1
- package/dist/public/{connections-BEn6JYdu.js → connections-DvbgdkaB.js} +1 -1
- package/dist/public/{custom-element-security-DdYlUozq.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/decisions/bundled-typescript-architecture.md +1 -1
- package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +2 -2
- package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
- package/dist/public/docs/example/article/index.html +1 -1
- 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 +1 -1
- package/dist/public/docs/skill-creator/index.html +1 -1
- 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-BQ4KzUt5.js → e2e-bridge-IGWsRl4i.js} +1 -1
- package/dist/public/file-viewer-D0eflgt6.js +1 -0
- package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
- package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
- package/dist/public/{git-DEUvQTUA.js → git-7Vq_SJSp.js} +2 -2
- package/dist/public/{iframe-sanitizer-0NS4XLOd.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
- package/dist/public/index.html +1 -1
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-llGkSJXg.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 +1 -1
- package/dist/public/main/memory/index.html +1 -1
- package/dist/public/{markdown-CyYR4App.js → markdown-BnpKxSxl.js} +1 -1
- package/dist/public/{mcp-reconnect-CSx4aMwy.js → mcp-reconnect-9emsqGRe.js} +1 -1
- package/dist/public/{memoryStorage-Dwg8NhAZ.js → memoryStorage-C0KvLNUp.js} +1 -1
- package/dist/public/{orchestrator-DRfEudXk.js → orchestrator-3h9xxHUm.js} +676 -676
- package/dist/public/orchestrator-nJj59RqD.js +15 -0
- package/dist/public/{peerjs-CurBuKvk.js → peerjs-BP4laMXj.js} +1 -1
- package/dist/public/{push-client-Dii-vPEh.js → push-client-CrjEXK3U.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-5IHaDDMJ.js → shadow-claw-CbwETBWf.js} +2 -2
- package/dist/public/{shadow-claw-accounts-CZvz4iQz.js → shadow-claw-accounts-B4sBTQEj.js} +1 -1
- package/dist/public/{shadow-claw-channels-BKXFyRS3.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
- package/dist/public/{shadow-claw-chat-CCLuhWdI.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-DV-87UUh.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-Dk2dbAUd.js → shadow-claw-file-viewer-Blf6yGVD.js} +1 -1
- package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
- package/dist/public/{shadow-claw-git-DRA7_qck.js → shadow-claw-git-v8S0JjuR.js} +2 -2
- package/dist/public/{shadow-claw-integrations-1Xu5snuV.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
- package/dist/public/{shadow-claw-llm-6Aci1BDP.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-BUjAeVXC.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
- package/dist/public/{shadow-claw-networking-FQe4nCp3.js → shadow-claw-networking-qHbP95z9.js} +1 -1
- package/dist/public/{shadow-claw-notifications-DV2wibL3.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
- package/dist/public/{shadow-claw-page-header-B63S4HTU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
- package/dist/public/{shadow-claw-pages-DE3jWj5K.js → shadow-claw-pages-DFZHhzFZ.js} +1 -1
- package/dist/public/{shadow-claw-settings-S6DAKfYx.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
- package/dist/public/{shadow-claw-storage-DaacA3_K.js → shadow-claw-storage-BFynbP0j.js} +2 -2
- package/dist/public/{shadow-claw-task-server-IjKX042p.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
- package/dist/public/{shadow-claw-tasks-Cy0JYmGU.js → shadow-claw-tasks-Caxc9atL.js} +1 -1
- package/dist/public/{shadow-claw-terminal-znAt7HyP.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
- package/dist/public/{shadow-claw-tools-Cutv9Si5.js → shadow-claw-tools-BfJx3A9W.js} +1 -1
- package/dist/public/{shadow-claw-webvm-BH8mPDEu.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
- package/dist/public/{syncWebMcpRegistration-BqJxaMGR.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.test.ts +76 -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 +273 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +408 -1
- 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.ts +10 -0
- 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/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/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-BYAuxx3j.js +0 -1
- package/dist/public/downloadGroupFile-Drdvg1Ev.js +0 -1
- package/dist/public/file-viewer-CcZYw-PS.js +0 -1
- package/dist/public/orchestrator-o9Fj67BN.js +0 -15
- package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
- package/dist/public/shadow-claw-files-BpfpJIuP.js +0 -840
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
const mockIsVMReady = jest.fn() as any;
|
|
4
|
+
|
|
5
|
+
jest.unstable_mockModule("../../../../shell/vm.js", () => ({
|
|
6
|
+
isVMReady: mockIsVMReady,
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
const { waitForVMReady } = await import("./waitForVMReady.js");
|
|
10
|
+
|
|
11
|
+
describe("waitForVMReady", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("returns true immediately if VM is already ready", async () => {
|
|
17
|
+
mockIsVMReady.mockReturnValue(true);
|
|
18
|
+
|
|
19
|
+
const result = await waitForVMReady(1000);
|
|
20
|
+
expect(result).toBe(true);
|
|
21
|
+
expect(mockIsVMReady).toHaveBeenCalledTimes(1);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("polls and returns true once VM becomes ready", async () => {
|
|
25
|
+
mockIsVMReady
|
|
26
|
+
.mockReturnValueOnce(false)
|
|
27
|
+
.mockReturnValueOnce(false)
|
|
28
|
+
.mockReturnValue(true);
|
|
29
|
+
|
|
30
|
+
const result = await waitForVMReady(500);
|
|
31
|
+
expect(result).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("times out and returns false if VM never becomes ready", async () => {
|
|
35
|
+
mockIsVMReady.mockReturnValue(false);
|
|
36
|
+
|
|
37
|
+
const result = await waitForVMReady(50);
|
|
38
|
+
expect(result).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -30,9 +30,14 @@ describe("worker/tools/email", () => {
|
|
|
30
30
|
id: "imap",
|
|
31
31
|
name: "IMAP",
|
|
32
32
|
protocol: "imap",
|
|
33
|
-
actions: [
|
|
33
|
+
actions: [
|
|
34
|
+
"messages.read",
|
|
35
|
+
"messages.send",
|
|
36
|
+
"messages.mark_read",
|
|
37
|
+
"messages.delete",
|
|
38
|
+
],
|
|
34
39
|
authTypes: ["basic_userpass", "oauth"],
|
|
35
|
-
configurableFields: [],
|
|
40
|
+
configurableFields: ["host", "port"],
|
|
36
41
|
}
|
|
37
42
|
: null,
|
|
38
43
|
);
|
|
@@ -44,7 +49,7 @@ describe("worker/tools/email", () => {
|
|
|
44
49
|
protocol: "imap",
|
|
45
50
|
actions: ["messages.read", "messages.send"],
|
|
46
51
|
authTypes: ["basic_userpass", "oauth"],
|
|
47
|
-
configurableFields: [],
|
|
52
|
+
configurableFields: ["host", "port"],
|
|
48
53
|
},
|
|
49
54
|
]);
|
|
50
55
|
|
|
@@ -119,10 +124,52 @@ describe("worker/tools/email", () => {
|
|
|
119
124
|
|
|
120
125
|
it("returns validation error when action is missing", async () => {
|
|
121
126
|
const result = await executeManageEmailTool({} as any, {}, "group-1");
|
|
122
|
-
|
|
123
127
|
expect(result).toContain("requires action");
|
|
124
128
|
});
|
|
125
129
|
|
|
130
|
+
it("lists plugins, connections and status", async () => {
|
|
131
|
+
const plugins = await executeManageEmailTool(
|
|
132
|
+
{} as any,
|
|
133
|
+
{ action: "list_plugins" },
|
|
134
|
+
"group-1",
|
|
135
|
+
);
|
|
136
|
+
expect(plugins).toContain("IMAP");
|
|
137
|
+
|
|
138
|
+
mockListEmailConnections.mockResolvedValueOnce([
|
|
139
|
+
{
|
|
140
|
+
id: "conn-1",
|
|
141
|
+
label: "Office 365",
|
|
142
|
+
pluginId: "imap",
|
|
143
|
+
enabled: true,
|
|
144
|
+
config: { host: "outlook.office365.com" },
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
const connections = await executeManageEmailTool(
|
|
149
|
+
{} as any,
|
|
150
|
+
{ action: "list_connections" },
|
|
151
|
+
"group-1",
|
|
152
|
+
);
|
|
153
|
+
expect(connections).toContain("conn-1 (Office 365)");
|
|
154
|
+
|
|
155
|
+
mockListEmailConnections.mockResolvedValueOnce([
|
|
156
|
+
{
|
|
157
|
+
id: "conn-1",
|
|
158
|
+
label: "Office 365",
|
|
159
|
+
pluginId: "imap",
|
|
160
|
+
enabled: true,
|
|
161
|
+
config: {},
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
const status = await executeManageEmailTool(
|
|
166
|
+
{} as any,
|
|
167
|
+
{ action: "status" },
|
|
168
|
+
"group-1",
|
|
169
|
+
);
|
|
170
|
+
expect(status).toContain("Configured plugin connections: 1");
|
|
171
|
+
});
|
|
172
|
+
|
|
126
173
|
it("connect stores encrypted basic credentials", async () => {
|
|
127
174
|
mockUpsertEmailConnection.mockResolvedValueOnce({
|
|
128
175
|
id: "conn-1",
|
|
@@ -156,6 +203,70 @@ describe("worker/tools/email", () => {
|
|
|
156
203
|
expect(result).toContain("Email connection created: conn-1");
|
|
157
204
|
});
|
|
158
205
|
|
|
206
|
+
it("configure, enable, disable, delete and test connections", async () => {
|
|
207
|
+
const existingConn = {
|
|
208
|
+
id: "conn-cfg",
|
|
209
|
+
label: "Old Label",
|
|
210
|
+
pluginId: "imap",
|
|
211
|
+
enabled: false,
|
|
212
|
+
config: { host: "old.host" },
|
|
213
|
+
credentialRef: {
|
|
214
|
+
username: "user@mail.com",
|
|
215
|
+
encryptedSecret: "enc:oldpw",
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
mockGetEmailConnection.mockResolvedValue(existingConn);
|
|
220
|
+
mockUpsertEmailConnection.mockImplementation(
|
|
221
|
+
async (_db: any, val: any) => val,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// configure
|
|
225
|
+
const cfgResult = await executeManageEmailTool(
|
|
226
|
+
{} as any,
|
|
227
|
+
{
|
|
228
|
+
action: "configure",
|
|
229
|
+
connection_id: "conn-cfg",
|
|
230
|
+
label: "New Label",
|
|
231
|
+
enabled: true,
|
|
232
|
+
},
|
|
233
|
+
"group-1",
|
|
234
|
+
);
|
|
235
|
+
expect(cfgResult).toContain("Integration connection updated: conn-cfg");
|
|
236
|
+
|
|
237
|
+
// enable
|
|
238
|
+
const enableResult = await executeManageEmailTool(
|
|
239
|
+
{} as any,
|
|
240
|
+
{ action: "enable", connection_id: "conn-cfg" },
|
|
241
|
+
"group-1",
|
|
242
|
+
);
|
|
243
|
+
expect(enableResult).toContain("enabled");
|
|
244
|
+
|
|
245
|
+
// disable
|
|
246
|
+
const disableResult = await executeManageEmailTool(
|
|
247
|
+
{} as any,
|
|
248
|
+
{ action: "disable", connection_id: "conn-cfg" },
|
|
249
|
+
"group-1",
|
|
250
|
+
);
|
|
251
|
+
expect(disableResult).toContain("disabled");
|
|
252
|
+
|
|
253
|
+
// test
|
|
254
|
+
const testResult = await executeManageEmailTool(
|
|
255
|
+
{} as any,
|
|
256
|
+
{ action: "test", connection_id: "conn-cfg" },
|
|
257
|
+
"group-1",
|
|
258
|
+
);
|
|
259
|
+
expect(testResult).toContain("passed for conn-cfg");
|
|
260
|
+
|
|
261
|
+
// delete
|
|
262
|
+
const deleteResult = await executeManageEmailTool(
|
|
263
|
+
{} as any,
|
|
264
|
+
{ action: "delete", connection_id: "conn-cfg" },
|
|
265
|
+
"group-1",
|
|
266
|
+
);
|
|
267
|
+
expect(deleteResult).toContain("Integration connection deleted: conn-cfg");
|
|
268
|
+
});
|
|
269
|
+
|
|
159
270
|
it("read_messages retries once after oauth 401", async () => {
|
|
160
271
|
mockGetEmailConnection.mockResolvedValueOnce({
|
|
161
272
|
id: "conn-oauth",
|
|
@@ -294,8 +405,8 @@ describe("worker/tools/email", () => {
|
|
|
294
405
|
expect(parsed.saved_paths).toEqual(["downloads/email/x.txt"]);
|
|
295
406
|
});
|
|
296
407
|
|
|
297
|
-
it("mark_as_read
|
|
298
|
-
mockGetEmailConnection.
|
|
408
|
+
it("mark_as_read and delete_messages execute flag modifications and delete requests", async () => {
|
|
409
|
+
mockGetEmailConnection.mockResolvedValue({
|
|
299
410
|
id: "conn-mark",
|
|
300
411
|
pluginId: "imap",
|
|
301
412
|
enabled: true,
|
|
@@ -307,15 +418,89 @@ describe("worker/tools/email", () => {
|
|
|
307
418
|
},
|
|
308
419
|
});
|
|
309
420
|
|
|
310
|
-
|
|
421
|
+
// mark_as_read missing uids
|
|
422
|
+
const noUidResult = await executeManageEmailTool(
|
|
423
|
+
{} as any,
|
|
424
|
+
{ action: "mark_as_read", connection_id: "conn-mark" },
|
|
425
|
+
"group-1",
|
|
426
|
+
);
|
|
427
|
+
expect(noUidResult).toContain("requires message_uids");
|
|
428
|
+
|
|
429
|
+
// mark_as_read success
|
|
430
|
+
(global.fetch as any).mockResolvedValueOnce({
|
|
431
|
+
ok: true,
|
|
432
|
+
status: 200,
|
|
433
|
+
json: async () => ({ updated: [101] }),
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const markResult = await executeManageEmailTool(
|
|
311
437
|
{} as any,
|
|
312
438
|
{
|
|
313
439
|
action: "mark_as_read",
|
|
314
440
|
connection_id: "conn-mark",
|
|
441
|
+
message_uids: [101],
|
|
315
442
|
},
|
|
316
443
|
"group-1",
|
|
317
444
|
);
|
|
445
|
+
expect(markResult).toContain('"updated": [');
|
|
446
|
+
|
|
447
|
+
// delete_messages success
|
|
448
|
+
(global.fetch as any).mockResolvedValueOnce({
|
|
449
|
+
ok: true,
|
|
450
|
+
status: 200,
|
|
451
|
+
json: async () => ({ deleted: [102] }),
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const deleteMsgResult = await executeManageEmailTool(
|
|
455
|
+
{} as any,
|
|
456
|
+
{
|
|
457
|
+
action: "delete_messages",
|
|
458
|
+
connection_id: "conn-mark",
|
|
459
|
+
message_uids: [102],
|
|
460
|
+
},
|
|
461
|
+
"group-1",
|
|
462
|
+
);
|
|
463
|
+
expect(deleteMsgResult).toContain('"deleted": [');
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("parses email attachment inputs and guesses mime types correctly", async () => {
|
|
467
|
+
const { asEmailAttachmentInputs } =
|
|
468
|
+
await import("./utils/asEmailAttachmentInputs.js");
|
|
469
|
+
const { guessMimeTypeFromFilename } =
|
|
470
|
+
await import("./utils/guessMimeTypeFromFilename.js");
|
|
471
|
+
|
|
472
|
+
// asEmailAttachmentInputs
|
|
473
|
+
expect(asEmailAttachmentInputs(null)).toEqual([]);
|
|
474
|
+
expect(
|
|
475
|
+
asEmailAttachmentInputs([
|
|
476
|
+
"file1.txt",
|
|
477
|
+
" ",
|
|
478
|
+
{
|
|
479
|
+
path: "file2.pdf",
|
|
480
|
+
filename: "doc.pdf",
|
|
481
|
+
content_type: "application/pdf",
|
|
482
|
+
},
|
|
483
|
+
{ path: "" },
|
|
484
|
+
123,
|
|
485
|
+
]),
|
|
486
|
+
).toEqual([
|
|
487
|
+
{ path: "file1.txt" },
|
|
488
|
+
{
|
|
489
|
+
path: "file2.pdf",
|
|
490
|
+
filename: "doc.pdf",
|
|
491
|
+
contentType: "application/pdf",
|
|
492
|
+
},
|
|
493
|
+
]);
|
|
318
494
|
|
|
319
|
-
|
|
495
|
+
// guessMimeTypeFromFilename
|
|
496
|
+
expect(guessMimeTypeFromFilename("photo.png")).toBe("image/png");
|
|
497
|
+
expect(guessMimeTypeFromFilename("photo.jpg")).toBe("image/jpeg");
|
|
498
|
+
expect(guessMimeTypeFromFilename("photo.jpeg")).toBe("image/jpeg");
|
|
499
|
+
expect(guessMimeTypeFromFilename("anim.gif")).toBe("image/gif");
|
|
500
|
+
expect(guessMimeTypeFromFilename("pic.webp")).toBe("image/webp");
|
|
501
|
+
expect(guessMimeTypeFromFilename("doc.pdf")).toBe("application/pdf");
|
|
502
|
+
expect(guessMimeTypeFromFilename("notes.txt")).toBe("text/plain");
|
|
503
|
+
expect(guessMimeTypeFromFilename("readme.md")).toBe("text/plain");
|
|
504
|
+
expect(guessMimeTypeFromFilename("archive.zip")).toBeUndefined();
|
|
320
505
|
});
|
|
321
506
|
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { asEmailAttachmentInputs } from "./asEmailAttachmentInputs.js";
|
|
2
|
+
|
|
3
|
+
describe("asEmailAttachmentInputs", () => {
|
|
4
|
+
it("returns empty array for non-array inputs", () => {
|
|
5
|
+
expect(asEmailAttachmentInputs(null)).toEqual([]);
|
|
6
|
+
expect(asEmailAttachmentInputs(undefined)).toEqual([]);
|
|
7
|
+
expect(asEmailAttachmentInputs("file.txt")).toEqual([]);
|
|
8
|
+
expect(asEmailAttachmentInputs({ path: "file.txt" })).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("parses string attachment items", () => {
|
|
12
|
+
const input = ["file1.txt", " ", "folder/file2.pdf"];
|
|
13
|
+
const result = asEmailAttachmentInputs(input);
|
|
14
|
+
expect(result).toEqual([
|
|
15
|
+
{ path: "file1.txt" },
|
|
16
|
+
{ path: "folder/file2.pdf" },
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("parses object attachment items with filename and content_type", () => {
|
|
21
|
+
const input = [
|
|
22
|
+
{
|
|
23
|
+
path: "docs/spec.pdf",
|
|
24
|
+
filename: "specs.pdf",
|
|
25
|
+
content_type: "application/pdf",
|
|
26
|
+
},
|
|
27
|
+
{ path: "images/logo.png" },
|
|
28
|
+
{ path: " ", filename: "ignored.txt" },
|
|
29
|
+
null,
|
|
30
|
+
123,
|
|
31
|
+
{ notPath: "ignored" },
|
|
32
|
+
];
|
|
33
|
+
const result = asEmailAttachmentInputs(input);
|
|
34
|
+
expect(result).toEqual([
|
|
35
|
+
{
|
|
36
|
+
path: "docs/spec.pdf",
|
|
37
|
+
filename: "specs.pdf",
|
|
38
|
+
contentType: "application/pdf",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: "images/logo.png",
|
|
42
|
+
filename: undefined,
|
|
43
|
+
contentType: undefined,
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -6,7 +6,11 @@ function makeDeps(overrides: Record<string, unknown> = {}) {
|
|
|
6
6
|
return {
|
|
7
7
|
getConfig: jest.fn(async () => undefined),
|
|
8
8
|
getProxyUrl: jest.fn(() => "https://proxy.local"),
|
|
9
|
-
resolveGitCredentials: jest.fn(async () => ({
|
|
9
|
+
resolveGitCredentials: jest.fn(async () => ({
|
|
10
|
+
token: "tok",
|
|
11
|
+
authorName: "Dev",
|
|
12
|
+
authorEmail: "dev@example.com",
|
|
13
|
+
})),
|
|
10
14
|
gitClone: jest.fn(async () => "demo"),
|
|
11
15
|
gitCheckout: jest.fn(async () => "checked out"),
|
|
12
16
|
gitBranch: jest.fn(async () => "branched"),
|
|
@@ -22,6 +26,15 @@ function makeDeps(overrides: Record<string, unknown> = {}) {
|
|
|
22
26
|
gitPush: jest.fn(async () => "pushed"),
|
|
23
27
|
gitMerge: jest.fn(async () => "merged"),
|
|
24
28
|
gitReset: jest.fn(async () => "reset"),
|
|
29
|
+
gitFetch: jest.fn(async () => "fetched"),
|
|
30
|
+
gitInit: jest.fn(async () => "initialized"),
|
|
31
|
+
gitTag: jest.fn(async () => "tagged"),
|
|
32
|
+
gitDeleteBranch: jest.fn(async () => "deleted branch"),
|
|
33
|
+
gitRemote: jest.fn(async () => "remote info"),
|
|
34
|
+
gitConfig: jest.fn(async () => "config val"),
|
|
35
|
+
gitUnstage: jest.fn(async () => "unstaged"),
|
|
36
|
+
gitShow: jest.fn(async () => "commit info"),
|
|
37
|
+
gitReadFileAtRef: jest.fn(async () => "file content"),
|
|
25
38
|
getRemoteUrl: jest.fn(async () => "https://example.com/repo.git"),
|
|
26
39
|
readGroupFile: jest.fn(async () => ""),
|
|
27
40
|
getGroupDir: jest.fn(async () => ({}) as any),
|
|
@@ -131,7 +144,7 @@ describe("worker/tools/git", () => {
|
|
|
131
144
|
expect(result).toBe("M file.ts");
|
|
132
145
|
});
|
|
133
146
|
|
|
134
|
-
it("calls gitCommit directly", async () => {
|
|
147
|
+
it("calls gitCommit directly and passes author info", async () => {
|
|
135
148
|
const deps = makeDeps({
|
|
136
149
|
gitCommit: jest.fn(async () => "Committed abc1234: msg"),
|
|
137
150
|
});
|
|
@@ -145,9 +158,16 @@ describe("worker/tools/git", () => {
|
|
|
145
158
|
);
|
|
146
159
|
|
|
147
160
|
expect(result).toContain("Committed");
|
|
161
|
+
expect(deps.gitCommit).toHaveBeenCalledWith(
|
|
162
|
+
expect.objectContaining({
|
|
163
|
+
authorName: "Dev",
|
|
164
|
+
authorEmail: "dev@example.com",
|
|
165
|
+
message: "msg",
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
148
168
|
});
|
|
149
169
|
|
|
150
|
-
it("returns missing-credentials message for git_push", async () => {
|
|
170
|
+
it("returns missing-credentials message for git_push when credentials missing", async () => {
|
|
151
171
|
const deps = makeDeps({
|
|
152
172
|
resolveGitCredentials: jest.fn(async () => ({})),
|
|
153
173
|
});
|
|
@@ -163,6 +183,23 @@ describe("worker/tools/git", () => {
|
|
|
163
183
|
expect(result).toContain("No git credentials configured");
|
|
164
184
|
});
|
|
165
185
|
|
|
186
|
+
it("calls git_push with valid credentials", async () => {
|
|
187
|
+
const deps = makeDeps({
|
|
188
|
+
gitPush: jest.fn(async () => "Pushed main successfully"),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = await executeGitTool(
|
|
192
|
+
{} as any,
|
|
193
|
+
"git_push",
|
|
194
|
+
{ repo: "demo", branch: "main" },
|
|
195
|
+
"group-1",
|
|
196
|
+
deps,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(result).toBe("Pushed main successfully");
|
|
200
|
+
expect(deps.gitPush).toHaveBeenCalled();
|
|
201
|
+
});
|
|
202
|
+
|
|
166
203
|
it("formats merge conflicts with inline regions", async () => {
|
|
167
204
|
const deps = makeDeps({
|
|
168
205
|
gitMerge: jest.fn(async () => {
|
|
@@ -197,4 +234,178 @@ describe("worker/tools/git", () => {
|
|
|
197
234
|
expect(result).toContain("Resolution steps:");
|
|
198
235
|
expect(result).toContain("<<<<<<< ours");
|
|
199
236
|
});
|
|
237
|
+
|
|
238
|
+
it("executes remaining git tool operations (add, branch, branches, checkout, config, etc.)", async () => {
|
|
239
|
+
const deps = makeDeps();
|
|
240
|
+
|
|
241
|
+
// git_add
|
|
242
|
+
await executeGitTool(
|
|
243
|
+
{} as any,
|
|
244
|
+
"git_add",
|
|
245
|
+
{ repo: "demo", filepath: "file.txt" },
|
|
246
|
+
"group-1",
|
|
247
|
+
deps,
|
|
248
|
+
);
|
|
249
|
+
expect(deps.gitAdd).toHaveBeenCalled();
|
|
250
|
+
|
|
251
|
+
// git_branch
|
|
252
|
+
await executeGitTool(
|
|
253
|
+
{} as any,
|
|
254
|
+
"git_branch",
|
|
255
|
+
{ repo: "demo", name: "feat" },
|
|
256
|
+
"group-1",
|
|
257
|
+
deps,
|
|
258
|
+
);
|
|
259
|
+
expect(deps.gitBranch).toHaveBeenCalled();
|
|
260
|
+
|
|
261
|
+
// git_branches
|
|
262
|
+
await executeGitTool(
|
|
263
|
+
{} as any,
|
|
264
|
+
"git_branches",
|
|
265
|
+
{ repo: "demo" },
|
|
266
|
+
"group-1",
|
|
267
|
+
deps,
|
|
268
|
+
);
|
|
269
|
+
expect(deps.gitListBranches).toHaveBeenCalled();
|
|
270
|
+
|
|
271
|
+
// git_checkout
|
|
272
|
+
await executeGitTool(
|
|
273
|
+
{} as any,
|
|
274
|
+
"git_checkout",
|
|
275
|
+
{ repo: "demo", ref: "main" },
|
|
276
|
+
"group-1",
|
|
277
|
+
deps,
|
|
278
|
+
);
|
|
279
|
+
expect(deps.gitCheckout).toHaveBeenCalled();
|
|
280
|
+
|
|
281
|
+
// git_config
|
|
282
|
+
await executeGitTool(
|
|
283
|
+
{} as any,
|
|
284
|
+
"git_config",
|
|
285
|
+
{ repo: "demo", command: "get", key: "user.name" },
|
|
286
|
+
"group-1",
|
|
287
|
+
deps,
|
|
288
|
+
);
|
|
289
|
+
expect(deps.gitConfig).toHaveBeenCalled();
|
|
290
|
+
|
|
291
|
+
// git_delete_branch
|
|
292
|
+
await executeGitTool(
|
|
293
|
+
{} as any,
|
|
294
|
+
"git_delete_branch",
|
|
295
|
+
{ repo: "demo", name: "old" },
|
|
296
|
+
"group-1",
|
|
297
|
+
deps,
|
|
298
|
+
);
|
|
299
|
+
expect(deps.gitDeleteBranch).toHaveBeenCalled();
|
|
300
|
+
|
|
301
|
+
// git_diff
|
|
302
|
+
await executeGitTool(
|
|
303
|
+
{} as any,
|
|
304
|
+
"git_diff",
|
|
305
|
+
{ repo: "demo" },
|
|
306
|
+
"group-1",
|
|
307
|
+
deps,
|
|
308
|
+
);
|
|
309
|
+
expect(deps.gitDiff).toHaveBeenCalled();
|
|
310
|
+
|
|
311
|
+
// git_fetch
|
|
312
|
+
await executeGitTool(
|
|
313
|
+
{} as any,
|
|
314
|
+
"git_fetch",
|
|
315
|
+
{ repo: "demo" },
|
|
316
|
+
"group-1",
|
|
317
|
+
deps,
|
|
318
|
+
);
|
|
319
|
+
expect(deps.gitFetch).toHaveBeenCalled();
|
|
320
|
+
|
|
321
|
+
// git_init
|
|
322
|
+
await executeGitTool(
|
|
323
|
+
{} as any,
|
|
324
|
+
"git_init",
|
|
325
|
+
{ repo: "demo" },
|
|
326
|
+
"group-1",
|
|
327
|
+
deps,
|
|
328
|
+
);
|
|
329
|
+
expect(deps.gitInit).toHaveBeenCalled();
|
|
330
|
+
|
|
331
|
+
// git_log
|
|
332
|
+
await executeGitTool(
|
|
333
|
+
{} as any,
|
|
334
|
+
"git_log",
|
|
335
|
+
{ repo: "demo" },
|
|
336
|
+
"group-1",
|
|
337
|
+
deps,
|
|
338
|
+
);
|
|
339
|
+
expect(deps.gitLog).toHaveBeenCalled();
|
|
340
|
+
|
|
341
|
+
// git_pull
|
|
342
|
+
await executeGitTool(
|
|
343
|
+
{} as any,
|
|
344
|
+
"git_pull",
|
|
345
|
+
{ repo: "demo" },
|
|
346
|
+
"group-1",
|
|
347
|
+
deps,
|
|
348
|
+
);
|
|
349
|
+
expect(deps.gitPull).toHaveBeenCalled();
|
|
350
|
+
|
|
351
|
+
// git_read_file_at_ref
|
|
352
|
+
await executeGitTool(
|
|
353
|
+
{} as any,
|
|
354
|
+
"git_read_file_at_ref",
|
|
355
|
+
{ repo: "demo", ref: "main", filepath: "a.ts" },
|
|
356
|
+
"group-1",
|
|
357
|
+
deps,
|
|
358
|
+
);
|
|
359
|
+
expect(deps.gitReadFileAtRef).toHaveBeenCalled();
|
|
360
|
+
|
|
361
|
+
// git_remote
|
|
362
|
+
await executeGitTool(
|
|
363
|
+
{} as any,
|
|
364
|
+
"git_remote",
|
|
365
|
+
{ repo: "demo", command: "list" },
|
|
366
|
+
"group-1",
|
|
367
|
+
deps,
|
|
368
|
+
);
|
|
369
|
+
expect(deps.gitRemote).toHaveBeenCalled();
|
|
370
|
+
|
|
371
|
+
// git_reset
|
|
372
|
+
await executeGitTool(
|
|
373
|
+
{} as any,
|
|
374
|
+
"git_reset",
|
|
375
|
+
{ repo: "demo", ref: "HEAD~1" },
|
|
376
|
+
"group-1",
|
|
377
|
+
deps,
|
|
378
|
+
);
|
|
379
|
+
expect(deps.gitReset).toHaveBeenCalled();
|
|
380
|
+
|
|
381
|
+
// git_show
|
|
382
|
+
await executeGitTool(
|
|
383
|
+
{} as any,
|
|
384
|
+
"git_show",
|
|
385
|
+
{ repo: "demo", ref: "HEAD" },
|
|
386
|
+
"group-1",
|
|
387
|
+
deps,
|
|
388
|
+
);
|
|
389
|
+
expect(deps.gitShow).toHaveBeenCalled();
|
|
390
|
+
|
|
391
|
+
// git_tag
|
|
392
|
+
await executeGitTool(
|
|
393
|
+
{} as any,
|
|
394
|
+
"git_tag",
|
|
395
|
+
{ repo: "demo", tag: "v1.0" },
|
|
396
|
+
"group-1",
|
|
397
|
+
deps,
|
|
398
|
+
);
|
|
399
|
+
expect(deps.gitTag).toHaveBeenCalled();
|
|
400
|
+
|
|
401
|
+
// git_unstage
|
|
402
|
+
await executeGitTool(
|
|
403
|
+
{} as any,
|
|
404
|
+
"git_unstage",
|
|
405
|
+
{ repo: "demo", filepath: "a.ts" },
|
|
406
|
+
"group-1",
|
|
407
|
+
deps,
|
|
408
|
+
);
|
|
409
|
+
expect(deps.gitUnstage).toHaveBeenCalled();
|
|
410
|
+
});
|
|
200
411
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../utils/post.js", () => ({
|
|
4
|
+
post: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
const { executeInviteToRoom } = await import("./invite-to-room.js");
|
|
8
|
+
const { post } = await import("../../utils/post.js");
|
|
9
|
+
|
|
10
|
+
describe("executeInviteToRoom", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("returns error when peer_id is missing", () => {
|
|
16
|
+
const result = executeInviteToRoom({}, "br:room-123");
|
|
17
|
+
expect(result).toBe("Error: peer_id is required to invite a participant.");
|
|
18
|
+
expect(post).not.toHaveBeenCalled();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns error when room_id is missing and groupId is not a room", () => {
|
|
22
|
+
const result = executeInviteToRoom({ peer_id: "peer-abc" }, "default");
|
|
23
|
+
expect(result).toBe(
|
|
24
|
+
"Error: no room_id provided and the current conversation is not a room.",
|
|
25
|
+
);
|
|
26
|
+
expect(post).not.toHaveBeenCalled();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("invites peer using explicit room_id", () => {
|
|
30
|
+
const result = executeInviteToRoom(
|
|
31
|
+
{ peer_id: "peer-abc", room_id: "custom-room" },
|
|
32
|
+
"default",
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(result).toBe("Invited peer peer-abc to room custom-room.");
|
|
36
|
+
expect(post).toHaveBeenCalledWith({
|
|
37
|
+
payload: { action: "invite", roomId: "custom-room", peerId: "peer-abc" },
|
|
38
|
+
type: "room-action",
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("invites peer inferring room from room groupId", () => {
|
|
43
|
+
const result = executeInviteToRoom(
|
|
44
|
+
{ peer_id: "peer-xyz" },
|
|
45
|
+
"room:active-room",
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(result).toBe("Invited peer peer-xyz to room active-room.");
|
|
49
|
+
expect(post).toHaveBeenCalledWith({
|
|
50
|
+
payload: { action: "invite", roomId: "active-room", peerId: "peer-xyz" },
|
|
51
|
+
type: "room-action",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("../../utils/post.js", () => ({
|
|
4
|
+
post: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
const { executeLeaveRoom } = await import("./leave-room.js");
|
|
8
|
+
const { post } = await import("../../utils/post.js");
|
|
9
|
+
|
|
10
|
+
describe("executeLeaveRoom", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("returns error when room_id is missing and groupId is not a room", () => {
|
|
16
|
+
const result = executeLeaveRoom({}, "main-group");
|
|
17
|
+
expect(result).toBe(
|
|
18
|
+
"Error: no room_id provided and the current conversation is not a room.",
|
|
19
|
+
);
|
|
20
|
+
expect(post).not.toHaveBeenCalled();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("leaves room with explicit room_id", () => {
|
|
24
|
+
const result = executeLeaveRoom({ room_id: "room-abc" }, "main-group");
|
|
25
|
+
expect(result).toBe("Leaving room room-abc.");
|
|
26
|
+
expect(post).toHaveBeenCalledWith({
|
|
27
|
+
payload: { action: "leave", roomId: "room-abc" },
|
|
28
|
+
type: "room-action",
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("leaves room inferring room from room groupId", () => {
|
|
33
|
+
const result = executeLeaveRoom({}, "room:current-room");
|
|
34
|
+
expect(result).toBe("Leaving room current-room.");
|
|
35
|
+
expect(post).toHaveBeenCalledWith({
|
|
36
|
+
payload: { action: "leave", roomId: "current-room" },
|
|
37
|
+
type: "room-action",
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|