Align canvas integration with corrected workspace UI and auth checks
creative workspace review checks / verify (pull_request) Has been cancelled

This commit is contained in:
2026-10-30 18:14:26 +08:00
parent 67dea99cd1
commit 8944e59ead
4 changed files with 22 additions and 9 deletions
+4 -2
View File
@@ -47,13 +47,15 @@ async function main() {
if (command === 'verify' || command === 'exercise') {
const request = (path:string,init:RequestInit={}) => fetch(base+path,{...init,redirect:'manual'});
const buildId = readFileSync('.next/BUILD_ID','utf8').trim();
assert.equal((await request('/')).status,200,'Public knowledge homepage');
for(const path of ['/','/workspace','/documents','/account'])assert.equal((await request(path)).status,200,'Public shell '+path);
const studio=await request('/studio');assert.equal(studio.status,307);assert.equal(studio.headers.get('location'),'/workspace');
for(const [from,to] of [['/studio/graphic','/graphic'],['/studio/infinite_canvas','/infinite_canvas']]){const r=await request(from);assert.equal(r.status,308);assert.equal(r.headers.get('location'),to)}
assert.equal((await request('/account')).status,200,'Public account page');
const homeData = await request(`/_next/data/${buildId}/index.json`);
assert.ok([200,404].includes(homeData.status),'Public homepage data is readable or absent for static pages');
assert.ok(!isLoginRedirect(homeData.headers.get('location') || homeData.headers.get('x-nextjs-redirect'),base));
assert.equal((await request('/api/posts',{method:'POST',headers:{Cookie:'wiki_account='+ 'a'.repeat(64)},body:'{}'})).status,401,'Account cookies never grant admin rights');
for (const path of ['/library/import','/skills/shadow-puppet/egret-example.png','/studio',`/_next/data/${buildId}/library/import.json`]) {
for (const path of ['/library/import','/skills/shadow-puppet/egret-example.png','/studio/layouts/ref-01',`/_next/data/${buildId}/library/import.json`]) {
const response = await request(path); assert.equal(response.status,307,path); const destination = response.headers.get('location') || response.headers.get('x-nextjs-redirect'); assert.ok(isLoginRedirect(destination,base),path+' must redirect to login: '+destination);
}
for (const path of ['/api/feed','/api/upload','/studio/api/export/test','/api/media/missing']) assert.equal((await request(path)).status,401,path);
@@ -30,12 +30,17 @@ export async function verifyGuidancePublishing(base:string){
const own=await fetch(base+'/api/documents?author='+owner).then(r=>r.json());assert.ok(own.documents.some((d:{id:string})=>d.id===httpDoc.id));assert.ok(own.documents.every((d:{author:string})=>d.author===owner));assert.equal((await fetch(base+'/api/documents?author=invalid')).status,400);
const xml=await fetch(base+'/api/documents/'+httpDoc.id+'?revision=1&format=xml');assert.equal(xml.status,200);assert.match(await xml.text(),/<tag>往返标签<\/tag>/);
const page=await fetch(base+'/documents/'+httpDoc.id+'?revision=1');assert.equal(page.status,200);assert.ok((await page.text()).includes('实际数据库文档'));
// The homepage and discovery list hydrate from the real API, so verify them in Chromium on the CI runner.
// The document discovery list hydrates from the real API; the homepage is the product landing page.
const browser=await chromium.launch({executablePath:process.env.CHROMIUM_PATH||'/usr/bin/chromium',headless:true,args:['--no-sandbox','--disable-dev-shm-usage']});
try{
const page=await browser.newPage();
const navigationDiagnostics:string[]=[];
const record=(value:string)=>{navigationDiagnostics.push(value.slice(0,1800));if(navigationDiagnostics.length>12)navigationDiagnostics.shift();};
page.on('pageerror',error=>record('pageerror: '+error.message));
page.on('console',message=>{if(message.type()==='error')record('console: '+message.text());});
page.on('response',response=>{if(response.url().includes('/_next/data/'))void response.text().then(body=>record(`data ${response.status()} ${new URL(response.url()).pathname}: ${body.slice(0,1400)}`)).catch(()=>{});});
const link=`a[href="/documents/${httpDoc.id}?revision=1"]`;
await page.goto(base+'/',{waitUntil:'domcontentloaded'});
await page.goto(base+'/documents',{waitUntil:'domcontentloaded'});
await page.locator(link).waitFor({state:'visible'});assert.equal(await page.locator(link).getByRole('heading').textContent(),httpDoc.title);
// Published matches must not inherit the examples' empty state; submitted filters survive reload.
const search=page.getByRole('textbox',{name:'搜索文档'});
@@ -54,7 +59,11 @@ export async function verifyGuidancePublishing(base:string){
await page.locator(link).waitFor({state:'visible'});
assert.equal(await search.inputValue(),'');
await page.getByRole('navigation',{name:'主导航',exact:true}).getByRole('link',{name:'社区文档',exact:true}).click();await page.locator(link).waitFor({state:'visible'});
await page.locator(link).click();await page.locator('h1').waitFor();assert.equal(await page.locator('h1').textContent(),httpDoc.title);
await page.locator(link).click();
await page.waitForURL(url=>url.pathname==='/documents/'+httpDoc.id&&url.searchParams.get('revision')==='1');
try{await page.getByRole('heading',{name:httpDoc.title,exact:true,level:1}).waitFor();}
catch(error){console.error('Document navigation diagnostics',JSON.stringify({url:page.url(),headings:await page.locator('h1').allTextContents(),events:navigationDiagnostics}));throw error;}
assert.equal(await page.locator('h1').textContent(),httpDoc.title);
await page.goto(base+'/write',{waitUntil:'domcontentloaded'});assert.equal(await page.locator('h1').textContent(),'发布独立文档');
}finally{await browser.close();}
assert.equal((await request('/'+httpDoc.id,'DELETE',undefined,{cookie:''})).status,401);
@@ -100,11 +100,11 @@ test(
const page = await context.newPage();
page.setDefaultTimeout(10000);
page.on("pageerror", (error) => errors.push(error.message));
await page.goto(base!, { waitUntil: "networkidle" });
await page.goto(base + "/documents", { waitUntil: "networkidle" });
await page
.getByRole("heading", { name: authored.title, exact: true })
.waitFor();
assert.equal(await page.title(), "探索文档 · 织页");
assert.equal(await page.title(), "社区文档 · 织页");
const search = page.getByRole("textbox", { name: "搜索文档" });
await search.fill("不存在的文档");
await search.press("Enter");
@@ -248,7 +248,7 @@ test(
"通过 XML 更新的文档",
);
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(base!, { waitUntil: "networkidle" });
await page.goto(base + "/documents", { waitUntil: "networkidle" });
assert.equal(
await page.evaluate(
() => document.documentElement.scrollWidth <= window.innerWidth,
@@ -35,7 +35,9 @@ test('real PostgreSQL + S3 notes, flat tags, resource sharing and video processi
await verifyEmailAccounts();
await verifyCanvasWorkspace(base);
await verifyGuidancePublishing(base);
const workspaceUI=spawn(process.execPath,['--import','tsx','--test','tests/guidance-workspace.test.ts'],{env:{...process.env,WORKSPACE_TEST_URL:base},timeout:90000,stdio:'inherit'});
const workspaceEnv:NodeJS.ProcessEnv={...process.env,WORKSPACE_TEST_URL:base};
delete workspaceEnv.NODE_TEST_CONTEXT;
const workspaceUI=spawn(process.execPath,['--import','tsx','--test','tests/guidance-workspace.test.ts'],{env:workspaceEnv,timeout:90000,stdio:'inherit'});
const [workspaceExit]=await once(workspaceUI,'exit');assert.equal(workspaceExit,0,'SaaS workspace UI regression checks');
await verifyFlatNoteMigration();
const root=await request('/api/tags',{name:'线条',slug:'lines'});const child=await request('/api/tags',{name:'白色',slug:'white'});