@chainlesschain/personal-data-hub 0.4.28 → 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.
- package/README.md +13 -5
- package/lib/adapters/social-douyin-adb/usage-profile-reader.js +253 -0
- package/lib/adapters/social-douyin-adb/watch-history-reader.js +104 -31
- package/lib/adapters/social-toutiao-adb/article-reader.js +202 -0
- package/lib/analysis-skills/overview.js +24 -4
- package/lib/analysis-skills/spending.js +63 -2
- package/lib/analysis-skills/timeline.js +11 -6
- package/lib/prompt-builder.js +15 -1
- package/lib/query-parser.js +38 -8
- package/package.json +4 -1
- package/__tests__/adapter-guide.test.js +0 -47
- package/__tests__/adapter-spec.test.js +0 -78
- package/__tests__/adapters/ai-chat-cookie-capture-spec.test.js +0 -211
- package/__tests__/adapters/ai-chat-health-checker.test.js +0 -262
- package/__tests__/adapters/ai-chat-history.test.js +0 -396
- package/__tests__/adapters/ai-chat-http-client.test.js +0 -242
- package/__tests__/adapters/ai-chat-vendors.test.js +0 -874
- package/__tests__/adapters/alipay-bill-adapter.test.js +0 -538
- package/__tests__/adapters/apple-health.test.js +0 -95
- package/__tests__/adapters/bank-family.test.js +0 -125
- package/__tests__/adapters/biz-tianyancha.test.js +0 -159
- package/__tests__/adapters/browser-history-chrome.test.js +0 -377
- package/__tests__/adapters/browser-history-edge.test.js +0 -159
- package/__tests__/adapters/car-mercedesme.test.js +0 -74
- package/__tests__/adapters/doc-baidu-netdisk.test.js +0 -102
- package/__tests__/adapters/doc-camscanner.test.js +0 -147
- package/__tests__/adapters/doc-platforms.test.js +0 -177
- package/__tests__/adapters/edu-huawei-learning-live.test.js +0 -198
- package/__tests__/adapters/edu-zuoyebang-live.test.js +0 -226
- package/__tests__/adapters/email-adapter-snapshot.test.js +0 -237
- package/__tests__/adapters/email-adapter.test.js +0 -742
- package/__tests__/adapters/email-classifier.test.js +0 -347
- package/__tests__/adapters/email-imap-session.test.js +0 -334
- package/__tests__/adapters/email-parser.test.js +0 -244
- package/__tests__/adapters/email-pdf-extractor.test.js +0 -529
- package/__tests__/adapters/email-providers.test.js +0 -84
- package/__tests__/adapters/email-retry-progress.test.js +0 -294
- package/__tests__/adapters/email-templates.test.js +0 -822
- package/__tests__/adapters/family-23-collectors-scaffold.test.js +0 -182
- package/__tests__/adapters/finance-alipay-live.test.js +0 -258
- package/__tests__/adapters/finance-dcep.test.js +0 -74
- package/__tests__/adapters/fitness-joyrun.test.js +0 -82
- package/__tests__/adapters/game-genshin-live.test.js +0 -238
- package/__tests__/adapters/game-genshin-scaffold.test.js +0 -108
- package/__tests__/adapters/game-honor-of-kings-live.test.js +0 -230
- package/__tests__/adapters/git-activity.test.js +0 -222
- package/__tests__/adapters/gov-12123.test.js +0 -103
- package/__tests__/adapters/gov-ixiamen.test.js +0 -150
- package/__tests__/adapters/gov-tax.test.js +0 -135
- package/__tests__/adapters/health-meiyou.test.js +0 -125
- package/__tests__/adapters/local-files.test.js +0 -264
- package/__tests__/adapters/local-im-pc.test.js +0 -154
- package/__tests__/adapters/messaging-whatsapp.test.js +0 -289
- package/__tests__/adapters/music-kugou.test.js +0 -187
- package/__tests__/adapters/music-qq.test.js +0 -112
- package/__tests__/adapters/netease-music-live.test.js +0 -244
- package/__tests__/adapters/netease-music.test.js +0 -74
- package/__tests__/adapters/pc-local-discovery.test.js +0 -141
- package/__tests__/adapters/qq-pc-direct-read.test.js +0 -227
- package/__tests__/adapters/reading-family.test.js +0 -108
- package/__tests__/adapters/recruit-boss.test.js +0 -180
- package/__tests__/adapters/shell-history.test.js +0 -180
- package/__tests__/adapters/shopping-base.test.js +0 -179
- package/__tests__/adapters/shopping-dianping.test.js +0 -239
- package/__tests__/adapters/social-bilibili-adb-api-client.test.js +0 -721
- package/__tests__/adapters/social-bilibili-adb-chromium-cookies-reader.test.js +0 -346
- package/__tests__/adapters/social-bilibili-adb-collector.test.js +0 -284
- package/__tests__/adapters/social-bilibili-adb-cookies-extension.test.js +0 -343
- package/__tests__/adapters/social-bilibili-adb-snapshot-builder.test.js +0 -296
- package/__tests__/adapters/social-csdn.test.js +0 -175
- package/__tests__/adapters/social-dongchedi.test.js +0 -165
- package/__tests__/adapters/social-douyin-adb-aweme-detail.test.js +0 -165
- package/__tests__/adapters/social-douyin-adb-collector.test.js +0 -254
- package/__tests__/adapters/social-douyin-adb-db-extension.test.js +0 -114
- package/__tests__/adapters/social-douyin-adb-im-db-parser.test.js +0 -304
- package/__tests__/adapters/social-douyin-adb-snapshot-builder.test.js +0 -216
- package/__tests__/adapters/social-douyin-adb-watch-history.test.js +0 -192
- package/__tests__/adapters/social-kuaishou-adb-api-client.test.js +0 -496
- package/__tests__/adapters/social-kuaishou-adb-collector.test.js +0 -276
- package/__tests__/adapters/social-kuaishou-adb-cookies-extension.test.js +0 -152
- package/__tests__/adapters/social-kuaishou-adb-snapshot-builder.test.js +0 -178
- package/__tests__/adapters/social-toutiao-adb-account-reader.test.js +0 -135
- package/__tests__/adapters/social-toutiao-adb-api-client.test.js +0 -626
- package/__tests__/adapters/social-toutiao-adb-collector.test.js +0 -378
- package/__tests__/adapters/social-toutiao-adb-cookies-extension.test.js +0 -193
- package/__tests__/adapters/social-toutiao-adb-snapshot-builder.test.js +0 -196
- package/__tests__/adapters/social-toutiao-kuaishou-scaffold.test.js +0 -311
- package/__tests__/adapters/social-weibo-adb-api-client.test.js +0 -362
- package/__tests__/adapters/social-weibo-adb-collector.test.js +0 -201
- package/__tests__/adapters/social-weibo-adb-cookies-extension.test.js +0 -167
- package/__tests__/adapters/social-weibo-adb-snapshot-builder.test.js +0 -189
- package/__tests__/adapters/social-xiaohongshu-adb-api-client.test.js +0 -431
- package/__tests__/adapters/social-xiaohongshu-adb-collector.test.js +0 -207
- package/__tests__/adapters/social-xiaohongshu-adb-cookies-extension.test.js +0 -0
- package/__tests__/adapters/social-xiaohongshu-adb-sign-provider-injection.test.js +0 -351
- package/__tests__/adapters/social-xiaohongshu-adb-sign.test.js +0 -130
- package/__tests__/adapters/social-xiaohongshu-adb-snapshot-builder.test.js +0 -200
- package/__tests__/adapters/social-zhihu.test.js +0 -246
- package/__tests__/adapters/system-data-adapter.test.js +0 -443
- package/__tests__/adapters/system-data-android-ingest.test.js +0 -144
- package/__tests__/adapters/system-data-android.test.js +0 -519
- package/__tests__/adapters/system-data-disclosure.test.js +0 -153
- package/__tests__/adapters/travel-12306.test.js +0 -512
- package/__tests__/adapters/travel-amap.test.js +0 -219
- package/__tests__/adapters/travel-baidu-map.test.js +0 -305
- package/__tests__/adapters/travel-base.test.js +0 -205
- package/__tests__/adapters/travel-ctrip.test.js +0 -377
- package/__tests__/adapters/travel-didi-consumer.test.js +0 -66
- package/__tests__/adapters/travel-didi.test.js +0 -204
- package/__tests__/adapters/travel-tencent-map.test.js +0 -207
- package/__tests__/adapters/travel-tongcheng.test.js +0 -289
- package/__tests__/adapters/video-platforms.test.js +0 -152
- package/__tests__/adapters/video-xigua.test.js +0 -106
- package/__tests__/adapters/vscode.test.js +0 -299
- package/__tests__/adapters/wechat-bootstrap.test.js +0 -240
- package/__tests__/adapters/wechat-env-probe.test.js +0 -162
- package/__tests__/adapters/wechat-frida-agent.test.js +0 -322
- package/__tests__/adapters/wechat-frida-integration.test.js +0 -149
- package/__tests__/adapters/wechat-frida-key-provider.test.js +0 -188
- package/__tests__/adapters/wechat-md5-key-provider.test.js +0 -101
- package/__tests__/adapters/wechat-pc-direct-read.test.js +0 -365
- package/__tests__/adapters/wechat-pc-group-topic.test.js +0 -63
- package/__tests__/adapters/wechat-pc-v4-sidecar.test.js +0 -72
- package/__tests__/adapters/weread.test.js +0 -123
- package/__tests__/adapters/wework-pc.test.js +0 -124
- package/__tests__/adapters/win-recent.test.js +0 -192
- package/__tests__/analysis-skills.test.js +0 -679
- package/__tests__/analysis.test.js +0 -1845
- package/__tests__/audio-ximalaya-snapshot.test.js +0 -279
- package/__tests__/batch.test.js +0 -133
- package/__tests__/bridges-cc-kg.test.js +0 -231
- package/__tests__/bridges-cc-llm.test.js +0 -191
- package/__tests__/bridges-cc-rag.test.js +0 -162
- package/__tests__/categories.test.js +0 -92
- package/__tests__/e2e/ai-chat-cross-source-journey.test.js +0 -213
- package/__tests__/e2e/full-user-journey.test.js +0 -188
- package/__tests__/e2e/local-data-adapters-cli.e2e.test.js +0 -146
- package/__tests__/entity-resolver-ingest-hook.test.js +0 -177
- package/__tests__/entity-resolver-stages.test.js +0 -411
- package/__tests__/entity-resolver-vault.test.js +0 -249
- package/__tests__/entity-resolver.test.js +0 -526
- package/__tests__/fitness-keep-snapshot.test.js +0 -224
- package/__tests__/fixtures/entity-resolver-200-mock.json +0 -96
- package/__tests__/ids.test.js +0 -45
- package/__tests__/integration/ai-chat-history-registry.test.js +0 -228
- package/__tests__/integration/aichat-wizard-end-to-end.test.js +0 -282
- package/__tests__/integration/cross-adapter-pipelines.test.js +0 -396
- package/__tests__/integration/local-data-adapters-pipeline.test.js +0 -373
- package/__tests__/integration/social-bilibili-pipeline.test.js +0 -261
- package/__tests__/integration/wechat-bootstrap-end-to-end.test.js +0 -390
- package/__tests__/key-providers.test.js +0 -126
- package/__tests__/kg-derive.test.js +0 -219
- package/__tests__/llm-client.test.js +0 -122
- package/__tests__/longtail-adapters.test.js +0 -281
- package/__tests__/messaging-qq-snapshot.test.js +0 -294
- package/__tests__/mobile-extractor-encrypted.test.js +0 -460
- package/__tests__/mobile-extractor.test.js +0 -288
- package/__tests__/mock-adapter.test.js +0 -93
- package/__tests__/prompt-builder.test.js +0 -249
- package/__tests__/query-parser.test.js +0 -302
- package/__tests__/rag-derive.test.js +0 -169
- package/__tests__/registry-readiness.test.js +0 -292
- package/__tests__/registry.test.js +0 -420
- package/__tests__/salvage-ingest.test.js +0 -97
- package/__tests__/schemas.test.js +0 -331
- package/__tests__/shopping-adapters.test.js +0 -392
- package/__tests__/shopping-eleme-snapshot.test.js +0 -454
- package/__tests__/shopping-pinduoduo-snapshot.test.js +0 -484
- package/__tests__/shopping-snapshot.test.js +0 -438
- package/__tests__/shopping-vipshop-snapshot.test.js +0 -425
- package/__tests__/shopping-xianyu-snapshot.test.js +0 -451
- package/__tests__/sidecar-contacts-cross-validate.test.js +0 -186
- package/__tests__/sidecar-supervisor.test.js +0 -128
- package/__tests__/sign-providers.test.js +0 -62
- package/__tests__/social-adapters.test.js +0 -280
- package/__tests__/social-bilibili-snapshot.test.js +0 -278
- package/__tests__/social-douban-snapshot.test.js +0 -351
- package/__tests__/social-douyin-im-direct-read.test.js +0 -377
- package/__tests__/social-douyin-salvage-collector.test.js +0 -98
- package/__tests__/social-douyin-salvage-mapper.test.js +0 -90
- package/__tests__/social-douyin-snapshot.test.js +0 -256
- package/__tests__/social-kuaishou-snapshot.test.js +0 -362
- package/__tests__/social-toutiao-snapshot.test.js +0 -366
- package/__tests__/social-weibo-snapshot.test.js +0 -234
- package/__tests__/social-weibo-sqlite-device.test.js +0 -174
- package/__tests__/social-xiaohongshu-snapshot.test.js +0 -232
- package/__tests__/sqlite-leaf-salvage.test.js +0 -97
- package/__tests__/travel-adapters.test.js +0 -483
- package/__tests__/travel-maps-snapshot.test.js +0 -426
- package/__tests__/vault-driver-error.test.js +0 -74
- package/__tests__/vault-search-helpers.test.js +0 -104
- package/__tests__/vault-search.test.js +0 -423
- package/__tests__/vault.test.js +0 -767
- package/__tests__/wechat-adapter.test.js +0 -594
- package/__tests__/whatsapp-adapter.test.js +0 -138
- package/scripts/_make-fixture-all.js +0 -126
- package/scripts/_make-fixture-contacts.js +0 -84
- package/scripts/evaluate-entity-resolver.js +0 -213
- package/scripts/run-native-tests-sandbox.sh +0 -55
- package/scripts/smoke-phase-5-5.js +0 -196
- package/scripts/smoke-phase-5-7.js +0 -181
- package/scripts/smoke-system-data-contacts.js +0 -309
- package/scripts/smoke-system-data.js +0 -312
- package/vitest.config.js +0 -88
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
createAIChatHealthChecker,
|
|
7
|
-
DEFAULT_INTERVAL_MS,
|
|
8
|
-
DEFAULT_FIRST_RUN_DELAY_MS,
|
|
9
|
-
} = require("../../lib/adapters/ai-chat-history/health-checker");
|
|
10
|
-
|
|
11
|
-
// ─── fakes ────────────────────────────────────────────────────────────────
|
|
12
|
-
|
|
13
|
-
function makeFakeAccountsStore({ initial = {} } = {}) {
|
|
14
|
-
const store = new Map(Object.entries(initial));
|
|
15
|
-
return {
|
|
16
|
-
get: vi.fn(async (v) => store.get(v) || null),
|
|
17
|
-
put: vi.fn(async (v, e) => store.set(v, e)),
|
|
18
|
-
delete: vi.fn(async (v) => store.delete(v)),
|
|
19
|
-
list: vi.fn(async () => Array.from(store.values())),
|
|
20
|
-
_store: store,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function makeFakeAdapter({ behaviors = {} } = {}) {
|
|
25
|
-
// behaviors = { vendor: ({ok, reason?, throw?: msg}) }
|
|
26
|
-
return {
|
|
27
|
-
registerVendor: vi.fn(async (vendor, _cookies) => {
|
|
28
|
-
const b = behaviors[vendor];
|
|
29
|
-
if (!b) return { ok: true, userId: "u_" + vendor };
|
|
30
|
-
if (b.throw) throw new Error(b.throw);
|
|
31
|
-
return b;
|
|
32
|
-
}),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function makeFakeTimers() {
|
|
37
|
-
// Deterministic timer dispatcher we can step through. We track scheduled
|
|
38
|
-
// callbacks + their delays in a queue and let tests `flush` them in order.
|
|
39
|
-
const scheduled = [];
|
|
40
|
-
let _now = 0;
|
|
41
|
-
let _id = 0;
|
|
42
|
-
const setTimeout = (fn, ms) => {
|
|
43
|
-
const id = ++_id;
|
|
44
|
-
scheduled.push({ id, type: "timeout", fireAt: _now + ms, fn });
|
|
45
|
-
return id;
|
|
46
|
-
};
|
|
47
|
-
const setInterval = (fn, ms) => {
|
|
48
|
-
const id = ++_id;
|
|
49
|
-
scheduled.push({ id, type: "interval", fireAt: _now + ms, ms, fn });
|
|
50
|
-
return id;
|
|
51
|
-
};
|
|
52
|
-
const clearTimeout = (id) => {
|
|
53
|
-
const i = scheduled.findIndex((s) => s.id === id);
|
|
54
|
-
if (i >= 0) scheduled.splice(i, 1);
|
|
55
|
-
};
|
|
56
|
-
const clearInterval = clearTimeout;
|
|
57
|
-
async function advance(ms) {
|
|
58
|
-
_now += ms;
|
|
59
|
-
while (true) {
|
|
60
|
-
const due = scheduled
|
|
61
|
-
.filter((s) => s.fireAt <= _now)
|
|
62
|
-
.sort((a, b) => a.fireAt - b.fireAt);
|
|
63
|
-
if (due.length === 0) break;
|
|
64
|
-
const next = due[0];
|
|
65
|
-
const idx = scheduled.indexOf(next);
|
|
66
|
-
scheduled.splice(idx, 1);
|
|
67
|
-
if (next.type === "interval") {
|
|
68
|
-
scheduled.push({ ...next, fireAt: next.fireAt + next.ms });
|
|
69
|
-
}
|
|
70
|
-
await next.fn();
|
|
71
|
-
// Flush any fire-and-forget microtasks the callback kicked off
|
|
72
|
-
// (runOnce returns a promise; the callback doesn't await it).
|
|
73
|
-
await new Promise((r) => setImmediate(r));
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
setTimeout, setInterval, clearTimeout, clearInterval,
|
|
78
|
-
clock: () => _now,
|
|
79
|
-
advance,
|
|
80
|
-
_scheduled: scheduled,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ─── construction guards ─────────────────────────────────────────────────
|
|
85
|
-
|
|
86
|
-
describe("createAIChatHealthChecker — guards", () => {
|
|
87
|
-
it("throws when accountsStore.list missing", () => {
|
|
88
|
-
expect(() => createAIChatHealthChecker({ accountsStore: {}, vendorAdapter: { registerVendor: () => {} } })).toThrow(/accountsStore.list/);
|
|
89
|
-
});
|
|
90
|
-
it("throws when accountsStore.put missing", () => {
|
|
91
|
-
expect(() => createAIChatHealthChecker({ accountsStore: { list: () => [] }, vendorAdapter: { registerVendor: () => {} } })).toThrow(/accountsStore.put/);
|
|
92
|
-
});
|
|
93
|
-
it("throws when vendorAdapter.registerVendor missing", () => {
|
|
94
|
-
expect(() => createAIChatHealthChecker({ accountsStore: makeFakeAccountsStore() })).toThrow(/vendorAdapter.registerVendor/);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// ─── runOnce behavior ────────────────────────────────────────────────────
|
|
99
|
-
|
|
100
|
-
describe("runOnce — health classification", () => {
|
|
101
|
-
let timers;
|
|
102
|
-
beforeEach(() => {
|
|
103
|
-
timers = makeFakeTimers();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("marks vendors ok when adapter returns ok=true", async () => {
|
|
107
|
-
const store = makeFakeAccountsStore({
|
|
108
|
-
initial: {
|
|
109
|
-
deepseek: { vendor: "deepseek", cookies: { userToken: "x" }, cookieSpecVersion: 1 },
|
|
110
|
-
kimi: { vendor: "kimi", cookies: { access_token: "y" }, cookieSpecVersion: 1 },
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
const adapter = makeFakeAdapter();
|
|
114
|
-
const hc = createAIChatHealthChecker({
|
|
115
|
-
accountsStore: store, vendorAdapter: adapter, _deps: timers,
|
|
116
|
-
});
|
|
117
|
-
const r = await hc.runOnce();
|
|
118
|
-
expect(r).toMatchObject({ checked: 2, ok: 2, failed: 0, mismatch: 0 });
|
|
119
|
-
expect(store._store.get("deepseek").lastHealth.ok).toBe(true);
|
|
120
|
-
expect(store._store.get("kimi").lastHealth.ok).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("marks vendors failed when adapter returns ok=false (with reason)", async () => {
|
|
124
|
-
const store = makeFakeAccountsStore({
|
|
125
|
-
initial: { deepseek: { vendor: "deepseek", cookies: {}, cookieSpecVersion: 1 } },
|
|
126
|
-
});
|
|
127
|
-
const adapter = makeFakeAdapter({
|
|
128
|
-
behaviors: { deepseek: { ok: false, reason: "COOKIE_EXPIRED" } },
|
|
129
|
-
});
|
|
130
|
-
const hc = createAIChatHealthChecker({
|
|
131
|
-
accountsStore: store, vendorAdapter: adapter, _deps: timers,
|
|
132
|
-
});
|
|
133
|
-
const r = await hc.runOnce();
|
|
134
|
-
expect(r).toMatchObject({ checked: 1, failed: 1, ok: 0 });
|
|
135
|
-
expect(store._store.get("deepseek").lastHealth).toMatchObject({
|
|
136
|
-
ok: false, reason: "COOKIE_EXPIRED",
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("marks SPEC_VERSION_MISMATCH when entry.cookieSpecVersion < specVersion", async () => {
|
|
141
|
-
const store = makeFakeAccountsStore({
|
|
142
|
-
initial: { deepseek: { vendor: "deepseek", cookies: { x: "y" }, cookieSpecVersion: 0 } },
|
|
143
|
-
});
|
|
144
|
-
const adapter = makeFakeAdapter();
|
|
145
|
-
const hc = createAIChatHealthChecker({
|
|
146
|
-
accountsStore: store, vendorAdapter: adapter, specVersion: 2, _deps: timers,
|
|
147
|
-
});
|
|
148
|
-
const r = await hc.runOnce();
|
|
149
|
-
expect(r).toMatchObject({ checked: 1, mismatch: 1, ok: 0, failed: 0 });
|
|
150
|
-
expect(store._store.get("deepseek").lastHealth).toMatchObject({
|
|
151
|
-
ok: false, reason: "SPEC_VERSION_MISMATCH",
|
|
152
|
-
});
|
|
153
|
-
// Adapter was NOT called because the version gate fired first
|
|
154
|
-
expect(adapter.registerVendor).not.toHaveBeenCalled();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("captures ADAPTER_THREW when registerVendor rejects", async () => {
|
|
158
|
-
const store = makeFakeAccountsStore({
|
|
159
|
-
initial: { deepseek: { vendor: "deepseek", cookies: { x: "y" }, cookieSpecVersion: 1 } },
|
|
160
|
-
});
|
|
161
|
-
const adapter = makeFakeAdapter({ behaviors: { deepseek: { throw: "net down" } } });
|
|
162
|
-
const hc = createAIChatHealthChecker({
|
|
163
|
-
accountsStore: store, vendorAdapter: adapter, _deps: timers,
|
|
164
|
-
});
|
|
165
|
-
const r = await hc.runOnce();
|
|
166
|
-
expect(r).toMatchObject({ checked: 1, failed: 1 });
|
|
167
|
-
expect(store._store.get("deepseek").lastHealth).toMatchObject({
|
|
168
|
-
ok: false, reason: "ADAPTER_THREW",
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("skips a run if previous one is still in flight", async () => {
|
|
173
|
-
const store = makeFakeAccountsStore({
|
|
174
|
-
initial: { deepseek: { vendor: "deepseek", cookies: {}, cookieSpecVersion: 1 } },
|
|
175
|
-
});
|
|
176
|
-
let release;
|
|
177
|
-
const adapter = {
|
|
178
|
-
registerVendor: vi.fn(() => new Promise((resolve) => {
|
|
179
|
-
release = () => resolve({ ok: true, userId: "u" });
|
|
180
|
-
})),
|
|
181
|
-
};
|
|
182
|
-
const hc = createAIChatHealthChecker({
|
|
183
|
-
accountsStore: store, vendorAdapter: adapter, _deps: timers,
|
|
184
|
-
});
|
|
185
|
-
const p1 = hc.runOnce();
|
|
186
|
-
const r2 = await hc.runOnce();
|
|
187
|
-
expect(r2.skipped).toBe(true);
|
|
188
|
-
release();
|
|
189
|
-
await p1;
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("returns 0 counts on empty accounts list", async () => {
|
|
193
|
-
const hc = createAIChatHealthChecker({
|
|
194
|
-
accountsStore: makeFakeAccountsStore(),
|
|
195
|
-
vendorAdapter: makeFakeAdapter(),
|
|
196
|
-
_deps: timers,
|
|
197
|
-
});
|
|
198
|
-
const r = await hc.runOnce();
|
|
199
|
-
expect(r).toMatchObject({ checked: 0, ok: 0, failed: 0, mismatch: 0 });
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// ─── start / stop / interval scheduling ──────────────────────────────────
|
|
204
|
-
|
|
205
|
-
describe("start / stop / interval", () => {
|
|
206
|
-
let timers, store, adapter;
|
|
207
|
-
beforeEach(() => {
|
|
208
|
-
timers = makeFakeTimers();
|
|
209
|
-
store = makeFakeAccountsStore({
|
|
210
|
-
initial: { deepseek: { vendor: "deepseek", cookies: { userToken: "x" }, cookieSpecVersion: 1 } },
|
|
211
|
-
});
|
|
212
|
-
adapter = makeFakeAdapter();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("first run happens after firstRunDelayMs (30s default), then interval (6h)", async () => {
|
|
216
|
-
const hc = createAIChatHealthChecker({
|
|
217
|
-
accountsStore: store, vendorAdapter: adapter,
|
|
218
|
-
intervalMs: 6 * 3600_000, firstRunDelayMs: 30_000,
|
|
219
|
-
_deps: timers,
|
|
220
|
-
});
|
|
221
|
-
hc.start();
|
|
222
|
-
expect(hc.status().started).toBe(true);
|
|
223
|
-
// 10s in — nothing has fired yet
|
|
224
|
-
await timers.advance(10_000);
|
|
225
|
-
expect(adapter.registerVendor).not.toHaveBeenCalled();
|
|
226
|
-
// 30s mark — first run fires
|
|
227
|
-
await timers.advance(20_000);
|
|
228
|
-
expect(adapter.registerVendor).toHaveBeenCalledTimes(1);
|
|
229
|
-
// Another 6h — interval run #1
|
|
230
|
-
await timers.advance(6 * 3600_000);
|
|
231
|
-
expect(adapter.registerVendor).toHaveBeenCalledTimes(2);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it("start is idempotent — second call returns false", () => {
|
|
235
|
-
const hc = createAIChatHealthChecker({
|
|
236
|
-
accountsStore: store, vendorAdapter: adapter, _deps: timers,
|
|
237
|
-
});
|
|
238
|
-
expect(hc.start()).toBe(true);
|
|
239
|
-
expect(hc.start()).toBe(false);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("stop clears pending first-run + interval", async () => {
|
|
243
|
-
const hc = createAIChatHealthChecker({
|
|
244
|
-
accountsStore: store, vendorAdapter: adapter, firstRunDelayMs: 30_000, _deps: timers,
|
|
245
|
-
});
|
|
246
|
-
hc.start();
|
|
247
|
-
hc.stop();
|
|
248
|
-
expect(hc.status().started).toBe(false);
|
|
249
|
-
// 60s in — nothing fires since we stopped
|
|
250
|
-
await timers.advance(60_000);
|
|
251
|
-
expect(adapter.registerVendor).not.toHaveBeenCalled();
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it("uses default 6h interval + 30s delay when not specified", () => {
|
|
255
|
-
const hc = createAIChatHealthChecker({
|
|
256
|
-
accountsStore: store, vendorAdapter: adapter, _deps: timers,
|
|
257
|
-
});
|
|
258
|
-
const s = hc.status();
|
|
259
|
-
expect(s.intervalMs).toBe(DEFAULT_INTERVAL_MS);
|
|
260
|
-
expect(s.firstRunDelayMs).toBe(DEFAULT_FIRST_RUN_DELAY_MS);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect } from "vitest";
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
AIChatHistoryAdapter,
|
|
7
|
-
CookieAuthSession,
|
|
8
|
-
NotImplementedYetError,
|
|
9
|
-
assertVendorSpec,
|
|
10
|
-
SUPPORTED_VENDORS,
|
|
11
|
-
DEFAULT_VENDOR_SPECS,
|
|
12
|
-
schemaMap,
|
|
13
|
-
} = require("../../lib/adapters/ai-chat-history");
|
|
14
|
-
const { assertAdapter } = require("../../lib/adapter-spec");
|
|
15
|
-
const {
|
|
16
|
-
EVENT_SUBTYPES,
|
|
17
|
-
PERSON_SUBTYPES,
|
|
18
|
-
ITEM_SUBTYPES,
|
|
19
|
-
} = require("../../lib/constants");
|
|
20
|
-
|
|
21
|
-
// ─── vendor-spec assertion ──────────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
describe("assertVendorSpec — SUPPORTED_VENDORS", () => {
|
|
24
|
-
it("9 vendors are declared (Phase 10.2 +doubao scaffold)", () => {
|
|
25
|
-
expect(SUPPORTED_VENDORS).toEqual([
|
|
26
|
-
"deepseek",
|
|
27
|
-
"kimi",
|
|
28
|
-
"tongyi",
|
|
29
|
-
"zhipu",
|
|
30
|
-
"hunyuan",
|
|
31
|
-
"qianfan",
|
|
32
|
-
"coze",
|
|
33
|
-
"dreamina",
|
|
34
|
-
"doubao",
|
|
35
|
-
]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("DEFAULT_VENDOR_SPECS includes one entry per supported vendor", () => {
|
|
39
|
-
for (const v of SUPPORTED_VENDORS) {
|
|
40
|
-
expect(DEFAULT_VENDOR_SPECS[v]).toBeDefined();
|
|
41
|
-
expect(DEFAULT_VENDOR_SPECS[v].name).toBe(v);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("each shipped vendor spec passes assertVendorSpec", () => {
|
|
46
|
-
for (const v of SUPPORTED_VENDORS) {
|
|
47
|
-
const check = assertVendorSpec(DEFAULT_VENDOR_SPECS[v]);
|
|
48
|
-
if (!check.ok) {
|
|
49
|
-
throw new Error(`vendor ${v} failed: ${check.errors.join("; ")}`);
|
|
50
|
-
}
|
|
51
|
-
expect(check.ok).toBe(true);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("rejects spec missing rateLimits.perMinute", () => {
|
|
56
|
-
const bad = { ...DEFAULT_VENDOR_SPECS.deepseek, rateLimits: {} };
|
|
57
|
-
const check = assertVendorSpec(bad);
|
|
58
|
-
expect(check.ok).toBe(false);
|
|
59
|
-
expect(check.errors.some((e) => e.includes("rateLimits"))).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("rejects spec with non-https loginUrl", () => {
|
|
63
|
-
const bad = { ...DEFAULT_VENDOR_SPECS.deepseek, loginUrl: "http://chat.deepseek.com/" };
|
|
64
|
-
const check = assertVendorSpec(bad);
|
|
65
|
-
expect(check.ok).toBe(false);
|
|
66
|
-
expect(check.errors.some((e) => e.includes("loginUrl"))).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("rejects unsupported vendor name", () => {
|
|
70
|
-
const bad = { ...DEFAULT_VENDOR_SPECS.deepseek, name: "claude" };
|
|
71
|
-
const check = assertVendorSpec(bad);
|
|
72
|
-
expect(check.ok).toBe(false);
|
|
73
|
-
expect(check.errors.some((e) => e.includes("name"))).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// ─── PersonalDataAdapter contract ────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
describe("AIChatHistoryAdapter contract", () => {
|
|
80
|
-
it("freshly constructed adapter passes assertAdapter", () => {
|
|
81
|
-
const a = new AIChatHistoryAdapter();
|
|
82
|
-
const r = assertAdapter(a);
|
|
83
|
-
if (!r.ok) throw new Error(r.errors.join("; "));
|
|
84
|
-
expect(r.ok).toBe(true);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("declares name + version + capabilities + extractMode", () => {
|
|
88
|
-
const a = new AIChatHistoryAdapter();
|
|
89
|
-
expect(a.name).toBe("ai-chat-history");
|
|
90
|
-
expect(a.version).toMatch(/^0\.1\.\d+$/);
|
|
91
|
-
expect(a.capabilities).toContain("sync:cookie-multi-vendor");
|
|
92
|
-
expect(a.extractMode).toBe("web-api");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("dataDisclosure is high-sensitivity without legalGate (cookies, not third-party content)", () => {
|
|
96
|
-
const a = new AIChatHistoryAdapter();
|
|
97
|
-
expect(a.dataDisclosure.sensitivity).toBe("high");
|
|
98
|
-
expect(a.dataDisclosure.legalGate).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("constructor rejects invalid vendor spec override", () => {
|
|
102
|
-
expect(
|
|
103
|
-
() =>
|
|
104
|
-
new AIChatHistoryAdapter({
|
|
105
|
-
vendorSpecs: { deepseek: { ...DEFAULT_VENDOR_SPECS.deepseek, rateLimits: {} } },
|
|
106
|
-
}),
|
|
107
|
-
).toThrow(/rateLimits/);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// ─── authenticate / healthCheck ──────────────────────────────────────────
|
|
112
|
-
|
|
113
|
-
describe("AIChatHistoryAdapter.authenticate", () => {
|
|
114
|
-
it("returns vendorsReady=[] when no sessions configured", async () => {
|
|
115
|
-
const a = new AIChatHistoryAdapter();
|
|
116
|
-
const r = await a.authenticate();
|
|
117
|
-
expect(r.ok).toBe(true);
|
|
118
|
-
expect(r.vendorsReady).toEqual([]);
|
|
119
|
-
expect(r.vendorsNeedingLogin.sort()).toEqual([...SUPPORTED_VENDORS].sort());
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("reflects configured sessions", async () => {
|
|
123
|
-
const a = new AIChatHistoryAdapter();
|
|
124
|
-
a.setSession("deepseek", new CookieAuthSession({ vendor: "deepseek", cookies: [{ name: "userToken", value: "x" }] }));
|
|
125
|
-
a.setSession("kimi", new CookieAuthSession({ vendor: "kimi", cookies: [{ name: "sess", value: "y" }] }));
|
|
126
|
-
const r = await a.authenticate();
|
|
127
|
-
expect(r.vendorsReady.sort()).toEqual(["deepseek", "kimi"]);
|
|
128
|
-
expect(r.vendorsNeedingLogin).not.toContain("deepseek");
|
|
129
|
-
expect(r.vendorsNeedingLogin).not.toContain("kimi");
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe("AIChatHistoryAdapter.healthCheck", () => {
|
|
134
|
-
it("reports per-vendor no-session when fresh", async () => {
|
|
135
|
-
const a = new AIChatHistoryAdapter();
|
|
136
|
-
const h = await a.healthCheck();
|
|
137
|
-
expect(h.ok).toBe(true);
|
|
138
|
-
for (const v of SUPPORTED_VENDORS) {
|
|
139
|
-
expect(h.perVendor[v]).toEqual({ ok: false, reason: "no-session" });
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("calls vendor.validateCookie when session present", async () => {
|
|
144
|
-
const a = new AIChatHistoryAdapter({
|
|
145
|
-
vendorSpecs: {
|
|
146
|
-
deepseek: {
|
|
147
|
-
...DEFAULT_VENDOR_SPECS.deepseek,
|
|
148
|
-
validateCookie: async () => ({ ok: true, expiresAt: 999 }),
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
a.setSession("deepseek", new CookieAuthSession({ vendor: "deepseek", cookies: [{ name: "x", value: "1" }] }));
|
|
153
|
-
const h = await a.healthCheck();
|
|
154
|
-
expect(h.perVendor.deepseek).toEqual({ ok: true, expiresAt: 999 });
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// ─── sync — Phase 10.1 stub path ─────────────────────────────────────────
|
|
159
|
-
|
|
160
|
-
describe("AIChatHistoryAdapter.sync — skeleton path", () => {
|
|
161
|
-
it("yields nothing when no sessions configured", async () => {
|
|
162
|
-
const a = new AIChatHistoryAdapter();
|
|
163
|
-
const out = [];
|
|
164
|
-
for await (const ev of a.sync()) out.push(ev);
|
|
165
|
-
expect(out).toEqual([]);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("vendor-not-wired sentinel path: surfaced via NotImplementedYetError direct throw", async () => {
|
|
169
|
-
// Phase 10.2 complete — all 8 vendors are wired with real h5 API.
|
|
170
|
-
// The sentinel kind:"vendor-not-wired" path remains in the sync()
|
|
171
|
-
// catch-block for forward-compat (e.g. when adding new vendors in
|
|
172
|
-
// Phase 10.3+ before their wiring lands). Verify the dispatch by
|
|
173
|
-
// injecting a synthetic always-throwing vendor spec.
|
|
174
|
-
const { NotImplementedYetError } = require("../../lib/adapters/ai-chat-history/vendor-spec");
|
|
175
|
-
const fakeSpec = {
|
|
176
|
-
...require("../../lib/adapters/ai-chat-history").DEFAULT_VENDOR_SPECS.deepseek,
|
|
177
|
-
// eslint-disable-next-line require-yield
|
|
178
|
-
async *listConversations() { throw new NotImplementedYetError("deepseek", "listConversations"); },
|
|
179
|
-
};
|
|
180
|
-
const a = new AIChatHistoryAdapter({ vendorSpecs: { deepseek: fakeSpec } });
|
|
181
|
-
a.setSession(
|
|
182
|
-
"deepseek",
|
|
183
|
-
new CookieAuthSession({ vendor: "deepseek", cookies: [{ name: "userToken", value: "x" }] }),
|
|
184
|
-
);
|
|
185
|
-
const out = [];
|
|
186
|
-
for await (const ev of a.sync({ vendors: ["deepseek"] })) out.push(ev);
|
|
187
|
-
expect(out.length).toBe(1);
|
|
188
|
-
expect(out[0].payload.kind).toBe("vendor-not-wired");
|
|
189
|
-
expect(out[0].payload.vendor).toBe("deepseek");
|
|
190
|
-
expect(out[0].payload.error).toBe("VENDOR_NOT_WIRED");
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("can be driven end-to-end with a mock vendor spec", async () => {
|
|
194
|
-
// Inject a faux vendor that yields one conversation + two messages.
|
|
195
|
-
const FAKE_CONV = {
|
|
196
|
-
vendor: "deepseek",
|
|
197
|
-
originalId: "conv-1",
|
|
198
|
-
title: "test title",
|
|
199
|
-
modelName: "deepseek-r1",
|
|
200
|
-
createdAt: Date.parse("2026-05-19T00:00:00Z"),
|
|
201
|
-
updatedAt: Date.parse("2026-05-19T01:00:00Z"),
|
|
202
|
-
messageCount: 2,
|
|
203
|
-
};
|
|
204
|
-
const FAKE_MSGS = [
|
|
205
|
-
{
|
|
206
|
-
vendor: "deepseek",
|
|
207
|
-
originalId: "m1",
|
|
208
|
-
conversationId: "conv-1",
|
|
209
|
-
role: "user",
|
|
210
|
-
content: { text: "hello" },
|
|
211
|
-
createdAt: Date.parse("2026-05-19T00:00:00Z"),
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
vendor: "deepseek",
|
|
215
|
-
originalId: "m2",
|
|
216
|
-
conversationId: "conv-1",
|
|
217
|
-
role: "assistant",
|
|
218
|
-
content: { text: "hi there" },
|
|
219
|
-
createdAt: Date.parse("2026-05-19T00:00:05Z"),
|
|
220
|
-
modelName: "deepseek-r1",
|
|
221
|
-
},
|
|
222
|
-
];
|
|
223
|
-
const a = new AIChatHistoryAdapter({
|
|
224
|
-
vendorSpecs: {
|
|
225
|
-
deepseek: {
|
|
226
|
-
...DEFAULT_VENDOR_SPECS.deepseek,
|
|
227
|
-
async validateCookie() { return { ok: true }; },
|
|
228
|
-
async *listConversations() { yield FAKE_CONV; },
|
|
229
|
-
async *listMessages() { for (const m of FAKE_MSGS) yield m; },
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
});
|
|
233
|
-
a.setSession(
|
|
234
|
-
"deepseek",
|
|
235
|
-
new CookieAuthSession({ vendor: "deepseek", cookies: [{ name: "userToken", value: "x" }] }),
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
const out = [];
|
|
239
|
-
for await (const ev of a.sync({ vendors: ["deepseek"] })) out.push(ev);
|
|
240
|
-
expect(out.length).toBe(3); // 1 conv + 2 msgs
|
|
241
|
-
expect(out[0].payload.kind).toBe("conversation");
|
|
242
|
-
expect(out[1].payload.kind).toBe("message");
|
|
243
|
-
expect(out[2].payload.kind).toBe("message");
|
|
244
|
-
|
|
245
|
-
// Drive normalize() over each
|
|
246
|
-
const batches = out.map((r) => a.normalize(r));
|
|
247
|
-
// First batch: conv → topic + vendor person
|
|
248
|
-
expect(batches[0].topics.length).toBe(1);
|
|
249
|
-
expect(batches[0].persons.length).toBe(1);
|
|
250
|
-
expect(batches[0].persons[0].subtype).toBe(PERSON_SUBTYPES.AI_AGENT);
|
|
251
|
-
expect(batches[0].events.length).toBe(0);
|
|
252
|
-
// Message batches: 1 event each
|
|
253
|
-
expect(batches[1].events.length).toBe(1);
|
|
254
|
-
expect(batches[1].events[0].subtype).toBe(EVENT_SUBTYPES.AI_MESSAGE);
|
|
255
|
-
expect(batches[1].events[0].actor).toBe("person-self");
|
|
256
|
-
expect(batches[2].events[0].actor).toBe("person-ai-deepseek");
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// ─── schema-map — deterministic transforms ───────────────────────────────
|
|
261
|
-
|
|
262
|
-
describe("schema-map.conversationToBatch", () => {
|
|
263
|
-
it("produces vendor Person + Topic + Event per message", () => {
|
|
264
|
-
const conv = {
|
|
265
|
-
vendor: "kimi",
|
|
266
|
-
originalId: "abc",
|
|
267
|
-
title: "research notes",
|
|
268
|
-
createdAt: 1700000000000,
|
|
269
|
-
updatedAt: 1700001000000,
|
|
270
|
-
};
|
|
271
|
-
const msgs = [
|
|
272
|
-
{ vendor: "kimi", originalId: "m1", conversationId: "abc", role: "user",
|
|
273
|
-
content: { text: "summarize this paper" }, createdAt: 1700000000000 },
|
|
274
|
-
{ vendor: "kimi", originalId: "m2", conversationId: "abc", role: "assistant",
|
|
275
|
-
content: { text: "ok here is the summary..." }, createdAt: 1700000010000 },
|
|
276
|
-
];
|
|
277
|
-
const batch = schemaMap.conversationToBatch(conv, msgs, { displayName: "Kimi" });
|
|
278
|
-
expect(batch.events.length).toBe(2);
|
|
279
|
-
expect(batch.events[0].subtype).toBe(EVENT_SUBTYPES.AI_MESSAGE);
|
|
280
|
-
expect(batch.events[0].topics).toEqual(["topic-aiconv-kimi-abc"]);
|
|
281
|
-
expect(batch.events[1].actor).toBe("person-ai-kimi");
|
|
282
|
-
expect(batch.persons.length).toBe(1);
|
|
283
|
-
expect(batch.persons[0].id).toBe("person-ai-kimi");
|
|
284
|
-
expect(batch.persons[0].names).toEqual(["Kimi"]);
|
|
285
|
-
expect(batch.topics.length).toBe(1);
|
|
286
|
-
expect(batch.topics[0].id).toBe("topic-aiconv-kimi-abc");
|
|
287
|
-
expect(batch.topics[0].extra.modelName).toBeUndefined(); // not in this conv
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("upgrades to ai-image-generation subtype when generatedImages present", () => {
|
|
291
|
-
const conv = { vendor: "dreamina", originalId: "img-1", createdAt: 1, updatedAt: 1 };
|
|
292
|
-
const msgs = [
|
|
293
|
-
{
|
|
294
|
-
vendor: "dreamina",
|
|
295
|
-
originalId: "m1",
|
|
296
|
-
conversationId: "img-1",
|
|
297
|
-
role: "assistant",
|
|
298
|
-
content: {
|
|
299
|
-
generatedImages: [{ url: "https://cdn/x.png", prompt: "a cat" }],
|
|
300
|
-
},
|
|
301
|
-
createdAt: 1700000000000,
|
|
302
|
-
},
|
|
303
|
-
];
|
|
304
|
-
const batch = schemaMap.conversationToBatch(conv, msgs, { displayName: "Dreamina" });
|
|
305
|
-
expect(batch.events[0].subtype).toBe(EVENT_SUBTYPES.AI_IMAGE_GENERATION);
|
|
306
|
-
expect(batch.items.length).toBe(1);
|
|
307
|
-
expect(batch.items[0].subtype).toBe(ITEM_SUBTYPES.MEDIA);
|
|
308
|
-
expect(batch.items[0].extra.prompt).toBe("a cat");
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("throws when message vendor mismatches conversation vendor", () => {
|
|
312
|
-
const conv = { vendor: "kimi", originalId: "a", createdAt: 1, updatedAt: 1 };
|
|
313
|
-
const msgs = [{ vendor: "deepseek", originalId: "m", conversationId: "a", role: "user", content: {}, createdAt: 1 }];
|
|
314
|
-
expect(() => schemaMap.conversationToBatch(conv, msgs)).toThrow(/vendor/);
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
describe("schema-map.mergeBatches", () => {
|
|
319
|
-
it("dedupes vendor Person by id", () => {
|
|
320
|
-
const conv1 = { vendor: "kimi", originalId: "a", createdAt: 1, updatedAt: 1 };
|
|
321
|
-
const conv2 = { vendor: "kimi", originalId: "b", createdAt: 1, updatedAt: 1 };
|
|
322
|
-
const b1 = schemaMap.conversationToBatch(conv1, [], { displayName: "Kimi" });
|
|
323
|
-
const b2 = schemaMap.conversationToBatch(conv2, [], { displayName: "Kimi" });
|
|
324
|
-
const merged = schemaMap.mergeBatches([b1, b2]);
|
|
325
|
-
expect(merged.persons.length).toBe(1); // dedup
|
|
326
|
-
expect(merged.topics.length).toBe(2); // distinct conversations
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// ─── CookieAuthSession ───────────────────────────────────────────────────
|
|
331
|
-
|
|
332
|
-
describe("CookieAuthSession", () => {
|
|
333
|
-
it("flattens cookies into a Cookie header", () => {
|
|
334
|
-
const s = new CookieAuthSession({
|
|
335
|
-
vendor: "deepseek",
|
|
336
|
-
cookies: [
|
|
337
|
-
{ name: "userToken", value: "abc" },
|
|
338
|
-
{ name: "sessId", value: "xyz" },
|
|
339
|
-
],
|
|
340
|
-
});
|
|
341
|
-
expect(s.toHeaderValue()).toBe("userToken=abc; sessId=xyz");
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it("filters by domain suffix when matchDomain provided", () => {
|
|
345
|
-
const s = new CookieAuthSession({
|
|
346
|
-
vendor: "coze",
|
|
347
|
-
cookies: [
|
|
348
|
-
{ name: "wwwTok", value: "1", domain: "www.coze.cn" },
|
|
349
|
-
{ name: "cdnTok", value: "2", domain: ".sf-cdn.com" },
|
|
350
|
-
],
|
|
351
|
-
});
|
|
352
|
-
expect(s.toHeaderValue("www.coze.cn")).toBe("wwwTok=1");
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
it("get(name) returns the matching cookie value or undefined", () => {
|
|
356
|
-
const s = new CookieAuthSession({
|
|
357
|
-
vendor: "deepseek",
|
|
358
|
-
cookies: [{ name: "userToken", value: "x" }],
|
|
359
|
-
});
|
|
360
|
-
expect(s.get("userToken")).toBe("x");
|
|
361
|
-
expect(s.get("nope")).toBeUndefined();
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("serializes and round-trips via toJSON / fromJSON", () => {
|
|
365
|
-
const s = new CookieAuthSession({
|
|
366
|
-
vendor: "deepseek",
|
|
367
|
-
cookies: [{ name: "userToken", value: "x" }],
|
|
368
|
-
capturedAt: 12345,
|
|
369
|
-
});
|
|
370
|
-
const json = s.toJSON();
|
|
371
|
-
const restored = CookieAuthSession.fromJSON(json);
|
|
372
|
-
expect(restored.vendor).toBe("deepseek");
|
|
373
|
-
expect(restored.capturedAt).toBe(12345);
|
|
374
|
-
expect(restored.get("userToken")).toBe("x");
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it("isExpired returns true when explicit expirationDate has passed", () => {
|
|
378
|
-
const past = Math.floor(Date.now() / 1000) - 3600;
|
|
379
|
-
const s = new CookieAuthSession({
|
|
380
|
-
vendor: "deepseek",
|
|
381
|
-
cookies: [{ name: "x", value: "y", expirationDate: past }],
|
|
382
|
-
});
|
|
383
|
-
expect(s.isExpired()).toBe(true);
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
// ─── NotImplementedYetError ──────────────────────────────────────────────
|
|
388
|
-
|
|
389
|
-
describe("NotImplementedYetError", () => {
|
|
390
|
-
it("is throwable with code VENDOR_NOT_WIRED", () => {
|
|
391
|
-
const err = new NotImplementedYetError("deepseek", "listConversations");
|
|
392
|
-
expect(err.code).toBe("VENDOR_NOT_WIRED");
|
|
393
|
-
expect(err.vendor).toBe("deepseek");
|
|
394
|
-
expect(err.capability).toBe("listConversations");
|
|
395
|
-
});
|
|
396
|
-
});
|