dify/e2e/features/step-definitions/agent-v2/access-point-service-api.steps.ts
yyh d66de8a47b
test(e2e): harden behavior coverage and CI gates (#39043)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-16 07:32:11 +00:00

279 lines
10 KiB
TypeScript

import type { DifyWorld } from '../../support/world'
import { Given, Then, When } from '@cucumber/cucumber'
import { expect } from '@playwright/test'
import {
createAgentApiKey,
sendAgentServiceApiChatMessage,
setAgentApiAccess,
} from '../../agent-v2/support/access-point'
import {
agentBuilderExpectedTokens,
agentBuilderFixedInputs,
} from '../../agent-v2/support/agent-builder-resources'
import { SERVICE_API_RUNTIME_STEP_TIMEOUT_MS } from '../../agent-v2/support/service-api-sse'
import { getCurrentAgentId, getServiceApiCard } from './access-point-helpers'
async function enableAgentApiAccessWithKey(world: DifyWorld) {
const agentId = getCurrentAgentId(world)
const apiAccess = await setAgentApiAccess(agentId, true)
const apiKey = await createAgentApiKey(agentId)
world.agentBuilder.accessPoint.serviceApiBaseURL = apiAccess.service_api_base_url
world.agentBuilder.accessPoint.generatedApiKey = apiKey.token
}
Given(
'Agent v2 Backend service API access has been enabled with a key via API',
async function (this: DifyWorld) {
await enableAgentApiAccessWithKey(this)
},
)
Then('I should see the Agent v2 Backend service API endpoint', async function (this: DifyWorld) {
const serviceApiCard = getServiceApiCard(this)
if (!this.agentBuilder.accessPoint.serviceApiBaseURL)
throw new Error('No Agent v2 service API endpoint found. Enable Backend service API first.')
await expect(serviceApiCard.getByRole('heading', { name: 'Backend service API' })).toBeVisible({
timeout: 30_000,
})
await expect(serviceApiCard.getByText('Service API Endpoint')).toBeVisible()
await expect(
serviceApiCard.getByText(this.agentBuilder.accessPoint.serviceApiBaseURL),
).toBeVisible()
await expect(serviceApiCard.getByLabel('Copy service API endpoint')).toBeEnabled()
})
When('I copy the Agent v2 Backend service API endpoint', async function (this: DifyWorld) {
await getServiceApiCard(this).getByLabel('Copy service API endpoint').click()
})
Then(
'the Agent v2 Backend service API endpoint should show it was copied',
async function (this: DifyWorld) {
await expect(this.getPage().getByLabel('Copied')).toBeVisible()
},
)
When('I open Agent v2 API key management', async function (this: DifyWorld) {
await getServiceApiCard(this)
.getByRole('button', { name: /^API Key\b/ })
.click()
})
Then('Agent v2 API keys should not expose a secret by default', async function (this: DifyWorld) {
const page = this.getPage()
const dialog = page.getByRole('dialog', { name: /API Secret key/i })
const existingSecret = this.agentBuilder.accessPoint.generatedApiKey
await expect(dialog).toBeVisible()
await expect(dialog.getByText('Secret Key', { exact: true })).toBeVisible()
await expect(dialog.getByText('CREATED', { exact: true })).toBeVisible()
await expect(dialog.getByText('LAST USED', { exact: true })).toBeVisible()
await expect(dialog.getByRole('button', { name: 'Create new Secret key' })).toBeVisible()
if (existingSecret)
await expect(dialog.getByText(existingSecret, { exact: true })).not.toBeVisible()
await expect(dialog.getByText(/^app-/)).not.toBeVisible()
await expect(page.getByRole('dialog', { name: 'Internal Server Error' })).not.toBeVisible()
})
When('I create a new Agent v2 API key', async function (this: DifyWorld) {
const dialog = this.getPage().getByRole('dialog', { name: /API Secret key/i })
await dialog.getByRole('button', { name: 'Create new Secret key' }).click()
})
Then('I should see the newly generated Agent v2 API key once', async function (this: DifyWorld) {
const generatedKeyDialog = this.getPage()
.getByRole('dialog', { name: /API Secret key/i })
.last()
const generatedKey = generatedKeyDialog.getByText(/^app-/)
await expect(generatedKeyDialog).toBeVisible()
await expect(
generatedKeyDialog.getByText('Keep this key in a secure and accessible place.'),
).toBeVisible()
await expect(generatedKey).toBeVisible()
await expect(generatedKeyDialog.getByLabel('Copy')).toBeVisible()
this.agentBuilder.accessPoint.generatedApiKey = (await generatedKey.textContent())?.trim()
if (!this.agentBuilder.accessPoint.generatedApiKey)
throw new Error('Generated Agent v2 API key was empty.')
})
When('I copy the newly generated Agent v2 API key', async function (this: DifyWorld) {
const generatedKeyDialog = this.getPage()
.getByRole('dialog', { name: /API Secret key/i })
.last()
await generatedKeyDialog.getByLabel('Copy').first().click()
})
Then(
'the newly generated Agent v2 API key should show it was copied',
async function (this: DifyWorld) {
const generatedKeyDialog = this.getPage()
.getByRole('dialog', { name: /API Secret key/i })
.last()
await expect(generatedKeyDialog.getByLabel('Copied')).toBeVisible()
},
)
When('I close the newly generated Agent v2 API key', async function (this: DifyWorld) {
const page = this.getPage()
const generatedKeyDialog = page.getByRole('dialog', { name: /API Secret key/i }).last()
await generatedKeyDialog.getByRole('button', { name: 'OK' }).click()
await expect(page.getByText('Keep this key in a secure and accessible place.')).not.toBeVisible()
})
Then(
'the Agent v2 API key list should not expose the full generated secret',
async function (this: DifyWorld) {
const fullSecret = this.agentBuilder.accessPoint.generatedApiKey
if (!fullSecret) throw new Error('No generated Agent v2 API key found.')
const apiKeyDialog = this.getPage().getByRole('dialog', { name: /API Secret key/i })
await expect(apiKeyDialog).toBeVisible()
await expect(apiKeyDialog.getByText(fullSecret, { exact: true })).not.toBeVisible()
await expect(apiKeyDialog.getByText(/^app-/)).not.toBeVisible()
await expect(apiKeyDialog.getByLabel('Copy').first()).toBeVisible()
},
)
When('I open the Agent v2 API Reference', async function (this: DifyWorld) {
const page = this.getPage()
const apiReferenceLink = page.getByRole('link', { name: 'API Reference' })
await expect(apiReferenceLink).toBeVisible()
await expect(apiReferenceLink).toHaveAttribute('href', /\/api-reference\/guides\/get-started/)
await expect(apiReferenceLink).toHaveAttribute('target', '_blank')
const [apiReferencePage] = await Promise.all([
page.waitForEvent('popup'),
apiReferenceLink.click(),
])
this.agentBuilder.accessPoint.apiReferencePage = apiReferencePage
})
Then('the Agent v2 API Reference should open in a new tab', async function (this: DifyWorld) {
const apiReferencePage = this.agentBuilder.accessPoint.apiReferencePage
if (!apiReferencePage) throw new Error('No Agent v2 API Reference page was opened.')
await expect(apiReferencePage).toHaveURL(/\/api-reference\/guides\/get-started/)
await apiReferencePage.close()
this.agentBuilder.accessPoint.apiReferencePage = undefined
})
When(
'I send the Agent v2 Backend service API minimal request',
{ timeout: SERVICE_API_RUNTIME_STEP_TIMEOUT_MS },
async function (this: DifyWorld) {
const serviceApiBaseURL = this.agentBuilder.accessPoint.serviceApiBaseURL
const apiKey = this.agentBuilder.accessPoint.generatedApiKey
if (!serviceApiBaseURL)
throw new Error('No Agent v2 service API endpoint found. Enable Backend service API first.')
if (!apiKey)
throw new Error('No Agent v2 API key found. Create a Backend service API key first.')
this.agentBuilder.accessPoint.serviceApiResponse = await sendAgentServiceApiChatMessage({
apiKey,
serviceApiBaseURL,
})
},
)
When(
'I send the Agent v2 Backend service API knowledge request',
{ timeout: SERVICE_API_RUNTIME_STEP_TIMEOUT_MS },
async function (this: DifyWorld) {
const serviceApiBaseURL = this.agentBuilder.accessPoint.serviceApiBaseURL
const apiKey = this.agentBuilder.accessPoint.generatedApiKey
if (!serviceApiBaseURL)
throw new Error('No Agent v2 service API endpoint found. Enable Backend service API first.')
if (!apiKey)
throw new Error('No Agent v2 API key found. Create a Backend service API key first.')
this.agentBuilder.accessPoint.serviceApiResponse = await sendAgentServiceApiChatMessage({
apiKey,
query: agentBuilderFixedInputs.knowledgeRuntimeQuery,
serviceApiBaseURL,
})
},
)
const stringifyServiceApiBody = (body: unknown) => {
try {
return JSON.stringify(body)
} catch {
return String(body)
}
}
const expectServiceApiResponseOK = (
response: NonNullable<DifyWorld['agentBuilder']['accessPoint']['serviceApiResponse']>,
action: string,
) => {
if (response.ok) return
throw new Error(
`${action} failed with ${response.status}: ${stringifyServiceApiBody(response.body)}`,
)
}
const expectServiceApiResponseIncludes = (
response: NonNullable<DifyWorld['agentBuilder']['accessPoint']['serviceApiResponse']>,
expectedToken: string,
action: string,
) => {
expectServiceApiResponseOK(response, action)
const body = stringifyServiceApiBody(response.body)
if (!body.includes(expectedToken))
throw new Error(`${action} response did not include ${expectedToken}: ${body}`)
}
Then(
'the Agent v2 Backend service API request should be rejected while disabled',
async function (this: DifyWorld) {
const response = this.agentBuilder.accessPoint.serviceApiResponse
if (!response) throw new Error('No Agent v2 Backend service API response was recorded.')
expect(response.ok).toBe(false)
expect(response.status).toBe(403)
expect(JSON.stringify(response.body).toLowerCase()).toContain('disabled')
},
)
Then(
'the Agent v2 Backend service API response should include the knowledge E2E marker',
async function (this: DifyWorld) {
const response = this.agentBuilder.accessPoint.serviceApiResponse
if (!response) throw new Error('No Agent v2 Backend service API response was recorded.')
expectServiceApiResponseIncludes(
response,
agentBuilderExpectedTokens.knowledgeReply,
'Agent v2 Backend service API knowledge request',
)
},
)
Then(
'the Agent v2 Backend service API request should succeed with the normal E2E marker',
async function (this: DifyWorld) {
const response = this.agentBuilder.accessPoint.serviceApiResponse
if (!response) throw new Error('No Agent v2 Backend service API response was recorded.')
expectServiceApiResponseIncludes(
response,
agentBuilderExpectedTokens.agentReply,
'Agent v2 Backend service API request',
)
},
)