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
22 changes: 21 additions & 1 deletion tests/test_api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@

from tests.utils.fixtures.mock_api_key import MockApiKey
from tests.utils.syncify import syncify
from workos.api_keys import API_KEY_VALIDATION_PATH, ApiKeys, AsyncApiKeys
from workos.api_keys import (
API_KEYS_PATH,
API_KEY_VALIDATION_PATH,
ApiKeys,
AsyncApiKeys,
)


@pytest.mark.sync_and_async(ApiKeys, AsyncApiKeys)
Expand Down Expand Up @@ -48,3 +53,18 @@ def test_validate_api_key_with_invalid_key(
)

assert syncify(module_instance.validate_api_key(value="invalid-key")) is None

def test_delete_api_key(
self,
module_instance,
capture_and_mock_http_client_request,
):
api_key_id = "api_key_01234567890"
request_kwargs = capture_and_mock_http_client_request(
module_instance._http_client, {}, 204
)

syncify(module_instance.delete_api_key(api_key_id))

assert request_kwargs["url"].endswith(f"{API_KEYS_PATH}/{api_key_id}")
assert request_kwargs["method"] == "delete"
113 changes: 113 additions & 0 deletions tests/test_organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Union
import pytest
from tests.types.test_auto_pagination_function import TestAutoPaginationFunction
from tests.utils.fixtures.mock_api_key import MockApiKey, MockApiKeyWithValue
from tests.utils.fixtures.mock_feature_flag import MockFeatureFlag
from tests.utils.fixtures.mock_organization import MockOrganization
from tests.utils.fixtures.mock_role import MockRole
Expand Down Expand Up @@ -298,3 +299,115 @@ def to_dict(x):
list(map(to_dict, feature_flags_response.data))
== mock_feature_flags["data"]
)

@pytest.fixture
def mock_api_key_with_value(self):
return MockApiKeyWithValue().dict()

@pytest.fixture
def mock_api_keys(self):
api_key_list = [MockApiKey(id=f"api_key_{i}").dict() for i in range(5)]
return {
"data": api_key_list,
"list_metadata": {"before": None, "after": None},
"object": "list",
}

@pytest.fixture
def mock_api_keys_multiple_pages(self):
api_key_list = [MockApiKey(id=f"api_key_{i}").dict() for i in range(40)]
return list_response_of(data=api_key_list)

def test_create_api_key(
self, mock_api_key_with_value, capture_and_mock_http_client_request
):
request_kwargs = capture_and_mock_http_client_request(
self.http_client, mock_api_key_with_value, 201
)

api_key = syncify(
self.organizations.create_api_key(
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
name="Production API Key",
permissions=["posts:read", "posts:write"],
)
)

assert request_kwargs["method"] == "post"
assert request_kwargs["url"].endswith(
"/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T/api_keys"
)
assert request_kwargs["json"]["name"] == "Production API Key"
assert request_kwargs["json"]["permissions"] == ["posts:read", "posts:write"]
assert api_key.id == mock_api_key_with_value["id"]
assert api_key.value == mock_api_key_with_value["value"]
assert api_key.object == "api_key"

def test_create_api_key_without_permissions(
self, mock_api_key_with_value, capture_and_mock_http_client_request
):
request_kwargs = capture_and_mock_http_client_request(
self.http_client, mock_api_key_with_value, 201
)

api_key = syncify(
self.organizations.create_api_key(
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
name="Basic API Key",
)
)

assert request_kwargs["method"] == "post"
assert request_kwargs["json"]["name"] == "Basic API Key"
assert request_kwargs["json"].get("permissions") is None
assert api_key.id == mock_api_key_with_value["id"]

def test_list_api_keys(self, mock_api_keys, capture_and_mock_http_client_request):
request_kwargs = capture_and_mock_http_client_request(
self.http_client, mock_api_keys, 200
)

api_keys_response = syncify(
self.organizations.list_api_keys(
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T"
)
)

def to_dict(x):
return x.dict()

assert request_kwargs["method"] == "get"
assert request_kwargs["url"].endswith(
"/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T/api_keys"
)
assert list(map(to_dict, api_keys_response.data)) == mock_api_keys["data"]

def test_list_api_keys_with_pagination_params(
self, mock_api_keys, capture_and_mock_http_client_request
):
request_kwargs = capture_and_mock_http_client_request(
self.http_client, mock_api_keys, 200
)

syncify(
self.organizations.list_api_keys(
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
limit=5,
order="asc",
)
)

assert request_kwargs["params"]["limit"] == 5
assert request_kwargs["params"]["order"] == "asc"

def test_list_api_keys_auto_pagination_for_multiple_pages(
self,
mock_api_keys_multiple_pages,
test_auto_pagination: TestAutoPaginationFunction,
):
test_auto_pagination(
http_client=self.http_client,
list_function=self.organizations.list_api_keys,
expected_all_page_data=mock_api_keys_multiple_pages["data"],
list_function_params={"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T"},
)
19 changes: 18 additions & 1 deletion tests/utils/fixtures/mock_api_key.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import datetime

from workos.types.api_keys import ApiKey
from workos.types.api_keys import ApiKey, ApiKeyWithValue


class MockApiKey(ApiKey):
Expand All @@ -17,3 +17,20 @@ def __init__(self, id="api_key_01234567890"):
created_at=now,
updated_at=now,
)


class MockApiKeyWithValue(ApiKeyWithValue):
def __init__(self, id="api_key_01234567890"):
now = datetime.datetime.now().isoformat()
super().__init__(
object="api_key",
id=id,
owner={"type": "organization", "id": "org_1337"},
name="Development API Key",
obfuscated_value="sk_...xyz",
value="sk_live_abc123xyz",
permissions=["posts:read", "posts:write"],
last_used_at=None,
created_at=now,
updated_at=now,
)
26 changes: 25 additions & 1 deletion workos/api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from workos.types.api_keys import ApiKey
from workos.typing.sync_or_async import SyncOrAsync
from workos.utils.http_client import AsyncHTTPClient, SyncHTTPClient
from workos.utils.request_helper import REQUEST_METHOD_POST
from workos.utils.request_helper import REQUEST_METHOD_DELETE, REQUEST_METHOD_POST

API_KEYS_PATH = "api_keys"
API_KEY_VALIDATION_PATH = "api_keys/validations"
RESOURCE_OBJECT_ATTRIBUTE_NAME = "api_key"

Expand All @@ -22,6 +23,17 @@ def validate_api_key(self, *, value: str) -> SyncOrAsync[Optional[ApiKey]]:
"""
...

def delete_api_key(self, api_key_id: str) -> SyncOrAsync[None]:
"""Delete an API key.

Args:
api_key_id (str): The ID of the API key to delete

Returns:
None
"""
...


class ApiKeys(ApiKeysModule):
_http_client: SyncHTTPClient
Expand All @@ -37,6 +49,12 @@ def validate_api_key(self, *, value: str) -> Optional[ApiKey]:
return None
return ApiKey.model_validate(response[RESOURCE_OBJECT_ATTRIBUTE_NAME])

def delete_api_key(self, api_key_id: str) -> None:
self._http_client.request(
f"{API_KEYS_PATH}/{api_key_id}",
method=REQUEST_METHOD_DELETE,
)


class AsyncApiKeys(ApiKeysModule):
_http_client: AsyncHTTPClient
Expand All @@ -51,3 +69,9 @@ async def validate_api_key(self, *, value: str) -> Optional[ApiKey]:
if response.get(RESOURCE_OBJECT_ATTRIBUTE_NAME) is None:
return None
return ApiKey.model_validate(response[RESOURCE_OBJECT_ATTRIBUTE_NAME])

async def delete_api_key(self, api_key_id: str) -> None:
await self._http_client.request(
f"{API_KEYS_PATH}/{api_key_id}",
method=REQUEST_METHOD_DELETE,
)
Loading