@chainlesschain/personal-data-hub 0.4.29 → 0.4.30

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 (198) hide show
  1. package/lib/prompt-builder.js +15 -1
  2. package/package.json +4 -1
  3. package/__tests__/adapter-guide.test.js +0 -47
  4. package/__tests__/adapter-spec.test.js +0 -78
  5. package/__tests__/adapters/ai-chat-cookie-capture-spec.test.js +0 -211
  6. package/__tests__/adapters/ai-chat-health-checker.test.js +0 -262
  7. package/__tests__/adapters/ai-chat-history.test.js +0 -396
  8. package/__tests__/adapters/ai-chat-http-client.test.js +0 -242
  9. package/__tests__/adapters/ai-chat-vendors.test.js +0 -874
  10. package/__tests__/adapters/alipay-bill-adapter.test.js +0 -538
  11. package/__tests__/adapters/apple-health.test.js +0 -95
  12. package/__tests__/adapters/bank-family.test.js +0 -125
  13. package/__tests__/adapters/biz-tianyancha.test.js +0 -159
  14. package/__tests__/adapters/browser-history-chrome.test.js +0 -377
  15. package/__tests__/adapters/browser-history-edge.test.js +0 -159
  16. package/__tests__/adapters/car-mercedesme.test.js +0 -74
  17. package/__tests__/adapters/doc-baidu-netdisk.test.js +0 -102
  18. package/__tests__/adapters/doc-camscanner.test.js +0 -147
  19. package/__tests__/adapters/doc-platforms.test.js +0 -177
  20. package/__tests__/adapters/edu-huawei-learning-live.test.js +0 -198
  21. package/__tests__/adapters/edu-zuoyebang-live.test.js +0 -226
  22. package/__tests__/adapters/email-adapter-snapshot.test.js +0 -237
  23. package/__tests__/adapters/email-adapter.test.js +0 -742
  24. package/__tests__/adapters/email-classifier.test.js +0 -347
  25. package/__tests__/adapters/email-imap-session.test.js +0 -334
  26. package/__tests__/adapters/email-parser.test.js +0 -244
  27. package/__tests__/adapters/email-pdf-extractor.test.js +0 -529
  28. package/__tests__/adapters/email-providers.test.js +0 -84
  29. package/__tests__/adapters/email-retry-progress.test.js +0 -294
  30. package/__tests__/adapters/email-templates.test.js +0 -822
  31. package/__tests__/adapters/family-23-collectors-scaffold.test.js +0 -182
  32. package/__tests__/adapters/finance-alipay-live.test.js +0 -258
  33. package/__tests__/adapters/finance-dcep.test.js +0 -74
  34. package/__tests__/adapters/fitness-joyrun.test.js +0 -82
  35. package/__tests__/adapters/game-genshin-live.test.js +0 -238
  36. package/__tests__/adapters/game-genshin-scaffold.test.js +0 -108
  37. package/__tests__/adapters/game-honor-of-kings-live.test.js +0 -230
  38. package/__tests__/adapters/git-activity.test.js +0 -222
  39. package/__tests__/adapters/gov-12123.test.js +0 -103
  40. package/__tests__/adapters/gov-ixiamen.test.js +0 -150
  41. package/__tests__/adapters/gov-tax.test.js +0 -135
  42. package/__tests__/adapters/health-meiyou.test.js +0 -125
  43. package/__tests__/adapters/local-files.test.js +0 -264
  44. package/__tests__/adapters/local-im-pc.test.js +0 -154
  45. package/__tests__/adapters/messaging-whatsapp.test.js +0 -289
  46. package/__tests__/adapters/music-kugou.test.js +0 -187
  47. package/__tests__/adapters/music-qq.test.js +0 -112
  48. package/__tests__/adapters/netease-music-live.test.js +0 -244
  49. package/__tests__/adapters/netease-music.test.js +0 -74
  50. package/__tests__/adapters/pc-local-discovery.test.js +0 -141
  51. package/__tests__/adapters/qq-pc-direct-read.test.js +0 -227
  52. package/__tests__/adapters/reading-family.test.js +0 -108
  53. package/__tests__/adapters/recruit-boss.test.js +0 -180
  54. package/__tests__/adapters/shell-history.test.js +0 -180
  55. package/__tests__/adapters/shopping-base.test.js +0 -179
  56. package/__tests__/adapters/shopping-dianping.test.js +0 -239
  57. package/__tests__/adapters/social-bilibili-adb-api-client.test.js +0 -721
  58. package/__tests__/adapters/social-bilibili-adb-chromium-cookies-reader.test.js +0 -346
  59. package/__tests__/adapters/social-bilibili-adb-collector.test.js +0 -284
  60. package/__tests__/adapters/social-bilibili-adb-cookies-extension.test.js +0 -343
  61. package/__tests__/adapters/social-bilibili-adb-snapshot-builder.test.js +0 -296
  62. package/__tests__/adapters/social-csdn.test.js +0 -175
  63. package/__tests__/adapters/social-dongchedi.test.js +0 -165
  64. package/__tests__/adapters/social-douyin-adb-aweme-detail.test.js +0 -165
  65. package/__tests__/adapters/social-douyin-adb-collector.test.js +0 -254
  66. package/__tests__/adapters/social-douyin-adb-db-extension.test.js +0 -114
  67. package/__tests__/adapters/social-douyin-adb-im-db-parser.test.js +0 -304
  68. package/__tests__/adapters/social-douyin-adb-snapshot-builder.test.js +0 -216
  69. package/__tests__/adapters/social-douyin-adb-usage-profile.test.js +0 -229
  70. package/__tests__/adapters/social-douyin-adb-watch-history.test.js +0 -269
  71. package/__tests__/adapters/social-kuaishou-adb-api-client.test.js +0 -496
  72. package/__tests__/adapters/social-kuaishou-adb-collector.test.js +0 -276
  73. package/__tests__/adapters/social-kuaishou-adb-cookies-extension.test.js +0 -152
  74. package/__tests__/adapters/social-kuaishou-adb-snapshot-builder.test.js +0 -178
  75. package/__tests__/adapters/social-toutiao-adb-account-reader.test.js +0 -135
  76. package/__tests__/adapters/social-toutiao-adb-api-client.test.js +0 -626
  77. package/__tests__/adapters/social-toutiao-adb-article.test.js +0 -155
  78. package/__tests__/adapters/social-toutiao-adb-collector.test.js +0 -378
  79. package/__tests__/adapters/social-toutiao-adb-cookies-extension.test.js +0 -193
  80. package/__tests__/adapters/social-toutiao-adb-snapshot-builder.test.js +0 -196
  81. package/__tests__/adapters/social-toutiao-kuaishou-scaffold.test.js +0 -311
  82. package/__tests__/adapters/social-weibo-adb-api-client.test.js +0 -362
  83. package/__tests__/adapters/social-weibo-adb-collector.test.js +0 -201
  84. package/__tests__/adapters/social-weibo-adb-cookies-extension.test.js +0 -167
  85. package/__tests__/adapters/social-weibo-adb-snapshot-builder.test.js +0 -189
  86. package/__tests__/adapters/social-xiaohongshu-adb-api-client.test.js +0 -431
  87. package/__tests__/adapters/social-xiaohongshu-adb-collector.test.js +0 -207
  88. package/__tests__/adapters/social-xiaohongshu-adb-cookies-extension.test.js +0 -0
  89. package/__tests__/adapters/social-xiaohongshu-adb-sign-provider-injection.test.js +0 -351
  90. package/__tests__/adapters/social-xiaohongshu-adb-sign.test.js +0 -130
  91. package/__tests__/adapters/social-xiaohongshu-adb-snapshot-builder.test.js +0 -200
  92. package/__tests__/adapters/social-zhihu.test.js +0 -246
  93. package/__tests__/adapters/system-data-adapter.test.js +0 -443
  94. package/__tests__/adapters/system-data-android-ingest.test.js +0 -144
  95. package/__tests__/adapters/system-data-android.test.js +0 -519
  96. package/__tests__/adapters/system-data-disclosure.test.js +0 -153
  97. package/__tests__/adapters/travel-12306.test.js +0 -512
  98. package/__tests__/adapters/travel-amap.test.js +0 -219
  99. package/__tests__/adapters/travel-baidu-map.test.js +0 -305
  100. package/__tests__/adapters/travel-base.test.js +0 -205
  101. package/__tests__/adapters/travel-ctrip.test.js +0 -377
  102. package/__tests__/adapters/travel-didi-consumer.test.js +0 -66
  103. package/__tests__/adapters/travel-didi.test.js +0 -204
  104. package/__tests__/adapters/travel-tencent-map.test.js +0 -207
  105. package/__tests__/adapters/travel-tongcheng.test.js +0 -289
  106. package/__tests__/adapters/video-platforms.test.js +0 -152
  107. package/__tests__/adapters/video-xigua.test.js +0 -106
  108. package/__tests__/adapters/vscode.test.js +0 -299
  109. package/__tests__/adapters/wechat-bootstrap.test.js +0 -240
  110. package/__tests__/adapters/wechat-env-probe.test.js +0 -162
  111. package/__tests__/adapters/wechat-frida-agent.test.js +0 -322
  112. package/__tests__/adapters/wechat-frida-integration.test.js +0 -149
  113. package/__tests__/adapters/wechat-frida-key-provider.test.js +0 -188
  114. package/__tests__/adapters/wechat-md5-key-provider.test.js +0 -101
  115. package/__tests__/adapters/wechat-pc-direct-read.test.js +0 -365
  116. package/__tests__/adapters/wechat-pc-group-topic.test.js +0 -63
  117. package/__tests__/adapters/wechat-pc-v4-sidecar.test.js +0 -72
  118. package/__tests__/adapters/weread.test.js +0 -123
  119. package/__tests__/adapters/wework-pc.test.js +0 -124
  120. package/__tests__/adapters/win-recent.test.js +0 -192
  121. package/__tests__/analysis-skills.test.js +0 -754
  122. package/__tests__/analysis.test.js +0 -1845
  123. package/__tests__/audio-ximalaya-snapshot.test.js +0 -279
  124. package/__tests__/batch.test.js +0 -133
  125. package/__tests__/bridges-cc-kg.test.js +0 -231
  126. package/__tests__/bridges-cc-llm.test.js +0 -191
  127. package/__tests__/bridges-cc-rag.test.js +0 -162
  128. package/__tests__/categories.test.js +0 -92
  129. package/__tests__/e2e/ai-chat-cross-source-journey.test.js +0 -213
  130. package/__tests__/e2e/full-user-journey.test.js +0 -188
  131. package/__tests__/e2e/local-data-adapters-cli.e2e.test.js +0 -146
  132. package/__tests__/entity-resolver-ingest-hook.test.js +0 -177
  133. package/__tests__/entity-resolver-stages.test.js +0 -411
  134. package/__tests__/entity-resolver-vault.test.js +0 -249
  135. package/__tests__/entity-resolver.test.js +0 -526
  136. package/__tests__/fitness-keep-snapshot.test.js +0 -224
  137. package/__tests__/fixtures/entity-resolver-200-mock.json +0 -96
  138. package/__tests__/ids.test.js +0 -45
  139. package/__tests__/integration/ai-chat-history-registry.test.js +0 -228
  140. package/__tests__/integration/aichat-wizard-end-to-end.test.js +0 -282
  141. package/__tests__/integration/cross-adapter-pipelines.test.js +0 -396
  142. package/__tests__/integration/local-data-adapters-pipeline.test.js +0 -373
  143. package/__tests__/integration/social-bilibili-pipeline.test.js +0 -261
  144. package/__tests__/integration/wechat-bootstrap-end-to-end.test.js +0 -390
  145. package/__tests__/key-providers.test.js +0 -126
  146. package/__tests__/kg-derive.test.js +0 -219
  147. package/__tests__/llm-client.test.js +0 -122
  148. package/__tests__/longtail-adapters.test.js +0 -281
  149. package/__tests__/messaging-qq-snapshot.test.js +0 -294
  150. package/__tests__/mobile-extractor-encrypted.test.js +0 -460
  151. package/__tests__/mobile-extractor.test.js +0 -288
  152. package/__tests__/mock-adapter.test.js +0 -93
  153. package/__tests__/prompt-builder.test.js +0 -249
  154. package/__tests__/query-parser.test.js +0 -365
  155. package/__tests__/rag-derive.test.js +0 -169
  156. package/__tests__/registry-readiness.test.js +0 -292
  157. package/__tests__/registry.test.js +0 -420
  158. package/__tests__/salvage-ingest.test.js +0 -97
  159. package/__tests__/schemas.test.js +0 -331
  160. package/__tests__/shopping-adapters.test.js +0 -392
  161. package/__tests__/shopping-eleme-snapshot.test.js +0 -454
  162. package/__tests__/shopping-pinduoduo-snapshot.test.js +0 -484
  163. package/__tests__/shopping-snapshot.test.js +0 -438
  164. package/__tests__/shopping-vipshop-snapshot.test.js +0 -425
  165. package/__tests__/shopping-xianyu-snapshot.test.js +0 -451
  166. package/__tests__/sidecar-contacts-cross-validate.test.js +0 -186
  167. package/__tests__/sidecar-supervisor.test.js +0 -128
  168. package/__tests__/sign-providers.test.js +0 -62
  169. package/__tests__/social-adapters.test.js +0 -280
  170. package/__tests__/social-bilibili-snapshot.test.js +0 -278
  171. package/__tests__/social-douban-snapshot.test.js +0 -351
  172. package/__tests__/social-douyin-im-direct-read.test.js +0 -377
  173. package/__tests__/social-douyin-salvage-collector.test.js +0 -98
  174. package/__tests__/social-douyin-salvage-mapper.test.js +0 -90
  175. package/__tests__/social-douyin-snapshot.test.js +0 -256
  176. package/__tests__/social-kuaishou-snapshot.test.js +0 -362
  177. package/__tests__/social-toutiao-snapshot.test.js +0 -366
  178. package/__tests__/social-weibo-snapshot.test.js +0 -234
  179. package/__tests__/social-weibo-sqlite-device.test.js +0 -174
  180. package/__tests__/social-xiaohongshu-snapshot.test.js +0 -232
  181. package/__tests__/sqlite-leaf-salvage.test.js +0 -97
  182. package/__tests__/travel-adapters.test.js +0 -483
  183. package/__tests__/travel-maps-snapshot.test.js +0 -426
  184. package/__tests__/vault-driver-error.test.js +0 -74
  185. package/__tests__/vault-search-helpers.test.js +0 -104
  186. package/__tests__/vault-search.test.js +0 -423
  187. package/__tests__/vault.test.js +0 -767
  188. package/__tests__/wechat-adapter.test.js +0 -594
  189. package/__tests__/whatsapp-adapter.test.js +0 -138
  190. package/scripts/_make-fixture-all.js +0 -126
  191. package/scripts/_make-fixture-contacts.js +0 -84
  192. package/scripts/evaluate-entity-resolver.js +0 -213
  193. package/scripts/run-native-tests-sandbox.sh +0 -55
  194. package/scripts/smoke-phase-5-5.js +0 -196
  195. package/scripts/smoke-phase-5-7.js +0 -181
  196. package/scripts/smoke-system-data-contacts.js +0 -309
  197. package/scripts/smoke-system-data.js +0 -312
  198. package/vitest.config.js +0 -88
@@ -1,282 +0,0 @@
1
- /**
2
- * AIChat WebView 鉴权向导 — end-to-end integration test (Phase 10.3.6).
3
- *
4
- * Exercises the full chain WITHOUT any real Electron / real network:
5
- *
6
- * cookie-capture-spec
7
- * ↓
8
- * wizard-controller (paste-fallback mode)
9
- * ↓
10
- * accountsStore (real fs, temp dir)
11
- * ↓
12
- * vendor-adapter-bridge (stub validateCookie)
13
- * ↓
14
- * health-checker (real periodic logic, injected timers)
15
- *
16
- * Three scenarios:
17
- * - Happy: probe → register → health=ok
18
- * - Expired: register → mutate stored cookies to "stale" + adapter
19
- * returns ok=false → health=failed
20
- * - Version: register at specVersion=1 → bump to specVersion=2 →
21
- * health marks SPEC_VERSION_MISMATCH
22
- *
23
- * Lives in integration/ rather than adapters/ because it spans modules
24
- * (controller + factory + health-checker + real fs).
25
- */
26
-
27
- "use strict";
28
-
29
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
30
- import { mkdtempSync, rmSync, readFileSync, writeFileSync } from "node:fs";
31
- import { tmpdir } from "node:os";
32
- import { join } from "node:path";
33
-
34
- const {
35
- createAIChatWizardController,
36
- } = require("../../lib/adapters/ai-chat-history/wizard-controller");
37
- const {
38
- createAIChatHealthChecker,
39
- } = require("../../lib/adapters/ai-chat-history/health-checker");
40
-
41
- // ─── Helpers ──────────────────────────────────────────────────────────────
42
-
43
- function makeRealFsAccountsStore({ hubDir }) {
44
- // A minimal accountsStore that mirrors the shape used by both desktop
45
- // factory + cli wizard wirings, but uses fs directly so integration is
46
- // observable on disk.
47
- const filePath = join(hubDir, "aichat-accounts.json");
48
- function readAll() {
49
- try {
50
- return JSON.parse(readFileSync(filePath, "utf-8")) || {};
51
- } catch (err) {
52
- if (err && err.code === "ENOENT") return {};
53
- return {};
54
- }
55
- }
56
- return {
57
- get: async (v) => readAll()[v] || null,
58
- put: async (v, entry) => {
59
- const all = readAll();
60
- all[v] = entry;
61
- writeFileSync(filePath, JSON.stringify(all, null, 2), { mode: 0o600 });
62
- },
63
- delete: async (v) => {
64
- const all = readAll();
65
- delete all[v];
66
- writeFileSync(filePath, JSON.stringify(all, null, 2), { mode: 0o600 });
67
- },
68
- list: async () => Object.values(readAll()),
69
- _filePath: filePath,
70
- };
71
- }
72
-
73
- function makeStubVendorAdapter({ behaviorByVendor = {} } = {}) {
74
- return {
75
- registerVendor: vi.fn(async (vendor, cookies) => {
76
- const b = behaviorByVendor[vendor];
77
- if (typeof b === "function") return b(vendor, cookies);
78
- return b || { ok: true, userId: "u_" + vendor };
79
- }),
80
- };
81
- }
82
-
83
- function buildWizard({ accountsStore, vendorAdapter }) {
84
- // Minimal _deps for paste-fallback mode. classifier/specLookup come
85
- // from the real spec module.
86
- const {
87
- classifyProbedCookies,
88
- getSpec,
89
- listVendors,
90
- COOKIE_SPEC_VERSION,
91
- } = require("../../lib/adapters/ai-chat-history/cookie-capture-spec");
92
- return createAIChatWizardController({
93
- accountsStore,
94
- vendorAdapter,
95
- _deps: {
96
- sessionFactory: () => ({}),
97
- clock: () => Date.now(),
98
- logger: { info: () => {}, warn: () => {}, error: () => {} },
99
- classifier: classifyProbedCookies,
100
- specLookup: getSpec,
101
- knownVendors: listVendors(),
102
- cookieSpecVersion: COOKIE_SPEC_VERSION,
103
- fallbackMode: "paste",
104
- },
105
- });
106
- }
107
-
108
- let hubDir;
109
- beforeEach(() => {
110
- hubDir = mkdtempSync(join(tmpdir(), "aichat-wiz-e2e-"));
111
- });
112
- afterEach(() => {
113
- rmSync(hubDir, { recursive: true, force: true });
114
- });
115
-
116
- // ─── Scenario A: happy path ──────────────────────────────────────────────
117
-
118
- describe("aichat wizard E2E — happy path", () => {
119
- it("probe → register → file written → health=ok", async () => {
120
- const accountsStore = makeRealFsAccountsStore({ hubDir });
121
- const vendorAdapter = makeStubVendorAdapter({
122
- behaviorByVendor: { doubao: { ok: true, userId: "u_42" } },
123
- });
124
- const wiz = buildWizard({ accountsStore, vendorAdapter });
125
-
126
- // Step 1: open paste-fallback metadata for doubao
127
- const opened = await wiz.openVendorLogin({ vendor: "doubao" });
128
- expect(opened.ok).toBe(true);
129
- expect(opened.fallbackMode).toBe("paste");
130
- expect(opened.loginUrl).toMatch(/doubao\.com/);
131
-
132
- // Step 2: probe a pasted cookie header
133
- const probed = await wiz.probeCookies({
134
- vendor: "doubao",
135
- cookieHeader: "sessionid=abc; sid_guard=xyz",
136
- });
137
- expect(probed.ok).toBe(true);
138
- expect(probed.foundRequired).toEqual(["sessionid"]);
139
-
140
- // Step 3: register — writes the accounts file on disk
141
- const registered = await wiz.registerVendor({
142
- vendor: "doubao",
143
- cookies: probed.cookies,
144
- });
145
- expect(registered.ok).toBe(true);
146
- expect(registered.accountId).toBe("doubao:u_42");
147
-
148
- const onDisk = JSON.parse(readFileSync(accountsStore._filePath, "utf-8"));
149
- expect(onDisk.doubao.userId).toBe("u_42");
150
- expect(onDisk.doubao.cookieSpecVersion).toBe(1);
151
- expect(onDisk.doubao.lastHealth.ok).toBe(true);
152
-
153
- // Step 4: HealthChecker.runOnce should re-validate and stay ok
154
- const hc = createAIChatHealthChecker({
155
- accountsStore, vendorAdapter,
156
- _deps: { logger: { info: () => {}, warn: () => {}, error: () => {} } },
157
- });
158
- const r = await hc.runOnce();
159
- expect(r).toMatchObject({ checked: 1, ok: 1, failed: 0, mismatch: 0 });
160
- expect(vendorAdapter.registerVendor).toHaveBeenCalledTimes(2);
161
-
162
- // Stored entry still ok=true; lastHealth.at should have changed.
163
- const refreshed = JSON.parse(readFileSync(accountsStore._filePath, "utf-8"));
164
- expect(refreshed.doubao.lastHealth.ok).toBe(true);
165
- });
166
- });
167
-
168
- // ─── Scenario B: cookie expired ──────────────────────────────────────────
169
-
170
- describe("aichat wizard E2E — expired path", () => {
171
- it("after stored cookies stop validating, health marks failed", async () => {
172
- const accountsStore = makeRealFsAccountsStore({ hubDir });
173
- let isValid = true;
174
- const vendorAdapter = makeStubVendorAdapter({
175
- behaviorByVendor: {
176
- kimi: async () => (isValid
177
- ? { ok: true, userId: "u_kimi" }
178
- : { ok: false, reason: "COOKIE_EXPIRED" }),
179
- },
180
- });
181
- const wiz = buildWizard({ accountsStore, vendorAdapter });
182
-
183
- await wiz.registerVendor({ vendor: "kimi", cookies: { access_token: "ok" } });
184
-
185
- // Simulate the server invalidating the session.
186
- isValid = false;
187
-
188
- const hc = createAIChatHealthChecker({
189
- accountsStore, vendorAdapter,
190
- _deps: { logger: { info: () => {}, warn: () => {}, error: () => {} } },
191
- });
192
- const r = await hc.runOnce();
193
- expect(r).toMatchObject({ checked: 1, ok: 0, failed: 1, mismatch: 0 });
194
-
195
- const refreshed = JSON.parse(readFileSync(accountsStore._filePath, "utf-8"));
196
- expect(refreshed.kimi.lastHealth).toMatchObject({
197
- ok: false, reason: "COOKIE_EXPIRED",
198
- });
199
- });
200
- });
201
-
202
- // ─── Scenario C: spec version mismatch ───────────────────────────────────
203
-
204
- describe("aichat wizard E2E — spec version mismatch", () => {
205
- it("bumping specVersion marks old registrations SPEC_VERSION_MISMATCH", async () => {
206
- const accountsStore = makeRealFsAccountsStore({ hubDir });
207
- const vendorAdapter = makeStubVendorAdapter();
208
- const wiz = buildWizard({ accountsStore, vendorAdapter });
209
-
210
- await wiz.registerVendor({
211
- vendor: "deepseek",
212
- cookies: { userToken: "abc" },
213
- });
214
-
215
- // Bump spec version by passing 2 to HealthChecker (simulates a future
216
- // cookie-capture-spec version bump).
217
- const hc = createAIChatHealthChecker({
218
- accountsStore, vendorAdapter, specVersion: 2,
219
- _deps: { logger: { info: () => {}, warn: () => {}, error: () => {} } },
220
- });
221
- const r = await hc.runOnce();
222
- expect(r).toMatchObject({ checked: 1, mismatch: 1, ok: 0, failed: 0 });
223
-
224
- const refreshed = JSON.parse(readFileSync(accountsStore._filePath, "utf-8"));
225
- expect(refreshed.deepseek.lastHealth).toMatchObject({
226
- ok: false, reason: "SPEC_VERSION_MISMATCH",
227
- });
228
- // Adapter should NOT have been called for this entry — version gate
229
- // short-circuits before validateCookie.
230
- expect(vendorAdapter.registerVendor).toHaveBeenCalledTimes(1); // only register, no health validate
231
- });
232
- });
233
-
234
- // ─── Scenario D: full lifecycle (register → cleanupOrphanPartitions) ─────
235
-
236
- describe("aichat wizard E2E — orphan partition cleanup", () => {
237
- it("cleanupOrphanPartitions drops partitions whose vendor was never registered", async () => {
238
- const accountsStore = makeRealFsAccountsStore({ hubDir });
239
- const vendorAdapter = makeStubVendorAdapter();
240
- // For this scenario we use the in-test paste fallback — sessions are
241
- // simulated. cleanupOrphanPartitions runs against the partition list
242
- // we pass in, returning the vendors it would have cleared.
243
- const cleared = new Set();
244
- const wiz = createAIChatWizardController({
245
- accountsStore,
246
- vendorAdapter,
247
- _deps: {
248
- sessionFactory: (partName) => ({
249
- clearStorageData: async () => {
250
- cleared.add(partName);
251
- },
252
- }),
253
- clock: () => Date.now(),
254
- logger: { info: () => {}, warn: () => {}, error: () => {} },
255
- classifier: require("../../lib/adapters/ai-chat-history/cookie-capture-spec").classifyProbedCookies,
256
- specLookup: require("../../lib/adapters/ai-chat-history/cookie-capture-spec").getSpec,
257
- knownVendors: require("../../lib/adapters/ai-chat-history/cookie-capture-spec").listVendors(),
258
- cookieSpecVersion: 1,
259
- fallbackMode: "browser-view",
260
- },
261
- });
262
-
263
- await wiz.registerVendor({
264
- vendor: "kimi",
265
- cookies: { access_token: "x" },
266
- });
267
-
268
- const result = await wiz.cleanupOrphanPartitions({
269
- partitions: [
270
- "persist:aichat-deepseek", // orphan: no entry in store
271
- "persist:aichat-kimi", // registered: should NOT be cleared
272
- "persist:aichat-doubao", // orphan
273
- "persist:unrelated", // not ours: leave alone
274
- ],
275
- });
276
-
277
- expect(result.ok).toBe(true);
278
- expect(result.cleared.sort()).toEqual(["deepseek", "doubao"]);
279
- expect(cleared.has("persist:aichat-kimi")).toBe(false);
280
- expect(cleared.has("persist:unrelated")).toBe(false);
281
- });
282
- });
@@ -1,396 +0,0 @@
1
- "use strict";
2
-
3
- /**
4
- * Integration tests — cross-adapter pipelines.
5
- *
6
- * Exercises the full ingest → vault → EntityResolver → analysis skill
7
- * flow with multiple adapters feeding the same vault. Each test scenario
8
- * mirrors a real-world user journey from architecture-doc §7.1.
9
- */
10
-
11
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
12
-
13
- const fs = require("node:fs");
14
- const path = require("node:path");
15
- const os = require("node:os");
16
-
17
- const {
18
- LocalVault, generateKeyHex, AdapterRegistry,
19
- EntityResolver,
20
- SpendingSkill, RelationsSkill, FootprintSkill, TimelineSkill,
21
- } = require("../../lib");
22
-
23
- function makeRig() {
24
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pdh-int-"));
25
- const vault = new LocalVault({ path: path.join(dir, "v.db"), key: generateKeyHex() });
26
- vault.open();
27
- const resolver = new EntityResolver({ vault });
28
- const registry = new AdapterRegistry({ vault, entityResolver: resolver });
29
- return { vault, resolver, registry, dir };
30
- }
31
-
32
- function cleanup({ vault, dir }) {
33
- try { vault.close(); } catch (_e) {}
34
- try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) {}
35
- }
36
-
37
- // Minimal adapter for integration tests — yields pre-baked normalized
38
- // batches so we don't depend on real Email/Alipay/etc subprocesses
39
- class FixtureAdapter {
40
- constructor(name, batches, opts = {}) {
41
- this.name = name;
42
- this.version = "1.0.0";
43
- this.capabilities = ["sync:fixture"];
44
- this.extractMode = opts.extractMode || "web-api";
45
- this.rateLimits = {};
46
- this.dataDisclosure = { fields: [], sensitivity: "low", legalGate: false };
47
- this._batches = batches;
48
- }
49
- async authenticate() { return { ok: true }; }
50
- async healthCheck() { return { ok: true, lastChecked: Date.now() }; }
51
- async *sync() {
52
- for (const b of this._batches) {
53
- yield {
54
- adapter: this.name,
55
- originalId: b.originalId,
56
- capturedAt: b.capturedAt || Date.now(),
57
- payload: { batch: b.batch },
58
- };
59
- }
60
- }
61
- normalize(raw) { return raw.payload.batch; }
62
- }
63
-
64
- function source(adapter, originalId) {
65
- return {
66
- adapter, adapterVersion: "1.0.0", originalId,
67
- capturedAt: Date.now(), capturedBy: "api",
68
- };
69
- }
70
-
71
- // ─── Scenario 1: Email + Alipay → cross-source person merge ─────────────
72
-
73
- describe("Integration — Email + Alipay → EntityResolver merges same person", () => {
74
- let rig;
75
- afterEach(() => cleanup(rig));
76
-
77
- it("same email across both adapters → one merge group, analysis sees combined spending", async () => {
78
- rig = makeRig();
79
- const src = (a, oid) => source(a, oid);
80
-
81
- // Email adapter: mom@163.com sends an order confirmation
82
- const emailBatch = {
83
- events: [{
84
- id: "evt-email-1", type: "event", subtype: "message",
85
- occurredAt: Date.parse("2026-05-15T10:00:00Z"),
86
- actor: "person-email-mom",
87
- content: { title: "妈 forwarded a link", text: "看看这个" },
88
- ingestedAt: Date.now(),
89
- source: src("email-imap", "msg-1"),
90
- }],
91
- persons: [{
92
- id: "person-email-mom",
93
- type: "person", subtype: "contact",
94
- names: ["妈"],
95
- identifiers: { email: ["mom@163.com"] },
96
- ingestedAt: Date.now(),
97
- source: src("email-imap", "person-1"),
98
- }],
99
- places: [], items: [], topics: [],
100
- };
101
-
102
- // Alipay adapter: 转账给 with same email associated
103
- const alipayBatch = {
104
- events: [{
105
- id: "evt-alipay-1", type: "event", subtype: "transfer",
106
- occurredAt: Date.parse("2026-05-20T12:00:00Z"),
107
- actor: "person-self",
108
- participants: ["person-self", "person-alipay-chen"],
109
- content: {
110
- title: "转账给 陈XX",
111
- amount: { value: 500, currency: "CNY", direction: "out" },
112
- },
113
- ingestedAt: Date.now(),
114
- source: src("alipay-bill", "tx-1"),
115
- }],
116
- persons: [{
117
- id: "person-alipay-chen",
118
- type: "person", subtype: "contact",
119
- names: ["陈XX"],
120
- identifiers: { email: ["mom@163.com"] }, // same email → R1 merge
121
- ingestedAt: Date.now(),
122
- source: src("alipay-bill", "person-1"),
123
- }],
124
- places: [], items: [], topics: [],
125
- };
126
-
127
- rig.registry.register(new FixtureAdapter("email-imap", [
128
- { originalId: "msg-1", batch: emailBatch },
129
- ]));
130
- rig.registry.register(new FixtureAdapter("alipay-bill", [
131
- { originalId: "tx-1", batch: alipayBatch },
132
- ]));
133
-
134
- const r1 = await rig.registry.syncAdapter("email-imap");
135
- const r2 = await rig.registry.syncAdapter("alipay-bill");
136
-
137
- expect(r1.status).toBe("ok");
138
- expect(r2.status).toBe("ok");
139
- // EntityResolver auto-merged via email rule
140
- expect(r2.entityResolver.sameImmediate).toBeGreaterThanOrEqual(1);
141
-
142
- // Both persons now in same merge group
143
- const members = rig.vault.getMergeGroupMembers("person-email-mom").sort();
144
- expect(members).toEqual(["person-alipay-chen", "person-email-mom"]);
145
-
146
- // RelationsSkill with merge-group expansion picks up BOTH events
147
- const skill = new RelationsSkill({ vault: rig.vault });
148
- const result = await skill.run({ personId: "person-email-mom" });
149
- expect(result.profile.totalInteractions).toBeGreaterThanOrEqual(2);
150
- expect(result.profile.totalSpend).toBe(500);
151
- });
152
-
153
- it("non-overlapping persons stay separate", async () => {
154
- rig = makeRig();
155
- const src = (a, oid) => source(a, oid);
156
-
157
- const emailBatch = {
158
- events: [], topics: [], places: [], items: [],
159
- persons: [{
160
- id: "p-alice", type: "person", subtype: "contact",
161
- names: ["Alice"], identifiers: { email: ["alice@x.com"] },
162
- ingestedAt: Date.now(), source: src("email-imap", "p1"),
163
- }],
164
- };
165
- const alipayBatch = {
166
- events: [], topics: [], places: [], items: [],
167
- persons: [{
168
- id: "p-bob", type: "person", subtype: "contact",
169
- names: ["Bob"], identifiers: { phone: ["13999998888"] },
170
- ingestedAt: Date.now(), source: src("alipay-bill", "p1"),
171
- }],
172
- };
173
- rig.registry.register(new FixtureAdapter("email-imap", [{ originalId: "1", batch: emailBatch }]));
174
- rig.registry.register(new FixtureAdapter("alipay-bill", [{ originalId: "1", batch: alipayBatch }]));
175
- await rig.registry.syncAdapter("email-imap");
176
- await rig.registry.syncAdapter("alipay-bill");
177
- // No merge group should form
178
- expect(rig.vault.stats().mergeGroups).toBe(0);
179
- });
180
- });
181
-
182
- // ─── Scenario 2: Multi-adapter spending analysis ────────────────────────
183
-
184
- describe("Integration — SpendingSkill aggregates across adapters", () => {
185
- let rig;
186
- afterEach(() => cleanup(rig));
187
-
188
- it("sums spend from Alipay + Shopping (Taobao) + Travel (12306)", async () => {
189
- rig = makeRig();
190
- const now = Date.now();
191
- const src = (a, oid) => source(a, oid);
192
-
193
- function payEvent(adapter, id, amount, merchant, subtype = "payment") {
194
- return {
195
- events: [{
196
- id, type: "event", subtype,
197
- occurredAt: now - 24 * 3600_000,
198
- actor: "person-self",
199
- participants: ["person-self", `person-${adapter}-${merchant}`],
200
- content: { title: `${merchant} 消费`, amount: { value: amount, currency: "CNY", direction: "out" } },
201
- ingestedAt: now,
202
- source: src(adapter, id),
203
- extra: { counterparty: merchant },
204
- }],
205
- persons: [{
206
- id: `person-${adapter}-${merchant}`, type: "person", subtype: "merchant",
207
- names: [merchant], identifiers: {},
208
- ingestedAt: now, source: src(adapter, `p-${merchant}`),
209
- }],
210
- places: [], items: [], topics: [],
211
- };
212
- }
213
-
214
- rig.registry.register(new FixtureAdapter("alipay-bill", [
215
- { originalId: "alipay-1", batch: payEvent("alipay-bill", "evt-1", 38.50, "美团") },
216
- { originalId: "alipay-2", batch: payEvent("alipay-bill", "evt-2", 299, "淘宝") },
217
- ]));
218
- rig.registry.register(new FixtureAdapter("shopping-taobao", [
219
- { originalId: "tb-1", batch: payEvent("shopping-taobao", "evt-3", 999, "Apple官方", "order") },
220
- ]));
221
- rig.registry.register(new FixtureAdapter("travel-12306", [
222
- { originalId: "12306-1", batch: payEvent("travel-12306", "evt-4", 553, "12306", "payment") },
223
- ]));
224
-
225
- await rig.registry.syncAdapter("alipay-bill");
226
- await rig.registry.syncAdapter("shopping-taobao");
227
- await rig.registry.syncAdapter("travel-12306");
228
-
229
- const skill = new SpendingSkill({ vault: rig.vault });
230
- const result = await skill.run({});
231
- expect(result.summary.eventCount).toBe(4);
232
- expect(result.summary.totalSpend).toBeCloseTo(38.50 + 299 + 999 + 553, 2);
233
- expect(result.summary.uniqueCounterparties).toBe(4);
234
- // Top merchant should be Apple
235
- expect(result.breakdown[0].key).toBe("Apple官方");
236
- });
237
- });
238
-
239
- // ─── Scenario 3: Travel adapters + footprint ────────────────────────────
240
-
241
- describe("Integration — Travel adapters → FootprintSkill", () => {
242
- let rig;
243
- afterEach(() => cleanup(rig));
244
-
245
- it("Amap routes + 12306 tickets feed unified footprint", async () => {
246
- rig = makeRig();
247
- const now = Date.now();
248
- const src = (a, oid) => source(a, oid);
249
-
250
- function tripEvent(adapter, id, from, to, ts) {
251
- return {
252
- events: [{
253
- id, type: "event", subtype: "trip",
254
- occurredAt: ts,
255
- actor: "person-self",
256
- content: { title: `${from} → ${to}` },
257
- ingestedAt: now,
258
- source: src(adapter, id),
259
- extra: { from, to },
260
- }],
261
- persons: [], places: [], items: [], topics: [],
262
- };
263
- }
264
-
265
- rig.registry.register(new FixtureAdapter("travel-12306", [
266
- { originalId: "t1", batch: tripEvent("travel-12306", "evt-1", "上海虹桥", "北京南", now - 7 * 24 * 3600_000) },
267
- { originalId: "t2", batch: tripEvent("travel-12306", "evt-2", "北京南", "上海虹桥", now - 5 * 24 * 3600_000) },
268
- ]));
269
- rig.registry.register(new FixtureAdapter("travel-amap", [
270
- { originalId: "a1", batch: tripEvent("travel-amap", "evt-3", "家", "公司", now - 4 * 24 * 3600_000) },
271
- ]));
272
-
273
- await rig.registry.syncAdapter("travel-12306");
274
- await rig.registry.syncAdapter("travel-amap");
275
-
276
- const skill = new FootprintSkill({ vault: rig.vault });
277
- const result = await skill.run({});
278
- expect(result.summary.totalTrips).toBeGreaterThanOrEqual(3);
279
- expect(result.topPlaces.length).toBeGreaterThanOrEqual(3);
280
- });
281
- });
282
-
283
- // ─── Scenario 4: Timeline weaves multiple adapter sources ──────────────
284
-
285
- describe("Integration — TimelineSkill weaves messaging + payment events", () => {
286
- let rig;
287
- afterEach(() => cleanup(rig));
288
-
289
- it("chronological merge of WeChat + Alipay events", async () => {
290
- rig = makeRig();
291
- const now = Date.now();
292
- const src = (a, oid) => source(a, oid);
293
-
294
- const wechatBatch = {
295
- events: [{
296
- id: "wc-1", type: "event", subtype: "message",
297
- occurredAt: now - 1 * 3600_000, // 1h ago
298
- actor: "person-wechat-friend",
299
- content: { title: "妈: 吃饭了么", text: "吃饭了么" },
300
- ingestedAt: now,
301
- source: src("wechat", "msg-1"),
302
- }],
303
- persons: [], places: [], items: [], topics: [],
304
- };
305
- const alipayBatch = {
306
- events: [{
307
- id: "ap-1", type: "event", subtype: "payment",
308
- occurredAt: now - 30 * 60_000, // 30min ago
309
- actor: "person-self",
310
- content: { title: "美团外卖", amount: { value: 30, currency: "CNY", direction: "out" } },
311
- ingestedAt: now,
312
- source: src("alipay-bill", "tx-1"),
313
- extra: { counterparty: "美团" },
314
- }],
315
- persons: [], places: [], items: [], topics: [],
316
- };
317
-
318
- rig.registry.register(new FixtureAdapter("wechat", [{ originalId: "wc-1", batch: wechatBatch }]));
319
- rig.registry.register(new FixtureAdapter("alipay-bill", [{ originalId: "tx-1", batch: alipayBatch }]));
320
- await rig.registry.syncAdapter("wechat");
321
- await rig.registry.syncAdapter("alipay-bill");
322
-
323
- const skill = new TimelineSkill({ vault: rig.vault });
324
- const result = await skill.run({ sinceDays: 1 });
325
- expect(result.entries.length).toBe(2);
326
- // WeChat msg older → first; Alipay payment newer → second
327
- expect(result.entries[0].adapter).toBe("wechat");
328
- expect(result.entries[1].adapter).toBe("alipay-bill");
329
- expect(result.summary.byAdapter.wechat).toBe(1);
330
- expect(result.summary.byAdapter["alipay-bill"]).toBe(1);
331
- });
332
- });
333
-
334
- // ─── Scenario 5: EntityResolver + analysis cascade ─────────────────────
335
-
336
- describe("Integration — EntityResolver review queue + manual merge unlocks combined view", () => {
337
- let rig;
338
- afterEach(() => cleanup(rig));
339
-
340
- it("uncertain pair queued → user merges → analysis picks up combined events", async () => {
341
- rig = makeRig();
342
- const now = Date.now();
343
- const src = (a, oid) => source(a, oid);
344
-
345
- // Two "张三" persons across email + alipay with no overlapping ids
346
- const emailBatch = {
347
- events: [{
348
- id: "evt-1", type: "event", subtype: "message",
349
- occurredAt: now, actor: "person-email-zs",
350
- content: { title: "msg" }, ingestedAt: now,
351
- source: src("email-imap", "m1"),
352
- }],
353
- persons: [{
354
- id: "person-email-zs", type: "person", subtype: "contact",
355
- names: ["张三"], identifiers: {},
356
- ingestedAt: now, source: src("email-imap", "p1"),
357
- }],
358
- places: [], items: [], topics: [],
359
- };
360
- const alipayBatch = {
361
- events: [{
362
- id: "evt-2", type: "event", subtype: "transfer",
363
- occurredAt: now, actor: "person-self",
364
- participants: ["person-self", "person-alipay-zs"],
365
- content: { title: "转账", amount: { value: 100, currency: "CNY", direction: "out" } },
366
- ingestedAt: now,
367
- source: src("alipay-bill", "t1"),
368
- }],
369
- persons: [{
370
- id: "person-alipay-zs", type: "person", subtype: "contact",
371
- names: ["张三"], identifiers: {},
372
- ingestedAt: now, source: src("alipay-bill", "p1"),
373
- }],
374
- places: [], items: [], topics: [],
375
- };
376
- rig.registry.register(new FixtureAdapter("email-imap", [{ originalId: "1", batch: emailBatch }]));
377
- rig.registry.register(new FixtureAdapter("alipay-bill", [{ originalId: "1", batch: alipayBatch }]));
378
- await rig.registry.syncAdapter("email-imap");
379
- const r2 = await rig.registry.syncAdapter("alipay-bill");
380
-
381
- // Without identifier overlap, rule stage = uncertain → queued
382
- expect(r2.entityResolver.enqueued).toBeGreaterThanOrEqual(1);
383
- expect(rig.vault.stats().mergeGroups).toBe(0);
384
-
385
- // User manually merges via UI
386
- rig.resolver.manualMerge({ aId: "person-email-zs", bId: "person-alipay-zs" });
387
- expect(rig.vault.getMergeGroupMembers("person-email-zs").sort()).toEqual(
388
- ["person-alipay-zs", "person-email-zs"]
389
- );
390
-
391
- // Now relations skill sees combined view
392
- const skill = new RelationsSkill({ vault: rig.vault });
393
- const result = await skill.run({ personId: "person-email-zs" });
394
- expect(result.profile.totalInteractions).toBe(2);
395
- });
396
- });