fix(mcp): привязать OAuth-согласие и завершать чтение логов при обслуживании

This commit is contained in:
Codex committed 2026-11-05 03:56:17 +07:00
1 parent 78b268cc39
commit 6569dbfc60
6 files changed
+173 -8

No files matched your search

+59
View File
@@ -31,6 +31,65 @@ async function setup(page: Page, permissions: string[]) {
return { mutations }
}
test('synthetic: bind OAuth consent to the displayed request and submit only once', async ({ page }) => {
await setup(page, ['mm:mcp:read'])
const firstId = 'fa58fa40-4bcb-4616-9316-cb69463b3151'
const secondId = 'fa58fa40-4bcb-4616-9316-cb69463b3152'
const headers = { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'GET,POST,OPTIONS', 'access-control-allow-headers': 'Authorization,Content-Type' }
const first = { id: firstId, name: 'OAuth A', redirect_uri: 'https://a.example.test/callback', scope: 'mcp' }
const second = { id: secondId, name: 'OAuth B', redirect_uri: 'https://b.example.test/callback', scope: 'mcp' }
let releaseSecond!: () => void
const secondPending = new Promise<void>(resolve => { releaseSecond = resolve })
await page.route('**/api/mcp/oauth/requests/*', async route => {
if (route.request().method() === 'OPTIONS') return route.fulfill({ status: 204, headers })
const isSecond = route.request().url().endsWith(secondId)
if (isSecond) await secondPending
await route.fulfill({ headers, json: isSecond ? second : first })
})
let releasePost!: () => void
const postPending = new Promise<void>(resolve => { releasePost = resolve })
const decisions: unknown[] = []
await page.route('**/api/mcp/oauth/consent', async route => {
if (route.request().method() === 'OPTIONS') return route.fulfill({ status: 204, headers })
decisions.push(route.request().postDataJSON())
await postPending
await route.fulfill({ status: 409, headers, json: { message: 'Синтетический отказ' } })
})
await page.goto('/mcp?oauthRequest=' + firstId)
const connection = page.getByLabel('Подключение', { exact: true })
const approve = page.getByRole('button', { name: 'Разрешить', exact: true })
const deny = page.getByRole('button', { name: 'Отклонить', exact: true })
await expect(page.getByText(first.redirect_uri, { exact: false })).toBeVisible()
await connection.selectOption(id)
await expect(approve).toBeEnabled()
const secondRequest = page.waitForRequest(request => request.method() === 'GET' && request.url().endsWith('/oauth/requests/' + secondId))
await page.evaluate(id => window.history.pushState(null, '', '/mcp?oauthRequest=' + id), secondId)
await secondRequest
await expect(page.getByText(first.redirect_uri, { exact: false })).toHaveCount(0)
await expect(connection).toHaveValue('')
await expect(approve).toBeDisabled()
await expect(deny).toBeDisabled()
expect(decisions).toEqual([])
releaseSecond()
await expect(page.getByText(second.redirect_uri, { exact: false })).toBeVisible()
await connection.selectOption(id)
await expect(approve).toBeEnabled()
await approve.evaluate((button, denyLabel) => {
const approve = button as HTMLButtonElement
const deny = [...document.querySelectorAll('button')].find(node => node.textContent === denyLabel)
approve.click()
deny?.click()
}, 'Отклонить')
await expect.poll(() => decisions).toEqual([{ requestId: secondId, connectionId: id, approve: true }])
await expect(approve).toBeDisabled()
await expect(deny).toBeDisabled()
releasePost()
await expect(approve).toBeEnabled()
expect(decisions).toHaveLength(1)
})
test('synthetic: issue only selected server/read permission and hide one-time token', async ({ page }) => {
const state = await setup(page, ['mm:mcp:admin', 'mm:servers:read'])
await page.goto('/mcp?tab=connections')