@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,193 +1,193 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the ChatGPT session-token auth helper
|
|
3
|
-
* (`packages/core/src/brain/adapters/chatgpt-auth.ts`).
|
|
4
|
-
*
|
|
5
|
-
* ChatGPTAuth reads whatever `@openai/codex login` wrote to
|
|
6
|
-
* `~/.codex/auth.json` (or the `CHATGPT_LOCAL_HOME` / `CODEX_HOME`
|
|
7
|
-
* override), auto-refreshes when the access token is near expiry, and
|
|
8
|
-
* exposes `{ accessToken, accountId, idToken, refreshToken }` to
|
|
9
|
-
* callers. Tests here exercise the pure-file behaviour — the refresh
|
|
10
|
-
* path uses an injected `httpClient` so no network hits the real
|
|
11
|
-
* `auth.openai.com`.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
15
|
-
import { promises as fs } from "node:fs";
|
|
16
|
-
import { writeFileSync } from "node:fs";
|
|
17
|
-
import os from "node:os";
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
import { ChatGPTAuth } from "../chatgpt-auth";
|
|
20
|
-
|
|
21
|
-
let tmp: string;
|
|
22
|
-
let authPath: string;
|
|
23
|
-
|
|
24
|
-
beforeEach(async () => {
|
|
25
|
-
tmp = await fs.mkdtemp(path.join(os.tmpdir(), "mandu-chatgpt-auth-"));
|
|
26
|
-
authPath = path.join(tmp, "auth.json");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
afterEach(async () => {
|
|
30
|
-
await fs.rm(tmp, { recursive: true, force: true });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
/** Minimal JWT whose `exp` claim is far in the future (no refresh). */
|
|
34
|
-
function makeFarFutureJwt(): string {
|
|
35
|
-
const header = Buffer.from('{"alg":"none"}').toString("base64url");
|
|
36
|
-
const payload = Buffer.from(
|
|
37
|
-
JSON.stringify({
|
|
38
|
-
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24, // 24h from now
|
|
39
|
-
"https://api.openai.com/auth": {
|
|
40
|
-
chatgpt_account_id: "acct_test",
|
|
41
|
-
},
|
|
42
|
-
}),
|
|
43
|
-
).toString("base64url");
|
|
44
|
-
return `${header}.${payload}.`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function makeExpiredJwt(): string {
|
|
48
|
-
const header = Buffer.from('{"alg":"none"}').toString("base64url");
|
|
49
|
-
const payload = Buffer.from(
|
|
50
|
-
JSON.stringify({
|
|
51
|
-
exp: Math.floor(Date.now() / 1000) - 60, // 1 min ago
|
|
52
|
-
"https://api.openai.com/auth": {
|
|
53
|
-
chatgpt_account_id: "acct_test",
|
|
54
|
-
},
|
|
55
|
-
}),
|
|
56
|
-
).toString("base64url");
|
|
57
|
-
return `${header}.${payload}.`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function seedAuthFile(contents: unknown): void {
|
|
61
|
-
writeFileSync(authPath, JSON.stringify(contents), { encoding: "utf-8" });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
describe("ChatGPTAuth — discovery + isAuthenticated", () => {
|
|
65
|
-
it("reports false when auth.json does not exist", () => {
|
|
66
|
-
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
67
|
-
expect(auth.isAuthenticated()).toBe(false);
|
|
68
|
-
expect(auth.locateAuthFile()).toBe(null);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("reports true when auth.json has an access_token", () => {
|
|
72
|
-
seedAuthFile({ tokens: { access_token: "x" } });
|
|
73
|
-
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
74
|
-
expect(auth.isAuthenticated()).toBe(true);
|
|
75
|
-
expect(auth.locateAuthFile()).toBe(authPath);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe("ChatGPTAuth — getAuth with a valid (non-expiring) token", () => {
|
|
80
|
-
it("returns accessToken + accountId without calling refresh", async () => {
|
|
81
|
-
const jwt = makeFarFutureJwt();
|
|
82
|
-
seedAuthFile({
|
|
83
|
-
tokens: {
|
|
84
|
-
access_token: "live-access",
|
|
85
|
-
id_token: jwt,
|
|
86
|
-
refresh_token: "live-refresh",
|
|
87
|
-
},
|
|
88
|
-
last_refresh: new Date().toISOString(),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
let refreshCalls = 0;
|
|
92
|
-
const auth = new ChatGPTAuth({
|
|
93
|
-
authFilePath: authPath,
|
|
94
|
-
httpClient: async () => {
|
|
95
|
-
refreshCalls++;
|
|
96
|
-
return new Response("{}", { status: 200 });
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const effective = await auth.getAuth();
|
|
101
|
-
expect(effective.accessToken).toBe("live-access");
|
|
102
|
-
expect(effective.accountId).toBe("acct_test");
|
|
103
|
-
expect(effective.sourcePath).toBe(authPath);
|
|
104
|
-
expect(refreshCalls).toBe(0);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("throws a helpful message when auth.json is missing", async () => {
|
|
108
|
-
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
109
|
-
await expect(auth.getAuth()).rejects.toThrow(/auth\.json not found/);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
describe("ChatGPTAuth — automatic refresh on expired access_token", () => {
|
|
114
|
-
it("calls the token endpoint once and persists the new tokens", async () => {
|
|
115
|
-
const expiredJwt = makeExpiredJwt();
|
|
116
|
-
seedAuthFile({
|
|
117
|
-
tokens: {
|
|
118
|
-
access_token: expiredJwt, // exp < now → triggers refresh
|
|
119
|
-
id_token: expiredJwt,
|
|
120
|
-
refresh_token: "r-fresh",
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
let tokenCalls = 0;
|
|
125
|
-
const newJwt = makeFarFutureJwt();
|
|
126
|
-
const auth = new ChatGPTAuth({
|
|
127
|
-
authFilePath: authPath,
|
|
128
|
-
httpClient: async (url, init) => {
|
|
129
|
-
tokenCalls++;
|
|
130
|
-
expect(url).toBe("https://auth.openai.com/oauth/token");
|
|
131
|
-
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
132
|
-
expect(body.grant_type).toBe("refresh_token");
|
|
133
|
-
expect(body.refresh_token).toBe("r-fresh");
|
|
134
|
-
return new Response(
|
|
135
|
-
JSON.stringify({
|
|
136
|
-
access_token: "brand-new-access",
|
|
137
|
-
refresh_token: "brand-new-refresh",
|
|
138
|
-
id_token: newJwt,
|
|
139
|
-
}),
|
|
140
|
-
{ status: 200, headers: { "content-type": "application/json" } },
|
|
141
|
-
);
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const effective = await auth.getAuth();
|
|
146
|
-
expect(effective.accessToken).toBe("brand-new-access");
|
|
147
|
-
expect(effective.refreshToken).toBe("brand-new-refresh");
|
|
148
|
-
expect(effective.accountId).toBe("acct_test");
|
|
149
|
-
expect(tokenCalls).toBe(1);
|
|
150
|
-
|
|
151
|
-
// auth.json on disk should have been rewritten with the new tokens.
|
|
152
|
-
const onDisk = JSON.parse(await fs.readFile(authPath, "utf-8"));
|
|
153
|
-
expect(onDisk.tokens.access_token).toBe("brand-new-access");
|
|
154
|
-
expect(onDisk.tokens.refresh_token).toBe("brand-new-refresh");
|
|
155
|
-
expect(typeof onDisk.last_refresh).toBe("string");
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("surfaces token endpoint errors verbatim", async () => {
|
|
159
|
-
seedAuthFile({
|
|
160
|
-
tokens: {
|
|
161
|
-
access_token: makeExpiredJwt(),
|
|
162
|
-
refresh_token: "r-bad",
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const auth = new ChatGPTAuth({
|
|
167
|
-
authFilePath: authPath,
|
|
168
|
-
httpClient: async () =>
|
|
169
|
-
new Response("bad token", { status: 401 }),
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
await expect(auth.getAuth()).rejects.toThrow(/Token refresh 401/);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe("ChatGPTAuth — malformed auth.json", () => {
|
|
177
|
-
it("rejects when access_token is missing", async () => {
|
|
178
|
-
seedAuthFile({ tokens: {} });
|
|
179
|
-
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
180
|
-
await expect(auth.getAuth()).rejects.toThrow(/no access_token/);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("rejects when refresh is required but refresh_token is absent", async () => {
|
|
184
|
-
seedAuthFile({
|
|
185
|
-
tokens: {
|
|
186
|
-
access_token: makeExpiredJwt(),
|
|
187
|
-
// refresh_token intentionally omitted
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
191
|
-
await expect(auth.getAuth()).rejects.toThrow(/no refresh_token/);
|
|
192
|
-
});
|
|
193
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the ChatGPT session-token auth helper
|
|
3
|
+
* (`packages/core/src/brain/adapters/chatgpt-auth.ts`).
|
|
4
|
+
*
|
|
5
|
+
* ChatGPTAuth reads whatever `@openai/codex login` wrote to
|
|
6
|
+
* `~/.codex/auth.json` (or the `CHATGPT_LOCAL_HOME` / `CODEX_HOME`
|
|
7
|
+
* override), auto-refreshes when the access token is near expiry, and
|
|
8
|
+
* exposes `{ accessToken, accountId, idToken, refreshToken }` to
|
|
9
|
+
* callers. Tests here exercise the pure-file behaviour — the refresh
|
|
10
|
+
* path uses an injected `httpClient` so no network hits the real
|
|
11
|
+
* `auth.openai.com`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
15
|
+
import { promises as fs } from "node:fs";
|
|
16
|
+
import { writeFileSync } from "node:fs";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { ChatGPTAuth } from "../chatgpt-auth";
|
|
20
|
+
|
|
21
|
+
let tmp: string;
|
|
22
|
+
let authPath: string;
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
tmp = await fs.mkdtemp(path.join(os.tmpdir(), "mandu-chatgpt-auth-"));
|
|
26
|
+
authPath = path.join(tmp, "auth.json");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
await fs.rm(tmp, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/** Minimal JWT whose `exp` claim is far in the future (no refresh). */
|
|
34
|
+
function makeFarFutureJwt(): string {
|
|
35
|
+
const header = Buffer.from('{"alg":"none"}').toString("base64url");
|
|
36
|
+
const payload = Buffer.from(
|
|
37
|
+
JSON.stringify({
|
|
38
|
+
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24, // 24h from now
|
|
39
|
+
"https://api.openai.com/auth": {
|
|
40
|
+
chatgpt_account_id: "acct_test",
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
).toString("base64url");
|
|
44
|
+
return `${header}.${payload}.`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function makeExpiredJwt(): string {
|
|
48
|
+
const header = Buffer.from('{"alg":"none"}').toString("base64url");
|
|
49
|
+
const payload = Buffer.from(
|
|
50
|
+
JSON.stringify({
|
|
51
|
+
exp: Math.floor(Date.now() / 1000) - 60, // 1 min ago
|
|
52
|
+
"https://api.openai.com/auth": {
|
|
53
|
+
chatgpt_account_id: "acct_test",
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
).toString("base64url");
|
|
57
|
+
return `${header}.${payload}.`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function seedAuthFile(contents: unknown): void {
|
|
61
|
+
writeFileSync(authPath, JSON.stringify(contents), { encoding: "utf-8" });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe("ChatGPTAuth — discovery + isAuthenticated", () => {
|
|
65
|
+
it("reports false when auth.json does not exist", () => {
|
|
66
|
+
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
67
|
+
expect(auth.isAuthenticated()).toBe(false);
|
|
68
|
+
expect(auth.locateAuthFile()).toBe(null);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("reports true when auth.json has an access_token", () => {
|
|
72
|
+
seedAuthFile({ tokens: { access_token: "x" } });
|
|
73
|
+
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
74
|
+
expect(auth.isAuthenticated()).toBe(true);
|
|
75
|
+
expect(auth.locateAuthFile()).toBe(authPath);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("ChatGPTAuth — getAuth with a valid (non-expiring) token", () => {
|
|
80
|
+
it("returns accessToken + accountId without calling refresh", async () => {
|
|
81
|
+
const jwt = makeFarFutureJwt();
|
|
82
|
+
seedAuthFile({
|
|
83
|
+
tokens: {
|
|
84
|
+
access_token: "live-access",
|
|
85
|
+
id_token: jwt,
|
|
86
|
+
refresh_token: "live-refresh",
|
|
87
|
+
},
|
|
88
|
+
last_refresh: new Date().toISOString(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
let refreshCalls = 0;
|
|
92
|
+
const auth = new ChatGPTAuth({
|
|
93
|
+
authFilePath: authPath,
|
|
94
|
+
httpClient: async () => {
|
|
95
|
+
refreshCalls++;
|
|
96
|
+
return new Response("{}", { status: 200 });
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const effective = await auth.getAuth();
|
|
101
|
+
expect(effective.accessToken).toBe("live-access");
|
|
102
|
+
expect(effective.accountId).toBe("acct_test");
|
|
103
|
+
expect(effective.sourcePath).toBe(authPath);
|
|
104
|
+
expect(refreshCalls).toBe(0);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("throws a helpful message when auth.json is missing", async () => {
|
|
108
|
+
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
109
|
+
await expect(auth.getAuth()).rejects.toThrow(/auth\.json not found/);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("ChatGPTAuth — automatic refresh on expired access_token", () => {
|
|
114
|
+
it("calls the token endpoint once and persists the new tokens", async () => {
|
|
115
|
+
const expiredJwt = makeExpiredJwt();
|
|
116
|
+
seedAuthFile({
|
|
117
|
+
tokens: {
|
|
118
|
+
access_token: expiredJwt, // exp < now → triggers refresh
|
|
119
|
+
id_token: expiredJwt,
|
|
120
|
+
refresh_token: "r-fresh",
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
let tokenCalls = 0;
|
|
125
|
+
const newJwt = makeFarFutureJwt();
|
|
126
|
+
const auth = new ChatGPTAuth({
|
|
127
|
+
authFilePath: authPath,
|
|
128
|
+
httpClient: async (url, init) => {
|
|
129
|
+
tokenCalls++;
|
|
130
|
+
expect(url).toBe("https://auth.openai.com/oauth/token");
|
|
131
|
+
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
132
|
+
expect(body.grant_type).toBe("refresh_token");
|
|
133
|
+
expect(body.refresh_token).toBe("r-fresh");
|
|
134
|
+
return new Response(
|
|
135
|
+
JSON.stringify({
|
|
136
|
+
access_token: "brand-new-access",
|
|
137
|
+
refresh_token: "brand-new-refresh",
|
|
138
|
+
id_token: newJwt,
|
|
139
|
+
}),
|
|
140
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const effective = await auth.getAuth();
|
|
146
|
+
expect(effective.accessToken).toBe("brand-new-access");
|
|
147
|
+
expect(effective.refreshToken).toBe("brand-new-refresh");
|
|
148
|
+
expect(effective.accountId).toBe("acct_test");
|
|
149
|
+
expect(tokenCalls).toBe(1);
|
|
150
|
+
|
|
151
|
+
// auth.json on disk should have been rewritten with the new tokens.
|
|
152
|
+
const onDisk = JSON.parse(await fs.readFile(authPath, "utf-8"));
|
|
153
|
+
expect(onDisk.tokens.access_token).toBe("brand-new-access");
|
|
154
|
+
expect(onDisk.tokens.refresh_token).toBe("brand-new-refresh");
|
|
155
|
+
expect(typeof onDisk.last_refresh).toBe("string");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("surfaces token endpoint errors verbatim", async () => {
|
|
159
|
+
seedAuthFile({
|
|
160
|
+
tokens: {
|
|
161
|
+
access_token: makeExpiredJwt(),
|
|
162
|
+
refresh_token: "r-bad",
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const auth = new ChatGPTAuth({
|
|
167
|
+
authFilePath: authPath,
|
|
168
|
+
httpClient: async () =>
|
|
169
|
+
new Response("bad token", { status: 401 }),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await expect(auth.getAuth()).rejects.toThrow(/Token refresh 401/);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("ChatGPTAuth — malformed auth.json", () => {
|
|
177
|
+
it("rejects when access_token is missing", async () => {
|
|
178
|
+
seedAuthFile({ tokens: {} });
|
|
179
|
+
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
180
|
+
await expect(auth.getAuth()).rejects.toThrow(/no access_token/);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("rejects when refresh is required but refresh_token is absent", async () => {
|
|
184
|
+
seedAuthFile({
|
|
185
|
+
tokens: {
|
|
186
|
+
access_token: makeExpiredJwt(),
|
|
187
|
+
// refresh_token intentionally omitted
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
const auth = new ChatGPTAuth({ authFilePath: authPath });
|
|
191
|
+
await expect(auth.getAuth()).rejects.toThrow(/no refresh_token/);
|
|
192
|
+
});
|
|
193
|
+
});
|