Update coverage reports and fix Ollama client abort handling
This commit is contained in:
@@ -23,16 +23,16 @@
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">90.36% </span>
|
||||
<span class="strong">92.55% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>197/218</span>
|
||||
<span class='fraction'>199/215</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">80.28% </span>
|
||||
<span class="strong">85.71% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>57/71</span>
|
||||
<span class='fraction'>60/70</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">92.15% </span>
|
||||
<span class="strong">94.52% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>188/204</span>
|
||||
<span class='fraction'>190/201</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -565,22 +565,22 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -596,11 +596,11 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-yes">36x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -640,8 +640,8 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">136x</span>
|
||||
<span class="cline-any cline-yes">23x</span>
|
||||
<span class="cline-any cline-yes">146x</span>
|
||||
<span class="cline-any cline-yes">28x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -649,71 +649,71 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">22x</span>
|
||||
<span class="cline-any cline-yes">27x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">22x</span>
|
||||
<span class="cline-any cline-yes">27x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">22x</span>
|
||||
<span class="cline-any cline-yes">22x</span>
|
||||
<span class="cline-any cline-yes">27x</span>
|
||||
<span class="cline-any cline-yes">27x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">282x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-yes">262x</span>
|
||||
<span class="cline-any cline-yes">262x</span>
|
||||
<span class="cline-any cline-yes">109x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">153x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">247x</span>
|
||||
<span class="cline-any cline-yes">247x</span>
|
||||
<span class="cline-any cline-yes">104x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">143x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">143x</span>
|
||||
<span class="cline-any cline-yes">143x</span>
|
||||
<span class="cline-any cline-yes">153x</span>
|
||||
<span class="cline-any cline-yes">153x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">128x</span>
|
||||
<span class="cline-any cline-yes">3656x</span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-yes">133x</span>
|
||||
<span class="cline-any cline-yes">3661x</span>
|
||||
<span class="cline-any cline-yes">24x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">262x</span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">282x</span>
|
||||
<span class="cline-any cline-yes">43x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -787,59 +787,61 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">138x</span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-yes">292x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">146x</span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">158x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-yes">21x</span>
|
||||
<span class="cline-any cline-yes">21x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-yes">21x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-yes">21x</span>
|
||||
<span class="cline-any cline-yes">21x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -852,7 +854,7 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -873,20 +875,20 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -895,109 +897,107 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">17x</span>
|
||||
<span class="cline-any cline-yes">17x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">17x</span>
|
||||
<span class="cline-any cline-yes">17x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">17x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">17x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -1011,8 +1011,8 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-yes">21x</span>
|
||||
<span class="cline-any cline-yes">21x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -1185,12 +1185,12 @@ export class ChatView extends ItemView {
|
||||
|
||||
// Re-attach streaming message if it exists
|
||||
const streamingMessage = messagesSnapshot.find((msg) => msg.isStreaming);
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (streamingMessage && this.lastMessageEl) {
|
||||
const existingStreamingEl = <span class="cstat-no" title="statement not covered" >container.querySelector(</span>
|
||||
if (streamingMessage && this.lastMessageEl) {
|
||||
const existingStreamingEl = container.querySelector(
|
||||
`.ollama-message[data-msg-id="${streamingMessage.id}"]`
|
||||
);
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!existingStreamingEl) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > container.appendChild(this.lastMessageEl);</span>
|
||||
if (!existingStreamingEl) {
|
||||
container.appendChild(this.lastMessageEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1306,14 +1306,16 @@ export class ChatView extends ItemView {
|
||||
<span class="cstat-no" title="statement not covered" > context = context.substring(0, MAX_CONTEXT_LENGTH) + '\n\n... (truncated)';</span>
|
||||
}
|
||||
|
||||
const systemContent = context
|
||||
? <span class="branch-0 cbranch-no" title="branch not covered" >`You are a helpful assistant.\n\nRelevant vault context:\n${context}`</span>
|
||||
: 'You are a helpful assistant.';
|
||||
const systemMessage: OllamaMessage = {
|
||||
role: 'system',
|
||||
content: 'You are a helpful assistant.',
|
||||
content: systemContent,
|
||||
};
|
||||
const userContent = context ? <span class="branch-0 cbranch-no" title="branch not covered" >`${context}\n\n${userMessage}` </span>: userMessage;
|
||||
const userMessageWithContext: OllamaMessage = {
|
||||
role: 'user',
|
||||
content: userContent,
|
||||
content: userMessage,
|
||||
};
|
||||
|
||||
const messages: OllamaMessage[] = [
|
||||
@@ -1421,9 +1423,9 @@ export class ChatView extends ItemView {
|
||||
for (const result of settledResults) {
|
||||
if (result.status === 'fulfilled') {
|
||||
toolResults.push(result.value);
|
||||
} else <span class="missing-if-branch" title="else path not taken" >E</span>{
|
||||
} else {
|
||||
// Use centralized error handler for tool errors
|
||||
<span class="cstat-no" title="statement not covered" > ErrorHandler.handleError(result.reason, 'ChatView.handleUserInput');</span>
|
||||
ErrorHandler.handleError(result.reason, 'ChatView.handleUserInput');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1448,23 +1450,21 @@ export class ChatView extends ItemView {
|
||||
content: fullResponse,
|
||||
isStreaming: false,
|
||||
});
|
||||
} else <span class="missing-if-branch" title="else path not taken" >E</span>{
|
||||
} else {
|
||||
// Even if no tool results were successful, mark streaming as complete
|
||||
// to prevent the assistant message from disappearing
|
||||
<span class="cstat-no" title="statement not covered" > this.updateMessageById(assistantMessageId, {</span>
|
||||
this.updateMessageById(assistantMessageId, {
|
||||
content: fullResponse,
|
||||
isStreaming: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update last message immutably — only if no tool calls were processed
|
||||
// Update assistant message immutably — only if no tool calls were processed
|
||||
if (toolCalls.length === 0) {
|
||||
const lastMessageIndex = this.messages.length - 1;
|
||||
if (lastMessageIndex >= 0) {
|
||||
const lastMessage = { ...this.messages[lastMessageIndex], isStreaming: false };
|
||||
this.messages = [...this.messages.slice(0, lastMessageIndex), lastMessage];
|
||||
}
|
||||
this.updateMessageById(assistantMessageId, {
|
||||
isStreaming: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Limit conversation history to prevent memory issues
|
||||
@@ -1501,7 +1501,7 @@ export class ChatView extends ItemView {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T20:36:57.831Z
|
||||
at 2026-05-06T21:42:52.284Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -214,30 +214,30 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -272,15 +272,15 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
@@ -475,7 +475,7 @@ export class ErrorHandler {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T20:36:57.831Z
|
||||
at 2026-05-06T21:42:52.284Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -23,30 +23,30 @@
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">74.47% </span>
|
||||
<span class="strong">93.41% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>499/670</span>
|
||||
<span class='fraction'>624/668</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">59.06% </span>
|
||||
<span class="strong">82.09% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>176/298</span>
|
||||
<span class='fraction'>243/296</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">78.21% </span>
|
||||
<span class="strong">93.06% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>79/101</span>
|
||||
<span class='fraction'>94/101</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">75.31% </span>
|
||||
<span class="strong">94.51% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>482/640</span>
|
||||
<span class='fraction'>603/638</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line medium'></div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
@@ -80,17 +80,17 @@
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="chat-view.ts"><a href="chat-view.ts.html">chat-view.ts</a></td>
|
||||
<td data-value="90.36" class="pic high">
|
||||
<div class="chart"><div class="cover-fill" style="width: 90%"></div><div class="cover-empty" style="width: 10%"></div></div>
|
||||
<td data-value="92.55" class="pic high">
|
||||
<div class="chart"><div class="cover-fill" style="width: 92%"></div><div class="cover-empty" style="width: 8%"></div></div>
|
||||
</td>
|
||||
<td data-value="90.36" class="pct high">90.36%</td>
|
||||
<td data-value="218" class="abs high">197/218</td>
|
||||
<td data-value="80.28" class="pct high">80.28%</td>
|
||||
<td data-value="71" class="abs high">57/71</td>
|
||||
<td data-value="92.55" class="pct high">92.55%</td>
|
||||
<td data-value="215" class="abs high">199/215</td>
|
||||
<td data-value="85.71" class="pct high">85.71%</td>
|
||||
<td data-value="70" class="abs high">60/70</td>
|
||||
<td data-value="82.85" class="pct high">82.85%</td>
|
||||
<td data-value="35" class="abs high">29/35</td>
|
||||
<td data-value="92.15" class="pct high">92.15%</td>
|
||||
<td data-value="204" class="abs high">188/204</td>
|
||||
<td data-value="94.52" class="pct high">94.52%</td>
|
||||
<td data-value="201" class="abs high">190/201</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -109,18 +109,18 @@
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="ollama-client.ts"><a href="ollama-client.ts.html">ollama-client.ts</a></td>
|
||||
<td data-value="17.7" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 17%"></div><div class="cover-empty" style="width: 83%"></div></div>
|
||||
<td class="file high" data-value="ollama-client.ts"><a href="ollama-client.ts.html">ollama-client.ts</a></td>
|
||||
<td data-value="91.91" class="pic high">
|
||||
<div class="chart"><div class="cover-fill" style="width: 91%"></div><div class="cover-empty" style="width: 9%"></div></div>
|
||||
</td>
|
||||
<td data-value="17.7" class="pct low">17.7%</td>
|
||||
<td data-value="96" class="abs low">17/96</td>
|
||||
<td data-value="4" class="pct low">4%</td>
|
||||
<td data-value="50" class="abs low">2/50</td>
|
||||
<td data-value="36.36" class="pct low">36.36%</td>
|
||||
<td data-value="11" class="abs low">4/11</td>
|
||||
<td data-value="18.47" class="pct low">18.47%</td>
|
||||
<td data-value="92" class="abs low">17/92</td>
|
||||
<td data-value="91.91" class="pct high">91.91%</td>
|
||||
<td data-value="99" class="abs high">91/99</td>
|
||||
<td data-value="72" class="pct medium">72%</td>
|
||||
<td data-value="50" class="abs medium">36/50</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="11" class="abs high">11/11</td>
|
||||
<td data-value="91.57" class="pct high">91.57%</td>
|
||||
<td data-value="95" class="abs high">87/95</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -154,33 +154,33 @@
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="utils.ts"><a href="utils.ts.html">utils.ts</a></td>
|
||||
<td data-value="44.32" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 44%"></div><div class="cover-empty" style="width: 56%"></div></div>
|
||||
<td class="file high" data-value="utils.ts"><a href="utils.ts.html">utils.ts</a></td>
|
||||
<td data-value="95.87" class="pic high">
|
||||
<div class="chart"><div class="cover-fill" style="width: 95%"></div><div class="cover-empty" style="width: 5%"></div></div>
|
||||
</td>
|
||||
<td data-value="44.32" class="pct low">44.32%</td>
|
||||
<td data-value="97" class="abs low">43/97</td>
|
||||
<td data-value="22.91" class="pct low">22.91%</td>
|
||||
<td data-value="48" class="abs low">11/48</td>
|
||||
<td data-value="43.75" class="pct low">43.75%</td>
|
||||
<td data-value="16" class="abs low">7/16</td>
|
||||
<td data-value="43.01" class="pct low">43.01%</td>
|
||||
<td data-value="93" class="abs low">40/93</td>
|
||||
<td data-value="95.87" class="pct high">95.87%</td>
|
||||
<td data-value="97" class="abs high">93/97</td>
|
||||
<td data-value="85.41" class="pct high">85.41%</td>
|
||||
<td data-value="48" class="abs high">41/48</td>
|
||||
<td data-value="93.75" class="pct high">93.75%</td>
|
||||
<td data-value="16" class="abs high">15/16</td>
|
||||
<td data-value="96.77" class="pct high">96.77%</td>
|
||||
<td data-value="93" class="abs high">90/93</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="vault-indexer.ts"><a href="vault-indexer.ts.html">vault-indexer.ts</a></td>
|
||||
<td data-value="92.1" class="pic high">
|
||||
<td data-value="92.85" class="pic high">
|
||||
<div class="chart"><div class="cover-fill" style="width: 92%"></div><div class="cover-empty" style="width: 8%"></div></div>
|
||||
</td>
|
||||
<td data-value="92.1" class="pct high">92.1%</td>
|
||||
<td data-value="114" class="abs high">105/114</td>
|
||||
<td data-value="78.26" class="pct medium">78.26%</td>
|
||||
<td data-value="46" class="abs medium">36/46</td>
|
||||
<td data-value="92.85" class="pct high">92.85%</td>
|
||||
<td data-value="112" class="abs high">104/112</td>
|
||||
<td data-value="80" class="pct high">80%</td>
|
||||
<td data-value="45" class="abs high">36/45</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="15" class="abs high">15/15</td>
|
||||
<td data-value="94.33" class="pct high">94.33%</td>
|
||||
<td data-value="106" class="abs high">100/106</td>
|
||||
<td data-value="95.19" class="pct high">95.19%</td>
|
||||
<td data-value="104" class="abs high">99/104</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
@@ -191,7 +191,7 @@
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T20:36:57.831Z
|
||||
at 2026-05-06T21:42:52.284Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -23,30 +23,30 @@
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">17.7% </span>
|
||||
<span class="strong">91.91% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>17/96</span>
|
||||
<span class='fraction'>91/99</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">4% </span>
|
||||
<span class="strong">72% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>2/50</span>
|
||||
<span class='fraction'>36/50</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">36.36% </span>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>4/11</span>
|
||||
<span class='fraction'>11/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">18.47% </span>
|
||||
<span class="strong">91.57% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>17/92</span>
|
||||
<span class='fraction'>87/95</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
@@ -293,7 +293,15 @@
|
||||
<a name='L228'></a><a href='#L228'>228</a>
|
||||
<a name='L229'></a><a href='#L229'>229</a>
|
||||
<a name='L230'></a><a href='#L230'>230</a>
|
||||
<a name='L231'></a><a href='#L231'>231</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<a name='L231'></a><a href='#L231'>231</a>
|
||||
<a name='L232'></a><a href='#L232'>232</a>
|
||||
<a name='L233'></a><a href='#L233'>233</a>
|
||||
<a name='L234'></a><a href='#L234'>234</a>
|
||||
<a name='L235'></a><a href='#L235'>235</a>
|
||||
<a name='L236'></a><a href='#L236'>236</a>
|
||||
<a name='L237'></a><a href='#L237'>237</a>
|
||||
<a name='L238'></a><a href='#L238'>238</a>
|
||||
<a name='L239'></a><a href='#L239'>239</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
@@ -306,19 +314,38 @@
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">59x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">59x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">59x</span>
|
||||
<span class="cline-any cline-yes">59x</span>
|
||||
<span class="cline-any cline-yes">59x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">28x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">20x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -327,29 +354,161 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">25x</span>
|
||||
<span class="cline-any cline-yes">25x</span>
|
||||
<span class="cline-any cline-yes">25x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">25x</span>
|
||||
<span class="cline-any cline-yes">25x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">25x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-yes">68x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">67x</span>
|
||||
<span class="cline-any cline-yes">67x</span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">54x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">53x</span>
|
||||
<span class="cline-any cline-yes">53x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">52x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">25x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
@@ -357,166 +516,23 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">18x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">17x</span>
|
||||
<span class="cline-any cline-yes">17x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -560,12 +576,12 @@ export class OllamaClient {
|
||||
yield* this.streamChatWithRetry(messages, tools, 0);
|
||||
}
|
||||
|
||||
<span class="fstat-no" title="function not covered" > async </span>streamChatAsPromise(
|
||||
async streamChatAsPromise(
|
||||
messages: OllamaMessage[],
|
||||
tools: OllamaTool[] = <span class="branch-0 cbranch-no" title="branch not covered" >[]</span>
|
||||
): Promise<OllamaMessage[]> {
|
||||
const chunks: OllamaMessage[] = <span class="cstat-no" title="statement not covered" >[];</span>
|
||||
<span class="cstat-no" title="statement not covered" > for await (const chunk of this.streamChat(messages, tools)) {</span>
|
||||
const chunks: OllamaMessage[] = [];
|
||||
for await (const chunk of this.streamChat(messages, tools)) {
|
||||
<span class="cstat-no" title="statement not covered" > chunks.push(chunk);</span>
|
||||
}
|
||||
<span class="cstat-no" title="statement not covered" > return chunks;</span>
|
||||
@@ -578,8 +594,8 @@ export class OllamaClient {
|
||||
): AsyncGenerator<OllamaMessage, void, unknown> {
|
||||
const prevController = this.abortController;
|
||||
this.abortController = new AbortController();
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (prevController) {
|
||||
<span class="cstat-no" title="statement not covered" > prevController.abort();</span>
|
||||
if (prevController) {
|
||||
prevController.abort();
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -597,70 +613,70 @@ export class OllamaClient {
|
||||
signal: this.abortController.signal,
|
||||
});
|
||||
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (!response.ok) {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (response.status >= 500 && attempt < this.maxRetries) {</span>
|
||||
const retryDelay = <span class="cstat-no" title="statement not covered" >Math.pow(2, attempt) * 100;</span>
|
||||
<span class="cstat-no" title="statement not covered" > Logger.warn(</span>
|
||||
if (!response.ok) {
|
||||
if (response.status >= 500 && attempt < this.maxRetries) {
|
||||
const retryDelay = Math.pow(2, attempt) * 100;
|
||||
Logger.warn(
|
||||
`Network error (status ${response.status}), retrying in ${retryDelay}ms (attempt ${attempt + 1}/${this.maxRetries})`,
|
||||
'ollama-client'
|
||||
);
|
||||
<span class="cstat-no" title="statement not covered" > await new Promise(<span class="fstat-no" title="function not covered" >(r</span>esolve) => <span class="cstat-no" title="statement not covered" >setTimeout(resolve, retryDelay))</span>;</span>
|
||||
<span class="cstat-no" title="statement not covered" > yield* this.streamChatWithRetry(messages, tools, attempt + 1);</span>
|
||||
<span class="cstat-no" title="statement not covered" > return;</span>
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
yield* this.streamChatWithRetry(messages, tools, attempt + 1);
|
||||
return;
|
||||
}
|
||||
<span class="cstat-no" title="statement not covered" > throw new ApiError(`Ollama API error: ${response.status}`, response.status);</span>
|
||||
throw new ApiError(`Ollama API error: ${response.status}`, response.status);
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!response.body) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('No response body');</span>
|
||||
if (!response.body) {
|
||||
throw new Error('No response body');
|
||||
}
|
||||
|
||||
const contentType = <span class="cstat-no" title="statement not covered" >response.headers.get('content-type');</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!contentType || (!contentType.includes('ndjson') && !contentType.includes('json'))) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Invalid response format');</span>
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (!contentType || (!contentType.includes('ndjson') && !contentType.includes('json'))) {
|
||||
throw new Error('Invalid response format');
|
||||
}
|
||||
|
||||
const reader = <span class="cstat-no" title="statement not covered" >response.body.getReader();</span>
|
||||
const decoder = <span class="cstat-no" title="statement not covered" >new TextDecoder();</span>
|
||||
let buffer = <span class="cstat-no" title="statement not covered" >'';</span>
|
||||
let malformedCount = <span class="cstat-no" title="statement not covered" >0;</span>
|
||||
const maxMalformed = <span class="cstat-no" title="statement not covered" >50;</span>
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
let malformedCount = 0;
|
||||
const maxMalformed = 50;
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
<span class="cstat-no" title="statement not covered" > while (true) {</span>
|
||||
const { done, value } = <span class="cstat-no" title="statement not covered" >await reader.read();</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (done) <span class="cstat-no" title="statement not covered" >break;</span></span>
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > buffer += decoder.decode(value, { stream: true });</span>
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
const lines = <span class="cstat-no" title="statement not covered" >buffer.split('\n');</span>
|
||||
<span class="cstat-no" title="statement not covered" > buffer = lines.pop() ?? '';</span>
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() ?? <span class="branch-1 cbranch-no" title="branch not covered" >'';</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > for (const line of lines) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!line.trim()) <span class="cstat-no" title="statement not covered" >continue;</span></span>
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const parsed = <span class="cstat-no" title="statement not covered" >JSON.parse(line) as Record<string, unknown>;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.throwIfOllamaError(parsed);</span>
|
||||
try {
|
||||
const parsed = JSON.parse(line) as Record<string, unknown>;
|
||||
this.throwIfOllamaError(parsed);
|
||||
|
||||
const message = <span class="cstat-no" title="statement not covered" >this.toOllamaMessage(parsed.message);</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!message) {</span>
|
||||
const message = this.toOllamaMessage(parsed.message);
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (!message) {
|
||||
<span class="cstat-no" title="statement not covered" > continue;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > malformedCount = 0;</span>
|
||||
<span class="cstat-no" title="statement not covered" > yield message;</span>
|
||||
malformedCount = 0;
|
||||
yield message;
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (error instanceof Error && error.message.startsWith('Ollama error:')) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw error;</span>
|
||||
if (error instanceof Error && error.message.startsWith('Ollama error:')) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > malformedCount++;</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (malformedCount > maxMalformed) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Too many malformed chunks in stream');</span>
|
||||
malformedCount++;
|
||||
if (malformedCount > maxMalformed) {
|
||||
throw new Error('Too many malformed chunks in stream');
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > Logger.warn(</span>
|
||||
Logger.warn(
|
||||
`Skipped malformed chunk: ${line.substring(0, 80)}... - ${(error as Error).message}`,
|
||||
'ollama-client'
|
||||
);
|
||||
@@ -668,14 +684,14 @@ export class OllamaClient {
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (buffer.trim()) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const parsed = <span class="cstat-no" title="statement not covered" >JSON.parse(buffer) as Record<string, unknown>;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.throwIfOllamaError(parsed);</span>
|
||||
if (buffer.trim()) {
|
||||
try {
|
||||
const parsed = JSON.parse(buffer) as Record<string, unknown>;
|
||||
this.throwIfOllamaError(parsed);
|
||||
|
||||
const message = <span class="cstat-no" title="statement not covered" >this.toOllamaMessage(parsed.message);</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (message) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > yield message;</span>
|
||||
const message = this.toOllamaMessage(parsed.message);
|
||||
if (message) {
|
||||
yield message;
|
||||
}
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (error instanceof Error && error.message.startsWith('Ollama error:')) {</span>
|
||||
@@ -688,23 +704,25 @@ export class OllamaClient {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
<span class="cstat-no" title="statement not covered" > reader.releaseLock();</span>
|
||||
reader.releaseLock();
|
||||
}
|
||||
} finally {
|
||||
this.abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
<span class="fstat-no" title="function not covered" > async </span>chat(messages: OllamaMessage[], tools: OllamaTool[] = <span class="branch-0 cbranch-no" title="branch not covered" >[])</span>: Promise<OllamaMessage> {
|
||||
<span class="cstat-no" title="statement not covered" > return this.chatWithRetry(messages, tools, 0);</span>
|
||||
async chat(messages: OllamaMessage[], tools: OllamaTool[] = <span class="branch-0 cbranch-no" title="branch not covered" >[])</span>: Promise<OllamaMessage> {
|
||||
return this.chatWithRetry(messages, tools, 0);
|
||||
}
|
||||
|
||||
private <span class="fstat-no" title="function not covered" >async </span>chatWithRetry(
|
||||
private async chatWithRetry(
|
||||
messages: OllamaMessage[],
|
||||
tools: OllamaTool[] = <span class="branch-0 cbranch-no" title="branch not covered" >[],</span>
|
||||
attempt: number = <span class="branch-0 cbranch-no" title="branch not covered" >0</span>
|
||||
): Promise<OllamaMessage> {
|
||||
const response = <span class="cstat-no" title="statement not covered" >await this.fetchFn(`${this.baseURL}/api/chat`, {</span>
|
||||
this.abortController = new AbortController();
|
||||
try {
|
||||
const response = await this.fetchFn(`${this.baseURL}/api/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -715,40 +733,46 @@ export class OllamaClient {
|
||||
tools,
|
||||
stream: false,
|
||||
}),
|
||||
signal: this.abortController.signal,
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!response.ok) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (response.status >= 500 && attempt < this.maxRetries) {</span>
|
||||
const retryDelay = <span class="cstat-no" title="statement not covered" >Math.pow(2, attempt) * 100;</span>
|
||||
<span class="cstat-no" title="statement not covered" > Logger.warn(</span>
|
||||
if (!response.ok) {
|
||||
if (response.status >= 500 && attempt < this.maxRetries) {
|
||||
const retryDelay = Math.pow(2, attempt) * 100;
|
||||
Logger.warn(
|
||||
`Network error (status ${response.status}), retrying in ${retryDelay}ms (attempt ${attempt + 1}/${this.maxRetries})`,
|
||||
'ollama-client'
|
||||
);
|
||||
<span class="cstat-no" title="statement not covered" > await new Promise(<span class="fstat-no" title="function not covered" >(r</span>esolve) => <span class="cstat-no" title="statement not covered" >setTimeout(resolve, retryDelay))</span>;</span>
|
||||
<span class="cstat-no" title="statement not covered" > return this.chatWithRetry(messages, tools, attempt + 1);</span>
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
return this.chatWithRetry(messages, tools, attempt + 1);
|
||||
}
|
||||
<span class="cstat-no" title="statement not covered" > throw new ApiError(`Ollama API error: ${response.status}`, response.status);</span>
|
||||
throw new ApiError(`Ollama API error: ${response.status}`, response.status);
|
||||
}
|
||||
|
||||
const data = (<span class="cstat-no" title="statement not covered" >await response.json())</span> as OllamaChatResponse;
|
||||
<span class="cstat-no" title="statement not covered" > return this.toOllamaMessage(data.message) ?? { role: 'assistant', content: '', tool_calls: [] };</span>
|
||||
}
|
||||
|
||||
private <span class="fstat-no" title="function not covered" >throwIfOllamaError(</span>parsed: Record<string, unknown>): void {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (parsed.error) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error(`Ollama error: ${String(parsed.error)}`);</span>
|
||||
const data = (await response.json()) as OllamaChatResponse;
|
||||
return (
|
||||
this.toOllamaMessage(data.message) ?? { role: 'assistant', content: '', tool_calls: [] }
|
||||
);
|
||||
} finally {
|
||||
this.abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
private <span class="fstat-no" title="function not covered" >toOllamaMessage(</span>value: unknown): OllamaMessage | null {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!value || typeof value !== 'object') {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return null;</span>
|
||||
private throwIfOllamaError(parsed: Record<string, unknown>): void {
|
||||
if (parsed.error) {
|
||||
throw new Error(`Ollama error: ${String(parsed.error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const record = <span class="cstat-no" title="statement not covered" >value as Partial<OllamaMessage>;</span>
|
||||
<span class="cstat-no" title="statement not covered" > return {</span>
|
||||
private toOllamaMessage(value: unknown): OllamaMessage | null {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const record = value as Partial<OllamaMessage>;
|
||||
return {
|
||||
role: record.role ?? 'assistant',
|
||||
content: typeof record.content === 'string' ? record.content : '',
|
||||
content: typeof record.content === 'string' ? record.content : <span class="branch-1 cbranch-no" title="branch not covered" >'',</span>
|
||||
tool_calls: record.tool_calls ?? [],
|
||||
};
|
||||
}
|
||||
@@ -760,7 +784,7 @@ export class OllamaClient {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T20:36:57.831Z
|
||||
at 2026-05-06T21:42:52.284Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -210,8 +210,8 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">58x</span>
|
||||
<span class="cline-any cline-yes">58x</span>
|
||||
<span class="cline-any cline-yes">63x</span>
|
||||
<span class="cline-any cline-yes">63x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -463,7 +463,7 @@ export class ToolExecutor {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T20:36:57.831Z
|
||||
at 2026-05-06T21:42:52.284Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -261,9 +261,9 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-yes">19x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -281,9 +281,9 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -604,7 +604,7 @@ export interface VaultIndexEntry {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T20:36:57.831Z
|
||||
at 2026-05-06T21:42:52.284Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
+167
-179
@@ -23,30 +23,30 @@
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">44.32% </span>
|
||||
<span class="strong">95.87% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>43/97</span>
|
||||
<span class='fraction'>93/97</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">22.91% </span>
|
||||
<span class="strong">85.41% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>11/48</span>
|
||||
<span class='fraction'>41/48</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">43.75% </span>
|
||||
<span class="strong">93.75% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>7/16</span>
|
||||
<span class='fraction'>15/16</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">43.01% </span>
|
||||
<span class="strong">96.77% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>40/93</span>
|
||||
<span class='fraction'>90/93</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
@@ -267,15 +267,7 @@
|
||||
<a name='L202'></a><a href='#L202'>202</a>
|
||||
<a name='L203'></a><a href='#L203'>203</a>
|
||||
<a name='L204'></a><a href='#L204'>204</a>
|
||||
<a name='L205'></a><a href='#L205'>205</a>
|
||||
<a name='L206'></a><a href='#L206'>206</a>
|
||||
<a name='L207'></a><a href='#L207'>207</a>
|
||||
<a name='L208'></a><a href='#L208'>208</a>
|
||||
<a name='L209'></a><a href='#L209'>209</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<a name='L205'></a><a href='#L205'>205</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
@@ -293,35 +285,35 @@
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">23x</span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">64x</span>
|
||||
<span class="cline-any cline-yes">63x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -329,71 +321,71 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-yes">9x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">14x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">13x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">11x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -402,67 +394,67 @@
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">75x</span>
|
||||
<span class="cline-any cline-yes">117x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">116x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">75x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">116x</span>
|
||||
<span class="cline-any cline-yes">58x</span>
|
||||
<span class="cline-any cline-yes">58x</span>
|
||||
<span class="cline-any cline-yes">83x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">75x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">49x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">49x</span>
|
||||
<span class="cline-any cline-yes">58x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">27x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">41x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">27x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">27x</span>
|
||||
<span class="cline-any cline-yes">27x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">40x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">75x</span>
|
||||
<span class="cline-any cline-yes">49x</span>
|
||||
<span class="cline-any cline-yes">39x</span>
|
||||
<span class="cline-any cline-yes">39x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">78x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">37x</span>
|
||||
<span class="cline-any cline-yes">121x</span>
|
||||
<span class="cline-any cline-yes">59x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">62x</span>
|
||||
<span class="cline-any cline-yes">183x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">59x</span>
|
||||
<span class="cline-any cline-yes">84x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">49x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">59x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">37x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">34x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -470,20 +462,16 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">// src/utils.ts
|
||||
|
||||
// ==================== Logger ====================
|
||||
|
||||
enum LogLevel {
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">export enum LogLevel {
|
||||
DEBUG = 0,
|
||||
INFO = 1,
|
||||
WARN = 2,
|
||||
@@ -500,24 +488,24 @@ const SEVERITY_ORDER: Record<string, number> = {
|
||||
export class Logger {
|
||||
private static minLevel: LogLevel = LogLevel.DEBUG;
|
||||
|
||||
<span class="fstat-no" title="function not covered" > static </span>setLevel(level: string | LogLevel): void {
|
||||
<span class="cstat-no" title="statement not covered" > if (typeof level === 'string') {</span>
|
||||
const lowerLevel = <span class="cstat-no" title="statement not covered" >level.toLowerCase();</span>
|
||||
<span class="cstat-no" title="statement not covered" > Logger.minLevel = SEVERITY_ORDER[lowerLevel] ?? LogLevel.DEBUG;</span>
|
||||
static setLevel(level: string | LogLevel): void {
|
||||
if (typeof level === 'string') {
|
||||
const lowerLevel = level.toLowerCase();
|
||||
Logger.minLevel = SEVERITY_ORDER[lowerLevel] ?? LogLevel.DEBUG;
|
||||
} else {
|
||||
<span class="cstat-no" title="statement not covered" > Logger.minLevel = level;</span>
|
||||
Logger.minLevel = level;
|
||||
}
|
||||
}
|
||||
|
||||
<span class="fstat-no" title="function not covered" > static </span>debug(message: string, category: string = <span class="branch-0 cbranch-no" title="branch not covered" >'general')</span>: void {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (LogLevel.DEBUG >= Logger.minLevel) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > console.debug(`[${category}] DEBUG: ${message}`);</span>
|
||||
static debug(message: string, category: string = <span class="branch-0 cbranch-no" title="branch not covered" >'general')</span>: void {
|
||||
if (LogLevel.DEBUG >= Logger.minLevel) {
|
||||
console.debug(`[${category}] DEBUG: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
<span class="fstat-no" title="function not covered" > static </span>info(message: string, category: string = <span class="branch-0 cbranch-no" title="branch not covered" >'general')</span>: void {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (LogLevel.INFO >= Logger.minLevel) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > console.info(`[${category}] INFO: ${message}`);</span>
|
||||
static info(message: string, category: string = <span class="branch-0 cbranch-no" title="branch not covered" >'general')</span>: void {
|
||||
if (LogLevel.INFO >= Logger.minLevel) {
|
||||
console.info(`[${category}] INFO: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,81 +515,81 @@ export class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
<span class="fstat-no" title="function not covered" > static </span>error(message: string, category: string = <span class="branch-0 cbranch-no" title="branch not covered" >'general')</span>: void {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (LogLevel.ERROR >= Logger.minLevel) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > console.error(`[${category}] ERROR: ${message}`);</span>
|
||||
static error(message: string, category: string = <span class="branch-0 cbranch-no" title="branch not covered" >'general')</span>: void {
|
||||
if (LogLevel.ERROR >= Logger.minLevel) {
|
||||
console.error(`[${category}] ERROR: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== URL & Model Validation ====================
|
||||
|
||||
export function <span class="fstat-no" title="function not covered" >validateOllamaUrl(</span>url: string): { valid: boolean; error?: string } {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (typeof url !== 'string' || !url.trim()) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: false, error: 'URL cannot be empty' };</span>
|
||||
export function validateOllamaUrl(url: string): { valid: boolean; error?: string } {
|
||||
if (typeof url !== 'string' || !url.trim()) {
|
||||
return { valid: false, error: 'URL cannot be empty' };
|
||||
}
|
||||
|
||||
const trimmedUrl = <span class="cstat-no" title="statement not covered" >url.trim();</span>
|
||||
const trimmedUrl = url.trim();
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (trimmedUrl.endsWith('/')) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: false, error: 'URL should not end with a slash' };</span>
|
||||
if (trimmedUrl.endsWith('/')) {
|
||||
return { valid: false, error: 'URL should not end with a slash' };
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const parsed = <span class="cstat-no" title="statement not covered" >new URL(trimmedUrl);</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {</span>
|
||||
try {
|
||||
const parsed = new URL(trimmedUrl);
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: false, error: 'Must be a valid HTTP or HTTPS URL' };</span>
|
||||
}
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: true };</span>
|
||||
return { valid: true };
|
||||
} catch {
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: false, error: 'Must be a valid HTTP or HTTPS URL' };</span>
|
||||
return { valid: false, error: 'Must be a valid HTTP or HTTPS URL' };
|
||||
}
|
||||
}
|
||||
|
||||
export function <span class="fstat-no" title="function not covered" >validateModelName(</span>model: string): { valid: boolean; error?: string } {
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (typeof model !== 'string') {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: false, error: 'Model name must be a string' };</span>
|
||||
export function validateModelName(model: string): { valid: boolean; error?: string } {
|
||||
if (typeof model !== 'string') {
|
||||
return { valid: false, error: 'Model name must be a string' };
|
||||
}
|
||||
|
||||
const trimmedModel = <span class="cstat-no" title="statement not covered" >model.trim();</span>
|
||||
const trimmedModel = model.trim();
|
||||
|
||||
// Explicit check for empty string after trimming
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!trimmedModel || trimmedModel.length === 0) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: false, error: 'Model name cannot be empty' };</span>
|
||||
if (!trimmedModel || trimmedModel.length === 0) {
|
||||
return { valid: false, error: 'Model name cannot be empty' };
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (trimmedModel.length < 2) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: false, error: 'Model name must be at least 2 characters long' };</span>
|
||||
if (trimmedModel.length < 2) {
|
||||
return { valid: false, error: 'Model name must be at least 2 characters long' };
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (trimmedModel.length > 100) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: false, error: 'Model name must be less than 100 characters long' };</span>
|
||||
if (trimmedModel.length > 100) {
|
||||
return { valid: false, error: 'Model name must be less than 100 characters long' };
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!/^[a-zA-Z0-9._:-]+$/.test(trimmedModel)) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return {</span>
|
||||
if (!/^[a-zA-Z0-9._:-]+$/.test(trimmedModel)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Model name can only contain letters, numbers, dots, dashes, underscores, and colons',
|
||||
};
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return { valid: true };</span>
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
export function <span class="fstat-no" title="function not covered" >validatePluginSettings(</span>settings: { ollamaUrl: string; model: string }): string[] {
|
||||
const errors: string[] = <span class="cstat-no" title="statement not covered" >[];</span>
|
||||
export function validatePluginSettings(settings: { ollamaUrl: string; model: string }): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
const urlValidation = <span class="cstat-no" title="statement not covered" >validateOllamaUrl(settings.ollamaUrl);</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!urlValidation.valid) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > errors.push(`Invalid Ollama URL: ${urlValidation.error}`);</span>
|
||||
const urlValidation = validateOllamaUrl(settings.ollamaUrl);
|
||||
if (!urlValidation.valid) {
|
||||
errors.push(`Invalid Ollama URL: ${urlValidation.error}`);
|
||||
}
|
||||
|
||||
const modelValidation = <span class="cstat-no" title="statement not covered" >validateModelName(settings.model);</span>
|
||||
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!modelValidation.valid) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > errors.push(`Invalid Model Name: ${modelValidation.error}`);</span>
|
||||
const modelValidation = validateModelName(settings.model);
|
||||
if (!modelValidation.valid) {
|
||||
errors.push(`Invalid Model Name: ${modelValidation.error}`);
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return errors;</span>
|
||||
return errors;
|
||||
}
|
||||
|
||||
// ==================== Safe JSON Parsing ====================
|
||||
@@ -610,8 +598,8 @@ const MAX_JSON_SIZE = 1_000_000;
|
||||
const MAX_JSON_NESTING = 24;
|
||||
|
||||
function countNestingDepth(value: unknown, depth: number = 0): number {
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (depth > MAX_JSON_NESTING) {
|
||||
<span class="cstat-no" title="statement not covered" > return depth;</span>
|
||||
if (depth > MAX_JSON_NESTING) {
|
||||
return depth;
|
||||
}
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (Array.isArray(value)) {
|
||||
<span class="cstat-no" title="statement not covered" > return Math.max(...value.map(<span class="fstat-no" title="function not covered" >(i</span>tem) => <span class="cstat-no" title="statement not covered" >countNestingDepth(item, depth + 1))</span>, depth);</span>
|
||||
@@ -625,12 +613,12 @@ function countNestingDepth(value: unknown, depth: number = 0): number {
|
||||
}
|
||||
|
||||
export function safeParseJson(jsonString: string): unknown {
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (typeof jsonString !== 'string') {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Input must be a string');</span>
|
||||
if (typeof jsonString !== 'string') {
|
||||
throw new Error('Input must be a string');
|
||||
}
|
||||
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (jsonString.length > MAX_JSON_SIZE) {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('JSON input too large');</span>
|
||||
if (jsonString.length > MAX_JSON_SIZE) {
|
||||
throw new Error('JSON input too large');
|
||||
}
|
||||
|
||||
let parsed: unknown;
|
||||
@@ -647,8 +635,8 @@ export function safeParseJson(jsonString: string): unknown {
|
||||
}
|
||||
|
||||
const dangerousKeys = ['constructor', 'prototype', '__proto__'];
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (dangerousKeys.some((key) => Object.keys(obj).includes(key))) {
|
||||
<span class="cstat-no" title="statement not covered" > return true;</span>
|
||||
if (dangerousKeys.some((key) => Object.keys(obj).includes(key))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Recursively check nested objects
|
||||
@@ -661,13 +649,13 @@ export function safeParseJson(jsonString: string): unknown {
|
||||
return false;
|
||||
};
|
||||
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (checkDangerousPatterns(parsed)) {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('dangerous code pattern detected');</span>
|
||||
if (checkDangerousPatterns(parsed)) {
|
||||
throw new Error('dangerous code pattern detected');
|
||||
}
|
||||
|
||||
// Check nesting depth
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (countNestingDepth(parsed) > MAX_JSON_NESTING) {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('JSON nesting too deep');</span>
|
||||
if (countNestingDepth(parsed) > MAX_JSON_NESTING) {
|
||||
throw new Error('JSON nesting too deep');
|
||||
}
|
||||
|
||||
return parsed;
|
||||
@@ -677,12 +665,12 @@ export function safeParseJson(jsonString: string): unknown {
|
||||
|
||||
// ==================== HTTP Helpers ====================
|
||||
|
||||
export function <span class="fstat-no" title="function not covered" >isValidHttpUrl(</span>url: string): boolean {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const parsed = <span class="cstat-no" title="statement not covered" >new URL(url);</span>
|
||||
<span class="cstat-no" title="statement not covered" > return parsed.protocol === 'http:' || parsed.protocol === 'https:';</span>
|
||||
export function isValidHttpUrl(url: string): boolean {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
||||
} catch {
|
||||
<span class="cstat-no" title="statement not covered" > return false;</span>
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,7 +682,7 @@ export function <span class="fstat-no" title="function not covered" >isValidHttp
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T20:36:57.831Z
|
||||
at 2026-05-06T21:42:52.284Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -23,16 +23,16 @@
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">92.1% </span>
|
||||
<span class="strong">92.85% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>105/114</span>
|
||||
<span class='fraction'>104/112</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">78.26% </span>
|
||||
<span class="strong">80% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>36/46</span>
|
||||
<span class='fraction'>36/45</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">94.33% </span>
|
||||
<span class="strong">95.19% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>100/106</span>
|
||||
<span class='fraction'>99/104</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -333,11 +333,7 @@
|
||||
<a name='L268'></a><a href='#L268'>268</a>
|
||||
<a name='L269'></a><a href='#L269'>269</a>
|
||||
<a name='L270'></a><a href='#L270'>270</a>
|
||||
<a name='L271'></a><a href='#L271'>271</a>
|
||||
<a name='L272'></a><a href='#L272'>272</a>
|
||||
<a name='L273'></a><a href='#L273'>273</a>
|
||||
<a name='L274'></a><a href='#L274'>274</a>
|
||||
<a name='L275'></a><a href='#L275'>275</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<a name='L271'></a><a href='#L271'>271</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
@@ -370,27 +366,27 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">62x</span>
|
||||
<span class="cline-any cline-yes">67x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">62x</span>
|
||||
<span class="cline-any cline-yes">67x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">28x</span>
|
||||
<span class="cline-any cline-yes">33x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">50x</span>
|
||||
<span class="cline-any cline-yes">38x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -401,11 +397,11 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -442,16 +438,12 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">15x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">31x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">95x</span>
|
||||
<span class="cline-any cline-yes">100x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -478,10 +470,10 @@
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">95x</span>
|
||||
<span class="cline-any cline-yes">100x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">362x</span>
|
||||
<span class="cline-any cline-yes">367x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
@@ -715,10 +707,6 @@ class VaultIndexer {
|
||||
(result): result is NonNullable<typeof result> => result !== null
|
||||
);
|
||||
results.push(...validResults);
|
||||
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (results.length >= 50) {
|
||||
<span class="cstat-no" title="statement not covered" > break;</span>
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
@@ -892,7 +880,7 @@ export { VaultIndexer };
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T20:36:57.831Z
|
||||
at 2026-05-06T21:42:52.284Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
+656
-660
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -4,15 +4,15 @@ module.exports = {
|
||||
testMatch: ['**/tests/**/*.test.ts'],
|
||||
moduleFileExtensions: ['ts', 'js', 'json', 'node'],
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest',
|
||||
'^.+\\.ts$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: 'tsconfig.test.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
coverageDirectory: 'coverage',
|
||||
collectCoverage: true,
|
||||
coverageReporters: ['text', 'lcov'],
|
||||
setupFiles: ['./jest.setup.js'],
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.test.json',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -179,6 +179,8 @@ export class OllamaClient {
|
||||
tools: OllamaTool[] = [],
|
||||
attempt: number = 0
|
||||
): Promise<OllamaMessage> {
|
||||
this.abortController = new AbortController();
|
||||
try {
|
||||
const response = await this.fetchFn(`${this.baseURL}/api/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -190,6 +192,7 @@ export class OllamaClient {
|
||||
tools,
|
||||
stream: false,
|
||||
}),
|
||||
signal: this.abortController.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -206,7 +209,12 @@ export class OllamaClient {
|
||||
}
|
||||
|
||||
const data = (await response.json()) as OllamaChatResponse;
|
||||
return this.toOllamaMessage(data.message) ?? { role: 'assistant', content: '', tool_calls: [] };
|
||||
return (
|
||||
this.toOllamaMessage(data.message) ?? { role: 'assistant', content: '', tool_calls: [] }
|
||||
);
|
||||
} finally {
|
||||
this.abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
private throwIfOllamaError(parsed: Record<string, unknown>): void {
|
||||
|
||||
+1
-5
@@ -1,8 +1,4 @@
|
||||
// src/utils.ts
|
||||
|
||||
// ==================== Logger ====================
|
||||
|
||||
enum LogLevel {
|
||||
export enum LogLevel {
|
||||
DEBUG = 0,
|
||||
INFO = 1,
|
||||
WARN = 2,
|
||||
|
||||
+209
-3
@@ -1,5 +1,6 @@
|
||||
import { ChatView } from '../src/chat-view';
|
||||
import { PluginSettings, OllamaMessage, ChatMessage, OllamaTool, ToolCall } from '../src/types';
|
||||
import { ErrorHandler } from '../src/error-handler';
|
||||
|
||||
// Mock Obsidian types
|
||||
interface MockVault {
|
||||
@@ -198,6 +199,68 @@ describe('ChatView', () => {
|
||||
expect(lastMessage.isStreaming).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle streaming re-attach when existing streaming element is found', async () => {
|
||||
view['sendButton'] = document.createElement('button');
|
||||
view['inputEl'] = document.createElement('textarea');
|
||||
(view['inputEl'] as HTMLTextAreaElement).value = 'test';
|
||||
(view as any).lastMessageEl = document.createElement('div');
|
||||
(view as any).lastMessageEl.setAttribute('data-msg-id', 'streaming-123');
|
||||
|
||||
// Mock querySelector to return existing element
|
||||
const container = document.createElement('div');
|
||||
const existingEl = document.createElement('div');
|
||||
existingEl.setAttribute('data-msg-id', 'streaming-123');
|
||||
container.appendChild(existingEl);
|
||||
|
||||
// Mock the contentEl to return our container
|
||||
const originalContentEl = (view as any).contentEl;
|
||||
(view as any).contentEl = container;
|
||||
|
||||
const chatSpy = jest.spyOn(view['ollamaClient'], 'streamChat').mockReturnValue(
|
||||
(async function* () {
|
||||
yield { role: 'assistant', content: 'response' };
|
||||
})()
|
||||
);
|
||||
|
||||
await (view as any).handleUserInput('test');
|
||||
|
||||
expect(chatSpy).toHaveBeenCalled();
|
||||
expect(container.contains((view as any).lastMessageEl)).toBe(true);
|
||||
|
||||
// Restore original contentEl
|
||||
(view as any).contentEl = originalContentEl;
|
||||
});
|
||||
|
||||
it('should append streaming element to container when no existing element found', async () => {
|
||||
view['sendButton'] = document.createElement('button');
|
||||
view['inputEl'] = document.createElement('textarea');
|
||||
(view['inputEl'] as HTMLTextAreaElement).value = 'test';
|
||||
|
||||
// Mock querySelector to return null (no existing element)
|
||||
const container = document.createElement('div');
|
||||
const originalQuerySelector = container.querySelector;
|
||||
container.querySelector = jest.fn().mockReturnValue(null);
|
||||
|
||||
// Mock the contentEl to return our container
|
||||
const originalContentEl = (view as any).contentEl;
|
||||
(view as any).contentEl = container;
|
||||
|
||||
const chatSpy = jest.spyOn(view['ollamaClient'], 'streamChat').mockReturnValue(
|
||||
(async function* () {
|
||||
yield { role: 'assistant', content: 'response' };
|
||||
})()
|
||||
);
|
||||
|
||||
await (view as any).handleUserInput('test');
|
||||
|
||||
expect(chatSpy).toHaveBeenCalled();
|
||||
expect(container.childElementCount).toBeGreaterThan(0);
|
||||
|
||||
// Restore original contentEl and querySelector
|
||||
(view as any).contentEl = originalContentEl;
|
||||
container.querySelector = originalQuerySelector;
|
||||
});
|
||||
|
||||
it('should limit conversation history to maxMessageHistory', async () => {
|
||||
view['sendButton'] = document.createElement('button');
|
||||
view['inputEl'] = document.createElement('textarea');
|
||||
@@ -325,6 +388,147 @@ describe('ChatView', () => {
|
||||
expect((view as any).messages.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('should handle tool call errors gracefully and continue with partial results', async () => {
|
||||
view['sendButton'] = document.createElement('button');
|
||||
view['inputEl'] = document.createElement('textarea');
|
||||
(view['inputEl'] as HTMLTextAreaElement).value = 'test';
|
||||
const chatSpy = jest.spyOn(view['ollamaClient'], 'streamChat').mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
role: 'assistant',
|
||||
content: 'Response with tools',
|
||||
tool_calls: [
|
||||
{
|
||||
id: 'call_1',
|
||||
type: 'function',
|
||||
function: { name: 'create_file', arguments: '{"path":"test.md","content":"test"}' },
|
||||
},
|
||||
{
|
||||
id: 'call_2',
|
||||
type: 'function',
|
||||
function: { name: 'nonexistent_tool', arguments: '{}' },
|
||||
},
|
||||
],
|
||||
};
|
||||
})()
|
||||
);
|
||||
const followUpSpy = jest.spyOn(view['ollamaClient'], 'chat').mockResolvedValue({
|
||||
role: 'assistant',
|
||||
content: 'Follow-up response',
|
||||
tool_calls: [],
|
||||
});
|
||||
|
||||
// Mock tool executor to return mixed results
|
||||
const toolExecutor = view['toolExecutor'];
|
||||
jest.spyOn(toolExecutor, 'handleToolCall').mockImplementation(async (call) => {
|
||||
if (call.function.name === 'create_file') {
|
||||
return { success: true, message: 'File created successfully' };
|
||||
} else {
|
||||
throw new Error('Tool not found');
|
||||
}
|
||||
});
|
||||
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const errorHandlerSpy = jest.spyOn(ErrorHandler, 'handleError').mockImplementation(() => {});
|
||||
|
||||
await (view as any).handleUserInput('test');
|
||||
|
||||
expect(chatSpy).toHaveBeenCalled();
|
||||
expect(followUpSpy).toHaveBeenCalled(); // Should still call follow-up with partial results
|
||||
expect(errorHandlerSpy).toHaveBeenCalledWith(expect.any(Error), 'ChatView.handleUserInput');
|
||||
expect((view as any).messages.length).toBeGreaterThan(1);
|
||||
consoleSpy.mockRestore();
|
||||
errorHandlerSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should continue processing even when all tool calls fail', async () => {
|
||||
view['sendButton'] = document.createElement('button');
|
||||
view['inputEl'] = document.createElement('textarea');
|
||||
(view['inputEl'] as HTMLTextAreaElement).value = 'test';
|
||||
const chatSpy = jest.spyOn(view['ollamaClient'], 'streamChat').mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
role: 'assistant',
|
||||
content: 'Response with tools',
|
||||
tool_calls: [
|
||||
{
|
||||
id: 'call_1',
|
||||
type: 'function',
|
||||
function: { name: 'nonexistent_tool_1', arguments: '{}' },
|
||||
},
|
||||
{
|
||||
id: 'call_2',
|
||||
type: 'function',
|
||||
function: { name: 'nonexistent_tool_2', arguments: '{}' },
|
||||
},
|
||||
],
|
||||
};
|
||||
})()
|
||||
);
|
||||
const followUpSpy = jest.spyOn(view['ollamaClient'], 'chat').mockResolvedValue({
|
||||
role: 'assistant',
|
||||
content: 'Final response despite tool failures',
|
||||
tool_calls: [],
|
||||
});
|
||||
|
||||
// Mock tool executor to fail for all calls
|
||||
const toolExecutor = view['toolExecutor'];
|
||||
jest.spyOn(toolExecutor, 'handleToolCall').mockImplementation(async () => {
|
||||
throw new Error('Tool not found');
|
||||
});
|
||||
|
||||
const errorHandlerSpy = jest.spyOn(ErrorHandler, 'handleError').mockImplementation(() => {});
|
||||
|
||||
await (view as any).handleUserInput('test');
|
||||
|
||||
expect(chatSpy).toHaveBeenCalled();
|
||||
expect(followUpSpy).not.toHaveBeenCalled(); // Should skip follow-up when no tool results
|
||||
expect(errorHandlerSpy).toHaveBeenCalledTimes(2); // Should be called for each failed tool call
|
||||
expect((view as any).messages.length).toBeGreaterThan(1);
|
||||
errorHandlerSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should skip follow-up when tool calls result in no successful results', async () => {
|
||||
view['sendButton'] = document.createElement('button');
|
||||
view['inputEl'] = document.createElement('textarea');
|
||||
(view['inputEl'] as HTMLTextAreaElement).value = 'test';
|
||||
const chatSpy = jest.spyOn(view['ollamaClient'], 'streamChat').mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
role: 'assistant',
|
||||
content: 'Response with tools',
|
||||
tool_calls: [
|
||||
{
|
||||
id: 'call_1',
|
||||
type: 'function',
|
||||
function: { name: 'nonexistent_tool', arguments: '{}' },
|
||||
},
|
||||
],
|
||||
};
|
||||
})()
|
||||
);
|
||||
const followUpSpy = jest.spyOn(view['ollamaClient'], 'chat').mockResolvedValue({
|
||||
role: 'assistant',
|
||||
content: 'Final response',
|
||||
tool_calls: [],
|
||||
});
|
||||
|
||||
// Mock tool executor to fail for the call
|
||||
const toolExecutor = view['toolExecutor'];
|
||||
jest.spyOn(toolExecutor, 'handleToolCall').mockImplementation(async () => {
|
||||
throw new Error('Tool not found');
|
||||
});
|
||||
|
||||
const errorHandlerSpy = jest.spyOn(ErrorHandler, 'handleError').mockImplementation(() => {});
|
||||
|
||||
await (view as any).handleUserInput('test');
|
||||
|
||||
expect(chatSpy).toHaveBeenCalled();
|
||||
expect(followUpSpy).not.toHaveBeenCalled(); // Should skip follow-up when no tool results
|
||||
expect(errorHandlerSpy).toHaveBeenCalledTimes(1); // Should be called for each failed tool call
|
||||
errorHandlerSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should limit conversation history to maxMessageHistory', async () => {
|
||||
view['sendButton'] = document.createElement('button');
|
||||
view['inputEl'] = document.createElement('textarea');
|
||||
@@ -383,14 +587,16 @@ describe('ChatView', () => {
|
||||
})()
|
||||
);
|
||||
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const errorHandlerSpy = jest.spyOn(ErrorHandler, 'handleError').mockImplementation(() => {});
|
||||
|
||||
await (view as any).handleUserInput('test');
|
||||
|
||||
expect(chatSpy).toHaveBeenCalled();
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
'Ollama Plugin Error [ChatView.handleUserInput]: Network error'
|
||||
expect(errorHandlerSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: 'Network error' }),
|
||||
'ChatView.handleUserInput'
|
||||
);
|
||||
errorHandlerSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('event handlers', () => {
|
||||
|
||||
+172
-16
@@ -5,6 +5,22 @@ describe('OllamaClient', () => {
|
||||
let client: OllamaClient;
|
||||
let mockFetch: jest.Mock;
|
||||
|
||||
function createMockReader(data: string) {
|
||||
const encoder = new TextEncoder();
|
||||
const encoded = encoder.encode(data);
|
||||
let called = false;
|
||||
return {
|
||||
read: () => {
|
||||
if (!called) {
|
||||
called = true;
|
||||
return Promise.resolve({ done: false, value: encoded });
|
||||
}
|
||||
return Promise.resolve({ done: true, value: new Uint8Array(0) });
|
||||
},
|
||||
releaseLock: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
const mockMessages: OllamaMessage[] = [
|
||||
{ role: 'system', content: 'You are helpful.' },
|
||||
{ role: 'user', content: 'Hello' },
|
||||
@@ -107,22 +123,6 @@ describe('OllamaClient', () => {
|
||||
});
|
||||
|
||||
describe('streamChat', () => {
|
||||
function createMockReader(data: string) {
|
||||
const encoder = new TextEncoder();
|
||||
const encoded = encoder.encode(data);
|
||||
let called = false;
|
||||
return {
|
||||
read: () => {
|
||||
if (!called) {
|
||||
called = true;
|
||||
return Promise.resolve({ done: false, value: encoded });
|
||||
}
|
||||
return Promise.resolve({ done: true, value: new Uint8Array(0) });
|
||||
},
|
||||
releaseLock: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
it('should send a streaming request and yield chunks', async () => {
|
||||
const streamData = [
|
||||
JSON.stringify({ message: { content: 'He' } }),
|
||||
@@ -357,6 +357,71 @@ describe('OllamaClient', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('streamChat with retry logic', () => {
|
||||
it('should retry on 5xx errors and eventually succeed', async () => {
|
||||
let callCount = 0;
|
||||
mockFetch.mockImplementation(async () => {
|
||||
callCount++;
|
||||
if (callCount === 1) {
|
||||
return { ok: false, status: 500 };
|
||||
}
|
||||
if (callCount === 2) {
|
||||
return { ok: false, status: 502 };
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
body: {
|
||||
getReader: () => ({
|
||||
read: () => Promise.resolve({ done: true, value: new Uint8Array(0) }),
|
||||
releaseLock: () => {},
|
||||
}),
|
||||
},
|
||||
headers: {
|
||||
get: (name: string) => (name === 'content-type' ? 'application/x-ndjson' : null),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const stream = await client.streamChat(mockMessages, mockTools);
|
||||
const chunks: string[] = [];
|
||||
|
||||
for await (const _ of stream) {
|
||||
chunks.push('chunk');
|
||||
}
|
||||
|
||||
expect(callCount).toBe(3);
|
||||
expect(chunks.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should give up after maxRetries attempts', async () => {
|
||||
mockFetch.mockResolvedValue({ ok: false, status: 500 });
|
||||
|
||||
const stream = await client.streamChat(mockMessages, mockTools);
|
||||
|
||||
await expect(
|
||||
(async () => {
|
||||
for await (const _ of stream) {
|
||||
/* consume */
|
||||
}
|
||||
})()
|
||||
).rejects.toThrow('Ollama API error: 500');
|
||||
});
|
||||
|
||||
it('should not retry on 4xx errors', async () => {
|
||||
mockFetch.mockResolvedValue({ ok: false, status: 404 });
|
||||
|
||||
const stream = await client.streamChat(mockMessages, mockTools);
|
||||
|
||||
await expect(
|
||||
(async () => {
|
||||
for await (const _ of stream) {
|
||||
/* consume */
|
||||
}
|
||||
})()
|
||||
).rejects.toThrow('Ollama API error: 404');
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancelStream', () => {
|
||||
it('should abort the current request', () => {
|
||||
client.cancelStream();
|
||||
@@ -368,4 +433,95 @@ describe('OllamaClient', () => {
|
||||
expect(client['abortController']).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('streamChat final buffer parsing', () => {
|
||||
it('should parse final buffer content when stream ends with partial line', async () => {
|
||||
const streamData = [
|
||||
JSON.stringify({ message: { content: 'First' } }),
|
||||
JSON.stringify({ message: { content: 'Second' } }),
|
||||
JSON.stringify({ message: { content: 'Third' } }),
|
||||
'', // Final line should be empty to signal end
|
||||
].join('\n');
|
||||
|
||||
const mockReader = createMockReader(streamData);
|
||||
|
||||
mockFetch.mockResolvedValue({
|
||||
ok: true,
|
||||
body: { getReader: () => mockReader },
|
||||
headers: {
|
||||
get: (name: string) => (name === 'content-type' ? 'application/x-ndjson' : null),
|
||||
},
|
||||
});
|
||||
|
||||
const stream = await client.streamChat(mockMessages, mockTools);
|
||||
const chunks: string[] = [];
|
||||
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk.content);
|
||||
}
|
||||
|
||||
expect(chunks).toEqual(['First', 'Second', 'Third']);
|
||||
expect(mockReader.releaseLock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle malformed final buffer content gracefully', async () => {
|
||||
const streamData = [
|
||||
JSON.stringify({ message: { content: 'Valid' } }),
|
||||
'malformed json',
|
||||
'', // Final line should be empty to signal end
|
||||
].join('\n');
|
||||
|
||||
const mockReader = createMockReader(streamData);
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
mockFetch.mockResolvedValue({
|
||||
ok: true,
|
||||
body: { getReader: () => mockReader },
|
||||
headers: {
|
||||
get: (name: string) => (name === 'content-type' ? 'application/x-ndjson' : null),
|
||||
},
|
||||
});
|
||||
|
||||
const stream = await client.streamChat(mockMessages, mockTools);
|
||||
const chunks: string[] = [];
|
||||
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk.content);
|
||||
}
|
||||
|
||||
expect(chunks).toEqual(['Valid']);
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Skipped malformed chunk')
|
||||
);
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should parse final buffer content even when it contains message data', async () => {
|
||||
const streamData = [
|
||||
JSON.stringify({ message: { content: 'First' } }),
|
||||
'', // Final line should be empty to signal end
|
||||
JSON.stringify({ message: { content: 'Final' } }),
|
||||
].join('\n');
|
||||
|
||||
const mockReader = createMockReader(streamData);
|
||||
|
||||
mockFetch.mockResolvedValue({
|
||||
ok: true,
|
||||
body: { getReader: () => mockReader },
|
||||
headers: {
|
||||
get: (name: string) => (name === 'content-type' ? 'application/x-ndjson' : null),
|
||||
},
|
||||
});
|
||||
|
||||
const stream = await client.streamChat(mockMessages, mockTools);
|
||||
const chunks: string[] = [];
|
||||
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk.content);
|
||||
}
|
||||
|
||||
expect(chunks).toEqual(['First', 'Final']);
|
||||
expect(mockReader.releaseLock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Unit tests for utility functions, particularly validation functions
|
||||
*/
|
||||
import { LogLevel, Logger } from '../src/utils';
|
||||
import {
|
||||
validateOllamaUrl,
|
||||
validateModelName,
|
||||
@@ -202,3 +203,116 @@ describe('Validation Functions', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Logger', () => {
|
||||
beforeEach(() => {
|
||||
// Reset logger level before each test
|
||||
Logger.setLevel(LogLevel.DEBUG);
|
||||
});
|
||||
|
||||
describe('setLevel', () => {
|
||||
it('should set level using string values', () => {
|
||||
Logger.setLevel('debug');
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.DEBUG);
|
||||
|
||||
Logger.setLevel('info');
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.INFO);
|
||||
|
||||
Logger.setLevel('warn');
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.WARN);
|
||||
|
||||
Logger.setLevel('error');
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.ERROR);
|
||||
});
|
||||
|
||||
it('should set level using numeric values', () => {
|
||||
Logger.setLevel(LogLevel.DEBUG);
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.DEBUG);
|
||||
|
||||
Logger.setLevel(LogLevel.INFO);
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.INFO);
|
||||
|
||||
Logger.setLevel(LogLevel.WARN);
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.WARN);
|
||||
|
||||
Logger.setLevel(LogLevel.ERROR);
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.ERROR);
|
||||
});
|
||||
|
||||
it('should default to DEBUG for unknown string levels', () => {
|
||||
Logger.setLevel('unknown-level');
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.DEBUG);
|
||||
});
|
||||
|
||||
it('should be case insensitive for string levels', () => {
|
||||
Logger.setLevel('DEBUG');
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.DEBUG);
|
||||
|
||||
Logger.setLevel('Info');
|
||||
expect((Logger as any).minLevel).toBe(LogLevel.INFO);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logging methods with different levels', () => {
|
||||
it('should log debug messages when level is DEBUG', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'debug').mockImplementation();
|
||||
Logger.setLevel('debug');
|
||||
|
||||
Logger.debug('test message', 'test-category');
|
||||
Logger.info('test message', 'test-category');
|
||||
Logger.warn('test message', 'test-category');
|
||||
Logger.error('test message', 'test-category');
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith('[test-category] DEBUG: test message');
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not log debug messages when level is INFO', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'debug').mockImplementation();
|
||||
Logger.setLevel('info');
|
||||
|
||||
Logger.debug('test message', 'test-category');
|
||||
Logger.info('test message', 'test-category');
|
||||
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not log debug or info messages when level is WARN', () => {
|
||||
const consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation();
|
||||
const consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
Logger.setLevel('warn');
|
||||
|
||||
Logger.debug('test message', 'test-category');
|
||||
Logger.info('test message', 'test-category');
|
||||
Logger.warn('test message', 'test-category');
|
||||
|
||||
expect(consoleDebugSpy).not.toHaveBeenCalled();
|
||||
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
||||
consoleDebugSpy.mockRestore();
|
||||
consoleInfoSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should only log error messages when level is ERROR', () => {
|
||||
const consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation();
|
||||
const consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
Logger.setLevel('error');
|
||||
|
||||
Logger.debug('test message', 'test-category');
|
||||
Logger.info('test message', 'test-category');
|
||||
Logger.warn('test message', 'test-category');
|
||||
Logger.error('test message', 'test-category');
|
||||
|
||||
expect(consoleDebugSpy).not.toHaveBeenCalled();
|
||||
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith('[test-category] ERROR: test message');
|
||||
consoleDebugSpy.mockRestore();
|
||||
consoleInfoSpy.mockRestore();
|
||||
consoleWarnSpy.mockRestore();
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user