-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
653 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import pytest | ||
from selenium.webdriver.common.by import By | ||
from selenium.webdriver.support.ui import WebDriverWait | ||
from selenium.webdriver.support import expected_conditions as EC | ||
from src.aihawk_authenticator import AIHawkAuthenticator | ||
from selenium.common.exceptions import NoSuchElementException, TimeoutException | ||
|
||
|
||
@pytest.fixture | ||
def mock_driver(mocker): | ||
"""Fixture to mock the Selenium WebDriver.""" | ||
return mocker.Mock() | ||
|
||
|
||
@pytest.fixture | ||
def authenticator(mock_driver): | ||
"""Fixture to initialize AIHawkAuthenticator with a mocked driver.""" | ||
return AIHawkAuthenticator(mock_driver) | ||
|
||
|
||
def test_handle_login(mocker, authenticator): | ||
"""Test handling the AIHawk login process.""" | ||
mocker.patch.object(authenticator.driver, 'get') | ||
mocker.patch.object(authenticator, 'enter_credentials') | ||
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') | ||
|
||
authenticator.handle_login() | ||
|
||
authenticator.driver.get.assert_called_with( | ||
'https://www.linkedin.com/login') | ||
authenticator.enter_credentials.assert_called_once() | ||
authenticator.handle_security_check.assert_called_once() | ||
|
||
|
||
def test_enter_credentials_success(mocker, authenticator): | ||
"""Test entering credentials.""" | ||
email_mock = mocker.Mock() | ||
password_mock = mocker.Mock() | ||
|
||
mocker.patch.object(WebDriverWait, 'until', return_value=email_mock) | ||
mocker.patch.object(authenticator.driver, 'find_element', | ||
return_value=password_mock) | ||
|
||
|
||
|
||
|
||
|
||
|
||
def test_is_logged_in_true(mocker, authenticator): | ||
"""Test if the user is logged in.""" | ||
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]) | ||
|
||
assert authenticator.is_logged_in() is True | ||
|
||
|
||
def test_is_logged_in_false(mocker, authenticator): | ||
"""Test if the user is not logged in.""" | ||
mocker.patch.object(WebDriverWait, 'until') | ||
mocker.patch.object(authenticator.driver, 'find_elements', return_value=[]) | ||
|
||
assert authenticator.is_logged_in() is False | ||
|
||
|
||
def test_handle_security_check_success(mocker, authenticator): | ||
"""Test handling security check successfully.""" | ||
mocker.patch.object(WebDriverWait, 'until', side_effect=[ | ||
mocker.Mock(), # Security checkpoint detection | ||
mocker.Mock() # Security check completion | ||
]) | ||
|
||
authenticator.handle_security_check() | ||
|
||
# Verify WebDriverWait is called with EC.url_contains for both the challenge and feed | ||
WebDriverWait(authenticator.driver, 10).until.assert_any_call(mocker.ANY) | ||
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) | ||
|
||
authenticator.handle_security_check() | ||
|
||
# Verify WebDriverWait is called with EC.url_contains for the challenge | ||
WebDriverWait(authenticator.driver, 10).until.assert_any_call(mocker.ANY) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import pytest | ||
# from src.linkedIn_job_manager import JobManager | ||
|
||
@pytest.fixture | ||
def job_manager(): | ||
"""Fixture for JobManager.""" | ||
return None # Replace with valid instance or mock later | ||
|
||
def test_bot_functionality(job_manager): | ||
"""Test AIHawk bot facade.""" | ||
# Example: test job manager interacts with the bot facade correctly | ||
job = {"title": "Software Engineer"} | ||
# job_manager.some_method_to_apply(job) | ||
assert job is not None # Placeholder for actual test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import pytest | ||
from unittest import mock | ||
from src.aihawk_easy_applier import AIHawkEasyApplier | ||
|
||
|
||
@pytest.fixture | ||
def mock_driver(): | ||
"""Fixture to mock Selenium WebDriver.""" | ||
return mock.Mock() | ||
|
||
|
||
@pytest.fixture | ||
def mock_gpt_answerer(): | ||
"""Fixture to mock GPT Answerer.""" | ||
return mock.Mock() | ||
|
||
|
||
@pytest.fixture | ||
def mock_resume_generator_manager(): | ||
"""Fixture to mock Resume Generator Manager.""" | ||
return mock.Mock() | ||
|
||
|
||
@pytest.fixture | ||
def easy_applier(mock_driver, mock_gpt_answerer, mock_resume_generator_manager): | ||
"""Fixture to initialize AIHawkEasyApplier with mocks.""" | ||
return AIHawkEasyApplier( | ||
driver=mock_driver, | ||
resume_dir="/path/to/resume", | ||
set_old_answers=[('Question 1', 'Answer 1', 'Type 1')], | ||
gpt_answerer=mock_gpt_answerer, | ||
resume_generator_manager=mock_resume_generator_manager | ||
) | ||
|
||
|
||
def test_initialization(mocker, easy_applier): | ||
"""Test that AIHawkEasyApplier is initialized correctly.""" | ||
# Mock os.path.exists to return True | ||
mocker.patch('os.path.exists', return_value=True) | ||
|
||
easy_applier = AIHawkEasyApplier( | ||
driver=mocker.Mock(), | ||
resume_dir="/path/to/resume", | ||
set_old_answers=[('Question 1', 'Answer 1', 'Type 1')], | ||
gpt_answerer=mocker.Mock(), | ||
resume_generator_manager=mocker.Mock() | ||
) | ||
|
||
assert easy_applier.resume_path == "/path/to/resume" | ||
assert len(easy_applier.set_old_answers) == 1 | ||
assert easy_applier.gpt_answerer is not None | ||
assert easy_applier.resume_generator_manager is not None | ||
|
||
|
||
def test_apply_to_job_success(mocker, easy_applier): | ||
"""Test successfully applying to a job.""" | ||
mock_job = mock.Mock() | ||
|
||
# Mock job_apply so we don't actually try to apply | ||
mocker.patch.object(easy_applier, 'job_apply') | ||
|
||
easy_applier.apply_to_job(mock_job) | ||
easy_applier.job_apply.assert_called_once_with(mock_job) | ||
|
||
|
||
def test_apply_to_job_failure(mocker, easy_applier): | ||
"""Test failure while applying to a job.""" | ||
mock_job = mock.Mock() | ||
mocker.patch.object(easy_applier, 'job_apply', | ||
side_effect=Exception("Test error")) | ||
|
||
with pytest.raises(Exception, match="Test error"): | ||
easy_applier.apply_to_job(mock_job) | ||
|
||
easy_applier.job_apply.assert_called_once_with(mock_job) | ||
|
||
|
||
def test_check_for_premium_redirect_no_redirect(mocker, easy_applier): | ||
"""Test that check_for_premium_redirect works when there's no redirect.""" | ||
mock_job = mock.Mock() | ||
easy_applier.driver.current_url = "https://www.linkedin.com/jobs/view/1234" | ||
|
||
easy_applier.check_for_premium_redirect(mock_job) | ||
easy_applier.driver.get.assert_not_called() | ||
|
||
|
||
def test_check_for_premium_redirect_with_redirect(mocker, easy_applier): | ||
"""Test that check_for_premium_redirect handles AIHawk Premium redirects.""" | ||
mock_job = mock.Mock() | ||
easy_applier.driver.current_url = "https://www.linkedin.com/premium" | ||
mock_job.link = "https://www.linkedin.com/jobs/view/1234" | ||
|
||
with pytest.raises(Exception, match="Redirected to AIHawk Premium page and failed to return"): | ||
easy_applier.check_for_premium_redirect(mock_job) | ||
|
||
# Verify that it attempted to return to the job page 3 times | ||
assert easy_applier.driver.get.call_count == 3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
from src.job import Job | ||
from unittest import mock | ||
from pathlib import Path | ||
import os | ||
import pytest | ||
from src.aihawk_job_manager import AIHawkJobManager | ||
from selenium.common.exceptions import NoSuchElementException | ||
from loguru import logger | ||
|
||
|
||
@pytest.fixture | ||
def job_manager(mocker): | ||
"""Fixture to create a AIHawkJobManager instance with mocked driver.""" | ||
mock_driver = mocker.Mock() | ||
return AIHawkJobManager(mock_driver) | ||
|
||
|
||
def test_initialization(job_manager): | ||
"""Test AIHawkJobManager initialization.""" | ||
assert job_manager.driver is not None | ||
assert job_manager.set_old_answers == set() | ||
assert job_manager.easy_applier_component is None | ||
|
||
|
||
def test_set_parameters(mocker, job_manager): | ||
"""Test setting parameters for the AIHawkJobManager.""" | ||
# Mocking os.path.exists to return True for the resume path | ||
mocker.patch('pathlib.Path.exists', return_value=True) | ||
|
||
params = { | ||
'company_blacklist': ['Company A', 'Company B'], | ||
'title_blacklist': ['Intern', 'Junior'], | ||
'positions': ['Software Engineer', 'Data Scientist'], | ||
'locations': ['New York', 'San Francisco'], | ||
'apply_once_at_company': True, | ||
'uploads': {'resume': '/path/to/resume'}, # Resume path provided here | ||
'outputFileDirectory': '/path/to/output', | ||
'job_applicants_threshold': { | ||
'min_applicants': 5, | ||
'max_applicants': 50 | ||
}, | ||
'remote': False, | ||
'distance': 50, | ||
'date': {'all time': True} | ||
} | ||
|
||
job_manager.set_parameters(params) | ||
|
||
# Normalize paths to handle platform differences (e.g., Windows vs Unix-like systems) | ||
assert str(job_manager.resume_path) == os.path.normpath('/path/to/resume') | ||
assert str(job_manager.output_file_directory) == os.path.normpath( | ||
'/path/to/output') | ||
|
||
|
||
def next_job_page(self, position, location, job_page): | ||
logger.debug(f"Navigating to next job page: {position} in {location}, page {job_page}") | ||
self.driver.get( | ||
f"https://www.linkedin.com/jobs/search/{self.base_search_url}&keywords={position}&location={location}&start={job_page * 25}") | ||
|
||
|
||
def test_get_jobs_from_page_no_jobs(mocker, job_manager): | ||
"""Test get_jobs_from_page when no jobs are found.""" | ||
mocker.patch.object(job_manager.driver, 'find_element', | ||
side_effect=NoSuchElementException) | ||
|
||
jobs = job_manager.get_jobs_from_page() | ||
assert jobs == [] | ||
|
||
|
||
def test_get_jobs_from_page_with_jobs(mocker, job_manager): | ||
"""Test get_jobs_from_page when job elements are found.""" | ||
# Mock the no_jobs_element to behave correctly | ||
mock_no_jobs_element = mocker.Mock() | ||
mock_no_jobs_element.text = "No matching jobs found" | ||
|
||
# Mocking the find_element to return the mock no_jobs_element | ||
mocker.patch.object(job_manager.driver, 'find_element', | ||
return_value=mock_no_jobs_element) | ||
|
||
# Mock the page_source | ||
mocker.patch.object(job_manager.driver, 'page_source', | ||
return_value="some page content") | ||
|
||
# Ensure jobs are returned as empty list due to "No matching jobs found" | ||
jobs = job_manager.get_jobs_from_page() | ||
assert jobs == [] # No jobs expected due to "No matching jobs found" | ||
|
||
|
||
def test_apply_jobs_with_no_jobs(mocker, job_manager): | ||
"""Test apply_jobs when no jobs are found.""" | ||
# Mocking find_element to return a mock element that simulates no jobs | ||
mock_element = mocker.Mock() | ||
mock_element.text = "No matching jobs found" | ||
|
||
# Mock the driver to simulate the page source | ||
mocker.patch.object(job_manager.driver, 'page_source', return_value="") | ||
|
||
# Mock the driver to return the mock element when find_element is called | ||
mocker.patch.object(job_manager.driver, 'find_element', | ||
return_value=mock_element) | ||
|
||
# Call apply_jobs and ensure no exceptions are raised | ||
job_manager.apply_jobs() | ||
|
||
# Ensure it attempted to find the job results list | ||
assert job_manager.driver.find_element.call_count == 1 | ||
|
||
|
||
def test_apply_jobs_with_jobs(mocker, job_manager): | ||
"""Test apply_jobs when jobs are present.""" | ||
|
||
# Mock no_jobs_element to simulate the absence of "No matching jobs found" banner | ||
no_jobs_element = mocker.Mock() | ||
no_jobs_element.text = "" # Empty text means "No matching jobs found" is not present | ||
mocker.patch.object(job_manager.driver, 'find_element', | ||
return_value=no_jobs_element) | ||
|
||
# Mock the page_source to simulate what the page looks like when jobs are present | ||
mocker.patch.object(job_manager.driver, 'page_source', | ||
return_value="some job content") | ||
|
||
# Mock the outer find_elements (scaffold-layout__list-container) | ||
container_mock = mocker.Mock() | ||
|
||
# Mock the inner find_elements to return job list items | ||
job_element_mock = mocker.Mock() | ||
# Simulating two job items | ||
job_elements_list = [job_element_mock, job_element_mock] | ||
|
||
# Return the container mock, which itself returns the job elements list | ||
container_mock.find_elements.return_value = job_elements_list | ||
mocker.patch.object(job_manager.driver, 'find_elements', | ||
return_value=[container_mock]) | ||
|
||
# Mock the extract_job_information_from_tile method to return sample job info | ||
mocker.patch.object(job_manager, 'extract_job_information_from_tile', return_value=( | ||
"Title", "Company", "Location", "Apply", "Link")) | ||
|
||
# Mock other methods like is_blacklisted, is_already_applied_to_job, and is_already_applied_to_company | ||
mocker.patch.object(job_manager, 'is_blacklisted', return_value=False) | ||
mocker.patch.object( | ||
job_manager, 'is_already_applied_to_job', return_value=False) | ||
mocker.patch.object( | ||
job_manager, 'is_already_applied_to_company', return_value=False) | ||
|
||
# Mock the AIHawkEasyApplier component | ||
job_manager.easy_applier_component = mocker.Mock() | ||
|
||
# Mock the output_file_directory as a valid Path object | ||
job_manager.output_file_directory = Path("/mocked/path/to/output") | ||
|
||
# Mock Path.exists() to always return True (so no actual file system interaction is needed) | ||
mocker.patch.object(Path, 'exists', return_value=True) | ||
|
||
# Mock the open function to prevent actual file writing | ||
mock_open = mocker.mock_open() | ||
mocker.patch('builtins.open', mock_open) | ||
|
||
# Run the apply_jobs method | ||
job_manager.apply_jobs() | ||
|
||
# Assertions | ||
assert job_manager.driver.find_elements.call_count == 3 | ||
# Called for each job element | ||
assert job_manager.extract_job_information_from_tile.call_count == 2 | ||
# Called for each job element | ||
assert job_manager.easy_applier_component.job_apply.call_count == 2 | ||
mock_open.assert_called() # Ensure that the open function was called |
Oops, something went wrong.