@@ -295,15 +295,18 @@ func TestEvictionCapEnforced(t *testing.T) {
295295 dir := t .TempDir ()
296296 cfg := DefaultConfig ()
297297 cfg .Enabled = boolPtr (true )
298- cfg .MaxSizeMB = 1
299- cfg .AtomMaxChars = 1_000_000
298+ cfg .AtomMaxChars = 100_000
300299 em := New (dir , newMockLLM (), cfg )
301- for i := 0 ; i < 4 ; i ++ {
302- atom := MemoryAtom {Text : strings .Repeat ("x" , 600_000 ), SourceClass : SourceUserSaid }
300+ em .testCapBytes = 50 * 1024
301+ em .index .newEmb = func () embedding.TextEmbedder { return newMockEmbedder (vectorDim ) }
302+ em .index .emb = newMockEmbedder (vectorDim )
303+ defer em .Close ()
304+ for i := 0 ; i < 10 ; i ++ {
305+ atom := MemoryAtom {Text : strings .Repeat ("x" , 8_000 ), SourceClass : SourceUserSaid }
303306 _ = em .AddAtom (context .Background (), atom )
304307 }
305308 atoms , _ := em .List ()
306- if len (atoms ) >= 4 {
309+ if len (atoms ) >= 10 {
307310 t .Errorf ("expected eviction to reduce atom count, got %d" , len (atoms ))
308311 }
309312}
@@ -312,10 +315,13 @@ func TestEvictionPinProtected(t *testing.T) {
312315 dir := t .TempDir ()
313316 cfg := DefaultConfig ()
314317 cfg .Enabled = boolPtr (true )
315- cfg .MaxSizeMB = 1
316- cfg .AtomMaxChars = 1_000_000
318+ cfg .AtomMaxChars = 100_000
317319 em := New (dir , newMockLLM (), cfg )
318- pinned := MemoryAtom {Text : strings .Repeat ("pinned" , 150_000 ), SourceClass : SourceUserSaid }
320+ em .testCapBytes = 50 * 1024
321+ em .index .newEmb = func () embedding.TextEmbedder { return newMockEmbedder (vectorDim ) }
322+ em .index .emb = newMockEmbedder (vectorDim )
323+ defer em .Close ()
324+ pinned := MemoryAtom {Text : strings .Repeat ("pinned" , 5_000 ), SourceClass : SourceUserSaid }
319325 _ = em .AddAtom (context .Background (), pinned )
320326 atoms , _ := em .List ()
321327 if len (atoms ) != 1 {
@@ -324,8 +330,8 @@ func TestEvictionPinProtected(t *testing.T) {
324330 if err := em .store .Pin (atoms [0 ].ID , true ); err != nil {
325331 t .Fatal (err )
326332 }
327- for i := 0 ; i < 4 ; i ++ {
328- _ = em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("x" , 600_000 ), SourceClass : SourceUserSaid })
333+ for i := 0 ; i < 10 ; i ++ {
334+ _ = em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("x" , 8_000 ), SourceClass : SourceUserSaid })
329335 }
330336 got , _ := em .store .Get (atoms [0 ].ID )
331337 if got .ID != atoms [0 ].ID {
@@ -377,10 +383,14 @@ func TestQuarantineCountsTowardSize(t *testing.T) {
377383 dir := t .TempDir ()
378384 cfg := DefaultConfig ()
379385 cfg .Enabled = boolPtr (true )
380- cfg .MaxSizeMB = 1
386+ cfg .AtomMaxChars = 100_000
381387 em := New (dir , newMockLLM (), cfg )
382- for i := 0 ; i < 4 ; i ++ {
383- _ = em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("x" , 600_000 ), SourceClass : SourceWeb })
388+ em .testCapBytes = 50 * 1024
389+ em .index .newEmb = func () embedding.TextEmbedder { return newMockEmbedder (vectorDim ) }
390+ em .index .emb = newMockEmbedder (vectorDim )
391+ defer em .Close ()
392+ for i := 0 ; i < 10 ; i ++ {
393+ _ = em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("x" , 8_000 ), SourceClass : SourceWeb })
384394 }
385395 if em .Size () == 0 {
386396 t .Error ("expected non-zero size from quarantined atoms" )
@@ -391,13 +401,14 @@ func TestCompactionTriggeredAfterHeavyEviction(t *testing.T) {
391401 dir := t .TempDir ()
392402 cfg := DefaultConfig ()
393403 cfg .Enabled = boolPtr (true )
394- cfg .MaxSizeMB = 1
395- cfg .AtomMaxChars = 1_000_000
404+ cfg .AtomMaxChars = 100_000
396405 em := New (dir , newMockLLM (), cfg )
406+ em .testCapBytes = 50 * 1024
397407 em .index .newEmb = func () embedding.TextEmbedder { return newMockEmbedder (vectorDim ) }
398408 em .index .emb = newMockEmbedder (vectorDim )
399- for i := 0 ; i < 8 ; i ++ {
400- _ = em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("x" , 200_000 ), SourceClass : SourceUserSaid })
409+ defer em .Close ()
410+ for i := 0 ; i < 12 ; i ++ {
411+ _ = em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("x" , 6_000 ), SourceClass : SourceUserSaid })
401412 }
402413 // Heavy eviction should have triggered compaction.
403414 if em .Size () == 0 {
@@ -613,25 +624,73 @@ func TestEvictionAllPinned(t *testing.T) {
613624 dir := t .TempDir ()
614625 cfg := DefaultConfig ()
615626 cfg .Enabled = boolPtr (true )
616- cfg .MaxSizeMB = 1
617- cfg .AtomMaxChars = 1_000_000
627+ cfg .AtomMaxChars = 100_000
618628 em := New (dir , newMockLLM (), cfg )
629+ em .testCapBytes = 50 * 1024
630+ em .index .newEmb = func () embedding.TextEmbedder { return newMockEmbedder (vectorDim ) }
631+ em .index .emb = newMockEmbedder (vectorDim )
632+ defer em .Close ()
619633 for i := 0 ; i < 3 ; i ++ {
620- _ = em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("p" , 150_000 ), SourceClass : SourceUserSaid })
634+ _ = em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("p" , 8_000 ), SourceClass : SourceUserSaid })
621635 }
622636 live , _ := em .List ()
623637 for _ , a := range live {
624638 _ = em .store .Pin (a .ID , true )
625639 }
626- // Adding another atom should not evict pinned atoms; error or no-op is acceptable.
627- _ = em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("x" , 600_000 ), SourceClass : SourceUserSaid })
640+ // Adding another atom should fail because no evictable atoms exist.
641+ err := em .AddAtom (context .Background (), MemoryAtom {Text : strings .Repeat ("x" , 30_000 ), SourceClass : SourceUserSaid })
642+ if err == nil {
643+ t .Error ("expected AddAtom to fail when all atoms are pinned and cap would be exceeded" )
644+ }
628645 for _ , a := range live {
629646 if _ , err := em .store .Get (a .ID ); err != nil {
630647 t .Error ("pinned atom was evicted" )
631648 }
632649 }
633650}
634651
652+ func TestCapFailClosedWhenAllPinned (t * testing.T ) {
653+ dir := t .TempDir ()
654+ cfg := DefaultConfig ()
655+ cfg .Enabled = boolPtr (true )
656+ cfg .AtomMaxChars = 100_000
657+ em := New (dir , newMockLLM (), cfg )
658+ em .testCapBytes = 50 * 1024
659+ em .index .newEmb = func () embedding.TextEmbedder { return newMockEmbedder (vectorDim ) }
660+ em .index .emb = newMockEmbedder (vectorDim )
661+ defer em .Close ()
662+
663+ // Fill the store with pinned atoms that consume nearly the whole cap.
664+ for i := 0 ; i < 5 ; i ++ {
665+ a := MemoryAtom {Text : strings .Repeat ("p" , 8_000 ), SourceClass : SourceUserSaid }
666+ if err := em .AddAtom (context .Background (), a ); err != nil {
667+ break
668+ }
669+ }
670+ live , _ := em .List ()
671+ if len (live ) == 0 {
672+ t .Fatal ("expected at least one live atom" )
673+ }
674+ for _ , a := range live {
675+ _ = em .store .Pin (a .ID , true )
676+ }
677+
678+ before , _ := em .store .List ()
679+ sz , _ := em .store .Size ()
680+ if sz <= 0 {
681+ t .Fatal ("expected positive store size" )
682+ }
683+ // Attempt to add another atom; it must be rejected.
684+ incoming := MemoryAtom {Text : strings .Repeat ("x" , 30_000 ), SourceClass : SourceUserSaid }
685+ if err := em .AddAtom (context .Background (), incoming ); err == nil {
686+ t .Error ("expected AddAtom to fail when cap exceeded and all atoms pinned" )
687+ }
688+ after , _ := em .store .List ()
689+ if len (after ) != len (before ) {
690+ t .Errorf ("expected pinned atom count unchanged, got %d before %d after" , len (before ), len (after ))
691+ }
692+ }
693+
635694func TestCapDisabled (t * testing.T ) {
636695 dir := t .TempDir ()
637696 cfg := DefaultConfig ()
@@ -702,3 +761,81 @@ func TestQuarantineScanBeforeStore(t *testing.T) {
702761 t .Errorf ("expected 0 quarantined atoms after scan rejection, got %d" , len (quarantined ))
703762 }
704763}
764+
765+ func TestSetEmbedderAndFactory (t * testing.T ) {
766+ dir := t .TempDir ()
767+ cfg := DefaultConfig ()
768+ cfg .Enabled = boolPtr (true )
769+ em := New (dir , newMockLLM (), cfg )
770+ defer em .Close ()
771+
772+ emb := newMockEmbedder (vectorDim )
773+ em .SetEmbedder (emb )
774+ if em .index .emb != emb {
775+ t .Error ("SetEmbedder did not set active embedder" )
776+ }
777+
778+ called := false
779+ em .SetEmbedderFactory (func () embedding.TextEmbedder {
780+ called = true
781+ return newMockEmbedder (vectorDim )
782+ })
783+ _ = em .index .newEmb ()
784+ if ! called {
785+ t .Error ("SetEmbedderFactory did not replace factory" )
786+ }
787+ }
788+
789+ func TestMarkDirtyAndCompact (t * testing.T ) {
790+ dir := t .TempDir ()
791+ cfg := DefaultConfig ()
792+ cfg .Enabled = boolPtr (true )
793+ em := New (dir , newMockLLM (), cfg )
794+ em .index .newEmb = func () embedding.TextEmbedder { return newMockEmbedder (vectorDim ) }
795+ em .index .emb = newMockEmbedder (vectorDim )
796+ em .MarkDirty ()
797+ if ! em .index .dirty {
798+ t .Error ("MarkDirty did not mark index dirty" )
799+ }
800+ em .Compact ()
801+ em .Close ()
802+ // Compaction should complete without panic; background goroutine drains.
803+ }
804+
805+ func TestUserModelAndAssociationsStubs (t * testing.T ) {
806+ um := NewUserModel ()
807+ um .Update (MemoryAtom {Text : "x" }) // should not panic
808+ if got := um .Summary (); got != "" {
809+ t .Errorf ("expected empty summary, got %q" , got )
810+ }
811+
812+ assoc := NewAssociations ()
813+ assoc .Link ("a" , "b" ) // should not panic
814+ if got := assoc .Related ("a" ); got != nil {
815+ t .Errorf ("expected nil related atoms, got %v" , got )
816+ }
817+ }
818+
819+ func TestAddAtomsBatchFailurePath (t * testing.T ) {
820+ dir := t .TempDir ()
821+ cfg := DefaultConfig ()
822+ cfg .Enabled = boolPtr (true )
823+ em := New (dir , newMockLLM (), cfg )
824+ defer em .Close ()
825+
826+ // Inject an invalid atom that the store will reject to exercise the error
827+ // logging path; valid atoms should still be added.
828+ atoms := []MemoryAtom {
829+ {ID : "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" , Text : "valid one" , SourceClass : SourceUserSaid },
830+ {ID : "" , Text : "" , SourceClass : SourceUserSaid }, // empty text will be rejected
831+ {ID : "b1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" , Text : "valid two" , SourceClass : SourceUserSaid },
832+ }
833+ if err := em .AddAtoms (context .Background (), atoms ); err != nil {
834+ t .Fatalf ("AddAtoms returned unexpected error: %v" , err )
835+ }
836+ live , _ := em .List ()
837+ if len (live ) != 2 {
838+ t .Errorf ("expected 2 live atoms, got %d" , len (live ))
839+ }
840+ }
841+
0 commit comments