diff --git a/lean/commands/decrypt.py b/lean/commands/decrypt.py index e479b041..3213421f 100644 --- a/lean/commands/decrypt.py +++ b/lean/commands/decrypt.py @@ -47,13 +47,13 @@ def decrypt(project: Path, source_files = project_manager.get_source_files(project) try: from lean.components.util.encryption_helper import get_decrypted_file_content_for_local_project - decrypted_data = get_decrypted_file_content_for_local_project(project, + decrypted_data = get_decrypted_file_content_for_local_project(project, source_files, decryption_key, project_config_manager, organization_id) except Exception as e: raise RuntimeError(f"Could not decrypt project {project}: {e}") for file, decrypted in zip(source_files, decrypted_data): - with open(file, 'w') as f: + with open(file, 'w', encoding="utf-8") as f: f.write(decrypted) # Mark the project as decrypted diff --git a/lean/commands/encrypt.py b/lean/commands/encrypt.py index 71e275ef..c42eb257 100644 --- a/lean/commands/encrypt.py +++ b/lean/commands/encrypt.py @@ -52,7 +52,7 @@ def encrypt(project: Path, except Exception as e: raise RuntimeError(f"Could not encrypt project {project}: {e}") for file, encrypted in zip(source_files, encrypted_data): - with open(file, 'w') as f: + with open(file, 'w', encoding="utf-8") as f: f.write(encrypted) # Mark the project as encrypted diff --git a/lean/components/util/encryption_helper.py b/lean/components/util/encryption_helper.py index 09f8db74..c7cec0d4 100644 --- a/lean/components/util/encryption_helper.py +++ b/lean/components/util/encryption_helper.py @@ -103,7 +103,7 @@ def get_decrypted_file_content_for_local_project(project: Path, source_files: Li for file in source_files: try: # lets read and decrypt the file - with open(file, 'r') as f: + with open(file, 'r', encoding="utf-8") as f: encrypted = f.read() if not areProjectFilesAlreadyEncrypted: decrypted = encrypted @@ -131,7 +131,7 @@ def get_encrypted_file_content_for_local_project(project: Path, source_files: Li for file in source_files: try: # lets read and decrypt the file - with open(file, 'r') as f: + with open(file, 'r', encoding= "utf-8") as f: plain_text = f.read() if areProjectFilesAlreadyEncrypted: encrypted = plain_text diff --git a/tests/commands/test_decrypt.py b/tests/commands/test_decrypt.py index a101db4b..d98ec5e5 100644 --- a/tests/commands/test_decrypt.py +++ b/tests/commands/test_decrypt.py @@ -49,6 +49,36 @@ def test_decrypt_decrypts_file_in_case_project_not_in_decrypt_state() -> None: assert project_config.get("encrypted", False) == False assert project_config.get("encryption-key-path", None) == None +def test_decrypt_decrypts_file_that_has_a_chinese_character() -> None: + create_fake_lean_cli_directory() + + project_path = Path.cwd() / "Python Project" + + source_files = container.project_manager.get_source_files(project_path) + file_contents_map = {file.name: file.read_text() for file in source_files} + + encryption_file_path = project_path / "encryption_x.txt" + encryption_file_path.write_text("是一的", encoding="utf-8") + + result = CliRunner().invoke(lean, ["encrypt", "Python Project", "--key", encryption_file_path]) + + project_config = container.project_config_manager.get_project_config(project_path) + assert project_config.get("encrypted", False) != False + assert project_config.get("encryption-key-path", None) != None + for file in source_files: + assert file_contents_map[file.name] != file.read_text() + + result = CliRunner().invoke(lean, ["decrypt", "Python Project", "--key", encryption_file_path]) + + assert result.exit_code == 0 + + source_files = container.project_manager.get_source_files(project_path) + for file in source_files: + assert file_contents_map[file.name] == file.read_text() + project_config = container.project_config_manager.get_project_config(project_path) + assert project_config.get("encrypted", False) == False + assert project_config.get("encryption-key-path", None) == None + def test_decrypt_does_not_change_file_in_case_project_already_in_decrypt_state() -> None: create_fake_lean_cli_directory() diff --git a/tests/commands/test_encrypt.py b/tests/commands/test_encrypt.py index 2af2c668..6a818fe6 100644 --- a/tests/commands/test_encrypt.py +++ b/tests/commands/test_encrypt.py @@ -43,6 +43,30 @@ def test_encrypt_encrypts_file_in_case_project_not_in_encrypt_state() -> None: assert project_config.get("encrypted", False) == True assert project_config.get("encryption-key-path", None) == str(encryption_file_path) +def test_encrypt_encrypts_file_with_chinese_characters() -> None: + create_fake_lean_cli_directory() + + project_path = Path.cwd() / "Python Project" + + encryption_file_path = project_path / "encryption_x.txt" + encryption_file_path.write_text("是一的", encoding="utf-8") + + project_config = container.project_config_manager.get_project_config(project_path) + assert project_config.get("encrypted", False) == False + assert project_config.get("encryption-key-path", None) == None + + result = CliRunner().invoke(lean, ["encrypt", "Python Project", "--key", encryption_file_path]) + + assert result.exit_code == 0 + + source_files = container.project_manager.get_source_files(project_path) + expected_encrypted_files = _get_expected_encrypted_files_content() + for file in source_files: + assert expected_encrypted_files["chinese_" + file.name].strip() == file.read_text().strip() + project_config = container.project_config_manager.get_project_config(project_path) + assert project_config.get("encrypted", False) == True + assert project_config.get("encryption-key-path", None) == str(encryption_file_path) + def test_encrypt_does_not_change_file_in_case_project_already_in_encrypt_state() -> None: create_fake_lean_cli_directory() @@ -167,6 +191,23 @@ def _get_expected_encrypted_files_content() -> dict: 4T/vRU2aSdisuNMdXyuF4OH7ZgBdUYaNtfxmuJlmS4tYsom5xJfxrEEGG203gq0ME5eZCmu4JlLbEo1w L4u74Hsr4mWkJKbMJMcwW8ByRuy/VJiWW8JKIcoB0yHlwLJ/YoqMDF0BPG5i2EF0DXu1USNC/vE=""" , + "chinese_main.py": + """/hBXLMAwLMr3D0WTwS5lbHxJquOwtubD1rwNFPyrPkaFfn4oJF/MlHR0+ibh0S/HmOfZy3bSaNUNyVlv +GwUc6BkTVduHfl0m0doinxtovFkLMi33PWYwTrdr7tXWoklzq+J+AyjmaYiJsN9GxJvUzM3fsvRR7+5h +6j16o+zI/PHntLsldC2+66e3E1yP+b6uYrOaqubIs6ORGV3G9oViLfKCADiHTRLH+7885cJxN1Is1wOE +5zIIe4DOBnq92XpazPM/1Lk3ECcu/bno2capvjRtqXZiINv7QGGtcHyZ5C4vanfE9KkUbXTaDBtxrxSH +jmI/kNo+2DFM9BQ9YWK8bBYBUEt6cBDnyM6yBC33QCoYVOm37p3lj5mZkeePPSMdLmXfi7tZ7iySuT49 +/iWJ88rddbKVKU2qMtGjem5loKTOtKWoqd2R/fpOLAr+rH2D/Zz/hwomD+TdQ3DyWSYES3B9mMhk2Mpk +jtWkTiMMRdOalmC6GyihFbvYVUU19MZr3nTk6aEjRs1lSOZ+HiNV3KcrOY5ZlgLazXWfa9BUPdbXiDNk +m7LoCb2deL+y+cvL92xuu9Vuu3coKPxpVR3wodrJHLvWma22QagNR1wVb3f7agBypJm0/Yjr4+bSfRK0 +tNsmCtsMpGmlsFMYae0gl6IVR5VMTohetSf2/Fu2YsSpeBfAp3AN2vM3lfJtrC3WqVhWG+rnNa4ye5N0 +OLELdewYwHyeHOq6UQKJ7Bx7andRhogpVp0SeQGtu2y44/PfnaWQXB2z6I4oRipqmwSHLDyvC9sZX4jq +d9zLWdJ3RrpeER0hC9XG1fa3WXG2sVJfjEyd54MFMO5/sgQhU+lbvDlXs2HkIx/bbNUz4yuaE4PD5xcO +9votaJDw3m4zFVBww8m+PO3ddHUUC672lP0jCZcsjYw2WlZNt5bC5DSvvzCVvDzIF5dc3IKKXpeWwadN +fLxzbUCRumfDes0yuw9E+nPKhbLCatXIGlp8c/wpQ+XyWD/SVI/vCJjHAzbYPY7nIVYp0FAnCQ6kpBbI +wb/6GovuiB5bl/zHc5iWw17zy+F//CjAv2RJUgEd4uiQWIOKohSd6yjUWbsvybIyGF+/DLs7F/ZbQqTi +qxP8h0s0CxgTowyBYncfYLOmD2AybNVQUPIE5DNIANKV2xWPTQo8NqZ/oDYFlaobqazmViTfrgM=""" + , "research.ipynb": """NIiAgzU8gzaJ3YEIWysBh0e0xxm9rpAWDE4Pir/wzKtla/wcbs98GU5cdOxgd7Gjlcu0zNbFzE5x8Eyx qSuh3cQU17xQSisPxvjfDD/h2z9AnFT1jD1Vhc+Nn5ngwpgCA6P5fHT4VhPgaKDp7r9zc8pAURcSd04M @@ -192,5 +233,31 @@ def _get_expected_encrypted_files_content() -> dict: HrFPt9ElvzsATZvrloOCorTqbWc5BYmXb+u4MZ4vLtnU2wq/j5B+DvSswQkXsvtlGDsNPwLyi4dZuIVV Oae0ese2fAU8lmosUY95ghYxEOGrMHg5ZPklje/afjpxwKAAgTfWqozYPdpNL+MJEqrVA9YRq5wSvjuX UGw0ehtO8qY5FmPGcUlkBGuqmd7r6aLE4mosoZrc/UyZb+clWNYJITRLFJbQpWm3EU/Xrt5UM8uWwEdV -bFWAAkX56MyDHwJefC1nkA==""" +bFWAAkX56MyDHwJefC1nkA==""", + "chinese_research.ipynb": + """YujNshgrEbvBBeZs6Dod4uQhoSpPAQRn31qHGUcoacZbmpPpm9XoTKOCz9/v3zcFkD/xNP88Ry/Q7Zra +X1k6SjoQsswUB4MM0i4HwieZCug7dKjarlO3OAN/RrMeH2x7DvJQsjDNKMlE1JdLQ2gZXcolLWRoQCsO +TFb/wXx5VhU9XGF756OTKDQmweIkLS8RNHhXgiC0eMwYlPafDz+EA5DDe9p6Vx+xb1wHs7UP+Hhz/ikj +8rL4JWrCRJVbCX0riZE9omwPnAAO2M5kyVNwArMc+H7PIE7zKJqhGwSzt+uXvOCv/3fHazbTEwHErhKK +padpAPsRsJVlgfgm8epD9KywctYvtHpmN1io47yXM4yhWQsLduBqSig4W/rPd54OYPL1af6v+mUhFWdm +XV4mQzO+crVCmEvyKIw85Ai1uXzOs2r10UNS5e+reD+jRz9pg9ZSzUY0wyTfvdL1FG9PJSmsK2uLnC2x +WNfjKW0sbmIOYy2gm8oz0cEOSE303KB//p5Tl57zorzPp2UTNPJZFW9YKD0NGglWKPCtJYDsLNX9gJu5 +5Z7+a/S3GeizpV7VbvsoKZw7xYxLbKCfW2eJzgiaDE3XKMyXm6ohS+zZUC+5C1/Fidx2GyzMViDfF+sg +0dmwlxShkESdVHa8AigMkcJ1lIjeTiiBnqU6ujqe9t/HnderOfit2ZLUjEW06ogA32WVzsNXQfpiF1My +fPY92a/uv0Z7hW0FyaJeKC0PhVCfcXWIqjZ3vRx3otDgYGn+fy/Y6dcHZ8gsIMwG0Amd1bxU2hssxXqR +sUiTaZYetwQzoy8qci2hxo7GTdndELWChmZ6IpbdvnLdnMVHfDBE14n7IhrShtk02R9is/hn0BbwK2Hu +UvrYZowKcXKdHcofPR413ak9xlvgNwlyquy4q2NWdYlfxb2vN9QorhhSd5GKyhksbVOTfeklUdRCOza6 +neXoiR2e8rv0BNnaBa4vnAhNxB+vp9MeTGFMqfvCRw7HuRT3+wJHwgtP1qjVK7n2jUF1dmAj6Bnxl7ju +Y0AEUsh+ioGjv1Jwn5WKEYuCFNN1YrttCabJP6JI+yjbzf6n6O6Ex10Zmea3rpxLAZ8Jpqm/A0b27XpT +FNEfo86uR5WO5yCmeUAtUCanknoXh41wmA762eJPMXubqlGQGijde3ZSaaSoxHaeCrfxhd63hqxxd8Uf +c7BA4yFmNpn9N9ZRwaSYDqsLmqbdvxhyBJQd3bu1aIMPX2NcxpGV87kjbfNRQCmdvj+H1/n+pFm/4MgU +xEndzUvoSQvRjIeU4eB2R5Um4j/beLMFxd6Is6ku2B3ypYqg9cqcaDuGbXzZ6mxLnpoVCPFCvDsRWJ8c +blGlOV0YfbzUDYlTFWxO/cs3hVOSpjhOVANOAtfQFt9ZLa00B8t1tRCNvjM7EX0nHf41T69c4V7ex+OJ +/5XNCwuWoHZ2GJSni3bxjZ3ZxhGhCkIvxIgGrUTmt1wGOKGKwjl88uD5bDWX4+sjom2oDGqQTvkIlZJn +kKiRBRvuG4VAv0QAv73+QURV0RGxMgedEjIFd8R9Yf2mb7IENzgq2SQSL065qWzoCRTBF+jVOP8fXdzs +KL21W4y756fM3nKMkFGQZbjPeHO5ojDjT/U3mMimgC/3haZy5mmLde9NJFHWM8SuEoA/0bnCgZV++vpZ +kqzmoRw8H4RE+oBT8W+ch6CZvEYYsTuAXIPe1cQBcNxanqk4Uh8gwlEko/zfffPS7r1FR3+pe0AajG+o +wT72mv1dhrRifrr0MVEZe1iRUdPwEjBttJjCr5bnnbmswSKf2fEg0cHRBv25z9t33mj4kth/T2W5zkw3 +Zbni33aJFF6uM9VECyU1QlEz5Eu8lhecWs5ZxFtjT73K/5tqmcwNYx7sO1UpT3QPF1DQ7RlP1yTTkTw2 +s0ntvYgXO0YOeItR43fpHw==""" }