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,103 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
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 origin = process.env.VERIFY_FRONTEND_ORIGIN || 'http://127.0.0.1:15173'
|
||||
const apiOrigin = process.env.VERIFY_API_ORIGIN || 'http://127.0.0.1:18080'
|
||||
|
||||
async function main() {
|
||||
const credentials = JSON.parse(fs.readFileSync(`${directory}/super-admin-login.json`, 'utf8'))
|
||||
const response = await fetch(`${apiOrigin}/api/v1/auth/login`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: credentials.email, password: credentials.password }),
|
||||
})
|
||||
assert.equal(response.status, 200)
|
||||
const auth = await response.json()
|
||||
const browser = await chromium.launch({ headless: true, channel: 'chrome' })
|
||||
const checks = [], errors = [], blockedWrites = [], writes = []
|
||||
const check = (name, passed) => { checks.push({ name, passed: !!passed }); assert.ok(passed, name) }
|
||||
try {
|
||||
const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } })
|
||||
await context.addInitScript(a => {
|
||||
const entries = {
|
||||
weknora_user: JSON.stringify({ ...a.user, tenant_id: String(a.user.tenant_id) }),
|
||||
weknora_tenant: JSON.stringify({ ...a.active_tenant, id: '10059' }),
|
||||
weknora_token: a.token, weknora_refresh_token: a.refresh_token,
|
||||
weknora_memberships: JSON.stringify(a.memberships), 'weknora:new-user-guide-done:v1': '1',
|
||||
}
|
||||
for (const [key, value] of Object.entries(entries)) localStorage.setItem(key, value)
|
||||
}, auth)
|
||||
const fixture = (id, kind, name, data) => ({ id, kind, name, data, tenant_id: 10059, owner_id: auth.user.id, owner_name: 'UI verification', status: kind === 'training-maps' ? 'confirmed' : 'draft', version: 1, created_at: new Date().toISOString(), updated_at: new Date().toISOString() })
|
||||
const map = fixture('duration-map', 'training-maps', '时长验证导图', { days: [{ day: 1, title: '基础与实践' }], agent_id: 'duration-agent' })
|
||||
const flow = fixture('duration-flow', 'flows', '时长验证画布', { nodes: [{ id: 'node', label: '内容作者', agent_id: 'duration-agent' }], edges: [] })
|
||||
let task = null
|
||||
// All business writes below are in-memory fixtures; no task or AI job reaches the API.
|
||||
await context.route('**/api/v1/**', async route => {
|
||||
const request = route.request(), path = new URL(request.url()).pathname, method = request.method()
|
||||
const ok = body => route.fulfill({ status: 200, json: { success: true, ...body } })
|
||||
if (path === '/api/v1/access/me') return ok({ data: { administrator: false, restricted: true, permissions: ['content.read', 'content.write', 'flows.read'] } })
|
||||
const match = path.match(/^\/api\/v1\/workbench\/resources\/(content-tasks|training-maps|flows)(?:\/([^/]+))?(?:\/(jobs|actions\/.*))?$/)
|
||||
if (match) {
|
||||
const [, kind, id, action] = match
|
||||
if (method === 'GET' && action === 'jobs') return ok({ data: [], total: 0 })
|
||||
if (method === 'GET') {
|
||||
const items = kind === 'training-maps' ? [map] : kind === 'flows' ? [flow] : task ? [task] : []
|
||||
return ok(id ? { data: items.find(item => item.id === id) } : { data: items, total: items.length })
|
||||
}
|
||||
if (kind === 'content-tasks' && !action && ['POST', 'PUT'].includes(method)) {
|
||||
const body = request.postDataJSON()
|
||||
writes.push({ method, data: body.data })
|
||||
task = { ...fixture('duration-task', kind, body.name, body.data), version: (task?.version || 0) + 1 }
|
||||
return ok({ data: task })
|
||||
}
|
||||
}
|
||||
if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) {
|
||||
blockedWrites.push({ path, method })
|
||||
return route.fulfill({ status: 409, json: { error: 'Verification prevents live writes' } })
|
||||
}
|
||||
return route.continue()
|
||||
})
|
||||
const page = await context.newPage()
|
||||
page.on('pageerror', error => errors.push(error.message))
|
||||
await page.goto(`${origin}/platform/workbench/content?tab=tasks`)
|
||||
await page.getByRole('button', { name: '创建内容任务', exact: true }).click()
|
||||
const drawer = page.locator('.t-drawer--open')
|
||||
const duration = drawer.locator('[aria-label="直播时长(分钟)"] input')
|
||||
await duration.waitFor()
|
||||
check('new_task_defaults_to_60_minutes', await duration.inputValue() === '60')
|
||||
await drawer.locator('.t-form__item').filter({ has: page.locator('.t-form__label').filter({ hasText: /^名称$/ }) }).locator('input').fill('直播时长验证任务')
|
||||
await duration.fill('135')
|
||||
await duration.press('Tab')
|
||||
await drawer.locator('.t-form__item').filter({ hasText: '已确认的训练营导图' }).locator('.t-select').click()
|
||||
await page.getByText('时长验证导图', { exact: true }).last().click()
|
||||
await drawer.locator('.t-form__item').filter({ hasText: '画布模板' }).locator('.t-select').click()
|
||||
await page.getByText('时长验证画布', { exact: true }).last().click()
|
||||
await page.screenshot({ path: `${directory}/content-duration-desktop.png`, animations: 'disabled' })
|
||||
await drawer.getByRole('button', { name: '保存草稿', exact: true }).click()
|
||||
await page.getByText('直播时长:135 分钟', { exact: true }).waitFor()
|
||||
check('draft_payload_and_detail_keep_duration', writes[0]?.data.duration_minutes === 135)
|
||||
await page.getByRole('button', { name: '编辑配置', exact: true }).click()
|
||||
await duration.waitFor()
|
||||
check('editing_reopens_saved_duration', await duration.inputValue() === '135')
|
||||
await page.setViewportSize({ width: 390, height: 844 })
|
||||
await duration.scrollIntoViewIfNeeded()
|
||||
await page.screenshot({ path: `${directory}/content-duration-mobile.png`, animations: 'disabled' })
|
||||
check('duration_control_fits_mobile', await duration.evaluate(element => { const r = element.getBoundingClientRect(); return r.left >= 0 && r.right <= innerWidth }))
|
||||
check('drawer_fits_mobile', await drawer.locator('.t-drawer__content-wrapper').evaluate(element => { const r = element.getBoundingClientRect(); return r.left >= -1 && r.right <= innerWidth + 1 }))
|
||||
await duration.fill('225')
|
||||
await duration.press('Tab')
|
||||
await drawer.getByRole('button', { name: '保存配置', exact: true }).click()
|
||||
await page.getByText('直播时长:225 分钟', { exact: true }).waitFor()
|
||||
check('edited_duration_saved', writes.at(-1)?.data.duration_minutes === 225)
|
||||
check('no_live_business_or_ai_writes', blockedWrites.length === 0 && writes.length === 2)
|
||||
check('no_runtime_errors', errors.length === 0)
|
||||
fs.writeFileSync(`${directory}/content-duration-ui.json`, JSON.stringify({ origin, checks, errors, businessWrites: 'mocked in browser only' }, null, 2))
|
||||
console.log(JSON.stringify({ checks, errors }, null, 2))
|
||||
} catch (error) {
|
||||
console.error(error.stack)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
main().catch(error => { console.error(error.stack); process.exitCode = 1 })
|
||||
Reference in New Issue
Block a user