|
|
@@ -0,0 +1,55 @@
|
|
|
+// @vitest-environment node
|
|
|
+import { mkdtempSync, rmSync } from 'node:fs'
|
|
|
+import { tmpdir, hostname } from 'node:os'
|
|
|
+import { join } from 'node:path'
|
|
|
+import request from 'supertest'
|
|
|
+import { afterEach, describe, expect, it } from 'vitest'
|
|
|
+import { WorkspaceStore } from '../../src/server/workspaceStore.js'
|
|
|
+import { readServerIdentity, updateServerIdentity } from '../../src/server/serverIdentity.js'
|
|
|
+import { createAuthenticatedApplication, createUserAuthenticatedApplication } from './authenticatedApplication.js'
|
|
|
+import type { ServerConfig } from '../../src/server/config.js'
|
|
|
+
|
|
|
+const homes: string[] = []
|
|
|
+const close: Array<() => void> = []
|
|
|
+const home = () => { const path=mkdtempSync(join(tmpdir(),'server-name-'));homes.push(path);return path }
|
|
|
+const store = (path=home()) => { const s=new WorkspaceStore(path);close.push(()=>s.close());return s }
|
|
|
+afterEach(()=>{for(const f of close.splice(0).reverse())f();for(const h of homes.splice(0))rmSync(h,{recursive:true,force:true})})
|
|
|
+describe('server identity',()=>{
|
|
|
+ it('persists one name across accounts and restarts without changing the server id',()=>{
|
|
|
+ const path=home(),s=store(path),initial=readServerIdentity(s)
|
|
|
+ const saved=updateServerIdentity(s,{name:' 家里的 Mac ',expectedRevision:0})
|
|
|
+ expect(saved).toEqual({...initial,name:'家里的 Mac',displayName:'家里的 Mac',revision:1})
|
|
|
+ expect(readServerIdentity(store(path))).toEqual(saved)
|
|
|
+ expect(updateServerIdentity(s,{name:'家里的 Mac',expectedRevision:1})).toEqual(saved)
|
|
|
+ })
|
|
|
+ it('clears to the server hostname and rejects a stale edit',()=>{
|
|
|
+ const s=store();updateServerIdentity(s,{name:'办公室',expectedRevision:0})
|
|
|
+ expect(()=>updateServerIdentity(s,{name:'旧草稿',expectedRevision:0})).toThrow(/其他端更新/)
|
|
|
+ expect(readServerIdentity(s).name).toBe('办公室')
|
|
|
+ expect(updateServerIdentity(s,{name:' ',expectedRevision:1})).toMatchObject({name:'',displayName:hostname(),revision:2})
|
|
|
+ })
|
|
|
+ it('rejects invalid values without changing the persisted revision',()=>{
|
|
|
+ const s=store()
|
|
|
+ for(const input of [{name:3},{name:'a'.repeat(101)},{name:'a\nb'},{name:'a\u0000b'},{name:'ok',expectedRevision:-1},{name:'ok',unexpected:1}])expect(()=>updateServerIdentity(s,input)).toThrow()
|
|
|
+ expect(readServerIdentity(s).revision).toBe(0)
|
|
|
+ })
|
|
|
+ it('keeps independent servers separate',()=>{
|
|
|
+ const a=store(),b=store();updateServerIdentity(a,{name:'A'})
|
|
|
+ expect(()=>updateServerIdentity(b,{name:'错误目标',expectedServerId:readServerIdentity(a).serverId})).toThrow(/服务器连接已切换/)
|
|
|
+ expect(readServerIdentity(b).name).toBe('');expect(readServerIdentity(a).serverId).not.toBe(readServerIdentity(b).serverId)
|
|
|
+ })
|
|
|
+ it('allows authenticated reads, admin saves, and includes updates in existing poll responses',async()=>{
|
|
|
+ const path=home()
|
|
|
+ const config:ServerConfig={home:path,host:'127.0.0.1',port:15300,upstream:new URL('http://127.0.0.1:19124'),allowedHosts:new Set(),mediaRoot:path,attachmentsRoot:path,imagesRoot:path,mediaOwner:'fixture',allowInsecureLan:false,insecureLan:false,production:false}
|
|
|
+ const fetchImpl=async()=>new Response(JSON.stringify({state:'ready',profiles:[],auth_required:false}),{headers:{'Content-Type':'application/json'}})
|
|
|
+ const admin=createAuthenticatedApplication({config,fetchImpl:fetchImpl as typeof fetch});close.push(()=>admin.close())
|
|
|
+ const agent=request.agent(admin.app.callback())
|
|
|
+ const bootstrap=await agent.get('/api/app/bootstrap').set('Host','127.0.0.1:15300').expect(200)
|
|
|
+ const saved=await agent.put('/api/app/server-identity').set('Host','127.0.0.1:15300').set('Origin','http://127.0.0.1:15300').set('X-CSRF-Token',bootstrap.body.csrfToken).send({name:'共享服务器',expectedRevision:0}).expect(200)
|
|
|
+ expect((await agent.get('/api/app/events?after=0').set('Host','127.0.0.1:15300').expect(200)).body.serverIdentity).toEqual(saved.body)
|
|
|
+ const member=createUserAuthenticatedApplication({config,fetchImpl:fetchImpl as typeof fetch});close.push(()=>member.close())
|
|
|
+ const reader=request.agent(member.app.callback()),token=(await reader.get('/api/app/bootstrap').set('Host','127.0.0.1:15300')).body.csrfToken
|
|
|
+ expect((await reader.get('/api/app/server-identity').set('Host','127.0.0.1:15300').expect(200)).body).toEqual(saved.body)
|
|
|
+ await reader.put('/api/app/server-identity').set('Host','127.0.0.1:15300').set('Origin','http://127.0.0.1:15300').set('X-CSRF-Token',token).send({name:'无权限'}).expect(403)
|
|
|
+ })
|
|
|
+})
|