Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 225 additions & 0 deletions compact/compact_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package compact

import (
"context"
"path/filepath"
"testing"

"github.com/google/uuid"
"github.com/GrayCodeAI/yaad/storage"
)

func setupStore(t *testing.T) storage.Storage {
t.Helper()
dir := t.TempDir()
store, err := storage.NewStore(filepath.Join(dir, "test.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { store.Close() })
return store
}

func TestNeedsCompaction_EmptyProject(t *testing.T) {
store := setupStore(t)
c := New(store, 100)
ctx := context.Background()

needs, tokens := c.NeedsCompaction(ctx, "proj1")
if needs {
t.Error("empty project should not need compaction")
}
if tokens != 0 {
t.Errorf("expected 0 tokens, got %d", tokens)
}
}

func TestNeedsCompaction_OverBudget(t *testing.T) {
store := setupStore(t)
ctx := context.Background()

// Create nodes with enough content to exceed a low token budget
for i := 0; i < 5; i++ {
content := make([]byte, 200)
for j := range content {
content[j] = 'a'
}
_ = store.CreateNode(ctx, &storage.Node{
ID: uuid.New().String(),
Type: "convention",
Content: string(content),
ContentHash: uuid.New().String(),
Scope: "project",
Project: "proj1",
Confidence: 0.3,
Version: 1,
})
}

// 5 nodes * 200 chars / 4 = 250 tokens. Budget = 100.
c := New(store, 100)
needs, tokens := c.NeedsCompaction(ctx, "proj1")
if !needs {
t.Errorf("expected compaction needed, tokens=%d", tokens)
}
if tokens < 200 {
t.Errorf("expected tokens >= 200, got %d", tokens)
}
}

func TestCompact_NoNodesNoError(t *testing.T) {
store := setupStore(t)
c := New(store, 100)
ctx := context.Background()

count, err := c.Compact(ctx, "empty-project")
if err != nil {
t.Fatalf("Compact on empty project: %v", err)
}
if count != 0 {
t.Errorf("expected 0 compacted, got %d", count)
}
}

func TestCompact_SkipsHighConfidenceNodes(t *testing.T) {
store := setupStore(t)
ctx := context.Background()

// Create high-confidence nodes - should not be compacted
for i := 0; i < 5; i++ {
_ = store.CreateNode(ctx, &storage.Node{
ID: uuid.New().String(),
Type: "convention",
Content: "high confidence content",
ContentHash: uuid.New().String(),
Scope: "project",
Project: "proj1",
Confidence: 0.9,
AccessCount: 10,
Version: 1,
})
}

c := New(store, 10)
count, err := c.Compact(ctx, "proj1")
if err != nil {
t.Fatalf("Compact: %v", err)
}
if count != 0 {
t.Errorf("high-confidence nodes should not be compacted, got %d", count)
}
}

func TestCompact_CompactsLowConfidenceNodes(t *testing.T) {
store := setupStore(t)
ctx := context.Background()

// Create 4 low-confidence convention nodes (> 3 threshold)
var ids []string
for i := 0; i < 4; i++ {
id := uuid.New().String()
ids = append(ids, id)
_ = store.CreateNode(ctx, &storage.Node{
ID: id,
Type: "convention",
Content: "low confidence content item",
ContentHash: uuid.New().String(),
Scope: "project",
Project: "proj1",
Confidence: 0.3,
AccessCount: 1,
Version: 1,
})
}

c := New(store, 10)
count, err := c.Compact(ctx, "proj1")
if err != nil {
t.Fatalf("Compact: %v", err)
}
if count != 4 {
t.Errorf("expected 4 compacted nodes, got %d", count)
}

// Verify compacted nodes have confidence 0
for _, id := range ids {
n, _ := store.GetNode(ctx, id)
if n != nil && n.Confidence != 0 {
t.Errorf("node %s should have confidence 0 after compaction, got %f", id, n.Confidence)
}
}
}

func TestCompact_SkipsAnchorTypes(t *testing.T) {
store := setupStore(t)
ctx := context.Background()

// Create low-confidence "file" nodes - should NOT be compacted (anchor type)
for i := 0; i < 5; i++ {
_ = store.CreateNode(ctx, &storage.Node{
ID: uuid.New().String(),
Type: "file",
Content: "file anchor content",
ContentHash: uuid.New().String(),
Scope: "project",
Project: "proj1",
Confidence: 0.1,
AccessCount: 0,
Version: 1,
})
}

c := New(store, 10)
count, err := c.Compact(ctx, "proj1")
if err != nil {
t.Fatalf("Compact: %v", err)
}
if count != 0 {
t.Errorf("file/entity/session types should be skipped, got %d", count)
}
}

func TestCompact_CancelledContext(t *testing.T) {
store := setupStore(t)
c := New(store, 100)
ctx, cancel := context.WithCancel(context.Background())
cancel()

_, err := c.Compact(ctx, "proj1")
if err == nil {
t.Error("expected error from cancelled context")
}
}

func TestDefaultSummarizer(t *testing.T) {
s := DefaultSummarizer{}
ctx := context.Background()

contents := []string{"item one", "item two", "item three"}
summary, err := s.Summarize(ctx, "convention", contents)
if err != nil {
t.Fatalf("Summarize: %v", err)
}
if summary == "" {
t.Error("expected non-empty summary")
}
if len(summary) == 0 {
t.Error("summary should contain content")
}
}

func TestNew_DefaultMaxTokens(t *testing.T) {
store := setupStore(t)
c := New(store, 0)
if c.maxTokens != 50000 {
t.Errorf("expected default maxTokens=50000, got %d", c.maxTokens)
}
}

func TestNew_NegativeMaxTokens(t *testing.T) {
store := setupStore(t)
c := New(store, -10)
if c.maxTokens != 50000 {
t.Errorf("expected default maxTokens=50000 for negative input, got %d", c.maxTokens)
}
}
Loading
Loading