-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add smoke test using fake integration
Also separate test helpers to fixtures and ocean app helpers
- Loading branch information
Showing
7 changed files
with
336 additions
and
81 deletions.
There are no files selected for viewing
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
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,4 @@ | ||
# ruff: noqa | ||
from port_ocean.tests.helpers.fixtures import ( | ||
port_client_for_fake_integration, | ||
) |
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 |
---|---|---|
@@ -1,80 +0,0 @@ | ||
import sys | ||
from inspect import getmembers | ||
from pathlib import Path | ||
from typing import Dict, List, Set, Tuple, Union | ||
|
||
from yaml import safe_load | ||
|
||
from port_ocean.bootstrap import create_default_app | ||
from port_ocean.core.handlers.port_app_config.models import ResourceConfig | ||
from port_ocean.core.ocean_types import RESYNC_RESULT | ||
from port_ocean.ocean import Ocean | ||
from port_ocean.utils.misc import get_spec_file, load_module | ||
|
||
|
||
def get_integration_ocean_app(integration_path: str) -> Ocean: | ||
spec_file = get_spec_file(Path(integration_path)) | ||
|
||
config_factory = None if not spec_file else spec_file.get("configurations", []) | ||
|
||
default_app = create_default_app( | ||
integration_path, | ||
config_factory, | ||
{ | ||
"port": { | ||
"client_id": "bla", | ||
"client_secret": "bla", | ||
}, | ||
}, | ||
) | ||
main_path = f"{integration_path}/main.py" | ||
sys.path.append(integration_path) | ||
app_module = load_module(main_path) | ||
app: Ocean = {name: item for name, item in getmembers(app_module)}.get( | ||
"app", default_app | ||
) | ||
|
||
return app | ||
|
||
|
||
def get_integation_resource_configs(integration_path: str) -> List[ResourceConfig]: | ||
with open( | ||
f"{integration_path}/.port/resources/port-app-config.yml" | ||
) as port_app_config_file: | ||
resource_configs = safe_load(port_app_config_file) | ||
|
||
return [ResourceConfig(**item) for item in resource_configs["resources"]] | ||
|
||
|
||
def get_integation_resource_config_by_name( | ||
integration_path: str, kind: str | ||
) -> Union[ResourceConfig, None]: | ||
resource_configs = get_integation_resource_configs(integration_path) | ||
|
||
relevant_configs = [x for x in resource_configs if x.kind == kind] | ||
|
||
return relevant_configs[0] if len(relevant_configs) else None | ||
|
||
|
||
async def get_raw_result_on_integration_sync_kinds( | ||
integration_path: str, override_kinds: Union[Set[str], None] = None | ||
) -> Dict[str, List[Tuple[RESYNC_RESULT, List[Exception]]]]: | ||
app = get_integration_ocean_app(integration_path) | ||
|
||
resource_configs = get_integation_resource_configs(integration_path) | ||
|
||
if override_kinds: | ||
resource_configs = [x for x in resource_configs if x.kind in override_kinds] | ||
|
||
results: Dict[str, List[Tuple[RESYNC_RESULT, List[Exception]]]] = {} | ||
|
||
for resource_config in resource_configs: | ||
resource_result = await app.integration._get_resource_raw_results( | ||
resource_config | ||
) | ||
|
||
results[resource_config.kind] = results.get(resource_config.kind, []) + [ | ||
resource_result | ||
] | ||
|
||
return results | ||
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,110 @@ | ||
from os import environ, path | ||
from typing import Any, AsyncGenerator, Callable, List, Tuple, Union | ||
|
||
import pytest_asyncio | ||
from pydantic import BaseModel | ||
|
||
from port_ocean.clients.port.client import PortClient | ||
from port_ocean.core.handlers.port_app_config.models import ResourceConfig | ||
from port_ocean.ocean import Ocean | ||
from port_ocean.tests.helpers.ocean_app import ( | ||
get_integation_resource_configs, | ||
get_integration_ocean_app, | ||
) | ||
|
||
|
||
def get_port_client_for_integration( | ||
client_id: str, | ||
client_secret: str, | ||
integration_identifier: str, | ||
integration_type: str, | ||
integration_version: str, | ||
base_url: Union[str, None], | ||
) -> PortClient: | ||
return PortClient( | ||
base_url=base_url or "https://api.getport/io", | ||
client_id=client_id, | ||
client_secret=client_secret, | ||
integration_identifier=integration_identifier, | ||
integration_type=integration_type, | ||
integration_version=integration_version, | ||
) | ||
|
||
|
||
async def cleanup_integration(client: PortClient, blueprints: List[str]) -> None: | ||
for blueprint in blueprints: | ||
bp = await client.get_blueprint(blueprint) | ||
if bp is not None: | ||
migration_id = await client.delete_blueprint( | ||
identifier=blueprint, delete_entities=True | ||
) | ||
if migration_id: | ||
await client.wait_for_migration_to_complete(migration_id=migration_id) | ||
headers = await client.auth.headers() | ||
await client.client.delete(f"{client.auth.api_url}/integrations", headers=headers) | ||
|
||
|
||
class SmokeTestDetails(BaseModel): | ||
integration_identifier: str | ||
blueprint_department: str | ||
blueprint_person: str | ||
|
||
|
||
@pytest_asyncio.fixture() | ||
async def port_client_for_fake_integration() -> ( | ||
AsyncGenerator[Tuple[SmokeTestDetails, PortClient], None] | ||
): | ||
blueprint_department = "fake-department" | ||
blueprint_person = "fake-person" | ||
integration_identifier = "smoke-test-integration" | ||
smoke_test_suffix = environ.get("SMOKE_TEST_SUFFIX") | ||
client_id = environ.get("PORT_CLIENT_ID") | ||
client_secret = environ.get("PORT_CLIENT_SECRET") | ||
|
||
if not client_secret or not client_id: | ||
assert False, "Missing port credentials" | ||
|
||
base_url = environ.get("PORT_BASE_URL") | ||
integration_version = "0.1.1-dev" | ||
integration_type = "smoke-test" | ||
if smoke_test_suffix is not None: | ||
integration_identifier = f"{integration_identifier}-{smoke_test_suffix}" | ||
blueprint_person = f"{blueprint_person}-{smoke_test_suffix}" | ||
blueprint_department = f"{blueprint_department}-{smoke_test_suffix}" | ||
|
||
client = get_port_client_for_integration( | ||
client_id, | ||
client_secret, | ||
integration_identifier, | ||
integration_type, | ||
integration_version, | ||
base_url, | ||
) | ||
|
||
smoke_test_details = SmokeTestDetails( | ||
integration_identifier=integration_identifier, | ||
blueprint_person=blueprint_person, | ||
blueprint_department=blueprint_department, | ||
) | ||
yield smoke_test_details, client | ||
await cleanup_integration(client, [blueprint_department, blueprint_person]) | ||
|
||
|
||
@pytest_asyncio.fixture | ||
def get_mocked_ocean_app(request: Any) -> Callable[[], Ocean]: | ||
test_dir = path.join(path.dirname(request.module.__file__), "..") | ||
|
||
def get_ocean_app() -> Ocean: | ||
return get_integration_ocean_app(test_dir) | ||
|
||
return get_ocean_app | ||
|
||
|
||
@pytest_asyncio.fixture | ||
def get_mock_ocean_resource_configs(request: Any) -> Callable[[], List[ResourceConfig]]: | ||
module_dir = path.join(path.dirname(request.module.__file__), "..") | ||
|
||
def get_ocean_resource_configs() -> List[ResourceConfig]: | ||
return get_integation_resource_configs(module_dir) | ||
|
||
return get_ocean_resource_configs |
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,54 @@ | ||
import sys | ||
from inspect import getmembers | ||
from pathlib import Path | ||
from typing import List, Tuple | ||
|
||
from yaml import safe_load | ||
|
||
from port_ocean.bootstrap import create_default_app | ||
from port_ocean.core.handlers.port_app_config.models import ResourceConfig | ||
from port_ocean.core.ocean_types import RESYNC_RESULT | ||
from port_ocean.ocean import Ocean | ||
from port_ocean.utils.misc import get_spec_file, load_module | ||
|
||
|
||
def get_integration_ocean_app(integration_path: str) -> Ocean: | ||
spec_file = get_spec_file(Path(integration_path)) | ||
|
||
config_factory = None if not spec_file else spec_file.get("configurations", []) | ||
|
||
default_app = create_default_app( | ||
integration_path, | ||
config_factory, | ||
{ | ||
"port": { | ||
"client_id": "bla", | ||
"client_secret": "bla", | ||
}, | ||
}, | ||
) | ||
main_path = f"{integration_path}/main.py" | ||
sys.path.append(integration_path) | ||
app_module = load_module(main_path) | ||
app: Ocean = {name: item for name, item in getmembers(app_module)}.get( | ||
"app", default_app | ||
) | ||
|
||
return app | ||
|
||
|
||
def get_integation_resource_configs(integration_path: str) -> List[ResourceConfig]: | ||
with open( | ||
f"{integration_path}/.port/resources/port-app-config.yml" | ||
) as port_app_config_file: | ||
resource_configs = safe_load(port_app_config_file) | ||
|
||
return [ResourceConfig(**item) for item in resource_configs["resources"]] | ||
|
||
|
||
async def get_raw_result_on_integration_sync_resource_config( | ||
app: Ocean, resource_config: ResourceConfig | ||
) -> Tuple[RESYNC_RESULT, List[Exception]]: | ||
resource_result = await app.integration._get_resource_raw_results(resource_config) | ||
|
||
return resource_result |
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,71 @@ | ||
from os import environ | ||
from typing import Tuple | ||
import pytest | ||
|
||
from port_ocean.clients.port.client import PortClient | ||
from port_ocean.clients.port.types import UserAgentType | ||
from port_ocean.tests.helpers.fixtures import SmokeTestDetails | ||
|
||
|
||
@pytest.mark.skipif( | ||
environ.get("SMOKE_TEST_SUFFIX", None) is None, | ||
reason="You need to run the fake integration once", | ||
) | ||
async def test_valid_fake_integration( | ||
port_client_for_fake_integration: Tuple[SmokeTestDetails, PortClient], | ||
) -> None: | ||
_, port_client = port_client_for_fake_integration | ||
current_integration = await port_client.get_current_integration() | ||
assert current_integration is not None | ||
assert current_integration.get("resyncState") is not None | ||
assert current_integration.get("resyncState", {}).get("status") == "completed" | ||
|
||
|
||
@pytest.mark.skipif( | ||
environ.get("SMOKE_TEST_SUFFIX", None) is None, | ||
reason="You need to run the fake integration once", | ||
) | ||
async def test_valid_fake_departments( | ||
port_client_for_fake_integration: Tuple[SmokeTestDetails, PortClient], | ||
) -> None: | ||
details, port_client = port_client_for_fake_integration | ||
entities = await port_client.search_entities(user_agent_type=UserAgentType.exporter) | ||
assert len(entities) | ||
departments = [ | ||
x for x in entities if f"{x.blueprint}" == details.blueprint_department | ||
] | ||
assert len(departments) == 5 | ||
|
||
|
||
@pytest.mark.skipif( | ||
environ.get("SMOKE_TEST_SUFFIX", None) is None, | ||
reason="You need to run the fake integration once", | ||
) | ||
async def test_valid_fake_persons( | ||
port_client_for_fake_integration: Tuple[SmokeTestDetails, PortClient], | ||
) -> None: | ||
details, port_client = port_client_for_fake_integration | ||
headers = await port_client.auth.headers() | ||
fake_person_entities_result = await port_client.client.get( | ||
f"{port_client.auth.api_url}/blueprints/{details.blueprint_person}/entities", | ||
headers=headers, | ||
) | ||
|
||
fake_person_entities = fake_person_entities_result.json()["entities"] | ||
assert len(fake_person_entities) | ||
|
||
fake_departments_result = await port_client.client.get( | ||
f"{port_client.auth.api_url}/blueprints/{details.blueprint_department}/entities", | ||
headers=headers, | ||
) | ||
|
||
departments = [x["identifier"] for x in fake_departments_result.json()["entities"]] | ||
|
||
for department in departments: | ||
assert len( | ||
[ | ||
x | ||
for x in fake_person_entities | ||
if x["relations"]["department"] == department | ||
] | ||
) |
Oops, something went wrong.