aws-runtime-bridge 1.7.8 → 1.7.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/dist/config.d.ts +6 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -0
- package/dist/config.test.js +12 -0
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +41 -6
- package/dist/routes/instance.test.js +70 -0
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export declare const port: number;
|
|
8
8
|
/** 默认调度器基础 URL */
|
|
9
9
|
export declare const DEFAULT_SCHEDULER_BASE_URL = "http://localhost:8080";
|
|
10
|
-
export type SchedulerBaseUrlSource = "env" | "single-auto-register-target" | "default" | "ambiguous-auto-register-targets";
|
|
10
|
+
export type SchedulerBaseUrlSource = "request" | "env" | "single-auto-register-target" | "default" | "ambiguous-auto-register-targets";
|
|
11
11
|
export interface SchedulerBaseUrlResolution {
|
|
12
12
|
url: string;
|
|
13
13
|
source: SchedulerBaseUrlSource;
|
|
@@ -18,6 +18,11 @@ export interface SchedulerBaseUrlResolution {
|
|
|
18
18
|
* 主流程:显式环境变量优先;未配置时,单个自动注册目标可作为宿主机部署的零额外配置回退;多目标不静默取第一个。
|
|
19
19
|
*/
|
|
20
20
|
export declare function resolveSchedulerBaseUrl(): SchedulerBaseUrlResolution;
|
|
21
|
+
/**
|
|
22
|
+
* 解析调度中心基础 URL。
|
|
23
|
+
* 主流程:服务端请求携带的 scheduler 地址优先;再读显式环境变量;最后回退到单个自动注册目标或默认值。
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveSchedulerBaseUrlFrom(requestSchedulerBaseUrl: unknown): SchedulerBaseUrlResolution;
|
|
21
26
|
/** 调度器基础 URL(兼容旧导入;请求处理应优先调用 resolveSchedulerBaseUrl 获取最新值) */
|
|
22
27
|
export declare const schedulerBaseUrl: string;
|
|
23
28
|
/** Node 环境 */
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,MAElB,CAAC;AAEF,kBAAkB;AAClB,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAC9B,KAAK,GACL,6BAA6B,GAC7B,SAAS,GACT,iCAAiC,CAAC;AAEtC,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,sBAAsB,CAAC;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AA+ED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,0BAA0B,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,MAElB,CAAC;AAEF,kBAAkB;AAClB,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAC9B,SAAS,GACT,KAAK,GACL,6BAA6B,GAC7B,SAAS,GACT,iCAAiC,CAAC;AAEtC,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,sBAAsB,CAAC;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AA+ED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,0BAA0B,CAEpE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,uBAAuB,EAAE,OAAO,GAC/B,0BAA0B,CA4C5B;AAED,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,EAAE,MAEH,CAAC;AAE7B,cAAc;AACd,eAAO,MAAM,OAAO,EAAE,MAA8C,CAAC;AAErE,oCAAoC;AACpC,eAAO,MAAM,oBAAoB,EAAE,OACiB,CAAC;AAErD,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB,EAAE,MAAM,EAMC,CAAC;AAEzC,gEAAgE;AAChE,wBAAgB,uBAAuB,IAAI,IAAI,CAAG;AAElD,eAAe;AACf,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,2BAA2B;AAC3B,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED,0BAA0B;AAC1B,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED,sBAAsB;AACtB,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED,4BAA4B;AAC5B,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;GAEG;AAEH,yBAAyB;AACzB,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB,EACrB,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1C,eAAe;AACf,eAAO,MAAM,mBAAmB,SACiB,CAAC;AAElD,iCAAiC;AACjC,eAAO,MAAM,2BAA2B,QAEvC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,uBAAuB,QAEnC,CAAC;AAEF;;GAEG;AAEH,eAAe;AACf,eAAO,MAAM,qBAAqB,SAA2C,CAAC;AAE9E,YAAY;AACZ,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,iBAAiB;AACjB,eAAO,MAAM,sBAAsB,QAAiC,CAAC;AAErE,oBAAoB;AACpB,eAAO,MAAM,2BAA2B,QAAsC,CAAC;AAE/E,WAAW;AACX,eAAO,MAAM,0BAA0B,QAAqC,CAAC;AAE7E,WAAW;AACX,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,aAAa;AACb,eAAO,MAAM,4BAA4B,QACH,CAAC;AAEvC,kCAAkC;AAClC,eAAO,MAAM,wBAAwB,QAC4B,CAAC;AAElE,aAAa;AACb,eAAO,MAAM,yBAAyB,QAErC,CAAC;AAEF,iBAAiB;AACjB,eAAO,MAAM,4BAA4B,QAExC,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -76,6 +76,21 @@ function readAutoRegisterTargetUrls() {
|
|
|
76
76
|
* 主流程:显式环境变量优先;未配置时,单个自动注册目标可作为宿主机部署的零额外配置回退;多目标不静默取第一个。
|
|
77
77
|
*/
|
|
78
78
|
export function resolveSchedulerBaseUrl() {
|
|
79
|
+
return resolveSchedulerBaseUrlFrom(undefined);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 解析调度中心基础 URL。
|
|
83
|
+
* 主流程:服务端请求携带的 scheduler 地址优先;再读显式环境变量;最后回退到单个自动注册目标或默认值。
|
|
84
|
+
*/
|
|
85
|
+
export function resolveSchedulerBaseUrlFrom(requestSchedulerBaseUrl) {
|
|
86
|
+
const normalizedRequestSchedulerBaseUrl = normalizeSchedulerHttpBaseUrl(requestSchedulerBaseUrl);
|
|
87
|
+
if (normalizedRequestSchedulerBaseUrl) {
|
|
88
|
+
return {
|
|
89
|
+
url: normalizedRequestSchedulerBaseUrl,
|
|
90
|
+
source: "request",
|
|
91
|
+
ambiguousTargetUrls: [],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
79
94
|
const envSchedulerBaseUrl = normalizeSchedulerHttpBaseUrl(process.env.AWS_RUNTIME_SCHEDULER_BASE_URL);
|
|
80
95
|
if (envSchedulerBaseUrl) {
|
|
81
96
|
return {
|
package/dist/config.test.js
CHANGED
|
@@ -42,6 +42,18 @@ afterEach(() => {
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
describe("scheduler base URL resolution", () => {
|
|
45
|
+
it("keeps scheduler URL supplied by the server request as the highest priority", async () => {
|
|
46
|
+
const runtimeHome = createRuntimeHome();
|
|
47
|
+
useRuntimeHome(runtimeHome);
|
|
48
|
+
process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = "http://env.local:7380";
|
|
49
|
+
writeBridgeConfig(runtimeHome, [{ serverUrl: "http://target.local:7380" }]);
|
|
50
|
+
const { resolveSchedulerBaseUrlFrom } = await import("./config.js");
|
|
51
|
+
expect(resolveSchedulerBaseUrlFrom("http://server.local:7380/api")).toEqual({
|
|
52
|
+
url: "http://server.local:7380",
|
|
53
|
+
source: "request",
|
|
54
|
+
ambiguousTargetUrls: [],
|
|
55
|
+
});
|
|
56
|
+
});
|
|
45
57
|
it("keeps explicit AWS_RUNTIME_SCHEDULER_BASE_URL as the highest priority", async () => {
|
|
46
58
|
const runtimeHome = createRuntimeHome();
|
|
47
59
|
useRuntimeHome(runtimeHome);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAYrD,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iCAAiC;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,8BAA8B,CAAC;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B;
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAYrD,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iCAAiC;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,8BAA8B,CAAC;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B;AAoDD;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,MAAM,EAAE,GACnB,iCAAiC,CAUnC;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,MAAM,EAAE,EACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAaR;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC,CAAC,CAY3F"}
|
package/dist/routes/instance.js
CHANGED
|
@@ -2,14 +2,14 @@ import { Router } from "express";
|
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { validateToken } from "../middleware/auth.js";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveSchedulerBaseUrlFrom } from "../config.js";
|
|
6
6
|
import { getRuntimeAccessToken } from "../services/runtime-binding.js";
|
|
7
7
|
import { loadInstanceState, saveInstanceState, } from "../services/instance-state.js";
|
|
8
8
|
import { initInstance } from "../services/instance-init-service.js";
|
|
9
9
|
import { discoverCcSwitchConfiguredItems, loadCcSwitchSdk } from "../services/cc-switch-sdk.js";
|
|
10
10
|
import { syncLegacyStateFromSdk } from "../services/instance-init-service.js";
|
|
11
11
|
import { detectToolStatuses, ensureToolsInstalled, SUPPORTED_INSTALLABLE_TOOLS, SUPPORTED_UNINSTALLABLE_TOOLS, uninstallTools, } from "../services/tool-installer.js";
|
|
12
|
-
import { getConfiguredConnectionKeys } from "../services/auto-register.js";
|
|
12
|
+
import { getConfiguredConnectionKeys, requestRuntimeAccessTokenRefreshForServer } from "../services/auto-register.js";
|
|
13
13
|
import { createLogger } from "../utils/logger.js";
|
|
14
14
|
const log = createLogger("instance");
|
|
15
15
|
const PANEL_TOOL_STATUS_KEYS = ["claude", "opencode", "codex"];
|
|
@@ -92,6 +92,40 @@ export function buildSchedulerPingFailureResponse(error, configuredSchedulerBase
|
|
|
92
92
|
: "aws-runtime-bridge 已连通,但它回连调度中心失败。请确认 AWS_RUNTIME_SCHEDULER_BASE_URL 指向的调度中心地址可从 bridge 机器访问,并且运行时访问令牌仍有效。",
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
+
function getAxiosStatusCode(error) {
|
|
96
|
+
if (typeof error !== "object" || error === null) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const response = Reflect.get(error, "response");
|
|
100
|
+
if (typeof response !== "object" || response === null) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const status = Reflect.get(response, "status");
|
|
104
|
+
return typeof status === "number" ? status : undefined;
|
|
105
|
+
}
|
|
106
|
+
async function pingSchedulerWithToken(schedulerBaseUrl, runtimeAccessToken) {
|
|
107
|
+
try {
|
|
108
|
+
const schedulerResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": runtimeAccessToken } });
|
|
109
|
+
return {
|
|
110
|
+
schedulerReachable: schedulerResponse.status >= 200 && schedulerResponse.status < 300,
|
|
111
|
+
tokenRefreshed: false,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (getAxiosStatusCode(error) !== 401) {
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
const refreshResult = await requestRuntimeAccessTokenRefreshForServer(schedulerBaseUrl);
|
|
119
|
+
if (!refreshResult.success || !refreshResult.runtimeAccessToken) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
const retryResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": refreshResult.runtimeAccessToken } });
|
|
123
|
+
return {
|
|
124
|
+
schedulerReachable: retryResponse.status >= 200 && retryResponse.status < 300,
|
|
125
|
+
tokenRefreshed: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
95
129
|
/**
|
|
96
130
|
* Build the response for ambiguous scheduler targets.
|
|
97
131
|
* Main flow: never silently pick the first target; require an explicit scheduler URL or future scheduler identity routing.
|
|
@@ -154,8 +188,8 @@ instanceRouter.get("/connection-check", (req, res) => {
|
|
|
154
188
|
const result = buildConnectionCheckResponse(connectionKeyMd5);
|
|
155
189
|
res.status(result.status).json(result.body);
|
|
156
190
|
});
|
|
157
|
-
instanceRouter.get("/ping", validateToken, async (
|
|
158
|
-
const schedulerBaseUrlResolution =
|
|
191
|
+
instanceRouter.get("/ping", validateToken, async (req, res) => {
|
|
192
|
+
const schedulerBaseUrlResolution = resolveSchedulerBaseUrlFrom(req.header("X-Scheduler-Base-Url"));
|
|
159
193
|
if (schedulerBaseUrlResolution.source === "ambiguous-auto-register-targets") {
|
|
160
194
|
res
|
|
161
195
|
.status(400)
|
|
@@ -169,11 +203,12 @@ instanceRouter.get("/ping", validateToken, async (_req, res) => {
|
|
|
169
203
|
res.status(401).json({ ok: false, error: "runtime_access_token_required" });
|
|
170
204
|
return;
|
|
171
205
|
}
|
|
172
|
-
const
|
|
206
|
+
const schedulerPing = await pingSchedulerWithToken(schedulerBaseUrl, runtimeAccessToken);
|
|
173
207
|
res.json({
|
|
174
208
|
ok: true,
|
|
175
209
|
runtimeBridge: "healthy",
|
|
176
|
-
schedulerReachable:
|
|
210
|
+
schedulerReachable: schedulerPing.schedulerReachable,
|
|
211
|
+
tokenRefreshed: schedulerPing.tokenRefreshed,
|
|
177
212
|
schedulerBaseUrl,
|
|
178
213
|
});
|
|
179
214
|
}
|
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
* Instance 路由单元测试
|
|
3
3
|
*/
|
|
4
4
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
5
|
+
vi.mock('axios', () => ({
|
|
6
|
+
default: {
|
|
7
|
+
get: vi.fn(),
|
|
8
|
+
},
|
|
9
|
+
}));
|
|
5
10
|
vi.mock('../services/auto-register.js', () => ({
|
|
6
11
|
getConfiguredConnectionKeys: vi.fn(() => []),
|
|
12
|
+
requestRuntimeAccessTokenRefreshForServer: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
vi.mock('../services/runtime-binding.js', () => ({
|
|
15
|
+
getRuntimeAccessToken: vi.fn(() => 'stale-runtime-token-123456'),
|
|
7
16
|
}));
|
|
8
17
|
afterEach(() => {
|
|
9
18
|
vi.clearAllMocks();
|
|
@@ -203,6 +212,67 @@ describe('instance route validation', () => {
|
|
|
203
212
|
expect(response.runtimeBridge).toBe('healthy');
|
|
204
213
|
expect(response.hint).toContain('运行时访问令牌仍有效');
|
|
205
214
|
});
|
|
215
|
+
it('refreshes runtime token and retries scheduler ping after 401', async () => {
|
|
216
|
+
process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = 'http://localhost:8080';
|
|
217
|
+
const { default: axios } = await import('axios');
|
|
218
|
+
const { requestRuntimeAccessTokenRefreshForServer } = await import('../services/auto-register.js');
|
|
219
|
+
const { instanceRouter } = await import('./instance.js');
|
|
220
|
+
vi.mocked(requestRuntimeAccessTokenRefreshForServer).mockResolvedValue({
|
|
221
|
+
success: true,
|
|
222
|
+
runtimeAccessToken: 'fresh-runtime-token-123456',
|
|
223
|
+
updated: true,
|
|
224
|
+
});
|
|
225
|
+
vi.mocked(axios.get)
|
|
226
|
+
.mockRejectedValueOnce({ response: { status: 401 }, message: 'Request failed with status code 401' })
|
|
227
|
+
.mockResolvedValueOnce({ status: 200 });
|
|
228
|
+
const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/ping')?.route?.stack[1]?.handle;
|
|
229
|
+
expect(handler).toBeTypeOf('function');
|
|
230
|
+
const json = vi.fn();
|
|
231
|
+
const status = vi.fn(() => ({ json }));
|
|
232
|
+
await handler?.({ header: vi.fn(() => '') }, { json, status }, vi.fn());
|
|
233
|
+
expect(requestRuntimeAccessTokenRefreshForServer).toHaveBeenCalledWith('http://localhost:8080');
|
|
234
|
+
expect(axios.get).toHaveBeenNthCalledWith(1, 'http://localhost:8080/api/runtime/ping', {
|
|
235
|
+
headers: { 'X-Runtime-Token': 'stale-runtime-token-123456' },
|
|
236
|
+
});
|
|
237
|
+
expect(axios.get).toHaveBeenNthCalledWith(2, 'http://localhost:8080/api/runtime/ping', {
|
|
238
|
+
headers: { 'X-Runtime-Token': 'fresh-runtime-token-123456' },
|
|
239
|
+
});
|
|
240
|
+
expect(json).toHaveBeenCalledWith({
|
|
241
|
+
ok: true,
|
|
242
|
+
runtimeBridge: 'healthy',
|
|
243
|
+
schedulerReachable: true,
|
|
244
|
+
tokenRefreshed: true,
|
|
245
|
+
schedulerBaseUrl: 'http://localhost:8080',
|
|
246
|
+
});
|
|
247
|
+
expect(status).not.toHaveBeenCalled();
|
|
248
|
+
});
|
|
249
|
+
it('uses scheduler base URL supplied by server ping request before local bridge config', async () => {
|
|
250
|
+
process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = 'http://localhost:8080';
|
|
251
|
+
const { default: axios } = await import('axios');
|
|
252
|
+
const { getRuntimeAccessToken } = await import('../services/runtime-binding.js');
|
|
253
|
+
const { instanceRouter } = await import('./instance.js');
|
|
254
|
+
vi.mocked(getRuntimeAccessToken).mockReturnValue('stale-runtime-token-123456');
|
|
255
|
+
vi.mocked(axios.get).mockReset();
|
|
256
|
+
vi.mocked(axios.get).mockResolvedValueOnce({ status: 200 });
|
|
257
|
+
const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/ping')?.route?.stack[1]?.handle;
|
|
258
|
+
expect(handler).toBeTypeOf('function');
|
|
259
|
+
const json = vi.fn();
|
|
260
|
+
const status = vi.fn(() => ({ json }));
|
|
261
|
+
const header = vi.fn((name) => (name === 'X-Scheduler-Base-Url' ? 'http://server.local:7380/api' : ''));
|
|
262
|
+
await handler?.({ header }, { json, status }, vi.fn());
|
|
263
|
+
expect(getRuntimeAccessToken).toHaveBeenCalledWith(undefined, 'http://server.local:7380');
|
|
264
|
+
expect(axios.get).toHaveBeenCalledWith('http://server.local:7380/api/runtime/ping', {
|
|
265
|
+
headers: { 'X-Runtime-Token': 'stale-runtime-token-123456' },
|
|
266
|
+
});
|
|
267
|
+
expect(json).toHaveBeenCalledWith({
|
|
268
|
+
ok: true,
|
|
269
|
+
runtimeBridge: 'healthy',
|
|
270
|
+
schedulerReachable: true,
|
|
271
|
+
tokenRefreshed: false,
|
|
272
|
+
schedulerBaseUrl: 'http://server.local:7380',
|
|
273
|
+
});
|
|
274
|
+
expect(status).not.toHaveBeenCalled();
|
|
275
|
+
});
|
|
206
276
|
it('explains ambiguous auto-register scheduler targets without picking the first one', async () => {
|
|
207
277
|
const { buildAmbiguousSchedulerBaseUrlResponse } = await import('./instance.js');
|
|
208
278
|
const response = buildAmbiguousSchedulerBaseUrlResponse([
|