Files
stack/packages/model-proxy/tests/settings.test.mjs

25 lines
6.2 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, readFileSync, mkdirSync, lstatSync, symlinkSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createHash } from 'node:crypto';
import { validateProxy, loadProxy, installPaths, initializeProxy, verifyInstall } from '../src/settings.mjs';
const digest=x=>createHash('sha256').update(x).digest('hex');
const pin={version:'8.0.42',binary:{sha256:digest('fake binary')},panel:{sha256:digest('fake panel')}};
function fixture(){const root=mkdtempSync(join(tmpdir(),'p1-'));const configDir=join(root,'config'),dataRoot=join(root,'data');mkdirSync(configDir);mkdirSync(dataRoot);writeFileSync(join(configDir,'config.json'),'unchanged');return {root,configDir,dataRoot};}
function installed(f){const p=installPaths(f.dataRoot,pin);mkdirSync(join(p.dir,'static'),{recursive:true});writeFileSync(p.binary,'fake binary',{mode:0o755});writeFileSync(p.panel,'fake panel');return p;}
const code=c=>e=>e.code===c;
test('absent proxy settings preserves no-proxy contract; present settings validate pinned path',()=>{const f=fixture();assert.equal(loadProxy(f.configDir,f.dataRoot),undefined);const v={port:18432,binary:installPaths(f.dataRoot).binary};assert.deepEqual(validateProxy(v,f.dataRoot),v);writeFileSync(join(f.configDir,'proxy.json'),JSON.stringify(v));assert.deepEqual(loadProxy(f.configDir,f.dataRoot),v);});
for(const [name,value]of Object.entries({unknown:{port:18432,binary:'/bad',extra:1},missing:{port:18432},low:{port:1023,binary:'/bad'},high:{port:65536,binary:'/bad'},host:{port:8317,binary:'/bad'},string:{port:'18432',binary:'/bad'},binary:{port:18432,binary:'/bad'},null:null}))test(`proxy-config-invalid: ${name}`,()=>{const f=fixture();const input=value&&name!=='binary'&&Object.hasOwn(value,'binary')?{...value,binary:installPaths(f.dataRoot).binary}:value;assert.throws(()=>validateProxy(input,f.dataRoot),code('proxy-config-invalid'));});
test('proxy-config-invalid: malformed or symlink settings refuse',()=>{const f=fixture(),p=join(f.configDir,'proxy.json');writeFileSync(p,'{');assert.throws(()=>loadProxy(f.configDir,f.dataRoot),code('proxy-config-invalid'));const g=fixture();symlinkSync(p,join(g.configDir,'proxy.json'));assert.throws(()=>loadProxy(g.configDir,g.dataRoot),code('proxy-config-invalid'));});
test('proxy-install-missing: init requires fetch before any state is written',async()=>{const f=fixture();await assert.rejects(initializeProxy({...f,pin,key:'p1-management',port:18432}),code('proxy-install-missing'));assert.equal(readFileSync(join(f.configDir,'config.json'),'utf8'),'unchanged');});
test('proxy-install-invalid: binary or panel mutation refuses',()=>{const f=fixture(),p=installed(f);writeFileSync(p.binary,'tampered');assert.throws(()=>verifyInstall(f.dataRoot,pin),code('proxy-install-invalid'));});
test('proxy-key-invalid: empty, multiline and too-long key refuse before creation',async()=>{for(const key of ['', 'a\nb','x'.repeat(4097),'bad-\u2603-key']){const f=fixture();installed(f);await assert.rejects(initializeProxy({...f,pin,key,port:18432}),code('proxy-key-invalid'));}});
test('exclusive initialization writes private sentinel config, never rewrites config.json or exposes key',async()=>{const f=fixture();installed(f);const key='p1-management';const result=await initializeProxy({...f,pin,key});assert.ok(result.port>=1024&&result.port!==8317);assert.equal(JSON.stringify(result).includes(key),false);const conf=JSON.parse(readFileSync(join(f.dataRoot,'proxy/config.yaml'),'utf8'));assert.equal(conf.server.host,'127.0.0.1');assert.equal(conf.management['allow-remote'],false);assert.equal(conf.management['disable-auto-update-panel'],true);assert.equal(conf.routing.strategy,'fill-first');assert.equal(conf.routing.retry['request-retry'],0);assert.match(conf.access['api-keys'][0],/^msk-.*-sentinel-/);for(const file of [join(f.configDir,'secrets/proxy-management.key'),join(f.dataRoot,'proxy/config.yaml')])assert.equal(lstatSync(file).mode&0o777,0o600);for(const dir of ['proxy','proxy/auth','proxy/logs'])assert.equal(lstatSync(join(f.dataRoot,dir)).mode&0o777,0o700);assert.equal(readFileSync(join(f.configDir,'config.json'),'utf8'),'unchanged');await assert.rejects(initializeProxy({...f,pin,key}),code('proxy-already-initialized'));});
test('proxy-port-in-use: init refuses occupied loopback without files',async()=>{const {createServer}=await import('node:net');const server=createServer();await new Promise(r=>server.listen(0,'127.0.0.1',r));try{const f=fixture();installed(f);await assert.rejects(initializeProxy({...f,pin,key:'p1-management',port:server.address().port}),code('proxy-port-in-use'));}finally{server.close();}});
test('proxy-init-busy: existing lock refuses without touching it',async()=>{const f=fixture();installed(f);writeFileSync(join(f.configDir,'.proxy-init.lock'),'owner');await assert.rejects(initializeProxy({...f,pin,key:'p1-management'}),code('proxy-init-busy'));assert.equal(readFileSync(join(f.configDir,'.proxy-init.lock'),'utf8'),'owner');});
test('proxy-init-failed: symlink or non-private secrets directory refuses without changing it',async()=>{const f=fixture();installed(f);mkdirSync(join(f.configDir,'secrets'),{mode:0o755});await assert.rejects(initializeProxy({...f,pin,key:'p1-management'}),code('proxy-init-failed'));assert.equal(lstatSync(join(f.configDir,'secrets')).mode&0o777,0o755);});
test('proxy-already-initialized: each existing target is preserved and never followed',async()=>{for(const target of ['proxy','key','settings']){const f=fixture();installed(f);let file;if(target==='proxy'){file=join(f.dataRoot,'proxy');mkdirSync(file);writeFileSync(join(file,'marker'),'old');}else if(target==='key'){mkdirSync(join(f.configDir,'secrets'),{mode:0o700});file=join(f.configDir,'secrets/proxy-management.key');writeFileSync(file,'old');}else{file=join(f.configDir,'proxy.json');writeFileSync(file,'old');}await assert.rejects(initializeProxy({...f,pin,key:'p1-management'}),code('proxy-already-initialized'));assert.equal(readFileSync(target==='proxy'?join(file,'marker'):file,'utf8'),'old');}});
test('proxy-install-invalid: modified panel also refuses',()=>{const f=fixture(),p=installed(f);writeFileSync(p.panel,'different');assert.throws(()=>verifyInstall(f.dataRoot,pin),code('proxy-install-invalid'));});