gitoriaLog in with ident

mpackdb

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Branchmaster87888725release 1.0.7caramboleyomaster/tests/unique-index.test.js

6.1 KB

  1. import MPackDB, { PrimaryKeyType } from '../src/MPackDB.js';
  2. import { rm } from 'fs/promises';
  3. const TEST_DB_PATH = 'db/unique-index';
  4. console.log('--- Unique Index Test ---\n');
  5. // Clean up
  6. await rm(TEST_DB_PATH, { recursive: true, force: true });
  7. // ============================================================
  8. // TEST 1: Primary key is unique by default
  9. // ============================================================
  10. console.log('TEST 1: Primary key uniqueness (auto-enforced)');
  11. const db1 = new MPackDB(`${TEST_DB_PATH}/test1`, {
  12. primaryKey: '*id',
  13. indexes: ['email']
  14. });
  15. await db1.insert({ email: '[email protected]' }); // id=0
  16. await db1.insert({ email: '[email protected]' }); // id=1
  17. // Try inserting with duplicate primary key
  18. try {
  19. await db1.insert({ id: 0, email: '[email protected]' }, { skipPrimaryKey: true });
  20. console.log(' ✗ FAIL — duplicate primary key was accepted');
  21. } catch (e) {
  22. if (e.code === 'DUPLICATE_KEY') {
  23. console.log(` ✓ PASS — rejected: ${e.message}`);
  24. } else {
  25. console.log(` ✗ FAIL — wrong error: ${e.message}`);
  26. }
  27. }
  28. const all1 = await db1.find();
  29. console.log(` Records: ${all1.length} (expected: 2)\n`);
  30. await db1.close();
  31. // ============================================================
  32. // TEST 2: Unique secondary index (!email)
  33. // ============================================================
  34. console.log('TEST 2: Unique secondary index (!email)');
  35. await rm(`${TEST_DB_PATH}/test2`, { recursive: true, force: true });
  36. const db2 = new MPackDB(`${TEST_DB_PATH}/test2`, {
  37. primaryKey: '*id',
  38. indexes: ['!email', '*age']
  39. });
  40. await db2.insert({ email: '[email protected]', age: 30 });
  41. await db2.insert({ email: '[email protected]', age: 25 });
  42. // Try inserting duplicate email
  43. try {
  44. await db2.insert({ email: '[email protected]', age: 99 });
  45. console.log(' ✗ FAIL — duplicate email was accepted');
  46. } catch (e) {
  47. if (e.code === 'DUPLICATE_KEY' && e.field === 'email') {
  48. console.log(` ✓ PASS — rejected: ${e.message}`);
  49. } else {
  50. console.log(` ✗ FAIL — wrong error: ${e.message}`);
  51. }
  52. }
  53. // Different email should work
  54. await db2.insert({ email: '[email protected]', age: 35 });
  55. const all2 = await db2.find();
  56. console.log(` Records: ${all2.length} (expected: 3)`);
  57. console.log(` ${all2.length === 3 ? '✓ PASS' : '✗ FAIL'}\n`);
  58. await db2.close();
  59. // ============================================================
  60. // TEST 3: Unique index after delete (can reuse deleted key)
  61. // ============================================================
  62. console.log('TEST 3: Unique index after delete');
  63. await rm(`${TEST_DB_PATH}/test3`, { recursive: true, force: true });
  64. const db3 = new MPackDB(`${TEST_DB_PATH}/test3`, {
  65. primaryKey: '*id',
  66. indexes: ['!email']
  67. });
  68. await db3.insert({ email: '[email protected]' });
  69. // Delete alice
  70. await db3.delete(r => r.email === '[email protected]');
  71. // Should now be able to insert same email
  72. try {
  73. await db3.insert({ email: '[email protected]' });
  74. console.log(' ✓ PASS — reinserted after delete');
  75. } catch (e) {
  76. console.log(` ✗ FAIL — rejected after delete: ${e.message}`);
  77. }
  78. const all3 = await db3.find();
  79. console.log(` Records: ${all3.length} (expected: 1)`);
  80. console.log(` ${all3.length === 1 ? '✓ PASS' : '✗ FAIL'}\n`);
  81. await db3.close();
  82. // ============================================================
  83. // TEST 4: Concurrent insert with unique index (the login pattern)
  84. // ============================================================
  85. console.log('TEST 4: Concurrent insert with unique email (race prevention)');
  86. await rm(`${TEST_DB_PATH}/test4`, { recursive: true, force: true });
  87. const db4 = new MPackDB(`${TEST_DB_PATH}/test4`, {
  88. primaryKey: '@id',
  89. indexes: ['!email']
  90. });
  91. // Simulate concurrent login: two inserts for same email at once
  92. const results = await Promise.allSettled([
  93. db4.insert({ email: '[email protected]' }),
  94. db4.insert({ email: '[email protected]' }),
  95. db4.insert({ email: '[email protected]' }),
  96. ]);
  97. const fulfilled = results.filter(r => r.status === 'fulfilled');
  98. const rejected = results.filter(r => r.status === 'rejected' && r.reason.code === 'DUPLICATE_KEY');
  99. console.log(` Succeeded: ${fulfilled.length} (expected: 1)`);
  100. console.log(` Rejected (DUPLICATE_KEY): ${rejected.length} (expected: 2)`);
  101. const all4 = await db4.find();
  102. console.log(` Records in DB: ${all4.length} (expected: 1)`);
  103. const pass4 = fulfilled.length === 1 && rejected.length === 2 && all4.length === 1;
  104. console.log(` ${pass4 ? '✓ PASS' : '✗ FAIL'}\n`);
  105. await db4.close();
  106. // ============================================================
  107. // TEST 5: Non-unique index allows duplicates
  108. // ============================================================
  109. console.log('TEST 5: Non-unique index allows duplicates');
  110. await rm(`${TEST_DB_PATH}/test5`, { recursive: true, force: true });
  111. const db5 = new MPackDB(`${TEST_DB_PATH}/test5`, {
  112. primaryKey: '*id',
  113. indexes: ['category'] // no ! prefix = not unique
  114. });
  115. await db5.insert({ category: 'Electronics' });
  116. await db5.insert({ category: 'Electronics' });
  117. await db5.insert({ category: 'Electronics' });
  118. const all5 = await db5.find();
  119. console.log(` Records: ${all5.length} (expected: 3)`);
  120. console.log(` ${all5.length === 3 ? '✓ PASS — duplicates allowed' : '✗ FAIL'}\n`);
  121. await db5.close();
  122. // ============================================================
  123. // TEST 6: Unique numeric index (!*score)
  124. // ============================================================
  125. console.log('TEST 6: Unique numeric index (!*score)');
  126. await rm(`${TEST_DB_PATH}/test6`, { recursive: true, force: true });
  127. const db6 = new MPackDB(`${TEST_DB_PATH}/test6`, {
  128. primaryKey: '*id',
  129. indexes: ['!*score']
  130. });
  131. await db6.insert({ score: 100 });
  132. await db6.insert({ score: 200 });
  133. try {
  134. await db6.insert({ score: 100 });
  135. console.log(' ✗ FAIL — duplicate score was accepted');
  136. } catch (e) {
  137. if (e.code === 'DUPLICATE_KEY' && e.field === 'score') {
  138. console.log(` ✓ PASS — rejected: ${e.message}`);
  139. } else {
  140. console.log(` ✗ FAIL — wrong error: ${e.message}`);
  141. }
  142. }
  143. await db6.close();
  144. // ============================================================
  145. // SUMMARY
  146. // ============================================================
  147. console.log('\n--- Done ---');

Branches

  • mastermain branch

Latest commits

  • 87888725release 1.0.7caramboleyo
  • c4cdb9b6node: import prefixes (Deno compat) + pre-existing index-state WIPcaramboleyo
  • 0afb8f4bupdate now must be a callbackcaramboleyo
  • cde73eb4release 1.0.6caramboleyo
  • d01dda02add index hints, intersection, boundingBox; remove findByIndexcaramboleyo
  • b8ffc1a0release 1.0.5caramboleyo
  • d47876a1reimplemented lost features like indexed find and more testscaramboleyo
  • 7f08da9afixed insert ignoring model definitioncaramboleyo
  • 705774a9added flush before findcaramboleyo
  • b4db6391initial commitcaramboleyo