295 lines
7.0 KiB
Go
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())
|
|
}
|