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
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- ubuntu-latest
- macos-latest
- windows-latest
async:
- true
- false
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
Expand All @@ -45,12 +48,13 @@ jobs:
- name: Run test suite
run: tox --skip-pkg-install
env:
COVERAGE_FILE: "coverage.${{ matrix.os }}.${{ matrix.py }}"
COVERAGE_FILE: "coverage.${{ matrix.os }}.${{ matrix.py }}.${{matrix.async}}"
ASYNC_MODE: ${{matrix.async}}
- name: Store coverage file
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: coverage.${{ matrix.os }}.${{ matrix.py }}
path: coverage.${{ matrix.os }}.${{ matrix.py }}
name: "coverage.${{ matrix.os }}.${{ matrix.py }}.${{matrix.async}}"
path: "coverage.${{ matrix.os }}.${{ matrix.py }}.${{matrix.async}}"
if-no-files-found: error

coverage:
Expand Down
369 changes: 292 additions & 77 deletions .gitleaksignore

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ omit = ["descope/flask/*"]


[tool.coverage.report]
fail_under = 97
fail_under = 98
skip_covered = true
skip_empty = true

Expand All @@ -89,3 +89,6 @@ profile = "black"
per-file-ignores = "__init__.py:F401"
ignore = "E501,N818,W503"
max-line-length = 120

[tool.pytest.ini_options]
asyncio_default_fixture_loop_scope = "function"
389 changes: 164 additions & 225 deletions requirements.txt

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ def sdk_version():
}


class DescopeTest(unittest.TestCase):
class DescopeTest(unittest.IsolatedAsyncioTestCase):
async_test = False

def setUp(self) -> None:
os.environ["DESCOPE_BASE_URI"] = (
DEFAULT_BASE_URL # Make sure tests always running against localhost
)

self.async_test = os.environ.get("ASYNC_MODE", "False").lower() == "true"
175 changes: 99 additions & 76 deletions tests/management/test_access_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

from descope import AssociatedTenant, AuthException, DescopeClient
from descope.common import DEFAULT_TIMEOUT_SECONDS
from descope.future_utils import futu_await
from descope.management.common import MgmtV1

from tests.testutils import SSLMatcher
from tests.testutils import SSLMatcher, mock_http_call
from .. import common


Expand All @@ -25,42 +26,46 @@ def setUp(self) -> None:
"y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0",
}

def test_create(self):
async def test_create(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
async_mode=self.async_test,
)

# Test failed flows
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = False
self.assertRaises(
AuthException,
client.mgmt.access_key.create,
"key-name",
)
with self.assertRaises(AuthException):
await futu_await(
client.mgmt.access_key.create(
"key-name",
)
)

# Test success flow
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
network_resp = mock.Mock()
network_resp.is_success = True
network_resp.json.return_value = json.loads(
"""{"key": {"id": "ak1"}, "cleartext": "abc"}"""
)
mock_post.return_value = network_resp
resp = client.mgmt.access_key.create(
name="key-name",
expire_time=123456789,
key_tenants=[
AssociatedTenant("tenant1"),
AssociatedTenant("tenant2", ["role1", "role2"]),
],
user_id="userid",
custom_claims={"k1": "v1"},
description="this is my access key",
permitted_ips=["10.0.0.1", "192.168.1.0/24"],
resp = await futu_await(
client.mgmt.access_key.create(
name="key-name",
expire_time=123456789,
key_tenants=[
AssociatedTenant("tenant1"),
AssociatedTenant("tenant2", ["role1", "role2"]),
],
user_id="userid",
custom_claims={"k1": "v1"},
description="this is my access key",
permitted_ips=["10.0.0.1", "192.168.1.0/24"],
)
)
access_key = resp["key"]
self.assertEqual(access_key["id"], "ak1")
Expand Down Expand Up @@ -90,30 +95,32 @@ def test_create(self):
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_load(self):
async def test_load(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
async_mode=self.async_test,
)

# Test failed flows
with patch("httpx.get") as mock_get:
with mock_http_call(self.async_test, "get") as mock_get:
mock_get.return_value.is_success = False
self.assertRaises(
AuthException,
client.mgmt.access_key.load,
"key-id",
)
with self.assertRaises(AuthException):
await futu_await(
client.mgmt.access_key.load(
"key-id",
)
)

# Test success flow
with patch("httpx.get") as mock_get:
with mock_http_call(self.async_test, "get") as mock_get:
network_resp = mock.Mock()
network_resp.is_success = True
network_resp.json.return_value = json.loads("""{"key": {"id": "ak1"}}""")
mock_get.return_value = network_resp
resp = client.mgmt.access_key.load("key-id")
resp = await futu_await(client.mgmt.access_key.load("key-id"))
access_key = resp["key"]
self.assertEqual(access_key["id"], "ak1")
mock_get.assert_called_with(
Expand All @@ -129,32 +136,36 @@ def test_load(self):
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_search_all_users(self):
async def test_search_all_users(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
async_mode=self.async_test,
)

# Test failed flows
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = False
self.assertRaises(
AuthException,
client.mgmt.access_key.search_all_access_keys,
["t1, t2"],
)
with self.assertRaises(AuthException):
await futu_await(
client.mgmt.access_key.search_all_access_keys(
["t1, t2"],
)
)

# Test success flow
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
network_resp = mock.Mock()
network_resp.is_success = True
network_resp.json.return_value = json.loads(
"""{"keys": [{"id": "ak1"}, {"id": "ak2"}]}"""
)
mock_post.return_value = network_resp
resp = client.mgmt.access_key.search_all_access_keys(["t1, t2"])
resp = await futu_await(
client.mgmt.access_key.search_all_access_keys(["t1, t2"])
)
keys = resp["keys"]
self.assertEqual(len(keys), 2)
self.assertEqual(keys[0]["id"], "ak1")
Expand All @@ -175,30 +186,34 @@ def test_search_all_users(self):
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_update(self):
async def test_update(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
async_mode=self.async_test,
)

# Test failed flows
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = False
self.assertRaises(
AuthException,
client.mgmt.access_key.update,
"key-id",
"new-name",
)
with self.assertRaises(AuthException):
await futu_await(
client.mgmt.access_key.update(
"key-id",
"new-name",
)
)

# Test success flow
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = True
self.assertIsNone(
client.mgmt.access_key.update(
"key-id", name="new-name", description=None
await futu_await(
client.mgmt.access_key.update(
"key-id", name="new-name", description=None
)
)
)
mock_post.assert_called_with(
Expand All @@ -219,27 +234,31 @@ def test_update(self):
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_deactivate(self):
async def test_deactivate(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
async_mode=self.async_test,
)

# Test failed flows
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = False
self.assertRaises(
AuthException,
client.mgmt.access_key.deactivate,
"key-id",
)
with self.assertRaises(AuthException):
await futu_await(
client.mgmt.access_key.deactivate(
"key-id",
)
)

# Test success flow
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = True
self.assertIsNone(client.mgmt.access_key.deactivate("ak1"))
self.assertIsNone(
await futu_await(client.mgmt.access_key.deactivate("ak1"))
)
mock_post.assert_called_with(
f"{common.DEFAULT_BASE_URL}{MgmtV1.access_key_deactivate_path}",
headers={
Expand All @@ -256,27 +275,29 @@ def test_deactivate(self):
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_activate(self):
async def test_activate(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
async_mode=self.async_test,
)

# Test failed flows
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = False
self.assertRaises(
AuthException,
client.mgmt.access_key.activate,
"key-id",
)
with self.assertRaises(AuthException):
await futu_await(
client.mgmt.access_key.activate(
"key-id",
)
)

# Test success flow
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = True
self.assertIsNone(client.mgmt.access_key.activate("ak1"))
self.assertIsNone(await futu_await(client.mgmt.access_key.activate("ak1")))
mock_post.assert_called_with(
f"{common.DEFAULT_BASE_URL}{MgmtV1.access_key_activate_path}",
headers={
Expand All @@ -293,27 +314,29 @@ def test_activate(self):
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_delete(self):
async def test_delete(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
async_mode=self.async_test,
)

# Test failed flows
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = False
self.assertRaises(
AuthException,
client.mgmt.access_key.delete,
"key-id",
)
with self.assertRaises(AuthException):
await futu_await(
client.mgmt.access_key.delete(
"key-id",
)
)

# Test success flow
with patch("httpx.post") as mock_post:
with mock_http_call(self.async_test, "post") as mock_post:
mock_post.return_value.is_success = True
self.assertIsNone(client.mgmt.access_key.delete("ak1"))
self.assertIsNone(await futu_await(client.mgmt.access_key.delete("ak1")))
mock_post.assert_called_with(
f"{common.DEFAULT_BASE_URL}{MgmtV1.access_key_delete_path}",
headers={
Expand Down
Loading
Loading