Files
sub-store/internal/database/repo_test.go
T

295 lines
7.0 KiB
Go

package database
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/jmoiron/sqlx"
_ "modernc.org/sqlite"
"github.com/peterqiu0516/sub-store/internal/model"
)
func testDB(t *testing.T) *sqlx.DB {
t.Helper()
dir := t.TempDir()
dbPath := filepath.Join(dir, "test.db")
db, err := sqlx.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=foreign_keys(on)")
if err != nil {
t.Fatalf("failed to open db: %v", err)
}
if err := RunMigrations(db); err != nil {
t.Fatalf("failed to run migrations: %v", err)
}
return db
}
func TestSourceCRUD(t *testing.T) {
db := testDB(t)
defer db.Close()
repo := NewSourceRepo(db)
// Create
src := model.SourceRecord{
ID: "test-src",
Name: "Test Source",
Type: "local",
Content: "ss://pass@host:port#name",
Enabled: true,
Filters: []model.FilterRule{},
Meta: map[string]any{},
}
created, err := repo.Upsert(src)
if err != nil {
t.Fatalf("create failed: %v", err)
}
if created.ID != "test-src" {
t.Errorf("expected id test-src, got %s", created.ID)
}
// Read
got, err := repo.Get("test-src")
if err != nil {
t.Fatalf("get failed: %v", err)
}
if got.Name != "Test Source" {
t.Errorf("expected name Test Source, got %s", got.Name)
}
// List
list, err := repo.List()
if err != nil {
t.Fatalf("list failed: %v", err)
}
if len(list) != 1 {
t.Errorf("expected 1 source, got %d", len(list))
}
// Update
src.Name = "Updated"
src.URL = "https://example.com/sub"
src.Type = "remote"
updated, err := repo.Upsert(src)
if err != nil {
t.Fatalf("update failed: %v", err)
}
if updated.Name != "Updated" {
t.Errorf("expected name Updated, got %s", updated.Name)
}
// Delete — use RecycleRepo for archive + delete
recycleRepo := NewRecycleRepo(db)
_, err = recycleRepo.ArchiveAndDelete("source", "test-src", map[string]any{"id": "test-src"}, "DELETE FROM sources WHERE id = ?", "test-src")
if err != nil {
t.Fatalf("delete failed: %v", err)
}
got, err = repo.Get("test-src")
if got != nil {
t.Error("expected nil after delete")
}
}
func TestCollectionCRUD(t *testing.T) {
db := testDB(t)
defer db.Close()
// Create a source first
srcRepo := NewSourceRepo(db)
srcRepo.Upsert(model.SourceRecord{
ID: "src1", Name: "Src1", Type: "local", Content: "ss://pass@host:port#name",
Enabled: true, Filters: []model.FilterRule{}, Meta: map[string]any{},
})
repo := NewCollectionRepo(db)
col := model.CollectionRecord{
ID: "test-col",
Name: "Test Collection",
SourceIds: []string{"src1"},
Filters: []model.FilterRule{},
TemplateId: "default",
IgnoreFailed: true,
Enabled: true,
Meta: map[string]any{},
}
created, err := repo.Upsert(col)
if err != nil {
t.Fatalf("create failed: %v", err)
}
if created.ID != "test-col" {
t.Errorf("expected id test-col, got %s", created.ID)
}
got, err := repo.Get("test-col")
if err != nil {
t.Fatalf("get failed: %v", err)
}
if len(got.SourceIds) != 1 || got.SourceIds[0] != "src1" {
t.Errorf("expected sourceIds [src1], got %v", got.SourceIds)
}
}
func TestTemplateCRUD(t *testing.T) {
db := testDB(t)
defer db.Close()
repo := NewTemplateRepo(db)
tmpl := model.TemplateRecord{
ID: "test-tmpl",
Name: "Test Template",
Target: "mihomo",
Config: map[string]any{
"proxy-groups": []any{
map[string]any{"name": "PROXY", "type": "select"},
},
},
}
created, err := repo.Upsert(tmpl)
if err != nil {
t.Fatalf("create failed: %v", err)
}
if created.ID != "test-tmpl" {
t.Errorf("expected id test-tmpl, got %s", created.ID)
}
got, err := repo.Get("test-tmpl")
if err != nil {
t.Fatalf("get failed: %v", err)
}
if got.Target != "mihomo" {
t.Errorf("expected target mihomo, got %s", got.Target)
}
}
func TestSettingsCRUD(t *testing.T) {
db := testDB(t)
defer db.Close()
repo := NewSettingsRepo(db)
// Get default (empty)
settings, err := repo.Get()
if err != nil {
t.Fatalf("get failed: %v", err)
}
if len(settings) != 0 {
t.Errorf("expected empty settings initially, got %d keys", len(settings))
}
// Update
updated, err := repo.Update(map[string]any{
"theme": map[string]any{"name": "dark"},
"appName": "MyStore",
})
if err != nil {
t.Fatalf("update failed: %v", err)
}
if updated["appName"] != "MyStore" {
t.Errorf("expected appName MyStore, got %v", updated["appName"])
}
// Get again
settings, err = repo.Get()
if err != nil {
t.Fatalf("get after update failed: %v", err)
}
if settings["appName"] != "MyStore" {
t.Errorf("expected appName MyStore, got %v", settings["appName"])
}
}
func TestCacheCRUD(t *testing.T) {
db := testDB(t)
defer db.Close()
repo := NewCacheRepo(db)
// Put
repo.SafePut("cache-key-1", "cached content", map[string]any{"etag": "abc"}, 300)
// Get
entry, ok := repo.SafeGet("cache-key-1")
if !ok {
t.Fatal("expected cache hit")
}
if entry.Content != "cached content" {
t.Errorf("expected content 'cached content', got %s", entry.Content)
}
// Test expiry — put with TTL 1 and wait for it to expire
repo.SafePut("expired-key", "old", map[string]any{}, 1)
time.Sleep(2 * time.Second)
_, ok = repo.SafeGet("expired-key")
if ok {
t.Error("expected cache miss for expired key")
}
}
func TestRecycleCRUD(t *testing.T) {
db := testDB(t)
defer db.Close()
repo := NewRecycleRepo(db)
// Archive — ArchiveAndDelete returns (id, error)
snapshot := map[string]any{"id": "test-src", "name": "Test"}
entryID, err := repo.ArchiveAndDelete("source", "test-src", snapshot, "SELECT 1", nil)
if err != nil {
t.Fatalf("archive failed: %v", err)
}
if entryID == "" {
t.Fatal("expected non-empty entry id")
}
// List
list, err := repo.List()
if err != nil {
t.Fatalf("list failed: %v", err)
}
if len(list) != 1 {
t.Errorf("expected 1 entry, got %d", len(list))
}
// Get
got, err := repo.Get(entryID)
if err != nil {
t.Fatalf("get failed: %v", err)
}
if got["resourceType"] != "source" {
t.Errorf("expected resourceType source, got %v", got["resourceType"])
}
// Delete
err = repo.Delete(entryID)
if err != nil {
t.Fatalf("delete failed: %v", err)
}
list, _ = repo.List()
if len(list) != 0 {
t.Errorf("expected 0 entries after delete, got %d", len(list))
}
}
func TestSortSources(t *testing.T) {
db := testDB(t)
defer db.Close()
repo := NewSourceRepo(db)
repo.Upsert(model.SourceRecord{ID: "a", Name: "A", Type: "local", Content: "", Enabled: true, Filters: []model.FilterRule{}, Meta: map[string]any{}})
repo.Upsert(model.SourceRecord{ID: "b", Name: "B", Type: "local", Content: "", Enabled: true, Filters: []model.FilterRule{}, Meta: map[string]any{}})
repo.Upsert(model.SourceRecord{ID: "c", Name: "C", Type: "local", Content: "", Enabled: true, Filters: []model.FilterRule{}, Meta: map[string]any{}})
// Sort reverse
err := repo.Sort([]string{"c", "b", "a"})
if err != nil {
t.Fatalf("sort failed: %v", err)
}
list, _ := repo.List()
if list[0].ID != "c" || list[1].ID != "b" || list[2].ID != "a" {
t.Errorf("sort order wrong: %s, %s, %s", list[0].ID, list[1].ID, list[2].ID)
}
}
func TestMain(m *testing.M) {
os.Exit(m.Run())
}