mirror of
https://github.com/lukaszraczylo/compaction-mcp.git
synced 2026-07-05 04:04:57 +00:00
Add release infrastructure and complete implementation
- Dockerfile: distroless container for MCP server - GoReleaser: multi-platform binary and Docker builds with cosign signing - GitHub Actions: release workflow using shared actions - Semver config for automatic version calculation - Persistence layer, content indexing, and improved tool handlers
This commit is contained in:
+178
@@ -0,0 +1,178 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPersistSaveLoad(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
store1 := NewStore(10000)
|
||||
|
||||
if _, err := store1.Add("first item content", "first summary", []string{"tag1"}, 5); err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
if _, err := store1.Add("second item content", "", []string{"tag2", "tag3"}, 8); err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
|
||||
p1, err := NewPersister(dir, store1)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPersister: %v", err)
|
||||
}
|
||||
|
||||
errSave := p1.Save()
|
||||
if errSave != nil {
|
||||
t.Fatalf("Save: %v", errSave)
|
||||
}
|
||||
|
||||
// Load into a fresh store and verify roundtrip.
|
||||
store2 := NewStore(10000)
|
||||
p2, errP2 := NewPersister(dir, store2)
|
||||
if errP2 != nil {
|
||||
t.Fatalf("NewPersister (store2): %v", errP2)
|
||||
}
|
||||
|
||||
errLoad := p2.Load()
|
||||
if errLoad != nil {
|
||||
t.Fatalf("Load: %v", errLoad)
|
||||
}
|
||||
|
||||
_, _, count1, _ := store1.Status()
|
||||
_, _, count2, _ := store2.Status()
|
||||
|
||||
if count2 != count1 {
|
||||
t.Fatalf("item count mismatch: got %d, want %d", count2, count1)
|
||||
}
|
||||
|
||||
// Verify individual items survived the roundtrip.
|
||||
items := store2.Query("", nil, 100)
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("expected 2 items from query, got %d", len(items))
|
||||
}
|
||||
|
||||
found := make(map[string]bool)
|
||||
for _, item := range items {
|
||||
found[item.Content] = true
|
||||
if item.Tokens <= 0 {
|
||||
t.Errorf("item %s has non-positive token count: %d", item.ID, item.Tokens)
|
||||
}
|
||||
}
|
||||
|
||||
if !found["first item content"] {
|
||||
t.Error("missing 'first item content' after load")
|
||||
}
|
||||
if !found["second item content"] {
|
||||
t.Error("missing 'second item content' after load")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistAtomicWrite(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
store := NewStore(10000)
|
||||
if _, err := store.Add("test content", "", nil, 5); err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
|
||||
p, err := NewPersister(dir, store)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPersister: %v", err)
|
||||
}
|
||||
|
||||
errSave := p.Save()
|
||||
if errSave != nil {
|
||||
t.Fatalf("Save: %v", errSave)
|
||||
}
|
||||
|
||||
// state.json should exist.
|
||||
_, errStat := os.Stat(filepath.Join(dir, "state.json"))
|
||||
if errStat != nil {
|
||||
t.Fatalf("state.json missing: %v", errStat)
|
||||
}
|
||||
|
||||
// Temp file should not linger.
|
||||
_, errTmp := os.Stat(filepath.Join(dir, "state.json.tmp"))
|
||||
if !os.IsNotExist(errTmp) {
|
||||
t.Fatal("state.json.tmp should not exist after successful save")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistLoadMissing(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
store := NewStore(10000)
|
||||
|
||||
p, err := NewPersister(dir, store)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPersister: %v", err)
|
||||
}
|
||||
|
||||
// Loading from a directory with no state.json should succeed (fresh start).
|
||||
errLoad := p.Load()
|
||||
if errLoad != nil {
|
||||
t.Fatalf("Load on missing file should return nil, got: %v", errLoad)
|
||||
}
|
||||
|
||||
_, _, count, _ := store.Status()
|
||||
if count != 0 {
|
||||
t.Fatalf("expected 0 items after loading missing file, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistLoadCorrupted(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
// Write garbage to state.json.
|
||||
errWrite := os.WriteFile(filepath.Join(dir, "state.json"), []byte("{{{garbage"), 0o600)
|
||||
if errWrite != nil {
|
||||
t.Fatalf("writing corrupt file: %v", errWrite)
|
||||
}
|
||||
|
||||
store := NewStore(10000)
|
||||
p, err := NewPersister(dir, store)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPersister: %v", err)
|
||||
}
|
||||
|
||||
if p.Load() == nil {
|
||||
t.Fatal("Load should return error for corrupted file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistMarkDirty(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
store := NewStore(10000)
|
||||
|
||||
p, err := NewPersister(dir, store)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPersister: %v", err)
|
||||
}
|
||||
|
||||
// Initially not dirty -- Start + Stop should not create state.json.
|
||||
p.Start(time.Hour) // long interval so tick won't fire
|
||||
p.Stop()
|
||||
|
||||
_, errStat := os.Stat(filepath.Join(dir, "state.json"))
|
||||
if !os.IsNotExist(errStat) {
|
||||
t.Fatal("state.json should not exist when not dirty")
|
||||
}
|
||||
|
||||
// Mark dirty, then Stop should trigger a save.
|
||||
if _, err := store.Add("dirty item", "", nil, 5); err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
p2, errP2 := NewPersister(dir, store)
|
||||
if errP2 != nil {
|
||||
t.Fatalf("NewPersister: %v", errP2)
|
||||
}
|
||||
|
||||
p2.Start(time.Hour)
|
||||
p2.MarkDirty()
|
||||
p2.Stop()
|
||||
|
||||
_, errFinal := os.Stat(filepath.Join(dir, "state.json"))
|
||||
if errFinal != nil {
|
||||
t.Fatalf("state.json should exist after dirty stop: %v", errFinal)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user