diff --git a/tests/integration/test_actor_events.py b/tests/integration/test_actor_events.py new file mode 100644 index 00000000..7b6206ac --- /dev/null +++ b/tests/integration/test_actor_events.py @@ -0,0 +1,71 @@ +import asyncio + +from apify import Actor +from apify.consts import ActorEventType +from apify_client import ApifyClientAsync + +from .conftest import ActorFactory + + +class TestActorEvents: + + async def test_interval_events(self, make_actor: ActorFactory, apify_client_async: ApifyClientAsync) -> None: + async def main() -> None: + import os + + from apify.consts import ActorEventType, ApifyEnvVars + + os.environ[ApifyEnvVars.PERSIST_STATE_INTERVAL_MILLIS] = '900' + + def on_event(event_type): # type: ignore + async def log_event(data): # type: ignore + await Actor.push_data({'event_type': event_type, 'data': data}) + return log_event + + async with Actor: + Actor.on(ActorEventType.SYSTEM_INFO, on_event(ActorEventType.SYSTEM_INFO)) # type: ignore + Actor.on(ActorEventType.PERSIST_STATE, on_event(ActorEventType.PERSIST_STATE)) # type: ignore + await asyncio.sleep(3) + + actor = await make_actor('actor-interval-events', main_func=main) + + run_result = await actor.call() + + assert run_result is not None + assert run_result['status'] == 'SUCCEEDED' + dataset_items_page = await apify_client_async.dataset(run_result['defaultDatasetId']).list_items() + persist_state_events = [item for item in dataset_items_page.items if item['event_type'] == ActorEventType.PERSIST_STATE] + system_info_events = [item for item in dataset_items_page.items if item['event_type'] == ActorEventType.SYSTEM_INFO] + assert len(persist_state_events) > 2 + assert len(system_info_events) > 0 + + async def test_off_event(self, make_actor: ActorFactory) -> None: + async def main() -> None: + import os + + from apify.consts import ActorEventType, ApifyEnvVars + + os.environ[ApifyEnvVars.PERSIST_STATE_INTERVAL_MILLIS] = '100' + + counter = 0 + + def count_event(data): # type: ignore + nonlocal counter + print(data) + counter += 1 + + async with Actor: + Actor.on(ActorEventType.PERSIST_STATE, count_event) + await asyncio.sleep(0.5) + assert counter > 1 + last_count = counter + Actor.off(ActorEventType.PERSIST_STATE, count_event) + await asyncio.sleep(0.5) + assert counter == last_count + + actor = await make_actor('actor-off-event', main_func=main) + + run = await actor.call() + + assert run is not None + assert run['status'] == 'SUCCEEDED' diff --git a/tests/integration/test_actor_lifecycle.py b/tests/integration/test_actor_lifecycle.py new file mode 100644 index 00000000..993e646b --- /dev/null +++ b/tests/integration/test_actor_lifecycle.py @@ -0,0 +1,146 @@ +from apify import Actor +from apify_client import ApifyClientAsync + +from .conftest import ActorFactory + + +class TestActorInit: + + async def test_actor_init(self, make_actor: ActorFactory) -> None: + async def main() -> None: + my_actor = Actor() + await my_actor.init() + assert my_actor._is_initialized is True + double_init = False + try: + await my_actor.init() + double_init = True + except RuntimeError as err: + assert str(err) == 'The actor was already initialized!' + except Exception as err: + raise err + try: + await Actor.init() + double_init = True + except RuntimeError as err: + assert str(err) == 'The actor was already initialized!' + except Exception as err: + raise err + await my_actor.exit() + assert double_init is False + assert my_actor._is_initialized is False + + actor = await make_actor('actor-init', main_func=main) + + run_result = await actor.call() + + assert run_result is not None + assert run_result['status'] == 'SUCCEEDED' + + async def test_async_with_actor_properly_initialize(self, make_actor: ActorFactory) -> None: + async def main() -> None: + async with Actor: + assert Actor._get_default_instance()._is_initialized + assert Actor._get_default_instance()._is_initialized is False + + actor = await make_actor('with-actor-init', main_func=main) + + run_result = await actor.call() + + assert run_result is not None + assert run_result['status'] == 'SUCCEEDED' + + +class TestActorExit: + + async def test_actor_exit_code(self, make_actor: ActorFactory) -> None: + async def main() -> None: + async with Actor: + input = await Actor.get_input() + await Actor.exit(**input) + + actor = await make_actor('actor-exit', main_func=main) + + for exit_code in [0, 1, 101]: + run_result = await actor.call(run_input={'exit_code': exit_code}) + assert run_result is not None + assert run_result['exitCode'] == exit_code + assert run_result['status'] == 'FAILED' if exit_code > 0 else 'SUCCEEDED' + + +class TestActorFail: + + async def test_fail_exit_code(self, make_actor: ActorFactory) -> None: + async def main() -> None: + async with Actor: + input = await Actor.get_input() + await Actor.fail(**input) if input else await Actor.fail() + + actor = await make_actor('actor-fail', main_func=main) + + run_result = await actor.call() + assert run_result is not None + assert run_result['exitCode'] == 1 + assert run_result['status'] == 'FAILED' + + for exit_code in [1, 10, 100]: + run_result = await actor.call(run_input={'exit_code': exit_code}) + assert run_result is not None + assert run_result['exitCode'] == exit_code + assert run_result['status'] == 'FAILED' + + async def test_with_actor_fail_correctly(self, make_actor: ActorFactory) -> None: + async def main() -> None: + async with Actor: + raise Exception('This is a test exception') + + actor = await make_actor('with-actor-fail', main_func=main) + run_result = await actor.call() + assert run_result is not None + assert run_result['exitCode'] == 91 + assert run_result['status'] == 'FAILED' + + +class TestActorMain: + + async def test_actor_main(self, make_actor: ActorFactory, apify_client_async: ApifyClientAsync) -> None: + async def main() -> None: + async def actor_function() -> None: + input = await Actor.get_input() + if input.get('raise_exception'): + raise Exception(input.get('raise_exception')) + elif input.get('exit_code'): + await Actor.exit(exit_code=input.get('exit_code')) + elif input.get('fail'): + await Actor.fail() + elif input.get('set_output'): + await Actor.set_value('OUTPUT', input.get('set_output')) + print('Main function called') + + await Actor.main(actor_function) + + actor = await make_actor('actor-main', main_func=main) + + exception_run = await actor.call(run_input={'raise_exception': 'This is a test exception'}) + assert exception_run is not None + assert exception_run['status'] == 'FAILED' + assert exception_run['exitCode'] == 91 + + exit_code = 10 + exited_run = await actor.call(run_input={'exit_code': exit_code}) + assert exited_run is not None + assert exited_run['status'] == 'FAILED' + assert exited_run['exitCode'] == exit_code + + failed_run = await actor.call(run_input={'fail': True}) + assert failed_run is not None + assert failed_run['status'] == 'FAILED' + assert failed_run['exitCode'] == 1 + + test_output = {'test': 'output'} + run_with_output = await actor.call(run_input={'set_output': test_output}) + assert run_with_output is not None + assert run_with_output['status'] == 'SUCCEEDED' + output = await apify_client_async.key_value_store(run_with_output['defaultKeyValueStoreId']).get_record('OUTPUT') + assert output is not None + assert output['value'] == test_output diff --git a/tests/unit/actor/test_actor_lifecycle.py b/tests/unit/actor/test_actor_lifecycle.py index de6416cf..0efb5e6d 100644 --- a/tests/unit/actor/test_actor_lifecycle.py +++ b/tests/unit/actor/test_actor_lifecycle.py @@ -6,7 +6,6 @@ from apify import Actor from apify.consts import ActorEventType, ApifyEnvVars -from apify_client import ApifyClientAsync class TestActorInit: @@ -64,7 +63,7 @@ def on_event(event_type: ActorEventType) -> Callable: on_system_info_count = len(on_system_info) assert on_persist_count != 0 assert on_system_info_count != 0 - # Check if envents stopped emitting. + # Check if events stopped emitting. await asyncio.sleep(0.2) assert on_persist_count == len(on_persist) assert on_system_info_count == len(on_system_info) @@ -91,7 +90,7 @@ async def test_with_actor_failed(self) -> None: pass assert my_actor._is_initialized is False - async def test_raise_on_fail_witout_init(self) -> None: + async def test_raise_on_fail_without_init(self) -> None: with pytest.raises(RuntimeError): await Actor.fail() @@ -137,19 +136,3 @@ async def actor_function() -> str: returned_value = await my_actor.main(actor_function) assert returned_value == expected_string - - class TestActorNewClient: - - async def test_actor_new_client_config(self, monkeypatch: pytest.MonkeyPatch) -> None: - token = 'my-token' - monkeypatch.setenv(ApifyEnvVars.TOKEN, token) - my_actor = Actor() - await my_actor.init() - client = my_actor.new_client() - assert type(client) == ApifyClientAsync - assert client.token == token - passed_token = 'my-passed-token' - client_with_token = my_actor.new_client(token=passed_token) - assert type(client_with_token) == ApifyClientAsync - assert client_with_token.token == passed_token - await my_actor.exit()