mpackdb
All repositories: gitoria
6.1 KB
import MPackDB, { PrimaryKeyType } from '../src/MPackDB.js';import { rm } from 'fs/promises';const TEST_DB_PATH = 'db/unique-index';console.log('--- Unique Index Test ---\n');// Clean upawait rm(TEST_DB_PATH, { recursive: true, force: true });// ============================================================// TEST 1: Primary key is unique by default// ============================================================console.log('TEST 1: Primary key uniqueness (auto-enforced)');const db1 = new MPackDB(`${TEST_DB_PATH}/test1`, {primaryKey: '*id',indexes: ['email']});await db1.insert({ email: '[email protected]' }); // id=0await db1.insert({ email: '[email protected]' }); // id=1// Try inserting with duplicate primary keytry {await db1.insert({ id: 0, email: '[email protected]' }, { skipPrimaryKey: true });console.log(' ✗ FAIL — duplicate primary key was accepted');} catch (e) {if (e.code === 'DUPLICATE_KEY') {console.log(` ✓ PASS — rejected: ${e.message}`);} else {console.log(` ✗ FAIL — wrong error: ${e.message}`);}}const all1 = await db1.find();console.log(` Records: ${all1.length} (expected: 2)\n`);await db1.close();// ============================================================// TEST 2: Unique secondary index (!email)// ============================================================console.log('TEST 2: Unique secondary index (!email)');await rm(`${TEST_DB_PATH}/test2`, { recursive: true, force: true });const db2 = new MPackDB(`${TEST_DB_PATH}/test2`, {primaryKey: '*id',indexes: ['!email', '*age']});await db2.insert({ email: '[email protected]', age: 30 });await db2.insert({ email: '[email protected]', age: 25 });// Try inserting duplicate emailtry {await db2.insert({ email: '[email protected]', age: 99 });console.log(' ✗ FAIL — duplicate email was accepted');} catch (e) {if (e.code === 'DUPLICATE_KEY' && e.field === 'email') {console.log(` ✓ PASS — rejected: ${e.message}`);} else {console.log(` ✗ FAIL — wrong error: ${e.message}`);}}// Different email should workawait db2.insert({ email: '[email protected]', age: 35 });const all2 = await db2.find();console.log(` Records: ${all2.length} (expected: 3)`);console.log(` ${all2.length === 3 ? '✓ PASS' : '✗ FAIL'}\n`);await db2.close();// ============================================================// TEST 3: Unique index after delete (can reuse deleted key)// ============================================================console.log('TEST 3: Unique index after delete');await rm(`${TEST_DB_PATH}/test3`, { recursive: true, force: true });const db3 = new MPackDB(`${TEST_DB_PATH}/test3`, {primaryKey: '*id',indexes: ['!email']});await db3.insert({ email: '[email protected]' });// Delete aliceawait db3.delete(r => r.email === '[email protected]');// Should now be able to insert same emailtry {await db3.insert({ email: '[email protected]' });console.log(' ✓ PASS — reinserted after delete');} catch (e) {console.log(` ✗ FAIL — rejected after delete: ${e.message}`);}const all3 = await db3.find();console.log(` Records: ${all3.length} (expected: 1)`);console.log(` ${all3.length === 1 ? '✓ PASS' : '✗ FAIL'}\n`);await db3.close();// ============================================================// TEST 4: Concurrent insert with unique index (the login pattern)// ============================================================console.log('TEST 4: Concurrent insert with unique email (race prevention)');await rm(`${TEST_DB_PATH}/test4`, { recursive: true, force: true });const db4 = new MPackDB(`${TEST_DB_PATH}/test4`, {primaryKey: '@id',indexes: ['!email']});// Simulate concurrent login: two inserts for same email at onceconst results = await Promise.allSettled([db4.insert({ email: '[email protected]' }),db4.insert({ email: '[email protected]' }),db4.insert({ email: '[email protected]' }),]);const fulfilled = results.filter(r => r.status === 'fulfilled');const rejected = results.filter(r => r.status === 'rejected' && r.reason.code === 'DUPLICATE_KEY');console.log(` Succeeded: ${fulfilled.length} (expected: 1)`);console.log(` Rejected (DUPLICATE_KEY): ${rejected.length} (expected: 2)`);const all4 = await db4.find();console.log(` Records in DB: ${all4.length} (expected: 1)`);const pass4 = fulfilled.length === 1 && rejected.length === 2 && all4.length === 1;console.log(` ${pass4 ? '✓ PASS' : '✗ FAIL'}\n`);await db4.close();// ============================================================// TEST 5: Non-unique index allows duplicates// ============================================================console.log('TEST 5: Non-unique index allows duplicates');await rm(`${TEST_DB_PATH}/test5`, { recursive: true, force: true });const db5 = new MPackDB(`${TEST_DB_PATH}/test5`, {primaryKey: '*id',indexes: ['category'] // no ! prefix = not unique});await db5.insert({ category: 'Electronics' });await db5.insert({ category: 'Electronics' });await db5.insert({ category: 'Electronics' });const all5 = await db5.find();console.log(` Records: ${all5.length} (expected: 3)`);console.log(` ${all5.length === 3 ? '✓ PASS — duplicates allowed' : '✗ FAIL'}\n`);await db5.close();// ============================================================// TEST 6: Unique numeric index (!*score)// ============================================================console.log('TEST 6: Unique numeric index (!*score)');await rm(`${TEST_DB_PATH}/test6`, { recursive: true, force: true });const db6 = new MPackDB(`${TEST_DB_PATH}/test6`, {primaryKey: '*id',indexes: ['!*score']});await db6.insert({ score: 100 });await db6.insert({ score: 200 });try {await db6.insert({ score: 100 });console.log(' ✗ FAIL — duplicate score was accepted');} catch (e) {if (e.code === 'DUPLICATE_KEY' && e.field === 'score') {console.log(` ✓ PASS — rejected: ${e.message}`);} else {console.log(` ✗ FAIL — wrong error: ${e.message}`);}}await db6.close();// ============================================================// SUMMARY// ============================================================console.log('\n--- Done ---');
Branches
- mastermain branch