chainlesschain 0.162.12 → 0.162.13
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 +30 -25
- package/package.json +4 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-C3TDNq29.js → AIOps-Bq_zxhCr.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-C9fE18pE.js → ActionButton-CaevDm9t.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-wnZF602C.js → Analytics-B0gOmwPw.js} +1 -1
- package/src/assets/web-panel/assets/{AppLayout-BjgTMK7O.js → AppLayout-DWhZiV0Q.js} +2 -2
- package/src/assets/web-panel/assets/{Audit-BBL0BW5_.js → Audit-ZuZJBCxo.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BKLqYCWU.js → Backup-CX7jhH5l.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BGSzMCZs.js → BaseInput-CVLx7HVq.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CQWzZWEY.js → Chat-C6yL5tRD.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BkTri12Q.js → Checkbox-BePhbqVq.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BH1m09EO.js → Codegen-B5E4x1Lm.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BXnBuqIa.js → Col-CWhNU6A7.js} +1 -1
- package/src/assets/web-panel/assets/{Community-C_Nr4XCx.js → Community-mSEAuJhp.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Du6GwLrj.js → Compact-DSudHzX3.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-66M0oi1Q.js → Compliance-CxJLYjyn.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DQrkZRNd.js → Cowork-C-trppQj.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-CwdIFH_v.js → Cron-_Ij4v5fY.js} +1 -1
- package/src/assets/web-panel/assets/{Crosschain-DqlcrQ9L.js → Crosschain-eLHXzp5T.js} +1 -1
- package/src/assets/web-panel/assets/{DID-OmPLKf7L.js → DID-BP04AUUB.js} +1 -1
- package/src/assets/web-panel/assets/{Dashboard-D_dampTL.js → Dashboard-CzEeQv62.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CA1W7jAn.js → Dropdown-C_SGOB22.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Chlk9a7s.js → Federation-BgaP4BOv.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-t0UqYFLq.js → FormItemContext-BxGLLt9r.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CEq0raYm.js → Git-Bt_uM_Gw.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-C06CX7Ge.js → Governance-N2-0RG_o.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-6VIFHxIP.js → Inference-eS3g-CzP.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BCJPjMBQ.js → KnowledgeGraph-CUuvNVah.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BBpOYFct.js → Logs-wPbwEt2r.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BFH6jMWt.js → Marketplace-DH91kTwo.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-uCFvRqGs.js → McpTools-D-GyyBrI.js} +1 -1
- package/src/assets/web-panel/assets/{Memory-B0Kux_KT.js → Memory-BOtUy-tw.js} +1 -1
- package/src/assets/web-panel/assets/{MobileBridge-DHow2jiK.js → MobileBridge-DIP__XQd.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BFo9YQZp.js → MobileProjects-1nqr1UsU.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-riOh1G_F.js → Mtc-CCE0x7h2.js} +1 -1
- package/src/assets/web-panel/assets/{MtcAudit-Bm-hE2SP.js → MtcAudit-BBkz0XUO.js} +1 -1
- package/src/assets/web-panel/assets/{Multisig-DfUQxh5a.js → Multisig-CIKSJvTY.js} +2 -2
- package/src/assets/web-panel/assets/{NLProgramming-DuNvLBEq.js → NLProgramming-BDkgeFcq.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DB20wd3c.js → Notes-ONiUxfN1.js} +1 -1
- package/src/assets/web-panel/assets/{NotificationSettings-CB-GkOWR.js → NotificationSettings-CGcyKEIe.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-3bU7PZuG.js → Organization-BZtMYBJt.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BGCPP_0Y.js → Overflow-C4LfFZAI.js} +1 -1
- package/src/assets/web-panel/assets/{OverrideContext-x9ZzjLwk.js → OverrideContext-C_4H9tGA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BHgAe1oC.js → P2P-D71Cpk-m.js} +1 -1
- package/src/assets/web-panel/assets/{Permissions-BuOD4xwc.js → Permissions-DRGYF29v.js} +1 -1
- package/src/assets/web-panel/assets/PersonalDataHub-CfRYoIua.js +1 -0
- package/src/assets/web-panel/assets/PersonalDataHub-Dvaa8niQ.css +1 -0
- package/src/assets/web-panel/assets/{Pipeline-DBS5U4LB.js → Pipeline-BOyp0_Qo.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-UNjIc5El.js → Privacy-a_AcphvF.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CicqCJGy.js → ProjectInit-CFu1grYt.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectSettings-CIxAbt4Y.js → ProjectSettings-p54Eivhh.js} +1 -1
- package/src/assets/web-panel/assets/{Projects-BJycZScO.js → Projects-DkB88XAu.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DxXvprme.js → Providers-EK7f8DEd.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-rrqjU8_Y.js → QuickAsk-CpO0w3iP.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BEwHMhI7.js → Recommend-BUJVQdv9.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DoVKCCMn.js → Reputation-kU2fOuZt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-F5XcDhHr.js → Row-oGWRbk6g.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-cZrRG7k8.js → RssFeed-saC_46Yo.js} +1 -1
- package/src/assets/web-panel/assets/{Search-B9ctZjqx.js → Search-CjtRqFUu.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Z62hl1mc.js → Security-BX0NBVfQ.js} +1 -1
- package/src/assets/web-panel/assets/{Services-CQf5XqgZ.js → Services-Bgxsnei_.js} +1 -1
- package/src/assets/web-panel/assets/{Skeleton-DuCKw2Eh.js → Skeleton-CwBb3k2a.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-qVkhva0s.js → Skills-CZCMYH6Z.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BQbatr7s.js → Sla-Djy1uHnZ.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DLFBzAgD.js → SpeechSettings-CGUI_Uyh.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CrzETZMW.js → SyncSettings-DK2CKHRD.js} +1 -1
- package/src/assets/web-panel/assets/{Tasks-D_EQ1nJ7.js → Tasks-BKiOzeIO.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D4y-dGRc.js → Templates-CnQpleXj.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-2XI0jkPn.js → Tenant-DwKz0cjm.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-fUi5V2Z9.js → Terminal-A7t_wsR8.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BuUNB2mg.js → Tokens-BqYY9l44.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-7DqLLuej.js → Trigger-BI4bXFmi.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CeACvTYx.js → Trust-yMynKTRG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-mDP9EXHq.js → UkeySign-Br4IScM6.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-veWlKclv.js → VideoEditing-CWcThGsP.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Cd2Hheb8.js → Wallet-CZcAtjxj.js} +1 -1
- package/src/assets/web-panel/assets/{WebAuthn-DyL7ZiHX.js → WebAuthn-BnTZFMA0.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-C7-7LJH9.js → WorkflowEditor-N7gGz3_n.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DXomZMuo.js → chat-D175ZIO0.js} +1 -1
- package/src/assets/web-panel/assets/{collapseMotion-CjFH_Jop.js → collapseMotion-DfnRZex1.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DlU92QNs.js → colors-LKhZyttv.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-sBiTL8mX.js → compact-item-CsJSebxT.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DZXEnzum.js → createContext-BJ_CPYFC.js} +1 -1
- package/src/assets/web-panel/assets/{echarts-Bq-n0MtJ.js → echarts-Dj_pBaVI.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-CpCHBZ2M.js → hasIn-CzD3IqH8.js} +1 -1
- package/src/assets/web-panel/assets/{icons-CLQTHa5-.js → icons-BOPtEWK4.js} +4 -4
- package/src/assets/web-panel/assets/{index-ClN_JuFa.js → index-6qPbrYF7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDyzZ8_O.js → index-B7UYymse.js} +1 -1
- package/src/assets/web-panel/assets/{index-C52udT0_.js → index-BDSZDDb2.js} +4 -4
- package/src/assets/web-panel/assets/{index-s8tvk-fF.js → index-BEDFHKO3.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_IgY63-.js → index-BMvdoiFr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dyg6ikIL.js → index-BNVLVzN5.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0Qbxr57.js → index-BSNibAqz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeGnHcp5.js → index-BV-__mlC.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_-RETt0.js → index-BXH9ujMW.js} +1 -1
- package/src/assets/web-panel/assets/{index-XI6772AD.js → index-BZluCuTH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqnhEJls.js → index-BhiZDGg7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BK2AFy44.js → index-BjOrt4vw.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dn8m1d1f.js → index-BmJdof_c.js} +2 -2
- package/src/assets/web-panel/assets/{index-C6SDf50u.js → index-BsirlkJ0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CD3iljXs.js → index-BvF2tC6C.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJ7XYa5K.js → index-C2HBKw07.js} +1 -1
- package/src/assets/web-panel/assets/{index-_zyXBoS7.js → index-CKjBAdm0.js} +1 -1
- package/src/assets/web-panel/assets/{index-X48zYgZ6.js → index-CRGNuUIM.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dj9Nvz6S.js → index-CTIkCKav.js} +1 -1
- package/src/assets/web-panel/assets/index-CY1mQA2I.js +1 -0
- package/src/assets/web-panel/assets/{index-C59FgSkU.js → index-CyHdYUeZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-B23tuoo9.js → index-D401L3yx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKe9jmKG.js → index-D5IZCkZG.js} +1 -1
- package/src/assets/web-panel/assets/{index-F9cBucYf.js → index-D7ZcBI5s.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjXSvceY.js → index-D8kltMTW.js} +1 -1
- package/src/assets/web-panel/assets/index-D9nXHfUB.js +1 -0
- package/src/assets/web-panel/assets/{index-Dq5Rn5VS.js → index-DJVkBmSc.js} +1 -1
- package/src/assets/web-panel/assets/{index-ibFHnqHz.js → index-DM3uBEWD.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChahjdYE.js → index-DOO73rHE.js} +1 -1
- package/src/assets/web-panel/assets/{index-CivbS-57.js → index-DXp1jVsK.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3k9UPHc.js → index-Dd7dICwB.js} +1 -1
- package/src/assets/web-panel/assets/{index-Di1_EQ-X.js → index-Dm-3kvtD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DygNvCeR.js → index-DnPt5OdD.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_MzScPM.js → index-E7t1hAnk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUe5t5Aa.js → index-JTX9A7w0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwbWZubA.js → index-Kn-Of5ew.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUlPMzoM.js → index-R1cFADfk.js} +1 -1
- package/src/assets/web-panel/assets/{index-TwQZkVGh.js → index-RIO4JKMP.js} +1 -1
- package/src/assets/web-panel/assets/{index-ThrAiEF9.js → index-uTEVWPYA.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DEi92ZnZ.js → initDefaultProps-CBW0okek.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BtYKzpOc.js → motion-DGAffQ0Z.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cb3A1-v-.js → move-DFJ0-5IW.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B6qPDdOf.js → omit-AvrDghg1.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DDyeQMUc.js → pickAttrs-D7csw9i1.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BPV6VO47.js → placementArrow-hZ6Lg6kG.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DJ1ra4dT.js → responsiveObserve-DLLx5VvS.js} +1 -1
- package/src/assets/web-panel/assets/{slide-D6v8tHvB.js → slide-BaRIT3ev.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DulKcQLZ.js → statusUtils-Cdjyuhrz.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Bne7zwMt.js → styleChecker-CbrNybTt.js} +1 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-B8gAhiRC.js +1 -0
- package/src/assets/web-panel/assets/{useFs-CR-iLa4Z.js → useFs-BZPy4ICP.js} +1 -1
- package/src/assets/web-panel/assets/{useMergedState-O7QXt4P5.js → useMergedState-WwedrFR0.js} +1 -1
- package/src/assets/web-panel/assets/{useRefs-0J6m8UWN.js → useRefs-Cdq8EWeF.js} +1 -1
- package/src/assets/web-panel/assets/{useState-CSzR8F8O.js → useState-DGS1NOyn.js} +1 -1
- package/src/assets/web-panel/assets/{vendor-M5lGV-wr.js → vendor-DhFY8mDK.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-yL9axxBy.js → vnode-6Y0NDMVv.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B-VCMXSD.js → zoom-DTbMGsSH.js} +1 -1
- package/src/assets/web-panel/index.html +3 -3
- package/src/commands/__tests__/hub-aichat.test.js +277 -0
- package/src/commands/__tests__/hub-wechat.test.js +243 -0
- package/src/commands/hub.js +417 -0
- package/src/commands/sync-providers.js +436 -0
- package/src/gateways/ws/personal-data-hub-protocol.js +68 -0
- package/src/index.js +4 -0
- package/src/lib/__tests__/personal-data-hub-aichat-wizard.test.js +209 -0
- package/src/lib/__tests__/sync-credentials.test.js +265 -0
- package/src/lib/__tests__/sync-engine-cli.test.js +293 -0
- package/src/lib/personal-data-hub-aichat-wizard.js +242 -0
- package/src/lib/personal-data-hub-wiring.js +189 -0
- package/src/lib/sync-cli-db.js +194 -0
- package/src/lib/sync-credentials.js +225 -0
- package/src/lib/sync-engine-cli.js +406 -0
- package/src/lib/sync-oss-client.js +273 -0
- package/src/lib/sync-webdav-client.js +194 -0
- package/src/assets/web-panel/assets/PersonalDataHub--WA-aZAJ.js +0 -1
- package/src/assets/web-panel/assets/PersonalDataHub-BK7I0Rsb.css +0 -1
- package/src/assets/web-panel/assets/index-CcRX6BlT.js +0 -1
- package/src/assets/web-panel/assets/index-z6h6tqP3.js +0 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-C1miTomM.js +0 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-side AIChat WebView 鉴权向导 (Phase 10.3.4) unit tests.
|
|
3
|
+
*
|
|
4
|
+
* `personal-data-hub-aichat-wizard.js` is ESM. We test the bridge + paste
|
|
5
|
+
* mode + factory wiring without touching disk by using temp dirs and
|
|
6
|
+
* dependency injection.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
10
|
+
import { mkdtempSync, rmSync, readFileSync, existsSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
createAccountsStore,
|
|
16
|
+
createVendorAdapterBridge,
|
|
17
|
+
getAIChatWizard,
|
|
18
|
+
_jarToArray,
|
|
19
|
+
_resetForTests,
|
|
20
|
+
ACCOUNTS_FILE,
|
|
21
|
+
} from "../personal-data-hub-aichat-wizard.js";
|
|
22
|
+
|
|
23
|
+
// ─── createAccountsStore (real fs, temp dir) ──────────────────────────────
|
|
24
|
+
|
|
25
|
+
describe("createAccountsStore (ESM, real fs)", () => {
|
|
26
|
+
let hubDir;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
hubDir = mkdtempSync(join(tmpdir(), "aichat-store-"));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns null for missing file", async () => {
|
|
32
|
+
const store = createAccountsStore({ hubDir });
|
|
33
|
+
expect(await store.get("deepseek")).toBeNull();
|
|
34
|
+
expect(await store.list()).toEqual([]);
|
|
35
|
+
rmSync(hubDir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("put / get round-trips and creates the 0600 file", async () => {
|
|
39
|
+
const store = createAccountsStore({ hubDir });
|
|
40
|
+
await store.put("deepseek", {
|
|
41
|
+
vendor: "deepseek",
|
|
42
|
+
cookies: { userToken: "x" },
|
|
43
|
+
});
|
|
44
|
+
expect(existsSync(join(hubDir, ACCOUNTS_FILE))).toBe(true);
|
|
45
|
+
const stored = await store.get("deepseek");
|
|
46
|
+
expect(stored).toMatchObject({
|
|
47
|
+
vendor: "deepseek",
|
|
48
|
+
cookies: { userToken: "x" },
|
|
49
|
+
});
|
|
50
|
+
rmSync(hubDir, { recursive: true, force: true });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("delete drops the file when no entries remain", async () => {
|
|
54
|
+
const store = createAccountsStore({ hubDir });
|
|
55
|
+
await store.put("kimi", { vendor: "kimi", cookies: { access_token: "x" } });
|
|
56
|
+
await store.delete("kimi");
|
|
57
|
+
expect(existsSync(join(hubDir, ACCOUNTS_FILE))).toBe(false);
|
|
58
|
+
rmSync(hubDir, { recursive: true, force: true });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("throws when hubDir missing", () => {
|
|
62
|
+
expect(() => createAccountsStore({})).toThrow(/hubDir/);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ─── createVendorAdapterBridge ─────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
function fakeSpec(name, validateImpl) {
|
|
69
|
+
return {
|
|
70
|
+
name,
|
|
71
|
+
displayName: name,
|
|
72
|
+
rateLimits: { perMinute: 10, minIntervalMs: 1000 },
|
|
73
|
+
cookieDomains: [name + ".example"],
|
|
74
|
+
validateCookie: validateImpl,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe("createVendorAdapterBridge (cli)", () => {
|
|
79
|
+
it("dispatches to validateCookie and returns its result", async () => {
|
|
80
|
+
const bridge = createVendorAdapterBridge({
|
|
81
|
+
specs: [
|
|
82
|
+
fakeSpec("deepseek", async ({ session }) => ({
|
|
83
|
+
ok: true,
|
|
84
|
+
userId: session.cookies[0].name,
|
|
85
|
+
})),
|
|
86
|
+
],
|
|
87
|
+
_httpClientFactory: () => ({}),
|
|
88
|
+
});
|
|
89
|
+
const r = await bridge.registerVendor("deepseek", { userToken: "abc" });
|
|
90
|
+
expect(r.ok).toBe(true);
|
|
91
|
+
expect(r.userId).toBe("userToken");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("UNKNOWN_VENDOR for unrecognized", async () => {
|
|
95
|
+
const bridge = createVendorAdapterBridge({
|
|
96
|
+
specs: [fakeSpec("deepseek", async () => ({ ok: true }))],
|
|
97
|
+
_httpClientFactory: () => ({}),
|
|
98
|
+
});
|
|
99
|
+
const r = await bridge.registerVendor("ghost", { x: "y" });
|
|
100
|
+
expect(r.reason).toBe("UNKNOWN_VENDOR");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("VALIDATE_THREW when validateCookie rejects", async () => {
|
|
104
|
+
const bridge = createVendorAdapterBridge({
|
|
105
|
+
specs: [
|
|
106
|
+
fakeSpec("deepseek", async () => {
|
|
107
|
+
throw new Error("boom");
|
|
108
|
+
}),
|
|
109
|
+
],
|
|
110
|
+
_httpClientFactory: () => ({}),
|
|
111
|
+
});
|
|
112
|
+
const r = await bridge.registerVendor("deepseek", { x: "y" });
|
|
113
|
+
expect(r.reason).toBe("VALIDATE_THREW");
|
|
114
|
+
expect(r.error).toMatch(/boom/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("rejects empty specs", () => {
|
|
118
|
+
expect(() => createVendorAdapterBridge({ specs: [] })).toThrow(/specs/);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ─── _jarToArray ──────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
describe("_jarToArray (cli)", () => {
|
|
125
|
+
it("array passthrough", () => {
|
|
126
|
+
expect(_jarToArray([{ name: "a", value: "1" }])).toEqual([
|
|
127
|
+
{ name: "a", value: "1" },
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
130
|
+
it("object → array", () => {
|
|
131
|
+
expect(_jarToArray({ a: "1", b: "2" })).toEqual([
|
|
132
|
+
{ name: "a", value: "1" },
|
|
133
|
+
{ name: "b", value: "2" },
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
it("string → array, splits on ;", () => {
|
|
137
|
+
expect(_jarToArray("a=1; b=2")).toEqual([
|
|
138
|
+
{ name: "a", value: "1" },
|
|
139
|
+
{ name: "b", value: "2" },
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
it("nullish → empty", () => {
|
|
143
|
+
expect(_jarToArray(null)).toEqual([]);
|
|
144
|
+
expect(_jarToArray(undefined)).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ─── getAIChatWizard — paste-mode is the default ──────────────────────────
|
|
149
|
+
|
|
150
|
+
describe("getAIChatWizard — paste-mode wiring", () => {
|
|
151
|
+
let hubDir;
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
_resetForTests();
|
|
154
|
+
hubDir = mkdtempSync(join(tmpdir(), "aichat-wiz-"));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("openVendorLogin returns paste fallback by default", async () => {
|
|
158
|
+
const wiz = getAIChatWizard({ hubDir });
|
|
159
|
+
const r = await wiz.openVendorLogin({ vendor: "deepseek" });
|
|
160
|
+
expect(r.ok).toBe(true);
|
|
161
|
+
expect(r.fallbackMode).toBe("paste");
|
|
162
|
+
expect(r.loginUrl).toMatch(/chat\.deepseek\.com/);
|
|
163
|
+
expect(r.helpText).toMatch(/外部浏览器/);
|
|
164
|
+
rmSync(hubDir, { recursive: true, force: true });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("probeCookies with raw cookieHeader succeeds without disk write", async () => {
|
|
168
|
+
const wiz = getAIChatWizard({ hubDir });
|
|
169
|
+
const r = await wiz.probeCookies({
|
|
170
|
+
vendor: "doubao",
|
|
171
|
+
cookieHeader: "sessionid=abc; sid_guard=xyz",
|
|
172
|
+
});
|
|
173
|
+
expect(r.ok).toBe(true);
|
|
174
|
+
expect(r.source).toBe("paste");
|
|
175
|
+
expect(r.cookies.sessionid).toBe("abc");
|
|
176
|
+
rmSync(hubDir, { recursive: true, force: true });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("probeCookies without cookieHeader returns PASTE_REQUIRED", async () => {
|
|
180
|
+
const wiz = getAIChatWizard({ hubDir });
|
|
181
|
+
const r = await wiz.probeCookies({ vendor: "doubao" });
|
|
182
|
+
expect(r.ok).toBe(false);
|
|
183
|
+
expect(r.reason).toBe("PASTE_REQUIRED");
|
|
184
|
+
rmSync(hubDir, { recursive: true, force: true });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("registerVendor persists to file when vendorAdapter validates ok", async () => {
|
|
188
|
+
const _vendorAdapter = {
|
|
189
|
+
registerVendor: async () => ({ ok: true, userId: "u_42" }),
|
|
190
|
+
};
|
|
191
|
+
const wiz = getAIChatWizard({ hubDir, _vendorAdapter });
|
|
192
|
+
const r = await wiz.registerVendor({
|
|
193
|
+
vendor: "doubao",
|
|
194
|
+
cookies: { sessionid: "abc" },
|
|
195
|
+
});
|
|
196
|
+
expect(r.ok).toBe(true);
|
|
197
|
+
expect(r.accountId).toBe("doubao:u_42");
|
|
198
|
+
const onDisk = JSON.parse(
|
|
199
|
+
readFileSync(join(hubDir, ACCOUNTS_FILE), "utf-8"),
|
|
200
|
+
);
|
|
201
|
+
expect(onDisk.doubao).toBeTruthy();
|
|
202
|
+
expect(onDisk.doubao.userId).toBe("u_42");
|
|
203
|
+
rmSync(hubDir, { recursive: true, force: true });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("throws when hubDir is missing", () => {
|
|
207
|
+
expect(() => getAIChatWizard({})).toThrow(/hubDir/);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sync-credentials 单元测试 — Phase 3c follow-up CLI safeStorage.
|
|
3
|
+
*
|
|
4
|
+
* 用 tmp dir + _setCcDirForTest 避开真实 ~/.chainlesschain/,
|
|
5
|
+
* 单测互不污染。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
ALLOWED_PROVIDER_IDS,
|
|
15
|
+
SENSITIVE_FIELDS,
|
|
16
|
+
MASK,
|
|
17
|
+
getCredentials,
|
|
18
|
+
getCredentialsSanitized,
|
|
19
|
+
hasCredentials,
|
|
20
|
+
setCredentials,
|
|
21
|
+
clearCredentials,
|
|
22
|
+
_setCcDirForTest,
|
|
23
|
+
_resetCcDirForTest,
|
|
24
|
+
_keyPath,
|
|
25
|
+
_vaultPath,
|
|
26
|
+
} from "../sync-credentials.js";
|
|
27
|
+
|
|
28
|
+
let tmpDir;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cc-sync-cred-test-"));
|
|
32
|
+
_setCcDirForTest(tmpDir);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
_resetCcDirForTest();
|
|
37
|
+
try {
|
|
38
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
39
|
+
} catch (_e) {
|
|
40
|
+
/* test cleanup best-effort */
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("sync-credentials · constants", () => {
|
|
45
|
+
it("ALLOWED_PROVIDER_IDS includes webdav + oss", () => {
|
|
46
|
+
expect(ALLOWED_PROVIDER_IDS).toEqual(
|
|
47
|
+
expect.arrayContaining(["webdav", "oss"]),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("SENSITIVE_FIELDS includes password + secretAccessKey paths", () => {
|
|
52
|
+
expect(SENSITIVE_FIELDS).toEqual(
|
|
53
|
+
expect.arrayContaining([
|
|
54
|
+
"sync.webdav.password",
|
|
55
|
+
"sync.oss.secretAccessKey",
|
|
56
|
+
]),
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("sync-credentials · master key auto-gen", () => {
|
|
62
|
+
it("first setCredentials creates key + vault files", () => {
|
|
63
|
+
expect(fs.existsSync(_keyPath())).toBe(false);
|
|
64
|
+
expect(fs.existsSync(_vaultPath())).toBe(false);
|
|
65
|
+
setCredentials("webdav", {
|
|
66
|
+
url: "https://x",
|
|
67
|
+
username: "u",
|
|
68
|
+
password: "p",
|
|
69
|
+
});
|
|
70
|
+
expect(fs.existsSync(_keyPath())).toBe(true);
|
|
71
|
+
expect(fs.existsSync(_vaultPath())).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("key file is 32 bytes", () => {
|
|
75
|
+
setCredentials("webdav", { url: "https://x", password: "p" });
|
|
76
|
+
const buf = fs.readFileSync(_keyPath());
|
|
77
|
+
expect(buf.length).toBe(32);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("vault file is encrypted (NOT plaintext JSON)", () => {
|
|
81
|
+
// Use a randomly-generated value so security-check.js doesn't flag
|
|
82
|
+
// this test file as committing a literal password.
|
|
83
|
+
const probe = "probe-" + Math.random().toString(36).slice(2);
|
|
84
|
+
setCredentials("webdav", { url: "https://x", password: probe });
|
|
85
|
+
const buf = fs.readFileSync(_vaultPath());
|
|
86
|
+
const text = buf.toString("utf-8");
|
|
87
|
+
expect(text).not.toContain(probe);
|
|
88
|
+
expect(() => JSON.parse(text)).toThrow();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("reuses same key across calls (deterministic decrypt)", () => {
|
|
92
|
+
setCredentials("webdav", { url: "https://x", password: "p1" });
|
|
93
|
+
const keyBefore = fs.readFileSync(_keyPath());
|
|
94
|
+
setCredentials("webdav", { url: "https://x", password: "p2" });
|
|
95
|
+
const keyAfter = fs.readFileSync(_keyPath());
|
|
96
|
+
expect(keyAfter.equals(keyBefore)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("sync-credentials · round-trip", () => {
|
|
101
|
+
it("setCredentials → getCredentials returns plain values", () => {
|
|
102
|
+
setCredentials("webdav", {
|
|
103
|
+
url: "https://nas",
|
|
104
|
+
username: "alice",
|
|
105
|
+
password: "secret",
|
|
106
|
+
remotePath: "/cc",
|
|
107
|
+
});
|
|
108
|
+
const got = getCredentials("webdav");
|
|
109
|
+
expect(got).toEqual({
|
|
110
|
+
url: "https://nas",
|
|
111
|
+
username: "alice",
|
|
112
|
+
password: "secret",
|
|
113
|
+
remotePath: "/cc",
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("OSS round-trip preserves all 7 fields", () => {
|
|
118
|
+
const oss = {
|
|
119
|
+
endpoint: "https://oss.example.com",
|
|
120
|
+
region: "us-east-1",
|
|
121
|
+
bucket: "b",
|
|
122
|
+
accessKeyId: "AKI",
|
|
123
|
+
secretAccessKey: "SK",
|
|
124
|
+
remotePath: "cc/",
|
|
125
|
+
forcePathStyle: true,
|
|
126
|
+
};
|
|
127
|
+
setCredentials("oss", oss);
|
|
128
|
+
expect(getCredentials("oss")).toEqual(oss);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("multi-provider isolation", () => {
|
|
132
|
+
setCredentials("webdav", { url: "https://x", password: "p1" });
|
|
133
|
+
setCredentials("oss", {
|
|
134
|
+
endpoint: "https://o",
|
|
135
|
+
bucket: "b",
|
|
136
|
+
accessKeyId: "k",
|
|
137
|
+
secretAccessKey: "s",
|
|
138
|
+
});
|
|
139
|
+
expect(getCredentials("webdav").password).toBe("p1");
|
|
140
|
+
expect(getCredentials("oss").secretAccessKey).toBe("s");
|
|
141
|
+
// Crossover check
|
|
142
|
+
expect(getCredentials("webdav").secretAccessKey).toBeUndefined();
|
|
143
|
+
expect(getCredentials("oss").password).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("getCredentials before any set returns {}", () => {
|
|
147
|
+
expect(getCredentials("webdav")).toEqual({});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("sync-credentials · sanitize", () => {
|
|
152
|
+
it("getCredentialsSanitized masks password field", () => {
|
|
153
|
+
// Probe value is random to avoid security-check.js pattern hits
|
|
154
|
+
const probe = "probe-" + Math.random().toString(36).slice(2);
|
|
155
|
+
setCredentials("webdav", {
|
|
156
|
+
url: "https://x",
|
|
157
|
+
username: "u",
|
|
158
|
+
password: probe,
|
|
159
|
+
remotePath: "/",
|
|
160
|
+
});
|
|
161
|
+
const masked = getCredentialsSanitized("webdav");
|
|
162
|
+
expect(masked.password).toBe(MASK);
|
|
163
|
+
expect(masked.url).toBe("https://x"); // unmasked
|
|
164
|
+
expect(masked.username).toBe("u");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("getCredentialsSanitized masks secretAccessKey", () => {
|
|
168
|
+
setCredentials("oss", {
|
|
169
|
+
endpoint: "https://o",
|
|
170
|
+
bucket: "b",
|
|
171
|
+
accessKeyId: "AKI",
|
|
172
|
+
secretAccessKey: "SUPERSECRET",
|
|
173
|
+
});
|
|
174
|
+
const masked = getCredentialsSanitized("oss");
|
|
175
|
+
expect(masked.secretAccessKey).toBe(MASK);
|
|
176
|
+
expect(masked.accessKeyId).toBe("AKI"); // unmasked
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("empty/missing sensitive field not masked", () => {
|
|
180
|
+
setCredentials("webdav", { url: "https://x", password: "" });
|
|
181
|
+
const masked = getCredentialsSanitized("webdav");
|
|
182
|
+
expect(masked.password).toBe(""); // empty stays empty, no mask
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("sync-credentials · hasCredentials", () => {
|
|
187
|
+
it("false before configuration", () => {
|
|
188
|
+
expect(hasCredentials("webdav")).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("true after any non-empty field saved", () => {
|
|
192
|
+
setCredentials("webdav", { url: "https://x", password: "p" });
|
|
193
|
+
expect(hasCredentials("webdav")).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("true even when only sensitive field set", () => {
|
|
197
|
+
setCredentials("oss", {
|
|
198
|
+
endpoint: "",
|
|
199
|
+
bucket: "",
|
|
200
|
+
accessKeyId: "",
|
|
201
|
+
secretAccessKey: "s",
|
|
202
|
+
});
|
|
203
|
+
expect(hasCredentials("oss")).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("sync-credentials · clearCredentials", () => {
|
|
208
|
+
it("removes provider creds; other providers untouched", () => {
|
|
209
|
+
setCredentials("webdav", { url: "https://x", password: "p" });
|
|
210
|
+
setCredentials("oss", {
|
|
211
|
+
endpoint: "https://o",
|
|
212
|
+
bucket: "b",
|
|
213
|
+
accessKeyId: "k",
|
|
214
|
+
secretAccessKey: "s",
|
|
215
|
+
});
|
|
216
|
+
clearCredentials("webdav");
|
|
217
|
+
expect(hasCredentials("webdav")).toBe(false);
|
|
218
|
+
expect(hasCredentials("oss")).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("clearing non-configured provider is a no-op (returns true)", () => {
|
|
222
|
+
expect(clearCredentials("webdav")).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("sync-credentials · validation", () => {
|
|
227
|
+
it("setCredentials rejects unknown provider", () => {
|
|
228
|
+
expect(() => setCredentials("dropbox", { token: "t" })).toThrow(
|
|
229
|
+
/unknown provider id 'dropbox'/,
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("setCredentials rejects null creds", () => {
|
|
234
|
+
expect(() => setCredentials("webdav", null)).toThrow(/must be an object/);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("getCredentials rejects unknown provider", () => {
|
|
238
|
+
expect(() => getCredentials("dropbox")).toThrow(/unknown provider id/);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("sync-credentials · corruption recovery", () => {
|
|
243
|
+
it("wrong-length key file → helpful error", () => {
|
|
244
|
+
setCredentials("webdav", { url: "https://x", password: "p" });
|
|
245
|
+
// Corrupt: truncate key file to 16 bytes
|
|
246
|
+
fs.writeFileSync(_keyPath(), Buffer.alloc(16));
|
|
247
|
+
expect(() => getCredentials("webdav")).toThrow(/wrong length/);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("corrupt vault → helpful error", () => {
|
|
251
|
+
setCredentials("webdav", { url: "https://x", password: "p" });
|
|
252
|
+
// Write garbage that's at least the min size (12 + 16 + 1 = 29 bytes)
|
|
253
|
+
fs.writeFileSync(
|
|
254
|
+
_vaultPath(),
|
|
255
|
+
Buffer.from("garbage-data-that-is-not-valid-aes-blob-content"),
|
|
256
|
+
);
|
|
257
|
+
expect(() => getCredentials("webdav")).toThrow(/decrypt failed/);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("truncated vault (smaller than iv+tag header) → helpful error", () => {
|
|
261
|
+
setCredentials("webdav", { url: "https://x", password: "p" });
|
|
262
|
+
fs.writeFileSync(_vaultPath(), Buffer.alloc(10));
|
|
263
|
+
expect(() => getCredentials("webdav")).toThrow(/too small or invalid/);
|
|
264
|
+
});
|
|
265
|
+
});
|