@mandujs/core 0.53.3 → 0.54.0
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 +654 -654
- package/package.json +2 -2
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +120 -120
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/bundler/__tests__/build-runner.ts +113 -113
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +155 -155
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +182 -182
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +450 -450
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -451
- package/src/diagnose/checks.ts +832 -832
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +93 -93
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +60 -60
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/index.ts +3 -3
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/watcher.ts +18 -18
|
@@ -1,209 +1,209 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for `packages/core/src/brain/adapters/openai-oauth.ts`.
|
|
3
|
-
*
|
|
4
|
-
* The adapter is fully isolatable via its options surface:
|
|
5
|
-
* - `credentialStore` — in-memory fixture (no keychain calls).
|
|
6
|
-
* - `httpClient` — stubbed fetch (no network).
|
|
7
|
-
* - `endpoints` — fake OAuth endpoints.
|
|
8
|
-
* - `skipConsent` — bypass the interactive consent prompt.
|
|
9
|
-
* - `projectRoot` — per-test tmpdir so redaction audit log is
|
|
10
|
-
* sandboxed.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { describe, it, expect, beforeEach } from "bun:test";
|
|
14
|
-
import { promises as fs } from "node:fs";
|
|
15
|
-
import os from "node:os";
|
|
16
|
-
import path from "node:path";
|
|
17
|
-
import {
|
|
18
|
-
OpenAIOAuthAdapter,
|
|
19
|
-
OPENAI_DEFAULT_MODEL,
|
|
20
|
-
} from "../openai-oauth";
|
|
21
|
-
import {
|
|
22
|
-
makeMemoryStore,
|
|
23
|
-
makeStubHttpClient,
|
|
24
|
-
FAKE_ENDPOINTS,
|
|
25
|
-
jsonResponse,
|
|
26
|
-
makeEmptyChatGPTAuth,
|
|
27
|
-
} from "./_helpers";
|
|
28
|
-
import type { StoredToken } from "../../credentials";
|
|
29
|
-
|
|
30
|
-
let tmp: string;
|
|
31
|
-
|
|
32
|
-
beforeEach(async () => {
|
|
33
|
-
tmp = await fs.mkdtemp(path.join(os.tmpdir(), "mandu-openai-adapter-"));
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
function seedToken(): StoredToken {
|
|
37
|
-
return {
|
|
38
|
-
access_token: "seed-access",
|
|
39
|
-
refresh_token: "seed-refresh",
|
|
40
|
-
expires_at: Math.floor(Date.now() / 1000) + 3600,
|
|
41
|
-
default_model: OPENAI_DEFAULT_MODEL,
|
|
42
|
-
provider: "openai",
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
describe("OpenAIOAuthAdapter — shape + defaults", () => {
|
|
47
|
-
it("reports default model gpt-5.4 and adapter name openai-oauth", async () => {
|
|
48
|
-
const store = makeMemoryStore({ openai: seedToken() });
|
|
49
|
-
const adapter = new OpenAIOAuthAdapter({
|
|
50
|
-
credentialStore: store,
|
|
51
|
-
projectRoot: tmp,
|
|
52
|
-
endpoints: FAKE_ENDPOINTS,
|
|
53
|
-
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
54
|
-
httpClient: makeStubHttpClient(() => new Response("ok")),
|
|
55
|
-
skipConsent: true,
|
|
56
|
-
});
|
|
57
|
-
expect(adapter.name).toBe("openai-oauth");
|
|
58
|
-
expect(adapter.model).toBe(OPENAI_DEFAULT_MODEL);
|
|
59
|
-
const status = await adapter.checkStatus();
|
|
60
|
-
expect(status.available).toBe(true);
|
|
61
|
-
expect(status.model).toBe(OPENAI_DEFAULT_MODEL);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe("OpenAIOAuthAdapter — redaction invariant", () => {
|
|
66
|
-
it("never transmits detected secrets in the chat body and writes an audit entry", async () => {
|
|
67
|
-
const store = makeMemoryStore({ openai: seedToken() });
|
|
68
|
-
let captured = "";
|
|
69
|
-
const http = makeStubHttpClient((_url, init) => {
|
|
70
|
-
captured = (init?.body as string) ?? "";
|
|
71
|
-
return jsonResponse({
|
|
72
|
-
choices: [{ message: { content: "ok" } }],
|
|
73
|
-
usage: { prompt_tokens: 5, completion_tokens: 3, total_tokens: 8 },
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const adapter = new OpenAIOAuthAdapter({
|
|
78
|
-
credentialStore: store,
|
|
79
|
-
projectRoot: tmp,
|
|
80
|
-
endpoints: FAKE_ENDPOINTS,
|
|
81
|
-
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
82
|
-
httpClient: http,
|
|
83
|
-
skipConsent: true,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const secret = "sk-proj-SECRET1234567890ABCDEFGH";
|
|
87
|
-
const res = await adapter.complete([
|
|
88
|
-
{ role: "user", content: `Analyze this: key=${secret}` },
|
|
89
|
-
]);
|
|
90
|
-
|
|
91
|
-
expect(res.content).toBe("ok");
|
|
92
|
-
expect(captured).not.toContain(secret);
|
|
93
|
-
expect(captured).toContain("[[REDACTED:");
|
|
94
|
-
|
|
95
|
-
const auditPath = path.join(tmp, ".mandu", "brain-redactions.jsonl");
|
|
96
|
-
const auditContents = await fs.readFile(auditPath, "utf8");
|
|
97
|
-
expect(auditContents).toContain("openai");
|
|
98
|
-
expect(auditContents).not.toContain(secret);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe("OpenAIOAuthAdapter — 401 fallback chain", () => {
|
|
103
|
-
it("attempts silent refresh once, then returns empty and scrubs the token on persistent 401", async () => {
|
|
104
|
-
const store = makeMemoryStore({ openai: seedToken() });
|
|
105
|
-
let calls = 0;
|
|
106
|
-
const http = makeStubHttpClient((url) => {
|
|
107
|
-
calls += 1;
|
|
108
|
-
if (url === FAKE_ENDPOINTS.tokenUrl) {
|
|
109
|
-
// Refresh endpoint — hand back a new access_token.
|
|
110
|
-
return jsonResponse({
|
|
111
|
-
access_token: "refreshed-access",
|
|
112
|
-
refresh_token: "new-refresh",
|
|
113
|
-
expires_in: 3600,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
// Chat endpoint — always 401.
|
|
117
|
-
return new Response("unauthorized", { status: 401 });
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const adapter = new OpenAIOAuthAdapter({
|
|
121
|
-
credentialStore: store,
|
|
122
|
-
projectRoot: tmp,
|
|
123
|
-
endpoints: FAKE_ENDPOINTS,
|
|
124
|
-
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
125
|
-
httpClient: http,
|
|
126
|
-
skipConsent: true,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const res = await adapter.complete([{ role: "user", content: "ping" }]);
|
|
130
|
-
expect(res.content).toBe("");
|
|
131
|
-
// Expect 3 calls: chat(401) → refresh(200) → chat(401) → scrub.
|
|
132
|
-
expect(calls).toBe(3);
|
|
133
|
-
expect(await store.load("openai")).toBeNull();
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe("OpenAIOAuthAdapter — no-token → empty completion (not strict)", () => {
|
|
138
|
-
it("returns empty completion when no token is stored and strict=false", async () => {
|
|
139
|
-
const store = makeMemoryStore();
|
|
140
|
-
const adapter = new OpenAIOAuthAdapter({
|
|
141
|
-
credentialStore: store,
|
|
142
|
-
projectRoot: tmp,
|
|
143
|
-
endpoints: FAKE_ENDPOINTS,
|
|
144
|
-
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
145
|
-
httpClient: makeStubHttpClient(() =>
|
|
146
|
-
jsonResponse({
|
|
147
|
-
choices: [{ message: { content: "should-not-run" } }],
|
|
148
|
-
}),
|
|
149
|
-
),
|
|
150
|
-
skipConsent: true,
|
|
151
|
-
});
|
|
152
|
-
const res = await adapter.complete([{ role: "user", content: "hi" }]);
|
|
153
|
-
expect(res.content).toBe("");
|
|
154
|
-
expect(res.usage?.totalTokens).toBe(0);
|
|
155
|
-
const status = await adapter.checkStatus();
|
|
156
|
-
expect(status.available).toBe(false);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe("OpenAIOAuthAdapter — consent decline short-circuits transmission", () => {
|
|
161
|
-
it("returns empty completion and never dispatches the chat call when consent is declined", async () => {
|
|
162
|
-
const store = makeMemoryStore({ openai: seedToken() });
|
|
163
|
-
let dispatched = false;
|
|
164
|
-
const http = makeStubHttpClient(() => {
|
|
165
|
-
dispatched = true;
|
|
166
|
-
return jsonResponse({ choices: [{ message: { content: "x" } }] });
|
|
167
|
-
});
|
|
168
|
-
const adapter = new OpenAIOAuthAdapter({
|
|
169
|
-
credentialStore: store,
|
|
170
|
-
projectRoot: tmp,
|
|
171
|
-
endpoints: FAKE_ENDPOINTS,
|
|
172
|
-
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
173
|
-
httpClient: http,
|
|
174
|
-
consentDeps: {
|
|
175
|
-
ask: async () => "n",
|
|
176
|
-
write: () => {},
|
|
177
|
-
env: {} as NodeJS.ProcessEnv,
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
const res = await adapter.complete([{ role: "user", content: "hi" }]);
|
|
181
|
-
expect(res.content).toBe("");
|
|
182
|
-
expect(dispatched).toBe(false);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
describe("OpenAIOAuthAdapter — model override flows through to the wire body", () => {
|
|
187
|
-
it("uses the configured model in the request payload", async () => {
|
|
188
|
-
const store = makeMemoryStore({ openai: seedToken() });
|
|
189
|
-
let capturedBody = "";
|
|
190
|
-
const http = makeStubHttpClient((_url, init) => {
|
|
191
|
-
capturedBody = (init?.body as string) ?? "";
|
|
192
|
-
return jsonResponse({
|
|
193
|
-
choices: [{ message: { content: "ok" } }],
|
|
194
|
-
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
const adapter = new OpenAIOAuthAdapter({
|
|
198
|
-
credentialStore: store,
|
|
199
|
-
projectRoot: tmp,
|
|
200
|
-
endpoints: FAKE_ENDPOINTS,
|
|
201
|
-
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
202
|
-
httpClient: http,
|
|
203
|
-
skipConsent: true,
|
|
204
|
-
model: "gpt-4o",
|
|
205
|
-
});
|
|
206
|
-
await adapter.complete([{ role: "user", content: "hello" }]);
|
|
207
|
-
expect(capturedBody).toContain('"model":"gpt-4o"');
|
|
208
|
-
});
|
|
209
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `packages/core/src/brain/adapters/openai-oauth.ts`.
|
|
3
|
+
*
|
|
4
|
+
* The adapter is fully isolatable via its options surface:
|
|
5
|
+
* - `credentialStore` — in-memory fixture (no keychain calls).
|
|
6
|
+
* - `httpClient` — stubbed fetch (no network).
|
|
7
|
+
* - `endpoints` — fake OAuth endpoints.
|
|
8
|
+
* - `skipConsent` — bypass the interactive consent prompt.
|
|
9
|
+
* - `projectRoot` — per-test tmpdir so redaction audit log is
|
|
10
|
+
* sandboxed.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, beforeEach } from "bun:test";
|
|
14
|
+
import { promises as fs } from "node:fs";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import {
|
|
18
|
+
OpenAIOAuthAdapter,
|
|
19
|
+
OPENAI_DEFAULT_MODEL,
|
|
20
|
+
} from "../openai-oauth";
|
|
21
|
+
import {
|
|
22
|
+
makeMemoryStore,
|
|
23
|
+
makeStubHttpClient,
|
|
24
|
+
FAKE_ENDPOINTS,
|
|
25
|
+
jsonResponse,
|
|
26
|
+
makeEmptyChatGPTAuth,
|
|
27
|
+
} from "./_helpers";
|
|
28
|
+
import type { StoredToken } from "../../credentials";
|
|
29
|
+
|
|
30
|
+
let tmp: string;
|
|
31
|
+
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
tmp = await fs.mkdtemp(path.join(os.tmpdir(), "mandu-openai-adapter-"));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function seedToken(): StoredToken {
|
|
37
|
+
return {
|
|
38
|
+
access_token: "seed-access",
|
|
39
|
+
refresh_token: "seed-refresh",
|
|
40
|
+
expires_at: Math.floor(Date.now() / 1000) + 3600,
|
|
41
|
+
default_model: OPENAI_DEFAULT_MODEL,
|
|
42
|
+
provider: "openai",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("OpenAIOAuthAdapter — shape + defaults", () => {
|
|
47
|
+
it("reports default model gpt-5.4 and adapter name openai-oauth", async () => {
|
|
48
|
+
const store = makeMemoryStore({ openai: seedToken() });
|
|
49
|
+
const adapter = new OpenAIOAuthAdapter({
|
|
50
|
+
credentialStore: store,
|
|
51
|
+
projectRoot: tmp,
|
|
52
|
+
endpoints: FAKE_ENDPOINTS,
|
|
53
|
+
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
54
|
+
httpClient: makeStubHttpClient(() => new Response("ok")),
|
|
55
|
+
skipConsent: true,
|
|
56
|
+
});
|
|
57
|
+
expect(adapter.name).toBe("openai-oauth");
|
|
58
|
+
expect(adapter.model).toBe(OPENAI_DEFAULT_MODEL);
|
|
59
|
+
const status = await adapter.checkStatus();
|
|
60
|
+
expect(status.available).toBe(true);
|
|
61
|
+
expect(status.model).toBe(OPENAI_DEFAULT_MODEL);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("OpenAIOAuthAdapter — redaction invariant", () => {
|
|
66
|
+
it("never transmits detected secrets in the chat body and writes an audit entry", async () => {
|
|
67
|
+
const store = makeMemoryStore({ openai: seedToken() });
|
|
68
|
+
let captured = "";
|
|
69
|
+
const http = makeStubHttpClient((_url, init) => {
|
|
70
|
+
captured = (init?.body as string) ?? "";
|
|
71
|
+
return jsonResponse({
|
|
72
|
+
choices: [{ message: { content: "ok" } }],
|
|
73
|
+
usage: { prompt_tokens: 5, completion_tokens: 3, total_tokens: 8 },
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const adapter = new OpenAIOAuthAdapter({
|
|
78
|
+
credentialStore: store,
|
|
79
|
+
projectRoot: tmp,
|
|
80
|
+
endpoints: FAKE_ENDPOINTS,
|
|
81
|
+
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
82
|
+
httpClient: http,
|
|
83
|
+
skipConsent: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const secret = "sk-proj-SECRET1234567890ABCDEFGH";
|
|
87
|
+
const res = await adapter.complete([
|
|
88
|
+
{ role: "user", content: `Analyze this: key=${secret}` },
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
expect(res.content).toBe("ok");
|
|
92
|
+
expect(captured).not.toContain(secret);
|
|
93
|
+
expect(captured).toContain("[[REDACTED:");
|
|
94
|
+
|
|
95
|
+
const auditPath = path.join(tmp, ".mandu", "brain-redactions.jsonl");
|
|
96
|
+
const auditContents = await fs.readFile(auditPath, "utf8");
|
|
97
|
+
expect(auditContents).toContain("openai");
|
|
98
|
+
expect(auditContents).not.toContain(secret);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("OpenAIOAuthAdapter — 401 fallback chain", () => {
|
|
103
|
+
it("attempts silent refresh once, then returns empty and scrubs the token on persistent 401", async () => {
|
|
104
|
+
const store = makeMemoryStore({ openai: seedToken() });
|
|
105
|
+
let calls = 0;
|
|
106
|
+
const http = makeStubHttpClient((url) => {
|
|
107
|
+
calls += 1;
|
|
108
|
+
if (url === FAKE_ENDPOINTS.tokenUrl) {
|
|
109
|
+
// Refresh endpoint — hand back a new access_token.
|
|
110
|
+
return jsonResponse({
|
|
111
|
+
access_token: "refreshed-access",
|
|
112
|
+
refresh_token: "new-refresh",
|
|
113
|
+
expires_in: 3600,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Chat endpoint — always 401.
|
|
117
|
+
return new Response("unauthorized", { status: 401 });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const adapter = new OpenAIOAuthAdapter({
|
|
121
|
+
credentialStore: store,
|
|
122
|
+
projectRoot: tmp,
|
|
123
|
+
endpoints: FAKE_ENDPOINTS,
|
|
124
|
+
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
125
|
+
httpClient: http,
|
|
126
|
+
skipConsent: true,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const res = await adapter.complete([{ role: "user", content: "ping" }]);
|
|
130
|
+
expect(res.content).toBe("");
|
|
131
|
+
// Expect 3 calls: chat(401) → refresh(200) → chat(401) → scrub.
|
|
132
|
+
expect(calls).toBe(3);
|
|
133
|
+
expect(await store.load("openai")).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("OpenAIOAuthAdapter — no-token → empty completion (not strict)", () => {
|
|
138
|
+
it("returns empty completion when no token is stored and strict=false", async () => {
|
|
139
|
+
const store = makeMemoryStore();
|
|
140
|
+
const adapter = new OpenAIOAuthAdapter({
|
|
141
|
+
credentialStore: store,
|
|
142
|
+
projectRoot: tmp,
|
|
143
|
+
endpoints: FAKE_ENDPOINTS,
|
|
144
|
+
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
145
|
+
httpClient: makeStubHttpClient(() =>
|
|
146
|
+
jsonResponse({
|
|
147
|
+
choices: [{ message: { content: "should-not-run" } }],
|
|
148
|
+
}),
|
|
149
|
+
),
|
|
150
|
+
skipConsent: true,
|
|
151
|
+
});
|
|
152
|
+
const res = await adapter.complete([{ role: "user", content: "hi" }]);
|
|
153
|
+
expect(res.content).toBe("");
|
|
154
|
+
expect(res.usage?.totalTokens).toBe(0);
|
|
155
|
+
const status = await adapter.checkStatus();
|
|
156
|
+
expect(status.available).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("OpenAIOAuthAdapter — consent decline short-circuits transmission", () => {
|
|
161
|
+
it("returns empty completion and never dispatches the chat call when consent is declined", async () => {
|
|
162
|
+
const store = makeMemoryStore({ openai: seedToken() });
|
|
163
|
+
let dispatched = false;
|
|
164
|
+
const http = makeStubHttpClient(() => {
|
|
165
|
+
dispatched = true;
|
|
166
|
+
return jsonResponse({ choices: [{ message: { content: "x" } }] });
|
|
167
|
+
});
|
|
168
|
+
const adapter = new OpenAIOAuthAdapter({
|
|
169
|
+
credentialStore: store,
|
|
170
|
+
projectRoot: tmp,
|
|
171
|
+
endpoints: FAKE_ENDPOINTS,
|
|
172
|
+
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
173
|
+
httpClient: http,
|
|
174
|
+
consentDeps: {
|
|
175
|
+
ask: async () => "n",
|
|
176
|
+
write: () => {},
|
|
177
|
+
env: {} as NodeJS.ProcessEnv,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
const res = await adapter.complete([{ role: "user", content: "hi" }]);
|
|
181
|
+
expect(res.content).toBe("");
|
|
182
|
+
expect(dispatched).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("OpenAIOAuthAdapter — model override flows through to the wire body", () => {
|
|
187
|
+
it("uses the configured model in the request payload", async () => {
|
|
188
|
+
const store = makeMemoryStore({ openai: seedToken() });
|
|
189
|
+
let capturedBody = "";
|
|
190
|
+
const http = makeStubHttpClient((_url, init) => {
|
|
191
|
+
capturedBody = (init?.body as string) ?? "";
|
|
192
|
+
return jsonResponse({
|
|
193
|
+
choices: [{ message: { content: "ok" } }],
|
|
194
|
+
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
const adapter = new OpenAIOAuthAdapter({
|
|
198
|
+
credentialStore: store,
|
|
199
|
+
projectRoot: tmp,
|
|
200
|
+
endpoints: FAKE_ENDPOINTS,
|
|
201
|
+
chatgptAuth: makeEmptyChatGPTAuth(),
|
|
202
|
+
httpClient: http,
|
|
203
|
+
skipConsent: true,
|
|
204
|
+
model: "gpt-4o",
|
|
205
|
+
});
|
|
206
|
+
await adapter.complete([{ role: "user", content: "hello" }]);
|
|
207
|
+
expect(capturedBody).toContain('"model":"gpt-4o"');
|
|
208
|
+
});
|
|
209
|
+
});
|