chainlesschain 0.162.20 → 0.162.26

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.
Files changed (156) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-Buh_mYlF.js → AIOps-DEhhAYYj.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-Dc7c0bZf.js → ActionButton-DEyfUz5F.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CR9TWfM-.js → Analytics-D1rjS61W.js} +1 -1
  6. package/src/assets/web-panel/assets/{AppLayout-DVfanUep.js → AppLayout-CptFJPy6.js} +2 -2
  7. package/src/assets/web-panel/assets/{Audit-BxojBqRe.js → Audit-BG6KQUHY.js} +1 -1
  8. package/src/assets/web-panel/assets/{Backup-CGkreaK9.js → Backup-D2ikQT54.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-B-Q2ZGfz.js → BaseInput-BG_d8yMt.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-Dd_VC5Av.js → Chat-CcHmGkLY.js} +1 -1
  11. package/src/assets/web-panel/assets/{ChatBubbleRenderer-B64kZfRH.js → ChatBubbleRenderer-DIXrOHbn.js} +1 -1
  12. package/src/assets/web-panel/assets/{Checkbox-NYBD1rCW.js → Checkbox-Bx2h6vT3.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-B3Kqhgv1.js → Codegen-o14zzfuK.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-2A3Bg2GC.js → Col-HlZjxy7V.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-C84SkDEJ.js → Community-DXxMflr2.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-Crv3pyZJ.js → Compact-DbswbBjd.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compliance-D4qasxoY.js → Compliance-C7Q4Da2p.js} +1 -1
  18. package/src/assets/web-panel/assets/{Cowork-Bfbvysw-.js → Cowork-BLImywOe.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cron-Dkjyiekd.js → Cron-DpXu1G4m.js} +1 -1
  20. package/src/assets/web-panel/assets/{Crosschain-DbJm9Kjd.js → Crosschain-C4qokeJu.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-C2HI6acG.js → DID-C0BjPiij.js} +1 -1
  22. package/src/assets/web-panel/assets/{Dashboard-BXMY7QVP.js → Dashboard-CsfDXwPl.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-DcOykpfh.js → Dropdown-CiwMUKrx.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-Doo52F2M.js → EmailListRenderer-DX7xfm5a.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-cHubZT6U.js → Federation-CA4Ibsg0.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-B5OVlspi.js → FormItemContext-DvM0lU-7.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-OgA46PJq.js → GenericCardRenderer-REjsnTHA.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-_OKH_JOq.js → Git-ZhnSDQGH.js} +1 -1
  29. package/src/assets/web-panel/assets/{Governance-CTByBwSV.js → Governance-BL8iUV_g.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-C0nqgySA.js → Inference-BONrgLk-.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-CWW4ZIqH.js → KnowledgeGraph-q9SAz7h7.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-DrwqUltr.js → Logs-eHfEFnEZ.js} +1 -1
  33. package/src/assets/web-panel/assets/{Marketplace-LJMRx-dm.js → Marketplace-DzwuVedm.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-F_EjC8j3.js → McpTools-BRoLIeq5.js} +1 -1
  35. package/src/assets/web-panel/assets/{Memory-VonDem9-.js → Memory-DjvAa1Ce.js} +1 -1
  36. package/src/assets/web-panel/assets/{MobileBridge-CUWxIrfb.js → MobileBridge-CGTanjsT.js} +1 -1
  37. package/src/assets/web-panel/assets/{MobileProjects-CGOo70lk.js → MobileProjects-cTccXHwN.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-CqUquFbQ.js → Mtc-CNUe29wS.js} +1 -1
  39. package/src/assets/web-panel/assets/{MtcAudit-CRVusaip.js → MtcAudit-COk24mdH.js} +1 -1
  40. package/src/assets/web-panel/assets/{Multisig-B0o8Mv8T.js → Multisig-CFq2j0BU.js} +1 -1
  41. package/src/assets/web-panel/assets/{NLProgramming-CxQINUhu.js → NLProgramming-dfRr-FGW.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-B6Q_apFW.js → Notes-iM4aoXiw.js} +1 -1
  43. package/src/assets/web-panel/assets/{NotificationSettings-DO-lgp6x.js → NotificationSettings-CeVJ-942.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-DxoBoMtz.js → OrderTableRenderer-zQ5jvUZI.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-DqJ_KawH.js → Organization-CTSWyMGT.js} +1 -1
  46. package/src/assets/web-panel/assets/{Overflow-Cg4iKODM.js → Overflow---n60sAp.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-n5GPMrwC.js → P2P-BCQurpAB.js} +1 -1
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-BdZI0EJs.js → PdhVaultBrowser-C5L1qAc0.js} +2 -2
  49. package/src/assets/web-panel/assets/{Permissions-BjtwAIGb.js → Permissions-I8sgLLE5.js} +1 -1
  50. package/src/assets/web-panel/assets/PersonalDataHub-CEUfFRCj.js +2 -0
  51. package/src/assets/web-panel/assets/{PersonalDataHub-D0ncF92t.css → PersonalDataHub-CO9-IYDY.css} +1 -1
  52. package/src/assets/web-panel/assets/{Pipeline-BZe2-Flj.js → Pipeline-BTBjJaRd.js} +1 -1
  53. package/src/assets/web-panel/assets/{Privacy-OJqMon4c.js → Privacy-aCs44rpW.js} +1 -1
  54. package/src/assets/web-panel/assets/{ProjectInit-Dw-5SMG1.js → ProjectInit-DZ2iIlbN.js} +1 -1
  55. package/src/assets/web-panel/assets/{ProjectSettings-DxqW44cV.js → ProjectSettings-oYUtNgHP.js} +1 -1
  56. package/src/assets/web-panel/assets/{Projects-CPOqs2ec.js → Projects-FonFg0ie.js} +1 -1
  57. package/src/assets/web-panel/assets/{Providers-hBfwscEl.js → Providers-CRmOuabY.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-DA3WFFAr.js → QuickAsk-BHd6UZ1A.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-DMpz3URg.js → Recommend-BkzGGuq-.js} +1 -1
  60. package/src/assets/web-panel/assets/{Reputation-6-z56FwC.js → Reputation-Do7cPr9u.js} +1 -1
  61. package/src/assets/web-panel/assets/{Row-CL1Y6SfW.js → Row-D_sR3MmD.js} +1 -1
  62. package/src/assets/web-panel/assets/{RssFeed-BIHU2bMf.js → RssFeed-pWSbZ06l.js} +1 -1
  63. package/src/assets/web-panel/assets/{Search-CRAfuEX-.js → Search-BTL71JSs.js} +1 -1
  64. package/src/assets/web-panel/assets/{Security-CVya3gvH.js → Security-8lbNoqY6.js} +1 -1
  65. package/src/assets/web-panel/assets/{Services-Cxv7_umF.js → Services-yr3QVduo.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skeleton-D_GvsCtv.js → Skeleton-DdnY1ZTz.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-DyiEpcvf.js → Skills-fk24WY3D.js} +1 -1
  68. package/src/assets/web-panel/assets/{Sla-wiglQzXV.js → Sla-bOXRT7wR.js} +1 -1
  69. package/src/assets/web-panel/assets/{SpeechSettings-CLtovaa5.js → SpeechSettings-DjonFUbP.js} +1 -1
  70. package/src/assets/web-panel/assets/{SyncSettings-BOgVef1O.js → SyncSettings-C-7HTl6d.js} +1 -1
  71. package/src/assets/web-panel/assets/{Tasks-Ik8AX7-J.js → Tasks-DDohfzFT.js} +1 -1
  72. package/src/assets/web-panel/assets/{Templates-CireiINW.js → Templates-C8PI_WTt.js} +1 -1
  73. package/src/assets/web-panel/assets/{Tenant-t7jF9NkQ.js → Tenant-CIvbeRkn.js} +1 -1
  74. package/src/assets/web-panel/assets/{Terminal-Btq_O951.js → Terminal-BuIPGugj.js} +1 -1
  75. package/src/assets/web-panel/assets/{TimelineRenderer-hC_Piiwy.js → TimelineRenderer-BefCbTxN.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tokens-BTIqA2J1.js → Tokens-gGAeez46.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trigger-Dzv2gJpK.js → Trigger-C_86E2v7.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trust-B13QpWOh.js → Trust-Beg23C1W.js} +1 -1
  79. package/src/assets/web-panel/assets/{UkeySign-CFj3UF4G.js → UkeySign--sGSaISg.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CGMOGTQb.js → VideoEditing-DIsnY6D_.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-BkCJ573Y.js → Wallet-a7ECYY0i.js} +1 -1
  82. package/src/assets/web-panel/assets/{WebAuthn-C8dCrmGH.js → WebAuthn-CsVpkUE8.js} +1 -1
  83. package/src/assets/web-panel/assets/{WorkflowEditor-CclfhxwH.js → WorkflowEditor-BOJ4WTgl.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-CW_X_A9l.js → chat-ezCR5ZZX.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-6GtheJb2.js → colors-DSUEQ7GR.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-tz7dJapw.js → compact-item-D4w3DGj_.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-CbiGcRCw.js → createContext-B_hvu-NR.js} +1 -1
  88. package/src/assets/web-panel/assets/{hasIn-bmNFD3PH.js → hasIn-FY775Vpc.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-DxHmNJ8S.js → index-1ki6aTIz.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-UId2JAY6.js → index-B3lbDu2b.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-BM4slfDH.js → index-B9LXVosT.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-b7sSMzkC.js → index-BGdchdRx.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-KFdfLxVt.js → index-BGrm33US.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B9sJpsz3.js → index-BHPD-_s2.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-B6GgMo1k.js → index-BMnGj-Ui.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-B2P1c5yD.js → index-BSp1hSN8.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-S9g45I5s.js → index-BaZmrJ6j.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Cb33JbDx.js → index-BnvLdkR8.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CQ7aJ-8s.js → index-BzME19Nh.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DeoJYTHX.js → index-C0wDzrUK.js} +3 -3
  101. package/src/assets/web-panel/assets/{index-CBNnbQxM.js → index-CAa3LSKI.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CLk3xgD2.js → index-CE3vDzJj.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-De0xvTEv.js → index-CLp4V3Tb.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CKP-AZD3.js → index-CNUQnJsg.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CzIQ0u4e.js → index-CSbCIyga.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-UjxsY00y.js → index-CUNYcWEF.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CgLF_zD1.js → index-CV-F9a_X.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C5zlrlPd.js → index-CcvzscCg.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-B3UH6whC.js → index-CdzLJsac.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CU0MeCwH.js → index-Cug_yoee.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DxdLksCx.js → index-CxMArAIk.js} +1 -1
  112. package/src/assets/web-panel/assets/index-CzkEIn_T.js +1 -0
  113. package/src/assets/web-panel/assets/index-D2PcvXPP.js +1 -0
  114. package/src/assets/web-panel/assets/{index-DMGJqZ-o.js → index-D6yJ0RMr.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DaYfMM7r.js → index-DM77-xyl.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CV9aPPYM.js → index-DfU6zsU8.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-l2VM-W36.js → index-DqsQUPY7.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DK7ee5hC.js → index-DqxLCUYa.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DaK59dFw.js → index-DtztktoP.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Cs5KWviO.js → index-Dz8-7APi.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Da2RUdcb.js → index-Enin7rzK.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CYIwY9zQ.js → index-S6A76pR1.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DvWXdCxl.js → index-ZGJo2fz1.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Dj1N1idV.js → index-d0HXotWE.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CTnIkosg.js → index-gAtp_KkQ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-zP_o4yKV.js → index-mlTTTHla.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BxCPkuTG.js → index-wWVM6ENX.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-BTGwT0NA.js → initDefaultProps-BoLcNBz9.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BheHkG8e.js → motion-ouRvP3eO.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-BkMMNkAw.js → move-BBITvaOV.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-B9zmSFf6.js → omit-D8Yxdvbb.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-CFNBcA5Z.js → pickAttrs-BIUy7mER.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-B4mvrUYw.js → placementArrow-CrNWpNSF.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-BJqsSjxJ.js → responsiveObserve-aHLPN464.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Cj78MXSi.js → slide-2qNtePjk.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-Bk6ChXAV.js → statusUtils-FAF3iR3R.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-B_YDSNYf.js → styleChecker-BNFTkrbl.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-q90prOQL.js → useFlexGapSupport-BgdPlW7E.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-BtpFJ9kI.js → useFs-q1QvAh3f.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-C3qhf1sQ.js → usePersonalDataHub-DEZ6cY8C.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-Bz2j0JiG.js → vnode-vp6H-NL_.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-CzaEtel2.js → zoom-UatfJMw4.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/__tests__/hub-bilibili-adb-sync.test.js +350 -0
  145. package/src/commands/__tests__/hub-douyin-adb-sync.test.js +199 -0
  146. package/src/commands/__tests__/hub-kuaishou-adb-sync.test.js +161 -0
  147. package/src/commands/__tests__/hub-toutiao-adb-sync.test.js +158 -0
  148. package/src/commands/__tests__/hub-weibo-adb-sync.test.js +163 -0
  149. package/src/commands/__tests__/hub-xhs-adb-sync.test.js +155 -0
  150. package/src/commands/hub.js +883 -0
  151. package/src/gateways/ws/personal-data-hub-protocol.js +84 -0
  152. package/src/lib/host-adb-bridge.js +77 -1
  153. package/src/lib/personal-data-hub-wiring.js +471 -12
  154. package/src/assets/web-panel/assets/PersonalDataHub-Bsf3Wh6n.js +0 -1
  155. package/src/assets/web-panel/assets/index-BFkZAdio.js +0 -1
  156. package/src/assets/web-panel/assets/index-BhgINPAH.js +0 -1
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Phase 6d — cc hub kuaishou-adb-sync CLI command unit tests.
3
+ *
4
+ * Mirror of hub-toutiao-adb-sync. Covers profileFetchFailed + CLI no-bridge
5
+ * short-circuit banner + lastErrorCode propagation.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
9
+
10
+ import { _internal } from "../hub.js";
11
+
12
+ let logSpy, errSpy;
13
+ let prevExitCode;
14
+
15
+ function fakeHub(syncResult) {
16
+ return { kuaishouAdbSync: vi.fn(async (_opts) => syncResult) };
17
+ }
18
+
19
+ const HAPPY_RESULT = {
20
+ ok: true,
21
+ report: {
22
+ adapter: "social-kuaishou",
23
+ status: "ok",
24
+ rawCount: 26,
25
+ entityCounts: { events: 26, persons: 1, places: 0, items: 0, topics: 0 },
26
+ kuaishou: {
27
+ uid: "12345",
28
+ nickname: "Alice",
29
+ eventCounts: {
30
+ profile: 1,
31
+ watch: 15,
32
+ collect: 5,
33
+ search: 5,
34
+ total: 26,
35
+ },
36
+ lastErrorCode: 0,
37
+ lastErrorMessage: null,
38
+ cookieDiagnostic: { cookieCount: 8, hadEncrypted: false },
39
+ profileFetchFailed: false,
40
+ signProviderUsed: "KuaishouSignBridge",
41
+ signProviderHits: 3,
42
+ signProviderFallbacks: 0,
43
+ cleanupFailed: false,
44
+ },
45
+ },
46
+ };
47
+
48
+ beforeEach(() => {
49
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
50
+ errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
51
+ prevExitCode = process.exitCode;
52
+ process.exitCode = 0;
53
+ });
54
+
55
+ afterEach(() => {
56
+ logSpy.mockRestore();
57
+ errSpy.mockRestore();
58
+ process.exitCode = prevExitCode;
59
+ });
60
+
61
+ describe("cc hub kuaishou-adb-sync — happy path", () => {
62
+ it("prints human summary with nickname", async () => {
63
+ const hub = fakeHub(HAPPY_RESULT);
64
+ await _internal.cmdKuaishouAdbSync({ _getHub: async () => hub });
65
+ expect(hub.kuaishouAdbSync).toHaveBeenCalledOnce();
66
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
67
+ expect(out).toMatch(/kuaishou-adb-sync succeeded/);
68
+ expect(out).toMatch(/uid:.*12345/);
69
+ expect(out).toMatch(/nickname:\s+Alice/);
70
+ expect(out).toMatch(/profile:\s+1/);
71
+ expect(out).toMatch(/watch:\s+15/);
72
+ expect(out).toMatch(/collect:\s+5/);
73
+ expect(out).toMatch(/search:\s+5/);
74
+ expect(out).toMatch(/total:\s+26/);
75
+ });
76
+
77
+ it("--json outputs raw result", async () => {
78
+ const hub = fakeHub(HAPPY_RESULT);
79
+ await _internal.cmdKuaishouAdbSync({
80
+ _getHub: async () => hub,
81
+ json: true,
82
+ });
83
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
84
+ const parsed = JSON.parse(out);
85
+ expect(parsed.ok).toBe(true);
86
+ expect(parsed.report.kuaishou.uid).toBe("12345");
87
+ });
88
+
89
+ it("renders profileFetchFailed warning when set", async () => {
90
+ const result = JSON.parse(JSON.stringify(HAPPY_RESULT));
91
+ result.report.kuaishou.profileFetchFailed = true;
92
+ result.report.kuaishou.lastErrorCode = -8;
93
+ result.report.kuaishou.uid = null;
94
+ const hub = fakeHub(result);
95
+ await _internal.cmdKuaishouAdbSync({ _getHub: async () => hub });
96
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
97
+ expect(out).toMatch(/cookie 缺 kuaishou\.web\.cp\.api_ph/);
98
+ expect(out).toMatch(/profile fetch failed/);
99
+ });
100
+
101
+ it("renders 'no sign bridge' short-circuit banner in CLI context", async () => {
102
+ const result = JSON.parse(JSON.stringify(HAPPY_RESULT));
103
+ result.report.kuaishou.signProviderUsed = "none";
104
+ result.report.kuaishou.signProviderHits = 0;
105
+ result.report.kuaishou.signProviderFallbacks = 3;
106
+ result.report.kuaishou.eventCounts.watch = 0;
107
+ result.report.kuaishou.eventCounts.collect = 0;
108
+ result.report.kuaishou.eventCounts.search = 0;
109
+ result.report.kuaishou.eventCounts.total = 1;
110
+ result.report.kuaishou.lastErrorCode = -99;
111
+ const hub = fakeHub(result);
112
+ await _internal.cmdKuaishouAdbSync({ _getHub: async () => hub });
113
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
114
+ expect(out).toMatch(/3 signed endpoints short-circuited/);
115
+ expect(out).toMatch(/no sign bridge in CLI context/);
116
+ expect(out).toMatch(/__NS_sig3 via Electron WebContentsView/);
117
+ });
118
+
119
+ it("renders lastErrorCode partial-result warning (non-(-99))", async () => {
120
+ const result = JSON.parse(JSON.stringify(HAPPY_RESULT));
121
+ result.report.kuaishou.lastErrorCode = 412;
122
+ result.report.kuaishou.lastErrorMessage = "HTTP 412";
123
+ const hub = fakeHub(result);
124
+ await _internal.cmdKuaishouAdbSync({ _getHub: async () => hub });
125
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
126
+ expect(out).toMatch(/partial: lastErrorCode=412/);
127
+ });
128
+ });
129
+
130
+ describe("cc hub kuaishou-adb-sync — failure paths", () => {
131
+ function failCase(reason, expectedHint) {
132
+ return async () => {
133
+ const hub = fakeHub({ ok: false, reason, message: "synthetic" });
134
+ await _internal.cmdKuaishouAdbSync({ _getHub: async () => hub });
135
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
136
+ expect(out).toMatch(new RegExp(`kuaishou-adb-sync failed: ${reason}`));
137
+ if (expectedHint) {
138
+ expect(out).toMatch(expectedHint);
139
+ }
140
+ expect(process.exitCode).toBe(1);
141
+ };
142
+ }
143
+
144
+ it("BRIDGE_UNAVAILABLE banner", failCase("BRIDGE_UNAVAILABLE", /ADB_PATH/));
145
+ it("KUAISHOU_NO_ROOT banner", failCase("KUAISHOU_NO_ROOT", /Magisk root/));
146
+ it(
147
+ "KUAISHOU_NOT_INSTALLED banner",
148
+ failCase("KUAISHOU_NOT_INSTALLED", /com\.smile\.gifmaker/),
149
+ );
150
+ it(
151
+ "KUAISHOU_COOKIES_INCOMPLETE banner",
152
+ failCase(
153
+ "KUAISHOU_COOKIES_INCOMPLETE",
154
+ /userId \/ kuaishou\.web\.cp\.api_ph/,
155
+ ),
156
+ );
157
+ it(
158
+ "KUAISHOU_COOKIES_TRUNCATED banner",
159
+ failCase("KUAISHOU_COOKIES_TRUNCATED", /unplug \+ replug USB/),
160
+ );
161
+ });
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Phase 6c — cc hub toutiao-adb-sync CLI command unit tests.
3
+ *
4
+ * Mirror of hub-xhs-adb-sync. Covers profileFetchFailed + CLI no-bridge
5
+ * short-circuit banner + lastErrorCode propagation.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
9
+
10
+ import { _internal } from "../hub.js";
11
+
12
+ let logSpy, errSpy;
13
+ let prevExitCode;
14
+
15
+ function fakeHub(syncResult) {
16
+ return { toutiaoAdbSync: vi.fn(async (_opts) => syncResult) };
17
+ }
18
+
19
+ const HAPPY_RESULT = {
20
+ ok: true,
21
+ report: {
22
+ adapter: "social-toutiao",
23
+ status: "ok",
24
+ rawCount: 26,
25
+ entityCounts: { events: 26, persons: 1, places: 0, items: 0, topics: 0 },
26
+ toutiao: {
27
+ uid: "12345",
28
+ nickname: "Alice",
29
+ eventCounts: {
30
+ profile: 1,
31
+ feed: 15,
32
+ collection: 5,
33
+ search: 5,
34
+ total: 26,
35
+ },
36
+ lastErrorCode: 0,
37
+ lastErrorMessage: null,
38
+ cookieDiagnostic: { cookieCount: 8, hadEncrypted: false },
39
+ profileFetchFailed: false,
40
+ signProviderUsed: "ToutiaoSignBridge",
41
+ signProviderHits: 3,
42
+ signProviderFallbacks: 0,
43
+ cleanupFailed: false,
44
+ },
45
+ },
46
+ };
47
+
48
+ beforeEach(() => {
49
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
50
+ errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
51
+ prevExitCode = process.exitCode;
52
+ process.exitCode = 0;
53
+ });
54
+
55
+ afterEach(() => {
56
+ logSpy.mockRestore();
57
+ errSpy.mockRestore();
58
+ process.exitCode = prevExitCode;
59
+ });
60
+
61
+ describe("cc hub toutiao-adb-sync — happy path", () => {
62
+ it("prints human summary with nickname", async () => {
63
+ const hub = fakeHub(HAPPY_RESULT);
64
+ await _internal.cmdToutiaoAdbSync({ _getHub: async () => hub });
65
+ expect(hub.toutiaoAdbSync).toHaveBeenCalledOnce();
66
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
67
+ expect(out).toMatch(/toutiao-adb-sync succeeded/);
68
+ expect(out).toMatch(/uid:.*12345/);
69
+ expect(out).toMatch(/nickname:\s+Alice/);
70
+ expect(out).toMatch(/profile:\s+1/);
71
+ expect(out).toMatch(/feed:\s+15/);
72
+ expect(out).toMatch(/collection:\s+5/);
73
+ expect(out).toMatch(/search:\s+5/);
74
+ expect(out).toMatch(/total:\s+26/);
75
+ });
76
+
77
+ it("--json outputs raw result", async () => {
78
+ const hub = fakeHub(HAPPY_RESULT);
79
+ await _internal.cmdToutiaoAdbSync({
80
+ _getHub: async () => hub,
81
+ json: true,
82
+ });
83
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
84
+ const parsed = JSON.parse(out);
85
+ expect(parsed.ok).toBe(true);
86
+ expect(parsed.report.toutiao.uid).toBe("12345");
87
+ });
88
+
89
+ it("renders profileFetchFailed warning when set", async () => {
90
+ const result = JSON.parse(JSON.stringify(HAPPY_RESULT));
91
+ result.report.toutiao.profileFetchFailed = true;
92
+ result.report.toutiao.lastErrorCode = 1;
93
+ result.report.toutiao.lastErrorMessage = "token expired";
94
+ result.report.toutiao.uid = null;
95
+ const hub = fakeHub(result);
96
+ await _internal.cmdToutiaoAdbSync({ _getHub: async () => hub });
97
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
98
+ expect(out).toMatch(/passport\/info\/v2 returned no user_id/);
99
+ expect(out).toMatch(/profile fetch failed/);
100
+ });
101
+
102
+ it("renders 'no sign bridge' short-circuit banner in CLI context", async () => {
103
+ const result = JSON.parse(JSON.stringify(HAPPY_RESULT));
104
+ result.report.toutiao.signProviderUsed = "none";
105
+ result.report.toutiao.signProviderHits = 0;
106
+ result.report.toutiao.signProviderFallbacks = 3;
107
+ result.report.toutiao.eventCounts.feed = 0;
108
+ result.report.toutiao.eventCounts.collection = 0;
109
+ result.report.toutiao.eventCounts.search = 0;
110
+ result.report.toutiao.eventCounts.total = 1; // just profile
111
+ result.report.toutiao.lastErrorCode = -99;
112
+ const hub = fakeHub(result);
113
+ await _internal.cmdToutiaoAdbSync({ _getHub: async () => hub });
114
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
115
+ expect(out).toMatch(/3 signed endpoints short-circuited/);
116
+ expect(out).toMatch(/no sign bridge in CLI context/);
117
+ });
118
+
119
+ it("renders lastErrorCode partial-result warning (non-(-99))", async () => {
120
+ const result = JSON.parse(JSON.stringify(HAPPY_RESULT));
121
+ result.report.toutiao.lastErrorCode = 412;
122
+ result.report.toutiao.lastErrorMessage = "HTTP 412";
123
+ const hub = fakeHub(result);
124
+ await _internal.cmdToutiaoAdbSync({ _getHub: async () => hub });
125
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
126
+ expect(out).toMatch(/partial: lastErrorCode=412/);
127
+ });
128
+ });
129
+
130
+ describe("cc hub toutiao-adb-sync — failure paths", () => {
131
+ function failCase(reason, expectedHint) {
132
+ return async () => {
133
+ const hub = fakeHub({ ok: false, reason, message: "synthetic" });
134
+ await _internal.cmdToutiaoAdbSync({ _getHub: async () => hub });
135
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
136
+ expect(out).toMatch(new RegExp(`toutiao-adb-sync failed: ${reason}`));
137
+ if (expectedHint) {
138
+ expect(out).toMatch(expectedHint);
139
+ }
140
+ expect(process.exitCode).toBe(1);
141
+ };
142
+ }
143
+
144
+ it("BRIDGE_UNAVAILABLE banner", failCase("BRIDGE_UNAVAILABLE", /ADB_PATH/));
145
+ it("TOUTIAO_NO_ROOT banner", failCase("TOUTIAO_NO_ROOT", /Magisk root/));
146
+ it(
147
+ "TOUTIAO_NOT_INSTALLED banner",
148
+ failCase("TOUTIAO_NOT_INSTALLED", /com\.ss\.android\.article\.news/),
149
+ );
150
+ it(
151
+ "TOUTIAO_COOKIES_INCOMPLETE banner",
152
+ failCase("TOUTIAO_COOKIES_INCOMPLETE", /sessionid \/ sessionid_ss/),
153
+ );
154
+ it(
155
+ "TOUTIAO_COOKIES_TRUNCATED banner",
156
+ failCase("TOUTIAO_COOKIES_TRUNCATED", /unplug \+ replug USB/),
157
+ );
158
+ });
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Phase 3a — cc hub weibo-adb-sync CLI command unit tests.
3
+ *
4
+ * Same _getHub test-seam pattern. Mirrors hub-bilibili / hub-douyin tests.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
8
+
9
+ import { _internal } from "../hub.js";
10
+
11
+ let logSpy, errSpy;
12
+ let prevExitCode;
13
+
14
+ function fakeHub(syncResult) {
15
+ return { weiboAdbSync: vi.fn(async (_opts) => syncResult) };
16
+ }
17
+
18
+ const HAPPY_RESULT = {
19
+ ok: true,
20
+ report: {
21
+ adapter: "social-weibo",
22
+ status: "ok",
23
+ rawCount: 25,
24
+ entityCounts: { events: 25, persons: 0, places: 0, items: 0, topics: 0 },
25
+ weibo: {
26
+ uid: 1234567890,
27
+ eventCounts: { post: 15, favourite: 5, follow: 5, total: 25 },
28
+ lastErrorCode: 0,
29
+ lastErrorMessage: null,
30
+ cookieDiagnostic: { cookieCount: 6, hasSub: true, hadEncrypted: false },
31
+ uidFetchFailed: false,
32
+ cleanupFailed: false,
33
+ },
34
+ },
35
+ };
36
+
37
+ beforeEach(() => {
38
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
39
+ errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
40
+ prevExitCode = process.exitCode;
41
+ process.exitCode = 0;
42
+ });
43
+
44
+ afterEach(() => {
45
+ logSpy.mockRestore();
46
+ errSpy.mockRestore();
47
+ process.exitCode = prevExitCode;
48
+ });
49
+
50
+ describe("cc hub weibo-adb-sync — happy path", () => {
51
+ it("prints human summary", async () => {
52
+ const hub = fakeHub(HAPPY_RESULT);
53
+ await _internal.cmdWeiboAdbSync({ _getHub: async () => hub });
54
+ expect(hub.weiboAdbSync).toHaveBeenCalledOnce();
55
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
56
+ expect(out).toMatch(/weibo-adb-sync succeeded/);
57
+ expect(out).toMatch(/uid:.*1234567890/);
58
+ expect(out).toMatch(/posts:\s+15/);
59
+ expect(out).toMatch(/favourites:\s+5/);
60
+ expect(out).toMatch(/follows:\s+5/);
61
+ expect(out).toMatch(/total:\s+25/);
62
+ });
63
+
64
+ it("--json outputs raw result", async () => {
65
+ const hub = fakeHub(HAPPY_RESULT);
66
+ await _internal.cmdWeiboAdbSync({ _getHub: async () => hub, json: true });
67
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
68
+ const parsed = JSON.parse(out);
69
+ expect(parsed.ok).toBe(true);
70
+ expect(parsed.report.weibo.uid).toBe(1234567890);
71
+ });
72
+
73
+ it("renders uidFetchFailed warning when set", async () => {
74
+ const result = JSON.parse(JSON.stringify(HAPPY_RESULT));
75
+ result.report.weibo.uidFetchFailed = true;
76
+ result.report.weibo.lastErrorCode = -4;
77
+ result.report.weibo.uid = null;
78
+ const hub = fakeHub(result);
79
+ await _internal.cmdWeiboAdbSync({ _getHub: async () => hub });
80
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
81
+ expect(out).toMatch(/api\/config returned login=false/);
82
+ expect(out).toMatch(/uid fetch failed/);
83
+ });
84
+ });
85
+
86
+ describe("cc hub weibo-adb-sync — failure reasons", () => {
87
+ it("BRIDGE_UNAVAILABLE → exit 1 + install tip", async () => {
88
+ const hub = fakeHub({
89
+ ok: false,
90
+ reason: "BRIDGE_UNAVAILABLE",
91
+ message: "adb not found",
92
+ });
93
+ await _internal.cmdWeiboAdbSync({ _getHub: async () => hub });
94
+ expect(process.exitCode).toBe(1);
95
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
96
+ expect(out).toMatch(/Install Android Platform Tools/);
97
+ });
98
+
99
+ it("WEIBO_NO_ROOT → Magisk tip", async () => {
100
+ const hub = fakeHub({
101
+ ok: false,
102
+ reason: "WEIBO_NO_ROOT",
103
+ message: "uid=2000",
104
+ });
105
+ await _internal.cmdWeiboAdbSync({ _getHub: async () => hub });
106
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
107
+ expect(out).toMatch(/Magisk root/);
108
+ });
109
+
110
+ it("WEIBO_NOT_INSTALLED → install tip", async () => {
111
+ const hub = fakeHub({
112
+ ok: false,
113
+ reason: "WEIBO_NOT_INSTALLED",
114
+ message: "Cookies path missing",
115
+ });
116
+ await _internal.cmdWeiboAdbSync({ _getHub: async () => hub });
117
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
118
+ expect(out).toMatch(/Install Weibo App/);
119
+ });
120
+
121
+ it("WEIBO_COOKIES_INCOMPLETE → relog tip (SUB missing)", async () => {
122
+ const hub = fakeHub({
123
+ ok: false,
124
+ reason: "WEIBO_COOKIES_INCOMPLETE",
125
+ message: "missing SUB",
126
+ });
127
+ await _internal.cmdWeiboAdbSync({ _getHub: async () => hub });
128
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
129
+ expect(out).toMatch(/SUB cookie missing.*relog/);
130
+ });
131
+ });
132
+
133
+ describe("cc hub weibo-adb-sync — options forwarding", () => {
134
+ it("forwards limit-post / limit-favourite / limit-follow", async () => {
135
+ const hub = fakeHub(HAPPY_RESULT);
136
+ await _internal.cmdWeiboAdbSync({
137
+ _getHub: async () => hub,
138
+ limitPost: "50",
139
+ limitFavourite: "30",
140
+ limitFollow: "100",
141
+ });
142
+ const arg = hub.weiboAdbSync.mock.calls[0][0];
143
+ expect(arg.limits).toEqual({ post: 50, favourite: 30, follow: 100 });
144
+ });
145
+
146
+ it("forwards display-name + staging-dir", async () => {
147
+ const hub = fakeHub(HAPPY_RESULT);
148
+ await _internal.cmdWeiboAdbSync({
149
+ _getHub: async () => hub,
150
+ displayName: "alice",
151
+ stagingDir: "/tmp/x",
152
+ });
153
+ const arg = hub.weiboAdbSync.mock.calls[0][0];
154
+ expect(arg.displayName).toBe("alice");
155
+ expect(arg.stagingDir).toBe("/tmp/x");
156
+ });
157
+ });
158
+
159
+ describe("cc hub weibo-adb-sync — _internal export", () => {
160
+ it("cmdWeiboAdbSync is exported", () => {
161
+ expect(typeof _internal.cmdWeiboAdbSync).toBe("function");
162
+ });
163
+ });
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Phase 3c — cc hub xhs-adb-sync CLI command unit tests.
3
+ *
4
+ * Mirror of hub-weibo-adb-sync test. Covers meFetchFailed warning + 461
5
+ * partial-result warning (X-S signing best-effort).
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
9
+
10
+ import { _internal } from "../hub.js";
11
+
12
+ let logSpy, errSpy;
13
+ let prevExitCode;
14
+
15
+ function fakeHub(syncResult) {
16
+ return { xhsAdbSync: vi.fn(async (_opts) => syncResult) };
17
+ }
18
+
19
+ const HAPPY_RESULT = {
20
+ ok: true,
21
+ report: {
22
+ adapter: "social-xiaohongshu",
23
+ status: "ok",
24
+ rawCount: 20,
25
+ entityCounts: { events: 20, persons: 0, places: 0, items: 0, topics: 0 },
26
+ xhs: {
27
+ userId: "5e8c8f7e1234abcdef",
28
+ nickname: "Alice",
29
+ eventCounts: { note: 15, liked: 3, follow: 2, total: 20 },
30
+ lastErrorCode: 0,
31
+ lastErrorMessage: null,
32
+ cookieDiagnostic: { cookieCount: 8, hadEncrypted: false },
33
+ meFetchFailed: false,
34
+ cleanupFailed: false,
35
+ },
36
+ },
37
+ };
38
+
39
+ beforeEach(() => {
40
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
41
+ errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
42
+ prevExitCode = process.exitCode;
43
+ process.exitCode = 0;
44
+ });
45
+
46
+ afterEach(() => {
47
+ logSpy.mockRestore();
48
+ errSpy.mockRestore();
49
+ process.exitCode = prevExitCode;
50
+ });
51
+
52
+ describe("cc hub xhs-adb-sync — happy path", () => {
53
+ it("prints human summary with nickname", async () => {
54
+ const hub = fakeHub(HAPPY_RESULT);
55
+ await _internal.cmdXhsAdbSync({ _getHub: async () => hub });
56
+ expect(hub.xhsAdbSync).toHaveBeenCalledOnce();
57
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
58
+ expect(out).toMatch(/xhs-adb-sync succeeded/);
59
+ expect(out).toMatch(/userId:.*5e8c8f7e/);
60
+ expect(out).toMatch(/nickname:\s+Alice/);
61
+ expect(out).toMatch(/notes:\s+15/);
62
+ expect(out).toMatch(/liked:\s+3/);
63
+ expect(out).toMatch(/follows:\s+2/);
64
+ expect(out).toMatch(/total:\s+20/);
65
+ });
66
+
67
+ it("--json outputs raw result", async () => {
68
+ const hub = fakeHub(HAPPY_RESULT);
69
+ await _internal.cmdXhsAdbSync({ _getHub: async () => hub, json: true });
70
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
71
+ const parsed = JSON.parse(out);
72
+ expect(parsed.ok).toBe(true);
73
+ expect(parsed.report.xhs.userId).toBe("5e8c8f7e1234abcdef");
74
+ });
75
+
76
+ it("renders meFetchFailed warning when set", async () => {
77
+ const result = JSON.parse(JSON.stringify(HAPPY_RESULT));
78
+ result.report.xhs.meFetchFailed = true;
79
+ result.report.xhs.lastErrorCode = -7;
80
+ result.report.xhs.userId = null;
81
+ const hub = fakeHub(result);
82
+ await _internal.cmdXhsAdbSync({ _getHub: async () => hub });
83
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
84
+ expect(out).toMatch(/\/user\/me returned no user_id/);
85
+ expect(out).toMatch(/me fetch failed/);
86
+ });
87
+
88
+ it("renders X-S 461 partial-result warning", async () => {
89
+ const result = JSON.parse(JSON.stringify(HAPPY_RESULT));
90
+ result.report.xhs.lastErrorCode = 461;
91
+ result.report.xhs.lastErrorMessage = "X-S validation failed";
92
+ result.report.xhs.eventCounts.total = 5; // partial — only 5 events got through
93
+ const hub = fakeHub(result);
94
+ await _internal.cmdXhsAdbSync({ _getHub: async () => hub });
95
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
96
+ expect(out).toMatch(/X-S 签名 best-effort/);
97
+ expect(out).toMatch(/lastErrorCode=461/);
98
+ });
99
+ });
100
+
101
+ describe("cc hub xhs-adb-sync — failure reasons", () => {
102
+ it("BRIDGE_UNAVAILABLE → exit 1 + install tip", async () => {
103
+ const hub = fakeHub({
104
+ ok: false,
105
+ reason: "BRIDGE_UNAVAILABLE",
106
+ message: "adb not found",
107
+ });
108
+ await _internal.cmdXhsAdbSync({ _getHub: async () => hub });
109
+ expect(process.exitCode).toBe(1);
110
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
111
+ expect(out).toMatch(/Install Android Platform Tools/);
112
+ });
113
+
114
+ it("XHS_NO_ROOT → Magisk tip", async () => {
115
+ const hub = fakeHub({
116
+ ok: false,
117
+ reason: "XHS_NO_ROOT",
118
+ message: "uid=2000",
119
+ });
120
+ await _internal.cmdXhsAdbSync({ _getHub: async () => hub });
121
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
122
+ expect(out).toMatch(/Magisk root/);
123
+ });
124
+
125
+ it("XHS_COOKIES_INCOMPLETE → a1/web_session tip", async () => {
126
+ const hub = fakeHub({
127
+ ok: false,
128
+ reason: "XHS_COOKIES_INCOMPLETE",
129
+ message: "missing a1",
130
+ });
131
+ await _internal.cmdXhsAdbSync({ _getHub: async () => hub });
132
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
133
+ expect(out).toMatch(/a1 \/ web_session cookie missing/);
134
+ });
135
+ });
136
+
137
+ describe("cc hub xhs-adb-sync — options forwarding", () => {
138
+ it("forwards limit-note / limit-liked / limit-follow", async () => {
139
+ const hub = fakeHub(HAPPY_RESULT);
140
+ await _internal.cmdXhsAdbSync({
141
+ _getHub: async () => hub,
142
+ limitNote: "20",
143
+ limitLiked: "15",
144
+ limitFollow: "50",
145
+ });
146
+ const arg = hub.xhsAdbSync.mock.calls[0][0];
147
+ expect(arg.limits).toEqual({ note: 20, liked: 15, follow: 50 });
148
+ });
149
+ });
150
+
151
+ describe("cc hub xhs-adb-sync — _internal export", () => {
152
+ it("cmdXhsAdbSync is exported", () => {
153
+ expect(typeof _internal.cmdXhsAdbSync).toBe("function");
154
+ });
155
+ });