Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions tests/e2e/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import os
from typing import Dict, List
from uuid import uuid4

import pytest

from nylas import Client


_API_KEY_ENV_VARS = ("NYLAS_E2E_API_KEY", "NYLAS_API_KEY")
_API_URI_ENV_VARS = ("NYLAS_E2E_API_URI", "NYLAS_API_URI")


def _first_env_value(keys: tuple) -> str:
for key in keys:
value = os.getenv(key)
if value:
return value
return ""


def extract_list_items(response_data):
"""
Normalize list endpoint response payloads across API shapes.
"""
if isinstance(response_data, list):
return response_data

if isinstance(response_data, dict):
items = response_data.get("items")
if isinstance(items, list):
return items

return []


def raw_list_ids(client: Client, path: str, id_key: str = "id", query_params=None):
json_response, _headers = client.http_client._execute(
"GET",
path,
None,
query_params or {"limit": 200},
None,
)
response_data = json_response.get("data")
items = extract_list_items(response_data)
return {item.get(id_key) for item in items if isinstance(item, dict) and item.get(id_key)}


@pytest.fixture
def raw_list_ids_helper():
return raw_list_ids


@pytest.fixture(scope="session")
def e2e_client() -> Client:
api_key = _first_env_value(_API_KEY_ENV_VARS)
if not api_key:
pytest.skip(
"E2E tests require NYLAS_E2E_API_KEY (or NYLAS_API_KEY) to be set."
)

api_uri = _first_env_value(_API_URI_ENV_VARS)
timeout = int(os.getenv("NYLAS_E2E_TIMEOUT", "90"))
if api_uri:
return Client(api_key=api_key, api_uri=api_uri, timeout=timeout)
return Client(api_key=api_key, timeout=timeout)


@pytest.fixture
def unique_name():
def _build(prefix: str) -> str:
return f"{prefix}-{uuid4().hex[:10]}"

return _build


@pytest.fixture
def e2e_resource_registry(e2e_client):
registry: Dict[str, List[str]] = {
"policies": [],
"rules": [],
"lists": [],
}
yield registry

for policy_id in reversed(registry["policies"]):
try:
e2e_client.policies.destroy(policy_id)
except Exception:
pass

for rule_id in reversed(registry["rules"]):
try:
e2e_client.rules.destroy(rule_id)
except Exception:
pass

for list_id in reversed(registry["lists"]):
try:
e2e_client.lists.destroy(list_id)
except Exception:
pass

60 changes: 60 additions & 0 deletions tests/e2e/test_lists_e2e.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import pytest


@pytest.mark.e2e
def test_lists_lifecycle_e2e(e2e_client, e2e_resource_registry, unique_name):
create_response = e2e_client.lists.create(
{
"name": unique_name("e2e-list"),
"type": "domain",
"description": "Created by SDK e2e test",
}
)
created_list = create_response.data
assert created_list.id
assert created_list.type == "domain"
e2e_resource_registry["lists"].append(created_list.id)

found_response = e2e_client.lists.find(created_list.id)
assert found_response.data.id == created_list.id

updated_name = unique_name("e2e-list-updated")
update_response = e2e_client.lists.update(
created_list.id,
{"name": updated_name, "description": "Updated by SDK e2e test"},
)
assert update_response.data.id == created_list.id
assert update_response.data.name == updated_name

first_domain = f"{unique_name('allowed')}.example"
second_domain = f"{unique_name('blocked')}.example"
add_items_response = e2e_client.lists.add_items(
created_list.id, {"items": [first_domain, second_domain]}
)
assert add_items_response.data.id == created_list.id

list_items_response = e2e_client.lists.list_items(
created_list.id, query_params={"limit": 200}
)
item_values = {item.value for item in list_items_response.data if item.value}
assert first_domain in item_values
assert second_domain in item_values

remove_items_response = e2e_client.lists.remove_items(
created_list.id, {"items": [first_domain]}
)
assert remove_items_response.data.id == created_list.id

after_remove_response = e2e_client.lists.list_items(
created_list.id, query_params={"limit": 200}
)
item_values_after_remove = {
item.value for item in after_remove_response.data if item.value
}
assert first_domain not in item_values_after_remove
assert second_domain in item_values_after_remove

destroy_response = e2e_client.lists.destroy(created_list.id)
assert destroy_response.request_id
e2e_resource_registry["lists"].remove(created_list.id)

70 changes: 70 additions & 0 deletions tests/e2e/test_policies_e2e.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import pytest


@pytest.mark.e2e
def test_policies_lifecycle_with_rule_association_e2e(
e2e_client, e2e_resource_registry, unique_name, raw_list_ids_helper
):
rule_response = e2e_client.rules.create(
{
"name": unique_name("e2e-policy-rule"),
"trigger": "inbound",
"match": {
"operator": "any",
"conditions": [
{
"field": "from.domain",
"operator": "is",
"value": "example.com",
}
],
},
"actions": [{"type": "archive"}],
}
)
created_rule = rule_response.data
assert created_rule.id
e2e_resource_registry["rules"].append(created_rule.id)

policy_response = e2e_client.policies.create(
{"name": unique_name("e2e-policy"), "rules": [created_rule.id]}
)
created_policy = policy_response.data
assert created_policy.id
e2e_resource_registry["policies"].append(created_policy.id)

find_response = e2e_client.policies.find(created_policy.id)
assert find_response.data.id == created_policy.id

updated_name = unique_name("e2e-policy-updated")
update_response = e2e_client.policies.update(
created_policy.id,
{
"name": updated_name,
"rules": [created_rule.id],
"spam_detection": {
"use_list_dnsbl": True,
"use_header_anomaly_detection": True,
},
},
)
# Some policy update responses may omit id; verify canonical state by refetching.
assert update_response.data.name == updated_name

refetch_response = e2e_client.policies.find(created_policy.id)
assert refetch_response.data.id == created_policy.id
assert refetch_response.data.name == updated_name
assert refetch_response.data.rules is not None
assert created_rule.id in refetch_response.data.rules

returned_policy_ids = raw_list_ids_helper(e2e_client, "/v3/policies")
assert created_policy.id in returned_policy_ids

destroy_policy_response = e2e_client.policies.destroy(created_policy.id)
assert destroy_policy_response.request_id
e2e_resource_registry["policies"].remove(created_policy.id)

destroy_rule_response = e2e_client.rules.destroy(created_rule.id)
assert destroy_rule_response.request_id
e2e_resource_registry["rules"].remove(created_rule.id)

51 changes: 51 additions & 0 deletions tests/e2e/test_rules_e2e.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import pytest


@pytest.mark.e2e
def test_rules_lifecycle_e2e(
e2e_client, e2e_resource_registry, unique_name, raw_list_ids_helper
):
create_response = e2e_client.rules.create(
{
"name": unique_name("e2e-rule"),
"description": "Created by SDK e2e test",
"trigger": "inbound",
"match": {
"operator": "any",
"conditions": [
{
"field": "from.domain",
"operator": "is",
"value": "example.com",
}
],
},
"actions": [{"type": "archive"}],
}
)
created_rule = create_response.data
assert created_rule.id
e2e_resource_registry["rules"].append(created_rule.id)

find_response = e2e_client.rules.find(created_rule.id)
assert find_response.data.id == created_rule.id

updated_name = unique_name("e2e-rule-updated")
update_response = e2e_client.rules.update(
created_rule.id,
{
"name": updated_name,
"enabled": False,
"actions": [{"type": "mark_as_spam"}],
},
)
assert update_response.data.id == created_rule.id
assert update_response.data.name == updated_name

returned_rule_ids = raw_list_ids_helper(e2e_client, "/v3/rules")
assert created_rule.id in returned_rule_ids

destroy_response = e2e_client.rules.destroy(created_rule.id)
assert destroy_response.request_id
e2e_resource_registry["rules"].remove(created_rule.id)

Loading