280 lines
8.0 KiB
Python
280 lines
8.0 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from unittest.mock import ANY, Mock, patch
|
|
|
|
import pytest
|
|
import readchar
|
|
from typer.testing import CliRunner
|
|
|
|
import crawlee._cli
|
|
|
|
runner = CliRunner()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_cookiecutter(monkeypatch: pytest.MonkeyPatch) -> Mock:
|
|
mock_cookiecutter = Mock()
|
|
monkeypatch.setattr(target=crawlee._cli, name='cookiecutter', value=mock_cookiecutter)
|
|
|
|
return mock_cookiecutter
|
|
|
|
|
|
def test_create_interactive(mock_cookiecutter: Mock, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
mock_input = iter(
|
|
[
|
|
*'my_project',
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
]
|
|
)
|
|
monkeypatch.setattr(target=readchar, name='readkey', value=lambda: next(mock_input))
|
|
|
|
result = runner.invoke(crawlee._cli.cli, ['create'])
|
|
assert 'Your project "my_project" was created.' in result.output
|
|
|
|
mock_cookiecutter.assert_called_with(
|
|
template=ANY,
|
|
no_input=True,
|
|
extra_context={
|
|
'project_name': 'my_project',
|
|
'package_manager': 'poetry',
|
|
'crawler_type': 'beautifulsoup',
|
|
'http_client': 'impit',
|
|
'enable_apify_integration': False,
|
|
'start_url': 'https://crawlee.dev',
|
|
'install_project': True,
|
|
},
|
|
)
|
|
|
|
|
|
def test_create_interactive_non_default_template(mock_cookiecutter: Mock, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
mock_input = iter(
|
|
[
|
|
*'my_project',
|
|
readchar.key.ENTER,
|
|
readchar.key.DOWN,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
]
|
|
)
|
|
monkeypatch.setattr(target=readchar, name='readkey', value=lambda: next(mock_input))
|
|
|
|
result = runner.invoke(crawlee._cli.cli, ['create'])
|
|
assert 'Your project "my_project" was created.' in result.output
|
|
|
|
mock_cookiecutter.assert_called_with(
|
|
template=ANY,
|
|
no_input=True,
|
|
extra_context={
|
|
'project_name': 'my_project',
|
|
'package_manager': 'poetry',
|
|
'crawler_type': 'parsel',
|
|
'http_client': 'impit',
|
|
'enable_apify_integration': False,
|
|
'start_url': 'https://crawlee.dev',
|
|
'install_project': True,
|
|
},
|
|
)
|
|
|
|
|
|
def test_create_non_interactive(mock_cookiecutter: Mock) -> None:
|
|
runner.invoke(
|
|
crawlee._cli.cli,
|
|
[
|
|
'create',
|
|
'my_project',
|
|
'--crawler-type',
|
|
'playwright',
|
|
'--http-client',
|
|
'httpx',
|
|
'--package-manager',
|
|
'pip',
|
|
'--start-url',
|
|
'https://yr.no',
|
|
'--no-apify',
|
|
'--no-install',
|
|
],
|
|
)
|
|
|
|
mock_cookiecutter.assert_called_with(
|
|
template=ANY,
|
|
no_input=True,
|
|
extra_context={
|
|
'project_name': 'my_project',
|
|
'package_manager': 'pip',
|
|
'crawler_type': 'playwright',
|
|
'http_client': 'httpx',
|
|
'start_url': 'https://yr.no',
|
|
'enable_apify_integration': False,
|
|
'install_project': False,
|
|
},
|
|
)
|
|
|
|
|
|
def test_create_existing_folder(
|
|
mock_cookiecutter: Mock, monkeypatch: pytest.MonkeyPatch, tmp_path_factory: pytest.TempPathFactory
|
|
) -> None:
|
|
mock_input = iter(
|
|
[
|
|
*'my_project',
|
|
readchar.key.ENTER,
|
|
]
|
|
)
|
|
monkeypatch.setattr(target=readchar, name='readkey', value=lambda: next(mock_input))
|
|
|
|
tmp = tmp_path_factory.mktemp('workdir')
|
|
os.chdir(tmp)
|
|
(tmp / 'existing_project').mkdir()
|
|
|
|
result = runner.invoke(
|
|
crawlee._cli.cli,
|
|
[
|
|
'create',
|
|
'existing_project',
|
|
'--crawler-type',
|
|
'playwright',
|
|
'--http-client',
|
|
'httpx',
|
|
'--package-manager',
|
|
'pip',
|
|
'--start-url',
|
|
'https://yr.no',
|
|
'--no-apify',
|
|
'--install',
|
|
],
|
|
)
|
|
assert 'existing_project already exists' in result.output
|
|
|
|
mock_cookiecutter.assert_called_with(
|
|
template=ANY,
|
|
no_input=True,
|
|
extra_context={
|
|
'project_name': 'my_project',
|
|
'package_manager': 'pip',
|
|
'crawler_type': 'playwright',
|
|
'http_client': 'httpx',
|
|
'start_url': 'https://yr.no',
|
|
'enable_apify_integration': False,
|
|
'install_project': True,
|
|
},
|
|
)
|
|
|
|
|
|
def test_create_existing_folder_interactive(
|
|
mock_cookiecutter: Mock, monkeypatch: pytest.MonkeyPatch, tmp_path_factory: pytest.TempPathFactory
|
|
) -> None:
|
|
mock_input = iter(
|
|
[
|
|
*'existing_project',
|
|
readchar.key.ENTER,
|
|
*'my_project',
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
]
|
|
)
|
|
monkeypatch.setattr(target=readchar, name='readkey', value=lambda: next(mock_input))
|
|
|
|
tmp = tmp_path_factory.mktemp('workdir')
|
|
os.chdir(tmp)
|
|
(tmp / 'existing_project').mkdir()
|
|
|
|
result = runner.invoke(crawlee._cli.cli, ['create', '--template', 'playwright'])
|
|
assert 'existing_project already exists' in result.output
|
|
|
|
mock_cookiecutter.assert_called_with(
|
|
template=ANY,
|
|
no_input=True,
|
|
extra_context={
|
|
'project_name': 'my_project',
|
|
'package_manager': 'poetry',
|
|
'crawler_type': 'playwright',
|
|
'http_client': 'impit',
|
|
'start_url': 'https://crawlee.dev',
|
|
'enable_apify_integration': False,
|
|
'install_project': True,
|
|
},
|
|
)
|
|
|
|
|
|
def test_create_existing_folder_interactive_multiple_attempts(
|
|
mock_cookiecutter: Mock, monkeypatch: pytest.MonkeyPatch, tmp_path_factory: pytest.TempPathFactory
|
|
) -> None:
|
|
mock_input = iter(
|
|
[
|
|
*'existing_project',
|
|
readchar.key.ENTER,
|
|
*'existing_project_2',
|
|
readchar.key.ENTER,
|
|
*'my_project',
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
readchar.key.ENTER,
|
|
]
|
|
)
|
|
monkeypatch.setattr(target=readchar, name='readkey', value=lambda: next(mock_input))
|
|
|
|
tmp = tmp_path_factory.mktemp('workdir')
|
|
os.chdir(tmp)
|
|
(tmp / 'existing_project').mkdir()
|
|
(tmp / 'existing_project_2').mkdir()
|
|
|
|
result = runner.invoke(crawlee._cli.cli, ['create', '--crawler-type', 'playwright'])
|
|
assert 'existing_project already exists' in result.output
|
|
|
|
mock_cookiecutter.assert_called_with(
|
|
template=ANY,
|
|
no_input=True,
|
|
extra_context={
|
|
'project_name': 'my_project',
|
|
'package_manager': 'poetry',
|
|
'crawler_type': 'playwright',
|
|
'http_client': 'impit',
|
|
'start_url': 'https://crawlee.dev',
|
|
'enable_apify_integration': False,
|
|
'install_project': True,
|
|
},
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'optional_module_name',
|
|
[
|
|
pytest.param('cookiecutter', id='cookiecutter'),
|
|
pytest.param('inquirer', id='inquirer'),
|
|
pytest.param('rich', id='rich'),
|
|
pytest.param('typer', id='typer'),
|
|
],
|
|
)
|
|
def test_import_error_handled(optional_module_name: str) -> None:
|
|
# Block the package and all its submodules to prevent
|
|
# cached submodule entries from bypassing the blocked top-level package.
|
|
blocked = {
|
|
mod_name: None
|
|
for mod_name in sys.modules
|
|
if mod_name == optional_module_name or mod_name.startswith(f'{optional_module_name}.')
|
|
}
|
|
|
|
with patch.dict('sys.modules', blocked):
|
|
sys.modules.pop('crawlee._cli', None)
|
|
|
|
with pytest.raises(ImportError):
|
|
from crawlee._cli import cli # noqa: F401 PLC0415
|