475 lines
15 KiB
Go
475 lines
15 KiB
Go
package tests
|
||
|
||
import (
|
||
"os"
|
||
"testing"
|
||
"time"
|
||
|
||
"linedb/pkg/linedb"
|
||
)
|
||
|
||
func TestLineDbBasic(t *testing.T) {
|
||
// Очищаем тестовую папку
|
||
os.RemoveAll("./testdata")
|
||
|
||
// Создаем опции инициализации
|
||
initOptions := &linedb.LineDbInitOptions{
|
||
CacheSize: 100,
|
||
CacheTTL: time.Minute,
|
||
DBFolder: "./testdata",
|
||
Collections: []linedb.JSONLFileOptions{
|
||
{
|
||
CollectionName: "test",
|
||
AllocSize: 256,
|
||
},
|
||
},
|
||
}
|
||
|
||
// Создаем базу данных
|
||
db := linedb.NewLineDb(nil)
|
||
defer db.Close()
|
||
|
||
// Инициализируем базу данных
|
||
if err := db.Init(false, initOptions); err != nil {
|
||
t.Fatalf("Failed to init database: %v", err)
|
||
}
|
||
|
||
// Тест вставки
|
||
testData := map[string]any{
|
||
"name": "test",
|
||
"value": 123,
|
||
}
|
||
|
||
if err := db.Insert(testData, "test", linedb.LineDbAdapterOptions{}); err != nil {
|
||
t.Fatalf("Failed to insert data: %v", err)
|
||
}
|
||
|
||
// Тест чтения
|
||
allData, err := db.Read("test", linedb.LineDbAdapterOptions{})
|
||
if err != nil {
|
||
t.Fatalf("Failed to read data: %v", err)
|
||
}
|
||
|
||
if len(allData) != 1 {
|
||
t.Fatalf("Expected 1 record, got %d", len(allData))
|
||
}
|
||
|
||
// Тест фильтрации
|
||
filter := map[string]any{"name": "test"}
|
||
filteredData, err := db.ReadByFilter(filter, "test", linedb.LineDbAdapterOptions{})
|
||
if err != nil {
|
||
t.Fatalf("Failed to filter data: %v", err)
|
||
}
|
||
|
||
if len(filteredData) != 1 {
|
||
t.Fatalf("Expected 1 filtered record, got %d", len(filteredData))
|
||
}
|
||
|
||
// Тест обновления
|
||
updateData := map[string]any{"value": 456}
|
||
updateFilter := map[string]any{"name": "test"}
|
||
updatedData, err := db.Update(updateData, "test", updateFilter, linedb.LineDbAdapterOptions{})
|
||
if err != nil {
|
||
t.Fatalf("Failed to update data: %v", err)
|
||
}
|
||
|
||
if len(updatedData) != 1 {
|
||
t.Fatalf("Expected 1 updated record, got %d", len(updatedData))
|
||
}
|
||
|
||
// Тест удаления
|
||
deleteFilter := map[string]any{"name": "test"}
|
||
deletedData, err := db.Delete(deleteFilter, "test", linedb.LineDbAdapterOptions{})
|
||
if err != nil {
|
||
t.Fatalf("Failed to delete data: %v", err)
|
||
}
|
||
|
||
if len(deletedData) != 1 {
|
||
t.Fatalf("Expected 1 deleted record, got %d", len(deletedData))
|
||
}
|
||
|
||
// Проверяем что данных больше нет
|
||
remainingData, err := db.Read("test", linedb.LineDbAdapterOptions{})
|
||
if err != nil {
|
||
t.Fatalf("Failed to read remaining data: %v", err)
|
||
}
|
||
|
||
if len(remainingData) != 0 {
|
||
t.Fatalf("Expected 0 remaining records, got %d", len(remainingData))
|
||
}
|
||
}
|
||
|
||
func TestLineDbPartitioning(t *testing.T) {
|
||
// Очищаем тестовую папку
|
||
os.RemoveAll("./testdata")
|
||
|
||
// Создаем опции инициализации с партиционированием
|
||
initOptions := &linedb.LineDbInitOptions{
|
||
CacheSize: 100,
|
||
CacheTTL: time.Minute,
|
||
DBFolder: "./testdata",
|
||
Collections: []linedb.JSONLFileOptions{
|
||
{
|
||
CollectionName: "orders",
|
||
AllocSize: 256,
|
||
},
|
||
},
|
||
Partitions: []linedb.PartitionCollection{
|
||
{
|
||
CollectionName: "orders",
|
||
PartIDFn: func(item any) string {
|
||
if itemMap, ok := item.(map[string]any); ok {
|
||
if userId, exists := itemMap["userId"]; exists {
|
||
return toString(userId)
|
||
}
|
||
}
|
||
return "default"
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
// Создаем базу данных
|
||
db := linedb.NewLineDb(nil)
|
||
defer db.Close()
|
||
|
||
// Инициализируем базу данных
|
||
if err := db.Init(false, initOptions); err != nil {
|
||
t.Fatalf("Failed to init database: %v", err)
|
||
}
|
||
|
||
// Создаем заказы для разных пользователей
|
||
orders := []any{
|
||
map[string]any{
|
||
"userId": 1,
|
||
"item": "laptop",
|
||
"price": 999.99,
|
||
},
|
||
map[string]any{
|
||
"userId": 1,
|
||
"item": "mouse",
|
||
"price": 29.99,
|
||
},
|
||
map[string]any{
|
||
"userId": 2,
|
||
"item": "keyboard",
|
||
"price": 89.99,
|
||
},
|
||
}
|
||
|
||
// Вставляем заказы
|
||
if err := db.Insert(orders, "orders", linedb.LineDbAdapterOptions{}); err != nil {
|
||
t.Fatalf("Failed to insert orders: %v", err)
|
||
}
|
||
|
||
// Читаем все заказы
|
||
allOrders, err := db.Read("orders", linedb.LineDbAdapterOptions{})
|
||
if err != nil {
|
||
t.Fatalf("Failed to read orders: %v", err)
|
||
}
|
||
|
||
if len(allOrders) != 3 {
|
||
t.Fatalf("Expected 3 orders, got %d", len(allOrders))
|
||
}
|
||
|
||
// Фильтруем заказы пользователя 1
|
||
user1Filter := map[string]any{"userId": 1}
|
||
user1Orders, err := db.ReadByFilter(user1Filter, "orders", linedb.LineDbAdapterOptions{})
|
||
if err != nil {
|
||
t.Fatalf("Failed to filter user 1 orders: %v", err)
|
||
}
|
||
|
||
if len(user1Orders) != 2 {
|
||
t.Fatalf("Expected 2 orders for user 1, got %d", len(user1Orders))
|
||
}
|
||
}
|
||
|
||
// toString конвертирует значение в строку (для партиционирования)
|
||
func toString(value any) string {
|
||
switch v := value.(type) {
|
||
case string:
|
||
return v
|
||
case int:
|
||
return string(rune(v))
|
||
case int64:
|
||
return string(rune(v))
|
||
case float64:
|
||
return string(rune(int(v)))
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
|
||
// setupUniqueCollection создаёт БД с коллекцией, у которой заданы UniqueFields
|
||
func setupUniqueCollection(t *testing.T, uniqueFields []string) (*linedb.LineDb, func()) {
|
||
t.Helper()
|
||
os.RemoveAll("./testdata")
|
||
initOptions := &linedb.LineDbInitOptions{
|
||
CacheSize: 100,
|
||
CacheTTL: time.Minute,
|
||
DBFolder: "./testdata",
|
||
Collections: []linedb.JSONLFileOptions{
|
||
{
|
||
CollectionName: "users",
|
||
AllocSize: 256,
|
||
UniqueFields: uniqueFields,
|
||
},
|
||
},
|
||
}
|
||
db := linedb.NewLineDb(nil)
|
||
if err := db.Init(false, initOptions); err != nil {
|
||
t.Fatalf("Failed to init database: %v", err)
|
||
}
|
||
return db, func() { db.Close(); os.RemoveAll("./testdata") }
|
||
}
|
||
|
||
// TestLineDbUniqueFieldsNonEmpty проверяет, что дубликаты непустых значений в уникальном поле запрещены
|
||
func TestLineDbUniqueFieldsNonEmpty(t *testing.T) {
|
||
db, cleanup := setupUniqueCollection(t, []string{"email"})
|
||
defer cleanup()
|
||
|
||
opts := linedb.LineDbAdapterOptions{}
|
||
|
||
if err := db.Insert(map[string]any{"name": "alice", "email": "alice@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert 1 failed: %v", err)
|
||
}
|
||
if err := db.Insert(map[string]any{"name": "bob", "email": "bob@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert 2 failed: %v", err)
|
||
}
|
||
|
||
// Дубликат email — ожидаем ошибку
|
||
err := db.Insert(map[string]any{"name": "alice2", "email": "alice@test.com"}, "users", opts)
|
||
if err == nil {
|
||
t.Fatal("Expected unique constraint violation for duplicate email")
|
||
}
|
||
if err.Error() == "" {
|
||
t.Fatalf("Expected meaningful error, got: %v", err)
|
||
}
|
||
|
||
all, _ := db.Read("users", opts)
|
||
if len(all) != 2 {
|
||
t.Fatalf("Expected 2 records, got %d", len(all))
|
||
}
|
||
}
|
||
|
||
// TestLineDbUniqueFieldsEmptyValues проверяет, что пустые значения (nil, "") в уникальном поле допускают несколько записей
|
||
func TestLineDbUniqueFieldsEmptyValues(t *testing.T) {
|
||
db, cleanup := setupUniqueCollection(t, []string{"email"})
|
||
defer cleanup()
|
||
|
||
opts := linedb.LineDbAdapterOptions{}
|
||
|
||
// Несколько записей с отсутствующим полем email — должно быть разрешено
|
||
if err := db.Insert(map[string]any{"name": "a"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert with nil email failed: %v", err)
|
||
}
|
||
if err := db.Insert(map[string]any{"name": "b"}, "users", opts); err != nil {
|
||
t.Fatalf("Second insert with nil email failed: %v", err)
|
||
}
|
||
|
||
// Несколько записей с nil в email — должно быть разрешено
|
||
if err := db.Insert(map[string]any{"name": "a", "email": nil}, "users", opts); err != nil {
|
||
t.Fatalf("Insert with nil email failed: %v", err)
|
||
}
|
||
if err := db.Insert(map[string]any{"name": "b", "email": nil}, "users", opts); err != nil {
|
||
t.Fatalf("Second insert with nil email failed: %v", err)
|
||
}
|
||
|
||
// Несколько записей с пустой строкой
|
||
if err := db.Insert(map[string]any{"name": "c", "email": ""}, "users", opts); err != nil {
|
||
t.Fatalf("Insert with empty email failed: %v", err)
|
||
}
|
||
if err := db.Insert(map[string]any{"name": "d", "email": ""}, "users", opts); err != nil {
|
||
t.Fatalf("Second insert with empty email failed: %v", err)
|
||
}
|
||
|
||
all, err := db.Read("users", opts)
|
||
if err != nil {
|
||
t.Fatalf("Read failed: %v", err)
|
||
}
|
||
if len(all) != 6 {
|
||
t.Fatalf("Expected 6 records (2 with nil email + 2 with empty string + 2 without email), got %d", len(all))
|
||
}
|
||
}
|
||
|
||
// TestLineDbUniqueFieldsSkipCheckExistingForWrite проверяет режим SkipCheckExistingForWrite — уникальность не проверяется
|
||
func TestLineDbUniqueFieldsSkipCheckExistingForWrite(t *testing.T) {
|
||
db, cleanup := setupUniqueCollection(t, []string{"email"})
|
||
defer cleanup()
|
||
|
||
opts := linedb.LineDbAdapterOptions{SkipCheckExistingForWrite: true}
|
||
|
||
if err := db.Insert(map[string]any{"name": "a", "email": "same@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert 1 failed: %v", err)
|
||
}
|
||
// С тем же email, но с пропуском проверки — должно пройти
|
||
if err := db.Insert(map[string]any{"name": "b", "email": "same@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert 2 with same email (skip check) failed: %v", err)
|
||
}
|
||
|
||
all, _ := db.Read("users", opts)
|
||
if len(all) != 2 {
|
||
t.Fatalf("Expected 2 records with SkipCheckExistingForWrite, got %d", len(all))
|
||
}
|
||
}
|
||
|
||
// TestLineDbUniqueFieldsUpdateConflict проверяет, что Update не может установить значение, уже занятое другой записью
|
||
func TestLineDbUniqueFieldsUpdateConflict(t *testing.T) {
|
||
db, cleanup := setupUniqueCollection(t, []string{"email"})
|
||
defer cleanup()
|
||
|
||
opts := linedb.LineDbAdapterOptions{}
|
||
|
||
if err := db.Insert(map[string]any{"name": "alice", "email": "alice@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert alice failed: %v", err)
|
||
}
|
||
if err := db.Insert(map[string]any{"name": "bob", "email": "bob@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert bob failed: %v", err)
|
||
}
|
||
|
||
// Пытаемся обновить bob на email alice
|
||
_, err := db.Update(
|
||
map[string]any{"email": "alice@test.com"},
|
||
"users",
|
||
map[string]any{"name": "bob"},
|
||
opts,
|
||
)
|
||
if err == nil {
|
||
t.Fatal("Expected unique constraint violation when updating to existing email")
|
||
}
|
||
}
|
||
|
||
// TestLineDbUniqueFieldsUpdateSameValue проверяет, что Update на ту же запись (тот же unique-значение) допустим
|
||
func TestLineDbUniqueFieldsUpdateSameValue(t *testing.T) {
|
||
db, cleanup := setupUniqueCollection(t, []string{"email"})
|
||
defer cleanup()
|
||
|
||
opts := linedb.LineDbAdapterOptions{}
|
||
|
||
if err := db.Insert(map[string]any{"name": "alice", "email": "alice@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert failed: %v", err)
|
||
}
|
||
|
||
// Обновляем имя, оставляя email тем же — допустимо
|
||
updated, err := db.Update(
|
||
map[string]any{"name": "alice_updated"},
|
||
"users",
|
||
map[string]any{"email": "alice@test.com"},
|
||
opts,
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("Update same record failed: %v", err)
|
||
}
|
||
if len(updated) != 1 {
|
||
t.Fatalf("Expected 1 updated record, got %d", len(updated))
|
||
}
|
||
}
|
||
|
||
// TestLineDbUniqueFieldsMultipleFields проверяет несколько уникальных полей
|
||
func TestLineDbUniqueFieldsMultipleFields(t *testing.T) {
|
||
db, cleanup := setupUniqueCollection(t, []string{"email", "username"})
|
||
defer cleanup()
|
||
|
||
opts := linedb.LineDbAdapterOptions{}
|
||
|
||
if err := db.Insert(map[string]any{"name": "a", "email": "a@test.com", "username": "user_a"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert 1 failed: %v", err)
|
||
}
|
||
if err := db.Insert(map[string]any{"name": "b", "email": "b@test.com", "username": "user_b"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert 2 failed: %v", err)
|
||
}
|
||
|
||
// Дубликат email
|
||
err := db.Insert(map[string]any{"name": "c", "email": "a@test.com", "username": "user_c"}, "users", opts)
|
||
if err == nil {
|
||
t.Fatal("Expected unique constraint violation for duplicate email")
|
||
}
|
||
|
||
// Дубликат username
|
||
err = db.Insert(map[string]any{"name": "d", "email": "d@test.com", "username": "user_a"}, "users", opts)
|
||
if err == nil {
|
||
t.Fatal("Expected unique constraint violation for duplicate username")
|
||
}
|
||
|
||
all, _ := db.Read("users", opts)
|
||
if len(all) != 2 {
|
||
t.Fatalf("Expected 2 records, got %d", len(all))
|
||
}
|
||
}
|
||
|
||
// TestLineDbUniqueFieldsBatchInsertDuplicate проверяет, что дубликат внутри одного batch Insert даёт ошибку
|
||
func TestLineDbUniqueFieldsBatchInsertDuplicate(t *testing.T) {
|
||
db, cleanup := setupUniqueCollection(t, []string{"email"})
|
||
defer cleanup()
|
||
|
||
opts := linedb.LineDbAdapterOptions{}
|
||
|
||
batch := []any{
|
||
map[string]any{"name": "a", "email": "x@test.com"},
|
||
map[string]any{"name": "b", "email": "x@test.com"},
|
||
}
|
||
err := db.Insert(batch, "users", opts)
|
||
if err == nil {
|
||
t.Fatal("Expected unique constraint violation in batch insert")
|
||
}
|
||
|
||
all, _ := db.Read("users", opts)
|
||
if len(all) != 0 {
|
||
t.Fatalf("Expected 0 records (batch should rollback or not persist), got %d", len(all))
|
||
}
|
||
}
|
||
|
||
// TestLineDbUniqueFieldsUpdateToEmpty проверяет, что Update может установить пустое значение в unique-поле
|
||
func TestLineDbUniqueFieldsUpdateToEmpty(t *testing.T) {
|
||
db, cleanup := setupUniqueCollection(t, []string{"email"})
|
||
defer cleanup()
|
||
|
||
opts := linedb.LineDbAdapterOptions{}
|
||
|
||
if err := db.Insert(map[string]any{"name": "a", "email": "a@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert failed: %v", err)
|
||
}
|
||
if err := db.Insert(map[string]any{"name": "b", "email": "b@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert 2 failed: %v", err)
|
||
}
|
||
|
||
// Обновляем одну запись: email -> ""
|
||
updated, err := db.Update(
|
||
map[string]any{"email": ""},
|
||
"users",
|
||
map[string]any{"email": "a@test.com"},
|
||
opts,
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("Update to empty email failed: %v", err)
|
||
}
|
||
if len(updated) != 1 {
|
||
t.Fatalf("Expected 1 updated record, got %d", len(updated))
|
||
}
|
||
|
||
all, _ := db.Read("users", opts)
|
||
if len(all) != 2 {
|
||
t.Fatalf("Expected 2 records, got %d", len(all))
|
||
}
|
||
}
|
||
|
||
// TestLineDbUniqueFieldsNoUniqueFields проверяет, что без UniqueFields дубликаты разрешены
|
||
func TestLineDbUniqueFieldsNoUniqueFields(t *testing.T) {
|
||
db, cleanup := setupUniqueCollection(t, nil)
|
||
defer cleanup()
|
||
|
||
opts := linedb.LineDbAdapterOptions{}
|
||
|
||
if err := db.Insert(map[string]any{"name": "a", "email": "dup@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert 1 failed: %v", err)
|
||
}
|
||
if err := db.Insert(map[string]any{"name": "b", "email": "dup@test.com"}, "users", opts); err != nil {
|
||
t.Fatalf("Insert 2 (duplicate email, no unique) failed: %v", err)
|
||
}
|
||
|
||
all, _ := db.Read("users", opts)
|
||
if len(all) != 2 {
|
||
t.Fatalf("Expected 2 records when no unique fields, got %d", len(all))
|
||
}
|
||
}
|