|
|
@@ -0,0 +1,3566 @@
|
|
|
+/**
|
|
|
+ * EmbeddedEngineTest - Unit tests for EmbeddedEngine
|
|
|
+ *
|
|
|
+ * All tests use async methods with MainLoop wrappers for the test framework.
|
|
|
+ */
|
|
|
+using Implexus.Core;
|
|
|
+using Implexus.Engine;
|
|
|
+using Implexus.Storage;
|
|
|
+
|
|
|
+public static int main(string[] args) {
|
|
|
+ int passed = 0;
|
|
|
+ int failed = 0;
|
|
|
+
|
|
|
+ // Test 1: Root creation
|
|
|
+ if (test_root_creation()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_root_creation\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_root_creation\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 2: Container creation
|
|
|
+ if (test_container_creation()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_container_creation\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_container_creation\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 3: Document creation
|
|
|
+ if (test_document_creation()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_document_creation\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_document_creation\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 4: Container deletion
|
|
|
+ if (test_container_deletion()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_container_deletion\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_container_deletion\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 5: Document deletion
|
|
|
+ if (test_document_deletion()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_document_deletion\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_document_deletion\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 6: Property access
|
|
|
+ if (test_property_access()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_property_access\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_property_access\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 7: Property removal
|
|
|
+ if (test_property_removal()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_property_removal\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_property_removal\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 8: Get children
|
|
|
+ if (test_get_children()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_get_children\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_get_children\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 9: Entity exists
|
|
|
+ if (test_entity_exists()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_entity_exists\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_entity_exists\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 10: Get entity
|
|
|
+ if (test_get_entity()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_get_entity\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_get_entity\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 11: Query by type
|
|
|
+ if (test_query_by_type()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_query_by_type\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_query_by_type\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 12: Nested containers
|
|
|
+ if (test_nested_containers()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_nested_containers\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_nested_containers\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 13: Multiple documents
|
|
|
+ if (test_multiple_documents()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_multiple_documents\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_multiple_documents\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 14: Entity path
|
|
|
+ if (test_entity_path()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_entity_path\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_entity_path\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 15: Persistence
|
|
|
+ if (test_engine_persistence()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_engine_persistence\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_engine_persistence\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 16: Category virtual child resolution
|
|
|
+ if (test_category_virtual_child_resolution()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_category_virtual_child_resolution\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_category_virtual_child_resolution\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 17: Category virtual child not found
|
|
|
+ if (test_category_virtual_child_not_found()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_category_virtual_child_not_found\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_category_virtual_child_not_found\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 18: Catalogue virtual child group resolution
|
|
|
+ if (test_catalogue_virtual_child_group()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_catalogue_virtual_child_group\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_catalogue_virtual_child_group\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 19: Catalogue virtual child document resolution
|
|
|
+ if (test_catalogue_virtual_child_document()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_catalogue_virtual_child_document\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_catalogue_virtual_child_document\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 20: Catalogue virtual child not found
|
|
|
+ if (test_catalogue_virtual_child_not_found()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_catalogue_virtual_child_not_found\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_catalogue_virtual_child_not_found\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 21: Index virtual child resolution
|
|
|
+ if (test_index_virtual_child_resolution()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_index_virtual_child_resolution\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_index_virtual_child_resolution\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 22: Index virtual child no matches
|
|
|
+ if (test_index_virtual_child_no_matches()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_index_virtual_child_no_matches\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_index_virtual_child_no_matches\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 23: Category direct vs navigation comparison
|
|
|
+ if (test_category_direct_vs_navigation()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_category_direct_vs_navigation\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_category_direct_vs_navigation\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 24: Catalogue direct vs navigation comparison
|
|
|
+ if (test_catalogue_direct_vs_navigation()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_catalogue_direct_vs_navigation\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_catalogue_direct_vs_navigation\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 25: Index direct vs navigation comparison
|
|
|
+ if (test_index_direct_vs_navigation()) {
|
|
|
+ passed++;
|
|
|
+ stdout.puts("PASS: test_index_direct_vs_navigation\n");
|
|
|
+ } else {
|
|
|
+ failed++;
|
|
|
+ stdout.puts("FAIL: test_index_direct_vs_navigation\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ stdout.printf("\nResults: %d passed, %d failed\n", passed, failed);
|
|
|
+ return failed > 0 ? 1 : 0;
|
|
|
+}
|
|
|
+
|
|
|
+// Helper to create temporary directory
|
|
|
+string create_temp_dir() {
|
|
|
+ string temp_dir = DirUtils.mkdtemp("implexus_engine_test_XXXXXX");
|
|
|
+ return temp_dir;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 1: Root creation
|
|
|
+bool test_root_creation() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ stderr.printf("Test error: %s\n", ((!)error).message);
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)root).entity_type != EntityType.CONTAINER) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!((!)root).path.is_root) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)root).name != "") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 2: Container creation
|
|
|
+bool test_container_creation() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ bool exists = false;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("users", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)container).entity_type != EntityType.CONTAINER) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)container).name != "users") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)container).path.to_string() != "/users") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify it exists
|
|
|
+ engine.entity_exists_async.begin(((!)container).path, (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 3: Document creation
|
|
|
+bool test_document_creation() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? users = null;
|
|
|
+ Entity? john = null;
|
|
|
+ bool exists = false;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("users", (obj, res) => {
|
|
|
+ try {
|
|
|
+ users = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || users == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)users).create_document_async.begin("john", "User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ john = ((!)users).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || john == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)john).entity_type != EntityType.DOCUMENT) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)john).name != "john") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)john).type_label != "User") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)john).path.to_string() != "/users/john") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify it exists
|
|
|
+ engine.entity_exists_async.begin(((!)john).path, (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 4: Container deletion
|
|
|
+bool test_container_deletion() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ bool exists_before = false;
|
|
|
+ bool exists_after = true;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("temp", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check exists before
|
|
|
+ engine.entity_exists_async.begin(((!)container).path, (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists_before = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists_before) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Delete
|
|
|
+ ((!)container).delete_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)container).delete_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check exists after
|
|
|
+ engine.entity_exists_async.begin(((!)container).path, (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists_after = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (exists_after) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 5: Document deletion
|
|
|
+bool test_document_deletion() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? docs = null;
|
|
|
+ Entity? doc = null;
|
|
|
+ bool exists_before = false;
|
|
|
+ bool exists_after = true;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("docs", (obj, res) => {
|
|
|
+ try {
|
|
|
+ docs = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || docs == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)docs).create_document_async.begin("doc1", "Document", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc = ((!)docs).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check exists before
|
|
|
+ engine.entity_exists_async.begin(((!)doc).path, (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists_before = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists_before) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Delete
|
|
|
+ ((!)doc).delete_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc).delete_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check exists after
|
|
|
+ engine.entity_exists_async.begin(((!)doc).path, (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists_after = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (exists_after) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 6: Property access
|
|
|
+bool test_property_access() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? docs = null;
|
|
|
+ Entity? doc = null;
|
|
|
+ Invercargill.Element? name = null;
|
|
|
+ Invercargill.Element? count = null;
|
|
|
+ Invercargill.Element? active = null;
|
|
|
+ Invercargill.Element? missing = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("docs", (obj, res) => {
|
|
|
+ try {
|
|
|
+ docs = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || docs == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)docs).create_document_async.begin("doc1", "Document", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc = ((!)docs).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set properties
|
|
|
+ ((!)doc).set_entity_property_async.begin("name", new Invercargill.NativeElement<string>("Test Document"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).set_entity_property_async.begin("count", new Invercargill.NativeElement<int64?>(42), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).set_entity_property_async.begin("active", new Invercargill.NativeElement<bool?>(true), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get properties
|
|
|
+ ((!)doc).get_entity_property_async.begin("name", (obj, res) => {
|
|
|
+ try {
|
|
|
+ name = ((!)doc).get_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || name == null || ((!)name).is_null()) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ string name_val = ((!)name).as<string>();
|
|
|
+ if (name_val != "Test Document") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).get_entity_property_async.begin("count", (obj, res) => {
|
|
|
+ try {
|
|
|
+ count = ((!)doc).get_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || count == null || ((!)count).is_null()) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ int64? count_val = ((!)count).as<int64?>();
|
|
|
+ if (count_val == null || (!)count_val != 42) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).get_entity_property_async.begin("active", (obj, res) => {
|
|
|
+ try {
|
|
|
+ active = ((!)doc).get_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || active == null || ((!)active).is_null()) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ bool active_val = ((!)active).as<bool?>();
|
|
|
+ if (active_val != true) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Non-existent property
|
|
|
+ ((!)doc).get_entity_property_async.begin("missing", (obj, res) => {
|
|
|
+ try {
|
|
|
+ missing = ((!)doc).get_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (missing != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 7: Property removal
|
|
|
+bool test_property_removal() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? docs = null;
|
|
|
+ Entity? doc = null;
|
|
|
+ Invercargill.Element? value = null;
|
|
|
+ Invercargill.Element? value_after = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("docs", (obj, res) => {
|
|
|
+ try {
|
|
|
+ docs = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || docs == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)docs).create_document_async.begin("doc1", "Document", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc = ((!)docs).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).set_entity_property_async.begin("temp", new Invercargill.NativeElement<string>("temporary"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).get_entity_property_async.begin("temp", (obj, res) => {
|
|
|
+ try {
|
|
|
+ value = ((!)doc).get_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || value == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).remove_property_async.begin("temp", (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc).remove_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).get_entity_property_async.begin("temp", (obj, res) => {
|
|
|
+ try {
|
|
|
+ value_after = ((!)doc).get_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (value_after != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 8: Get children
|
|
|
+bool test_get_children() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? users = null;
|
|
|
+ Entity[] children = {};
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("users", (obj, res) => {
|
|
|
+ try {
|
|
|
+ users = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || users == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create documents
|
|
|
+ ((!)users).create_document_async.begin("john", "User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)users).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)users).create_document_async.begin("jane", "User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)users).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)users).create_document_async.begin("bob", "User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)users).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)users).get_children_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ children = ((!)users).get_children_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || children.length != 3) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check all children are present
|
|
|
+ var child_names = new Invercargill.DataStructures.HashSet<string>();
|
|
|
+ foreach (var child in children) {
|
|
|
+ child_names.add(child.name);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!child_names.has("john") || !child_names.has("jane") || !child_names.has("bob")) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 9: Entity exists
|
|
|
+bool test_entity_exists() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? users = null;
|
|
|
+ bool exists1 = false;
|
|
|
+ bool exists2 = true;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("users", (obj, res) => {
|
|
|
+ try {
|
|
|
+ users = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || users == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/users"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists1 = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists1) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/nonexistent"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists2 = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (exists2) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 10: Get entity
|
|
|
+bool test_get_entity() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? users = null;
|
|
|
+ Entity? retrieved = null;
|
|
|
+ Entity? nonexistent = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("users", (obj, res) => {
|
|
|
+ try {
|
|
|
+ users = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || users == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)users).create_document_async.begin("john", "User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)users).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get entity by path
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/users/john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ retrieved = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || retrieved == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)retrieved).entity_type != EntityType.DOCUMENT) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)retrieved).name != "john") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)retrieved).type_label != "User") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get non-existent entity - should throw
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/nonexistent"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ nonexistent = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ // Should have thrown
|
|
|
+ if (nonexistent != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 11: Query by type
|
|
|
+bool test_query_by_type() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? users = null;
|
|
|
+ Entity[] user_results = {};
|
|
|
+ Entity[] admin_results = {};
|
|
|
+ Entity[] none_results = {};
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("users", (obj, res) => {
|
|
|
+ try {
|
|
|
+ users = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || users == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)users).create_document_async.begin("john", "User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)users).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)users).create_document_async.begin("jane", "User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)users).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)users).create_document_async.begin("admin", "Admin", (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)users).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Query for Users
|
|
|
+ engine.query_by_type_async.begin("User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ user_results = engine.query_by_type_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || user_results.length != 2) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Query for Admins
|
|
|
+ engine.query_by_type_async.begin("Admin", (obj, res) => {
|
|
|
+ try {
|
|
|
+ admin_results = engine.query_by_type_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || admin_results.length != 1) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Query for non-existent type
|
|
|
+ engine.query_by_type_async.begin("NonExistent", (obj, res) => {
|
|
|
+ try {
|
|
|
+ none_results = engine.query_by_type_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || none_results.length != 0) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 12: Nested containers
|
|
|
+bool test_nested_containers() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? level1 = null;
|
|
|
+ Entity? level2 = null;
|
|
|
+ Entity? level3 = null;
|
|
|
+ bool exists1 = false;
|
|
|
+ bool exists2 = false;
|
|
|
+ bool exists3 = false;
|
|
|
+ Entity[] level1_children = {};
|
|
|
+ Entity[] level2_children = {};
|
|
|
+ Entity[] level3_children = {};
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("level1", (obj, res) => {
|
|
|
+ try {
|
|
|
+ level1 = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || level1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)level1).create_container_async.begin("level2", (obj, res) => {
|
|
|
+ try {
|
|
|
+ level2 = ((!)level1).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || level2 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)level2).create_container_async.begin("level3", (obj, res) => {
|
|
|
+ try {
|
|
|
+ level3 = ((!)level2).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || level3 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/level1"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists1 = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists1) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/level1/level2"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists2 = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists2) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/level1/level2/level3"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists3 = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists3) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify parent-child relationships
|
|
|
+ ((!)level1).get_children_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ level1_children = ((!)level1).get_children_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || level1_children.length != 1) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)level2).get_children_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ level2_children = ((!)level2).get_children_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || level2_children.length != 1) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)level3).get_children_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ level3_children = ((!)level3).get_children_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || level3_children.length != 0) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 13: Multiple documents
|
|
|
+bool test_multiple_documents() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? users = null;
|
|
|
+ Entity[] children = {};
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("users", (obj, res) => {
|
|
|
+ try {
|
|
|
+ users = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || users == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create multiple documents
|
|
|
+ for (int i = 0; i < 10; i++) {
|
|
|
+ int idx = i;
|
|
|
+ Entity? doc = null;
|
|
|
+ ((!)users).create_document_async.begin(@"user$idx", "User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc = ((!)users).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).set_entity_property_async.begin("index", new Invercargill.NativeElement<int64?>(idx), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify all exist
|
|
|
+ ((!)users).get_children_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ children = ((!)users).get_children_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || children.length != 10) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify properties
|
|
|
+ for (int i = 0; i < 10; i++) {
|
|
|
+ int idx = i;
|
|
|
+ Entity? doc = null;
|
|
|
+ Invercargill.Element? index = null;
|
|
|
+
|
|
|
+ engine.get_entity_async.begin(new EntityPath(@"/users/user$idx"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc).get_entity_property_async.begin("index", (obj, res) => {
|
|
|
+ try {
|
|
|
+ index = ((!)doc).get_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || index == null || ((!)index).is_null()) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ int64? index_val = ((!)index).as<int64?>();
|
|
|
+ if (index_val == null || (!)index_val != idx) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 14: Entity path
|
|
|
+bool test_entity_path() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? a = null;
|
|
|
+ Entity? b = null;
|
|
|
+ Entity? c = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root).create_container_async.begin("a", (obj, res) => {
|
|
|
+ try {
|
|
|
+ a = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || a == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)a).create_container_async.begin("b", (obj, res) => {
|
|
|
+ try {
|
|
|
+ b = ((!)a).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || b == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)b).create_document_async.begin("c", "Document", (obj, res) => {
|
|
|
+ try {
|
|
|
+ c = ((!)b).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || c == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!((!)root).path.is_root) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)a).path.to_string() != "/a") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)b).path.to_string() != "/a/b") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (((!)c).path.to_string() != "/a/b/c") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check parent relationships
|
|
|
+ if (!((!)c).path.parent.equals(((!)b).path)) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!((!)b).path.parent.equals(((!)a).path)) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!((!)a).path.parent.equals(((!)root).path)) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 15: Persistence
|
|
|
+bool test_engine_persistence() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ // Create and write
|
|
|
+ var engine1 = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root1 = null;
|
|
|
+ Entity? users1 = null;
|
|
|
+ Entity? john1 = null;
|
|
|
+ bool exists1 = false;
|
|
|
+ bool exists2 = false;
|
|
|
+ Entity? root2 = null;
|
|
|
+ Entity? john2 = null;
|
|
|
+ Invercargill.Element? email = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine1.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root1 = engine1.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)root1).create_container_async.begin("users", (obj, res) => {
|
|
|
+ try {
|
|
|
+ users1 = ((!)root1).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || users1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)users1).create_document_async.begin("john", "User", (obj, res) => {
|
|
|
+ try {
|
|
|
+ john1 = ((!)users1).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || john1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)john1).set_entity_property_async.begin("email", new Invercargill.NativeElement<string>("john@example.com"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)john1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create new engine instance
|
|
|
+ var engine2 = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ // Verify data persists
|
|
|
+ engine2.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root2 = engine2.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root2 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ engine2.entity_exists_async.begin(new EntityPath("/users"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists1 = engine2.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists1) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ engine2.entity_exists_async.begin(new EntityPath("/users/john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists2 = engine2.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (!exists2) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ engine2.get_entity_async.begin(new EntityPath("/users/john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ john2 = engine2.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || john2 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)john2).get_entity_property_async.begin("email", (obj, res) => {
|
|
|
+ try {
|
|
|
+ email = ((!)john2).get_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || email == null || ((!)email).is_null()) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ string email_val = ((!)email).as<string>();
|
|
|
+ if (email_val != "john@example.com") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Cleanup helper
|
|
|
+void cleanup_dir(string path) {
|
|
|
+ try {
|
|
|
+ Dir dir = Dir.open(path, 0);
|
|
|
+ string? name;
|
|
|
+ while ((name = dir.read_name()) != null) {
|
|
|
+ FileUtils.unlink(Path.build_filename(path, name));
|
|
|
+ }
|
|
|
+ } catch (FileError e) {
|
|
|
+ // Ignore errors
|
|
|
+ }
|
|
|
+ DirUtils.remove(path);
|
|
|
+}
|
|
|
+
|
|
|
+// ============================================================================
|
|
|
+// Virtual Entity Resolution Tests
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
+// Test 16: Category virtual child resolution - get_entity_async for member
|
|
|
+bool test_category_virtual_child_resolution() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? doc2 = null;
|
|
|
+ Entity? category = null;
|
|
|
+ Entity? resolved_doc = null;
|
|
|
+ bool exists = false;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("posts", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // IMPORTANT: Create category FIRST so hooks are registered
|
|
|
+ ((!)container).create_category_async.begin("published", "Post", "!draft", (obj, res) => {
|
|
|
+ try {
|
|
|
+ category = ((!)container).create_category_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || category == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now create documents - hooks will update the category index
|
|
|
+ ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set draft = false on post1 (matches !draft predicate)
|
|
|
+ ((!)doc1).set_entity_property_async.begin("draft", new Invercargill.NativeElement<bool?>(false), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)container).create_document_async.begin("post2", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc2 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc2 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set draft = true on post2 (does NOT match !draft predicate)
|
|
|
+ ((!)doc2).set_entity_property_async.begin("draft", new Invercargill.NativeElement<bool?>(true), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc2).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: Resolve category member via direct path
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ resolved_doc = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ // post1 should be found (draft = false, matches !draft)
|
|
|
+ if (error != null || resolved_doc == null) {
|
|
|
+ stderr.printf("Failed to resolve post1: %s\n", error != null ? ((!)error).message : "null");
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)resolved_doc).entity_type != EntityType.DOCUMENT) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)resolved_doc).name != "post1") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: entity_exists_async for category member
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || !exists) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 17: Category virtual child resolution - non-existent child returns ENTITY_NOT_FOUND
|
|
|
+bool test_category_virtual_child_not_found() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? category = null;
|
|
|
+ Entity? resolved_doc = null;
|
|
|
+ bool exists = true;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("posts", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // IMPORTANT: Create category FIRST so hooks are registered
|
|
|
+ ((!)container).create_category_async.begin("published", "Post", "!draft", (obj, res) => {
|
|
|
+ try {
|
|
|
+ category = ((!)container).create_category_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || category == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now create document with draft = true (does NOT match !draft predicate)
|
|
|
+ ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc1).set_entity_property_async.begin("draft", new Invercargill.NativeElement<bool?>(true), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: Try to resolve non-member (post1 has draft=true, doesn't match !draft)
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ resolved_doc = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ // post1 should NOT be found (draft = true, doesn't match !draft)
|
|
|
+ if (resolved_doc != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Should have thrown ENTITY_NOT_FOUND
|
|
|
+ if (error == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: entity_exists_async for non-member
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (exists) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 18: Catalogue virtual child resolution - group key returns CatalogueGroup
|
|
|
+bool test_catalogue_virtual_child_group() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? doc2 = null;
|
|
|
+ Entity? catalogue = null;
|
|
|
+ Entity? resolved_group = null;
|
|
|
+ bool exists = false;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("posts", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // IMPORTANT: Create catalogue FIRST so hooks are registered
|
|
|
+ ((!)container).create_catalogue_async.begin("by-author", "Post", "author", (obj, res) => {
|
|
|
+ try {
|
|
|
+ catalogue = ((!)container).create_catalogue_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || catalogue == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now create documents - hooks will update the catalogue index
|
|
|
+ ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc1).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)container).create_document_async.begin("post2", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc2 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc2 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc2).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("jane"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc2).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: Resolve group via direct path
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/posts/by-author/john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ resolved_group = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || resolved_group == null) {
|
|
|
+ stderr.printf("Failed to resolve group: %s\n", error != null ? ((!)error).message : "null");
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Should be a CatalogueGroup (appears as CATALOGUE type)
|
|
|
+ if (((!)resolved_group).entity_type != EntityType.CATALOGUE) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)resolved_group).name != "john") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: entity_exists_async for group
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/posts/by-author/john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || !exists) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 19: Catalogue virtual child resolution - document within group
|
|
|
+bool test_catalogue_virtual_child_document() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? doc2 = null;
|
|
|
+ Entity? catalogue = null;
|
|
|
+ Entity? resolved_doc = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("posts", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // IMPORTANT: Create catalogue FIRST so hooks are registered
|
|
|
+ ((!)container).create_catalogue_async.begin("by-author", "Post", "author", (obj, res) => {
|
|
|
+ try {
|
|
|
+ catalogue = ((!)container).create_catalogue_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || catalogue == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now create documents - hooks will update the catalogue index
|
|
|
+ ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc1).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)container).create_document_async.begin("post2", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc2 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc2 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc2).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("jane"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc2).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: Resolve document via direct path (document name within any group)
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/posts/by-author/post1"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ resolved_doc = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || resolved_doc == null) {
|
|
|
+ stderr.printf("Failed to resolve document: %s\n", error != null ? ((!)error).message : "null");
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Should be a Document
|
|
|
+ if (((!)resolved_doc).entity_type != EntityType.DOCUMENT) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)resolved_doc).name != "post1") {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 20: Catalogue virtual child resolution - non-existent returns ENTITY_NOT_FOUND
|
|
|
+bool test_catalogue_virtual_child_not_found() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? catalogue = null;
|
|
|
+ Entity? resolved = null;
|
|
|
+ bool exists = true;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("posts", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create document
|
|
|
+ ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc1).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create catalogue grouped by author
|
|
|
+ ((!)container).create_catalogue_async.begin("by-author", "Post", "author", (obj, res) => {
|
|
|
+ try {
|
|
|
+ catalogue = ((!)container).create_catalogue_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || catalogue == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Populate the catalogue index
|
|
|
+ var catalogue_impl = catalogue as Implexus.Entities.Catalogue;
|
|
|
+ if (catalogue_impl != null) {
|
|
|
+ try {
|
|
|
+ ((!)catalogue_impl).populate_index();
|
|
|
+ } catch (Error e) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: Try to resolve non-existent group
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/posts/by-author/nonexistent"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ resolved = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ // Should NOT be found
|
|
|
+ if (resolved != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Should have thrown ENTITY_NOT_FOUND
|
|
|
+ if (error == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: entity_exists_async for non-existent
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/posts/by-author/nonexistent"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (exists) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 21: Index virtual child resolution - search pattern returns IndexResult
|
|
|
+bool test_index_virtual_child_resolution() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? doc2 = null;
|
|
|
+ Entity? index = null;
|
|
|
+ Entity? resolved_result = null;
|
|
|
+ bool exists = false;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("articles", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create documents FIRST with searchable content
|
|
|
+ ((!)container).create_document_async.begin("article1", "Article", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc1).set_entity_property_async.begin("title", new Invercargill.NativeElement<string>("Introduction to Vala"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)container).create_document_async.begin("article2", "Article", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc2 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc2 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc2).set_entity_property_async.begin("title", new Invercargill.NativeElement<string>("Advanced Vala Techniques"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc2).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create index AFTER documents - populate_index() will be called internally
|
|
|
+ ((!)container).create_index_async.begin("search", "Article", "title", (obj, res) => {
|
|
|
+ try {
|
|
|
+ index = ((!)container).create_index_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || index == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Explicitly populate the index (needed for tests)
|
|
|
+ var index_impl = index as Implexus.Entities.Index;
|
|
|
+ if (index_impl != null) {
|
|
|
+ try {
|
|
|
+ ((!)index_impl).populate_index();
|
|
|
+ } catch (Error e) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: Resolve search pattern via direct path
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/articles/search/*Vala*"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ resolved_result = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || resolved_result == null) {
|
|
|
+ stderr.printf("Failed to resolve search: %s\n", error != null ? ((!)error).message : "null");
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Should be a Container (IndexResult appears as CONTAINER)
|
|
|
+ if (((!)resolved_result).entity_type != EntityType.CONTAINER) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: entity_exists_async for search with matches
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/articles/search/*Vala*"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || !exists) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 22: Index virtual child resolution - no matches returns ENTITY_NOT_FOUND
|
|
|
+bool test_index_virtual_child_no_matches() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? index = null;
|
|
|
+ Entity? resolved = null;
|
|
|
+ bool exists = true;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("articles", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create document
|
|
|
+ ((!)container).create_document_async.begin("article1", "Article", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc1).set_entity_property_async.begin("title", new Invercargill.NativeElement<string>("Introduction to Vala"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create index over title property
|
|
|
+ ((!)container).create_index_async.begin("search", "Article", "title", (obj, res) => {
|
|
|
+ try {
|
|
|
+ index = ((!)container).create_index_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || index == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Populate the index
|
|
|
+ var index_impl = index as Implexus.Entities.Index;
|
|
|
+ if (index_impl != null) {
|
|
|
+ try {
|
|
|
+ ((!)index_impl).populate_index();
|
|
|
+ } catch (Error e) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: Search with no matches
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/articles/search/*NonExistent*"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ resolved = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ // Should NOT be found
|
|
|
+ if (resolved != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Should have thrown ENTITY_NOT_FOUND
|
|
|
+ if (error == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test: entity_exists_async for search with no matches
|
|
|
+ engine.entity_exists_async.begin(new EntityPath("/articles/search/*NonExistent*"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ exists = engine.entity_exists_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (exists) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 23: Comparison - direct path equals navigation for Category
|
|
|
+bool test_category_direct_vs_navigation() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? category = null;
|
|
|
+ Entity? direct_doc = null;
|
|
|
+ Entity? nav_doc = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("posts", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // IMPORTANT: Create category FIRST so hooks are registered
|
|
|
+ ((!)container).create_category_async.begin("published", "Post", "!draft", (obj, res) => {
|
|
|
+ try {
|
|
|
+ category = ((!)container).create_category_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || category == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now create document - hooks will update the category index
|
|
|
+ ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc1).set_entity_property_async.begin("draft", new Invercargill.NativeElement<bool?>(false), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Method 1: Direct path lookup
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ direct_doc = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || direct_doc == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Method 2: Navigation via get_child_async
|
|
|
+ ((!)category).get_child_async.begin("post1", (obj, res) => {
|
|
|
+ try {
|
|
|
+ nav_doc = ((!)category).get_child_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || nav_doc == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Both methods should return the same document (by path)
|
|
|
+ if (((!)direct_doc).path.to_string() != ((!)nav_doc).path.to_string()) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)direct_doc).name != ((!)nav_doc).name) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 24: Comparison - direct path equals navigation for Catalogue group
|
|
|
+bool test_catalogue_direct_vs_navigation() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? catalogue = null;
|
|
|
+ Entity? direct_group = null;
|
|
|
+ Entity? nav_group = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("posts", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // IMPORTANT: Create catalogue FIRST so hooks are registered
|
|
|
+ ((!)container).create_catalogue_async.begin("by-author", "Post", "author", (obj, res) => {
|
|
|
+ try {
|
|
|
+ catalogue = ((!)container).create_catalogue_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || catalogue == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now create document - hooks will update the catalogue index
|
|
|
+ ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc1).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Method 1: Direct path lookup
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/posts/by-author/john"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ direct_group = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || direct_group == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Method 2: Navigation via get_child_async
|
|
|
+ ((!)catalogue).get_child_async.begin("john", (obj, res) => {
|
|
|
+ try {
|
|
|
+ nav_group = ((!)catalogue).get_child_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || nav_group == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Both methods should return the same group (by path)
|
|
|
+ if (((!)direct_group).path.to_string() != ((!)nav_group).path.to_string()) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((!)direct_group).name != ((!)nav_group).name) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Test 25: Comparison - direct path equals navigation for Index search
|
|
|
+bool test_index_direct_vs_navigation() {
|
|
|
+ string temp_dir = create_temp_dir();
|
|
|
+
|
|
|
+ var engine = new EmbeddedEngine.with_path(temp_dir);
|
|
|
+
|
|
|
+ var loop = new MainLoop();
|
|
|
+ Entity? root = null;
|
|
|
+ Entity? container = null;
|
|
|
+ Entity? doc1 = null;
|
|
|
+ Entity? index = null;
|
|
|
+ Entity? direct_result = null;
|
|
|
+ Entity? nav_result = null;
|
|
|
+ Error? error = null;
|
|
|
+
|
|
|
+ engine.get_root_async.begin((obj, res) => {
|
|
|
+ try {
|
|
|
+ root = engine.get_root_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || root == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create container
|
|
|
+ ((!)root).create_container_async.begin("articles", (obj, res) => {
|
|
|
+ try {
|
|
|
+ container = ((!)root).create_container_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || container == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create document FIRST with searchable content
|
|
|
+ ((!)container).create_document_async.begin("article1", "Article", (obj, res) => {
|
|
|
+ try {
|
|
|
+ doc1 = ((!)container).create_document_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || doc1 == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((!)doc1).set_entity_property_async.begin("title", new Invercargill.NativeElement<string>("Introduction to Vala"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ ((!)doc1).set_entity_property_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create index AFTER documents - populate_index() will be called internally
|
|
|
+ ((!)container).create_index_async.begin("search", "Article", "title", (obj, res) => {
|
|
|
+ try {
|
|
|
+ index = ((!)container).create_index_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || index == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Explicitly populate the index (needed for tests)
|
|
|
+ var index_impl = index as Implexus.Entities.Index;
|
|
|
+ if (index_impl != null) {
|
|
|
+ try {
|
|
|
+ ((!)index_impl).populate_index();
|
|
|
+ } catch (Error e) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Method 1: Direct path lookup
|
|
|
+ engine.get_entity_async.begin(new EntityPath("/articles/search/*Vala*"), (obj, res) => {
|
|
|
+ try {
|
|
|
+ direct_result = engine.get_entity_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || direct_result == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Method 2: Navigation via get_child_async
|
|
|
+ ((!)index).get_child_async.begin("*Vala*", (obj, res) => {
|
|
|
+ try {
|
|
|
+ nav_result = ((!)index).get_child_async.end(res);
|
|
|
+ } catch (Error e) {
|
|
|
+ error = e;
|
|
|
+ }
|
|
|
+ loop.quit();
|
|
|
+ });
|
|
|
+ loop.run();
|
|
|
+
|
|
|
+ if (error != null || nav_result == null) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Both methods should return an IndexResult with the same path
|
|
|
+ if (((!)direct_result).path.to_string() != ((!)nav_result).path.to_string()) {
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_dir(temp_dir);
|
|
|
+ return true;
|
|
|
+}
|