getstack 1.4.0__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {getstack-1.4.0 → getstack-2.0.0}/PKG-INFO +1 -1
- {getstack-1.4.0 → getstack-2.0.0}/pyproject.toml +1 -1
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/partner_tenants.py +17 -6
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/partner_connect.py +3 -2
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/partner_tenants.py +33 -9
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/proxy.py +3 -1
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/scan.py +3 -1
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/usage.py +4 -0
- getstack-2.0.0/tests/test_partner_signing.py +69 -0
- getstack-1.4.0/tests/test_partner_signing.py +0 -36
- {getstack-1.4.0 → getstack-2.0.0}/.gitignore +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/CLAUDE.md +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/LICENSE +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/README.md +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/scripts/verify_sign_claim_crosslang.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/__init__.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/__init__.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/agents.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/audit.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/authority.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/credentials.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/delivery_methods.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/detector_config.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/dropoffs.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/evidence_packs.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/fleets.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/identity.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/inbound_webhooks.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/intents.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/llm.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/missions.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/notifications.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/partner_connect.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/passport_session.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/passports.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/pii_config.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/proxy.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/reviews.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/scan.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/security_events.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/services.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/skills.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/team.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_async/usage.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_authority_request.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/_fleet_key.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/agent_auth.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/agents.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/audit.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/auth.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/authority.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/browser_bootstrap.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/client.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/credentials.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/delivery_methods.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/detector_config.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/dropoffs.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/errors.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/evidence_packs.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/fleets.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/identity.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/inbound_webhooks.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/intents.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/llm.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/missions.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/notifications.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/passport_header_verify.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/passport_session.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/passports.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/pii_config.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/py.typed +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/reviews.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/security_events.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/services.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/sign_claim.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/skills.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/team.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/types.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/src/getstack/verify_offline.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_authority.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_delivery_methods.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_dropoffs.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_errors.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_evidence_packs.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_fleets.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_identity_requirements.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_inbound_webhooks.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_missions.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_parity.py +0 -0
- {getstack-1.4.0 → getstack-2.0.0}/tests/test_sign_claim.py +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Async mirror of PartnerTenantsService (
|
|
1
|
+
"""Async mirror of PartnerTenantsService (white-label tenants)."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -8,8 +8,6 @@ from urllib.parse import quote
|
|
|
8
8
|
from ..client import AsyncHttpClient
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
Tier = Literal["free", "developer", "pro", "business", "enterprise"]
|
|
12
|
-
|
|
13
11
|
|
|
14
12
|
class AsyncPartnerTenantsService:
|
|
15
13
|
def __init__(self, client: AsyncHttpClient):
|
|
@@ -18,23 +16,36 @@ class AsyncPartnerTenantsService:
|
|
|
18
16
|
async def create(
|
|
19
17
|
self,
|
|
20
18
|
external_customer_id: Optional[str] = None,
|
|
21
|
-
tier: Optional[Tier] = None,
|
|
22
19
|
) -> dict[str, Any]:
|
|
20
|
+
"""Create a white-label tenant.
|
|
21
|
+
|
|
22
|
+
Tenants have no plan of their own; they draw from your allowance
|
|
23
|
+
and wallet. Returns operator_id, api_key (shown once — store it),
|
|
24
|
+
external_customer_id, tier ('partner'), billing ('partner') and
|
|
25
|
+
created_at. Raises with code TENANT_LIMIT_REACHED (409) when your
|
|
26
|
+
deal caps the tenant count.
|
|
27
|
+
"""
|
|
23
28
|
body: dict[str, Any] = {}
|
|
24
29
|
if external_customer_id is not None:
|
|
25
30
|
body["external_customer_id"] = external_customer_id
|
|
26
|
-
if tier is not None:
|
|
27
|
-
body["tier"] = tier
|
|
28
31
|
return await self._client.post("/v1/partner/tenants", json=body)
|
|
29
32
|
|
|
30
33
|
async def list(self) -> list[dict[str, Any]]:
|
|
34
|
+
"""List your tenants. Each carries ``usage`` ({period, actions}):
|
|
35
|
+
its month-to-date share of your action pool, when available."""
|
|
31
36
|
res = await self._client.get("/v1/partner/tenants")
|
|
32
37
|
return list(res.get("tenants", []))
|
|
33
38
|
|
|
34
39
|
async def get(self, tenant_id: str) -> dict[str, Any]:
|
|
40
|
+
"""Fetch one tenant (same shape as a ``list`` entry)."""
|
|
35
41
|
return await self._client.get(f"/v1/partner/tenants/{tenant_id}")
|
|
36
42
|
|
|
37
43
|
async def revoke(self, tenant_id: str) -> dict[str, Any]:
|
|
44
|
+
"""Revoke a tenant. Cascades to its inbound webhooks and passports.
|
|
45
|
+
|
|
46
|
+
Returns {transitioned, webhooks_revoked, passports_revoked}.
|
|
47
|
+
Idempotent: a retry finishes a cascade that failed part-way.
|
|
48
|
+
"""
|
|
38
49
|
return await self._client.delete(f"/v1/partner/tenants/{tenant_id}")
|
|
39
50
|
|
|
40
51
|
async def create_inbound_webhook(
|
|
@@ -6,8 +6,9 @@ the account exists, and returns an opaque ``sk_grant_*`` token you
|
|
|
6
6
|
attach to the calls you already make — same Bearer header, scoped to
|
|
7
7
|
what the grant created, revocable by the user under /services.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
This is the user-owned partner model. For white-label deployment, where
|
|
10
|
+
you provision and manage the customer's account yourself, use the tenant
|
|
11
|
+
surface (``partner_tenants``) instead.
|
|
11
12
|
|
|
12
13
|
Near-zero integration::
|
|
13
14
|
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""White-label tenants — partner surface.
|
|
2
2
|
|
|
3
|
-
Platform partners (
|
|
4
|
-
their
|
|
5
|
-
|
|
3
|
+
Platform partners (is_partner=True on their operator row) run STACK under
|
|
4
|
+
their own product: each customer gets an isolated child operator that the
|
|
5
|
+
partner provisions and manages. This is the white-label alternative to
|
|
6
|
+
"Connect with STACK" (``partner_connect.connect``), where the customer owns
|
|
7
|
+
their own STACK account.
|
|
8
|
+
|
|
9
|
+
Tenants have no plan of their own. Every tenant draws from the partner's
|
|
10
|
+
single allowance and wallet, under the partner's negotiated limits.
|
|
11
|
+
|
|
12
|
+
Error codes worth handling (``StackError.code``):
|
|
13
|
+
|
|
14
|
+
- TENANT_TIER_NOT_ALLOWED (400): ``tier`` was sent on create.
|
|
15
|
+
- TENANT_LIMIT_REACHED (409): the partner's deal caps the tenant count.
|
|
16
|
+
- ALLOWANCE_EXHAUSTED (402): the pool hit a negotiated hard cap.
|
|
17
|
+
- TENANT_BILLED_BY_PARTNER (403): a tenant key called a billing mutation.
|
|
18
|
+
- BILLING_ACCOUNT_INACTIVE (403): the tenant's partner is suspended or removed.
|
|
6
19
|
"""
|
|
7
20
|
|
|
8
21
|
from __future__ import annotations
|
|
@@ -13,8 +26,6 @@ from urllib.parse import quote
|
|
|
13
26
|
from .client import HttpClient
|
|
14
27
|
|
|
15
28
|
|
|
16
|
-
Tier = Literal["free", "developer", "pro", "business", "enterprise"]
|
|
17
|
-
|
|
18
29
|
|
|
19
30
|
class PartnerTenantsService:
|
|
20
31
|
def __init__(self, client: HttpClient):
|
|
@@ -23,23 +34,36 @@ class PartnerTenantsService:
|
|
|
23
34
|
def create(
|
|
24
35
|
self,
|
|
25
36
|
external_customer_id: Optional[str] = None,
|
|
26
|
-
tier: Optional[Tier] = None,
|
|
27
37
|
) -> dict[str, Any]:
|
|
38
|
+
"""Create a white-label tenant.
|
|
39
|
+
|
|
40
|
+
Tenants have no plan of their own; they draw from your allowance
|
|
41
|
+
and wallet. Returns operator_id, api_key (shown once — store it),
|
|
42
|
+
external_customer_id, tier ('partner'), billing ('partner') and
|
|
43
|
+
created_at. Raises with code TENANT_LIMIT_REACHED (409) when your
|
|
44
|
+
deal caps the tenant count.
|
|
45
|
+
"""
|
|
28
46
|
body: dict[str, Any] = {}
|
|
29
47
|
if external_customer_id is not None:
|
|
30
48
|
body["external_customer_id"] = external_customer_id
|
|
31
|
-
if tier is not None:
|
|
32
|
-
body["tier"] = tier
|
|
33
49
|
return self._client.post("/v1/partner/tenants", json=body)
|
|
34
50
|
|
|
35
51
|
def list(self) -> list[dict[str, Any]]:
|
|
52
|
+
"""List your tenants. Each carries ``usage`` ({period, actions}):
|
|
53
|
+
its month-to-date share of your action pool, when available."""
|
|
36
54
|
res = self._client.get("/v1/partner/tenants")
|
|
37
55
|
return list(res.get("tenants", []))
|
|
38
56
|
|
|
39
57
|
def get(self, tenant_id: str) -> dict[str, Any]:
|
|
58
|
+
"""Fetch one tenant (same shape as a ``list`` entry)."""
|
|
40
59
|
return self._client.get(f"/v1/partner/tenants/{tenant_id}")
|
|
41
60
|
|
|
42
61
|
def revoke(self, tenant_id: str) -> dict[str, Any]:
|
|
62
|
+
"""Revoke a tenant. Cascades to its inbound webhooks and passports.
|
|
63
|
+
|
|
64
|
+
Returns {transitioned, webhooks_revoked, passports_revoked}.
|
|
65
|
+
Idempotent: a retry finishes a cascade that failed part-way.
|
|
66
|
+
"""
|
|
43
67
|
return self._client.delete(f"/v1/partner/tenants/{tenant_id}")
|
|
44
68
|
|
|
45
69
|
def create_inbound_webhook(
|
|
@@ -174,6 +174,8 @@ class ProxyService:
|
|
|
174
174
|
def usage(self) -> dict:
|
|
175
175
|
"""Get monthly proxy usage stats.
|
|
176
176
|
|
|
177
|
-
Returns dict with: tier
|
|
177
|
+
Returns dict with: tier (the plan the account draws from), limit,
|
|
178
|
+
used, remaining, period, and pooled=True on white-label tenants,
|
|
179
|
+
whose counts are the partner's shared pool.
|
|
178
180
|
"""
|
|
179
181
|
return self._client.request("GET", "/v1/proxy/usage")
|
|
@@ -72,6 +72,8 @@ class ScanService:
|
|
|
72
72
|
"""Get monthly scan usage stats.
|
|
73
73
|
|
|
74
74
|
Returns:
|
|
75
|
-
Dict with keys: tier
|
|
75
|
+
Dict with keys: tier (the plan the account draws from), limit,
|
|
76
|
+
used, remaining, period, and pooled=True on white-label tenants,
|
|
77
|
+
whose counts are the partner's shared pool.
|
|
76
78
|
"""
|
|
77
79
|
return self._client.request("GET", "/v1/scan/usage")
|
|
@@ -50,6 +50,10 @@ class UsageService:
|
|
|
50
50
|
{used, limit, created_in_window?}), passthrough (None under a since/until
|
|
51
51
|
window - those counters only exist for the current month),
|
|
52
52
|
spend_cents (skills / llm_compute / identity / overage / total),
|
|
53
|
+
For pooled accounts (white-label tenants and their partners),
|
|
54
|
+
consumption.actions also carries pooled=True, own_used (this
|
|
55
|
+
account's share) and tenants (accounts sharing the pool), and
|
|
56
|
+
its ``used`` is the whole pool total.
|
|
53
57
|
wallet_cents ({balance}), and earnings_cents ({unpaid, lifetime}).
|
|
54
58
|
All money in cents.
|
|
55
59
|
"""
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""White-label tenant service: create without tier, revoke cascade, signing setup."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from unittest.mock import AsyncMock, Mock
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
8
|
+
|
|
9
|
+
from getstack.partner_tenants import PartnerTenantsService
|
|
10
|
+
from getstack._async.partner_tenants import AsyncPartnerTenantsService
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_sync_tenant_signing():
|
|
14
|
+
client = Mock()
|
|
15
|
+
client.put.return_value = {"configured": True}
|
|
16
|
+
result = PartnerTenantsService(client).set_inbound_webhook_signing_secret(
|
|
17
|
+
"tenant fixture", "wh/fixture", "synthetic-provider", "synthetic-downstream",
|
|
18
|
+
)
|
|
19
|
+
assert result == {"configured": True}
|
|
20
|
+
client.put.assert_called_once_with(
|
|
21
|
+
"/v1/partner/tenants/tenant%20fixture/inbound-webhooks/wh%2Ffixture/provider-signing-secret",
|
|
22
|
+
json={"signing_secret": "synthetic-provider", "downstream_signing_secret": "synthetic-downstream"},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_async_tenant_signing():
|
|
27
|
+
client = Mock()
|
|
28
|
+
client.put = AsyncMock(return_value={"configured": True})
|
|
29
|
+
result = asyncio.run(AsyncPartnerTenantsService(client).set_inbound_webhook_signing_secret(
|
|
30
|
+
"tenant fixture", "wh/fixture", "synthetic-provider",
|
|
31
|
+
))
|
|
32
|
+
assert result == {"configured": True}
|
|
33
|
+
client.put.assert_awaited_once_with(
|
|
34
|
+
"/v1/partner/tenants/tenant%20fixture/inbound-webhooks/wh%2Ffixture/provider-signing-secret",
|
|
35
|
+
json={"signing_secret": "synthetic-provider"},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_sync_tenant_create_sends_no_tier():
|
|
40
|
+
client = Mock()
|
|
41
|
+
client.post.return_value = {"operator_id": "op_t", "tier": "partner", "billing": "partner"}
|
|
42
|
+
service = PartnerTenantsService(client)
|
|
43
|
+
result = service.create(external_customer_id="cust_1")
|
|
44
|
+
assert result["billing"] == "partner"
|
|
45
|
+
client.post.assert_called_once_with("/v1/partner/tenants", json={"external_customer_id": "cust_1"})
|
|
46
|
+
try:
|
|
47
|
+
service.create(external_customer_id="cust_2", tier="pro") # type: ignore[call-arg]
|
|
48
|
+
except TypeError:
|
|
49
|
+
pass
|
|
50
|
+
else:
|
|
51
|
+
raise AssertionError("create must not accept tier; tenants draw from the partner's plan")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_async_tenant_create_sends_no_tier():
|
|
55
|
+
client = Mock()
|
|
56
|
+
client.post = AsyncMock(return_value={"operator_id": "op_t", "tier": "partner", "billing": "partner"})
|
|
57
|
+
asyncio.run(AsyncPartnerTenantsService(client).create())
|
|
58
|
+
client.post.assert_awaited_once_with("/v1/partner/tenants", json={})
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_tenant_revoke_returns_cascade_counts():
|
|
62
|
+
cascade = {"transitioned": True, "webhooks_revoked": 2, "passports_revoked": 3}
|
|
63
|
+
client = Mock()
|
|
64
|
+
client.delete.return_value = cascade
|
|
65
|
+
assert PartnerTenantsService(client).revoke("op_t") == cascade
|
|
66
|
+
client.delete.assert_called_once_with("/v1/partner/tenants/op_t")
|
|
67
|
+
aclient = Mock()
|
|
68
|
+
aclient.delete = AsyncMock(return_value=cascade)
|
|
69
|
+
assert asyncio.run(AsyncPartnerTenantsService(aclient).revoke("op_t")) == cascade
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"""Tenant signing setup sends fixture keys in the body, never the URL."""
|
|
2
|
-
import asyncio
|
|
3
|
-
import sys
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from unittest.mock import AsyncMock, Mock
|
|
6
|
-
|
|
7
|
-
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
8
|
-
|
|
9
|
-
from getstack.partner_tenants import PartnerTenantsService
|
|
10
|
-
from getstack._async.partner_tenants import AsyncPartnerTenantsService
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_sync_tenant_signing():
|
|
14
|
-
client = Mock()
|
|
15
|
-
client.put.return_value = {"configured": True}
|
|
16
|
-
result = PartnerTenantsService(client).set_inbound_webhook_signing_secret(
|
|
17
|
-
"tenant fixture", "wh/fixture", "synthetic-provider", "synthetic-downstream",
|
|
18
|
-
)
|
|
19
|
-
assert result == {"configured": True}
|
|
20
|
-
client.put.assert_called_once_with(
|
|
21
|
-
"/v1/partner/tenants/tenant%20fixture/inbound-webhooks/wh%2Ffixture/provider-signing-secret",
|
|
22
|
-
json={"signing_secret": "synthetic-provider", "downstream_signing_secret": "synthetic-downstream"},
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def test_async_tenant_signing():
|
|
27
|
-
client = Mock()
|
|
28
|
-
client.put = AsyncMock(return_value={"configured": True})
|
|
29
|
-
result = asyncio.run(AsyncPartnerTenantsService(client).set_inbound_webhook_signing_secret(
|
|
30
|
-
"tenant fixture", "wh/fixture", "synthetic-provider",
|
|
31
|
-
))
|
|
32
|
-
assert result == {"configured": True}
|
|
33
|
-
client.put.assert_awaited_once_with(
|
|
34
|
-
"/v1/partner/tenants/tenant%20fixture/inbound-webhooks/wh%2Ffixture/provider-signing-secret",
|
|
35
|
-
json={"signing_secret": "synthetic-provider"},
|
|
36
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|