@joystick.js/db-canary 0.0.0-canary.2209
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.
- package/.build/getFilesToBuild.js +26 -0
- package/.build/getPlatformSafeFilePath.js +6 -0
- package/.build/getPlatformSafePath.js +6 -0
- package/.build/index.js +88 -0
- package/.build/isWindows.js +3 -0
- package/API_KEY +1 -0
- package/README.md +1821 -0
- package/data/data.mdb +0 -0
- package/data/lock.mdb +0 -0
- package/dist/client/database.js +1 -0
- package/dist/client/index.js +1 -0
- package/dist/server/cluster/index.js +1 -0
- package/dist/server/cluster/master.js +20 -0
- package/dist/server/cluster/worker.js +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/lib/api_key_manager.js +9 -0
- package/dist/server/lib/auth_manager.js +1 -0
- package/dist/server/lib/auto_index_manager.js +1 -0
- package/dist/server/lib/backup_manager.js +1 -0
- package/dist/server/lib/connection_manager.js +1 -0
- package/dist/server/lib/disk_utils.js +2 -0
- package/dist/server/lib/http_server.js +405 -0
- package/dist/server/lib/index_manager.js +1 -0
- package/dist/server/lib/load_settings.js +1 -0
- package/dist/server/lib/logger.js +1 -0
- package/dist/server/lib/op_types.js +1 -0
- package/dist/server/lib/operation_dispatcher.js +1 -0
- package/dist/server/lib/operations/admin.js +1 -0
- package/dist/server/lib/operations/bulk_write.js +1 -0
- package/dist/server/lib/operations/create_index.js +1 -0
- package/dist/server/lib/operations/delete_one.js +1 -0
- package/dist/server/lib/operations/drop_index.js +1 -0
- package/dist/server/lib/operations/find.js +1 -0
- package/dist/server/lib/operations/find_one.js +1 -0
- package/dist/server/lib/operations/get_indexes.js +1 -0
- package/dist/server/lib/operations/insert_one.js +1 -0
- package/dist/server/lib/operations/update_one.js +1 -0
- package/dist/server/lib/performance_monitor.js +1 -0
- package/dist/server/lib/query_engine.js +1 -0
- package/dist/server/lib/recovery_manager.js +1 -0
- package/dist/server/lib/replication_manager.js +1 -0
- package/dist/server/lib/safe_json_parse.js +1 -0
- package/dist/server/lib/send_response.js +1 -0
- package/dist/server/lib/tcp_protocol.js +1 -0
- package/dist/server/lib/write_forwarder.js +1 -0
- package/dist/server/lib/write_queue.js +1 -0
- package/increment_version.js +3 -0
- package/logs/.013e15b54597d05db4b4b53ecc37b10c92a72927-audit.json +20 -0
- package/logs/.02de550a67ea0f5961faa2dfd458a4d06f59ebd1-audit.json +20 -0
- package/logs/.03494ba24eb3c72214b4068a77d54b8993bee651-audit.json +20 -0
- package/logs/.06309ec60b339be1259a7993dd09c732f8907fbc-audit.json +20 -0
- package/logs/.0663a04dcfa17285661e5e1b8cfa51f41523b210-audit.json +20 -0
- package/logs/.0f06e6c4c9b824622729e13927587479e5060391-audit.json +20 -0
- package/logs/.16ccf58682ecb22b3e3ec63f0da1b7fe9be56528-audit.json +20 -0
- package/logs/.1fa1a5d02f496474b1ab473524c65c984146a9ad-audit.json +20 -0
- package/logs/.2223c0ae3bea6f0d62c62b1d319cc8634856abb7-audit.json +20 -0
- package/logs/.23dc79ffda3e083665e6f5993f59397adcbf4a46-audit.json +20 -0
- package/logs/.28104f49b03906b189eefd1cd462cb46c3c0af22-audit.json +20 -0
- package/logs/.29cdbf13808abe6a0ce70ee2f2efdd680ce3fd8e-audit.json +20 -0
- package/logs/.2a9889afd071f77f41f5170d08703a0afca866b7-audit.json +20 -0
- package/logs/.2acec3d1940a2bbed487528b703ee5948959a599-audit.json +20 -0
- package/logs/.2fb60ff326338c02bfedbcd0e936444e4a216750-audit.json +20 -0
- package/logs/.318fc7a19530d76a345f030f7cad00dda15300e7-audit.json +20 -0
- package/logs/.3cf27043e19085f908cedc7701e6d013463208ee-audit.json +25 -0
- package/logs/.3d90d785415817fc443402843b7c95f8371adc9b-audit.json +20 -0
- package/logs/.4074bca620375f72966fc52dfd439577727671e5-audit.json +20 -0
- package/logs/.40eecf018417ea80a70ea8ec7a3cc9406bc6334b-audit.json +20 -0
- package/logs/.50e974f1ef7c365fca6a1251b2e2c2252914cb5e-audit.json +20 -0
- package/logs/.52cb7d9e4223cf26ba36006ac26b949a97c7923c-audit.json +20 -0
- package/logs/.54befcdb84c15aad980705a31bcc9f555c3577ab-audit.json +20 -0
- package/logs/.57dfb70e22eddb84db2e3c0ceeefac5c0b9baffa-audit.json +20 -0
- package/logs/.5f0b24705a1eaad4eca4968f2d86f91b3f9be683-audit.json +20 -0
- package/logs/.61ba98fdda7db58576b382fee07904e5db1169d6-audit.json +20 -0
- package/logs/.6235017727ef6b199d569a99d6aa8c8e80a1b475-audit.json +20 -0
- package/logs/.63db16193699219489d218a1ddea5dde3750cae4-audit.json +20 -0
- package/logs/.64fb67dfe14149c9eef728d79bf30a54da809c60-audit.json +20 -0
- package/logs/.669137453368987c1f311b5345342527afb54e50-audit.json +20 -0
- package/logs/.7a71f8c89ea28ae266d356aeff6306e876a30fbb-audit.json +20 -0
- package/logs/.7afbaa90fe9dc3a7d682676f9bb79f9a1b1fd9a6-audit.json +20 -0
- package/logs/.7ca29e322cd05327035de850099e7610864f2347-audit.json +20 -0
- package/logs/.83335ab3347e449dae03455a110aaf7f120d4802-audit.json +20 -0
- package/logs/.8c2487b5fd445d2c8e5c483c80b9fa99bbf1ca58-audit.json +20 -0
- package/logs/.8c8b9dc386922c9f3b4c13251af7052aac1d24c0-audit.json +20 -0
- package/logs/.8d6155d94640c4863301ae0fee5e4e7372a21446-audit.json +20 -0
- package/logs/.944a3119a243deea7c8270d5d9e582bb1d0eaa10-audit.json +20 -0
- package/logs/.9816a845c30fb2909f3b26a23eeb3538ebcad5db-audit.json +20 -0
- package/logs/.9dc08784e38b865488177c26d4af5934555e0323-audit.json +20 -0
- package/logs/.9dd27d2e0e454ac0a37600206d1cac5493b0d7ee-audit.json +20 -0
- package/logs/.a3d486feeac7654c59b547de96600e8849a06d4f-audit.json +20 -0
- package/logs/.a5b811f4def22250f86cc18870d7c4573625df22-audit.json +20 -0
- package/logs/.a61648eb5f830e0b6f508ac35e4f8f629d2ad4c7-audit.json +20 -0
- package/logs/.a89016d507045771b4b5a65656944a9c0f1e528b-audit.json +20 -0
- package/logs/.a99bee160a1c590be959af46bacc02724803f691-audit.json +20 -0
- package/logs/.ada7906d6243fd7da802f03d86c4ae5dd9df6236-audit.json +20 -0
- package/logs/.b518339ee942143b6af983af167f5bbb6983b4de-audit.json +20 -0
- package/logs/.b51b124b166d53c9519017856ea610d61d65fabe-audit.json +20 -0
- package/logs/.b7a6aee19f58e55633d5e4a3709041c47dfff975-audit.json +20 -0
- package/logs/.bd7a8a6ba9c55d557a4867ab53f02e3ec2e1553d-audit.json +20 -0
- package/logs/.c1435dafe453b169d6392b25065f3cf4ab6fbb21-audit.json +20 -0
- package/logs/.c17e1ce043109f77dc2f0e2aa290a9d1ed842c03-audit.json +20 -0
- package/logs/.ca62637ce9540e5a38a2fbedb2115febb6ad308a-audit.json +15 -0
- package/logs/.ccee67b9c176967f8977071409a41f5cb5cd6ad4-audit.json +20 -0
- package/logs/.db24043417ea79a6f14cd947476399e53930b48d-audit.json +20 -0
- package/logs/.e0f12acccb57829f5f33712bb2e2607ecd808147-audit.json +20 -0
- package/logs/.e9b6cc33d0bbd2e644c4e2bf44d177f850016557-audit.json +20 -0
- package/logs/.f15291d434808e3bdca7963ccd2e73893be027e6-audit.json +20 -0
- package/logs/.f4bdf9e21ef84f8a3fae3ffb32bbc39275991351-audit.json +15 -0
- package/logs/.fbac3aefac1e81b4230df5aa50667cb90d51024f-audit.json +20 -0
- package/logs/.fcfd495c0a9169db243f4a4f21878ee02b76413c-audit.json +20 -0
- package/logs/admin-2025-09-12.log +580 -0
- package/logs/admin-2025-09-15.log +283 -0
- package/logs/admin-error-2025-09-12.log +22 -0
- package/logs/admin-error-2025-09-15.log +10 -0
- package/logs/api_key_manager-2025-09-12.log +658 -0
- package/logs/api_key_manager-2025-09-15.log +295 -0
- package/logs/api_key_manager-error-2025-09-12.log +0 -0
- package/logs/api_key_manager-error-2025-09-15.log +0 -0
- package/logs/auth_manager-2025-09-12.log +4432 -0
- package/logs/auth_manager-2025-09-15.log +2000 -0
- package/logs/auth_manager-error-2025-09-12.log +11 -0
- package/logs/auth_manager-error-2025-09-15.log +5 -0
- package/logs/auto_index_manager-2025-09-12.log +84 -0
- package/logs/auto_index_manager-2025-09-15.log +45 -0
- package/logs/auto_index_manager-error-2025-09-12.log +6 -0
- package/logs/auto_index_manager-error-2025-09-15.log +0 -0
- package/logs/backup_manager-2025-09-12.log +198 -0
- package/logs/backup_manager-2025-09-15.log +90 -0
- package/logs/backup_manager-error-2025-09-12.log +198 -0
- package/logs/backup_manager-error-2025-09-15.log +90 -0
- package/logs/bulk_write-2025-09-12.log +66 -0
- package/logs/bulk_write-2025-09-15.log +38 -0
- package/logs/bulk_write-error-2025-09-12.log +0 -0
- package/logs/bulk_write-error-2025-09-15.log +0 -0
- package/logs/connection_manager-2025-09-12.log +2412 -0
- package/logs/connection_manager-2025-09-15.log +1132 -0
- package/logs/connection_manager-error-2025-09-12.log +0 -0
- package/logs/connection_manager-error-2025-09-15.log +0 -0
- package/logs/create_index-2025-09-12.log +302 -0
- package/logs/create_index-2025-09-15.log +158 -0
- package/logs/create_index-error-2025-09-12.log +30 -0
- package/logs/create_index-error-2025-09-15.log +13 -0
- package/logs/delete_one-2025-09-12.log +73 -0
- package/logs/delete_one-2025-09-15.log +43 -0
- package/logs/delete_one-error-2025-09-12.log +0 -0
- package/logs/delete_one-error-2025-09-15.log +0 -0
- package/logs/disk_utils-2025-09-12.log +4954 -0
- package/logs/disk_utils-2025-09-15.log +2446 -0
- package/logs/disk_utils-error-2025-09-12.log +0 -0
- package/logs/disk_utils-error-2025-09-15.log +0 -0
- package/logs/drop_index-2025-09-12.log +41 -0
- package/logs/drop_index-2025-09-15.log +23 -0
- package/logs/drop_index-error-2025-09-12.log +11 -0
- package/logs/drop_index-error-2025-09-15.log +5 -0
- package/logs/find-2025-09-12.log +1050 -0
- package/logs/find-2025-09-15.log +592 -0
- package/logs/find-error-2025-09-12.log +1 -0
- package/logs/find-error-2025-09-15.log +0 -0
- package/logs/find_one-2025-09-12.log +425 -0
- package/logs/find_one-2025-09-15.log +264 -0
- package/logs/find_one-error-2025-09-12.log +5 -0
- package/logs/find_one-error-2025-09-15.log +0 -0
- package/logs/get_indexes-2025-09-12.log +84 -0
- package/logs/get_indexes-2025-09-15.log +56 -0
- package/logs/get_indexes-error-2025-09-12.log +6 -0
- package/logs/get_indexes-error-2025-09-15.log +0 -0
- package/logs/http_server-2025-09-12.log +2772 -0
- package/logs/http_server-2025-09-15.log +1276 -0
- package/logs/http_server-error-2025-09-12.log +212 -0
- package/logs/http_server-error-2025-09-15.log +44 -0
- package/logs/index_manager-2025-09-12.log +5031 -0
- package/logs/index_manager-2025-09-15.log +2909 -0
- package/logs/index_manager-error-2025-09-12.log +80 -0
- package/logs/index_manager-error-2025-09-15.log +38 -0
- package/logs/insert_one-2025-09-12.log +2181 -0
- package/logs/insert_one-2025-09-15.log +1293 -0
- package/logs/insert_one-error-2025-09-12.log +0 -0
- package/logs/insert_one-error-2025-09-15.log +0 -0
- package/logs/master-2025-09-12.log +1882 -0
- package/logs/master-2025-09-15.log +910 -0
- package/logs/master-error-2025-09-12.log +80 -0
- package/logs/master-error-2025-09-15.log +0 -0
- package/logs/operation_dispatcher-2025-09-12.log +751 -0
- package/logs/operation_dispatcher-2025-09-15.log +359 -0
- package/logs/operation_dispatcher-error-2025-09-12.log +33 -0
- package/logs/operation_dispatcher-error-2025-09-15.log +11 -0
- package/logs/performance_monitor-2025-09-12.log +14889 -0
- package/logs/performance_monitor-2025-09-15.log +6803 -0
- package/logs/performance_monitor-error-2025-09-12.log +0 -0
- package/logs/performance_monitor-error-2025-09-15.log +0 -0
- package/logs/query_engine-2025-09-12.log +5310 -0
- package/logs/query_engine-2025-09-15.log +2639 -0
- package/logs/query_engine-error-2025-09-12.log +0 -0
- package/logs/query_engine-error-2025-09-15.log +0 -0
- package/logs/recovery_manager-2025-09-12.log +462 -0
- package/logs/recovery_manager-2025-09-15.log +210 -0
- package/logs/recovery_manager-error-2025-09-12.log +22 -0
- package/logs/recovery_manager-error-2025-09-15.log +10 -0
- package/logs/replication-2025-09-12.log +1923 -0
- package/logs/replication-2025-09-15.log +917 -0
- package/logs/replication-error-2025-09-12.log +33 -0
- package/logs/replication-error-2025-09-15.log +15 -0
- package/logs/server-2025-09-12.log +2601 -0
- package/logs/server-2025-09-15.log +1191 -0
- package/logs/server-error-2025-09-12.log +0 -0
- package/logs/server-error-2025-09-15.log +0 -0
- package/logs/tcp_protocol-2025-09-12.log +22 -0
- package/logs/tcp_protocol-2025-09-15.log +10 -0
- package/logs/tcp_protocol-error-2025-09-12.log +22 -0
- package/logs/tcp_protocol-error-2025-09-15.log +10 -0
- package/logs/test-2025-09-12.log +0 -0
- package/logs/test-2025-09-15.log +0 -0
- package/logs/test-error-2025-09-12.log +0 -0
- package/logs/test-error-2025-09-15.log +0 -0
- package/logs/update_one-2025-09-12.log +173 -0
- package/logs/update_one-2025-09-15.log +118 -0
- package/logs/update_one-error-2025-09-12.log +0 -0
- package/logs/update_one-error-2025-09-15.log +0 -0
- package/logs/worker-2025-09-12.log +1457 -0
- package/logs/worker-2025-09-15.log +695 -0
- package/logs/worker-error-2025-09-12.log +0 -0
- package/logs/worker-error-2025-09-15.log +0 -0
- package/logs/write_forwarder-2025-09-12.log +1956 -0
- package/logs/write_forwarder-2025-09-15.log +932 -0
- package/logs/write_forwarder-error-2025-09-12.log +66 -0
- package/logs/write_forwarder-error-2025-09-15.log +30 -0
- package/logs/write_queue-2025-09-12.log +612 -0
- package/logs/write_queue-2025-09-15.log +301 -0
- package/logs/write_queue-error-2025-09-12.log +184 -0
- package/logs/write_queue-error-2025-09-15.log +83 -0
- package/package.json +48 -0
- package/prompts/01-core-infrastructure.md +56 -0
- package/prompts/02-secondary-indexing.md +65 -0
- package/prompts/03-write-serialization.md +63 -0
- package/prompts/04-enhanced-authentication.md +75 -0
- package/prompts/05-comprehensive-admin-operations.md +75 -0
- package/prompts/06-backup-and-restore-system.md +106 -0
- package/prompts/07-production-safety-features.md +107 -0
- package/prompts/08-tcp-client-library.md +121 -0
- package/prompts/09-api-method-chaining.md +134 -0
- package/prompts/10-automatic-index-creation.md +223 -0
- package/prompts/11-operation-naming-consistency.md +268 -0
- package/prompts/12-tcp-replication-system.md +333 -0
- package/prompts/13-master-read-write-operations.md +57 -0
- package/prompts/14-index-upsert-operations.md +68 -0
- package/prompts/15-client-api-return-types.md +81 -0
- package/prompts/16-server-setup-ui.md +97 -0
- package/prompts/17-emergency-password-change.md +108 -0
- package/prompts/18-joystick-framework-integration.md +116 -0
- package/prompts/19-api-key-authentication-system.md +137 -0
- package/prompts/20-configurable-server-port.md +105 -0
- package/prompts/21-multi-database-support.md +161 -0
- package/prompts/FULL_TEXT_SEARCH.md +293 -0
- package/prompts/PROMPTS.md +158 -0
- package/prompts/README.md +221 -0
- package/prompts/TYPESCRIPT_GENERATION.md +179 -0
- package/src/client/database.js +166 -0
- package/src/client/index.js +752 -0
- package/src/server/cluster/index.js +53 -0
- package/src/server/cluster/master.js +774 -0
- package/src/server/cluster/worker.js +537 -0
- package/src/server/index.js +540 -0
- package/src/server/lib/api_key_manager.js +473 -0
- package/src/server/lib/auth_manager.js +375 -0
- package/src/server/lib/auto_index_manager.js +681 -0
- package/src/server/lib/backup_manager.js +650 -0
- package/src/server/lib/connection_manager.js +218 -0
- package/src/server/lib/disk_utils.js +118 -0
- package/src/server/lib/http_server.js +1165 -0
- package/src/server/lib/index_manager.js +756 -0
- package/src/server/lib/load_settings.js +143 -0
- package/src/server/lib/logger.js +135 -0
- package/src/server/lib/op_types.js +29 -0
- package/src/server/lib/operation_dispatcher.js +268 -0
- package/src/server/lib/operations/admin.js +808 -0
- package/src/server/lib/operations/bulk_write.js +367 -0
- package/src/server/lib/operations/create_index.js +68 -0
- package/src/server/lib/operations/delete_one.js +114 -0
- package/src/server/lib/operations/drop_index.js +58 -0
- package/src/server/lib/operations/find.js +340 -0
- package/src/server/lib/operations/find_one.js +319 -0
- package/src/server/lib/operations/get_indexes.js +52 -0
- package/src/server/lib/operations/insert_one.js +113 -0
- package/src/server/lib/operations/update_one.js +225 -0
- package/src/server/lib/performance_monitor.js +313 -0
- package/src/server/lib/query_engine.js +243 -0
- package/src/server/lib/recovery_manager.js +388 -0
- package/src/server/lib/replication_manager.js +727 -0
- package/src/server/lib/safe_json_parse.js +21 -0
- package/src/server/lib/send_response.js +47 -0
- package/src/server/lib/tcp_protocol.js +130 -0
- package/src/server/lib/write_forwarder.js +636 -0
- package/src/server/lib/write_queue.js +335 -0
- package/test_data/data.mdb +0 -0
- package/test_data/lock.mdb +0 -0
- package/tests/client/index.test.js +1232 -0
- package/tests/server/cluster/cluster.test.js +248 -0
- package/tests/server/cluster/master_read_write_operations.test.js +577 -0
- package/tests/server/index.test.js +651 -0
- package/tests/server/integration/authentication_integration.test.js +294 -0
- package/tests/server/integration/auto_indexing_integration.test.js +268 -0
- package/tests/server/integration/backup_integration.test.js +513 -0
- package/tests/server/integration/indexing_integration.test.js +126 -0
- package/tests/server/integration/production_safety_integration.test.js +358 -0
- package/tests/server/integration/replication_integration.test.js +227 -0
- package/tests/server/integration/write_serialization_integration.test.js +246 -0
- package/tests/server/lib/api_key_manager.test.js +516 -0
- package/tests/server/lib/auth_manager.test.js +317 -0
- package/tests/server/lib/auto_index_manager.test.js +275 -0
- package/tests/server/lib/backup_manager.test.js +238 -0
- package/tests/server/lib/connection_manager.test.js +221 -0
- package/tests/server/lib/disk_utils.test.js +63 -0
- package/tests/server/lib/http_server.test.js +389 -0
- package/tests/server/lib/index_manager.test.js +301 -0
- package/tests/server/lib/load_settings.test.js +107 -0
- package/tests/server/lib/load_settings_port_config.test.js +243 -0
- package/tests/server/lib/logger.test.js +282 -0
- package/tests/server/lib/operations/admin.test.js +638 -0
- package/tests/server/lib/operations/bulk_write.test.js +128 -0
- package/tests/server/lib/operations/create_index.test.js +138 -0
- package/tests/server/lib/operations/delete_one.test.js +52 -0
- package/tests/server/lib/operations/drop_index.test.js +72 -0
- package/tests/server/lib/operations/find.test.js +93 -0
- package/tests/server/lib/operations/find_one.test.js +91 -0
- package/tests/server/lib/operations/get_indexes.test.js +87 -0
- package/tests/server/lib/operations/insert_one.test.js +42 -0
- package/tests/server/lib/operations/update_one.test.js +89 -0
- package/tests/server/lib/performance_monitor.test.js +185 -0
- package/tests/server/lib/query_engine.test.js +46 -0
- package/tests/server/lib/recovery_manager.test.js +414 -0
- package/tests/server/lib/replication_manager.test.js +202 -0
- package/tests/server/lib/safe_json_parse.test.js +45 -0
- package/tests/server/lib/send_response.test.js +155 -0
- package/tests/server/lib/tcp_protocol.test.js +169 -0
- package/tests/server/lib/write_forwarder.test.js +258 -0
- package/tests/server/lib/write_queue.test.js +255 -0
- package/tsconfig.json +30 -0
- package/types/client/index.d.ts +447 -0
- package/types/server/cluster/index.d.ts +28 -0
- package/types/server/cluster/master.d.ts +115 -0
- package/types/server/cluster/worker.d.ts +1 -0
- package/types/server/lib/auth_manager.d.ts +13 -0
- package/types/server/lib/backup_manager.d.ts +43 -0
- package/types/server/lib/connection_manager.d.ts +15 -0
- package/types/server/lib/disk_utils.d.ts +3 -0
- package/types/server/lib/index_manager.d.ts +24 -0
- package/types/server/lib/load_settings.d.ts +4 -0
- package/types/server/lib/logger.d.ts +44 -0
- package/types/server/lib/op_types.d.ts +6 -0
- package/types/server/lib/performance_monitor.d.ts +68 -0
- package/types/server/lib/query_engine.d.ts +10 -0
- package/types/server/lib/safe_json_parse.d.ts +7 -0
- package/types/server/lib/send_response.d.ts +3 -0
- package/types/server/lib/tcp_protocol.d.ts +12 -0
- package/types/server/lib/write_queue.d.ts +2 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import bcrypt from 'bcrypt';
|
|
4
|
+
import {
|
|
5
|
+
create_recovery_token,
|
|
6
|
+
is_token_valid,
|
|
7
|
+
record_failed_recovery_attempt,
|
|
8
|
+
change_password,
|
|
9
|
+
get_recovery_status,
|
|
10
|
+
initialize_recovery_manager,
|
|
11
|
+
reset_recovery_state
|
|
12
|
+
} from '../../../src/server/lib/recovery_manager.js';
|
|
13
|
+
|
|
14
|
+
const RECOVERY_TOKEN_FILE = './recovery_token.json';
|
|
15
|
+
|
|
16
|
+
test.beforeEach(() => {
|
|
17
|
+
// Clean up any existing files and environment variables
|
|
18
|
+
reset_recovery_state();
|
|
19
|
+
if (fs.existsSync(RECOVERY_TOKEN_FILE)) {
|
|
20
|
+
fs.unlinkSync(RECOVERY_TOKEN_FILE);
|
|
21
|
+
}
|
|
22
|
+
delete process.env.JOYSTICK_DB_SETTINGS;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test.afterEach(() => {
|
|
26
|
+
// Clean up files and environment variables after each test
|
|
27
|
+
reset_recovery_state();
|
|
28
|
+
if (fs.existsSync(RECOVERY_TOKEN_FILE)) {
|
|
29
|
+
fs.unlinkSync(RECOVERY_TOKEN_FILE);
|
|
30
|
+
}
|
|
31
|
+
delete process.env.JOYSTICK_DB_SETTINGS;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('create_recovery_token generates valid token with expiration', (t) => {
|
|
35
|
+
const recovery_info = create_recovery_token();
|
|
36
|
+
|
|
37
|
+
t.true(typeof recovery_info.token === 'string');
|
|
38
|
+
t.true(recovery_info.token.length > 0);
|
|
39
|
+
t.true(typeof recovery_info.expires_at === 'number');
|
|
40
|
+
t.true(recovery_info.expires_at > Date.now());
|
|
41
|
+
t.true(recovery_info.url.includes(recovery_info.token));
|
|
42
|
+
|
|
43
|
+
// Verify token file was created
|
|
44
|
+
t.true(fs.existsSync(RECOVERY_TOKEN_FILE));
|
|
45
|
+
|
|
46
|
+
const token_data = JSON.parse(fs.readFileSync(RECOVERY_TOKEN_FILE, 'utf8'));
|
|
47
|
+
t.is(token_data.token, recovery_info.token);
|
|
48
|
+
t.is(token_data.expires_at, recovery_info.expires_at);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('create_recovery_token sets 10 minute expiration', (t) => {
|
|
52
|
+
const before_creation = Date.now();
|
|
53
|
+
const recovery_info = create_recovery_token();
|
|
54
|
+
const after_creation = Date.now();
|
|
55
|
+
|
|
56
|
+
const expected_min_expiry = before_creation + (10 * 60 * 1000);
|
|
57
|
+
const expected_max_expiry = after_creation + (10 * 60 * 1000);
|
|
58
|
+
|
|
59
|
+
t.true(recovery_info.expires_at >= expected_min_expiry);
|
|
60
|
+
t.true(recovery_info.expires_at <= expected_max_expiry);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('is_token_valid returns true for valid token', (t) => {
|
|
64
|
+
const recovery_info = create_recovery_token();
|
|
65
|
+
const validation = is_token_valid(recovery_info.token);
|
|
66
|
+
|
|
67
|
+
t.true(validation.valid);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('is_token_valid returns false for invalid token', (t) => {
|
|
71
|
+
create_recovery_token();
|
|
72
|
+
const validation = is_token_valid('invalid-token');
|
|
73
|
+
|
|
74
|
+
t.false(validation.valid);
|
|
75
|
+
t.is(validation.reason, 'invalid');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('is_token_valid returns false when no token exists', (t) => {
|
|
79
|
+
const validation = is_token_valid('any-token');
|
|
80
|
+
|
|
81
|
+
t.false(validation.valid);
|
|
82
|
+
t.is(validation.reason, 'no_token');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('is_token_valid returns false for expired token', (t) => {
|
|
86
|
+
const recovery_info = create_recovery_token();
|
|
87
|
+
|
|
88
|
+
// Manually expire the token by modifying the file
|
|
89
|
+
const token_data = JSON.parse(fs.readFileSync(RECOVERY_TOKEN_FILE, 'utf8'));
|
|
90
|
+
token_data.expires_at = Date.now() - 1000; // 1 second ago
|
|
91
|
+
fs.writeFileSync(RECOVERY_TOKEN_FILE, JSON.stringify(token_data, null, 2));
|
|
92
|
+
|
|
93
|
+
// Reload state to pick up the expired token (but don't initialize, which would clean it up)
|
|
94
|
+
// Instead, directly test the validation which will detect expiration and clean up
|
|
95
|
+
const validation = is_token_valid(recovery_info.token);
|
|
96
|
+
|
|
97
|
+
t.false(validation.valid);
|
|
98
|
+
t.is(validation.reason, 'expired');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('record_failed_recovery_attempt increments counter', (t) => {
|
|
102
|
+
create_recovery_token();
|
|
103
|
+
|
|
104
|
+
record_failed_recovery_attempt('192.168.1.100');
|
|
105
|
+
|
|
106
|
+
const status = get_recovery_status();
|
|
107
|
+
t.is(status.failed_attempts, 1);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('record_failed_recovery_attempt locks after max attempts', (t) => {
|
|
111
|
+
create_recovery_token();
|
|
112
|
+
|
|
113
|
+
// Make 3 failed attempts (the maximum)
|
|
114
|
+
for (let i = 0; i < 3; i++) {
|
|
115
|
+
record_failed_recovery_attempt('192.168.1.100');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const status = get_recovery_status();
|
|
119
|
+
t.is(status.failed_attempts, 3);
|
|
120
|
+
t.true(status.is_locked);
|
|
121
|
+
t.true(typeof status.locked_until === 'number');
|
|
122
|
+
t.true(status.locked_until > Date.now());
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('is_token_valid returns locked when recovery is locked', (t) => {
|
|
126
|
+
const recovery_info = create_recovery_token();
|
|
127
|
+
|
|
128
|
+
// Lock the recovery by making too many failed attempts
|
|
129
|
+
for (let i = 0; i < 3; i++) {
|
|
130
|
+
record_failed_recovery_attempt('192.168.1.100');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const validation = is_token_valid(recovery_info.token);
|
|
134
|
+
|
|
135
|
+
t.false(validation.valid);
|
|
136
|
+
t.is(validation.reason, 'locked');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('change_password validates password strength', async (t) => {
|
|
140
|
+
// Create environment variable with authentication
|
|
141
|
+
const settings_data = {
|
|
142
|
+
authentication: {
|
|
143
|
+
password_hash: await bcrypt.hash('old_password', 12),
|
|
144
|
+
created_at: new Date().toISOString(),
|
|
145
|
+
last_updated: new Date().toISOString(),
|
|
146
|
+
failed_attempts: {},
|
|
147
|
+
rate_limits: {}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(settings_data);
|
|
151
|
+
|
|
152
|
+
// Test short password
|
|
153
|
+
const error = await t.throwsAsync(async () => {
|
|
154
|
+
await change_password('short', '192.168.1.100');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
t.is(error.message, 'Password must be at least 12 characters long');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('change_password requires environment variable', async (t) => {
|
|
161
|
+
const error = await t.throwsAsync(async () => {
|
|
162
|
+
await change_password('valid_password_123', '192.168.1.100');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
t.is(error.message, 'JOYSTICK_DB_SETTINGS environment variable not found');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('change_password requires authentication configuration', async (t) => {
|
|
169
|
+
// Create environment variable without authentication
|
|
170
|
+
const settings_data = { port: 1983 };
|
|
171
|
+
process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(settings_data);
|
|
172
|
+
|
|
173
|
+
const error = await t.throwsAsync(async () => {
|
|
174
|
+
await change_password('valid_password_123', '192.168.1.100');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
t.is(error.message, 'Authentication not configured');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('change_password successfully updates password', async (t) => {
|
|
181
|
+
// Create environment variable with authentication
|
|
182
|
+
const old_password = 'old_password_123';
|
|
183
|
+
const new_password = 'new_password_456';
|
|
184
|
+
|
|
185
|
+
const settings_data = {
|
|
186
|
+
authentication: {
|
|
187
|
+
password_hash: await bcrypt.hash(old_password, 12),
|
|
188
|
+
created_at: new Date().toISOString(),
|
|
189
|
+
last_updated: new Date().toISOString(),
|
|
190
|
+
failed_attempts: { '192.168.1.50': [Date.now()] },
|
|
191
|
+
rate_limits: { '192.168.1.50': { expires_at: Date.now() + 60000, attempts: 1 } }
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(settings_data);
|
|
195
|
+
|
|
196
|
+
const result = await change_password(new_password, '192.168.1.100');
|
|
197
|
+
|
|
198
|
+
t.true(result.success);
|
|
199
|
+
t.is(result.message, 'Password changed successfully');
|
|
200
|
+
t.true(typeof result.timestamp === 'string');
|
|
201
|
+
|
|
202
|
+
// Verify password was updated in environment variable
|
|
203
|
+
const updated_settings = JSON.parse(process.env.JOYSTICK_DB_SETTINGS);
|
|
204
|
+
t.true(await bcrypt.compare(new_password, updated_settings.authentication.password_hash));
|
|
205
|
+
t.false(await bcrypt.compare(old_password, updated_settings.authentication.password_hash));
|
|
206
|
+
|
|
207
|
+
// Verify failed attempts and rate limits were reset
|
|
208
|
+
t.deepEqual(updated_settings.authentication.failed_attempts, {});
|
|
209
|
+
t.deepEqual(updated_settings.authentication.rate_limits, {});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('change_password calls connection terminator', async (t) => {
|
|
213
|
+
// Create environment variable with authentication
|
|
214
|
+
const settings_data = {
|
|
215
|
+
authentication: {
|
|
216
|
+
password_hash: await bcrypt.hash('old_password_123', 12),
|
|
217
|
+
created_at: new Date().toISOString(),
|
|
218
|
+
last_updated: new Date().toISOString(),
|
|
219
|
+
failed_attempts: {},
|
|
220
|
+
rate_limits: {}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(settings_data);
|
|
224
|
+
|
|
225
|
+
let terminator_called = false;
|
|
226
|
+
const connection_terminator = () => {
|
|
227
|
+
terminator_called = true;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
await change_password('new_password_456', '192.168.1.100', connection_terminator);
|
|
231
|
+
|
|
232
|
+
t.true(terminator_called);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('change_password cleans up recovery token', async (t) => {
|
|
236
|
+
// Create recovery token
|
|
237
|
+
create_recovery_token();
|
|
238
|
+
t.true(fs.existsSync(RECOVERY_TOKEN_FILE));
|
|
239
|
+
|
|
240
|
+
// Create environment variable with authentication
|
|
241
|
+
const settings_data = {
|
|
242
|
+
authentication: {
|
|
243
|
+
password_hash: await bcrypt.hash('old_password_123', 12),
|
|
244
|
+
created_at: new Date().toISOString(),
|
|
245
|
+
last_updated: new Date().toISOString(),
|
|
246
|
+
failed_attempts: {},
|
|
247
|
+
rate_limits: {}
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(settings_data);
|
|
251
|
+
|
|
252
|
+
await change_password('new_password_456', '192.168.1.100');
|
|
253
|
+
|
|
254
|
+
// Verify recovery token was cleaned up
|
|
255
|
+
t.false(fs.existsSync(RECOVERY_TOKEN_FILE));
|
|
256
|
+
|
|
257
|
+
const status = get_recovery_status();
|
|
258
|
+
t.false(status.token_active);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('get_recovery_status returns correct status', (t) => {
|
|
262
|
+
// Test with no token
|
|
263
|
+
let status = get_recovery_status();
|
|
264
|
+
t.false(status.token_active);
|
|
265
|
+
t.is(status.failed_attempts, 0);
|
|
266
|
+
t.false(status.is_locked);
|
|
267
|
+
|
|
268
|
+
// Test with active token
|
|
269
|
+
create_recovery_token();
|
|
270
|
+
status = get_recovery_status();
|
|
271
|
+
t.true(status.token_active);
|
|
272
|
+
t.true(typeof status.expires_at === 'number');
|
|
273
|
+
|
|
274
|
+
// Test with failed attempts
|
|
275
|
+
record_failed_recovery_attempt('192.168.1.100');
|
|
276
|
+
status = get_recovery_status();
|
|
277
|
+
t.is(status.failed_attempts, 1);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('initialize_recovery_manager loads existing state', (t) => {
|
|
281
|
+
// Create a recovery token file manually
|
|
282
|
+
const token_data = {
|
|
283
|
+
token: 'test-token-123',
|
|
284
|
+
expires_at: Date.now() + 600000, // 10 minutes from now
|
|
285
|
+
failed_attempts: 2,
|
|
286
|
+
locked_until: null
|
|
287
|
+
};
|
|
288
|
+
fs.writeFileSync(RECOVERY_TOKEN_FILE, JSON.stringify(token_data, null, 2));
|
|
289
|
+
|
|
290
|
+
initialize_recovery_manager();
|
|
291
|
+
|
|
292
|
+
const status = get_recovery_status();
|
|
293
|
+
t.true(status.token_active);
|
|
294
|
+
t.is(status.failed_attempts, 2);
|
|
295
|
+
|
|
296
|
+
const validation = is_token_valid('test-token-123');
|
|
297
|
+
t.true(validation.valid);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('initialize_recovery_manager cleans up expired tokens', (t) => {
|
|
301
|
+
// Create an expired recovery token file
|
|
302
|
+
const token_data = {
|
|
303
|
+
token: 'expired-token-123',
|
|
304
|
+
expires_at: Date.now() - 1000, // 1 second ago
|
|
305
|
+
failed_attempts: 1,
|
|
306
|
+
locked_until: null
|
|
307
|
+
};
|
|
308
|
+
fs.writeFileSync(RECOVERY_TOKEN_FILE, JSON.stringify(token_data, null, 2));
|
|
309
|
+
|
|
310
|
+
initialize_recovery_manager();
|
|
311
|
+
|
|
312
|
+
const status = get_recovery_status();
|
|
313
|
+
t.false(status.token_active);
|
|
314
|
+
t.is(status.failed_attempts, 0);
|
|
315
|
+
|
|
316
|
+
// Token file should be cleaned up
|
|
317
|
+
t.false(fs.existsSync(RECOVERY_TOKEN_FILE));
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('initialize_recovery_manager cleans up expired locks', (t) => {
|
|
321
|
+
// Create a recovery state with expired lock
|
|
322
|
+
const token_data = {
|
|
323
|
+
token: 'test-token-123',
|
|
324
|
+
expires_at: Date.now() + 600000, // 10 minutes from now
|
|
325
|
+
failed_attempts: 3,
|
|
326
|
+
locked_until: Date.now() - 1000 // 1 second ago (expired)
|
|
327
|
+
};
|
|
328
|
+
fs.writeFileSync(RECOVERY_TOKEN_FILE, JSON.stringify(token_data, null, 2));
|
|
329
|
+
|
|
330
|
+
initialize_recovery_manager();
|
|
331
|
+
|
|
332
|
+
const status = get_recovery_status();
|
|
333
|
+
t.false(status.is_locked);
|
|
334
|
+
t.is(status.failed_attempts, 0); // Should be reset when lock expires
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('reset_recovery_state cleans up everything', (t) => {
|
|
338
|
+
// Create recovery token and make some failed attempts
|
|
339
|
+
create_recovery_token();
|
|
340
|
+
record_failed_recovery_attempt('192.168.1.100');
|
|
341
|
+
|
|
342
|
+
t.true(fs.existsSync(RECOVERY_TOKEN_FILE));
|
|
343
|
+
|
|
344
|
+
reset_recovery_state();
|
|
345
|
+
|
|
346
|
+
t.false(fs.existsSync(RECOVERY_TOKEN_FILE));
|
|
347
|
+
|
|
348
|
+
const status = get_recovery_status();
|
|
349
|
+
t.false(status.token_active);
|
|
350
|
+
t.is(status.failed_attempts, 0);
|
|
351
|
+
t.false(status.is_locked);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('recovery token file has secure permissions', (t) => {
|
|
355
|
+
create_recovery_token();
|
|
356
|
+
|
|
357
|
+
const stats = fs.statSync(RECOVERY_TOKEN_FILE);
|
|
358
|
+
const mode = stats.mode & parseInt('777', 8);
|
|
359
|
+
|
|
360
|
+
// Should have 600 permissions (owner read/write only)
|
|
361
|
+
t.is(mode, parseInt('600', 8));
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test('multiple recovery tokens not allowed', (t) => {
|
|
365
|
+
const first_token = create_recovery_token();
|
|
366
|
+
const second_token = create_recovery_token();
|
|
367
|
+
|
|
368
|
+
// Second token should replace the first
|
|
369
|
+
t.not(first_token.token, second_token.token);
|
|
370
|
+
|
|
371
|
+
// First token should no longer be valid
|
|
372
|
+
const first_validation = is_token_valid(first_token.token);
|
|
373
|
+
t.false(first_validation.valid);
|
|
374
|
+
|
|
375
|
+
// Second token should be valid
|
|
376
|
+
const second_validation = is_token_valid(second_token.token);
|
|
377
|
+
t.true(second_validation.valid);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('password strength validation edge cases', async (t) => {
|
|
381
|
+
// Create environment variable with authentication
|
|
382
|
+
const settings_data = {
|
|
383
|
+
authentication: {
|
|
384
|
+
password_hash: await bcrypt.hash('old_password_123', 12),
|
|
385
|
+
created_at: new Date().toISOString(),
|
|
386
|
+
last_updated: new Date().toISOString(),
|
|
387
|
+
failed_attempts: {},
|
|
388
|
+
rate_limits: {}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(settings_data);
|
|
392
|
+
|
|
393
|
+
// Test null password
|
|
394
|
+
let error = await t.throwsAsync(async () => {
|
|
395
|
+
await change_password(null, '192.168.1.100');
|
|
396
|
+
});
|
|
397
|
+
t.is(error.message, 'Password is required');
|
|
398
|
+
|
|
399
|
+
// Test undefined password
|
|
400
|
+
error = await t.throwsAsync(async () => {
|
|
401
|
+
await change_password(undefined, '192.168.1.100');
|
|
402
|
+
});
|
|
403
|
+
t.is(error.message, 'Password is required');
|
|
404
|
+
|
|
405
|
+
// Test non-string password
|
|
406
|
+
error = await t.throwsAsync(async () => {
|
|
407
|
+
await change_password(123456789012, '192.168.1.100');
|
|
408
|
+
});
|
|
409
|
+
t.is(error.message, 'Password is required');
|
|
410
|
+
|
|
411
|
+
// Test exactly 12 characters (should pass)
|
|
412
|
+
const result = await change_password('exactly12chr', '192.168.1.100');
|
|
413
|
+
t.true(result.success);
|
|
414
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import { get_replication_manager, initialize_replication_manager, shutdown_replication_manager } from '../../../src/server/lib/replication_manager.js';
|
|
3
|
+
|
|
4
|
+
test.beforeEach(async () => {
|
|
5
|
+
// Clean up any existing replication manager
|
|
6
|
+
await shutdown_replication_manager();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test.afterEach(async () => {
|
|
10
|
+
// Clean up after each test
|
|
11
|
+
await shutdown_replication_manager();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('replication manager singleton', t => {
|
|
15
|
+
const manager1 = get_replication_manager();
|
|
16
|
+
const manager2 = get_replication_manager();
|
|
17
|
+
|
|
18
|
+
t.is(manager1, manager2, 'Should return same instance');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('replication manager initialization without settings', t => {
|
|
22
|
+
const manager = get_replication_manager();
|
|
23
|
+
|
|
24
|
+
// Should not throw when no settings are available
|
|
25
|
+
t.notThrows(() => {
|
|
26
|
+
manager.initialize();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
t.false(manager.enabled, 'Should be disabled without settings');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('replication manager default configuration', t => {
|
|
33
|
+
const manager = get_replication_manager();
|
|
34
|
+
|
|
35
|
+
t.false(manager.enabled, 'Should be disabled by default');
|
|
36
|
+
t.is(manager.mode, 'async', 'Should default to async mode');
|
|
37
|
+
t.is(manager.timeout_ms, 5000, 'Should have default timeout');
|
|
38
|
+
t.is(manager.retry_attempts, 3, 'Should have default retry attempts');
|
|
39
|
+
t.is(manager.batch_size, 100, 'Should have default batch size');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('replication manager queue operation when disabled', t => {
|
|
43
|
+
const manager = get_replication_manager();
|
|
44
|
+
|
|
45
|
+
// Should not throw when queuing operations while disabled
|
|
46
|
+
t.notThrows(() => {
|
|
47
|
+
manager.queue_replication('insert_one', 'test_collection', { document: { name: 'test' } });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
t.is(manager.replication_queue.length, 0, 'Should not queue when disabled');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('replication manager get status when disabled', t => {
|
|
54
|
+
const manager = get_replication_manager();
|
|
55
|
+
|
|
56
|
+
const status = manager.get_replication_status();
|
|
57
|
+
|
|
58
|
+
t.false(status.enabled, 'Should report as disabled');
|
|
59
|
+
t.is(status.connected_secondaries, 0, 'Should have no connected secondaries');
|
|
60
|
+
t.is(status.queue_length, 0, 'Should have empty queue');
|
|
61
|
+
t.false(status.processing, 'Should not be processing');
|
|
62
|
+
t.is(status.secondaries.length, 0, 'Should have no secondaries');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('replication manager get secondary health when disabled', t => {
|
|
66
|
+
const manager = get_replication_manager();
|
|
67
|
+
|
|
68
|
+
const health = manager.get_secondary_health();
|
|
69
|
+
|
|
70
|
+
t.is(health.total_secondaries, 0, 'Should have no secondaries');
|
|
71
|
+
t.is(health.healthy_secondaries, 0, 'Should have no healthy secondaries');
|
|
72
|
+
t.is(health.secondaries.length, 0, 'Should have empty secondaries array');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('replication manager add secondary when disabled', async t => {
|
|
76
|
+
const manager = get_replication_manager();
|
|
77
|
+
|
|
78
|
+
const secondary = {
|
|
79
|
+
id: 'test-secondary',
|
|
80
|
+
ip: '127.0.0.1',
|
|
81
|
+
port: 1984,
|
|
82
|
+
private_key: Buffer.from('test-key').toString('base64'),
|
|
83
|
+
enabled: true
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Should not throw but won't actually connect
|
|
87
|
+
const result = await manager.add_secondary(secondary);
|
|
88
|
+
|
|
89
|
+
t.true(result.success, 'Should return success');
|
|
90
|
+
t.is(result.secondary_id, 'test-secondary', 'Should return secondary ID');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('replication manager remove non-existent secondary', t => {
|
|
94
|
+
const manager = get_replication_manager();
|
|
95
|
+
|
|
96
|
+
const error = t.throws(() => {
|
|
97
|
+
manager.remove_secondary('non-existent');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
t.true(error.message.includes('not found'), 'Should throw not found error');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('replication manager sync when disabled', async t => {
|
|
104
|
+
const manager = get_replication_manager();
|
|
105
|
+
|
|
106
|
+
const error = await t.throwsAsync(async () => {
|
|
107
|
+
await manager.sync_secondaries();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
t.true(error.message.includes('not enabled'), 'Should throw not enabled error');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('replication manager shutdown', async t => {
|
|
114
|
+
const manager = get_replication_manager();
|
|
115
|
+
|
|
116
|
+
// Should not throw when shutting down
|
|
117
|
+
await t.notThrowsAsync(async () => {
|
|
118
|
+
await manager.shutdown();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
t.false(manager.enabled, 'Should be disabled after shutdown');
|
|
122
|
+
t.false(manager.processing_replication, 'Should not be processing after shutdown');
|
|
123
|
+
t.is(manager.secondary_connections.size, 0, 'Should have no connections after shutdown');
|
|
124
|
+
t.is(manager.replication_queue.length, 0, 'Should have empty queue after shutdown');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('replication manager statistics tracking', t => {
|
|
128
|
+
const manager = get_replication_manager();
|
|
129
|
+
|
|
130
|
+
const initialStats = manager.stats;
|
|
131
|
+
|
|
132
|
+
t.is(initialStats.total_operations_replicated, 0, 'Should start with zero operations');
|
|
133
|
+
t.is(initialStats.successful_replications, 0, 'Should start with zero successful replications');
|
|
134
|
+
t.is(initialStats.failed_replications, 0, 'Should start with zero failed replications');
|
|
135
|
+
t.is(initialStats.connected_secondaries, 0, 'Should start with zero connected secondaries');
|
|
136
|
+
t.is(initialStats.avg_replication_latency_ms, 0, 'Should start with zero average latency');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('replication manager sequence number generation', t => {
|
|
140
|
+
const manager = get_replication_manager();
|
|
141
|
+
|
|
142
|
+
const initialSequence = manager.sequence_number;
|
|
143
|
+
|
|
144
|
+
// Enable manager temporarily to test sequence generation
|
|
145
|
+
manager.enabled = true;
|
|
146
|
+
manager.secondary_connections.set('test', { authenticated: true });
|
|
147
|
+
|
|
148
|
+
manager.queue_replication('insert_one', 'test', { document: { name: 'test1' } });
|
|
149
|
+
manager.queue_replication('insert_one', 'test', { document: { name: 'test2' } });
|
|
150
|
+
|
|
151
|
+
t.is(manager.sequence_number, initialSequence + 2, 'Should increment sequence number for each operation');
|
|
152
|
+
t.true(manager.sequence_number > initialSequence, 'Should generate increasing sequence numbers');
|
|
153
|
+
|
|
154
|
+
// Clean up
|
|
155
|
+
manager.enabled = false;
|
|
156
|
+
manager.secondary_connections.clear();
|
|
157
|
+
manager.replication_queue = [];
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('replication manager sequence number increment', t => {
|
|
161
|
+
const manager = get_replication_manager();
|
|
162
|
+
|
|
163
|
+
const initialSequence = manager.sequence_number;
|
|
164
|
+
|
|
165
|
+
// Enable manager temporarily to test queue operation
|
|
166
|
+
manager.enabled = true;
|
|
167
|
+
manager.secondary_connections.set('test', { authenticated: true });
|
|
168
|
+
|
|
169
|
+
manager.queue_replication('insert_one', 'test', { document: { name: 'test' } });
|
|
170
|
+
|
|
171
|
+
t.is(manager.sequence_number, initialSequence + 1, 'Should increment sequence number');
|
|
172
|
+
|
|
173
|
+
// Clean up
|
|
174
|
+
manager.enabled = false;
|
|
175
|
+
manager.secondary_connections.clear();
|
|
176
|
+
manager.replication_queue = [];
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('replication manager batch processing preparation', t => {
|
|
180
|
+
const manager = get_replication_manager();
|
|
181
|
+
|
|
182
|
+
// Enable manager and add mock secondary
|
|
183
|
+
manager.enabled = true;
|
|
184
|
+
manager.secondary_connections.set('test', {
|
|
185
|
+
authenticated: true,
|
|
186
|
+
socket: { write: () => {} },
|
|
187
|
+
pending_operations: new Map()
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Add multiple operations to queue
|
|
191
|
+
for (let i = 0; i < 5; i++) {
|
|
192
|
+
manager.queue_replication('insert_one', 'test', { document: { id: i } });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
t.is(manager.replication_queue.length, 5, 'Should queue all operations');
|
|
196
|
+
t.is(manager.stats.total_operations_replicated, 5, 'Should track total operations');
|
|
197
|
+
|
|
198
|
+
// Clean up
|
|
199
|
+
manager.enabled = false;
|
|
200
|
+
manager.secondary_connections.clear();
|
|
201
|
+
manager.replication_queue = [];
|
|
202
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import safe_json_parse from '../../../src/server/lib/safe_json_parse.js';
|
|
3
|
+
|
|
4
|
+
test('safe_json_parse parses valid JSON string', (t = {}) => {
|
|
5
|
+
const valid_json = '{"name": "test", "value": 123}';
|
|
6
|
+
const result = safe_json_parse(valid_json);
|
|
7
|
+
|
|
8
|
+
t.deepEqual(result, { name: "test", value: 123 });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('safe_json_parse returns null for invalid JSON string', (t = {}) => {
|
|
12
|
+
const invalid_json = '{"name": "test", "value":}';
|
|
13
|
+
const result = safe_json_parse(invalid_json);
|
|
14
|
+
|
|
15
|
+
t.is(result, null);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('safe_json_parse returns null for empty string', (t = {}) => {
|
|
19
|
+
const empty_string = '';
|
|
20
|
+
const result = safe_json_parse(empty_string);
|
|
21
|
+
|
|
22
|
+
t.is(result, null);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('safe_json_parse returns null for non-string input', (t = {}) => {
|
|
26
|
+
const non_string = undefined;
|
|
27
|
+
const result = safe_json_parse(non_string);
|
|
28
|
+
|
|
29
|
+
t.is(result, null);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('safe_json_parse parses array JSON', (t = {}) => {
|
|
33
|
+
const array_json = '[1, 2, 3]';
|
|
34
|
+
const result = safe_json_parse(array_json);
|
|
35
|
+
|
|
36
|
+
t.deepEqual(result, [1, 2, 3]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('safe_json_parse parses primitive JSON values', (t = {}) => {
|
|
40
|
+
t.is(safe_json_parse('true'), true);
|
|
41
|
+
t.is(safe_json_parse('false'), false);
|
|
42
|
+
t.is(safe_json_parse('null'), null);
|
|
43
|
+
t.is(safe_json_parse('42'), 42);
|
|
44
|
+
t.is(safe_json_parse('"hello"'), "hello");
|
|
45
|
+
});
|