From e99f4ab86a94ac2a9e0815e0dc954461873e99fc Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 19 Oct 2025 01:48:28 +0000 Subject: [PATCH] feat: Improve test coverage for main.py This commit introduces a new test file, `tests/test_main.py`, which adds comprehensive unit tests for the `main.py` module. The new tests cover all the functions in `main.py`, including `run_startup_tasks`, `handle_password_authentication`, `create_password`, `application_hub`, `run_onboarding`, `check_connection`, and `pass_exsists`. The tests make extensive use of mocking to isolate the functions from their dependencies, ensuring that the tests are robust and focused on the logic of the functions themselves. In addition to adding new tests, this commit also includes a bug fix for the `pass_exsists` function, which was previously reading from the password file twice. A new test case has been added to cover this fix. Finally, the `.coverage` file has been added to the `.gitignore` file to prevent coverage reports from being committed to the repository. --- .coverage | Bin 0 -> 53248 bytes .gitignore | 1 + main.py | 3 +- tests/test_main.py | 221 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 .coverage create mode 100644 tests/test_main.py diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..deb467ccf6f3a7ef1b5347ab8ae3f32b93c2be2b GIT binary patch literal 53248 zcmeI4&u<&Y6~|{e6se_1juJ&?1jW@{Tv@PwgfQG7KM>b#1=MJOG^vqG0&kY&NZN$l zrFWP5amWuP0UR6cv4<8#dhe+}jz!vAi{_H#A4mffNSrpX4@Hrp?xAmXcb23r837!? zvHM-{$L^bXGw*%o%?yW2&X1mX%8QlU2*REl%ad%HSr+@Yl#DT(o;7-!qf947b3yOc z$oXNXHe21kR1?p!YUw9ToUc7CR;s_KeRJx=YR|qr^-kHf+mwI<1V8`;K;ZwI!2UDU za$|1JdgGPYZFN-~x@{GvueI+!yME@}x;%I0J5R04R3?vCBpoX&@{A0F9obi*-0-?e zdVa@iyRqkQ%6Lne+=EDU^h8H8n(B1M)kI&PIbMgfVzo&{^h2-bhI{g5wYQvP$ZCt# zZk!6J5an(9I)^--sV~V;ZKzQBZ55?f9QQg)m8CadtCSlvGuB0w_~eFddQ6RZLxQ0z zXE&&zb`W;7^{vqL+gmDHmTvB0J0LrE2+8lL zjkMx8I85^}-8jgc{nBl7KwA#|9cxa5T(@YdHSoO)1C=-avK(qICvNz`veZsc1RR?x zH6ER@7)7PpjV^TQ^OoZd;vl(psPUa9y;R@2Z?fE2SgISXx zSW6-%Qq)0cEHK$N27X(2l}P*04z^Y3ZmQKRH60f2bRbdaH0@Gjec`as$wR<&rk7Oz2&Put|I5U5k zJ6S%bnVn4Tmy6}b?5wq)_6R*L>F30l6IwFt0yj(qU*7z_8*j;*ZP#Kld~u@GczpIS z!?g)cQ@@tpe7=yFd~q(J-=jfaKlzx+S~4{G2WoF++wIb5(s#X(x-5;s=?oxs#*Ryw z-BvJ&llaw_NqFkHXJ~I)!0_6zr(8=(cIw_g45G-RX;n{K(PKs`7q{FhvO+*l_c#bb44uw5QA1++Iv7thSO4M z?AM)B*OLw+q$n4uQ%2N9Zz$6gnx*Oy%ap&`Cnmbop4&;gyKa<`5_h)LO~s|N(f8a~ z#a>TIoh30Q39KDAlI>7YBF&{si~Lq$pj*pn>Q}u=p41=Z_7|*j!3m z00ck)1V8`;KmY_l00ck)1abmZe$+Bw0~9KJ-p;-S(D(nR>zA1LtN4@nwfLEMTlB^A z;``#1Xoyn%O8tZSZ|m<+1{@#&0w4eaAOHd&00JNY0w4eaAn;WXn3?8mWzFsP>FKQ{ z|J(2H-7h#hkqCF_KlocJq<{E7ROjq;BI^cvlb^tMA{B0XwC@h39;tD5GApLp@!8p_bcmsGe@JMG)-(LRw;qvtx-?&-Hv=NyTIjdk*dY~5O=7_h} z4YGP3;GC`H;z8_nwf&6(XQy(hLzQ{4<50qa#o2n25UVJT*7Pem?>RVt;y3jV#o792_1}qaiL!W5ydXXnm&AqoFTaW=Mj0Rg0w4eaAOHd&00JNY0w4ea zAi&rIe8tL_u-W>5dWN4!g~Ro~ILc3_vV+$D^#(s_iWy!1*G9B%UjJ98`LR?hTmMhx z*$-O(SMKLa#)NELZs($WNk5r;G_w9L=k|@S|4X?A+4_It2yYrkM%MquS-v(Dn^io2 zke?b#N7ny^1-@?IYW|Iqh900ck)1V8`;KmY_l00ck)1VG@PCcx>p0X2R9-xB|2 z^uPfEAOHd&00JNY0w4eaAOHd&00JOz4-(+KX6yU^Me!>p{w+Qef1wi`AOHd&00JNY z0w4eaAOHd&00JNY0(X$W(PGiIpFIEGPycxB-JOr#zxKu6r| zwCpGON7j|={Pll)@q2c4r~JWd*Zy`rdgAJ<)~i;nKw0~^zl0}#Gf~D{O z7sV$`d?NlMJ{SMIgASr<5C8!X009sH0T2KI5C8!X009sHf!iW5L0=tM#S$&~EzXm- zVqt==?1H}k&!%sya43TS2!H?xfB*=900@8p2!H?xfB*=5%?arH|5*Qj%^L~kfdB}A z00@8p2!H?xfB*=900@A None: def pass_exsists() -> bool: try: with open('etc/psswrd.txt', 'r') as f: - if len(f.read()) < 8 or len(f.read()) > 72: # pass min len is 8 max 72 + content = f.read() + if len(content) < 8 or len(content) > 72: # pass min len is 8 max 72 return False # might as well mk a new one else: return True diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..df98045 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,221 @@ +import sys +import os +from unittest.mock import patch +import pytest + +# Add the root directory to the python path to allow for imports +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from main import pass_exsists +from unittest.mock import mock_open + +@patch('builtins.open', new_callable=mock_open, read_data='a' * 8) +def test_pass_exsists_true(mock_file): + """ + Test that pass_exsists returns True when the password file exists and is valid. + """ + assert pass_exsists() is True + +@patch('builtins.open', side_effect=FileNotFoundError) +@patch('os.makedirs') +@patch('main.file_system_helper', return_value=True) +def test_pass_exsists_false_no_file(mock_fs_helper, mock_makedirs, mock_open): + """ + Test that pass_exsists returns False when the password file does not exist. + """ + assert pass_exsists() is False + mock_makedirs.assert_called_once_with("etc") + mock_fs_helper.assert_called_with("etc/psswrd.txt", "touch") + +@patch('builtins.open', new_callable=mock_open, read_data='a' * 7) +def test_pass_exsists_false_short_password(mock_file): + """ + Test that pass_exsists returns False when the password is too short. + """ + assert pass_exsists() is False + +@patch('builtins.open', new_callable=mock_open, read_data='a' * 73) +def test_pass_exsists_false_long_password(mock_file): + """ + Test that pass_exsists returns False when the password is too long. + """ + assert pass_exsists() is False + +from main import check_connection + +@patch('main.check_url', return_value=True) +@patch('main.fl.error') +@patch('main.console') +def test_check_connection_success(mock_console, mock_fl_error, mock_check_url): + """ + Test that check_connection handles a successful connection correctly. + """ + check_connection() + mock_check_url.assert_called_once_with("https://www.python.org", 3) + mock_fl_error.assert_not_called() + mock_console.print.assert_not_called() + +@patch('main.check_url', return_value=False) +@patch('main.fl.error') +@patch('main.console') +def test_check_connection_failure(mock_console, mock_fl_error, mock_check_url): + """ + Test that check_connection handles a failed connection correctly. + """ + check_connection() + mock_check_url.assert_called_once_with("https://www.python.org", 3) + mock_fl_error.assert_called_once_with("No internet connection detected") + mock_console.print.assert_called_once() + +from main import run_onboarding + +@patch('main.print') +@patch('main.time.sleep') +@patch('main.console') +@patch('main.printWelcome') +def test_run_onboarding(mock_print_welcome, mock_console, mock_sleep, mock_print): + """ + Test that run_onboarding prints the correct messages and sleeps for 5 seconds. + """ + run_onboarding() + assert mock_print.call_count == 3 + mock_sleep.assert_called_once_with(5) + mock_console.clear.assert_called_once() + mock_print_welcome.assert_called_once() + +from main import application_hub + +@patch('builtins.input', side_effect=['1', 'exit']) +@patch('main.Weather.main') +def test_application_hub_weather(mock_weather_main, mock_input): + """ + Test that application_hub calls Weather.main when the user enters '1'. + """ + with pytest.raises(SystemExit): + application_hub() + mock_weather_main.assert_called_once() + +@patch('builtins.input', side_effect=['2', 'exit']) +@patch('main.ToDo.main') +def test_application_hub_todo(mock_todo_main, mock_input): + """ + Test that application_hub calls ToDo.main when the user enters '2'. + """ + with pytest.raises(SystemExit): + application_hub() + mock_todo_main.assert_called_once() + +@patch('builtins.input', side_effect=['onboarding', 'exit']) +@patch('main.run_onboarding') +def test_application_hub_onboarding(mock_run_onboarding, mock_input): + """ + Test that application_hub calls run_onboarding when the user enters 'onboarding'. + """ + with pytest.raises(SystemExit): + application_hub() + mock_run_onboarding.assert_called_once() + +@patch('builtins.input', side_effect=['invalid', 'exit']) +@patch('main.console') +def test_application_hub_invalid_input(mock_console, mock_input): + """ + Test that application_hub prints an error message for invalid input. + """ + with pytest.raises(SystemExit): + application_hub() + mock_console.print.assert_called_with("[red]Invalid application[/red]") + +@patch('builtins.input', side_effect=['1', 'exit']) +@patch('main.Weather.main', side_effect=Exception("Test Exception")) +@patch('main.application_error') +def test_application_hub_exception(mock_application_error, mock_weather_main, mock_input): + """ + Test that application_hub handles exceptions in applications. + """ + with pytest.raises(SystemExit): + application_hub() + mock_application_error.assert_called_once() + +from main import create_password + +@patch('getpass.getpass', return_value='password123') +@patch('bcrypt.hashpw', return_value=b'hashed_password') +@patch('builtins.open') +def test_create_password_success(mock_open, mock_hashpw, mock_getpass): + """ + Test that create_password successfully creates a password. + """ + create_password() + mock_hashpw.assert_called_once() + mock_open.assert_called_with('etc/psswrd.txt', 'w') + +@patch('getpass.getpass', side_effect=['short', 'validpassword']) +@patch('main.print') +@patch('bcrypt.hashpw', return_value=b'hashed_password') +@patch('builtins.open') +def test_create_password_too_short(mock_open, mock_hashpw, mock_print, mock_getpass): + """ + Test that create_password handles a password that is too short. + """ + create_password() + mock_print.assert_called_with("[red]Your password does not meet the required safety guidelines of at least 8 chars. Please choose a stronger password.[/red]") + mock_hashpw.assert_called_once() + +@patch('getpass.getpass', side_effect=['a' * 73, 'validpassword']) +@patch('main.print') +@patch('bcrypt.hashpw', return_value=b'hashed_password') +@patch('builtins.open') +def test_create_password_too_long(mock_open, mock_hashpw, mock_print, mock_getpass): + """ + Test that create_password handles a password that is too long. + """ + create_password() + mock_print.assert_called_with("[red]Your password is too long! Choose a shorter one[/red]") + mock_hashpw.assert_called_once() + +from main import handle_password_authentication + +@patch('getpass.getpass', return_value='password123') +@patch('bcrypt.checkpw', return_value=True) +@patch('builtins.open') +def test_handle_password_authentication_success(mock_open, mock_checkpw, mock_getpass): + """ + Test successful password authentication. + """ + handle_password_authentication() + mock_checkpw.assert_called_once() + +@patch('getpass.getpass', side_effect=['wrongpassword', 'password123']) +@patch('bcrypt.checkpw', side_effect=[False, True]) +@patch('builtins.open') +@patch('main.print') +def test_handle_password_authentication_incorrect_password(mock_print, mock_open, mock_checkpw, mock_getpass): + """ + Test incorrect password authentication. + """ + handle_password_authentication() + assert mock_checkpw.call_count == 2 + mock_print.assert_any_call("[red]Incorrect password, please try again[/red]\n[orange]Please double check for typeos[/orange]") + +@patch('getpass.getpass', side_effect=KeyboardInterrupt) +@patch('builtins.open') +@patch('main.print') +def test_handle_password_authentication_keyboard_interrupt(mock_print, mock_open, mock_getpass): + """ + Test keyboard interrupt during password authentication. + """ + with pytest.raises(SystemExit): + handle_password_authentication() + mock_print.assert_any_call("\n[red on white]Exiting password check...[/red on white]") + +from main import run_startup_tasks + +@patch('threading.Thread') +def test_run_startup_tasks(mock_thread): + """ + Test that run_startup_tasks starts and joins threads correctly. + """ + run_startup_tasks() + assert mock_thread.call_count == 1 + mock_thread.return_value.start.assert_called_once() + mock_thread.return_value.join.assert_called_once() \ No newline at end of file