147 lines
3.9 KiB
Go
147 lines
3.9 KiB
Go
package database
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/kandev/kandev/internal/system/jobs"
|
|
)
|
|
|
|
// httpGet builds a GET request and is shared by stats/maintenance/reset tests.
|
|
func httpGet(t *testing.T, path string) *http.Request {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, path, nil)
|
|
if err != nil {
|
|
t.Fatalf("http.NewRequest: %v", err)
|
|
}
|
|
return req
|
|
}
|
|
|
|
// httpPost builds a POST request with an optional JSON body.
|
|
func httpPost(t *testing.T, path string, body string) *http.Request {
|
|
t.Helper()
|
|
var reader *bytes.Reader
|
|
if body == "" {
|
|
reader = bytes.NewReader(nil)
|
|
} else {
|
|
reader = bytes.NewReader([]byte(body))
|
|
}
|
|
req, err := http.NewRequest(http.MethodPost, path, reader)
|
|
if err != nil {
|
|
t.Fatalf("http.NewRequest: %v", err)
|
|
}
|
|
if body != "" {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
return req
|
|
}
|
|
|
|
func serveHTTP(r *gin.Engine, req *http.Request) *httptest.ResponseRecorder {
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
return w
|
|
}
|
|
|
|
func contains(haystack, needle string) bool { return strings.Contains(haystack, needle) }
|
|
|
|
func TestVacuum_StartsJobAndShrinksDB(t *testing.T) {
|
|
svc, tracker, _, _ := newTestService(t)
|
|
|
|
before, err := svc.Stats()
|
|
if err != nil {
|
|
t.Fatalf("stats before: %v", err)
|
|
}
|
|
|
|
id := svc.Vacuum(context.Background())
|
|
if id == "" {
|
|
t.Fatal("Vacuum returned empty job id")
|
|
}
|
|
job := waitForState(t, tracker, id, jobs.StateSucceeded)
|
|
if job.Result["reclaimed_bytes"] == nil {
|
|
t.Errorf("expected reclaimed_bytes in result, got %+v", job.Result)
|
|
}
|
|
|
|
after, err := svc.Stats()
|
|
if err != nil {
|
|
t.Fatalf("stats after: %v", err)
|
|
}
|
|
// DB file must still exist (Stats reads from it).
|
|
if after.Path != before.Path {
|
|
t.Errorf("path changed: %s -> %s", before.Path, after.Path)
|
|
}
|
|
if after.SizeBytes <= 0 {
|
|
t.Errorf("size after vacuum = %d, want > 0", after.SizeBytes)
|
|
}
|
|
// schema_version preserved across vacuum.
|
|
if after.SchemaVersion != "v0.99.0" {
|
|
t.Errorf("schema_version lost after vacuum: %q", after.SchemaVersion)
|
|
}
|
|
}
|
|
|
|
func TestOptimize_StartsJobAndSucceeds(t *testing.T) {
|
|
svc, tracker, _, _ := newTestService(t)
|
|
|
|
id := svc.Optimize(context.Background())
|
|
if id == "" {
|
|
t.Fatal("Optimize returned empty job id")
|
|
}
|
|
job := waitForState(t, tracker, id, jobs.StateSucceeded)
|
|
if job.State != jobs.StateSucceeded {
|
|
t.Errorf("state = %s, want succeeded", job.State)
|
|
}
|
|
|
|
// Verify a follow-up query against the DB still works (sanity).
|
|
stats, err := svc.Stats()
|
|
if err != nil {
|
|
t.Fatalf("post-optimize stats: %v", err)
|
|
}
|
|
if stats.SchemaVersion != "v0.99.0" {
|
|
t.Errorf("schema_version lost after optimize: %q", stats.SchemaVersion)
|
|
}
|
|
}
|
|
|
|
func TestHandleVacuum_Returns202WithJobID(t *testing.T) {
|
|
svc, tracker, _, _ := newTestService(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.POST("/vacuum", HandleVacuum(svc))
|
|
|
|
w := serveHTTP(r, httpPost(t, "/vacuum", ""))
|
|
if w.Code != http.StatusAccepted {
|
|
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if !contains(w.Body.String(), `"job_id"`) {
|
|
t.Errorf("body missing job_id: %s", w.Body.String())
|
|
}
|
|
// Make sure the spawned job finishes before the test exits so the temp dir
|
|
// cleanup does not race with VACUUM still writing the SQLite journal.
|
|
for _, j := range tracker.List() {
|
|
waitForState(t, tracker, j.ID, jobs.StateSucceeded)
|
|
}
|
|
}
|
|
|
|
func TestHandleOptimize_Returns202WithJobID(t *testing.T) {
|
|
svc, tracker, _, _ := newTestService(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.POST("/optimize", HandleOptimize(svc))
|
|
|
|
w := serveHTTP(r, httpPost(t, "/optimize", ""))
|
|
if w.Code != http.StatusAccepted {
|
|
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if !contains(w.Body.String(), `"job_id"`) {
|
|
t.Errorf("body missing job_id: %s", w.Body.String())
|
|
}
|
|
// Same cleanup-race guard as TestHandleVacuum_Returns202WithJobID.
|
|
for _, j := range tracker.List() {
|
|
waitForState(t, tracker, j.ID, jobs.StateSucceeded)
|
|
}
|
|
}
|