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) return dbInst } func TestBootstrapAndSync(t *testing.T) { dbFile := "test_bootstrap.db" dbInst := setupDB(t, dbFile) defer os.Remove(dbFile) // 1. Bootstrap: Sync empty DSL, should still create system tables err := dbInst.SyncSchema("") if err != nil { t.Fatalf("Bootstrap failed: %v", err) } // 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. Sync business schema businessDSL := ` == Business == users SD id c10 PK name v64 U age i ` err = dbInst.SyncSchema(businessDSL) if err != nil { t.Fatalf("Business sync failed: %v", err) } // Verify business table if GlobalCache.GetTable("users") == nil { t.Errorf("users table not found in cache after sync") } fields := GlobalCache.GetValidFields("users") if len(fields) < 3 { // id, name, age (and maybe others from SD) 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) _ = dbInst.SyncSchema("") app := dbInst.Auth("admin") // 1. Create table via _Table API err := app.Table("_Table").Set(map[string]any{ "name": "tasks", "memo": "Task list", }) if err != nil { t.Fatalf("Failed to create table via API: %v", err) } // Get tid tbl, _ := app.Table("_Table").List(map[string]any{"name": "tasks"}) if len(tbl) == 0 { t.Fatalf("Table tasks not found in _Table") } tid := cast.String(tbl[0]["id"]) // 2. Add fields via _Field API _ = app.Table("_Field").Set(map[string]any{ "tableId": tid, "name": "title", "type": "v100", "memo": "Task title", }) _ = app.Table("_Field").Set(map[string]any{ "tableId": tid, "name": "done", "type": "b", "isIndex": 1, }) // 3. 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) } } func TestDataOperations(t *testing.T) { dbFile := "test_data_ops.db" dbInst := setupDB(t, dbFile) defer os.Remove(dbFile) _ = dbInst.SyncSchema(` == Test == products SD id c10 PK name v64 U price i `) app := dbInst.Auth("admin") table := app.Table("products") // Create _ = table.Set(map[string]any{"name": "Laptop", "price": 1000}) _ = table.Set(map[string]any{"id": "p2", "name": "Mouse", "price": 50}) // 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 (Shadow Delete) _ = table.Remove("p2") p1_removed, _ := table.Get("p2") if p1_removed != nil { t.Errorf("Expected p2 to be removed") } // Verify shadow delete (actual table has _deleted suffix) res := dbInst.Base().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.SyncSchema(` == Secret == secrets SD id c10 PK content t creator v64 `) 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 -> should fail or return nil depending on implementation // Current implementation: checkAuth returns error if no permission _, err := user2.Table("secrets").Get("s1") if err == nil { t.Errorf("user2 should not have permission to get user1 secret") } // admin gets it -> _system or admin should have permission? // Implementation says t.userID == "_system" bypasses. sys := dbInst.Auth("_system") s, err := sys.Table("secrets").Get("s1") if err != nil || s == nil { t.Errorf("system should have permission: %v", err) } // Test _Policy // allow user2 to see s1 err = sys.Table("_Policy").Set(map[string]any{ "subject": "user2", "action": "read", "resource": "s1", "effect": "allow", }) 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 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(tableName string, record map[string]any) { tableCreated = tableName } dbInst.Hooks.OnUpdatedRows = func(tableName string, count int) { rowsUpdated += count } _ = dbInst.SyncSchema("") app := dbInst.Auth("admin") // Trigger OnCreatedTable _ = app.Table("_Table").Set(map[string]any{"name": "hook_test"}) if tableCreated != "hook_test" { t.Errorf("OnCreatedTable hook failed, got %s", tableCreated) } // Add a field to ensure table exists tbl, _ := app.Table("_Table").List(map[string]any{"name": "hook_test"}) tid := cast.String(tbl[0]["id"]) _ = app.Table("_Field").Set(map[string]any{"tableId": tid, "name": "val", "type": "v10"}) // 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.SyncSchema(` == Test == safe_table SD id c10 PK name v64 `) // Valid query req := QueryRequest{ Table: "safe_table", Select: []string{"id", "name"}, OrderBy: "name DESC", } _, _, err := dbInst.BuildQuery(req) if err != nil { t.Errorf("Valid query failed: %v", err) } // Invalid table req.Table = "invalid_table" _, _, err = dbInst.BuildQuery(req) if err == nil { t.Errorf("Should fail for invalid table") } // SQL Injection in Select req.Table = "safe_table" req.Select = []string{"name` FROM safe_table; --"} _, _, err = dbInst.BuildQuery(req) if err == nil { t.Errorf("Should fail for injection in Select") } // SQL Injection in OrderBy req.Select = []string{"name"} req.OrderBy = "name; DROP TABLE safe_table" _, _, err = dbInst.BuildQuery(req) if err == nil { t.Errorf("Should fail for injection in OrderBy") } } 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) dbInst := GetDB("sqlite://"+dbFile, logger) _ = dbInst.SyncSchema("bench_ops SD\n id c10 PK\n name v50 U\n val i") app := dbInst.Auth("admin") 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, }) } }