Git Repository
Personal RAG
Personal RAG speichert Texte und strukturierte Fakten lokal in SQLite, durchsucht sie mit FTS5 und stellt sie über MCP per stdio oder Streamable HTTP bereit.
HTTPS
https://zanvex.de/git/personal-rag.gitcmd/personal-rag/main_test.go
Zum Verzeichnispackage main
import (
"context"
"database/sql"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
_ "modernc.org/sqlite"
)
func testApp(t *testing.T) *App {
t.Helper()
path := filepath.Join(t.TempDir(), "knowledge.db")
db, err := sql.Open("sqlite", path)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
a := &App{db: db, dbPath: path}
if err := a.migrate(); err != nil {
t.Fatal(err)
}
return a
}
func TestAddTextDuplicateAndUpdateFTS(t *testing.T) {
a := testApp(t)
ctx := context.Background()
_, first, err := a.addText(ctx, nil, AddTextInput{Title: "Old", Text: "unique old phrase", Tags: []string{"one"}})
if err != nil {
t.Fatal(err)
}
_, duplicate, err := a.addText(ctx, nil, AddTextInput{Title: "Other", Text: "unique old phrase"})
if err != nil {
t.Fatal(err)
}
if !duplicate.AlreadyExists || duplicate.ID != first.ID {
t.Fatalf("duplicate = %+v, first = %+v", duplicate, first)
}
title, content := "New", "fresh searchable phrase"
_, updated, err := a.update(ctx, nil, UpdateInput{ID: first.ID, Title: &title, Content: &content, Tags: []string{"two"}})
if err != nil {
t.Fatal(err)
}
if !updated.Updated {
t.Fatal("expected update")
}
_, found, err := a.search(ctx, nil, SearchInput{Query: "fresh", Limit: 5})
if err != nil {
t.Fatal(err)
}
if len(found.Results) != 1 || found.Results[0].ID != first.ID || found.Results[0].Title != title {
t.Fatalf("results = %+v", found.Results)
}
_, old, err := a.search(ctx, nil, SearchInput{Query: "unique", Limit: 5})
if err != nil {
t.Fatal(err)
}
if len(old.Results) != 0 {
t.Fatalf("old FTS content still found: %+v", old.Results)
}
}
func TestAddFactDuplicate(t *testing.T) {
a := testApp(t)
in := AddFactInput{Subject: "s", Predicate: "p", Value: "v"}
_, first, err := a.addFact(context.Background(), nil, in)
if err != nil {
t.Fatal(err)
}
_, duplicate, err := a.addFact(context.Background(), nil, in)
if err != nil {
t.Fatal(err)
}
if !duplicate.AlreadyExists || duplicate.ID != first.ID {
t.Fatalf("duplicate = %+v, first = %+v", duplicate, first)
}
}
func TestBearerAuth(t *testing.T) {
const token = "test-secret"
tests := []struct {
name string
header string
path string
status int
called bool
}{
{name: "valid header token", header: "Bearer " + token, path: "/", status: http.StatusNoContent, called: true},
{name: "valid path token", path: "/token=" + token, status: http.StatusNoContent, called: true},
{name: "missing token", path: "/", status: http.StatusUnauthorized},
{name: "wrong token", header: "Bearer wrong", path: "/", status: http.StatusUnauthorized},
{name: "wrong path token", path: "/token=wrong", status: http.StatusUnauthorized},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
called := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true
w.WriteHeader(http.StatusNoContent)
})
req := httptest.NewRequest(http.MethodPost, tt.path, nil)
if tt.header != "" {
req.Header.Set("Authorization", tt.header)
}
response := httptest.NewRecorder()
bearerAuth(next, token).ServeHTTP(response, req)
if response.Code != tt.status {
t.Fatalf("status = %d, want %d", response.Code, tt.status)
}
if called != tt.called {
t.Fatalf("handler called = %v, want %v", called, tt.called)
}
if tt.called && req.URL.Path != "/" {
t.Fatalf("handler path = %q, want /", req.URL.Path)
}
})
}
}