EmbeddedEngineTest.vala 88 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566
  1. /**
  2. * EmbeddedEngineTest - Unit tests for EmbeddedEngine
  3. *
  4. * All tests use async methods with MainLoop wrappers for the test framework.
  5. */
  6. using Implexus.Core;
  7. using Implexus.Engine;
  8. using Implexus.Storage;
  9. public static int main(string[] args) {
  10. int passed = 0;
  11. int failed = 0;
  12. // Test 1: Root creation
  13. if (test_root_creation()) {
  14. passed++;
  15. stdout.puts("PASS: test_root_creation\n");
  16. } else {
  17. failed++;
  18. stdout.puts("FAIL: test_root_creation\n");
  19. }
  20. // Test 2: Container creation
  21. if (test_container_creation()) {
  22. passed++;
  23. stdout.puts("PASS: test_container_creation\n");
  24. } else {
  25. failed++;
  26. stdout.puts("FAIL: test_container_creation\n");
  27. }
  28. // Test 3: Document creation
  29. if (test_document_creation()) {
  30. passed++;
  31. stdout.puts("PASS: test_document_creation\n");
  32. } else {
  33. failed++;
  34. stdout.puts("FAIL: test_document_creation\n");
  35. }
  36. // Test 4: Container deletion
  37. if (test_container_deletion()) {
  38. passed++;
  39. stdout.puts("PASS: test_container_deletion\n");
  40. } else {
  41. failed++;
  42. stdout.puts("FAIL: test_container_deletion\n");
  43. }
  44. // Test 5: Document deletion
  45. if (test_document_deletion()) {
  46. passed++;
  47. stdout.puts("PASS: test_document_deletion\n");
  48. } else {
  49. failed++;
  50. stdout.puts("FAIL: test_document_deletion\n");
  51. }
  52. // Test 6: Property access
  53. if (test_property_access()) {
  54. passed++;
  55. stdout.puts("PASS: test_property_access\n");
  56. } else {
  57. failed++;
  58. stdout.puts("FAIL: test_property_access\n");
  59. }
  60. // Test 7: Property removal
  61. if (test_property_removal()) {
  62. passed++;
  63. stdout.puts("PASS: test_property_removal\n");
  64. } else {
  65. failed++;
  66. stdout.puts("FAIL: test_property_removal\n");
  67. }
  68. // Test 8: Get children
  69. if (test_get_children()) {
  70. passed++;
  71. stdout.puts("PASS: test_get_children\n");
  72. } else {
  73. failed++;
  74. stdout.puts("FAIL: test_get_children\n");
  75. }
  76. // Test 9: Entity exists
  77. if (test_entity_exists()) {
  78. passed++;
  79. stdout.puts("PASS: test_entity_exists\n");
  80. } else {
  81. failed++;
  82. stdout.puts("FAIL: test_entity_exists\n");
  83. }
  84. // Test 10: Get entity
  85. if (test_get_entity()) {
  86. passed++;
  87. stdout.puts("PASS: test_get_entity\n");
  88. } else {
  89. failed++;
  90. stdout.puts("FAIL: test_get_entity\n");
  91. }
  92. // Test 11: Query by type
  93. if (test_query_by_type()) {
  94. passed++;
  95. stdout.puts("PASS: test_query_by_type\n");
  96. } else {
  97. failed++;
  98. stdout.puts("FAIL: test_query_by_type\n");
  99. }
  100. // Test 12: Nested containers
  101. if (test_nested_containers()) {
  102. passed++;
  103. stdout.puts("PASS: test_nested_containers\n");
  104. } else {
  105. failed++;
  106. stdout.puts("FAIL: test_nested_containers\n");
  107. }
  108. // Test 13: Multiple documents
  109. if (test_multiple_documents()) {
  110. passed++;
  111. stdout.puts("PASS: test_multiple_documents\n");
  112. } else {
  113. failed++;
  114. stdout.puts("FAIL: test_multiple_documents\n");
  115. }
  116. // Test 14: Entity path
  117. if (test_entity_path()) {
  118. passed++;
  119. stdout.puts("PASS: test_entity_path\n");
  120. } else {
  121. failed++;
  122. stdout.puts("FAIL: test_entity_path\n");
  123. }
  124. // Test 15: Persistence
  125. if (test_engine_persistence()) {
  126. passed++;
  127. stdout.puts("PASS: test_engine_persistence\n");
  128. } else {
  129. failed++;
  130. stdout.puts("FAIL: test_engine_persistence\n");
  131. }
  132. // Test 16: Category virtual child resolution
  133. if (test_category_virtual_child_resolution()) {
  134. passed++;
  135. stdout.puts("PASS: test_category_virtual_child_resolution\n");
  136. } else {
  137. failed++;
  138. stdout.puts("FAIL: test_category_virtual_child_resolution\n");
  139. }
  140. // Test 17: Category virtual child not found
  141. if (test_category_virtual_child_not_found()) {
  142. passed++;
  143. stdout.puts("PASS: test_category_virtual_child_not_found\n");
  144. } else {
  145. failed++;
  146. stdout.puts("FAIL: test_category_virtual_child_not_found\n");
  147. }
  148. // Test 18: Catalogue virtual child group resolution
  149. if (test_catalogue_virtual_child_group()) {
  150. passed++;
  151. stdout.puts("PASS: test_catalogue_virtual_child_group\n");
  152. } else {
  153. failed++;
  154. stdout.puts("FAIL: test_catalogue_virtual_child_group\n");
  155. }
  156. // Test 19: Catalogue virtual child document resolution
  157. if (test_catalogue_virtual_child_document()) {
  158. passed++;
  159. stdout.puts("PASS: test_catalogue_virtual_child_document\n");
  160. } else {
  161. failed++;
  162. stdout.puts("FAIL: test_catalogue_virtual_child_document\n");
  163. }
  164. // Test 20: Catalogue virtual child not found
  165. if (test_catalogue_virtual_child_not_found()) {
  166. passed++;
  167. stdout.puts("PASS: test_catalogue_virtual_child_not_found\n");
  168. } else {
  169. failed++;
  170. stdout.puts("FAIL: test_catalogue_virtual_child_not_found\n");
  171. }
  172. // Test 21: Index virtual child resolution
  173. if (test_index_virtual_child_resolution()) {
  174. passed++;
  175. stdout.puts("PASS: test_index_virtual_child_resolution\n");
  176. } else {
  177. failed++;
  178. stdout.puts("FAIL: test_index_virtual_child_resolution\n");
  179. }
  180. // Test 22: Index virtual child no matches
  181. if (test_index_virtual_child_no_matches()) {
  182. passed++;
  183. stdout.puts("PASS: test_index_virtual_child_no_matches\n");
  184. } else {
  185. failed++;
  186. stdout.puts("FAIL: test_index_virtual_child_no_matches\n");
  187. }
  188. // Test 23: Category direct vs navigation comparison
  189. if (test_category_direct_vs_navigation()) {
  190. passed++;
  191. stdout.puts("PASS: test_category_direct_vs_navigation\n");
  192. } else {
  193. failed++;
  194. stdout.puts("FAIL: test_category_direct_vs_navigation\n");
  195. }
  196. // Test 24: Catalogue direct vs navigation comparison
  197. if (test_catalogue_direct_vs_navigation()) {
  198. passed++;
  199. stdout.puts("PASS: test_catalogue_direct_vs_navigation\n");
  200. } else {
  201. failed++;
  202. stdout.puts("FAIL: test_catalogue_direct_vs_navigation\n");
  203. }
  204. // Test 25: Index direct vs navigation comparison
  205. if (test_index_direct_vs_navigation()) {
  206. passed++;
  207. stdout.puts("PASS: test_index_direct_vs_navigation\n");
  208. } else {
  209. failed++;
  210. stdout.puts("FAIL: test_index_direct_vs_navigation\n");
  211. }
  212. stdout.printf("\nResults: %d passed, %d failed\n", passed, failed);
  213. return failed > 0 ? 1 : 0;
  214. }
  215. // Helper to create temporary directory
  216. string create_temp_dir() {
  217. string temp_dir = DirUtils.mkdtemp("implexus_engine_test_XXXXXX");
  218. return temp_dir;
  219. }
  220. // Test 1: Root creation
  221. bool test_root_creation() {
  222. string temp_dir = create_temp_dir();
  223. var engine = new EmbeddedEngine.with_path(temp_dir);
  224. var loop = new MainLoop();
  225. Entity? root = null;
  226. Error? error = null;
  227. engine.get_root_async.begin((obj, res) => {
  228. try {
  229. root = engine.get_root_async.end(res);
  230. } catch (Error e) {
  231. error = e;
  232. }
  233. loop.quit();
  234. });
  235. loop.run();
  236. if (error != null) {
  237. stderr.printf("Test error: %s\n", ((!)error).message);
  238. cleanup_dir(temp_dir);
  239. return false;
  240. }
  241. if (root == null) {
  242. cleanup_dir(temp_dir);
  243. return false;
  244. }
  245. if (((!)root).entity_type != EntityType.CONTAINER) {
  246. cleanup_dir(temp_dir);
  247. return false;
  248. }
  249. if (!((!)root).path.is_root) {
  250. cleanup_dir(temp_dir);
  251. return false;
  252. }
  253. if (((!)root).name != "") {
  254. cleanup_dir(temp_dir);
  255. return false;
  256. }
  257. cleanup_dir(temp_dir);
  258. return true;
  259. }
  260. // Test 2: Container creation
  261. bool test_container_creation() {
  262. string temp_dir = create_temp_dir();
  263. var engine = new EmbeddedEngine.with_path(temp_dir);
  264. var loop = new MainLoop();
  265. Entity? root = null;
  266. Entity? container = null;
  267. bool exists = false;
  268. Error? error = null;
  269. engine.get_root_async.begin((obj, res) => {
  270. try {
  271. root = engine.get_root_async.end(res);
  272. } catch (Error e) {
  273. error = e;
  274. }
  275. loop.quit();
  276. });
  277. loop.run();
  278. if (error != null || root == null) {
  279. cleanup_dir(temp_dir);
  280. return false;
  281. }
  282. ((!)root).create_container_async.begin("users", (obj, res) => {
  283. try {
  284. container = ((!)root).create_container_async.end(res);
  285. } catch (Error e) {
  286. error = e;
  287. }
  288. loop.quit();
  289. });
  290. loop.run();
  291. if (error != null || container == null) {
  292. cleanup_dir(temp_dir);
  293. return false;
  294. }
  295. if (((!)container).entity_type != EntityType.CONTAINER) {
  296. cleanup_dir(temp_dir);
  297. return false;
  298. }
  299. if (((!)container).name != "users") {
  300. cleanup_dir(temp_dir);
  301. return false;
  302. }
  303. if (((!)container).path.to_string() != "/users") {
  304. cleanup_dir(temp_dir);
  305. return false;
  306. }
  307. // Verify it exists
  308. engine.entity_exists_async.begin(((!)container).path, (obj, res) => {
  309. try {
  310. exists = engine.entity_exists_async.end(res);
  311. } catch (Error e) {
  312. error = e;
  313. }
  314. loop.quit();
  315. });
  316. loop.run();
  317. if (!exists) {
  318. cleanup_dir(temp_dir);
  319. return false;
  320. }
  321. cleanup_dir(temp_dir);
  322. return true;
  323. }
  324. // Test 3: Document creation
  325. bool test_document_creation() {
  326. string temp_dir = create_temp_dir();
  327. var engine = new EmbeddedEngine.with_path(temp_dir);
  328. var loop = new MainLoop();
  329. Entity? root = null;
  330. Entity? users = null;
  331. Entity? john = null;
  332. bool exists = false;
  333. Error? error = null;
  334. engine.get_root_async.begin((obj, res) => {
  335. try {
  336. root = engine.get_root_async.end(res);
  337. } catch (Error e) {
  338. error = e;
  339. }
  340. loop.quit();
  341. });
  342. loop.run();
  343. if (error != null || root == null) {
  344. cleanup_dir(temp_dir);
  345. return false;
  346. }
  347. ((!)root).create_container_async.begin("users", (obj, res) => {
  348. try {
  349. users = ((!)root).create_container_async.end(res);
  350. } catch (Error e) {
  351. error = e;
  352. }
  353. loop.quit();
  354. });
  355. loop.run();
  356. if (error != null || users == null) {
  357. cleanup_dir(temp_dir);
  358. return false;
  359. }
  360. ((!)users).create_document_async.begin("john", "User", (obj, res) => {
  361. try {
  362. john = ((!)users).create_document_async.end(res);
  363. } catch (Error e) {
  364. error = e;
  365. }
  366. loop.quit();
  367. });
  368. loop.run();
  369. if (error != null || john == null) {
  370. cleanup_dir(temp_dir);
  371. return false;
  372. }
  373. if (((!)john).entity_type != EntityType.DOCUMENT) {
  374. cleanup_dir(temp_dir);
  375. return false;
  376. }
  377. if (((!)john).name != "john") {
  378. cleanup_dir(temp_dir);
  379. return false;
  380. }
  381. if (((!)john).type_label != "User") {
  382. cleanup_dir(temp_dir);
  383. return false;
  384. }
  385. if (((!)john).path.to_string() != "/users/john") {
  386. cleanup_dir(temp_dir);
  387. return false;
  388. }
  389. // Verify it exists
  390. engine.entity_exists_async.begin(((!)john).path, (obj, res) => {
  391. try {
  392. exists = engine.entity_exists_async.end(res);
  393. } catch (Error e) {
  394. error = e;
  395. }
  396. loop.quit();
  397. });
  398. loop.run();
  399. if (!exists) {
  400. cleanup_dir(temp_dir);
  401. return false;
  402. }
  403. cleanup_dir(temp_dir);
  404. return true;
  405. }
  406. // Test 4: Container deletion
  407. bool test_container_deletion() {
  408. string temp_dir = create_temp_dir();
  409. var engine = new EmbeddedEngine.with_path(temp_dir);
  410. var loop = new MainLoop();
  411. Entity? root = null;
  412. Entity? container = null;
  413. bool exists_before = false;
  414. bool exists_after = true;
  415. Error? error = null;
  416. engine.get_root_async.begin((obj, res) => {
  417. try {
  418. root = engine.get_root_async.end(res);
  419. } catch (Error e) {
  420. error = e;
  421. }
  422. loop.quit();
  423. });
  424. loop.run();
  425. if (error != null || root == null) {
  426. cleanup_dir(temp_dir);
  427. return false;
  428. }
  429. ((!)root).create_container_async.begin("temp", (obj, res) => {
  430. try {
  431. container = ((!)root).create_container_async.end(res);
  432. } catch (Error e) {
  433. error = e;
  434. }
  435. loop.quit();
  436. });
  437. loop.run();
  438. if (error != null || container == null) {
  439. cleanup_dir(temp_dir);
  440. return false;
  441. }
  442. // Check exists before
  443. engine.entity_exists_async.begin(((!)container).path, (obj, res) => {
  444. try {
  445. exists_before = engine.entity_exists_async.end(res);
  446. } catch (Error e) {
  447. error = e;
  448. }
  449. loop.quit();
  450. });
  451. loop.run();
  452. if (!exists_before) {
  453. cleanup_dir(temp_dir);
  454. return false;
  455. }
  456. // Delete
  457. ((!)container).delete_async.begin((obj, res) => {
  458. try {
  459. ((!)container).delete_async.end(res);
  460. } catch (Error e) {
  461. error = e;
  462. }
  463. loop.quit();
  464. });
  465. loop.run();
  466. if (error != null) {
  467. cleanup_dir(temp_dir);
  468. return false;
  469. }
  470. // Check exists after
  471. engine.entity_exists_async.begin(((!)container).path, (obj, res) => {
  472. try {
  473. exists_after = engine.entity_exists_async.end(res);
  474. } catch (Error e) {
  475. error = e;
  476. }
  477. loop.quit();
  478. });
  479. loop.run();
  480. if (exists_after) {
  481. cleanup_dir(temp_dir);
  482. return false;
  483. }
  484. cleanup_dir(temp_dir);
  485. return true;
  486. }
  487. // Test 5: Document deletion
  488. bool test_document_deletion() {
  489. string temp_dir = create_temp_dir();
  490. var engine = new EmbeddedEngine.with_path(temp_dir);
  491. var loop = new MainLoop();
  492. Entity? root = null;
  493. Entity? docs = null;
  494. Entity? doc = null;
  495. bool exists_before = false;
  496. bool exists_after = true;
  497. Error? error = null;
  498. engine.get_root_async.begin((obj, res) => {
  499. try {
  500. root = engine.get_root_async.end(res);
  501. } catch (Error e) {
  502. error = e;
  503. }
  504. loop.quit();
  505. });
  506. loop.run();
  507. if (error != null || root == null) {
  508. cleanup_dir(temp_dir);
  509. return false;
  510. }
  511. ((!)root).create_container_async.begin("docs", (obj, res) => {
  512. try {
  513. docs = ((!)root).create_container_async.end(res);
  514. } catch (Error e) {
  515. error = e;
  516. }
  517. loop.quit();
  518. });
  519. loop.run();
  520. if (error != null || docs == null) {
  521. cleanup_dir(temp_dir);
  522. return false;
  523. }
  524. ((!)docs).create_document_async.begin("doc1", "Document", (obj, res) => {
  525. try {
  526. doc = ((!)docs).create_document_async.end(res);
  527. } catch (Error e) {
  528. error = e;
  529. }
  530. loop.quit();
  531. });
  532. loop.run();
  533. if (error != null || doc == null) {
  534. cleanup_dir(temp_dir);
  535. return false;
  536. }
  537. // Check exists before
  538. engine.entity_exists_async.begin(((!)doc).path, (obj, res) => {
  539. try {
  540. exists_before = engine.entity_exists_async.end(res);
  541. } catch (Error e) {
  542. error = e;
  543. }
  544. loop.quit();
  545. });
  546. loop.run();
  547. if (!exists_before) {
  548. cleanup_dir(temp_dir);
  549. return false;
  550. }
  551. // Delete
  552. ((!)doc).delete_async.begin((obj, res) => {
  553. try {
  554. ((!)doc).delete_async.end(res);
  555. } catch (Error e) {
  556. error = e;
  557. }
  558. loop.quit();
  559. });
  560. loop.run();
  561. if (error != null) {
  562. cleanup_dir(temp_dir);
  563. return false;
  564. }
  565. // Check exists after
  566. engine.entity_exists_async.begin(((!)doc).path, (obj, res) => {
  567. try {
  568. exists_after = engine.entity_exists_async.end(res);
  569. } catch (Error e) {
  570. error = e;
  571. }
  572. loop.quit();
  573. });
  574. loop.run();
  575. if (exists_after) {
  576. cleanup_dir(temp_dir);
  577. return false;
  578. }
  579. cleanup_dir(temp_dir);
  580. return true;
  581. }
  582. // Test 6: Property access
  583. bool test_property_access() {
  584. string temp_dir = create_temp_dir();
  585. var engine = new EmbeddedEngine.with_path(temp_dir);
  586. var loop = new MainLoop();
  587. Entity? root = null;
  588. Entity? docs = null;
  589. Entity? doc = null;
  590. Invercargill.Element? name = null;
  591. Invercargill.Element? count = null;
  592. Invercargill.Element? active = null;
  593. Invercargill.Element? missing = null;
  594. Error? error = null;
  595. engine.get_root_async.begin((obj, res) => {
  596. try {
  597. root = engine.get_root_async.end(res);
  598. } catch (Error e) {
  599. error = e;
  600. }
  601. loop.quit();
  602. });
  603. loop.run();
  604. if (error != null || root == null) {
  605. cleanup_dir(temp_dir);
  606. return false;
  607. }
  608. ((!)root).create_container_async.begin("docs", (obj, res) => {
  609. try {
  610. docs = ((!)root).create_container_async.end(res);
  611. } catch (Error e) {
  612. error = e;
  613. }
  614. loop.quit();
  615. });
  616. loop.run();
  617. if (error != null || docs == null) {
  618. cleanup_dir(temp_dir);
  619. return false;
  620. }
  621. ((!)docs).create_document_async.begin("doc1", "Document", (obj, res) => {
  622. try {
  623. doc = ((!)docs).create_document_async.end(res);
  624. } catch (Error e) {
  625. error = e;
  626. }
  627. loop.quit();
  628. });
  629. loop.run();
  630. if (error != null || doc == null) {
  631. cleanup_dir(temp_dir);
  632. return false;
  633. }
  634. // Set properties
  635. ((!)doc).set_entity_property_async.begin("name", new Invercargill.NativeElement<string>("Test Document"), (obj, res) => {
  636. try {
  637. ((!)doc).set_entity_property_async.end(res);
  638. } catch (Error e) {
  639. error = e;
  640. }
  641. loop.quit();
  642. });
  643. loop.run();
  644. if (error != null) {
  645. cleanup_dir(temp_dir);
  646. return false;
  647. }
  648. ((!)doc).set_entity_property_async.begin("count", new Invercargill.NativeElement<int64?>(42), (obj, res) => {
  649. try {
  650. ((!)doc).set_entity_property_async.end(res);
  651. } catch (Error e) {
  652. error = e;
  653. }
  654. loop.quit();
  655. });
  656. loop.run();
  657. if (error != null) {
  658. cleanup_dir(temp_dir);
  659. return false;
  660. }
  661. ((!)doc).set_entity_property_async.begin("active", new Invercargill.NativeElement<bool?>(true), (obj, res) => {
  662. try {
  663. ((!)doc).set_entity_property_async.end(res);
  664. } catch (Error e) {
  665. error = e;
  666. }
  667. loop.quit();
  668. });
  669. loop.run();
  670. if (error != null) {
  671. cleanup_dir(temp_dir);
  672. return false;
  673. }
  674. // Get properties
  675. ((!)doc).get_entity_property_async.begin("name", (obj, res) => {
  676. try {
  677. name = ((!)doc).get_entity_property_async.end(res);
  678. } catch (Error e) {
  679. error = e;
  680. }
  681. loop.quit();
  682. });
  683. loop.run();
  684. if (error != null || name == null || ((!)name).is_null()) {
  685. cleanup_dir(temp_dir);
  686. return false;
  687. }
  688. string name_val = ((!)name).as<string>();
  689. if (name_val != "Test Document") {
  690. cleanup_dir(temp_dir);
  691. return false;
  692. }
  693. ((!)doc).get_entity_property_async.begin("count", (obj, res) => {
  694. try {
  695. count = ((!)doc).get_entity_property_async.end(res);
  696. } catch (Error e) {
  697. error = e;
  698. }
  699. loop.quit();
  700. });
  701. loop.run();
  702. if (error != null || count == null || ((!)count).is_null()) {
  703. cleanup_dir(temp_dir);
  704. return false;
  705. }
  706. int64? count_val = ((!)count).as<int64?>();
  707. if (count_val == null || (!)count_val != 42) {
  708. cleanup_dir(temp_dir);
  709. return false;
  710. }
  711. ((!)doc).get_entity_property_async.begin("active", (obj, res) => {
  712. try {
  713. active = ((!)doc).get_entity_property_async.end(res);
  714. } catch (Error e) {
  715. error = e;
  716. }
  717. loop.quit();
  718. });
  719. loop.run();
  720. if (error != null || active == null || ((!)active).is_null()) {
  721. cleanup_dir(temp_dir);
  722. return false;
  723. }
  724. bool active_val = ((!)active).as<bool?>();
  725. if (active_val != true) {
  726. cleanup_dir(temp_dir);
  727. return false;
  728. }
  729. // Non-existent property
  730. ((!)doc).get_entity_property_async.begin("missing", (obj, res) => {
  731. try {
  732. missing = ((!)doc).get_entity_property_async.end(res);
  733. } catch (Error e) {
  734. error = e;
  735. }
  736. loop.quit();
  737. });
  738. loop.run();
  739. if (missing != null) {
  740. cleanup_dir(temp_dir);
  741. return false;
  742. }
  743. cleanup_dir(temp_dir);
  744. return true;
  745. }
  746. // Test 7: Property removal
  747. bool test_property_removal() {
  748. string temp_dir = create_temp_dir();
  749. var engine = new EmbeddedEngine.with_path(temp_dir);
  750. var loop = new MainLoop();
  751. Entity? root = null;
  752. Entity? docs = null;
  753. Entity? doc = null;
  754. Invercargill.Element? value = null;
  755. Invercargill.Element? value_after = null;
  756. Error? error = null;
  757. engine.get_root_async.begin((obj, res) => {
  758. try {
  759. root = engine.get_root_async.end(res);
  760. } catch (Error e) {
  761. error = e;
  762. }
  763. loop.quit();
  764. });
  765. loop.run();
  766. if (error != null || root == null) {
  767. cleanup_dir(temp_dir);
  768. return false;
  769. }
  770. ((!)root).create_container_async.begin("docs", (obj, res) => {
  771. try {
  772. docs = ((!)root).create_container_async.end(res);
  773. } catch (Error e) {
  774. error = e;
  775. }
  776. loop.quit();
  777. });
  778. loop.run();
  779. if (error != null || docs == null) {
  780. cleanup_dir(temp_dir);
  781. return false;
  782. }
  783. ((!)docs).create_document_async.begin("doc1", "Document", (obj, res) => {
  784. try {
  785. doc = ((!)docs).create_document_async.end(res);
  786. } catch (Error e) {
  787. error = e;
  788. }
  789. loop.quit();
  790. });
  791. loop.run();
  792. if (error != null || doc == null) {
  793. cleanup_dir(temp_dir);
  794. return false;
  795. }
  796. ((!)doc).set_entity_property_async.begin("temp", new Invercargill.NativeElement<string>("temporary"), (obj, res) => {
  797. try {
  798. ((!)doc).set_entity_property_async.end(res);
  799. } catch (Error e) {
  800. error = e;
  801. }
  802. loop.quit();
  803. });
  804. loop.run();
  805. if (error != null) {
  806. cleanup_dir(temp_dir);
  807. return false;
  808. }
  809. ((!)doc).get_entity_property_async.begin("temp", (obj, res) => {
  810. try {
  811. value = ((!)doc).get_entity_property_async.end(res);
  812. } catch (Error e) {
  813. error = e;
  814. }
  815. loop.quit();
  816. });
  817. loop.run();
  818. if (error != null || value == null) {
  819. cleanup_dir(temp_dir);
  820. return false;
  821. }
  822. ((!)doc).remove_property_async.begin("temp", (obj, res) => {
  823. try {
  824. ((!)doc).remove_property_async.end(res);
  825. } catch (Error e) {
  826. error = e;
  827. }
  828. loop.quit();
  829. });
  830. loop.run();
  831. if (error != null) {
  832. cleanup_dir(temp_dir);
  833. return false;
  834. }
  835. ((!)doc).get_entity_property_async.begin("temp", (obj, res) => {
  836. try {
  837. value_after = ((!)doc).get_entity_property_async.end(res);
  838. } catch (Error e) {
  839. error = e;
  840. }
  841. loop.quit();
  842. });
  843. loop.run();
  844. if (value_after != null) {
  845. cleanup_dir(temp_dir);
  846. return false;
  847. }
  848. cleanup_dir(temp_dir);
  849. return true;
  850. }
  851. // Test 8: Get children
  852. bool test_get_children() {
  853. string temp_dir = create_temp_dir();
  854. var engine = new EmbeddedEngine.with_path(temp_dir);
  855. var loop = new MainLoop();
  856. Entity? root = null;
  857. Entity? users = null;
  858. Entity[] children = {};
  859. Error? error = null;
  860. engine.get_root_async.begin((obj, res) => {
  861. try {
  862. root = engine.get_root_async.end(res);
  863. } catch (Error e) {
  864. error = e;
  865. }
  866. loop.quit();
  867. });
  868. loop.run();
  869. if (error != null || root == null) {
  870. cleanup_dir(temp_dir);
  871. return false;
  872. }
  873. ((!)root).create_container_async.begin("users", (obj, res) => {
  874. try {
  875. users = ((!)root).create_container_async.end(res);
  876. } catch (Error e) {
  877. error = e;
  878. }
  879. loop.quit();
  880. });
  881. loop.run();
  882. if (error != null || users == null) {
  883. cleanup_dir(temp_dir);
  884. return false;
  885. }
  886. // Create documents
  887. ((!)users).create_document_async.begin("john", "User", (obj, res) => {
  888. try {
  889. ((!)users).create_document_async.end(res);
  890. } catch (Error e) {
  891. error = e;
  892. }
  893. loop.quit();
  894. });
  895. loop.run();
  896. if (error != null) {
  897. cleanup_dir(temp_dir);
  898. return false;
  899. }
  900. ((!)users).create_document_async.begin("jane", "User", (obj, res) => {
  901. try {
  902. ((!)users).create_document_async.end(res);
  903. } catch (Error e) {
  904. error = e;
  905. }
  906. loop.quit();
  907. });
  908. loop.run();
  909. if (error != null) {
  910. cleanup_dir(temp_dir);
  911. return false;
  912. }
  913. ((!)users).create_document_async.begin("bob", "User", (obj, res) => {
  914. try {
  915. ((!)users).create_document_async.end(res);
  916. } catch (Error e) {
  917. error = e;
  918. }
  919. loop.quit();
  920. });
  921. loop.run();
  922. if (error != null) {
  923. cleanup_dir(temp_dir);
  924. return false;
  925. }
  926. ((!)users).get_children_async.begin((obj, res) => {
  927. try {
  928. children = ((!)users).get_children_async.end(res);
  929. } catch (Error e) {
  930. error = e;
  931. }
  932. loop.quit();
  933. });
  934. loop.run();
  935. if (error != null || children.length != 3) {
  936. cleanup_dir(temp_dir);
  937. return false;
  938. }
  939. // Check all children are present
  940. var child_names = new Invercargill.DataStructures.HashSet<string>();
  941. foreach (var child in children) {
  942. child_names.add(child.name);
  943. }
  944. if (!child_names.has("john") || !child_names.has("jane") || !child_names.has("bob")) {
  945. cleanup_dir(temp_dir);
  946. return false;
  947. }
  948. cleanup_dir(temp_dir);
  949. return true;
  950. }
  951. // Test 9: Entity exists
  952. bool test_entity_exists() {
  953. string temp_dir = create_temp_dir();
  954. var engine = new EmbeddedEngine.with_path(temp_dir);
  955. var loop = new MainLoop();
  956. Entity? root = null;
  957. Entity? users = null;
  958. bool exists1 = false;
  959. bool exists2 = true;
  960. Error? error = null;
  961. engine.get_root_async.begin((obj, res) => {
  962. try {
  963. root = engine.get_root_async.end(res);
  964. } catch (Error e) {
  965. error = e;
  966. }
  967. loop.quit();
  968. });
  969. loop.run();
  970. if (error != null || root == null) {
  971. cleanup_dir(temp_dir);
  972. return false;
  973. }
  974. ((!)root).create_container_async.begin("users", (obj, res) => {
  975. try {
  976. users = ((!)root).create_container_async.end(res);
  977. } catch (Error e) {
  978. error = e;
  979. }
  980. loop.quit();
  981. });
  982. loop.run();
  983. if (error != null || users == null) {
  984. cleanup_dir(temp_dir);
  985. return false;
  986. }
  987. engine.entity_exists_async.begin(new EntityPath("/users"), (obj, res) => {
  988. try {
  989. exists1 = engine.entity_exists_async.end(res);
  990. } catch (Error e) {
  991. error = e;
  992. }
  993. loop.quit();
  994. });
  995. loop.run();
  996. if (!exists1) {
  997. cleanup_dir(temp_dir);
  998. return false;
  999. }
  1000. engine.entity_exists_async.begin(new EntityPath("/nonexistent"), (obj, res) => {
  1001. try {
  1002. exists2 = engine.entity_exists_async.end(res);
  1003. } catch (Error e) {
  1004. error = e;
  1005. }
  1006. loop.quit();
  1007. });
  1008. loop.run();
  1009. if (exists2) {
  1010. cleanup_dir(temp_dir);
  1011. return false;
  1012. }
  1013. cleanup_dir(temp_dir);
  1014. return true;
  1015. }
  1016. // Test 10: Get entity
  1017. bool test_get_entity() {
  1018. string temp_dir = create_temp_dir();
  1019. var engine = new EmbeddedEngine.with_path(temp_dir);
  1020. var loop = new MainLoop();
  1021. Entity? root = null;
  1022. Entity? users = null;
  1023. Entity? retrieved = null;
  1024. Entity? nonexistent = null;
  1025. Error? error = null;
  1026. engine.get_root_async.begin((obj, res) => {
  1027. try {
  1028. root = engine.get_root_async.end(res);
  1029. } catch (Error e) {
  1030. error = e;
  1031. }
  1032. loop.quit();
  1033. });
  1034. loop.run();
  1035. if (error != null || root == null) {
  1036. cleanup_dir(temp_dir);
  1037. return false;
  1038. }
  1039. ((!)root).create_container_async.begin("users", (obj, res) => {
  1040. try {
  1041. users = ((!)root).create_container_async.end(res);
  1042. } catch (Error e) {
  1043. error = e;
  1044. }
  1045. loop.quit();
  1046. });
  1047. loop.run();
  1048. if (error != null || users == null) {
  1049. cleanup_dir(temp_dir);
  1050. return false;
  1051. }
  1052. ((!)users).create_document_async.begin("john", "User", (obj, res) => {
  1053. try {
  1054. ((!)users).create_document_async.end(res);
  1055. } catch (Error e) {
  1056. error = e;
  1057. }
  1058. loop.quit();
  1059. });
  1060. loop.run();
  1061. if (error != null) {
  1062. cleanup_dir(temp_dir);
  1063. return false;
  1064. }
  1065. // Get entity by path
  1066. engine.get_entity_async.begin(new EntityPath("/users/john"), (obj, res) => {
  1067. try {
  1068. retrieved = engine.get_entity_async.end(res);
  1069. } catch (Error e) {
  1070. error = e;
  1071. }
  1072. loop.quit();
  1073. });
  1074. loop.run();
  1075. if (error != null || retrieved == null) {
  1076. cleanup_dir(temp_dir);
  1077. return false;
  1078. }
  1079. if (((!)retrieved).entity_type != EntityType.DOCUMENT) {
  1080. cleanup_dir(temp_dir);
  1081. return false;
  1082. }
  1083. if (((!)retrieved).name != "john") {
  1084. cleanup_dir(temp_dir);
  1085. return false;
  1086. }
  1087. if (((!)retrieved).type_label != "User") {
  1088. cleanup_dir(temp_dir);
  1089. return false;
  1090. }
  1091. // Get non-existent entity - should throw
  1092. engine.get_entity_async.begin(new EntityPath("/nonexistent"), (obj, res) => {
  1093. try {
  1094. nonexistent = engine.get_entity_async.end(res);
  1095. } catch (Error e) {
  1096. error = e;
  1097. }
  1098. loop.quit();
  1099. });
  1100. loop.run();
  1101. // Should have thrown
  1102. if (nonexistent != null) {
  1103. cleanup_dir(temp_dir);
  1104. return false;
  1105. }
  1106. cleanup_dir(temp_dir);
  1107. return true;
  1108. }
  1109. // Test 11: Query by type
  1110. bool test_query_by_type() {
  1111. string temp_dir = create_temp_dir();
  1112. var engine = new EmbeddedEngine.with_path(temp_dir);
  1113. var loop = new MainLoop();
  1114. Entity? root = null;
  1115. Entity? users = null;
  1116. Entity[] user_results = {};
  1117. Entity[] admin_results = {};
  1118. Entity[] none_results = {};
  1119. Error? error = null;
  1120. engine.get_root_async.begin((obj, res) => {
  1121. try {
  1122. root = engine.get_root_async.end(res);
  1123. } catch (Error e) {
  1124. error = e;
  1125. }
  1126. loop.quit();
  1127. });
  1128. loop.run();
  1129. if (error != null || root == null) {
  1130. cleanup_dir(temp_dir);
  1131. return false;
  1132. }
  1133. ((!)root).create_container_async.begin("users", (obj, res) => {
  1134. try {
  1135. users = ((!)root).create_container_async.end(res);
  1136. } catch (Error e) {
  1137. error = e;
  1138. }
  1139. loop.quit();
  1140. });
  1141. loop.run();
  1142. if (error != null || users == null) {
  1143. cleanup_dir(temp_dir);
  1144. return false;
  1145. }
  1146. ((!)users).create_document_async.begin("john", "User", (obj, res) => {
  1147. try {
  1148. ((!)users).create_document_async.end(res);
  1149. } catch (Error e) {
  1150. error = e;
  1151. }
  1152. loop.quit();
  1153. });
  1154. loop.run();
  1155. if (error != null) {
  1156. cleanup_dir(temp_dir);
  1157. return false;
  1158. }
  1159. ((!)users).create_document_async.begin("jane", "User", (obj, res) => {
  1160. try {
  1161. ((!)users).create_document_async.end(res);
  1162. } catch (Error e) {
  1163. error = e;
  1164. }
  1165. loop.quit();
  1166. });
  1167. loop.run();
  1168. if (error != null) {
  1169. cleanup_dir(temp_dir);
  1170. return false;
  1171. }
  1172. ((!)users).create_document_async.begin("admin", "Admin", (obj, res) => {
  1173. try {
  1174. ((!)users).create_document_async.end(res);
  1175. } catch (Error e) {
  1176. error = e;
  1177. }
  1178. loop.quit();
  1179. });
  1180. loop.run();
  1181. if (error != null) {
  1182. cleanup_dir(temp_dir);
  1183. return false;
  1184. }
  1185. // Query for Users
  1186. engine.query_by_type_async.begin("User", (obj, res) => {
  1187. try {
  1188. user_results = engine.query_by_type_async.end(res);
  1189. } catch (Error e) {
  1190. error = e;
  1191. }
  1192. loop.quit();
  1193. });
  1194. loop.run();
  1195. if (error != null || user_results.length != 2) {
  1196. cleanup_dir(temp_dir);
  1197. return false;
  1198. }
  1199. // Query for Admins
  1200. engine.query_by_type_async.begin("Admin", (obj, res) => {
  1201. try {
  1202. admin_results = engine.query_by_type_async.end(res);
  1203. } catch (Error e) {
  1204. error = e;
  1205. }
  1206. loop.quit();
  1207. });
  1208. loop.run();
  1209. if (error != null || admin_results.length != 1) {
  1210. cleanup_dir(temp_dir);
  1211. return false;
  1212. }
  1213. // Query for non-existent type
  1214. engine.query_by_type_async.begin("NonExistent", (obj, res) => {
  1215. try {
  1216. none_results = engine.query_by_type_async.end(res);
  1217. } catch (Error e) {
  1218. error = e;
  1219. }
  1220. loop.quit();
  1221. });
  1222. loop.run();
  1223. if (error != null || none_results.length != 0) {
  1224. cleanup_dir(temp_dir);
  1225. return false;
  1226. }
  1227. cleanup_dir(temp_dir);
  1228. return true;
  1229. }
  1230. // Test 12: Nested containers
  1231. bool test_nested_containers() {
  1232. string temp_dir = create_temp_dir();
  1233. var engine = new EmbeddedEngine.with_path(temp_dir);
  1234. var loop = new MainLoop();
  1235. Entity? root = null;
  1236. Entity? level1 = null;
  1237. Entity? level2 = null;
  1238. Entity? level3 = null;
  1239. bool exists1 = false;
  1240. bool exists2 = false;
  1241. bool exists3 = false;
  1242. Entity[] level1_children = {};
  1243. Entity[] level2_children = {};
  1244. Entity[] level3_children = {};
  1245. Error? error = null;
  1246. engine.get_root_async.begin((obj, res) => {
  1247. try {
  1248. root = engine.get_root_async.end(res);
  1249. } catch (Error e) {
  1250. error = e;
  1251. }
  1252. loop.quit();
  1253. });
  1254. loop.run();
  1255. if (error != null || root == null) {
  1256. cleanup_dir(temp_dir);
  1257. return false;
  1258. }
  1259. ((!)root).create_container_async.begin("level1", (obj, res) => {
  1260. try {
  1261. level1 = ((!)root).create_container_async.end(res);
  1262. } catch (Error e) {
  1263. error = e;
  1264. }
  1265. loop.quit();
  1266. });
  1267. loop.run();
  1268. if (error != null || level1 == null) {
  1269. cleanup_dir(temp_dir);
  1270. return false;
  1271. }
  1272. ((!)level1).create_container_async.begin("level2", (obj, res) => {
  1273. try {
  1274. level2 = ((!)level1).create_container_async.end(res);
  1275. } catch (Error e) {
  1276. error = e;
  1277. }
  1278. loop.quit();
  1279. });
  1280. loop.run();
  1281. if (error != null || level2 == null) {
  1282. cleanup_dir(temp_dir);
  1283. return false;
  1284. }
  1285. ((!)level2).create_container_async.begin("level3", (obj, res) => {
  1286. try {
  1287. level3 = ((!)level2).create_container_async.end(res);
  1288. } catch (Error e) {
  1289. error = e;
  1290. }
  1291. loop.quit();
  1292. });
  1293. loop.run();
  1294. if (error != null || level3 == null) {
  1295. cleanup_dir(temp_dir);
  1296. return false;
  1297. }
  1298. engine.entity_exists_async.begin(new EntityPath("/level1"), (obj, res) => {
  1299. try {
  1300. exists1 = engine.entity_exists_async.end(res);
  1301. } catch (Error e) {
  1302. error = e;
  1303. }
  1304. loop.quit();
  1305. });
  1306. loop.run();
  1307. if (!exists1) {
  1308. cleanup_dir(temp_dir);
  1309. return false;
  1310. }
  1311. engine.entity_exists_async.begin(new EntityPath("/level1/level2"), (obj, res) => {
  1312. try {
  1313. exists2 = engine.entity_exists_async.end(res);
  1314. } catch (Error e) {
  1315. error = e;
  1316. }
  1317. loop.quit();
  1318. });
  1319. loop.run();
  1320. if (!exists2) {
  1321. cleanup_dir(temp_dir);
  1322. return false;
  1323. }
  1324. engine.entity_exists_async.begin(new EntityPath("/level1/level2/level3"), (obj, res) => {
  1325. try {
  1326. exists3 = engine.entity_exists_async.end(res);
  1327. } catch (Error e) {
  1328. error = e;
  1329. }
  1330. loop.quit();
  1331. });
  1332. loop.run();
  1333. if (!exists3) {
  1334. cleanup_dir(temp_dir);
  1335. return false;
  1336. }
  1337. // Verify parent-child relationships
  1338. ((!)level1).get_children_async.begin((obj, res) => {
  1339. try {
  1340. level1_children = ((!)level1).get_children_async.end(res);
  1341. } catch (Error e) {
  1342. error = e;
  1343. }
  1344. loop.quit();
  1345. });
  1346. loop.run();
  1347. if (error != null || level1_children.length != 1) {
  1348. cleanup_dir(temp_dir);
  1349. return false;
  1350. }
  1351. ((!)level2).get_children_async.begin((obj, res) => {
  1352. try {
  1353. level2_children = ((!)level2).get_children_async.end(res);
  1354. } catch (Error e) {
  1355. error = e;
  1356. }
  1357. loop.quit();
  1358. });
  1359. loop.run();
  1360. if (error != null || level2_children.length != 1) {
  1361. cleanup_dir(temp_dir);
  1362. return false;
  1363. }
  1364. ((!)level3).get_children_async.begin((obj, res) => {
  1365. try {
  1366. level3_children = ((!)level3).get_children_async.end(res);
  1367. } catch (Error e) {
  1368. error = e;
  1369. }
  1370. loop.quit();
  1371. });
  1372. loop.run();
  1373. if (error != null || level3_children.length != 0) {
  1374. cleanup_dir(temp_dir);
  1375. return false;
  1376. }
  1377. cleanup_dir(temp_dir);
  1378. return true;
  1379. }
  1380. // Test 13: Multiple documents
  1381. bool test_multiple_documents() {
  1382. string temp_dir = create_temp_dir();
  1383. var engine = new EmbeddedEngine.with_path(temp_dir);
  1384. var loop = new MainLoop();
  1385. Entity? root = null;
  1386. Entity? users = null;
  1387. Entity[] children = {};
  1388. Error? error = null;
  1389. engine.get_root_async.begin((obj, res) => {
  1390. try {
  1391. root = engine.get_root_async.end(res);
  1392. } catch (Error e) {
  1393. error = e;
  1394. }
  1395. loop.quit();
  1396. });
  1397. loop.run();
  1398. if (error != null || root == null) {
  1399. cleanup_dir(temp_dir);
  1400. return false;
  1401. }
  1402. ((!)root).create_container_async.begin("users", (obj, res) => {
  1403. try {
  1404. users = ((!)root).create_container_async.end(res);
  1405. } catch (Error e) {
  1406. error = e;
  1407. }
  1408. loop.quit();
  1409. });
  1410. loop.run();
  1411. if (error != null || users == null) {
  1412. cleanup_dir(temp_dir);
  1413. return false;
  1414. }
  1415. // Create multiple documents
  1416. for (int i = 0; i < 10; i++) {
  1417. int idx = i;
  1418. Entity? doc = null;
  1419. ((!)users).create_document_async.begin(@"user$idx", "User", (obj, res) => {
  1420. try {
  1421. doc = ((!)users).create_document_async.end(res);
  1422. } catch (Error e) {
  1423. error = e;
  1424. }
  1425. loop.quit();
  1426. });
  1427. loop.run();
  1428. if (error != null || doc == null) {
  1429. cleanup_dir(temp_dir);
  1430. return false;
  1431. }
  1432. ((!)doc).set_entity_property_async.begin("index", new Invercargill.NativeElement<int64?>(idx), (obj, res) => {
  1433. try {
  1434. ((!)doc).set_entity_property_async.end(res);
  1435. } catch (Error e) {
  1436. error = e;
  1437. }
  1438. loop.quit();
  1439. });
  1440. loop.run();
  1441. if (error != null) {
  1442. cleanup_dir(temp_dir);
  1443. return false;
  1444. }
  1445. }
  1446. // Verify all exist
  1447. ((!)users).get_children_async.begin((obj, res) => {
  1448. try {
  1449. children = ((!)users).get_children_async.end(res);
  1450. } catch (Error e) {
  1451. error = e;
  1452. }
  1453. loop.quit();
  1454. });
  1455. loop.run();
  1456. if (error != null || children.length != 10) {
  1457. cleanup_dir(temp_dir);
  1458. return false;
  1459. }
  1460. // Verify properties
  1461. for (int i = 0; i < 10; i++) {
  1462. int idx = i;
  1463. Entity? doc = null;
  1464. Invercargill.Element? index = null;
  1465. engine.get_entity_async.begin(new EntityPath(@"/users/user$idx"), (obj, res) => {
  1466. try {
  1467. doc = engine.get_entity_async.end(res);
  1468. } catch (Error e) {
  1469. error = e;
  1470. }
  1471. loop.quit();
  1472. });
  1473. loop.run();
  1474. if (error != null || doc == null) {
  1475. cleanup_dir(temp_dir);
  1476. return false;
  1477. }
  1478. ((!)doc).get_entity_property_async.begin("index", (obj, res) => {
  1479. try {
  1480. index = ((!)doc).get_entity_property_async.end(res);
  1481. } catch (Error e) {
  1482. error = e;
  1483. }
  1484. loop.quit();
  1485. });
  1486. loop.run();
  1487. if (error != null || index == null || ((!)index).is_null()) {
  1488. cleanup_dir(temp_dir);
  1489. return false;
  1490. }
  1491. int64? index_val = ((!)index).as<int64?>();
  1492. if (index_val == null || (!)index_val != idx) {
  1493. cleanup_dir(temp_dir);
  1494. return false;
  1495. }
  1496. }
  1497. cleanup_dir(temp_dir);
  1498. return true;
  1499. }
  1500. // Test 14: Entity path
  1501. bool test_entity_path() {
  1502. string temp_dir = create_temp_dir();
  1503. var engine = new EmbeddedEngine.with_path(temp_dir);
  1504. var loop = new MainLoop();
  1505. Entity? root = null;
  1506. Entity? a = null;
  1507. Entity? b = null;
  1508. Entity? c = null;
  1509. Error? error = null;
  1510. engine.get_root_async.begin((obj, res) => {
  1511. try {
  1512. root = engine.get_root_async.end(res);
  1513. } catch (Error e) {
  1514. error = e;
  1515. }
  1516. loop.quit();
  1517. });
  1518. loop.run();
  1519. if (error != null || root == null) {
  1520. cleanup_dir(temp_dir);
  1521. return false;
  1522. }
  1523. ((!)root).create_container_async.begin("a", (obj, res) => {
  1524. try {
  1525. a = ((!)root).create_container_async.end(res);
  1526. } catch (Error e) {
  1527. error = e;
  1528. }
  1529. loop.quit();
  1530. });
  1531. loop.run();
  1532. if (error != null || a == null) {
  1533. cleanup_dir(temp_dir);
  1534. return false;
  1535. }
  1536. ((!)a).create_container_async.begin("b", (obj, res) => {
  1537. try {
  1538. b = ((!)a).create_container_async.end(res);
  1539. } catch (Error e) {
  1540. error = e;
  1541. }
  1542. loop.quit();
  1543. });
  1544. loop.run();
  1545. if (error != null || b == null) {
  1546. cleanup_dir(temp_dir);
  1547. return false;
  1548. }
  1549. ((!)b).create_document_async.begin("c", "Document", (obj, res) => {
  1550. try {
  1551. c = ((!)b).create_document_async.end(res);
  1552. } catch (Error e) {
  1553. error = e;
  1554. }
  1555. loop.quit();
  1556. });
  1557. loop.run();
  1558. if (error != null || c == null) {
  1559. cleanup_dir(temp_dir);
  1560. return false;
  1561. }
  1562. if (!((!)root).path.is_root) {
  1563. cleanup_dir(temp_dir);
  1564. return false;
  1565. }
  1566. if (((!)a).path.to_string() != "/a") {
  1567. cleanup_dir(temp_dir);
  1568. return false;
  1569. }
  1570. if (((!)b).path.to_string() != "/a/b") {
  1571. cleanup_dir(temp_dir);
  1572. return false;
  1573. }
  1574. if (((!)c).path.to_string() != "/a/b/c") {
  1575. cleanup_dir(temp_dir);
  1576. return false;
  1577. }
  1578. // Check parent relationships
  1579. if (!((!)c).path.parent.equals(((!)b).path)) {
  1580. cleanup_dir(temp_dir);
  1581. return false;
  1582. }
  1583. if (!((!)b).path.parent.equals(((!)a).path)) {
  1584. cleanup_dir(temp_dir);
  1585. return false;
  1586. }
  1587. if (!((!)a).path.parent.equals(((!)root).path)) {
  1588. cleanup_dir(temp_dir);
  1589. return false;
  1590. }
  1591. cleanup_dir(temp_dir);
  1592. return true;
  1593. }
  1594. // Test 15: Persistence
  1595. bool test_engine_persistence() {
  1596. string temp_dir = create_temp_dir();
  1597. // Create and write
  1598. var engine1 = new EmbeddedEngine.with_path(temp_dir);
  1599. var loop = new MainLoop();
  1600. Entity? root1 = null;
  1601. Entity? users1 = null;
  1602. Entity? john1 = null;
  1603. bool exists1 = false;
  1604. bool exists2 = false;
  1605. Entity? root2 = null;
  1606. Entity? john2 = null;
  1607. Invercargill.Element? email = null;
  1608. Error? error = null;
  1609. engine1.get_root_async.begin((obj, res) => {
  1610. try {
  1611. root1 = engine1.get_root_async.end(res);
  1612. } catch (Error e) {
  1613. error = e;
  1614. }
  1615. loop.quit();
  1616. });
  1617. loop.run();
  1618. if (error != null || root1 == null) {
  1619. cleanup_dir(temp_dir);
  1620. return false;
  1621. }
  1622. ((!)root1).create_container_async.begin("users", (obj, res) => {
  1623. try {
  1624. users1 = ((!)root1).create_container_async.end(res);
  1625. } catch (Error e) {
  1626. error = e;
  1627. }
  1628. loop.quit();
  1629. });
  1630. loop.run();
  1631. if (error != null || users1 == null) {
  1632. cleanup_dir(temp_dir);
  1633. return false;
  1634. }
  1635. ((!)users1).create_document_async.begin("john", "User", (obj, res) => {
  1636. try {
  1637. john1 = ((!)users1).create_document_async.end(res);
  1638. } catch (Error e) {
  1639. error = e;
  1640. }
  1641. loop.quit();
  1642. });
  1643. loop.run();
  1644. if (error != null || john1 == null) {
  1645. cleanup_dir(temp_dir);
  1646. return false;
  1647. }
  1648. ((!)john1).set_entity_property_async.begin("email", new Invercargill.NativeElement<string>("john@example.com"), (obj, res) => {
  1649. try {
  1650. ((!)john1).set_entity_property_async.end(res);
  1651. } catch (Error e) {
  1652. error = e;
  1653. }
  1654. loop.quit();
  1655. });
  1656. loop.run();
  1657. if (error != null) {
  1658. cleanup_dir(temp_dir);
  1659. return false;
  1660. }
  1661. // Create new engine instance
  1662. var engine2 = new EmbeddedEngine.with_path(temp_dir);
  1663. // Verify data persists
  1664. engine2.get_root_async.begin((obj, res) => {
  1665. try {
  1666. root2 = engine2.get_root_async.end(res);
  1667. } catch (Error e) {
  1668. error = e;
  1669. }
  1670. loop.quit();
  1671. });
  1672. loop.run();
  1673. if (error != null || root2 == null) {
  1674. cleanup_dir(temp_dir);
  1675. return false;
  1676. }
  1677. engine2.entity_exists_async.begin(new EntityPath("/users"), (obj, res) => {
  1678. try {
  1679. exists1 = engine2.entity_exists_async.end(res);
  1680. } catch (Error e) {
  1681. error = e;
  1682. }
  1683. loop.quit();
  1684. });
  1685. loop.run();
  1686. if (!exists1) {
  1687. cleanup_dir(temp_dir);
  1688. return false;
  1689. }
  1690. engine2.entity_exists_async.begin(new EntityPath("/users/john"), (obj, res) => {
  1691. try {
  1692. exists2 = engine2.entity_exists_async.end(res);
  1693. } catch (Error e) {
  1694. error = e;
  1695. }
  1696. loop.quit();
  1697. });
  1698. loop.run();
  1699. if (!exists2) {
  1700. cleanup_dir(temp_dir);
  1701. return false;
  1702. }
  1703. engine2.get_entity_async.begin(new EntityPath("/users/john"), (obj, res) => {
  1704. try {
  1705. john2 = engine2.get_entity_async.end(res);
  1706. } catch (Error e) {
  1707. error = e;
  1708. }
  1709. loop.quit();
  1710. });
  1711. loop.run();
  1712. if (error != null || john2 == null) {
  1713. cleanup_dir(temp_dir);
  1714. return false;
  1715. }
  1716. ((!)john2).get_entity_property_async.begin("email", (obj, res) => {
  1717. try {
  1718. email = ((!)john2).get_entity_property_async.end(res);
  1719. } catch (Error e) {
  1720. error = e;
  1721. }
  1722. loop.quit();
  1723. });
  1724. loop.run();
  1725. if (error != null || email == null || ((!)email).is_null()) {
  1726. cleanup_dir(temp_dir);
  1727. return false;
  1728. }
  1729. string email_val = ((!)email).as<string>();
  1730. if (email_val != "john@example.com") {
  1731. cleanup_dir(temp_dir);
  1732. return false;
  1733. }
  1734. cleanup_dir(temp_dir);
  1735. return true;
  1736. }
  1737. // Cleanup helper
  1738. void cleanup_dir(string path) {
  1739. try {
  1740. Dir dir = Dir.open(path, 0);
  1741. string? name;
  1742. while ((name = dir.read_name()) != null) {
  1743. FileUtils.unlink(Path.build_filename(path, name));
  1744. }
  1745. } catch (FileError e) {
  1746. // Ignore errors
  1747. }
  1748. DirUtils.remove(path);
  1749. }
  1750. // ============================================================================
  1751. // Virtual Entity Resolution Tests
  1752. // ============================================================================
  1753. // Test 16: Category virtual child resolution - get_entity_async for member
  1754. bool test_category_virtual_child_resolution() {
  1755. string temp_dir = create_temp_dir();
  1756. var engine = new EmbeddedEngine.with_path(temp_dir);
  1757. var loop = new MainLoop();
  1758. Entity? root = null;
  1759. Entity? container = null;
  1760. Entity? doc1 = null;
  1761. Entity? doc2 = null;
  1762. Entity? category = null;
  1763. Entity? resolved_doc = null;
  1764. bool exists = false;
  1765. Error? error = null;
  1766. engine.get_root_async.begin((obj, res) => {
  1767. try {
  1768. root = engine.get_root_async.end(res);
  1769. } catch (Error e) {
  1770. error = e;
  1771. }
  1772. loop.quit();
  1773. });
  1774. loop.run();
  1775. if (error != null || root == null) {
  1776. cleanup_dir(temp_dir);
  1777. return false;
  1778. }
  1779. // Create container
  1780. ((!)root).create_container_async.begin("posts", (obj, res) => {
  1781. try {
  1782. container = ((!)root).create_container_async.end(res);
  1783. } catch (Error e) {
  1784. error = e;
  1785. }
  1786. loop.quit();
  1787. });
  1788. loop.run();
  1789. if (error != null || container == null) {
  1790. cleanup_dir(temp_dir);
  1791. return false;
  1792. }
  1793. // IMPORTANT: Create category FIRST so hooks are registered
  1794. ((!)container).create_category_async.begin("published", "Post", "!draft", (obj, res) => {
  1795. try {
  1796. category = ((!)container).create_category_async.end(res);
  1797. } catch (Error e) {
  1798. error = e;
  1799. }
  1800. loop.quit();
  1801. });
  1802. loop.run();
  1803. if (error != null || category == null) {
  1804. cleanup_dir(temp_dir);
  1805. return false;
  1806. }
  1807. // Now create documents - hooks will update the category index
  1808. ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
  1809. try {
  1810. doc1 = ((!)container).create_document_async.end(res);
  1811. } catch (Error e) {
  1812. error = e;
  1813. }
  1814. loop.quit();
  1815. });
  1816. loop.run();
  1817. if (error != null || doc1 == null) {
  1818. cleanup_dir(temp_dir);
  1819. return false;
  1820. }
  1821. // Set draft = false on post1 (matches !draft predicate)
  1822. ((!)doc1).set_entity_property_async.begin("draft", new Invercargill.NativeElement<bool?>(false), (obj, res) => {
  1823. try {
  1824. ((!)doc1).set_entity_property_async.end(res);
  1825. } catch (Error e) {
  1826. error = e;
  1827. }
  1828. loop.quit();
  1829. });
  1830. loop.run();
  1831. if (error != null) {
  1832. cleanup_dir(temp_dir);
  1833. return false;
  1834. }
  1835. ((!)container).create_document_async.begin("post2", "Post", (obj, res) => {
  1836. try {
  1837. doc2 = ((!)container).create_document_async.end(res);
  1838. } catch (Error e) {
  1839. error = e;
  1840. }
  1841. loop.quit();
  1842. });
  1843. loop.run();
  1844. if (error != null || doc2 == null) {
  1845. cleanup_dir(temp_dir);
  1846. return false;
  1847. }
  1848. // Set draft = true on post2 (does NOT match !draft predicate)
  1849. ((!)doc2).set_entity_property_async.begin("draft", new Invercargill.NativeElement<bool?>(true), (obj, res) => {
  1850. try {
  1851. ((!)doc2).set_entity_property_async.end(res);
  1852. } catch (Error e) {
  1853. error = e;
  1854. }
  1855. loop.quit();
  1856. });
  1857. loop.run();
  1858. if (error != null) {
  1859. cleanup_dir(temp_dir);
  1860. return false;
  1861. }
  1862. // Test: Resolve category member via direct path
  1863. engine.get_entity_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
  1864. try {
  1865. resolved_doc = engine.get_entity_async.end(res);
  1866. } catch (Error e) {
  1867. error = e;
  1868. }
  1869. loop.quit();
  1870. });
  1871. loop.run();
  1872. // post1 should be found (draft = false, matches !draft)
  1873. if (error != null || resolved_doc == null) {
  1874. stderr.printf("Failed to resolve post1: %s\n", error != null ? ((!)error).message : "null");
  1875. cleanup_dir(temp_dir);
  1876. return false;
  1877. }
  1878. if (((!)resolved_doc).entity_type != EntityType.DOCUMENT) {
  1879. cleanup_dir(temp_dir);
  1880. return false;
  1881. }
  1882. if (((!)resolved_doc).name != "post1") {
  1883. cleanup_dir(temp_dir);
  1884. return false;
  1885. }
  1886. // Test: entity_exists_async for category member
  1887. engine.entity_exists_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
  1888. try {
  1889. exists = engine.entity_exists_async.end(res);
  1890. } catch (Error e) {
  1891. error = e;
  1892. }
  1893. loop.quit();
  1894. });
  1895. loop.run();
  1896. if (error != null || !exists) {
  1897. cleanup_dir(temp_dir);
  1898. return false;
  1899. }
  1900. cleanup_dir(temp_dir);
  1901. return true;
  1902. }
  1903. // Test 17: Category virtual child resolution - non-existent child returns ENTITY_NOT_FOUND
  1904. bool test_category_virtual_child_not_found() {
  1905. string temp_dir = create_temp_dir();
  1906. var engine = new EmbeddedEngine.with_path(temp_dir);
  1907. var loop = new MainLoop();
  1908. Entity? root = null;
  1909. Entity? container = null;
  1910. Entity? doc1 = null;
  1911. Entity? category = null;
  1912. Entity? resolved_doc = null;
  1913. bool exists = true;
  1914. Error? error = null;
  1915. engine.get_root_async.begin((obj, res) => {
  1916. try {
  1917. root = engine.get_root_async.end(res);
  1918. } catch (Error e) {
  1919. error = e;
  1920. }
  1921. loop.quit();
  1922. });
  1923. loop.run();
  1924. if (error != null || root == null) {
  1925. cleanup_dir(temp_dir);
  1926. return false;
  1927. }
  1928. // Create container
  1929. ((!)root).create_container_async.begin("posts", (obj, res) => {
  1930. try {
  1931. container = ((!)root).create_container_async.end(res);
  1932. } catch (Error e) {
  1933. error = e;
  1934. }
  1935. loop.quit();
  1936. });
  1937. loop.run();
  1938. if (error != null || container == null) {
  1939. cleanup_dir(temp_dir);
  1940. return false;
  1941. }
  1942. // IMPORTANT: Create category FIRST so hooks are registered
  1943. ((!)container).create_category_async.begin("published", "Post", "!draft", (obj, res) => {
  1944. try {
  1945. category = ((!)container).create_category_async.end(res);
  1946. } catch (Error e) {
  1947. error = e;
  1948. }
  1949. loop.quit();
  1950. });
  1951. loop.run();
  1952. if (error != null || category == null) {
  1953. cleanup_dir(temp_dir);
  1954. return false;
  1955. }
  1956. // Now create document with draft = true (does NOT match !draft predicate)
  1957. ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
  1958. try {
  1959. doc1 = ((!)container).create_document_async.end(res);
  1960. } catch (Error e) {
  1961. error = e;
  1962. }
  1963. loop.quit();
  1964. });
  1965. loop.run();
  1966. if (error != null || doc1 == null) {
  1967. cleanup_dir(temp_dir);
  1968. return false;
  1969. }
  1970. ((!)doc1).set_entity_property_async.begin("draft", new Invercargill.NativeElement<bool?>(true), (obj, res) => {
  1971. try {
  1972. ((!)doc1).set_entity_property_async.end(res);
  1973. } catch (Error e) {
  1974. error = e;
  1975. }
  1976. loop.quit();
  1977. });
  1978. loop.run();
  1979. if (error != null) {
  1980. cleanup_dir(temp_dir);
  1981. return false;
  1982. }
  1983. // Test: Try to resolve non-member (post1 has draft=true, doesn't match !draft)
  1984. engine.get_entity_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
  1985. try {
  1986. resolved_doc = engine.get_entity_async.end(res);
  1987. } catch (Error e) {
  1988. error = e;
  1989. }
  1990. loop.quit();
  1991. });
  1992. loop.run();
  1993. // post1 should NOT be found (draft = true, doesn't match !draft)
  1994. if (resolved_doc != null) {
  1995. cleanup_dir(temp_dir);
  1996. return false;
  1997. }
  1998. // Should have thrown ENTITY_NOT_FOUND
  1999. if (error == null) {
  2000. cleanup_dir(temp_dir);
  2001. return false;
  2002. }
  2003. // Test: entity_exists_async for non-member
  2004. engine.entity_exists_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
  2005. try {
  2006. exists = engine.entity_exists_async.end(res);
  2007. } catch (Error e) {
  2008. error = e;
  2009. }
  2010. loop.quit();
  2011. });
  2012. loop.run();
  2013. if (exists) {
  2014. cleanup_dir(temp_dir);
  2015. return false;
  2016. }
  2017. cleanup_dir(temp_dir);
  2018. return true;
  2019. }
  2020. // Test 18: Catalogue virtual child resolution - group key returns CatalogueGroup
  2021. bool test_catalogue_virtual_child_group() {
  2022. string temp_dir = create_temp_dir();
  2023. var engine = new EmbeddedEngine.with_path(temp_dir);
  2024. var loop = new MainLoop();
  2025. Entity? root = null;
  2026. Entity? container = null;
  2027. Entity? doc1 = null;
  2028. Entity? doc2 = null;
  2029. Entity? catalogue = null;
  2030. Entity? resolved_group = null;
  2031. bool exists = false;
  2032. Error? error = null;
  2033. engine.get_root_async.begin((obj, res) => {
  2034. try {
  2035. root = engine.get_root_async.end(res);
  2036. } catch (Error e) {
  2037. error = e;
  2038. }
  2039. loop.quit();
  2040. });
  2041. loop.run();
  2042. if (error != null || root == null) {
  2043. cleanup_dir(temp_dir);
  2044. return false;
  2045. }
  2046. // Create container
  2047. ((!)root).create_container_async.begin("posts", (obj, res) => {
  2048. try {
  2049. container = ((!)root).create_container_async.end(res);
  2050. } catch (Error e) {
  2051. error = e;
  2052. }
  2053. loop.quit();
  2054. });
  2055. loop.run();
  2056. if (error != null || container == null) {
  2057. cleanup_dir(temp_dir);
  2058. return false;
  2059. }
  2060. // IMPORTANT: Create catalogue FIRST so hooks are registered
  2061. ((!)container).create_catalogue_async.begin("by-author", "Post", "author", (obj, res) => {
  2062. try {
  2063. catalogue = ((!)container).create_catalogue_async.end(res);
  2064. } catch (Error e) {
  2065. error = e;
  2066. }
  2067. loop.quit();
  2068. });
  2069. loop.run();
  2070. if (error != null || catalogue == null) {
  2071. cleanup_dir(temp_dir);
  2072. return false;
  2073. }
  2074. // Now create documents - hooks will update the catalogue index
  2075. ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
  2076. try {
  2077. doc1 = ((!)container).create_document_async.end(res);
  2078. } catch (Error e) {
  2079. error = e;
  2080. }
  2081. loop.quit();
  2082. });
  2083. loop.run();
  2084. if (error != null || doc1 == null) {
  2085. cleanup_dir(temp_dir);
  2086. return false;
  2087. }
  2088. ((!)doc1).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("john"), (obj, res) => {
  2089. try {
  2090. ((!)doc1).set_entity_property_async.end(res);
  2091. } catch (Error e) {
  2092. error = e;
  2093. }
  2094. loop.quit();
  2095. });
  2096. loop.run();
  2097. if (error != null) {
  2098. cleanup_dir(temp_dir);
  2099. return false;
  2100. }
  2101. ((!)container).create_document_async.begin("post2", "Post", (obj, res) => {
  2102. try {
  2103. doc2 = ((!)container).create_document_async.end(res);
  2104. } catch (Error e) {
  2105. error = e;
  2106. }
  2107. loop.quit();
  2108. });
  2109. loop.run();
  2110. if (error != null || doc2 == null) {
  2111. cleanup_dir(temp_dir);
  2112. return false;
  2113. }
  2114. ((!)doc2).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("jane"), (obj, res) => {
  2115. try {
  2116. ((!)doc2).set_entity_property_async.end(res);
  2117. } catch (Error e) {
  2118. error = e;
  2119. }
  2120. loop.quit();
  2121. });
  2122. loop.run();
  2123. if (error != null) {
  2124. cleanup_dir(temp_dir);
  2125. return false;
  2126. }
  2127. // Test: Resolve group via direct path
  2128. engine.get_entity_async.begin(new EntityPath("/posts/by-author/john"), (obj, res) => {
  2129. try {
  2130. resolved_group = engine.get_entity_async.end(res);
  2131. } catch (Error e) {
  2132. error = e;
  2133. }
  2134. loop.quit();
  2135. });
  2136. loop.run();
  2137. if (error != null || resolved_group == null) {
  2138. stderr.printf("Failed to resolve group: %s\n", error != null ? ((!)error).message : "null");
  2139. cleanup_dir(temp_dir);
  2140. return false;
  2141. }
  2142. // Should be a CatalogueGroup (appears as CATALOGUE type)
  2143. if (((!)resolved_group).entity_type != EntityType.CATALOGUE) {
  2144. cleanup_dir(temp_dir);
  2145. return false;
  2146. }
  2147. if (((!)resolved_group).name != "john") {
  2148. cleanup_dir(temp_dir);
  2149. return false;
  2150. }
  2151. // Test: entity_exists_async for group
  2152. engine.entity_exists_async.begin(new EntityPath("/posts/by-author/john"), (obj, res) => {
  2153. try {
  2154. exists = engine.entity_exists_async.end(res);
  2155. } catch (Error e) {
  2156. error = e;
  2157. }
  2158. loop.quit();
  2159. });
  2160. loop.run();
  2161. if (error != null || !exists) {
  2162. cleanup_dir(temp_dir);
  2163. return false;
  2164. }
  2165. cleanup_dir(temp_dir);
  2166. return true;
  2167. }
  2168. // Test 19: Catalogue virtual child resolution - document within group
  2169. bool test_catalogue_virtual_child_document() {
  2170. string temp_dir = create_temp_dir();
  2171. var engine = new EmbeddedEngine.with_path(temp_dir);
  2172. var loop = new MainLoop();
  2173. Entity? root = null;
  2174. Entity? container = null;
  2175. Entity? doc1 = null;
  2176. Entity? doc2 = null;
  2177. Entity? catalogue = null;
  2178. Entity? resolved_doc = null;
  2179. Error? error = null;
  2180. engine.get_root_async.begin((obj, res) => {
  2181. try {
  2182. root = engine.get_root_async.end(res);
  2183. } catch (Error e) {
  2184. error = e;
  2185. }
  2186. loop.quit();
  2187. });
  2188. loop.run();
  2189. if (error != null || root == null) {
  2190. cleanup_dir(temp_dir);
  2191. return false;
  2192. }
  2193. // Create container
  2194. ((!)root).create_container_async.begin("posts", (obj, res) => {
  2195. try {
  2196. container = ((!)root).create_container_async.end(res);
  2197. } catch (Error e) {
  2198. error = e;
  2199. }
  2200. loop.quit();
  2201. });
  2202. loop.run();
  2203. if (error != null || container == null) {
  2204. cleanup_dir(temp_dir);
  2205. return false;
  2206. }
  2207. // IMPORTANT: Create catalogue FIRST so hooks are registered
  2208. ((!)container).create_catalogue_async.begin("by-author", "Post", "author", (obj, res) => {
  2209. try {
  2210. catalogue = ((!)container).create_catalogue_async.end(res);
  2211. } catch (Error e) {
  2212. error = e;
  2213. }
  2214. loop.quit();
  2215. });
  2216. loop.run();
  2217. if (error != null || catalogue == null) {
  2218. cleanup_dir(temp_dir);
  2219. return false;
  2220. }
  2221. // Now create documents - hooks will update the catalogue index
  2222. ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
  2223. try {
  2224. doc1 = ((!)container).create_document_async.end(res);
  2225. } catch (Error e) {
  2226. error = e;
  2227. }
  2228. loop.quit();
  2229. });
  2230. loop.run();
  2231. if (error != null || doc1 == null) {
  2232. cleanup_dir(temp_dir);
  2233. return false;
  2234. }
  2235. ((!)doc1).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("john"), (obj, res) => {
  2236. try {
  2237. ((!)doc1).set_entity_property_async.end(res);
  2238. } catch (Error e) {
  2239. error = e;
  2240. }
  2241. loop.quit();
  2242. });
  2243. loop.run();
  2244. if (error != null) {
  2245. cleanup_dir(temp_dir);
  2246. return false;
  2247. }
  2248. ((!)container).create_document_async.begin("post2", "Post", (obj, res) => {
  2249. try {
  2250. doc2 = ((!)container).create_document_async.end(res);
  2251. } catch (Error e) {
  2252. error = e;
  2253. }
  2254. loop.quit();
  2255. });
  2256. loop.run();
  2257. if (error != null || doc2 == null) {
  2258. cleanup_dir(temp_dir);
  2259. return false;
  2260. }
  2261. ((!)doc2).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("jane"), (obj, res) => {
  2262. try {
  2263. ((!)doc2).set_entity_property_async.end(res);
  2264. } catch (Error e) {
  2265. error = e;
  2266. }
  2267. loop.quit();
  2268. });
  2269. loop.run();
  2270. if (error != null) {
  2271. cleanup_dir(temp_dir);
  2272. return false;
  2273. }
  2274. // Test: Resolve document via direct path (document name within any group)
  2275. engine.get_entity_async.begin(new EntityPath("/posts/by-author/post1"), (obj, res) => {
  2276. try {
  2277. resolved_doc = engine.get_entity_async.end(res);
  2278. } catch (Error e) {
  2279. error = e;
  2280. }
  2281. loop.quit();
  2282. });
  2283. loop.run();
  2284. if (error != null || resolved_doc == null) {
  2285. stderr.printf("Failed to resolve document: %s\n", error != null ? ((!)error).message : "null");
  2286. cleanup_dir(temp_dir);
  2287. return false;
  2288. }
  2289. // Should be a Document
  2290. if (((!)resolved_doc).entity_type != EntityType.DOCUMENT) {
  2291. cleanup_dir(temp_dir);
  2292. return false;
  2293. }
  2294. if (((!)resolved_doc).name != "post1") {
  2295. cleanup_dir(temp_dir);
  2296. return false;
  2297. }
  2298. cleanup_dir(temp_dir);
  2299. return true;
  2300. }
  2301. // Test 20: Catalogue virtual child resolution - non-existent returns ENTITY_NOT_FOUND
  2302. bool test_catalogue_virtual_child_not_found() {
  2303. string temp_dir = create_temp_dir();
  2304. var engine = new EmbeddedEngine.with_path(temp_dir);
  2305. var loop = new MainLoop();
  2306. Entity? root = null;
  2307. Entity? container = null;
  2308. Entity? doc1 = null;
  2309. Entity? catalogue = null;
  2310. Entity? resolved = null;
  2311. bool exists = true;
  2312. Error? error = null;
  2313. engine.get_root_async.begin((obj, res) => {
  2314. try {
  2315. root = engine.get_root_async.end(res);
  2316. } catch (Error e) {
  2317. error = e;
  2318. }
  2319. loop.quit();
  2320. });
  2321. loop.run();
  2322. if (error != null || root == null) {
  2323. cleanup_dir(temp_dir);
  2324. return false;
  2325. }
  2326. // Create container
  2327. ((!)root).create_container_async.begin("posts", (obj, res) => {
  2328. try {
  2329. container = ((!)root).create_container_async.end(res);
  2330. } catch (Error e) {
  2331. error = e;
  2332. }
  2333. loop.quit();
  2334. });
  2335. loop.run();
  2336. if (error != null || container == null) {
  2337. cleanup_dir(temp_dir);
  2338. return false;
  2339. }
  2340. // Create document
  2341. ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
  2342. try {
  2343. doc1 = ((!)container).create_document_async.end(res);
  2344. } catch (Error e) {
  2345. error = e;
  2346. }
  2347. loop.quit();
  2348. });
  2349. loop.run();
  2350. if (error != null || doc1 == null) {
  2351. cleanup_dir(temp_dir);
  2352. return false;
  2353. }
  2354. ((!)doc1).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("john"), (obj, res) => {
  2355. try {
  2356. ((!)doc1).set_entity_property_async.end(res);
  2357. } catch (Error e) {
  2358. error = e;
  2359. }
  2360. loop.quit();
  2361. });
  2362. loop.run();
  2363. if (error != null) {
  2364. cleanup_dir(temp_dir);
  2365. return false;
  2366. }
  2367. // Create catalogue grouped by author
  2368. ((!)container).create_catalogue_async.begin("by-author", "Post", "author", (obj, res) => {
  2369. try {
  2370. catalogue = ((!)container).create_catalogue_async.end(res);
  2371. } catch (Error e) {
  2372. error = e;
  2373. }
  2374. loop.quit();
  2375. });
  2376. loop.run();
  2377. if (error != null || catalogue == null) {
  2378. cleanup_dir(temp_dir);
  2379. return false;
  2380. }
  2381. // Populate the catalogue index
  2382. var catalogue_impl = catalogue as Implexus.Entities.Catalogue;
  2383. if (catalogue_impl != null) {
  2384. try {
  2385. ((!)catalogue_impl).populate_index();
  2386. } catch (Error e) {
  2387. cleanup_dir(temp_dir);
  2388. return false;
  2389. }
  2390. }
  2391. // Test: Try to resolve non-existent group
  2392. engine.get_entity_async.begin(new EntityPath("/posts/by-author/nonexistent"), (obj, res) => {
  2393. try {
  2394. resolved = engine.get_entity_async.end(res);
  2395. } catch (Error e) {
  2396. error = e;
  2397. }
  2398. loop.quit();
  2399. });
  2400. loop.run();
  2401. // Should NOT be found
  2402. if (resolved != null) {
  2403. cleanup_dir(temp_dir);
  2404. return false;
  2405. }
  2406. // Should have thrown ENTITY_NOT_FOUND
  2407. if (error == null) {
  2408. cleanup_dir(temp_dir);
  2409. return false;
  2410. }
  2411. // Test: entity_exists_async for non-existent
  2412. engine.entity_exists_async.begin(new EntityPath("/posts/by-author/nonexistent"), (obj, res) => {
  2413. try {
  2414. exists = engine.entity_exists_async.end(res);
  2415. } catch (Error e) {
  2416. error = e;
  2417. }
  2418. loop.quit();
  2419. });
  2420. loop.run();
  2421. if (exists) {
  2422. cleanup_dir(temp_dir);
  2423. return false;
  2424. }
  2425. cleanup_dir(temp_dir);
  2426. return true;
  2427. }
  2428. // Test 21: Index virtual child resolution - search pattern returns IndexResult
  2429. bool test_index_virtual_child_resolution() {
  2430. string temp_dir = create_temp_dir();
  2431. var engine = new EmbeddedEngine.with_path(temp_dir);
  2432. var loop = new MainLoop();
  2433. Entity? root = null;
  2434. Entity? container = null;
  2435. Entity? doc1 = null;
  2436. Entity? doc2 = null;
  2437. Entity? index = null;
  2438. Entity? resolved_result = null;
  2439. bool exists = false;
  2440. Error? error = null;
  2441. engine.get_root_async.begin((obj, res) => {
  2442. try {
  2443. root = engine.get_root_async.end(res);
  2444. } catch (Error e) {
  2445. error = e;
  2446. }
  2447. loop.quit();
  2448. });
  2449. loop.run();
  2450. if (error != null || root == null) {
  2451. cleanup_dir(temp_dir);
  2452. return false;
  2453. }
  2454. // Create container
  2455. ((!)root).create_container_async.begin("articles", (obj, res) => {
  2456. try {
  2457. container = ((!)root).create_container_async.end(res);
  2458. } catch (Error e) {
  2459. error = e;
  2460. }
  2461. loop.quit();
  2462. });
  2463. loop.run();
  2464. if (error != null || container == null) {
  2465. cleanup_dir(temp_dir);
  2466. return false;
  2467. }
  2468. // Create documents FIRST with searchable content
  2469. ((!)container).create_document_async.begin("article1", "Article", (obj, res) => {
  2470. try {
  2471. doc1 = ((!)container).create_document_async.end(res);
  2472. } catch (Error e) {
  2473. error = e;
  2474. }
  2475. loop.quit();
  2476. });
  2477. loop.run();
  2478. if (error != null || doc1 == null) {
  2479. cleanup_dir(temp_dir);
  2480. return false;
  2481. }
  2482. ((!)doc1).set_entity_property_async.begin("title", new Invercargill.NativeElement<string>("Introduction to Vala"), (obj, res) => {
  2483. try {
  2484. ((!)doc1).set_entity_property_async.end(res);
  2485. } catch (Error e) {
  2486. error = e;
  2487. }
  2488. loop.quit();
  2489. });
  2490. loop.run();
  2491. if (error != null) {
  2492. cleanup_dir(temp_dir);
  2493. return false;
  2494. }
  2495. ((!)container).create_document_async.begin("article2", "Article", (obj, res) => {
  2496. try {
  2497. doc2 = ((!)container).create_document_async.end(res);
  2498. } catch (Error e) {
  2499. error = e;
  2500. }
  2501. loop.quit();
  2502. });
  2503. loop.run();
  2504. if (error != null || doc2 == null) {
  2505. cleanup_dir(temp_dir);
  2506. return false;
  2507. }
  2508. ((!)doc2).set_entity_property_async.begin("title", new Invercargill.NativeElement<string>("Advanced Vala Techniques"), (obj, res) => {
  2509. try {
  2510. ((!)doc2).set_entity_property_async.end(res);
  2511. } catch (Error e) {
  2512. error = e;
  2513. }
  2514. loop.quit();
  2515. });
  2516. loop.run();
  2517. if (error != null) {
  2518. cleanup_dir(temp_dir);
  2519. return false;
  2520. }
  2521. // Create index AFTER documents - populate_index() will be called internally
  2522. ((!)container).create_index_async.begin("search", "Article", "title", (obj, res) => {
  2523. try {
  2524. index = ((!)container).create_index_async.end(res);
  2525. } catch (Error e) {
  2526. error = e;
  2527. }
  2528. loop.quit();
  2529. });
  2530. loop.run();
  2531. if (error != null || index == null) {
  2532. cleanup_dir(temp_dir);
  2533. return false;
  2534. }
  2535. // Explicitly populate the index (needed for tests)
  2536. var index_impl = index as Implexus.Entities.Index;
  2537. if (index_impl != null) {
  2538. try {
  2539. ((!)index_impl).populate_index();
  2540. } catch (Error e) {
  2541. cleanup_dir(temp_dir);
  2542. return false;
  2543. }
  2544. }
  2545. // Test: Resolve search pattern via direct path
  2546. engine.get_entity_async.begin(new EntityPath("/articles/search/*Vala*"), (obj, res) => {
  2547. try {
  2548. resolved_result = engine.get_entity_async.end(res);
  2549. } catch (Error e) {
  2550. error = e;
  2551. }
  2552. loop.quit();
  2553. });
  2554. loop.run();
  2555. if (error != null || resolved_result == null) {
  2556. stderr.printf("Failed to resolve search: %s\n", error != null ? ((!)error).message : "null");
  2557. cleanup_dir(temp_dir);
  2558. return false;
  2559. }
  2560. // Should be a Container (IndexResult appears as CONTAINER)
  2561. if (((!)resolved_result).entity_type != EntityType.CONTAINER) {
  2562. cleanup_dir(temp_dir);
  2563. return false;
  2564. }
  2565. // Test: entity_exists_async for search with matches
  2566. engine.entity_exists_async.begin(new EntityPath("/articles/search/*Vala*"), (obj, res) => {
  2567. try {
  2568. exists = engine.entity_exists_async.end(res);
  2569. } catch (Error e) {
  2570. error = e;
  2571. }
  2572. loop.quit();
  2573. });
  2574. loop.run();
  2575. if (error != null || !exists) {
  2576. cleanup_dir(temp_dir);
  2577. return false;
  2578. }
  2579. cleanup_dir(temp_dir);
  2580. return true;
  2581. }
  2582. // Test 22: Index virtual child resolution - no matches returns ENTITY_NOT_FOUND
  2583. bool test_index_virtual_child_no_matches() {
  2584. string temp_dir = create_temp_dir();
  2585. var engine = new EmbeddedEngine.with_path(temp_dir);
  2586. var loop = new MainLoop();
  2587. Entity? root = null;
  2588. Entity? container = null;
  2589. Entity? doc1 = null;
  2590. Entity? index = null;
  2591. Entity? resolved = null;
  2592. bool exists = true;
  2593. Error? error = null;
  2594. engine.get_root_async.begin((obj, res) => {
  2595. try {
  2596. root = engine.get_root_async.end(res);
  2597. } catch (Error e) {
  2598. error = e;
  2599. }
  2600. loop.quit();
  2601. });
  2602. loop.run();
  2603. if (error != null || root == null) {
  2604. cleanup_dir(temp_dir);
  2605. return false;
  2606. }
  2607. // Create container
  2608. ((!)root).create_container_async.begin("articles", (obj, res) => {
  2609. try {
  2610. container = ((!)root).create_container_async.end(res);
  2611. } catch (Error e) {
  2612. error = e;
  2613. }
  2614. loop.quit();
  2615. });
  2616. loop.run();
  2617. if (error != null || container == null) {
  2618. cleanup_dir(temp_dir);
  2619. return false;
  2620. }
  2621. // Create document
  2622. ((!)container).create_document_async.begin("article1", "Article", (obj, res) => {
  2623. try {
  2624. doc1 = ((!)container).create_document_async.end(res);
  2625. } catch (Error e) {
  2626. error = e;
  2627. }
  2628. loop.quit();
  2629. });
  2630. loop.run();
  2631. if (error != null || doc1 == null) {
  2632. cleanup_dir(temp_dir);
  2633. return false;
  2634. }
  2635. ((!)doc1).set_entity_property_async.begin("title", new Invercargill.NativeElement<string>("Introduction to Vala"), (obj, res) => {
  2636. try {
  2637. ((!)doc1).set_entity_property_async.end(res);
  2638. } catch (Error e) {
  2639. error = e;
  2640. }
  2641. loop.quit();
  2642. });
  2643. loop.run();
  2644. if (error != null) {
  2645. cleanup_dir(temp_dir);
  2646. return false;
  2647. }
  2648. // Create index over title property
  2649. ((!)container).create_index_async.begin("search", "Article", "title", (obj, res) => {
  2650. try {
  2651. index = ((!)container).create_index_async.end(res);
  2652. } catch (Error e) {
  2653. error = e;
  2654. }
  2655. loop.quit();
  2656. });
  2657. loop.run();
  2658. if (error != null || index == null) {
  2659. cleanup_dir(temp_dir);
  2660. return false;
  2661. }
  2662. // Populate the index
  2663. var index_impl = index as Implexus.Entities.Index;
  2664. if (index_impl != null) {
  2665. try {
  2666. ((!)index_impl).populate_index();
  2667. } catch (Error e) {
  2668. cleanup_dir(temp_dir);
  2669. return false;
  2670. }
  2671. }
  2672. // Test: Search with no matches
  2673. engine.get_entity_async.begin(new EntityPath("/articles/search/*NonExistent*"), (obj, res) => {
  2674. try {
  2675. resolved = engine.get_entity_async.end(res);
  2676. } catch (Error e) {
  2677. error = e;
  2678. }
  2679. loop.quit();
  2680. });
  2681. loop.run();
  2682. // Should NOT be found
  2683. if (resolved != null) {
  2684. cleanup_dir(temp_dir);
  2685. return false;
  2686. }
  2687. // Should have thrown ENTITY_NOT_FOUND
  2688. if (error == null) {
  2689. cleanup_dir(temp_dir);
  2690. return false;
  2691. }
  2692. // Test: entity_exists_async for search with no matches
  2693. engine.entity_exists_async.begin(new EntityPath("/articles/search/*NonExistent*"), (obj, res) => {
  2694. try {
  2695. exists = engine.entity_exists_async.end(res);
  2696. } catch (Error e) {
  2697. error = e;
  2698. }
  2699. loop.quit();
  2700. });
  2701. loop.run();
  2702. if (exists) {
  2703. cleanup_dir(temp_dir);
  2704. return false;
  2705. }
  2706. cleanup_dir(temp_dir);
  2707. return true;
  2708. }
  2709. // Test 23: Comparison - direct path equals navigation for Category
  2710. bool test_category_direct_vs_navigation() {
  2711. string temp_dir = create_temp_dir();
  2712. var engine = new EmbeddedEngine.with_path(temp_dir);
  2713. var loop = new MainLoop();
  2714. Entity? root = null;
  2715. Entity? container = null;
  2716. Entity? doc1 = null;
  2717. Entity? category = null;
  2718. Entity? direct_doc = null;
  2719. Entity? nav_doc = null;
  2720. Error? error = null;
  2721. engine.get_root_async.begin((obj, res) => {
  2722. try {
  2723. root = engine.get_root_async.end(res);
  2724. } catch (Error e) {
  2725. error = e;
  2726. }
  2727. loop.quit();
  2728. });
  2729. loop.run();
  2730. if (error != null || root == null) {
  2731. cleanup_dir(temp_dir);
  2732. return false;
  2733. }
  2734. // Create container
  2735. ((!)root).create_container_async.begin("posts", (obj, res) => {
  2736. try {
  2737. container = ((!)root).create_container_async.end(res);
  2738. } catch (Error e) {
  2739. error = e;
  2740. }
  2741. loop.quit();
  2742. });
  2743. loop.run();
  2744. if (error != null || container == null) {
  2745. cleanup_dir(temp_dir);
  2746. return false;
  2747. }
  2748. // IMPORTANT: Create category FIRST so hooks are registered
  2749. ((!)container).create_category_async.begin("published", "Post", "!draft", (obj, res) => {
  2750. try {
  2751. category = ((!)container).create_category_async.end(res);
  2752. } catch (Error e) {
  2753. error = e;
  2754. }
  2755. loop.quit();
  2756. });
  2757. loop.run();
  2758. if (error != null || category == null) {
  2759. cleanup_dir(temp_dir);
  2760. return false;
  2761. }
  2762. // Now create document - hooks will update the category index
  2763. ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
  2764. try {
  2765. doc1 = ((!)container).create_document_async.end(res);
  2766. } catch (Error e) {
  2767. error = e;
  2768. }
  2769. loop.quit();
  2770. });
  2771. loop.run();
  2772. if (error != null || doc1 == null) {
  2773. cleanup_dir(temp_dir);
  2774. return false;
  2775. }
  2776. ((!)doc1).set_entity_property_async.begin("draft", new Invercargill.NativeElement<bool?>(false), (obj, res) => {
  2777. try {
  2778. ((!)doc1).set_entity_property_async.end(res);
  2779. } catch (Error e) {
  2780. error = e;
  2781. }
  2782. loop.quit();
  2783. });
  2784. loop.run();
  2785. if (error != null) {
  2786. cleanup_dir(temp_dir);
  2787. return false;
  2788. }
  2789. // Method 1: Direct path lookup
  2790. engine.get_entity_async.begin(new EntityPath("/posts/published/post1"), (obj, res) => {
  2791. try {
  2792. direct_doc = engine.get_entity_async.end(res);
  2793. } catch (Error e) {
  2794. error = e;
  2795. }
  2796. loop.quit();
  2797. });
  2798. loop.run();
  2799. if (error != null || direct_doc == null) {
  2800. cleanup_dir(temp_dir);
  2801. return false;
  2802. }
  2803. // Method 2: Navigation via get_child_async
  2804. ((!)category).get_child_async.begin("post1", (obj, res) => {
  2805. try {
  2806. nav_doc = ((!)category).get_child_async.end(res);
  2807. } catch (Error e) {
  2808. error = e;
  2809. }
  2810. loop.quit();
  2811. });
  2812. loop.run();
  2813. if (error != null || nav_doc == null) {
  2814. cleanup_dir(temp_dir);
  2815. return false;
  2816. }
  2817. // Both methods should return the same document (by path)
  2818. if (((!)direct_doc).path.to_string() != ((!)nav_doc).path.to_string()) {
  2819. cleanup_dir(temp_dir);
  2820. return false;
  2821. }
  2822. if (((!)direct_doc).name != ((!)nav_doc).name) {
  2823. cleanup_dir(temp_dir);
  2824. return false;
  2825. }
  2826. cleanup_dir(temp_dir);
  2827. return true;
  2828. }
  2829. // Test 24: Comparison - direct path equals navigation for Catalogue group
  2830. bool test_catalogue_direct_vs_navigation() {
  2831. string temp_dir = create_temp_dir();
  2832. var engine = new EmbeddedEngine.with_path(temp_dir);
  2833. var loop = new MainLoop();
  2834. Entity? root = null;
  2835. Entity? container = null;
  2836. Entity? doc1 = null;
  2837. Entity? catalogue = null;
  2838. Entity? direct_group = null;
  2839. Entity? nav_group = null;
  2840. Error? error = null;
  2841. engine.get_root_async.begin((obj, res) => {
  2842. try {
  2843. root = engine.get_root_async.end(res);
  2844. } catch (Error e) {
  2845. error = e;
  2846. }
  2847. loop.quit();
  2848. });
  2849. loop.run();
  2850. if (error != null || root == null) {
  2851. cleanup_dir(temp_dir);
  2852. return false;
  2853. }
  2854. // Create container
  2855. ((!)root).create_container_async.begin("posts", (obj, res) => {
  2856. try {
  2857. container = ((!)root).create_container_async.end(res);
  2858. } catch (Error e) {
  2859. error = e;
  2860. }
  2861. loop.quit();
  2862. });
  2863. loop.run();
  2864. if (error != null || container == null) {
  2865. cleanup_dir(temp_dir);
  2866. return false;
  2867. }
  2868. // IMPORTANT: Create catalogue FIRST so hooks are registered
  2869. ((!)container).create_catalogue_async.begin("by-author", "Post", "author", (obj, res) => {
  2870. try {
  2871. catalogue = ((!)container).create_catalogue_async.end(res);
  2872. } catch (Error e) {
  2873. error = e;
  2874. }
  2875. loop.quit();
  2876. });
  2877. loop.run();
  2878. if (error != null || catalogue == null) {
  2879. cleanup_dir(temp_dir);
  2880. return false;
  2881. }
  2882. // Now create document - hooks will update the catalogue index
  2883. ((!)container).create_document_async.begin("post1", "Post", (obj, res) => {
  2884. try {
  2885. doc1 = ((!)container).create_document_async.end(res);
  2886. } catch (Error e) {
  2887. error = e;
  2888. }
  2889. loop.quit();
  2890. });
  2891. loop.run();
  2892. if (error != null || doc1 == null) {
  2893. cleanup_dir(temp_dir);
  2894. return false;
  2895. }
  2896. ((!)doc1).set_entity_property_async.begin("author", new Invercargill.NativeElement<string>("john"), (obj, res) => {
  2897. try {
  2898. ((!)doc1).set_entity_property_async.end(res);
  2899. } catch (Error e) {
  2900. error = e;
  2901. }
  2902. loop.quit();
  2903. });
  2904. loop.run();
  2905. if (error != null) {
  2906. cleanup_dir(temp_dir);
  2907. return false;
  2908. }
  2909. // Method 1: Direct path lookup
  2910. engine.get_entity_async.begin(new EntityPath("/posts/by-author/john"), (obj, res) => {
  2911. try {
  2912. direct_group = engine.get_entity_async.end(res);
  2913. } catch (Error e) {
  2914. error = e;
  2915. }
  2916. loop.quit();
  2917. });
  2918. loop.run();
  2919. if (error != null || direct_group == null) {
  2920. cleanup_dir(temp_dir);
  2921. return false;
  2922. }
  2923. // Method 2: Navigation via get_child_async
  2924. ((!)catalogue).get_child_async.begin("john", (obj, res) => {
  2925. try {
  2926. nav_group = ((!)catalogue).get_child_async.end(res);
  2927. } catch (Error e) {
  2928. error = e;
  2929. }
  2930. loop.quit();
  2931. });
  2932. loop.run();
  2933. if (error != null || nav_group == null) {
  2934. cleanup_dir(temp_dir);
  2935. return false;
  2936. }
  2937. // Both methods should return the same group (by path)
  2938. if (((!)direct_group).path.to_string() != ((!)nav_group).path.to_string()) {
  2939. cleanup_dir(temp_dir);
  2940. return false;
  2941. }
  2942. if (((!)direct_group).name != ((!)nav_group).name) {
  2943. cleanup_dir(temp_dir);
  2944. return false;
  2945. }
  2946. cleanup_dir(temp_dir);
  2947. return true;
  2948. }
  2949. // Test 25: Comparison - direct path equals navigation for Index search
  2950. bool test_index_direct_vs_navigation() {
  2951. string temp_dir = create_temp_dir();
  2952. var engine = new EmbeddedEngine.with_path(temp_dir);
  2953. var loop = new MainLoop();
  2954. Entity? root = null;
  2955. Entity? container = null;
  2956. Entity? doc1 = null;
  2957. Entity? index = null;
  2958. Entity? direct_result = null;
  2959. Entity? nav_result = null;
  2960. Error? error = null;
  2961. engine.get_root_async.begin((obj, res) => {
  2962. try {
  2963. root = engine.get_root_async.end(res);
  2964. } catch (Error e) {
  2965. error = e;
  2966. }
  2967. loop.quit();
  2968. });
  2969. loop.run();
  2970. if (error != null || root == null) {
  2971. cleanup_dir(temp_dir);
  2972. return false;
  2973. }
  2974. // Create container
  2975. ((!)root).create_container_async.begin("articles", (obj, res) => {
  2976. try {
  2977. container = ((!)root).create_container_async.end(res);
  2978. } catch (Error e) {
  2979. error = e;
  2980. }
  2981. loop.quit();
  2982. });
  2983. loop.run();
  2984. if (error != null || container == null) {
  2985. cleanup_dir(temp_dir);
  2986. return false;
  2987. }
  2988. // Create document FIRST with searchable content
  2989. ((!)container).create_document_async.begin("article1", "Article", (obj, res) => {
  2990. try {
  2991. doc1 = ((!)container).create_document_async.end(res);
  2992. } catch (Error e) {
  2993. error = e;
  2994. }
  2995. loop.quit();
  2996. });
  2997. loop.run();
  2998. if (error != null || doc1 == null) {
  2999. cleanup_dir(temp_dir);
  3000. return false;
  3001. }
  3002. ((!)doc1).set_entity_property_async.begin("title", new Invercargill.NativeElement<string>("Introduction to Vala"), (obj, res) => {
  3003. try {
  3004. ((!)doc1).set_entity_property_async.end(res);
  3005. } catch (Error e) {
  3006. error = e;
  3007. }
  3008. loop.quit();
  3009. });
  3010. loop.run();
  3011. if (error != null) {
  3012. cleanup_dir(temp_dir);
  3013. return false;
  3014. }
  3015. // Create index AFTER documents - populate_index() will be called internally
  3016. ((!)container).create_index_async.begin("search", "Article", "title", (obj, res) => {
  3017. try {
  3018. index = ((!)container).create_index_async.end(res);
  3019. } catch (Error e) {
  3020. error = e;
  3021. }
  3022. loop.quit();
  3023. });
  3024. loop.run();
  3025. if (error != null || index == null) {
  3026. cleanup_dir(temp_dir);
  3027. return false;
  3028. }
  3029. // Explicitly populate the index (needed for tests)
  3030. var index_impl = index as Implexus.Entities.Index;
  3031. if (index_impl != null) {
  3032. try {
  3033. ((!)index_impl).populate_index();
  3034. } catch (Error e) {
  3035. cleanup_dir(temp_dir);
  3036. return false;
  3037. }
  3038. }
  3039. // Method 1: Direct path lookup
  3040. engine.get_entity_async.begin(new EntityPath("/articles/search/*Vala*"), (obj, res) => {
  3041. try {
  3042. direct_result = engine.get_entity_async.end(res);
  3043. } catch (Error e) {
  3044. error = e;
  3045. }
  3046. loop.quit();
  3047. });
  3048. loop.run();
  3049. if (error != null || direct_result == null) {
  3050. cleanup_dir(temp_dir);
  3051. return false;
  3052. }
  3053. // Method 2: Navigation via get_child_async
  3054. ((!)index).get_child_async.begin("*Vala*", (obj, res) => {
  3055. try {
  3056. nav_result = ((!)index).get_child_async.end(res);
  3057. } catch (Error e) {
  3058. error = e;
  3059. }
  3060. loop.quit();
  3061. });
  3062. loop.run();
  3063. if (error != null || nav_result == null) {
  3064. cleanup_dir(temp_dir);
  3065. return false;
  3066. }
  3067. // Both methods should return an IndexResult with the same path
  3068. if (((!)direct_result).path.to_string() != ((!)nav_result).path.to_string()) {
  3069. cleanup_dir(temp_dir);
  3070. return false;
  3071. }
  3072. cleanup_dir(temp_dir);
  3073. return true;
  3074. }