diff --git a/apps/waterfall-wiki/tests/guidance-integration.ts b/apps/waterfall-wiki/tests/guidance-integration.ts index a18c746..ef4d4a2 100644 --- a/apps/waterfall-wiki/tests/guidance-integration.ts +++ b/apps/waterfall-wiki/tests/guidance-integration.ts @@ -1,8 +1,10 @@ import assert from 'node:assert/strict'; import {randomUUID} from 'node:crypto'; +import {newToken,tokenDigest,ACCOUNT_COOKIE} from '../src/lib/account-crypto'; +import {guidanceXml} from '../src/content/guides/catalog'; import {db} from '../src/lib/db'; import {publishDocument,resolveDocument,withdrawDocument,discoverDocuments} from '../src/lib/guidance-store'; -export async function verifyGuidancePublishing(){ +export async function verifyGuidancePublishing(base:string){ const owner=randomUUID(),other=randomUUID(),id='user-'+owner+'-document'; for(const uid of [owner,other])await db().query("INSERT INTO account_users(id,email,password_hash,verified_at) VALUES($1,$2,'test',now())",[uid,uid+'@example.com']); const doc={id,revision:1,kind:'context',title:'实际数据库文档',summary:'摘要',applies:'条件',body:'正文',steps:[],checks:[],links:[],tags:[]}; @@ -14,4 +16,21 @@ export async function verifyGuidancePublishing(){ assert.equal((await resolveDocument(id,1))?.body,'正文');assert.equal(await resolveDocument(id,999),null); await assert.rejects(()=>db().query('UPDATE guidance_revisions SET content=$2 WHERE document_id=$1',[id,{}]),/immutable/); assert.equal(await withdrawDocument(other,id),false);assert.equal(await withdrawDocument(owner,id),true);assert.equal(await resolveDocument(id),null);assert.ok(await resolveDocument(id,1));assert.ok(!(await discoverDocuments()).some(d=>d.id===id)); + const token=newToken();await db().query("INSERT INTO account_tokens(digest,user_id,kind,expires_at) VALUES($1,$2,'session',now()+interval '1 hour')",[tokenDigest(token),owner]); + const headers={'Content-Type':'application/json',origin:process.env.ACCOUNT_PUBLIC_ORIGIN||base,cookie:ACCOUNT_COOKIE+'='+token}; + const httpDoc={...doc,id:'user-'+owner+'-http',tags:['往返标签']}; + const request=(path:string,method:string,body?:unknown,extra:Record={})=>fetch(base+'/api/documents'+path,{method,headers:{...headers,...extra},...(body?{body:typeof body==='string'?body:JSON.stringify(body)}:{})}); + assert.equal((await request('','POST',httpDoc,{cookie:''})).status,401); + assert.equal((await request('','POST',httpDoc,{origin:'https://untrusted.example'})).status,403); + const preview=await request('/preview','POST',httpDoc);assert.equal(preview.status,200,await preview.clone().text());assert.equal(await resolveDocument(httpDoc.id,1),null); + const created=await request('','POST',guidanceXml(httpDoc),{'Content-Type':'application/xml'});assert.equal(created.status,201,await created.clone().text()); + assert.equal((await request('','POST',httpDoc)).status,409); + assert.equal((await request('','POST',{...httpDoc,id:'user-'+other+'-http'})).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>/); + const page=await fetch(base+'/documents/'+httpDoc.id+'?revision=1');assert.equal(page.status,200);assert.ok((await page.text()).includes('实际数据库文档')); + assert.equal((await request('/'+httpDoc.id,'DELETE',undefined,{cookie:''})).status,401); + assert.equal((await request('/'+httpDoc.id,'DELETE')).status,200); + assert.equal((await fetch(base+'/api/documents/'+httpDoc.id)).status,404); + assert.equal((await fetch(base+'/api/documents/'+httpDoc.id+'?revision=1')).status,200); + } diff --git a/apps/waterfall-wiki/tests/integration.test.ts b/apps/waterfall-wiki/tests/integration.test.ts index ccd36a7..3ed898a 100644 --- a/apps/waterfall-wiki/tests/integration.test.ts +++ b/apps/waterfall-wiki/tests/integration.test.ts @@ -30,7 +30,7 @@ test('real PostgreSQL + S3 notes, flat tags, resource sharing and video processi let ready=false;for(let i=0;i<60;i++){try{if((await fetch(base+'/api/health')).ok){ready=true;break;}}catch{}await setTimeout(250);}assert.ok(ready); for(const route of ['/','/images','/videos','/audio','/articles','/motion','/library/import','/tools/doodle','/tools/skills','/tools/skills/street-people','/tools/skills/shadow-puppet'])assert.equal((await fetch(base+route)).status,200,route); await verifyEmailAccounts(); - await verifyGuidancePublishing(); + await verifyGuidancePublishing(base); await verifyFlatNoteMigration(); const root=await request('/api/tags',{name:'线条',slug:'lines'});const child=await request('/api/tags',{name:'白色',slug:'white'}); const sameTag=await request('/api/tags',{name:'#线条'});assert.equal(sameTag.id,root.id,'Hashtag creation reuses the globally unique tag');