char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
521a64cd0
char/aliceai-t5-moe
1import { afterEach , beforeAll , beforeEach , describe , expect , it } from 'vitest' ; 2 3// node env unit project has no DOM, install a minimal localStorage backed by a Map 4beforeAll (() => { 5const store = new Map < string , string >(); 6const polyfill :Storage = { 7clear :() => store . clear (), 8getItem :( k ) => ( store . has ( k ) ?store . get ( k ) ! :null ), 9key :( i ) => Array . from ( store . keys ())[ i ] ?? null , 10get length () { 11return store . size ; 12}, 13removeItem :( k ) => { 14store . delete ( k ); 15}, 16setItem :( k , v ) => { 17store . set ( k , String ( v )); 18} 19}; 20 21( globalThis as unknown as { localStorage :Storage }). localStorage = polyfill ; 22}); 23 24import { STREAM_RESUME_LOCALSTORAGE_KEY_PREFIX } from '$lib/constants' ; 25import { ChatService } from '$lib/services/chat.service' ; 26 27describe ( 'ChatService stream resume' , () => { 28beforeEach (() => { 29localStorage . clear (); 30}); 31afterEach (() => { 32localStorage . clear (); 33}); 34 35it ( 'returns null when no state exists for the conversation' , () => { 36expect ( ChatService . getStreamState ( 'conv-a' )). toBeNull (); 37}); 38 39it ( 'saves and reads back the byte count' , () => { 40ChatService . saveStreamState ( 'conv-a' , 4242 ); 41const got = ChatService . getStreamState ( 'conv-a' ); 42 43expect ( got ). not . toBeNull (); 44expect ( got ! . bytesReceived ). toBe ( 4242 ); 45expect ( typeof got ! . updatedAt ). toBe ( 'number' ); 46}); 47 48it ( 'overwrites the previous byte count on a new save for the same conversation' , () => { 49ChatService . saveStreamState ( 'conv-a' , 100 ); 50ChatService . saveStreamState ( 'conv-a' , 200 ); 51const got = ChatService . getStreamState ( 'conv-a' ); 52 53expect ( got ! . bytesReceived ). toBe ( 200 ); 54}); 55 56it ( 'keeps states for distinct conversations isolated' , () => { 57ChatService . saveStreamState ( 'conv-a' , 10 ); 58ChatService . saveStreamState ( 'conv-b' , 20 ); 59expect ( ChatService . getStreamState ( 'conv-a' ) ! . bytesReceived ). toBe ( 10 ); 60expect ( ChatService . getStreamState ( 'conv-b' ) ! . bytesReceived ). toBe ( 20 ); 61}); 62 63it ( 'clears the state for a given conversation' , () => { 64ChatService . saveStreamState ( 'conv-a' , 10 ); 65ChatService . clearStreamState ( 'conv-a' ); 66expect ( ChatService . getStreamState ( 'conv-a' )). toBeNull (); 67}); 68 69it ( 'ignores empty conversation id on save' , () => { 70ChatService . saveStreamState ( '' , 1 ); 71expect ( ChatService . getStreamState ( '' )). toBeNull (); 72}); 73 74it ( 'returns null on corrupted storage payload' , () => { 75localStorage . setItem ( ` ${ STREAM_RESUME_LOCALSTORAGE_KEY_PREFIX } conv-a` , '{not-json' ); 76expect ( ChatService . getStreamState ( 'conv-a' )). toBeNull (); 77}); 78 79it ( 'persists the model alongside the byte count' , () => { 80ChatService . saveStreamState ( 'conv-a' , 10 , 'model-x' ); 81expect ( ChatService . getStreamState ( 'conv-a' ) ! . model ). toBe ( 'model-x' ); 82}); 83 84it ( 'stores a null model when none is provided' , () => { 85ChatService . saveStreamState ( 'conv-a' , 10 ); 86expect ( ChatService . getStreamState ( 'conv-a' ) ! . model ). toBeNull (); 87}); 88 89it ( 'overwrites the model on a new save for the same conversation' , () => { 90ChatService . saveStreamState ( 'conv-a' , 10 , 'model-x' ); 91ChatService . saveStreamState ( 'conv-a' , 20 , 'model-y' ); 92expect ( ChatService . getStreamState ( 'conv-a' ) ! . model ). toBe ( 'model-y' ); 93}); 94 95describe ( 'throttled saves (per-chunk path)' , () => { 96// unique conversation ids: the throttle tracker is module state and 97// outlives beforeEach's localStorage.clear() 98let counter = 0 ; 99 100const freshConv = () => `conv-throttle- ${ ++ counter } ` ; 101 102it ( 'writes immediately when no write was recorded for the conversation' , () => { 103const conv = freshConv (); 104 105ChatService . saveStreamStateThrottled ( conv , 100 ); 106expect ( ChatService . getStreamState ( conv ) ! . bytesReceived ). toBe ( 100 ); 107}); 108 109it ( 'holds a save pending when it lands inside the interval, flush forces it out' , () => { 110const conv = freshConv (); 111 112ChatService . saveStreamStateThrottled ( conv , 100 ); 113ChatService . saveStreamStateThrottled ( conv , 200 ); 114expect ( ChatService . getStreamState ( conv ) ! . bytesReceived ). toBe ( 100 ); 115 116ChatService . flushStreamState ( conv ); 117expect ( ChatService . getStreamState ( conv ) ! . bytesReceived ). toBe ( 200 ); 118}); 119 120it ( 'flush is a no-op when nothing is pending' , () => { 121const conv = freshConv (); 122 123ChatService . saveStreamStateThrottled ( conv , 100 ); 124ChatService . flushStreamState ( conv ); 125ChatService . flushStreamState ( conv ); 126expect ( ChatService . getStreamState ( conv ) ! . bytesReceived ). toBe ( 100 ); 127}); 128 129it ( 'an immediate save resets the throttle window' , () => { 130const conv = freshConv (); 131 132ChatService . saveStreamStateThrottled ( conv , 100 ); 133ChatService . saveStreamState ( conv , 150 ); 134expect ( ChatService . getStreamState ( conv ) ! . bytesReceived ). toBe ( 150 ); 135 136ChatService . saveStreamStateThrottled ( conv , 200 ); 137expect ( ChatService . getStreamState ( conv ) ! . bytesReceived ). toBe ( 150 ); 138 139ChatService . flushStreamState ( conv ); 140expect ( ChatService . getStreamState ( conv ) ! . bytesReceived ). toBe ( 200 ); 141}); 142 143it ( 'clearStreamState drops the pending throttled state' , () => { 144const conv = freshConv (); 145 146ChatService . saveStreamStateThrottled ( conv , 100 ); 147ChatService . saveStreamStateThrottled ( conv , 200 ); 148ChatService . clearStreamState ( conv ); 149expect ( ChatService . getStreamState ( conv )). toBeNull (); 150 151ChatService . flushStreamState ( conv ); 152expect ( ChatService . getStreamState ( conv )). toBeNull (); 153}); 154}); 155 156describe ( 'resumeStreamIdentity' , () => { 157it ( 'appends the persisted model so the resume key matches the frozen POST identity' , () => { 158ChatService . saveStreamState ( 'conv-a' , 10 , 'model-x' ); 159expect ( 160ChatService . resumeStreamIdentity ( 'conv-a' , ChatService . getStreamState ( 'conv-a' ), 'dropdown' ) 161). toBe ( 'conv-a::model-x' ); 162}); 163 164it ( 'keeps the bare conv id when the persisted model is null' , () => { 165ChatService . saveStreamState ( 'conv-a' , 10 ); 166expect ( 167ChatService . resumeStreamIdentity ( 'conv-a' , ChatService . getStreamState ( 'conv-a' ), 'dropdown' ) 168). toBe ( 'conv-a' ); 169}); 170 171it ( 'falls back to the current model only when no state is persisted' , () => { 172expect ( ChatService . resumeStreamIdentity ( 'conv-a' , null , 'dropdown' )). toBe ( 'conv-a::dropdown' ); 173}); 174 175it ( 'ignores the fallback when a state exists, the persisted value is authoritative' , () => { 176ChatService . saveStreamState ( 'conv-a' , 10 , 'model-x' ); 177expect ( 178ChatService . resumeStreamIdentity ( 'conv-a' , ChatService . getStreamState ( 'conv-a' ), 'dropdown' ) 179). toBe ( 'conv-a::model-x' ); 180}); 181 182it ( 'falls back when a legacy state has no model field' , () => { 183localStorage . setItem ( 184` ${ STREAM_RESUME_LOCALSTORAGE_KEY_PREFIX } conv-a` , 185JSON . stringify ({ bytesReceived :10 , updatedAt :1 }) 186); 187expect ( 188ChatService . resumeStreamIdentity ( 'conv-a' , ChatService . getStreamState ( 'conv-a' ), 'dropdown' ) 189). toBe ( 'conv-a::dropdown' ); 190}); 191}); 192});