char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
e21152dc9
char/aliceai-t5-moe
1import { MCP_SERVER_ID_PREFIX } from '$lib/constants' ; 2import { parseMcpServerSettings } from '$lib/utils/mcp' ; 3import { describe , expect , it , vi } from 'vitest' ; 4 5/** 6* Tests for the mcpServers settings parser. 7* 8* The parser has to be resilient to anything that may live in the 9* user's localStorage: malformed JSON, wrong shapes, missing fields, 10* falsy-but-not-zero numbers, and entry arrays that have been mutated 11* by the user via the settings form. 12*/ 13describe ( 'parseMcpServerSettings' , () => { 14it ( 'returns an empty array for falsy or whitespace-only input' , () => { 15expect ( parseMcpServerSettings ( null )). toEqual ([]); 16expect ( parseMcpServerSettings ( undefined )). toEqual ([]); 17expect ( parseMcpServerSettings ( '' )). toEqual ([]); 18expect ( parseMcpServerSettings ( ' ' )). toEqual ([]); 19}); 20 21it ( 'returns an empty array and logs a warning for invalid JSON strings' , () => { 22const warn = vi . spyOn ( console , 'warn' ). mockImplementation (() => {}); 23 24expect ( parseMcpServerSettings ( '{not-json' )). toEqual ([]); 25expect ( warn ). toHaveBeenCalled (); 26 27warn . mockRestore (); 28}); 29 30it ( 'returns an empty array for valid JSON that is not an array' , () => { 31expect ( parseMcpServerSettings ( '"plain-string"' )). toEqual ([]); 32expect ( parseMcpServerSettings ( '{"id":"foo"}' )). toEqual ([]); 33expect ( parseMcpServerSettings ( '42' )). toEqual ([]); 34expect ( parseMcpServerSettings ( 'null' )). toEqual ([]); 35}); 36 37it ( 'drops entries with no parseable id and substitutes a stable fallback' , () => { 38const parsed = parseMcpServerSettings ( 39JSON . stringify ([{ enabled :true , url :'https://a.test' }, { url :'https://b.test' }]) 40); 41 42expect ( parsed ). toHaveLength ( 2 ); 43expect ( parsed [ 0 ]?. id ). toBe ( ` ${ MCP_SERVER_ID_PREFIX } -1` ); 44expect ( parsed [ 1 ]?. id ). toBe ( ` ${ MCP_SERVER_ID_PREFIX } -2` ); 45}); 46 47it ( 'reuses the first id when it is present and falls back only for missing ones' , () => { 48const parsed = parseMcpServerSettings ( 49JSON . stringify ([ 50{ id :'custom-1' , url :'https://a.test' }, 51{ url :'https://b.test' }, 52{ id :'custom-3' , url :'https://c.test' } 53]) 54); 55 56expect ( parsed [ 0 ]?. id ). toBe ( 'custom-1' ); 57expect ( parsed [ 1 ]?. id ). toBe ( ` ${ MCP_SERVER_ID_PREFIX } -2` ); 58expect ( parsed [ 2 ]?. id ). toBe ( 'custom-3' ); 59}); 60 61it ( 'does not emit a per-server timeout, the request timeout is a live global setting' , () => { 62// A stored per-server requestTimeoutSeconds was never editable in 63// any UI and froze the global setting at server creation time, 64// making the Settings value a no-op for existing servers. The 65// parser drops the field so the global applies live everywhere. 66const parsed = parseMcpServerSettings ( 67JSON . stringify ([{ id :'a' , requestTimeoutSeconds :45 , url :'https://a.test' }]) 68); 69 70expect ( parsed [ 0 ]). not . toHaveProperty ( 'requestTimeoutSeconds' ); 71}); 72 73it ( 'treats whitespace-only headers strings as undefined' , () => { 74const parsed = parseMcpServerSettings ( 75JSON . stringify ([ 76{ headers :' ' , id :'a' , url :'https://a.test' }, 77{ headers :'{"X-Foo":"bar"}' , id :'b' , url :'https://b.test' } 78]) 79); 80 81// The parser trims headers and coerces empty/whitespace to undefined. 82expect ( parsed [ 0 ]?. headers ). toBeUndefined (); 83expect ( parsed [ 1 ]?. headers ). toBe ( '{"X-Foo":"bar"}' ); 84}); 85 86it ( 'defaults coercion for booleans (undefined -> false, true -> true)' , () => { 87const parsed = parseMcpServerSettings ( 88JSON . stringify ([ 89{ id :'a' , url :'https://a.test' }, 90{ enabled :true , id :'b' , url :'https://b.test' }, 91{ enabled :false , id :'c' , url :'https://c.test' }, 92{ id :'d' , url :'https://d.test' , useProxy :true } 93]) 94); 95 96expect ( parsed [ 0 ]?. enabled ). toBe ( false ); 97expect ( parsed [ 1 ]?. enabled ). toBe ( true ); 98expect ( parsed [ 2 ]?. enabled ). toBe ( false ); 99expect ( parsed [ 0 ]?. useProxy ). toBe ( false ); 100expect ( parsed [ 3 ]?. useProxy ). toBe ( true ); 101}); 102 103it ( 'keeps disabled entries in the list, enabled is state and never a visibility filter' , () => { 104// Regression guard for issue #25625: filtering the server list on 105// `enabled` hides a toggled-off server from every UI surface with 106// no way to re-enable it. Any list derived from this parser must 107// contain disabled entries. 108const parsed = parseMcpServerSettings ( 109JSON . stringify ([ 110{ enabled :true , id :'on' , url :'https://on.test' }, 111{ enabled :false , id :'off' , url :'https://off.test' } 112]) 113); 114 115expect ( parsed . map (( entry ) => entry . id )). toEqual ([ 'on' , 'off' ]); 116expect ( parsed [ 1 ]?. enabled ). toBe ( false ); 117}); 118 119it ( 'preserves input order when mapping entries' , () => { 120const source = [ 121{ id :'gamma' , url :'https://c.test' }, 122{ id :'alpha' , url :'https://a.test' }, 123{ id :'beta' , url :'https://b.test' } 124]; 125const parsed = parseMcpServerSettings ( JSON . stringify ( source )); 126 127expect ( parsed . map (( entry ) => entry . id )). toEqual ([ 'gamma' , 'alpha' , 'beta' ]); 128}); 129 130it ( 'passes non-string raw input through the JSON-equality path' , () => { 131const parsed = parseMcpServerSettings ([ 132{ id :'a' , url :'https://a.test' }, 133{ enabled :true , id :'b' , url :'https://b.test' } 134]); 135 136expect ( parsed ). toHaveLength ( 2 ); 137expect ( parsed [ 0 ]?. id ). toBe ( 'a' ); 138expect ( parsed [ 1 ]?. enabled ). toBe ( true ); 139}); 140 141it ( 'coerces non-string url values to an empty string rather than throwing' , () => { 142const parsed = parseMcpServerSettings ( 143JSON . stringify ([{ id :'a' , url :42 }, { id :'b' }, { id :'c' , url :'https://c.test' }]) 144); 145 146expect ( parsed [ 0 ]?. url ). toBe ( '' ); 147expect ( parsed [ 1 ]?. url ). toBe ( '' ); 148expect ( parsed [ 2 ]?. url ). toBe ( 'https://c.test' ); 149}); 150});