tableDB/tableDB_test.go

402 lines
10 KiB
Go
Raw Permalink Normal View History

package tableDB
import (
"fmt"
"os"
"testing"
"time"
"apigo.cc/go/cast"
"apigo.cc/go/log"
)
func setupDB(t *testing.T, dbFile string) *TableDB {
_ = os.Remove(dbFile)
logger := log.DefaultLogger
logger.SetLevel(log.ERROR)
dbInst := GetDB("sqlite://"+dbFile, logger, "")
// Bootstrap system tables
err := dbInst.Bootstrap()
if err != nil {
panic(fmt.Sprintf("Bootstrap failed in setupDB: %v", err))
}
return dbInst.Auth(SystemUserID)
}
func TestBootstrapAndSync(t *testing.T) {
dbFile := "test_bootstrap.db"
_ = os.Remove(dbFile)
unauth := GetDB("sqlite://"+dbFile, log.DefaultLogger, "")
defer os.Remove(dbFile)
// 1. Bootstrap: sync system DSL
err := unauth.Bootstrap()
if err != nil {
t.Fatalf("Bootstrap failed: %v", err)
}
dbInst := unauth.Auth(SystemUserID)
// Verify system tables in cache
if GlobalCache.GetTable("_Table") == nil {
t.Errorf("_Table not found in cache after bootstrap")
}
if GlobalCache.GetTable("_Field") == nil {
t.Errorf("_Field not found in cache after bootstrap")
}
// 2. Create business table via API
err = dbInst.Table("users").SetField(
FieldSchema{Name: "name", Type: "v64", IsIndex: true},
FieldSchema{Name: "age", Type: "i"},
)
if err != nil {
t.Fatalf("Create table failed: %v", err)
}
// Verify business table
if GlobalCache.GetTable("users") == nil {
t.Errorf("users table not found in cache after sync")
}
fields, _ := dbInst.Table("users").Fields()
if len(fields) < 3 { // id, name, age
t.Errorf("Expected at least 3 fields for users, got %v", fields)
}
}
func TestAPIDrivenSchema(t *testing.T) {
dbFile := "test_api_schema.db"
dbInst := setupDB(t, dbFile)
defer os.Remove(dbFile)
app := dbInst.Auth("admin")
// 1. Create table and add fields via SetField API
err := app.Table("tasks").SetField(
FieldSchema{Name: "title", Type: "v100", Memo: "Task title"},
FieldSchema{Name: "done", Type: "b", IsIndex: true},
)
if err != nil {
t.Fatalf("Failed to create table/fields via API: %v", err)
}
// 2. Verify table works
taskTable := app.Table("tasks")
err = taskTable.Set(map[string]any{
"title": "Fix tests",
"done": false,
})
if err != nil {
t.Fatalf("Failed to insert into tasks table: %v", err)
}
res, _ := taskTable.List(nil)
if len(res) != 1 || res[0]["title"] != "Fix tests" {
t.Errorf("Query from tasks failed: %v", res)
}
// Verify ID is automatically generated
if idVal := cast.String(res[0]["id"]); len(idVal) == 0 {
t.Errorf("ID was not automatically generated")
} else {
fmt.Printf("Generated ID: %s\n", idVal)
}
}
func TestDataOperations(t *testing.T) {
dbFile := "test_data_ops.db"
dbInst := setupDB(t, dbFile)
defer os.Remove(dbFile)
_ = dbInst.Table("products").SetField(
FieldSchema{Name: "name", Type: "v64", IsIndex: true},
FieldSchema{Name: "price", Type: "i"},
)
app := dbInst.Auth("admin")
table := app.Table("products")
// Create (Batch)
err := table.Set(
map[string]any{"name": "Laptop", "price": 1000},
map[string]any{"id": "p2", "name": "Mouse", "price": 50},
)
if err != nil {
t.Fatalf("Batch set failed: %v", err)
}
laptop, _ := table.List(map[string]any{"name": "Laptop"})
if len(laptop) == 0 {
t.Fatalf("Laptop not created")
}
laptopID := cast.String(laptop[0]["id"])
// Read
p1, _ := table.Get("p2")
if p1 == nil || p1["name"] != "Mouse" {
t.Errorf("Get p2 failed")
}
// Update
_ = table.Set(map[string]any{"id": "p2", "price": 45})
p1_updated, _ := table.Get("p2")
if cast.Int(p1_updated["price"]) != 45 {
t.Errorf("Update p2 failed, got %v", p1_updated["price"])
}
// List & Filter
list, _ := table.List(map[string]any{"price >": 100})
if len(list) != 1 || list[0]["name"] != "Laptop" {
t.Errorf("List/Filter failed: %v", list)
}
// Count
cnt, _ := table.Count(nil)
if cnt != 2 {
t.Errorf("Count failed: %d", cnt)
}
// Delete (Batch)
err = table.Remove("p2", laptopID)
if err != nil {
t.Fatalf("Batch remove failed: %v", err)
}
p1_removed, _ := table.Get("p2")
if p1_removed != nil {
t.Errorf("Expected p2 to be removed")
}
// Verify shadow delete (actual table has _deleted suffix)
raw, _ := dbInst.GetRawDB()
res := raw.Query("SELECT name FROM sqlite_master WHERE name='products_deleted'")
if res.MapOnR1()["name"] == nil {
t.Errorf("Shadow delete table products_deleted not found")
}
}
func TestPermissionsAndAuth(t *testing.T) {
dbFile := "test_auth.db"
dbInst := setupDB(t, dbFile)
defer os.Remove(dbFile)
_ = dbInst.Table("secrets").SetField(
FieldSchema{Name: "content", Type: "t"},
FieldSchema{Name: "creator", Type: "v64"},
)
_ = dbInst.SetTable(TableSchema{Name: "secrets", EnableRLS: true})
user1 := dbInst.Auth("user1")
user2 := dbInst.Auth("user2")
// user1 creates a secret
_ = user1.Table("secrets").Set(map[string]any{"id": "s1", "content": "user1-secret"})
// user2 tries to get it
_, err := user2.Table("secrets").Get("s1")
if err == nil {
t.Errorf("user2 should not have permission to get user1 secret")
}
// system gets it
sys := dbInst.Auth(SystemUserID)
s, err := sys.Table("secrets").Get("s1")
if err != nil || s == nil {
t.Errorf("system should have permission: %v", err)
}
// Test _Policy: Grant access to specific ID via condition
err = sys.Table("_Policy").Set(map[string]any{
"userID": "user2",
"type": "table",
"targets": []string{"secrets"},
"action": "read",
"condition": "id = ?",
"conditionArgs": []any{"s1"},
})
if err != nil {
t.Fatalf("Failed to set policy: %v", err)
}
// Now user2 should be able to get it
s, err = user2.Table("secrets").Get("s1")
if err != nil || s == nil {
t.Errorf("user2 should now have permission via policy: %v", err)
}
}
func TestInheritance(t *testing.T) {
dbFile := "test_inherit.db"
dbInst := setupDB(t, dbFile)
defer os.Remove(dbFile)
_ = dbInst.Table("docs").SetField(
FieldSchema{Name: "title", Type: "v64"},
FieldSchema{Name: "creator", Type: "v64"},
)
_ = dbInst.SetTable(TableSchema{Name: "docs", EnableRLS: true})
sys := dbInst.Auth(SystemUserID)
_ = sys.Table("docs").Set(map[string]any{"id": "d1", "title": "secret doc", "creator": "boss"})
// manager inherits boss
_ = sys.Table("_Policy").Set(map[string]any{
"userID": "manager",
"type": "inherit",
"targets": []string{"boss"},
})
// boss can read docs
_ = sys.Table("_Policy").Set(map[string]any{
"userID": "boss",
"type": "table",
"targets": []string{"docs"},
"action": "read",
})
manager := dbInst.Auth("manager")
d, err := manager.Table("docs").Get("d1")
if err != nil || d == nil {
t.Errorf("manager should inherit boss's permission to read docs: %v", err)
}
}
func TestPolicyInterfaces(t *testing.T) {
dbFile := "test_policy_api.db"
dbInst := setupDB(t, dbFile)
defer os.Remove(dbFile)
sys := dbInst.Auth(SystemUserID)
_ = sys.Table("data").SetField(FieldSchema{Name: "val", Type: "v10"})
_ = sys.SetTable(TableSchema{Name: "data", EnableRLS: true})
// 1. Normal user tries to set inherit policy (should fail)
user1 := dbInst.Auth("user1")
err := user1.SetPolicy(PolicySchema{
UserID: "user2",
Type: "inherit",
Targets: []string{"user1"},
})
if err == nil {
t.Errorf("Normal user should not be able to set inherit policy")
}
// 2. Normal user tries to set table policy for table they don't have full access to (should fail)
err = user1.SetPolicy(PolicySchema{
UserID: "user2",
Type: "table",
Targets: []string{"data"},
Action: "read",
})
if err == nil {
t.Errorf("User without full access to table should not be able to set policy for it")
}
// 3. System grants user1 full access to 'data'
_ = sys.SetPolicy(PolicySchema{
UserID: "user1",
Type: "table",
Targets: []string{"data"},
Action: "full",
})
// 4. Now user1 sets policy for user2 on 'data' (should succeed)
err = user1.SetPolicy(PolicySchema{
UserID: "user2",
Type: "table",
Targets: []string{"data"},
Action: "read",
})
if err != nil {
t.Errorf("User with full access should be able to set policy: %v", err)
}
// 5. List policies
pols, err := user1.ListPolicy(map[string]any{"userID": "user2"})
if err != nil || len(pols) == 0 || pols[0].UserID != "user2" {
t.Errorf("ListPolicy failed: %v, %v", err, pols)
}
}
func TestHooks(t *testing.T) {
dbFile := "test_hooks.db"
dbInst := setupDB(t, dbFile)
defer os.Remove(dbFile)
var tableCreated string
var rowsUpdated int
dbInst.hooks.OnCreatedTable = func(table *TableSchema) {
tableCreated = table.Name
}
dbInst.hooks.OnUpdatedRows = func(rows []map[string]any, table *TableSchema, fields []FieldSchema) {
rowsUpdated += len(rows)
}
app := dbInst.Auth("admin")
// Trigger OnCreatedTable
_ = app.Table("hook_test").SetField(FieldSchema{Name: "val", Type: "v10"})
if tableCreated != "hook_test" {
t.Errorf("OnCreatedTable hook failed, got %s", tableCreated)
}
// Trigger OnUpdatedRows
_ = app.Table("hook_test").Set(map[string]any{"id": "1", "val": "a"})
if rowsUpdated != 1 {
t.Errorf("OnUpdatedRows hook failed, got %d", rowsUpdated)
}
}
func TestQueryValidationAndInjection(t *testing.T) {
dbFile := "test_query.db"
dbInst := setupDB(t, dbFile)
defer os.Remove(dbFile)
_ = dbInst.Table("safe_table").SetField(FieldSchema{Name: "name", Type: "v64"})
app := dbInst.Auth("user1")
// Valid query
req := QueryRequest{
Select: []string{"id", "name"},
OrderBy: "name DESC",
}
_, err := app.Table("safe_table").Query(req)
if err != nil {
t.Errorf("Valid query failed: %v", err)
}
// SQL Injection in Select
req.Select = []string{"name` FROM safe_table; --"}
_, err = app.Table("safe_table").Query(req)
if err == nil {
t.Errorf("Should fail for injection in Select")
}
}
func BenchmarkTableSet(b *testing.B) {
logger := log.DefaultLogger
logger.SetLevel(log.ERROR)
dbFile := fmt.Sprintf("bench_ops_%d.db", time.Now().UnixNano())
defer os.Remove(dbFile)
unauth := GetDB("sqlite://"+dbFile, logger, "")
_ = unauth.Bootstrap()
app := unauth.Auth(SystemUserID)
_ = app.Table("bench_ops").SetField(
FieldSchema{Name: "name", Type: "v50", IsIndex: true},
FieldSchema{Name: "val", Type: "i"},
)
table := app.Table("bench_ops")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = table.Set(map[string]any{
"name": "user_" + cast.String(i),
"val": i,
})
}
}