chainlesschain 0.162.13 → 0.162.15
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 +1 -1
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-Bq_zxhCr.js → AIOps-Dr5poTWh.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CaevDm9t.js → ActionButton-DLOGPHQ-.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-B0gOmwPw.js → Analytics-DBlO3FPX.js} +1 -1
- package/src/assets/web-panel/assets/{AppLayout-DWhZiV0Q.js → AppLayout-BrMarmWx.js} +2 -2
- package/src/assets/web-panel/assets/{Audit-ZuZJBCxo.js → Audit-DX6omFqA.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CX7jhH5l.js → Backup-0QSLlQkB.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CVLx7HVq.js → BaseInput-Dn4GFfR8.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C6yL5tRD.js → Chat-CMTeA0Zo.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BePhbqVq.js → Checkbox-BQQxf6iA.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B5E4x1Lm.js → Codegen-D2vrazFC.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CWhNU6A7.js → Col-P1hVDVKm.js} +1 -1
- package/src/assets/web-panel/assets/{Community-mSEAuJhp.js → Community-CPcDkABv.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DSudHzX3.js → Compact-DvBQwE-b.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CxJLYjyn.js → Compliance-GX2qsHkq.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-C-trppQj.js → Cowork-CEEISrBb.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-_Ij4v5fY.js → Cron-Y3D6wB4C.js} +1 -1
- package/src/assets/web-panel/assets/{Crosschain-eLHXzp5T.js → Crosschain-CnCjDKKD.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BP04AUUB.js → DID-CJ2EFB5b.js} +1 -1
- package/src/assets/web-panel/assets/{Dashboard-CzEeQv62.js → Dashboard-CWujrs70.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-C_SGOB22.js → Dropdown-BzKSQA5P.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BgaP4BOv.js → Federation-DgaAWIwd.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BxGLLt9r.js → FormItemContext-BitgB4Gb.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Bt_uM_Gw.js → Git-Bzj4vLLf.js} +1 -1
- package/src/assets/web-panel/assets/{Governance-N2-0RG_o.js → Governance-D7DEdSBD.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-eS3g-CzP.js → Inference-CVsGLfcX.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CUuvNVah.js → KnowledgeGraph-CF6C6ZNo.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-wPbwEt2r.js → Logs-C0Er-tt8.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DH91kTwo.js → Marketplace-_E7uwUUU.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D-GyyBrI.js → McpTools-dHbktLMV.js} +1 -1
- package/src/assets/web-panel/assets/{Memory-BOtUy-tw.js → Memory-CaQ4UY7b.js} +1 -1
- package/src/assets/web-panel/assets/{MobileBridge-DIP__XQd.js → MobileBridge-BbOh0uJt.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-1nqr1UsU.js → MobileProjects-CfdNEimZ.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CCE0x7h2.js → Mtc-HLvyepRP.js} +1 -1
- package/src/assets/web-panel/assets/{MtcAudit-BBkz0XUO.js → MtcAudit-DMaHGoIr.js} +1 -1
- package/src/assets/web-panel/assets/{Multisig-CIKSJvTY.js → Multisig-DFNZf0AB.js} +1 -1
- package/src/assets/web-panel/assets/{NLProgramming-BDkgeFcq.js → NLProgramming-cZwXhWy2.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-ONiUxfN1.js → Notes-U_3n8Zid.js} +1 -1
- package/src/assets/web-panel/assets/{NotificationSettings-CGcyKEIe.js → NotificationSettings-B4VvhkKf.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BZtMYBJt.js → Organization-BIwBFrdX.js} +1 -1
- package/src/assets/web-panel/assets/{Overflow-C4LfFZAI.js → Overflow-_zxR8gF2.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D71Cpk-m.js → P2P-DJXmWhLC.js} +1 -1
- package/src/assets/web-panel/assets/{Permissions-DRGYF29v.js → Permissions-DwJ35K8Z.js} +1 -1
- package/src/assets/web-panel/assets/{PersonalDataHub-CfRYoIua.js → PersonalDataHub-CzZXLXGN.js} +1 -1
- package/src/assets/web-panel/assets/{Pipeline-BOyp0_Qo.js → Pipeline-DjtRGM8X.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-a_AcphvF.js → Privacy-BJhsIhGf.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CFu1grYt.js → ProjectInit-DEx5SDLh.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectSettings-p54Eivhh.js → ProjectSettings-B6qDg7nn.js} +1 -1
- package/src/assets/web-panel/assets/{Projects-DkB88XAu.js → Projects-CKkTgrBf.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-EK7f8DEd.js → Providers-3GEshcW_.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CpO0w3iP.js → QuickAsk-BfUXbKxa.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BUJVQdv9.js → Recommend-DgxYasSJ.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-kU2fOuZt.js → Reputation-CWvVg_V1.js} +1 -1
- package/src/assets/web-panel/assets/{Row-oGWRbk6g.js → Row-42M5ot1v.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-saC_46Yo.js → RssFeed-Do3isL1x.js} +1 -1
- package/src/assets/web-panel/assets/{Search-CjtRqFUu.js → Search-_7wgwjaR.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BX0NBVfQ.js → Security-DKB6pe8t.js} +1 -1
- package/src/assets/web-panel/assets/{Services-Bgxsnei_.js → Services-BHrBJAcB.js} +1 -1
- package/src/assets/web-panel/assets/{Skeleton-CwBb3k2a.js → Skeleton-C8L-EfGp.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CZCMYH6Z.js → Skills-CU66huTh.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Djy1uHnZ.js → Sla-DA8AKNmI.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CGUI_Uyh.js → SpeechSettings-CufjYTzV.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DK2CKHRD.js → SyncSettings-D_VAYHIg.js} +1 -1
- package/src/assets/web-panel/assets/{Tasks-BKiOzeIO.js → Tasks-BE6nYh9k.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CnQpleXj.js → Templates-BfRbttd6.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DwKz0cjm.js → Tenant-YO71CL80.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-A7t_wsR8.js → Terminal-D1mQaE_Z.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BqYY9l44.js → Tokens-CvnNQtX4.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BI4bXFmi.js → Trigger-BnN19FJt.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-yMynKTRG.js → Trust-C6oZLHqp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Br4IScM6.js → UkeySign-BOhFYuWu.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CWcThGsP.js → VideoEditing-NMc6-qc-.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CZcAtjxj.js → Wallet-BALmcKtd.js} +1 -1
- package/src/assets/web-panel/assets/{WebAuthn-BnTZFMA0.js → WebAuthn-6IVe-W6O.js} +1 -1
- package/src/assets/web-panel/assets/{WorkflowEditor-N7gGz3_n.js → WorkflowEditor-BIv20DXb.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D175ZIO0.js → chat-CiceyA69.js} +1 -1
- package/src/assets/web-panel/assets/{colors-LKhZyttv.js → colors-CxwvpRW0.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CsJSebxT.js → compact-item-N6ASDseQ.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BJ_CPYFC.js → createContext-CQ8PEhyP.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-CzD3IqH8.js → hasIn-DDiIuryC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhiZDGg7.js → index-1jBrqw2R.js} +1 -1
- package/src/assets/web-panel/assets/index-1losWCP0.js +1 -0
- package/src/assets/web-panel/assets/{index-CRGNuUIM.js → index-92sMXPmR.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dm-3kvtD.js → index-BCgZTQf8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZluCuTH.js → index-BKLatStI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTIkCKav.js → index-BKmY57ry.js} +1 -1
- package/src/assets/web-panel/assets/{index-E7t1hAnk.js → index-BZ4O1Vp7.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7ZcBI5s.js → index-BdmcwwMp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNVLVzN5.js → index-Bj-mAQFK.js} +1 -1
- package/src/assets/web-panel/assets/{index-R1cFADfk.js → index-BjvirvV4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvF2tC6C.js → index-Bw6UqIkJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-6qPbrYF7.js → index-BzdEj9_B.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMvdoiFr.js → index-C43WIe_p.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjOrt4vw.js → index-CHsNn-Qv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsirlkJ0.js → index-COIxnEwP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D401L3yx.js → index-C_dbCu-F.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDSZDDb2.js → index-Cb0QVAZL.js} +3 -3
- package/src/assets/web-panel/assets/{index-BV-__mlC.js → index-CcUK2M7W.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXp1jVsK.js → index-ClASVywF.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8kltMTW.js → index-CnArbjXg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Kn-Of5ew.js → index-Cur_KKpV.js} +1 -1
- package/src/assets/web-panel/assets/{index-RIO4JKMP.js → index-Cwk0olXV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKjBAdm0.js → index-D0yG93O4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSNibAqz.js → index-D58a5SL3.js} +1 -1
- package/src/assets/web-panel/assets/{index-uTEVWPYA.js → index-DEjAgx2R.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJVkBmSc.js → index-DUKjS4kF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmJdof_c.js → index-DWKigrAM.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2HBKw07.js → index-DcMESTJs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnPt5OdD.js → index-DgwpVvQq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOO73rHE.js → index-Dj2wgF3A.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5IZCkZG.js → index-DobYLmfZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-JTX9A7w0.js → index-Dp3r80qO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyHdYUeZ.js → index-DtpWqJ-2.js} +1 -1
- package/src/assets/web-panel/assets/index-EoP_WtDt.js +1 -0
- package/src/assets/web-panel/assets/{index-BEDFHKO3.js → index-_BHtKVC_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXH9ujMW.js → index-bYorCa3r.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dd7dICwB.js → index-hn9LVkIY.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7UYymse.js → index-mmpauJ3E.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM3uBEWD.js → index-xQlQRo2j.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CBW0okek.js → initDefaultProps-1MKZtqXZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DGAffQ0Z.js → motion-DFBQpRS8.js} +1 -1
- package/src/assets/web-panel/assets/{move-DFJ0-5IW.js → move-CiI8Ada0.js} +1 -1
- package/src/assets/web-panel/assets/{omit-AvrDghg1.js → omit-CjeFoPcC.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D7csw9i1.js → pickAttrs-Be3kefJq.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-hZ6Lg6kG.js → placementArrow-Be5Ra1_B.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DLLx5VvS.js → responsiveObserve-NCu3YHiX.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BaRIT3ev.js → slide-f23lSB4X.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Cdjyuhrz.js → statusUtils-Dq99US_U.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CbrNybTt.js → styleChecker-B-UUq5Ww.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B8gAhiRC.js → useFlexGapSupport-D6aUzeVO.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BZPy4ICP.js → useFs-DU-R5a4I.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-6Y0NDMVv.js → vnode-N7r8LSGe.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DTbMGsSH.js → zoom-07xoxB1t.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/__tests__/android.test.js +260 -0
- package/src/commands/__tests__/hub-wechat.test.js +186 -15
- package/src/commands/android.js +284 -0
- package/src/commands/hub.js +263 -19
- package/src/gateways/ws/personal-data-hub-protocol.js +28 -8
- package/src/index.js +2 -0
- package/src/lib/__tests__/cc-android-bridge.test.js +245 -0
- package/src/lib/cc-android-bridge.js +206 -0
- package/src/lib/personal-data-hub-wiring.js +98 -20
- package/src/lib/web-ui-server.js +2 -1
- package/src/assets/web-panel/assets/index-CY1mQA2I.js +0 -1
- package/src/assets/web-panel/assets/index-D9nXHfUB.js +0 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cc android` command + cc-android-bridge unit tests (Plan A A7 scaffold).
|
|
3
|
+
*
|
|
4
|
+
* These exercise the command routing without a real Android device. The
|
|
5
|
+
* `_deps` injection seam swaps the bridge module with a mock so we hit both
|
|
6
|
+
* the success path (bridge resolves) and error path (bridge throws
|
|
7
|
+
* AndroidBridgeUnavailableError, which on a real non-Android host is what
|
|
8
|
+
* actually happens).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
12
|
+
|
|
13
|
+
import * as androidCmd from "../android.js";
|
|
14
|
+
import * as bridge from "../../lib/cc-android-bridge.js";
|
|
15
|
+
|
|
16
|
+
let logSpy, errSpy, exitSpy, jsonSpy;
|
|
17
|
+
|
|
18
|
+
function withMockBridge(invokeResult) {
|
|
19
|
+
const mock = {
|
|
20
|
+
invoke: vi.fn(async (method, params) => {
|
|
21
|
+
if (invokeResult instanceof Error) throw invokeResult;
|
|
22
|
+
if (typeof invokeResult === "function")
|
|
23
|
+
return await invokeResult(method, params);
|
|
24
|
+
return invokeResult;
|
|
25
|
+
}),
|
|
26
|
+
caps: vi.fn(() => ({ available: true, reason: "mock" })),
|
|
27
|
+
AndroidBridgeUnavailableError: bridge.AndroidBridgeUnavailableError,
|
|
28
|
+
};
|
|
29
|
+
androidCmd._deps.bridge = mock;
|
|
30
|
+
return mock;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
35
|
+
errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
36
|
+
exitSpy = vi.spyOn(process, "exit").mockImplementation((code) => {
|
|
37
|
+
throw new Error(`__exit:${code}`);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
androidCmd._deps.bridge = bridge;
|
|
43
|
+
vi.restoreAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ─── cc-android-bridge ────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
describe("cc-android-bridge", () => {
|
|
49
|
+
it("detectAndroid returns false on a vanilla non-Android host", () => {
|
|
50
|
+
// Test runs on win32 / linux / darwin — not android.
|
|
51
|
+
expect(bridge.detectAndroid()).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("detectAndroid returns true under CC_ANDROID_BRIDGE_OVERRIDE=1", () => {
|
|
55
|
+
process.env.CC_ANDROID_BRIDGE_OVERRIDE = "1";
|
|
56
|
+
try {
|
|
57
|
+
expect(bridge.detectAndroid()).toBe(true);
|
|
58
|
+
} finally {
|
|
59
|
+
delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("invoke rejects with ANDROID_BRIDGE_NOT_AVAILABLE on non-Android host", async () => {
|
|
64
|
+
await expect(bridge.invoke("contacts.query", {})).rejects.toMatchObject({
|
|
65
|
+
code: "ANDROID_BRIDGE_NOT_AVAILABLE",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("invoke routes through testInvoke when override is set", async () => {
|
|
70
|
+
process.env.CC_ANDROID_BRIDGE_OVERRIDE = "1";
|
|
71
|
+
const original = bridge._deps.testInvoke;
|
|
72
|
+
bridge._deps.testInvoke = vi.fn(async (m, p) => ({ echo: m, params: p }));
|
|
73
|
+
try {
|
|
74
|
+
const r = await bridge.invoke("foo.bar", { x: 1 });
|
|
75
|
+
expect(r).toEqual({ echo: "foo.bar", params: { x: 1 } });
|
|
76
|
+
expect(bridge._deps.testInvoke).toHaveBeenCalledWith("foo.bar", { x: 1 });
|
|
77
|
+
} finally {
|
|
78
|
+
bridge._deps.testInvoke = original;
|
|
79
|
+
delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("invoke rejects empty method", async () => {
|
|
84
|
+
await expect(bridge.invoke("")).rejects.toThrow(/non-empty string/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("caps reports unavailable on non-Android host", () => {
|
|
88
|
+
const c = bridge.caps();
|
|
89
|
+
expect(c.available).toBe(false);
|
|
90
|
+
expect(c.reason).toMatch(/not-on-android/);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ─── cmd routing — bridge success path ─────────────────────────────────
|
|
95
|
+
|
|
96
|
+
describe("cc android commands — success path (mocked bridge)", () => {
|
|
97
|
+
it("cmdCaps prints available when bridge.caps returns available", async () => {
|
|
98
|
+
withMockBridge({});
|
|
99
|
+
await androidCmd._cmds.cmdCaps({ json: true });
|
|
100
|
+
expect(logSpy).toHaveBeenCalled();
|
|
101
|
+
const out = logSpy.mock.calls[0][0];
|
|
102
|
+
expect(JSON.parse(out).available).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("cmdContactsPull invokes contacts.query with --since", async () => {
|
|
106
|
+
const mock = withMockBridge({ ingested: 42, events: [] });
|
|
107
|
+
await androidCmd._cmds.cmdContactsPull({
|
|
108
|
+
json: true,
|
|
109
|
+
since: "1700000000000",
|
|
110
|
+
});
|
|
111
|
+
expect(mock.invoke).toHaveBeenCalledWith("contacts.query", {
|
|
112
|
+
since: 1700000000000,
|
|
113
|
+
});
|
|
114
|
+
const out = JSON.parse(logSpy.mock.calls[0][0]);
|
|
115
|
+
expect(out.ingested).toBe(42);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("cmdSmsPull invokes sms.query", async () => {
|
|
119
|
+
const mock = withMockBridge({ ok: true });
|
|
120
|
+
await androidCmd._cmds.cmdSmsPull({ json: true });
|
|
121
|
+
expect(mock.invoke).toHaveBeenCalledWith("sms.query", { since: undefined });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("cmdCallsPull invokes calls.query", async () => {
|
|
125
|
+
const mock = withMockBridge({ ok: true });
|
|
126
|
+
await androidCmd._cmds.cmdCallsPull({ json: true });
|
|
127
|
+
expect(mock.invoke).toHaveBeenCalledWith("calls.query", {
|
|
128
|
+
since: undefined,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("cmdAppList passes includeSystem flag", async () => {
|
|
133
|
+
const mock = withMockBridge([{ pkg: "com.foo" }]);
|
|
134
|
+
await androidCmd._cmds.cmdAppList({ json: true, system: true });
|
|
135
|
+
expect(mock.invoke).toHaveBeenCalledWith("app.list", {
|
|
136
|
+
includeSystem: true,
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("cmdAppLaunch forwards pkg", async () => {
|
|
141
|
+
const mock = withMockBridge({ ok: true });
|
|
142
|
+
await androidCmd._cmds.cmdAppLaunch("com.tencent.mm", { json: true });
|
|
143
|
+
expect(mock.invoke).toHaveBeenCalledWith("app.launch", {
|
|
144
|
+
pkg: "com.tencent.mm",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("cmdAppIntent parses --extra K=V repeatable", async () => {
|
|
149
|
+
const mock = withMockBridge({ ok: true });
|
|
150
|
+
await androidCmd._cmds.cmdAppIntent("com.foo", "VIEW", {
|
|
151
|
+
json: true,
|
|
152
|
+
extra: ["url=https://x", "id=42"],
|
|
153
|
+
});
|
|
154
|
+
expect(mock.invoke).toHaveBeenCalledWith("app.intent", {
|
|
155
|
+
pkg: "com.foo",
|
|
156
|
+
action: "VIEW",
|
|
157
|
+
extras: { url: "https://x", id: "42" },
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("cmdFsRead forwards SAF tree URI", async () => {
|
|
162
|
+
const mock = withMockBridge({ bytes: 100 });
|
|
163
|
+
await androidCmd._cmds.cmdFsRead(
|
|
164
|
+
"content://com.android.externalstorage.documents/tree/abc",
|
|
165
|
+
{
|
|
166
|
+
json: true,
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
expect(mock.invoke).toHaveBeenCalledWith("fs.read", {
|
|
170
|
+
target: "content://com.android.externalstorage.documents/tree/abc",
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("cmdFsList invokes fs.list", async () => {
|
|
175
|
+
const mock = withMockBridge([]);
|
|
176
|
+
await androidCmd._cmds.cmdFsList("/sdcard/Download/", { json: true });
|
|
177
|
+
expect(mock.invoke).toHaveBeenCalledWith("fs.list", {
|
|
178
|
+
target: "/sdcard/Download/",
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("cmdA11yQuery forwards --filter", async () => {
|
|
183
|
+
const mock = withMockBridge({ nodes: [] });
|
|
184
|
+
await androidCmd._cmds.cmdA11yQuery({ json: true, filter: "button" });
|
|
185
|
+
expect(mock.invoke).toHaveBeenCalledWith("a11y.query", {
|
|
186
|
+
filter: "button",
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("cmdA11yClick forwards nodeId", async () => {
|
|
191
|
+
const mock = withMockBridge({ ok: true });
|
|
192
|
+
await androidCmd._cmds.cmdA11yClick("node-7", { json: true });
|
|
193
|
+
expect(mock.invoke).toHaveBeenCalledWith("a11y.click", {
|
|
194
|
+
nodeId: "node-7",
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("cmdA11yType forwards text", async () => {
|
|
199
|
+
const mock = withMockBridge({ ok: true });
|
|
200
|
+
await androidCmd._cmds.cmdA11yType("hello", { json: true });
|
|
201
|
+
expect(mock.invoke).toHaveBeenCalledWith("a11y.type", { text: "hello" });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("cmdShizukuExec forwards cmd", async () => {
|
|
205
|
+
const mock = withMockBridge({ stdout: "ok" });
|
|
206
|
+
await androidCmd._cmds.cmdShizukuExec("pm list packages", { json: true });
|
|
207
|
+
expect(mock.invoke).toHaveBeenCalledWith("shizuku.exec", {
|
|
208
|
+
cmd: "pm list packages",
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("cmdRootExec forwards cmd", async () => {
|
|
213
|
+
const mock = withMockBridge({ stdout: "" });
|
|
214
|
+
await androidCmd._cmds.cmdRootExec("ls /data/data", { json: true });
|
|
215
|
+
expect(mock.invoke).toHaveBeenCalledWith("root.exec", {
|
|
216
|
+
cmd: "ls /data/data",
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("cmdPerms forwards permission name", async () => {
|
|
221
|
+
const mock = withMockBridge({ granted: false });
|
|
222
|
+
await androidCmd._cmds.cmdPerms("READ_CONTACTS", { json: true });
|
|
223
|
+
expect(mock.invoke).toHaveBeenCalledWith("perms.check", {
|
|
224
|
+
name: "READ_CONTACTS",
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ─── cmd routing — bridge error path ──────────────────────────────────
|
|
230
|
+
|
|
231
|
+
describe("cc android commands — error path", () => {
|
|
232
|
+
it("propagates ANDROID_BRIDGE_NOT_AVAILABLE as JSON when --json", async () => {
|
|
233
|
+
withMockBridge(
|
|
234
|
+
new bridge.AndroidBridgeUnavailableError("test-not-on-android"),
|
|
235
|
+
);
|
|
236
|
+
await expect(
|
|
237
|
+
androidCmd._cmds.cmdContactsPull({ json: true }),
|
|
238
|
+
).rejects.toThrow(/__exit:1/);
|
|
239
|
+
const out = JSON.parse(logSpy.mock.calls[0][0]);
|
|
240
|
+
expect(out.code).toBe("ANDROID_BRIDGE_NOT_AVAILABLE");
|
|
241
|
+
expect(out.error).toMatch(/test-not-on-android/);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("propagates error human-readable on non-JSON", async () => {
|
|
245
|
+
withMockBridge(new bridge.AndroidBridgeUnavailableError("nope"));
|
|
246
|
+
await expect(androidCmd._cmds.cmdSmsPull({ json: false })).rejects.toThrow(
|
|
247
|
+
/__exit:1/,
|
|
248
|
+
);
|
|
249
|
+
expect(errSpy).toHaveBeenCalled();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("non-bridge errors still surface", async () => {
|
|
253
|
+
withMockBridge(new Error("network timeout"));
|
|
254
|
+
await expect(androidCmd._cmds.cmdAppList({ json: true })).rejects.toThrow(
|
|
255
|
+
/__exit:1/,
|
|
256
|
+
);
|
|
257
|
+
const out = JSON.parse(logSpy.mock.calls[0][0]);
|
|
258
|
+
expect(out.error).toMatch(/network timeout/);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
@@ -41,19 +41,27 @@ function fakeHub(overrides = {}) {
|
|
|
41
41
|
...overrides.register,
|
|
42
42
|
};
|
|
43
43
|
}),
|
|
44
|
-
listWechatAccounts: vi.fn(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
listWechatAccounts: vi.fn(
|
|
45
|
+
() =>
|
|
46
|
+
overrides.list || [
|
|
47
|
+
{
|
|
48
|
+
uin: "1234567890",
|
|
49
|
+
dbPath: "/tmp/EnMicroMsg.db",
|
|
50
|
+
hasWechatDataPath: true,
|
|
51
|
+
chosenKeyProvider: "md5",
|
|
52
|
+
registeredAt: 1716280000000,
|
|
53
|
+
lastSyncAt: null,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
),
|
|
54
57
|
unregisterWechatAdapter: vi.fn(async (uin) => {
|
|
55
58
|
if (!uin) return { ok: false, reason: "UIN_REQUIRED" };
|
|
56
|
-
return {
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
removed: uin === "1234567890",
|
|
62
|
+
uin,
|
|
63
|
+
...overrides.unregister,
|
|
64
|
+
};
|
|
57
65
|
}),
|
|
58
66
|
};
|
|
59
67
|
}
|
|
@@ -106,9 +114,21 @@ describe("cc hub wechat register", () => {
|
|
|
106
114
|
_internal.cmdWechatRegister({ _getHub: async () => hub, json: true }),
|
|
107
115
|
).rejects.toThrow(/process\.exit/);
|
|
108
116
|
const allOut =
|
|
109
|
-
logSpy.mock.calls
|
|
117
|
+
logSpy.mock.calls
|
|
118
|
+
.map((c) =>
|
|
119
|
+
c
|
|
120
|
+
.map((p) => (typeof p === "string" ? p : JSON.stringify(p)))
|
|
121
|
+
.join(" "),
|
|
122
|
+
)
|
|
123
|
+
.join("\n") +
|
|
110
124
|
"\n" +
|
|
111
|
-
errSpy.mock.calls
|
|
125
|
+
errSpy.mock.calls
|
|
126
|
+
.map((c) =>
|
|
127
|
+
c
|
|
128
|
+
.map((p) => (typeof p === "string" ? p : JSON.stringify(p)))
|
|
129
|
+
.join(" "),
|
|
130
|
+
)
|
|
131
|
+
.join("\n");
|
|
112
132
|
expect(allOut).toMatch(/--uin|uin/i);
|
|
113
133
|
});
|
|
114
134
|
|
|
@@ -217,7 +237,9 @@ describe("cc hub wechat list", () => {
|
|
|
217
237
|
describe("cc hub wechat unregister", () => {
|
|
218
238
|
it("calls hub with uin + reports removed", async () => {
|
|
219
239
|
const hub = fakeHub();
|
|
220
|
-
await _internal.cmdWechatUnregister("1234567890", {
|
|
240
|
+
await _internal.cmdWechatUnregister("1234567890", {
|
|
241
|
+
_getHub: async () => hub,
|
|
242
|
+
});
|
|
221
243
|
expect(hub.unregisterWechatAdapter).toHaveBeenCalledWith("1234567890");
|
|
222
244
|
const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
|
|
223
245
|
expect(out).toMatch(/removed wechat account.*1234567890/);
|
|
@@ -238,6 +260,155 @@ describe("cc hub wechat unregister", () => {
|
|
|
238
260
|
});
|
|
239
261
|
const out = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
240
262
|
const parsed = JSON.parse(out);
|
|
241
|
-
expect(parsed).toEqual(
|
|
263
|
+
expect(parsed).toEqual(
|
|
264
|
+
expect.objectContaining({ ok: true, removed: true, uin: "1234567890" }),
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ─── Phase 12.9 — `cc hub wechat doctor` ──────────────────────────────
|
|
270
|
+
|
|
271
|
+
describe("interpretWechatProbe (pure)", () => {
|
|
272
|
+
const { interpretWechatProbe } = _internal;
|
|
273
|
+
|
|
274
|
+
it("device unreachable → blocked + adb hint", () => {
|
|
275
|
+
const r = interpretWechatProbe({
|
|
276
|
+
device: { reachable: false },
|
|
277
|
+
wechat: {},
|
|
278
|
+
root: {},
|
|
279
|
+
frida: {},
|
|
280
|
+
});
|
|
281
|
+
expect(r.readiness).toBe("blocked");
|
|
282
|
+
expect(r.blockers.join(" ")).toMatch(/adb/);
|
|
283
|
+
expect(r.nextSteps.join(" ")).toMatch(/adb devices/);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("WeChat not installed → blocked + install hint", () => {
|
|
287
|
+
const r = interpretWechatProbe({
|
|
288
|
+
device: { reachable: true },
|
|
289
|
+
wechat: { installed: false },
|
|
290
|
+
root: {},
|
|
291
|
+
frida: {},
|
|
292
|
+
});
|
|
293
|
+
expect(r.readiness).toBe("blocked");
|
|
294
|
+
expect(r.blockers.join(" ")).toMatch(/com\.tencent\.mm/);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("WeChat < 8 + md5 path → ready with adb pull instructions", () => {
|
|
298
|
+
const r = interpretWechatProbe({
|
|
299
|
+
device: { reachable: true },
|
|
300
|
+
wechat: { installed: true, majorVersion: 7, versionName: "7.0.22" },
|
|
301
|
+
root: { detected: false },
|
|
302
|
+
frida: {},
|
|
303
|
+
suggestedKeyProvider: "md5",
|
|
304
|
+
});
|
|
305
|
+
expect(r.readiness).toBe("ready");
|
|
306
|
+
expect(r.nextSteps.some((s) => /adb pull/.test(s))).toBe(true);
|
|
307
|
+
expect(r.nextSteps.some((s) => /cc hub wechat register/.test(s))).toBe(
|
|
308
|
+
true,
|
|
309
|
+
);
|
|
310
|
+
// non-root warning when MD5 path lacks root (backup subset only)
|
|
311
|
+
expect(r.warnings.some((w) => /非 root|backup 子集/.test(w))).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("WeChat ≥ 8 + no root → blocked with root requirement", () => {
|
|
315
|
+
const r = interpretWechatProbe({
|
|
316
|
+
device: { reachable: true },
|
|
317
|
+
wechat: { installed: true, majorVersion: 8, versionName: "8.0.45" },
|
|
318
|
+
root: { detected: false },
|
|
319
|
+
frida: { serverRunning: false },
|
|
320
|
+
suggestedKeyProvider: "frida",
|
|
321
|
+
});
|
|
322
|
+
expect(r.readiness).toBe("blocked");
|
|
323
|
+
expect(r.blockers.join(" ")).toMatch(/root/);
|
|
324
|
+
expect(r.nextSteps.join(" ")).toMatch(/Magisk/);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("WeChat ≥ 8 + root + no frida → partial with setup pointer", () => {
|
|
328
|
+
const r = interpretWechatProbe({
|
|
329
|
+
device: { reachable: true },
|
|
330
|
+
wechat: { installed: true, majorVersion: 8, versionName: "8.0.45" },
|
|
331
|
+
root: { detected: true },
|
|
332
|
+
frida: { serverRunning: false },
|
|
333
|
+
suggestedKeyProvider: "frida",
|
|
334
|
+
});
|
|
335
|
+
expect(r.readiness).toBe("partial");
|
|
336
|
+
expect(r.blockers.join(" ")).toMatch(/Frida server/i);
|
|
337
|
+
expect(r.nextSteps.join(" ")).toMatch(/Frida_Setup/);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("WeChat ≥ 8 + root + frida running → ready", () => {
|
|
341
|
+
const r = interpretWechatProbe({
|
|
342
|
+
device: { reachable: true },
|
|
343
|
+
wechat: { installed: true, majorVersion: 8, versionName: "8.0.45" },
|
|
344
|
+
root: { detected: true },
|
|
345
|
+
frida: { serverRunning: true, port: 27042 },
|
|
346
|
+
suggestedKeyProvider: "frida",
|
|
347
|
+
});
|
|
348
|
+
expect(r.readiness).toBe("ready");
|
|
349
|
+
expect(r.nextSteps.some((s) => /register/.test(s))).toBe(true);
|
|
350
|
+
expect(r.nextSteps.some((s) => /前台运行/.test(s))).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("env-probe judges unsupported → blocked with reasons", () => {
|
|
354
|
+
const r = interpretWechatProbe({
|
|
355
|
+
device: { reachable: true },
|
|
356
|
+
wechat: { installed: true, majorVersion: 8, versionName: "8.0.50" },
|
|
357
|
+
root: { detected: true },
|
|
358
|
+
frida: { serverRunning: true },
|
|
359
|
+
suggestedKeyProvider: "unsupported",
|
|
360
|
+
reasons: [
|
|
361
|
+
"MMKV-only storage on this build — neither MD5 nor Frida path works",
|
|
362
|
+
],
|
|
363
|
+
});
|
|
364
|
+
expect(r.readiness).toBe("blocked");
|
|
365
|
+
expect(r.blockers.join(" ")).toMatch(/MMKV-only/);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("cc hub wechat doctor", () => {
|
|
370
|
+
it("--json includes probe + doctor.readiness/blockers/nextSteps", async () => {
|
|
371
|
+
const hub = fakeHub();
|
|
372
|
+
await _internal.cmdWechatDoctor({ _getHub: async () => hub, json: true });
|
|
373
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join("\n");
|
|
374
|
+
const parsed = JSON.parse(out);
|
|
375
|
+
expect(parsed.probe).toEqual(
|
|
376
|
+
expect.objectContaining({ suggestedKeyProvider: "md5" }),
|
|
377
|
+
);
|
|
378
|
+
expect(parsed.doctor).toEqual(
|
|
379
|
+
expect.objectContaining({
|
|
380
|
+
readiness: "ready",
|
|
381
|
+
nextSteps: expect.any(Array),
|
|
382
|
+
}),
|
|
383
|
+
);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("human-readable surfaces trap table reference + telemetry capture cmd", async () => {
|
|
387
|
+
const hub = fakeHub();
|
|
388
|
+
await _internal.cmdWechatDoctor({ _getHub: async () => hub });
|
|
389
|
+
const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
|
|
390
|
+
expect(out).toMatch(/Doctor:/);
|
|
391
|
+
expect(out).toMatch(/READY|PARTIAL|BLOCKED/);
|
|
392
|
+
expect(out).toMatch(/fridaTelemetry/);
|
|
393
|
+
expect(out).toMatch(/trap table|§5\.1|Runbook/i);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("device unreachable: omits telemetry capture section (no point if blocked)", async () => {
|
|
397
|
+
const hub = fakeHub({
|
|
398
|
+
probe: {
|
|
399
|
+
ok: false,
|
|
400
|
+
suggestedKeyProvider: "unsupported",
|
|
401
|
+
device: { reachable: false },
|
|
402
|
+
wechat: {},
|
|
403
|
+
root: {},
|
|
404
|
+
frida: {},
|
|
405
|
+
reasons: [],
|
|
406
|
+
warnings: [],
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
await _internal.cmdWechatDoctor({ _getHub: async () => hub });
|
|
410
|
+
const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
|
|
411
|
+
expect(out).toMatch(/BLOCKED/);
|
|
412
|
+
expect(out).not.toMatch(/fridaTelemetry/);
|
|
242
413
|
});
|
|
243
414
|
});
|