diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..6ef73ca3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: Python CI + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Run tests + run: pytest \ No newline at end of file diff --git a/.gitignore b/.gitignore index 74ef2bcc..8c1f82e9 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,4 @@ venv.bak/ # Mono Auto Generated Files mono_crash.* +generated_cv diff --git a/README.md b/README.md index 04cecfcf..61819cab 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ Join our **Telegram community** for: 📲 **[Join now!](https://t.me/AIhawkCommunity)** --> +![CI](https://github.com/feder-cr/linkedIn_auto_jobs_applier_with_AI/actions/workflows/ci.yml/badge.svg) + ## Table of Contents 1. [Introduction](#introduction) diff --git a/requirements.txt b/requirements.txt index dcf225dc..f3b0eee5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,3 +25,6 @@ regex==2024.7.24 reportlab==4.2.2 selenium==4.9.1 webdriver-manager==4.0.2 +pytest +pytest-mock +pytest-cov diff --git a/tests/test_linkedIn_authenticator.py b/tests/test_linkedIn_authenticator.py index 6277fc01..28eaab7d 100644 --- a/tests/test_linkedIn_authenticator.py +++ b/tests/test_linkedIn_authenticator.py @@ -32,8 +32,9 @@ def test_start_logged_in(mocker, authenticator): mocker.patch("time.sleep") # Avoid waiting during the test authenticator.start() - - authenticator.driver.get.assert_called_with('https://www.linkedin.com/feed') + + authenticator.driver.get.assert_called_with( + 'https://www.linkedin.com/feed') authenticator.is_logged_in.assert_called_once() assert authenticator.driver.get.call_count == 1 @@ -47,7 +48,8 @@ def test_start_not_logged_in(mocker, authenticator): authenticator.start() - authenticator.driver.get.assert_called_with('https://www.linkedin.com/feed') + authenticator.driver.get.assert_called_with( + 'https://www.linkedin.com/feed') authenticator.handle_login.assert_called_once() @@ -57,13 +59,15 @@ def test_handle_login(mocker, authenticator): mocker.patch.object(authenticator, 'enter_credentials') mocker.patch.object(authenticator, 'submit_login_form') mocker.patch.object(authenticator, 'handle_security_check') - + # Mock current_url as a regular return value, not PropertyMock - mocker.patch.object(authenticator.driver, 'current_url', return_value='https://www.linkedin.com/login') + mocker.patch.object(authenticator.driver, 'current_url', + return_value='https://www.linkedin.com/login') authenticator.handle_login() - authenticator.driver.get.assert_called_with('https://www.linkedin.com/login') + authenticator.driver.get.assert_called_with( + 'https://www.linkedin.com/login') authenticator.enter_credentials.assert_called_once() authenticator.submit_login_form.assert_called_once() authenticator.handle_security_check.assert_called_once() @@ -75,7 +79,8 @@ def test_enter_credentials_success(mocker, authenticator): password_mock = mocker.Mock() mocker.patch.object(WebDriverWait, 'until', return_value=email_mock) - mocker.patch.object(authenticator.driver, 'find_element', return_value=password_mock) + mocker.patch.object(authenticator.driver, 'find_element', + return_value=password_mock) authenticator.set_secrets("test@example.com", "password123") authenticator.enter_credentials() @@ -92,13 +97,15 @@ def test_enter_credentials_timeout(mocker, authenticator): authenticator.enter_credentials() - authenticator.driver.find_element.assert_not_called() # Password input should not be accessed if email fails + # Password input should not be accessed if email fails + authenticator.driver.find_element.assert_not_called() def test_submit_login_form_success(mocker, authenticator): """Test submitting the login form.""" login_button_mock = mocker.Mock() - mocker.patch.object(authenticator.driver, 'find_element', return_value=login_button_mock) + mocker.patch.object(authenticator.driver, 'find_element', + return_value=login_button_mock) authenticator.submit_login_form() @@ -107,11 +114,13 @@ def test_submit_login_form_success(mocker, authenticator): def test_submit_login_form_no_button(mocker, authenticator): """Test submitting the login form when the login button is not found.""" - mocker.patch.object(authenticator.driver, 'find_element', side_effect=NoSuchElementException) + mocker.patch.object(authenticator.driver, 'find_element', + side_effect=NoSuchElementException) authenticator.submit_login_form() - authenticator.driver.find_element.assert_called_once_with(By.XPATH, '//button[@type="submit"]') + authenticator.driver.find_element.assert_called_once_with( + By.XPATH, '//button[@type="submit"]') def test_is_logged_in_true(mocker, authenticator): @@ -119,7 +128,8 @@ def test_is_logged_in_true(mocker, authenticator): buttons_mock = mocker.Mock() buttons_mock.text = "Start a post" mocker.patch.object(WebDriverWait, 'until') - mocker.patch.object(authenticator.driver, 'find_elements', return_value=[buttons_mock]) + mocker.patch.object(authenticator.driver, 'find_elements', + return_value=[buttons_mock]) assert authenticator.is_logged_in() is True @@ -146,7 +156,6 @@ def test_handle_security_check_success(mocker, authenticator): WebDriverWait(authenticator.driver, 300).until.assert_any_call(mocker.ANY) - def test_handle_security_check_timeout(mocker, authenticator): """Test handling security check timeout.""" mocker.patch.object(WebDriverWait, 'until', side_effect=TimeoutException) @@ -155,4 +164,3 @@ def test_handle_security_check_timeout(mocker, authenticator): # Verify WebDriverWait is called with EC.url_contains for the challenge WebDriverWait(authenticator.driver, 10).until.assert_any_call(mocker.ANY) -