WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const crypto = require('node:crypto')
|
||||
const { execFileSync } = require('node:child_process')
|
||||
const { chromium } = require('/Users/macclaw/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright')
|
||||
const directory = '/Volumes/Extreme SSD/yawan-storage/weknora/acceptance'
|
||||
const base = 'http://127.0.0.1:18080/api/v1'
|
||||
const prefix = `fusion-check-${Date.now()}`
|
||||
const checks = [], failures = [], users = [], roles = [], agents = []
|
||||
let admin, browser
|
||||
const literal = value => `'${String(value).replaceAll("'", "''")}'`
|
||||
const sql = statement => execFileSync('docker', ['--context', 'colima-yawan', 'exec', 'WeKnora-postgres', 'psql', '-U', 'postgres', '-d', 'yawan_native_acceptance_20260915', '-v', 'ON_ERROR_STOP=1', '-Atc', statement], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
|
||||
const check = (name, value) => { checks.push({ name, passed: !!value }); assert.ok(value, name) }
|
||||
async function api(route, method = 'GET', body, identity = admin, expected = 200) {
|
||||
const response = await fetch(base + route, { method, headers: { 'Content-Type': 'application/json', ...(identity ? { Authorization: `Bearer ${identity.token}`, 'X-Tenant-ID': '10059' } : {}) }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(15000) })
|
||||
const result = await response.json()
|
||||
assert.ok((Array.isArray(expected) ? expected : [expected]).includes(response.status), `${method} ${route}: ${response.status} ${result.message || result.error?.message || ''}`)
|
||||
return result
|
||||
}
|
||||
async function main() {
|
||||
const credentials = JSON.parse(fs.readFileSync(`${directory}/super-admin-login.json`, 'utf8'))
|
||||
admin = await api('/auth/login', 'POST', { email: credentials.email, password: credentials.password }, null)
|
||||
const role = (await api('/access/roles', 'POST', { name: prefix, permissions: ['agents.read', 'agents.write'] })).data
|
||||
roles.push(role.id)
|
||||
async function member(label) {
|
||||
const password = crypto.randomBytes(18).toString('base64url'), email = `${prefix}-${label}@example.test`
|
||||
const user = (await api('/access/users', 'POST', { username: `${prefix}-${label}`, email, password, role: 'contributor', status: 'active', custom_role_id: role.id })).data
|
||||
users.push(user.id)
|
||||
return api('/auth/login', 'POST', { email, password }, null)
|
||||
}
|
||||
const alice = await member('alice'), bob = await member('bob')
|
||||
async function agent(label, identity, role = 'production') {
|
||||
const row = (await api('/agents', 'POST', { name: `${prefix}-${label}`, config: { role, agent_mode: 'quick-answer', model_id: '404e005b-83ae-5e2e-a3b3-999f9d5cd47e', kb_selection_mode: 'none', knowledge_bases: [], fusion_agent_ids: [], system_prompt: '保持原有身份与表达语气' } }, identity, 201)).data
|
||||
agents.push(row.id)
|
||||
return row
|
||||
}
|
||||
const primary = await agent('primary', alice), helper = await agent('private-helper', bob), second = await agent('second-helper', alice), qc = await agent('qc', alice, 'qc')
|
||||
check('new_agents_are_private_by_default', [primary, helper, second, qc].every(row => row.visibility === 'private'))
|
||||
const update = (ids, expected = 200) => api(`/agents/${primary.id}`, 'PUT', { name: primary.name, config: { ...primary.config, fusion_agent_ids: ids } }, alice, expected)
|
||||
const denied = await update([helper.id], 400)
|
||||
check('private_other_user_helper_is_rejected_without_name_leak', !JSON.stringify(denied).includes(helper.name))
|
||||
await update([primary.id], 400)
|
||||
await update([qc.id], 400)
|
||||
await update([second.id, second.id], 400)
|
||||
await update(Array.from({ length: 9 }, () => crypto.randomUUID()), 400)
|
||||
check('self_wrong_role_duplicate_and_over_limit_rejected', true)
|
||||
async function grant(action) { await api('/access/grants', 'POST', { resources: [{ kind: 'agents', id: helper.id }], user_ids: [alice.user.id], action }) }
|
||||
await grant('grant')
|
||||
await update([helper.id, second.id])
|
||||
check('direct_grant_can_configure_multiple_helpers', JSON.stringify((await api(`/agents/${primary.id}`, 'GET', undefined, alice)).data.config.fusion_agent_ids) === JSON.stringify([helper.id, second.id]))
|
||||
await grant('revoke')
|
||||
await update([helper.id], 400)
|
||||
await api(`/agents/${primary.id}/copy`, 'POST', undefined, alice, 400)
|
||||
check('revoked_access_blocks_update_and_copy', true)
|
||||
await grant('grant')
|
||||
browser = await chromium.launch({ headless: true, channel: 'chrome' })
|
||||
const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } })
|
||||
await context.addInitScript(auth => {
|
||||
for (const [key, value] of Object.entries({ weknora_user: JSON.stringify({ ...auth.user, tenant_id: String(auth.user.tenant_id) }), weknora_tenant: JSON.stringify({ ...auth.active_tenant, id: '10059' }), weknora_token: auth.token, weknora_refresh_token: auth.refresh_token, weknora_memberships: JSON.stringify(auth.memberships), 'weknora:new-user-guide-done:v1': '1' })) localStorage.setItem(key, value)
|
||||
}, alice)
|
||||
const page = await context.newPage(), errors = []
|
||||
page.on('pageerror', error => errors.push(error.message))
|
||||
await page.goto(`http://127.0.0.1:15173/platform/agents?edit=${primary.id}`)
|
||||
const row = page.locator('.setting-row').filter({ has: page.getByText('融合表达 Agent', { exact: true }) })
|
||||
await row.waitFor()
|
||||
await row.scrollIntoViewIfNeeded()
|
||||
check('editor_displays_saved_multi_selection', await row.getByText(helper.name, { exact: true }).count() > 0 && await row.getByText(second.name, { exact: true }).count() > 0)
|
||||
await page.screenshot({ path: `${directory}/agent-fusion-desktop.png`, animations: 'disabled' })
|
||||
await page.setViewportSize({ width: 390, height: 844 })
|
||||
await row.scrollIntoViewIfNeeded()
|
||||
await page.screenshot({ path: `${directory}/agent-fusion-mobile.png`, animations: 'disabled' })
|
||||
const mobileLayout = await page.evaluate(() => ({ width: innerWidth, documentWidth: document.documentElement.scrollWidth,
|
||||
overflow: [...document.querySelectorAll('body *')].filter(element => { const rect = element.getBoundingClientRect(); return rect.width > 0 && rect.right > innerWidth + 2 }).slice(0, 15).map(element => ({ tag: element.tagName, className: element.className, right: element.getBoundingClientRect().right })) }))
|
||||
if (mobileLayout.documentWidth > mobileLayout.width + 2) console.log(JSON.stringify(mobileLayout))
|
||||
check('mobile_no_document_overflow', mobileLayout.documentWidth <= mobileLayout.width + 2)
|
||||
check('browser_no_runtime_errors', errors.length === 0)
|
||||
await api(`/agents/${helper.id}?detach=true`, 'DELETE', undefined, bob)
|
||||
const after = (await api(`/agents/${primary.id}`, 'GET', undefined, alice)).data
|
||||
check('deletion_unbinds_reference_and_preserves_other_helper', JSON.stringify(after.config.fusion_agent_ids) === JSON.stringify([second.id]))
|
||||
}
|
||||
main().catch(error => { failures.push(error.message); process.exitCode = 1 }).finally(async () => {
|
||||
await browser?.close()
|
||||
if (admin) {
|
||||
for (const id of agents) try { await api(`/agents/${id}?detach=true`, 'DELETE', undefined, admin, [200, 404]) } catch (error) { failures.push(`cleanup agent: ${error.message}`) }
|
||||
if (agents.length) {
|
||||
const ids = agents.map(literal).join(',')
|
||||
sql(`DELETE FROM workbench_access_grants WHERE tenant_id=10059 AND kind='agents' AND resource_id IN (${ids}); DELETE FROM workbench_access_publications WHERE tenant_id=10059 AND kind='agents' AND resource_id IN (${ids}); DELETE FROM custom_agents WHERE tenant_id=10059 AND id IN (${ids})`)
|
||||
}
|
||||
for (const id of users) try { await api(`/access/users/${id}`, 'DELETE') } catch (error) { failures.push(`cleanup user: ${error.message}`) }
|
||||
for (const id of roles) try { await api(`/access/roles/${id}`, 'DELETE') } catch (error) { failures.push(`cleanup role: ${error.message}`) }
|
||||
check('no_live_fixture_agents_remain', sql(`SELECT count(*) FROM custom_agents WHERE tenant_id=10059 AND name LIKE ${literal(prefix + '%')}`) === '0')
|
||||
}
|
||||
const report = { time: new Date().toISOString(), prefix, checks, failures, passed: failures.length === 0 }
|
||||
fs.writeFileSync(`${directory}/agent-fusion-live.json`, JSON.stringify(report, null, 2))
|
||||
console.log(JSON.stringify(report, null, 2))
|
||||
if (failures.length) process.exitCode = 1
|
||||
})
|
||||
Reference in New Issue
Block a user