File size: 4,600 Bytes
7646924
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import test from 'node:test';
import assert from 'node:assert/strict';
import {spawnSync} from 'node:child_process';
import {readFileSync} from 'node:fs';
import {buildPayload, previewPayload, pythonExample, parseAnswer, requestDecision, ENDPOINT} from '../api.mjs';
import {words, examples} from '../locales.mjs';

const make = (extra={}) => buildPayload({model:'valen-2b',mode:'predicate',question:'Is it positive?',context:'Great service!',options:'Low\nHigh',...extra});

test('native predicate, choice and zero-based score request contracts',()=>{
  const p=make();assert.equal(p.questions[0].type,'predicate');assert.equal(p.temperature,1);
  assert.equal(p.questions[0].choices,undefined);
  assert.deepEqual(make({mode:'choice'}).questions[0].choices[0],{value:'option_1',description:'Low'});
  assert.deepEqual(make({mode:'score'}).questions[0].levels[1],{label:'1',description:'High'});
  assert.equal(make({context:''}).input,'Is it positive?');
});
test('rejects invalid input before network calls',()=>{
  for(const value of [{model:'invalid'},{question:' '},{context:'x'.repeat(4001)},{mode:'choice',options:'one'},{mode:'score',options:Array.from({length:9},(_,i)=>String(i)).join('\n')},{image:'https://example.com/image.jpg'}])assert.throws(()=>make(value));
});
test('images use inline data; preview omits bytes without changing request',()=>{
  const p=make({image:'data:image/jpeg;base64,Ymx1ZQ=='});
  assert.equal(p.input[0].content[1].type,'input_image');
  assert(!JSON.stringify(previewPayload(p)).includes('Ymx1ZQ=='));
  assert(p.input[0].content[1].image_url.includes('Ymx1ZQ=='));
  assert(pythonExample(p).includes('Image.open("image.jpg")'));
});
test('generated Python preserves literal user text and is syntactically valid',()=>{
  const p=make({context:'Quotes " and \'\'\', backslash \\, newlines\n中文 \u2028 and </script>'});
  const script=`import ast,json,sys\ndata=json.load(sys.stdin)\ntree=ast.parse(data['code'])\nnode=next(n for n in tree.body if isinstance(n,ast.Assign) and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='payload')\nassert json.loads(ast.literal_eval(node.value.args[0]))==data['payload']\n`;
  const result=spawnSync('python3',['-c',script],{input:JSON.stringify({code:pythonExample(p),payload:p}),encoding:'utf8'});
  assert.equal(result.status,0,result.stderr);
});
test('native results preserve optional confidence separately',()=>{
  const a=parseAnswer({answers:[{name:'q0',type:'predicate',probability:.8}]},make());
  assert.equal(a.title,'yes');assert(Math.abs(a.rows[1].probability-.2)<1e-10);assert.equal(a.confidence,undefined);
  const c=parseAnswer({answers:[{name:'q0',type:'choice',choice:'option_2',confidence:.4,probabilities:[{value:'option_1',probability:.1},{value:'option_2',probability:.9}]}]},make({mode:'choice'}));
  assert.equal(c.title,'High');assert.equal(c.confidence,.4);
  const s=parseAnswer({answers:[{name:'q0',type:'score',score:.9,probabilities:[{value:0,probability:.1},{value:1,probability:.9}]}]},make({mode:'score'}));
  assert.equal(s.title,'0.90 / 1');
});
test('malformed results cannot render as successful answers',()=>{
  for(const body of [null,{answers:[]},{answers:[null]},{answers:[{name:'q0',type:'predicate',probability:NaN}]},{answers:[{name:'wrong',type:'predicate',probability:.8}]}])assert.throws(()=>parseAnswer(body,make()),/response/);
  assert.throws(()=>parseAnswer({answers:[{name:'q0',type:'choice',choice:'option_1',probabilities:[{value:'option_1',probability:.5},{value:'option_1',probability:.5}]}]},make({mode:'choice'})),/response/);
});
test('browser request uses real origin, no credentials or automatic retries',async()=>{
  let calls=0;
  await assert.rejects(requestDecision(make(),undefined,async(url,options)=>{
    calls++;assert.equal(url,ENDPOINT);assert.equal(options.credentials,'omit');assert.equal(options.headers.Origin,undefined);assert.equal(options.redirect,'error');
    return {ok:false,status:429};
  }),/http_429/);assert.equal(calls,1);
  await assert.rejects(requestDecision(make(),undefined,async()=>({ok:true,json:async()=>{throw new Error('invalid json');}})),/response/);
});
test('both locales cover UI labels and all examples create valid payloads',()=>{
  const html=readFileSync(new URL('../index.html',import.meta.url),'utf8');
  for(const locale of ['en','zh']){
    for(const [,key] of html.matchAll(/data-(?:i18n|placeholder)="([^"]+)"/g))assert.equal(typeof words[locale][key],'string',`${locale}:${key}`);
    for(const [mode,question,context,options] of examples[locale])make({mode,question,context,options});
  }
});