@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9
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 +88 -88
- package/dist/opencode-anthropic-auth-cli.mjs +804 -507
- package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
- package/package.json +67 -59
- package/src/__tests__/billing-edge-cases.test.ts +59 -59
- package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
- package/src/__tests__/cc-comparison.test.ts +87 -87
- package/src/__tests__/cc-credentials.test.ts +254 -250
- package/src/__tests__/cch-drift-checker.test.ts +51 -51
- package/src/__tests__/cch-native-style.test.ts +56 -56
- package/src/__tests__/debug-gating.test.ts +42 -42
- package/src/__tests__/decomposition-smoke.test.ts +68 -68
- package/src/__tests__/fingerprint-regression.test.ts +575 -566
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
- package/src/__tests__/helpers/conversation-history.ts +119 -119
- package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
- package/src/__tests__/helpers/deferred.ts +69 -69
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
- package/src/__tests__/helpers/in-memory-storage.ts +88 -88
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
- package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
- package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
- package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
- package/src/__tests__/helpers/sse.ts +209 -209
- package/src/__tests__/index.parallel.test.ts +605 -595
- package/src/__tests__/sanitization-regex.test.ts +112 -112
- package/src/__tests__/state-bounds.test.ts +90 -90
- package/src/account-identity.test.ts +197 -192
- package/src/account-identity.ts +69 -67
- package/src/account-state.test.ts +86 -86
- package/src/account-state.ts +25 -25
- package/src/accounts/matching.test.ts +335 -0
- package/src/accounts/matching.ts +167 -0
- package/src/accounts/persistence.test.ts +345 -0
- package/src/accounts/persistence.ts +432 -0
- package/src/accounts/repair.test.ts +276 -0
- package/src/accounts/repair.ts +407 -0
- package/src/accounts.dedup.test.ts +621 -621
- package/src/accounts.test.ts +933 -929
- package/src/accounts.ts +633 -989
- package/src/backoff.test.ts +345 -345
- package/src/backoff.ts +219 -219
- package/src/betas.ts +124 -124
- package/src/bun-fetch.test.ts +345 -342
- package/src/bun-fetch.ts +424 -424
- package/src/bun-proxy.test.ts +25 -25
- package/src/bun-proxy.ts +209 -209
- package/src/cc-credentials.ts +111 -111
- package/src/circuit-breaker.test.ts +184 -184
- package/src/circuit-breaker.ts +169 -169
- package/src/cli/commands/auth.ts +963 -0
- package/src/cli/commands/config.ts +547 -0
- package/src/cli/formatting.test.ts +406 -0
- package/src/cli/formatting.ts +219 -0
- package/src/cli.ts +255 -2022
- package/src/commands/handlers/betas.ts +100 -0
- package/src/commands/handlers/config.ts +99 -0
- package/src/commands/handlers/files.ts +375 -0
- package/src/commands/oauth-flow.ts +181 -166
- package/src/commands/prompts.ts +61 -61
- package/src/commands/router.test.ts +421 -0
- package/src/commands/router.ts +143 -635
- package/src/config.test.ts +482 -482
- package/src/config.ts +412 -404
- package/src/constants.ts +48 -48
- package/src/drift/cch-constants.ts +95 -95
- package/src/env.ts +111 -105
- package/src/headers/billing.ts +33 -33
- package/src/headers/builder.ts +130 -130
- package/src/headers/cch.ts +75 -75
- package/src/headers/stainless.ts +25 -25
- package/src/headers/user-agent.ts +23 -23
- package/src/index.ts +436 -828
- package/src/models.ts +27 -27
- package/src/oauth.test.ts +102 -102
- package/src/oauth.ts +178 -178
- package/src/parent-pid-watcher.test.ts +148 -148
- package/src/parent-pid-watcher.ts +69 -69
- package/src/plugin-helpers.ts +82 -82
- package/src/refresh-helpers.ts +145 -139
- package/src/refresh-lock.test.ts +94 -94
- package/src/refresh-lock.ts +93 -93
- package/src/request/body.history.test.ts +579 -571
- package/src/request/body.ts +255 -255
- package/src/request/metadata.ts +65 -65
- package/src/request/retry.test.ts +156 -156
- package/src/request/retry.ts +67 -67
- package/src/request/url.ts +21 -21
- package/src/request-orchestration-helpers.ts +648 -0
- package/src/response/index.ts +5 -5
- package/src/response/mcp.ts +58 -58
- package/src/response/streaming.test.ts +313 -311
- package/src/response/streaming.ts +412 -410
- package/src/rotation.test.ts +304 -301
- package/src/rotation.ts +205 -205
- package/src/storage.test.ts +547 -547
- package/src/storage.ts +315 -291
- package/src/system-prompt/builder.ts +38 -38
- package/src/system-prompt/index.ts +5 -5
- package/src/system-prompt/normalize.ts +60 -60
- package/src/system-prompt/sanitize.ts +30 -30
- package/src/thinking.ts +21 -20
- package/src/token-refresh.test.ts +265 -265
- package/src/token-refresh.ts +219 -214
- package/src/types.ts +30 -30
- package/dist/bun-proxy.mjs +0 -291
|
@@ -1,61 +1,61 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
EXPECTED_CCH_PLACEHOLDER,
|
|
5
|
+
EXPECTED_CCH_SALT,
|
|
6
|
+
EXPECTED_CCH_SEED,
|
|
7
|
+
EXPECTED_XXHASH64_PRIMES,
|
|
8
|
+
bigintToLittleEndianBytes,
|
|
9
|
+
findAllOccurrences,
|
|
10
|
+
scanCchConstants,
|
|
11
11
|
} from "../drift/cch-constants.js";
|
|
12
12
|
|
|
13
13
|
describe("cch drift checker helpers", () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
it("encodes bigint constants as little-endian bytes", () => {
|
|
15
|
+
expect(Array.from(bigintToLittleEndianBytes(EXPECTED_CCH_SEED))).toEqual([
|
|
16
|
+
0x1e, 0x83, 0x06, 0xc8, 0x6a, 0x73, 0x52, 0x6e,
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("finds all occurrences of a byte pattern", () => {
|
|
21
|
+
const haystack = new Uint8Array([1, 2, 3, 1, 2, 3, 4]);
|
|
22
|
+
const needle = new Uint8Array([1, 2, 3]);
|
|
23
|
+
expect(findAllOccurrences(haystack, needle)).toEqual([0, 3]);
|
|
24
|
+
});
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
describe("scanCchConstants", () => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
28
|
+
it("passes when all standalone constants are present", () => {
|
|
29
|
+
const bytes = new Uint8Array([
|
|
30
|
+
...new TextEncoder().encode(`xx cch=${EXPECTED_CCH_PLACEHOLDER} yy ${EXPECTED_CCH_SALT} zz`),
|
|
31
|
+
...bigintToLittleEndianBytes(EXPECTED_CCH_SEED),
|
|
32
|
+
...EXPECTED_XXHASH64_PRIMES.flatMap((prime) => Array.from(bigintToLittleEndianBytes(prime))),
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const report = scanCchConstants(bytes, "synthetic-standalone", "standalone");
|
|
36
|
+
expect(report.passed).toBe(true);
|
|
37
|
+
expect(report.findings).toEqual([]);
|
|
38
|
+
expect(report.checked.placeholder).toBeGreaterThan(0);
|
|
39
|
+
expect(report.checked.salt).toBeGreaterThan(0);
|
|
40
|
+
expect(report.checked.seed).toBeGreaterThan(0);
|
|
41
|
+
expect(report.checked.primes.every((count) => count > 0)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("fails critically when placeholder and seed are missing", () => {
|
|
45
|
+
const bytes = new TextEncoder().encode(`missing ${EXPECTED_CCH_SALT}`);
|
|
46
|
+
const report = scanCchConstants(bytes, "broken-standalone", "standalone");
|
|
47
|
+
|
|
48
|
+
expect(report.passed).toBe(false);
|
|
49
|
+
expect(report.findings.map((finding) => finding.name)).toContain("cch placeholder");
|
|
50
|
+
expect(report.findings.map((finding) => finding.name)).toContain("native cch seed");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("only requires placeholder and salt for npm cli bundles", () => {
|
|
54
|
+
const bytes = new TextEncoder().encode(`prefix cch=${EXPECTED_CCH_PLACEHOLDER}; ${EXPECTED_CCH_SALT} suffix`);
|
|
55
|
+
const report = scanCchConstants(bytes, "synthetic-bundle", "bundle");
|
|
56
|
+
|
|
57
|
+
expect(report.passed).toBe(true);
|
|
58
|
+
expect(report.checked.seed).toBe(0);
|
|
59
|
+
expect(report.findings).toEqual([]);
|
|
60
|
+
});
|
|
61
61
|
});
|
|
@@ -5,72 +5,72 @@ import { CCH_PLACEHOLDER, computeNativeStyleCch, replaceNativeStyleCch, xxHash64
|
|
|
5
5
|
import { transformRequestBody } from "../request/body.js";
|
|
6
6
|
|
|
7
7
|
describe("native-style cch", () => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
it("matches independently verified xxHash64 reference values", () => {
|
|
9
|
+
expect(xxHash64(new TextEncoder().encode(""))).toBe(0x6e798575d82647edn);
|
|
10
|
+
expect(xxHash64(new TextEncoder().encode("a"))).toBe(0x8404a7f0a8a6bcaen);
|
|
11
|
+
expect(xxHash64(new TextEncoder().encode("hello"))).toBe(0x95ab2f66e009922an);
|
|
12
|
+
});
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
it("derives the expected 5-char cch from independently verified vectors", () => {
|
|
15
|
+
expect(computeNativeStyleCch("")).toBe("647ed");
|
|
16
|
+
expect(computeNativeStyleCch("cch=00000")).toBe("1d7f8");
|
|
17
|
+
expect(
|
|
18
|
+
computeNativeStyleCch("x-anthropic-billing-header: cc_version=2.1.101.0e7; cc_entrypoint=cli; cch=00000;"),
|
|
19
|
+
).toBe("101fb");
|
|
20
|
+
});
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
it("replaces the placeholder in a serialized body", () => {
|
|
23
|
+
const serialized =
|
|
24
|
+
'{"system":[{"type":"text","text":"x-anthropic-billing-header: cc_version=2.1.101.0e7; cc_entrypoint=cli; cch=00000;"}],"messages":[{"role":"user","content":"Reply with the single word: OK"}]}';
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
expect(replaceNativeStyleCch(serialized)).toContain("cch=0ca30");
|
|
27
|
+
expect(replaceNativeStyleCch(serialized)).not.toContain(`cch=${CCH_PLACEHOLDER}`);
|
|
28
|
+
});
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
it("leaves strings without the placeholder unchanged", () => {
|
|
31
|
+
const serialized = '{"system":[{"type":"text","text":"no billing block here"}]}';
|
|
32
|
+
expect(replaceNativeStyleCch(serialized)).toBe(serialized);
|
|
33
|
+
});
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
describe("billing header placeholder + transformRequestBody replacement", () => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
const signature = {
|
|
38
|
+
enabled: true,
|
|
39
|
+
claudeCliVersion: "2.1.101",
|
|
40
|
+
promptCompactionMode: "minimal" as const,
|
|
41
|
+
sanitizeSystemPrompt: false,
|
|
42
|
+
};
|
|
43
|
+
const runtime = {
|
|
44
|
+
persistentUserId: "0".repeat(64),
|
|
45
|
+
accountId: "acct-1",
|
|
46
|
+
sessionId: "session-1",
|
|
47
|
+
};
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("transformRequestBody replaces the placeholder after serialization", () => {
|
|
56
|
-
const body = JSON.stringify({
|
|
57
|
-
model: "claude-sonnet-4-5",
|
|
58
|
-
max_tokens: 1024,
|
|
59
|
-
stream: false,
|
|
60
|
-
messages: [{ role: "user", content: "Reply with the single word: OK" }],
|
|
61
|
-
system: "You are a helpful assistant.",
|
|
49
|
+
it("buildAnthropicBillingHeader emits the native placeholder", () => {
|
|
50
|
+
process.env.CLAUDE_CODE_ATTRIBUTION_HEADER = "true";
|
|
51
|
+
const header = buildAnthropicBillingHeader("2.1.101", [{ role: "user", content: "Hello world from a test" }]);
|
|
52
|
+
expect(header).toContain(`cch=${CCH_PLACEHOLDER};`);
|
|
62
53
|
});
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
it("transformRequestBody replaces the placeholder after serialization", () => {
|
|
56
|
+
const body = JSON.stringify({
|
|
57
|
+
model: "claude-sonnet-4-5",
|
|
58
|
+
max_tokens: 1024,
|
|
59
|
+
stream: false,
|
|
60
|
+
messages: [{ role: "user", content: "Reply with the single word: OK" }],
|
|
61
|
+
system: "You are a helpful assistant.",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const transformed = transformRequestBody(body, signature, runtime);
|
|
65
|
+
expect(transformed).toBeDefined();
|
|
66
|
+
expect(transformed).not.toContain(`cch=${CCH_PLACEHOLDER}`);
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
const actualCch = transformed?.match(/cch=([0-9a-f]{5})/)?.[1];
|
|
69
|
+
const placeholderBody = transformed?.replace(/cch=[0-9a-f]{5}/, `cch=${CCH_PLACEHOLDER}`);
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
expect(actualCch).toBeDefined();
|
|
72
|
+
expect(placeholderBody).toBeDefined();
|
|
73
|
+
expect(actualCch).toBe(computeNativeStyleCch(placeholderBody!));
|
|
74
|
+
expect(transformed).toBe(replaceNativeStyleCch(placeholderBody!));
|
|
75
|
+
});
|
|
76
76
|
});
|
|
@@ -17,60 +17,60 @@ const bunFetchSource = readFileSync(join(SRC_ROOT, "bun-fetch.ts"), "utf8");
|
|
|
17
17
|
const bunProxySource = readFileSync(join(SRC_ROOT, "bun-proxy.ts"), "utf8");
|
|
18
18
|
|
|
19
19
|
describe("debug gating in bun-fetch.ts (Task 2/4)", () => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
it("threads OPENCODE_ANTHROPIC_DEBUG env var", () => {
|
|
21
|
+
expect(bunFetchSource).toContain("OPENCODE_ANTHROPIC_DEBUG");
|
|
22
|
+
});
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
it("gates /tmp debug dump behind debug flag if any /tmp writes exist", () => {
|
|
25
|
+
const hasTmpWrite = /writeFileSync\s*\(\s*["']\/tmp\/opencode/.test(bunFetchSource);
|
|
26
|
+
if (hasTmpWrite) {
|
|
27
|
+
expect(bunFetchSource).toMatch(/if\s*\(\s*(debug|resolveDebug)[^)]*\)|(debug|resolveDebug)\s*&&/);
|
|
28
|
+
} else {
|
|
29
|
+
expect(hasTmpWrite).toBe(false);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
it("does not register an uncaughtException handler (Task 12)", () => {
|
|
34
|
+
expect(bunFetchSource).not.toContain("uncaughtException");
|
|
35
|
+
});
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
it("does not register an unhandledRejection handler (Task 12)", () => {
|
|
38
|
+
expect(bunFetchSource).not.toContain("unhandledRejection");
|
|
39
|
+
});
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
describe("debug gating in bun-proxy.ts (Task 3)", () => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
it("references OPENCODE_ANTHROPIC_DEBUG for request logging", () => {
|
|
44
|
+
expect(bunProxySource).toContain("OPENCODE_ANTHROPIC_DEBUG");
|
|
45
|
+
});
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
it("uses AbortSignal-based timeout handling on upstream fetch (Task 11)", () => {
|
|
48
|
+
expect(bunProxySource).toMatch(/AbortSignal\.(timeout|any)/);
|
|
49
|
+
});
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
it("emits BUN_PROXY_PORT IPC ungated (parent must always detect port)", () => {
|
|
52
|
+
expect(bunProxySource).toContain("BUN_PROXY_PORT");
|
|
53
|
+
});
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
describe("silent error swallowing fixes (Task 5)", () => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
it("accounts.ts saveToDisk catch logs the error", () => {
|
|
58
|
+
const source = readFileSync(join(SRC_ROOT, "accounts.ts"), "utf8");
|
|
59
|
+
expect(source).not.toMatch(/saveToDisk\(\)\s*\.catch\(\s*\(\)\s*=>\s*\{\s*\}\s*\)/);
|
|
60
|
+
});
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
it("token-refresh.ts has no '.catch(() => undefined)' patterns", () => {
|
|
63
|
+
const source = readFileSync(join(SRC_ROOT, "token-refresh.ts"), "utf8");
|
|
64
|
+
expect(source).not.toMatch(/\.catch\(\s*\(\)\s*=>\s*undefined\s*\)/);
|
|
65
|
+
});
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
it("request/body.ts JSON parse catch binds the error parameter", () => {
|
|
68
|
+
const source = readFileSync(join(SRC_ROOT, "request", "body.ts"), "utf8");
|
|
69
|
+
expect(source).not.toMatch(/catch\s*\{\s*return\s+body\s*;?\s*\}/);
|
|
70
|
+
});
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
it("request/metadata.ts extractFileIds catch binds the error parameter", () => {
|
|
73
|
+
const source = readFileSync(join(SRC_ROOT, "request", "metadata.ts"), "utf8");
|
|
74
|
+
expect(source).not.toMatch(/catch\s*\{\s*return\s+\[\]\s*;?\s*\}/);
|
|
75
|
+
});
|
|
76
76
|
});
|
|
@@ -12,81 +12,81 @@ import { createRefreshHelpers } from "../refresh-helpers.js";
|
|
|
12
12
|
import { createPluginHelpers } from "../plugin-helpers.js";
|
|
13
13
|
|
|
14
14
|
describe("refresh-helpers module", () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("factory returns an object when called with valid deps", () => {
|
|
20
|
-
const stubClient = {
|
|
21
|
-
tui: { showToast: vi.fn() },
|
|
22
|
-
command: { prompt: vi.fn() },
|
|
23
|
-
session: { prompt: vi.fn() },
|
|
24
|
-
};
|
|
25
|
-
const helpers = createRefreshHelpers({
|
|
26
|
-
client: stubClient as any,
|
|
27
|
-
config: { ...DEFAULT_CONFIG } as any,
|
|
28
|
-
getAccountManager: () => null,
|
|
29
|
-
debugLog: vi.fn(),
|
|
15
|
+
it("exports createRefreshHelpers as a function", () => {
|
|
16
|
+
expect(typeof createRefreshHelpers).toBe("function");
|
|
30
17
|
});
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
it("factory returns an object when called with valid deps", () => {
|
|
20
|
+
const stubClient = {
|
|
21
|
+
tui: { showToast: vi.fn() },
|
|
22
|
+
command: { prompt: vi.fn() },
|
|
23
|
+
session: { prompt: vi.fn() },
|
|
24
|
+
};
|
|
25
|
+
const helpers = createRefreshHelpers({
|
|
26
|
+
client: stubClient as any,
|
|
27
|
+
config: { ...DEFAULT_CONFIG } as any,
|
|
28
|
+
getAccountManager: () => null,
|
|
29
|
+
debugLog: vi.fn(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(helpers).toBeDefined();
|
|
33
|
+
expect(typeof helpers).toBe("object");
|
|
34
|
+
expect(helpers).not.toBeNull();
|
|
35
|
+
});
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
it("factory reads idle_refresh config fields at construction", () => {
|
|
38
|
+
const config = {
|
|
39
|
+
...DEFAULT_CONFIG,
|
|
40
|
+
idle_refresh: { enabled: false, window_minutes: 10, min_interval_minutes: 5 },
|
|
41
|
+
};
|
|
42
|
+
expect(() =>
|
|
43
|
+
createRefreshHelpers({
|
|
44
|
+
client: {} as any,
|
|
45
|
+
config: config as any,
|
|
46
|
+
getAccountManager: () => null,
|
|
47
|
+
debugLog: vi.fn(),
|
|
48
|
+
}),
|
|
49
|
+
).not.toThrow();
|
|
50
|
+
});
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
describe("plugin-helpers module", () => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("factory returns an object when called with valid deps", () => {
|
|
59
|
-
const stubClient = {
|
|
60
|
-
tui: { showToast: vi.fn() },
|
|
61
|
-
command: { prompt: vi.fn() },
|
|
62
|
-
session: { prompt: vi.fn() },
|
|
63
|
-
};
|
|
64
|
-
const helpers = createPluginHelpers({
|
|
65
|
-
client: stubClient as any,
|
|
66
|
-
config: { ...DEFAULT_CONFIG } as any,
|
|
67
|
-
debugLog: vi.fn(),
|
|
68
|
-
getAccountManager: () => null,
|
|
69
|
-
setAccountManager: vi.fn(),
|
|
54
|
+
it("exports createPluginHelpers as a function", () => {
|
|
55
|
+
expect(typeof createPluginHelpers).toBe("function");
|
|
70
56
|
});
|
|
71
57
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
it("factory returns an object when called with valid deps", () => {
|
|
59
|
+
const stubClient = {
|
|
60
|
+
tui: { showToast: vi.fn() },
|
|
61
|
+
command: { prompt: vi.fn() },
|
|
62
|
+
session: { prompt: vi.fn() },
|
|
63
|
+
};
|
|
64
|
+
const helpers = createPluginHelpers({
|
|
65
|
+
client: stubClient as any,
|
|
66
|
+
config: { ...DEFAULT_CONFIG } as any,
|
|
67
|
+
debugLog: vi.fn(),
|
|
68
|
+
getAccountManager: () => null,
|
|
69
|
+
setAccountManager: vi.fn(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(helpers).toBeDefined();
|
|
73
|
+
expect(typeof helpers).toBe("object");
|
|
74
|
+
expect(helpers).not.toBeNull();
|
|
75
|
+
});
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
it("factory accepts quiet toast config without throwing", () => {
|
|
78
|
+
const config = {
|
|
79
|
+
...DEFAULT_CONFIG,
|
|
80
|
+
toasts: { quiet: true, debounce_seconds: 60 },
|
|
81
|
+
};
|
|
82
|
+
expect(() =>
|
|
83
|
+
createPluginHelpers({
|
|
84
|
+
client: {} as any,
|
|
85
|
+
config: config as any,
|
|
86
|
+
debugLog: vi.fn(),
|
|
87
|
+
getAccountManager: () => null,
|
|
88
|
+
setAccountManager: vi.fn(),
|
|
89
|
+
}),
|
|
90
|
+
).not.toThrow();
|
|
91
|
+
});
|
|
92
92
|
});
|