feat: add standalone login renewal
CI / Client installer (macos-latest) (push) Canceled after 0s
CI / Client installer (ubuntu-latest) (push) Canceled after 0s
CI / Client installer (windows-latest) (push) Canceled after 0s
CI / test (22) (push) Canceled after 0s
CI / test (24) (push) Canceled after 0s
CI / release-smoke (push) Canceled after 0s
CI / Client installer (macos-latest) (push) Canceled after 0s
CI / Client installer (ubuntu-latest) (push) Canceled after 0s
CI / Client installer (windows-latest) (push) Canceled after 0s
CI / test (22) (push) Canceled after 0s
CI / test (24) (push) Canceled after 0s
CI / release-smoke (push) Canceled after 0s
This commit is contained in:
@@ -182,6 +182,39 @@ test("stdio bridge discards a connected client after its access token expires",
|
||||
assert.equal(result.content[1].uri, "https://approval.example.test/oauth/device?user_code=NEXT-1234");
|
||||
});
|
||||
|
||||
test("expired device link is replaced by a fresh login challenge without reinstalling", async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "ding-bridge-expired-link-"));
|
||||
const statePath = join(root, "session.json");
|
||||
writeFileSync(statePath, JSON.stringify({
|
||||
client: { client_id: "bridge-client" },
|
||||
device: {
|
||||
device_code: "expired-device", user_code: "OLD-1234",
|
||||
verification_uri_complete: "https://approval.example.test/oauth/device?user_code=OLD-1234",
|
||||
expiresAt: Date.now() - 1, interval: 5,
|
||||
},
|
||||
}));
|
||||
const paths: string[] = [];
|
||||
const bridge = new RemoteBridge("https://approval.example.test/mcp", statePath, async (input) => {
|
||||
const path = new URL(String(input)).pathname;
|
||||
paths.push(path);
|
||||
if (path === "/oauth/device/authorize") return Response.json({
|
||||
device_code: "fresh-device", user_code: "NEW-5678",
|
||||
verification_uri_complete: "https://approval.example.test/oauth/device?user_code=NEW-5678",
|
||||
expires_in: 600, interval: 5,
|
||||
});
|
||||
throw new Error(`unexpected ${path}`);
|
||||
});
|
||||
|
||||
const result = await bridge.loginChallenge();
|
||||
|
||||
assert.deepEqual(paths, ["/oauth/device/authorize"]);
|
||||
assert.equal(result.authenticated, false);
|
||||
assert.ok("authChallenge" in result);
|
||||
assert.equal(result.authChallenge.directUrl, "https://approval.example.test/oauth/device?user_code=NEW-5678");
|
||||
assert.match(result.message, /无需重新安装/);
|
||||
assert.doesNotMatch(readFileSync(statePath, "utf8"), /expired-device|OLD-1234/);
|
||||
});
|
||||
|
||||
test("MCP approval_login returns both portable text and a standard resource link", async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "ding-bridge-mcp-login-"));
|
||||
const bridge = new RemoteBridge("https://approval.example.test/mcp", join(root, "session.json"), async (input) => {
|
||||
@@ -210,6 +243,7 @@ test("MCP approval_login returns both portable text and a standard resource link
|
||||
assert.equal((response.structuredContent as any).result.authChallenge.directUrl, link?.uri);
|
||||
const tools = await client.listTools();
|
||||
const instructions = client.getInstructions() ?? "";
|
||||
assert.match(instructions, /登录链接过期.*approval_login.*绝不.*重新安装/);
|
||||
assert.match(instructions, /审批引用.*调用容量.*内部分批.*不得向用户解释/);
|
||||
assert.doesNotMatch(instructions, /最多.*20|selectionId|approvalId|targetId/);
|
||||
const history = tools.tools.find((tool) => tool.name === "approval_list_history")!;
|
||||
|
||||
Reference in New Issue
Block a user