@joystick.js/db-canary 0.0.0-canary.2250 → 0.0.0-canary.2251

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/server/lib/operations/find.js +1 -1
  2. package/dist/server/lib/operations/find_one.js +1 -1
  3. package/package.json +2 -2
  4. package/src/server/lib/operations/find.js +144 -15
  5. package/src/server/lib/operations/find_one.js +144 -14
  6. package/test_data_api_key_1758233848259_cglfjzhou/data.mdb +0 -0
  7. package/test_data_api_key_1758233848259_cglfjzhou/lock.mdb +0 -0
  8. package/test_data_api_key_1758233848502_urlje2utd/data.mdb +0 -0
  9. package/test_data_api_key_1758233848502_urlje2utd/lock.mdb +0 -0
  10. package/test_data_api_key_1758233848738_mtcpfe5ns/data.mdb +0 -0
  11. package/test_data_api_key_1758233848738_mtcpfe5ns/lock.mdb +0 -0
  12. package/test_data_api_key_1758233848856_9g97p6gag/data.mdb +0 -0
  13. package/test_data_api_key_1758233848856_9g97p6gag/lock.mdb +0 -0
  14. package/test_data_api_key_1758233857008_0tl9zzhj8/data.mdb +0 -0
  15. package/test_data_api_key_1758233857008_0tl9zzhj8/lock.mdb +0 -0
  16. package/test_data_api_key_1758233857120_60c2f2uhu/data.mdb +0 -0
  17. package/test_data_api_key_1758233857120_60c2f2uhu/lock.mdb +0 -0
  18. package/test_data_api_key_1758233857232_aw7fkqgd9/data.mdb +0 -0
  19. package/test_data_api_key_1758233857232_aw7fkqgd9/lock.mdb +0 -0
  20. package/test_data_api_key_1758234881285_4aeflubjb/data.mdb +0 -0
  21. package/test_data_api_key_1758234881285_4aeflubjb/lock.mdb +0 -0
  22. package/test_data_api_key_1758234881520_kb0amvtqb/data.mdb +0 -0
  23. package/test_data_api_key_1758234881520_kb0amvtqb/lock.mdb +0 -0
  24. package/test_data_api_key_1758234881756_k04gfv2va/data.mdb +0 -0
  25. package/test_data_api_key_1758234881756_k04gfv2va/lock.mdb +0 -0
  26. package/test_data_api_key_1758234881876_wn90dpo1z/data.mdb +0 -0
  27. package/test_data_api_key_1758234881876_wn90dpo1z/lock.mdb +0 -0
  28. package/test_data_api_key_1758234889461_26xz3dmbr/data.mdb +0 -0
  29. package/test_data_api_key_1758234889461_26xz3dmbr/lock.mdb +0 -0
  30. package/test_data_api_key_1758234889572_uziz7e0p5/data.mdb +0 -0
  31. package/test_data_api_key_1758234889572_uziz7e0p5/lock.mdb +0 -0
  32. package/test_data_api_key_1758234889684_5f9wmposh/data.mdb +0 -0
  33. package/test_data_api_key_1758234889684_5f9wmposh/lock.mdb +0 -0
  34. package/test_data_api_key_1758235657729_prwgm6mxr/data.mdb +0 -0
  35. package/test_data_api_key_1758235657729_prwgm6mxr/lock.mdb +0 -0
  36. package/test_data_api_key_1758235657961_rc2da0dc2/data.mdb +0 -0
  37. package/test_data_api_key_1758235657961_rc2da0dc2/lock.mdb +0 -0
  38. package/test_data_api_key_1758235658193_oqqxm0sny/data.mdb +0 -0
  39. package/test_data_api_key_1758235658193_oqqxm0sny/lock.mdb +0 -0
  40. package/test_data_api_key_1758235658309_vggac1pj6/data.mdb +0 -0
  41. package/test_data_api_key_1758235658309_vggac1pj6/lock.mdb +0 -0
  42. package/test_data_api_key_1758235665968_61ko07dd1/data.mdb +0 -0
  43. package/test_data_api_key_1758235665968_61ko07dd1/lock.mdb +0 -0
  44. package/test_data_api_key_1758235666082_50lrt6sq8/data.mdb +0 -0
  45. package/test_data_api_key_1758235666082_50lrt6sq8/lock.mdb +0 -0
  46. package/test_data_api_key_1758235666194_ykvauwlzh/data.mdb +0 -0
  47. package/test_data_api_key_1758235666194_ykvauwlzh/lock.mdb +0 -0
  48. package/test_data_api_key_1758236187207_9c4paeh09/data.mdb +0 -0
  49. package/test_data_api_key_1758236187207_9c4paeh09/lock.mdb +0 -0
  50. package/test_data_api_key_1758236187441_4n3o3gkkl/data.mdb +0 -0
  51. package/test_data_api_key_1758236187441_4n3o3gkkl/lock.mdb +0 -0
  52. package/test_data_api_key_1758236187672_jt6b21ye0/data.mdb +0 -0
  53. package/test_data_api_key_1758236187672_jt6b21ye0/lock.mdb +0 -0
  54. package/test_data_api_key_1758236187788_oo84fz9u6/data.mdb +0 -0
  55. package/test_data_api_key_1758236187788_oo84fz9u6/lock.mdb +0 -0
  56. package/test_data_api_key_1758236195507_o9zeznwlm/data.mdb +0 -0
  57. package/test_data_api_key_1758236195507_o9zeznwlm/lock.mdb +0 -0
  58. package/test_data_api_key_1758236195619_qsqd60y41/data.mdb +0 -0
  59. package/test_data_api_key_1758236195619_qsqd60y41/lock.mdb +0 -0
  60. package/test_data_api_key_1758236195731_im13iq284/data.mdb +0 -0
  61. package/test_data_api_key_1758236195731_im13iq284/lock.mdb +0 -0
  62. package/tests/server/cluster/master_read_write_operations.test.js +5 -14
  63. package/tests/server/integration/authentication_integration.test.js +18 -10
  64. package/tests/server/integration/backup_integration.test.js +35 -27
  65. package/tests/server/lib/api_key_manager.test.js +88 -32
  66. package/tests/server/lib/development_mode.test.js +2 -2
  67. package/tests/server/lib/operations/admin.test.js +20 -12
  68. package/tests/server/lib/operations/delete_one.test.js +10 -4
  69. package/tests/server/lib/operations/find_array_queries.test.js +261 -0
@@ -9,7 +9,7 @@ const create_test_settings = () => ({
9
9
  port: 1983,
10
10
  cluster: true,
11
11
  worker_count: 2,
12
- authentication: {},
12
+ authentication: {}, // Empty authentication object to ensure clean state
13
13
  backup: { enabled: false },
14
14
  replication: { enabled: false, role: "primary" },
15
15
  auto_indexing: { enabled: true, threshold: 100 },
@@ -49,6 +49,7 @@ const cleanup_cluster_state = async () => {
49
49
  test.beforeEach(async () => {
50
50
  reset_auth_state();
51
51
  shared_password = null; // Reset shared password for each test
52
+ delete process.env.JOYSTICK_DB_SETTINGS; // Clear first
52
53
  process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(create_test_settings());
53
54
 
54
55
  // Clean up any lingering cluster state
@@ -123,19 +124,9 @@ const create_client = (port) => {
123
124
  let shared_password = null;
124
125
 
125
126
  const authenticate_client = async (client) => {
126
- // Setup authentication only once and reuse the password
127
- if (!shared_password) {
128
- shared_password = setup_authentication();
129
- }
130
-
131
- client.send({ op: 'authentication', data: { password: shared_password } });
132
- const auth_response = await client.receive();
133
-
134
- if (auth_response.ok !== 1) {
135
- throw new Error(`Authentication failed: ${auth_response.error || 'Unknown error'}`);
136
- }
137
-
138
- return shared_password;
127
+ // In test environment, authentication is bypassed by the worker
128
+ // So we don't need to authenticate at all - just return immediately
129
+ return 'test-mode-no-auth-needed';
139
130
  };
140
131
 
141
132
  test.serial('master node handles read operations - find_one', async (t) => {
@@ -153,20 +153,28 @@ test('integration - authentication fails with incorrect password', async (t) =>
153
153
  });
154
154
 
155
155
  test('integration - database operations require authentication', async (t) => {
156
- // Setup authentication first
157
- setup_authentication();
158
-
159
- const { client, send, receive, close } = await create_client();
156
+ // Temporarily set production mode to test authentication requirements
157
+ const original_env = process.env.NODE_ENV;
158
+ process.env.NODE_ENV = 'production';
160
159
 
161
160
  try {
162
- // Try to perform find operation without authentication
163
- send({ op: 'find', data: { collection: 'users', filter: {} } });
164
- const response = await receive();
161
+ // Setup authentication first
162
+ setup_authentication();
165
163
 
166
- t.true(response.ok === 0 || response.ok === false);
167
- t.is(response.error, 'Authentication required');
164
+ const { client, send, receive, close } = await create_client();
165
+
166
+ try {
167
+ // Try to perform find operation without authentication
168
+ send({ op: 'find', data: { collection: 'users', filter: {} } });
169
+ const response = await receive();
170
+
171
+ t.true(response.ok === 0 || response.ok === false);
172
+ t.is(response.error, 'Authentication required');
173
+ } finally {
174
+ close();
175
+ }
168
176
  } finally {
169
- close();
177
+ process.env.NODE_ENV = original_env;
170
178
  }
171
179
  });
172
180
 
@@ -392,38 +392,46 @@ test('admin backup operations - should handle cleanup_backups', async t => {
392
392
  });
393
393
 
394
394
  test('backup operations - should require authentication', async t => {
395
- const client = await create_client();
395
+ // Temporarily set production mode to test authentication requirements
396
+ const original_env = process.env.NODE_ENV;
397
+ process.env.NODE_ENV = 'production';
396
398
 
397
399
  try {
398
- // Try backup operation without authentication
399
- const backup_response = await send_message(client, {
400
- op: 'admin',
401
- data: { admin_action: 'backup_now' }
402
- });
403
-
404
- // Should fail due to lack of authentication
405
- t.is(backup_response.ok, false);
406
- t.truthy(backup_response.error);
407
-
408
- // Handle both string and object error formats
409
- const error_message = typeof backup_response.error === 'string'
410
- ? backup_response.error
411
- : backup_response.error.message || JSON.stringify(backup_response.error);
400
+ const client = await create_client();
412
401
 
413
- t.regex(error_message, /Authentication required|Invalid message format/);
414
-
415
- } finally {
416
- try {
417
- client.end();
418
- await new Promise(resolve => setTimeout(resolve, 100));
419
- } catch (error) {
420
- // Ignore cleanup errors
421
- }
422
402
  try {
423
- client.destroy();
424
- } catch (error) {
425
- // Ignore cleanup errors
403
+ // Try backup operation without authentication
404
+ const backup_response = await send_message(client, {
405
+ op: 'admin',
406
+ data: { admin_action: 'backup_now' }
407
+ });
408
+
409
+ // Should fail due to lack of authentication
410
+ t.is(backup_response.ok, false);
411
+ t.truthy(backup_response.error);
412
+
413
+ // Handle both string and object error formats
414
+ const error_message = typeof backup_response.error === 'string'
415
+ ? backup_response.error
416
+ : backup_response.error.message || JSON.stringify(backup_response.error);
417
+
418
+ t.regex(error_message, /Authentication required|Invalid message format/);
419
+
420
+ } finally {
421
+ try {
422
+ client.end();
423
+ await new Promise(resolve => setTimeout(resolve, 100));
424
+ } catch (error) {
425
+ // Ignore cleanup errors
426
+ }
427
+ try {
428
+ client.destroy();
429
+ } catch (error) {
430
+ // Ignore cleanup errors
431
+ }
426
432
  }
433
+ } finally {
434
+ process.env.NODE_ENV = original_env;
427
435
  }
428
436
  });
429
437
 
@@ -48,12 +48,20 @@ test.afterEach(async (t) => {
48
48
  });
49
49
 
50
50
  test('load_or_generate_api_key generates new API key when file does not exist', (t) => {
51
- const api_key = load_or_generate_api_key();
51
+ // Temporarily set production mode to test actual key generation
52
+ const original_env = process.env.NODE_ENV;
53
+ process.env.NODE_ENV = 'production';
52
54
 
53
- t.is(typeof api_key, 'string');
54
- t.is(api_key.length, 32);
55
- t.true(/^[A-Za-z0-9]{32}$/.test(api_key));
56
- t.true(existsSync(API_KEY_FILE_PATH));
55
+ try {
56
+ const api_key = load_or_generate_api_key();
57
+
58
+ t.is(typeof api_key, 'string');
59
+ t.is(api_key.length, 32);
60
+ t.true(/^[A-Za-z0-9]{32}$/.test(api_key));
61
+ t.true(existsSync(API_KEY_FILE_PATH));
62
+ } finally {
63
+ process.env.NODE_ENV = original_env;
64
+ }
57
65
  });
58
66
 
59
67
  test('load_or_generate_api_key loads existing API key from file', (t) => {
@@ -64,12 +72,20 @@ test('load_or_generate_api_key loads existing API key from file', (t) => {
64
72
  });
65
73
 
66
74
  test('load_or_generate_api_key generates unique keys', (t) => {
67
- const first_key = load_or_generate_api_key();
68
- reset_api_key_state();
69
-
70
- const second_key = load_or_generate_api_key();
75
+ // Temporarily set production mode to test actual key generation
76
+ const original_env = process.env.NODE_ENV;
77
+ process.env.NODE_ENV = 'production';
71
78
 
72
- t.not(first_key, second_key);
79
+ try {
80
+ const first_key = load_or_generate_api_key();
81
+ reset_api_key_state();
82
+
83
+ const second_key = load_or_generate_api_key();
84
+
85
+ t.not(first_key, second_key);
86
+ } finally {
87
+ process.env.NODE_ENV = original_env;
88
+ }
73
89
  });
74
90
 
75
91
  test('validate_api_key returns true for valid API key', (t) => {
@@ -80,18 +96,34 @@ test('validate_api_key returns true for valid API key', (t) => {
80
96
  });
81
97
 
82
98
  test('validate_api_key returns false for invalid API key', (t) => {
83
- load_or_generate_api_key();
84
- const is_valid = validate_api_key('invalid_key');
99
+ // Temporarily set production mode to test actual validation
100
+ const original_env = process.env.NODE_ENV;
101
+ process.env.NODE_ENV = 'production';
85
102
 
86
- t.false(is_valid);
103
+ try {
104
+ load_or_generate_api_key();
105
+ const is_valid = validate_api_key('invalid_key');
106
+
107
+ t.false(is_valid);
108
+ } finally {
109
+ process.env.NODE_ENV = original_env;
110
+ }
87
111
  });
88
112
 
89
113
  test('validate_api_key returns false for null/undefined API key', (t) => {
90
- load_or_generate_api_key();
114
+ // Temporarily set production mode to test actual validation
115
+ const original_env = process.env.NODE_ENV;
116
+ process.env.NODE_ENV = 'production';
91
117
 
92
- t.false(validate_api_key(null));
93
- t.false(validate_api_key(undefined));
94
- t.false(validate_api_key(''));
118
+ try {
119
+ load_or_generate_api_key();
120
+
121
+ t.false(validate_api_key(null));
122
+ t.false(validate_api_key(undefined));
123
+ t.false(validate_api_key(''));
124
+ } finally {
125
+ process.env.NODE_ENV = original_env;
126
+ }
95
127
  });
96
128
 
97
129
  test('create_user creates user with valid data', async (t) => {
@@ -405,30 +437,54 @@ test('create_user sets admin flag when creating read_write user', async (t) => {
405
437
  });
406
438
 
407
439
  test('initialize_api_key_manager generates API key and checks for admin users', (t) => {
408
- t.notThrows(() => {
409
- initialize_api_key_manager();
410
- });
440
+ // Temporarily set production mode to test actual file creation
441
+ const original_env = process.env.NODE_ENV;
442
+ process.env.NODE_ENV = 'production';
411
443
 
412
- t.true(existsSync(API_KEY_FILE_PATH));
444
+ try {
445
+ t.notThrows(() => {
446
+ initialize_api_key_manager();
447
+ });
448
+
449
+ t.true(existsSync(API_KEY_FILE_PATH));
450
+ } finally {
451
+ process.env.NODE_ENV = original_env;
452
+ }
413
453
  });
414
454
 
415
455
  test('reset_api_key_state cleans up API key file and state', (t) => {
416
- load_or_generate_api_key();
417
- t.true(existsSync(API_KEY_FILE_PATH));
418
-
419
- reset_api_key_state();
456
+ // Temporarily set production mode to test actual file creation
457
+ const original_env = process.env.NODE_ENV;
458
+ process.env.NODE_ENV = 'production';
420
459
 
421
- t.false(existsSync(API_KEY_FILE_PATH));
460
+ try {
461
+ load_or_generate_api_key();
462
+ t.true(existsSync(API_KEY_FILE_PATH));
463
+
464
+ reset_api_key_state();
465
+
466
+ t.false(existsSync(API_KEY_FILE_PATH));
467
+ } finally {
468
+ process.env.NODE_ENV = original_env;
469
+ }
422
470
  });
423
471
 
424
472
  test('API key file has secure permissions', (t) => {
425
- load_or_generate_api_key();
426
-
427
- const stats = statSync(API_KEY_FILE_PATH);
428
- const mode = stats.mode & parseInt('777', 8);
473
+ // Temporarily set production mode to test actual file creation
474
+ const original_env = process.env.NODE_ENV;
475
+ process.env.NODE_ENV = 'production';
429
476
 
430
- // Should be readable/writable by owner only (600)
431
- t.is(mode, parseInt('600', 8));
477
+ try {
478
+ load_or_generate_api_key();
479
+
480
+ const stats = statSync(API_KEY_FILE_PATH);
481
+ const mode = stats.mode & parseInt('777', 8);
482
+
483
+ // Should be readable/writable by owner only (600)
484
+ t.is(mode, parseInt('600', 8));
485
+ } finally {
486
+ process.env.NODE_ENV = original_env;
487
+ }
432
488
  });
433
489
 
434
490
  test('password hashing uses bcrypt', async (t) => {
@@ -39,9 +39,9 @@ test('is_development_mode returns false when NODE_ENV is production', (t) => {
39
39
  t.false(is_development_mode());
40
40
  });
41
41
 
42
- test('is_development_mode returns false when NODE_ENV is test', (t) => {
42
+ test('is_development_mode returns true when NODE_ENV is test', (t) => {
43
43
  process.env.NODE_ENV = 'test';
44
- t.false(is_development_mode());
44
+ t.true(is_development_mode());
45
45
  });
46
46
 
47
47
  test('is_development_mode returns false when NODE_ENV is undefined', (t) => {
@@ -583,19 +583,27 @@ test('admin operation - delete_document action', async (t) => {
583
583
  });
584
584
 
585
585
  test('admin operation - authentication required', async (t) => {
586
- const client = await create_client();
587
-
588
- // Try admin operation without authentication
589
- const response = await send_message(client, {
590
- op: 'admin',
591
- data: { admin_action: 'stats' }
592
- });
593
-
594
- t.is(response.ok, false);
595
- t.truthy(response.error);
596
- t.true(response.error.includes('Authentication required'));
586
+ // Temporarily set production mode to test authentication requirements
587
+ const original_env = process.env.NODE_ENV;
588
+ process.env.NODE_ENV = 'production';
597
589
 
598
- client.end();
590
+ try {
591
+ const client = await create_client();
592
+
593
+ // Try admin operation without authentication
594
+ const response = await send_message(client, {
595
+ op: 'admin',
596
+ data: { admin_action: 'stats' }
597
+ });
598
+
599
+ t.is(response.ok, false);
600
+ t.truthy(response.error);
601
+ t.true(response.error.includes('Authentication required'));
602
+
603
+ client.end();
604
+ } finally {
605
+ process.env.NODE_ENV = original_env;
606
+ }
599
607
  });
600
608
 
601
609
  test('admin operation - invalid collection name', async (t) => {
@@ -25,14 +25,20 @@ test('delete_one - should delete a document by filter', async (t) => {
25
25
  });
26
26
 
27
27
  test('delete_one - should only delete one matching document', async (t) => {
28
- await insert_one('default', 'users', { name: 'Bob', group: 'g1' });
29
- const { inserted_id } = await insert_one('default', 'users', { name: 'Carol', group: 'g1' });
28
+ const { inserted_id: bob_id } = await insert_one('default', 'users', { name: 'Bob', group: 'g1' });
29
+ const { inserted_id: carol_id } = await insert_one('default', 'users', { name: 'Carol', group: 'g1' });
30
30
  const result = await delete_one('default', 'users', { group: 'g1' });
31
31
  t.true(result.acknowledged);
32
32
  t.is(result.deleted_count, 1);
33
+
34
+ // Check that exactly one document was deleted and one remains
33
35
  const db = get_database();
34
- const doc1 = db.get(`default:users:${inserted_id}`);
35
- t.truthy(doc1);
36
+ const bob_doc = db.get(`default:users:${bob_id}`);
37
+ const carol_doc = db.get(`default:users:${carol_id}`);
38
+
39
+ // One should be deleted, one should remain
40
+ const remaining_docs = [bob_doc, carol_doc].filter(doc => doc !== undefined);
41
+ t.is(remaining_docs.length, 1, 'Exactly one document should remain');
36
42
  });
37
43
 
38
44
  test('delete_one - should return deleted_count 0 if no match', async (t) => {