readysite / pkg / database / collection_test.go
6.2 KB
collection_test.go
package database

import (
	"database/sql"
	"testing"

	_ "github.com/tursodatabase/go-libsql"
)

type TestEntity struct {
	Model
	Name   string
	Status string
}

// openTestDatabase creates an in-memory database for collection tests
func openTestDatabase() *Database {
	db, _ := sql.Open("libsql", ":memory:")
	return &Database{DB: db}
}

func TestCollection_AutoCreateTable(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	// Table should be auto-created by Manage
	entities := Manage(db, new(TestEntity))

	// Verify table exists
	var tableName string
	err := db.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name='TestEntity'").Scan(&tableName)
	if err != nil {
		t.Fatalf("table was not created: %v", err)
	}
	if tableName != "TestEntity" {
		t.Errorf("expected table 'TestEntity', got '%s'", tableName)
	}

	// Should be able to insert without manual table creation
	entity := &TestEntity{Name: "Test", Status: "active"}
	_, err = entities.Insert(entity)
	if err != nil {
		t.Fatalf("Insert failed: %v", err)
	}
}

func TestCollection_AutoAddColumns(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	// Create table with only ID column
	db.Exec("CREATE TABLE TestEntity (ID TEXT PRIMARY KEY)")

	// Manage should add missing columns
	entities := Manage(db, new(TestEntity))

	// Should be able to insert with all fields
	entity := &TestEntity{Name: "Test", Status: "active"}
	_, err := entities.Insert(entity)
	if err != nil {
		t.Fatalf("Insert failed after column migration: %v", err)
	}

	// Verify we can read the data back
	found, err := entities.Get(entity.ID)
	if err != nil {
		t.Fatalf("Get failed: %v", err)
	}
	if found.Name != "Test" {
		t.Errorf("expected Name 'Test', got '%s'", found.Name)
	}
}

func TestCollection_CRUD(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	entities := Manage(db, new(TestEntity))

	// Test Insert
	entity := &TestEntity{Name: "Test", Status: "active"}
	id, err := entities.Insert(entity)
	if err != nil {
		t.Fatalf("Insert failed: %v", err)
	}
	if id == "" {
		t.Error("expected ID to be generated")
	}
	if entity.ID != id {
		t.Errorf("expected entity.ID to be %s, got %s", id, entity.ID)
	}

	// Test Get
	found, err := entities.Get(id)
	if err != nil {
		t.Fatalf("Get failed: %v", err)
	}
	if found.Name != "Test" {
		t.Errorf("expected Name 'Test', got '%s'", found.Name)
	}

	// Test Update
	found.Status = "inactive"
	err = entities.Update(found)
	if err != nil {
		t.Fatalf("Update failed: %v", err)
	}

	updated, _ := entities.Get(id)
	if updated.Status != "inactive" {
		t.Errorf("expected Status 'inactive', got '%s'", updated.Status)
	}

	// Test Search
	results, err := entities.Search("WHERE Status = ?", "inactive")
	if err != nil {
		t.Fatalf("Search failed: %v", err)
	}
	if len(results) != 1 {
		t.Errorf("expected 1 result, got %d", len(results))
	}

	// Test Count
	count := entities.Count("WHERE Status = ?", "inactive")
	if count != 1 {
		t.Errorf("expected count 1, got %d", count)
	}

	// Test Delete
	err = entities.Delete(found)
	if err != nil {
		t.Fatalf("Delete failed: %v", err)
	}

	_, err = entities.Get(id)
	if err != ErrNotFound {
		t.Errorf("expected ErrNotFound after delete, got %v", err)
	}
}

func TestCollection_First_NotFound(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	entities := Manage(db, new(TestEntity))

	_, err := entities.First("WHERE Name = ?", "nonexistent")
	if err != ErrNotFound {
		t.Errorf("expected ErrNotFound, got %v", err)
	}
}

func TestCollection_All(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	entities := Manage(db, new(TestEntity))

	// Insert a few entities
	entities.Insert(&TestEntity{Name: "One", Status: "active"})
	entities.Insert(&TestEntity{Name: "Two", Status: "active"})
	entities.Insert(&TestEntity{Name: "Three", Status: "inactive"})

	all, err := entities.All()
	if err != nil {
		t.Fatalf("All failed: %v", err)
	}
	if len(all) != 3 {
		t.Errorf("expected 3 entities, got %d", len(all))
	}
}

func TestCollection_WithIndex(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	// Test single field index via option
	Manage(db, new(TestEntity),
		WithIndex[TestEntity]("Name"),
	)

	// Verify index was created
	var indexName string
	err := db.QueryRow("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_testentity_name'").Scan(&indexName)
	if err != nil {
		t.Errorf("single field index not created: %v", err)
	}
}

func TestCollection_WithCompositeIndex(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	// Test multi-field index via option
	Manage(db, new(TestEntity),
		WithIndex[TestEntity]("Name", "Status"),
	)

	// Verify composite index was created
	var indexName string
	err := db.QueryRow("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_testentity_name_status'").Scan(&indexName)
	if err != nil {
		t.Errorf("composite index not created: %v", err)
	}
}

func TestCollection_WithUniqueIndex(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	entities := Manage(db, new(TestEntity),
		WithUniqueIndex[TestEntity]("Name"),
	)

	// Test that unique constraint works
	entities.Insert(&TestEntity{Name: "Unique", Status: "active"})
	_, err := entities.Insert(&TestEntity{Name: "Unique", Status: "active"})
	if err != ErrDuplicate {
		t.Errorf("expected ErrDuplicate, got %v", err)
	}
}

func TestCollection_DBAndTable(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	entities := Manage(db, new(TestEntity))

	if entities.DB() != db {
		t.Error("expected DB() to return the database")
	}

	if entities.Table() != "TestEntity" {
		t.Errorf("expected Table() to return 'TestEntity', got '%s'", entities.Table())
	}
}

func TestCollection_Update_NotFound(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	entities := Manage(db, new(TestEntity))

	entity := &TestEntity{Name: "Ghost", Status: "active"}
	entity.ID = "nonexistent-id"

	err := entities.Update(entity)
	if err != ErrNotFound {
		t.Errorf("expected ErrNotFound, got %v", err)
	}
}

func TestCollection_Delete_NotFound(t *testing.T) {
	db := openTestDatabase()
	defer db.Close()

	entities := Manage(db, new(TestEntity))

	entity := &TestEntity{Name: "Ghost", Status: "active"}
	entity.ID = "nonexistent-id"

	err := entities.Delete(entity)
	if err != ErrNotFound {
		t.Errorf("expected ErrNotFound, got %v", err)
	}
}
← Back