Files
Andrey Kumanyaev 18ca30f9e3 refactor(serverstack)!: select the sqlite backend unconditionally
The server stack now opens the sqlite store for every lifecycle. An empty
backend name still resolves to it, so the daemon and the one-shot embedded
server keep working unchanged, but the in-memory names are refused with an
error that names the replacement: --backend sqlite with a throwaway
--backend-path reproduces the old ephemeral behaviour. Falling back silently
would write a caller who asked for a scratch store into the shared database.

With one backend left, the name check no longer varies by lifecycle: the
per-lifecycle default and the is-it-sqlite predicate collapse into a single
validation that runs before the store lock is taken, and the unused HTTP
lifecycle constant goes with them (the daemon's HTTP surface has always run
as the daemon lifecycle).

*graph.Graph is untouched and still implements the whole Store contract — it
remains the indexer's cold-index staging buffer and the fixture the rest of
the tree's tests build on.
2026-08-06 01:11:54 +02:00

94 lines
3.2 KiB
Go

package serverstack
import (
"os"
"path/filepath"
"strings"
"testing"
"go.uber.org/zap"
"github.com/zzet/gortex/internal/config"
)
// TestNewSharedServer_Oneshot asserts the shared constructor builds a
// working stack over a tmp repo: the graph indexes, and the engine /
// MCP server / overlay manager are wired. This is the single-construction-
// path validation.
func TestNewSharedServer_Oneshot(t *testing.T) {
repo := t.TempDir()
src := "package toy\n\nfunc Add(a, b int) int { return a + b }\nfunc Mul(a, b int) int { return a * b }\n"
if err := os.WriteFile(filepath.Join(repo, "toy.go"), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
ss, err := NewSharedServer(SharedServerConfig{
Lifecycle: LifecycleOneshot,
Index: repo,
// Private per-process store: never the shared default.
BackendPath: filepath.Join(t.TempDir(), "embedded.sqlite"),
Config: config.Default(),
Logger: zap.NewNop(),
Version: "test",
SideStores: SideStores{NotesDir: t.TempDir(), NotesRepo: "test"},
// Pin the savings ledger + legacy-import probe to temp paths:
// with both empty the constructor opens the REAL machine-global
// sidecar and imports (renaming!) the developer's real flat-file
// ledger — a unit test must never mutate ~/.gortex.
SavingsPath: filepath.Join(t.TempDir(), "sidecar.sqlite"),
SavingsLegacyJSON: filepath.Join(t.TempDir(), "savings.json"),
})
if err != nil {
t.Fatalf("NewSharedServer: %v", err)
}
defer ss.Close()
if ss.Graph == nil || ss.Indexer == nil || ss.Engine == nil || ss.MCP == nil {
t.Fatalf("incomplete stack: graph=%v idx=%v eng=%v mcp=%v", ss.Graph != nil, ss.Indexer != nil, ss.Engine != nil, ss.MCP != nil)
}
if ss.Overlays == nil {
t.Error("overlay manager should be wired")
}
if _, err := ss.Indexer.Index(repo); err != nil {
t.Fatalf("Index: %v", err)
}
if n := ss.Graph.Stats().TotalNodes; n == 0 {
t.Fatal("graph should be non-empty after indexing the tmp repo")
}
}
// TestNewSharedServer_LifecyclesDefaultToSqlite asserts every lifecycle
// accepts the empty backend name (there is only the sqlite store), and that
// one-shot alone stays outside the store lock.
func TestNewSharedServer_LifecyclesDefaultToSqlite(t *testing.T) {
if err := checkBackend(""); err != nil {
t.Errorf("the empty backend name must resolve: %v", err)
}
if LifecycleOneshot.Writable() {
t.Error("oneshot must not be writable (no store lock)")
}
if !LifecycleDaemon.Writable() {
t.Error("the daemon lifecycle owns a durable store")
}
}
// TestNewSharedServer_OneshotRefusesSharedStore pins the safety property
// that keeps the unlocked embedded server off the daemon's database: with
// no BackendPath the sqlite path would resolve to ~/.gortex/store, so the
// constructor must refuse instead of opening it.
func TestNewSharedServer_OneshotRefusesSharedStore(t *testing.T) {
_, err := NewSharedServer(SharedServerConfig{
Lifecycle: LifecycleOneshot,
Index: t.TempDir(),
Config: config.Default(),
Logger: zap.NewNop(),
})
if err == nil {
t.Fatal("one-shot without BackendPath must be refused, not pointed at the shared store")
}
if !strings.Contains(err.Error(), "BackendPath") {
t.Errorf("error should name the missing BackendPath, got: %v", err)
}
}