cred-semantic-kernel 1.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.
- cred_semantic_kernel-1.0.0/.gitignore +50 -0
- cred_semantic_kernel-1.0.0/PKG-INFO +76 -0
- cred_semantic_kernel-1.0.0/README.md +60 -0
- cred_semantic_kernel-1.0.0/cred_semantic_kernel/__init__.py +5 -0
- cred_semantic_kernel-1.0.0/cred_semantic_kernel/plugin.py +88 -0
- cred_semantic_kernel-1.0.0/pyproject.toml +26 -0
- cred_semantic_kernel-1.0.0/tests/__init__.py +0 -0
- cred_semantic_kernel-1.0.0/tests/test_plugin.py +187 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
dist/
|
|
3
|
+
build/
|
|
4
|
+
|
|
5
|
+
# Sprint artifacts
|
|
6
|
+
.factory/
|
|
7
|
+
.sprint-*
|
|
8
|
+
.sprint-pid
|
|
9
|
+
.rewire-done
|
|
10
|
+
.content-rewrite-done
|
|
11
|
+
.hubspot-done
|
|
12
|
+
.integrations-done
|
|
13
|
+
.done
|
|
14
|
+
.validation-report.md
|
|
15
|
+
.sprint-logs/
|
|
16
|
+
.sprint-prompts/
|
|
17
|
+
SESSION_PACKAGE.md
|
|
18
|
+
run-sprint.sh
|
|
19
|
+
validate.sh
|
|
20
|
+
|
|
21
|
+
# Internal planning docs (not for public repo)
|
|
22
|
+
CLAUDE.md
|
|
23
|
+
PLAN.md
|
|
24
|
+
HANDOFF.md
|
|
25
|
+
VALIDATION.md
|
|
26
|
+
MARKETING-VALIDATION.md
|
|
27
|
+
FINAL-SUBMISSION-VALIDATION.md
|
|
28
|
+
.next/
|
|
29
|
+
out/
|
|
30
|
+
coverage/
|
|
31
|
+
*.tsbuildinfo
|
|
32
|
+
.turbo/
|
|
33
|
+
.DS_Store
|
|
34
|
+
*.pem
|
|
35
|
+
.env
|
|
36
|
+
.env.local
|
|
37
|
+
.env*.local
|
|
38
|
+
!.env.example
|
|
39
|
+
|
|
40
|
+
# Python
|
|
41
|
+
__pycache__/
|
|
42
|
+
*.py[cod]
|
|
43
|
+
*.pyo
|
|
44
|
+
*.egg-info/
|
|
45
|
+
.pytest_cache/
|
|
46
|
+
.venv/
|
|
47
|
+
venv/
|
|
48
|
+
dist/
|
|
49
|
+
*.dist-info/
|
|
50
|
+
packages/sdk-python/.venv/
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cred-semantic-kernel
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Cred Semantic Kernel integration — credential delegation plugin for Microsoft Semantic Kernel agents
|
|
5
|
+
Author-email: Cred <hello@cred.ninja>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Keywords: ai-agents,cred,delegation,microsoft,oauth,semantic-kernel
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Requires-Dist: cred-auth>=0.1.0
|
|
10
|
+
Requires-Dist: semantic-kernel>=1.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: hatchling; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# cred-semantic-kernel
|
|
18
|
+
|
|
19
|
+
Microsoft Semantic Kernel integration for Cred. OAuth2 credential delegation for AI agents.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install cred-semantic-kernel
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import os
|
|
31
|
+
import semantic_kernel as sk
|
|
32
|
+
from cred_semantic_kernel import CredPlugin
|
|
33
|
+
|
|
34
|
+
kernel = sk.Kernel()
|
|
35
|
+
|
|
36
|
+
plugin = CredPlugin(
|
|
37
|
+
agent_token=os.environ["CRED_AGENT_TOKEN"],
|
|
38
|
+
user_id="user_123",
|
|
39
|
+
app_client_id="my_app_client_id",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
kernel.add_plugin(plugin, plugin_name="cred")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Plugin Functions
|
|
46
|
+
|
|
47
|
+
The `cred` plugin exposes one kernel function:
|
|
48
|
+
|
|
49
|
+
### `delegate`
|
|
50
|
+
|
|
51
|
+
| Parameter | Type | Description |
|
|
52
|
+
|-----------|------|-------------|
|
|
53
|
+
| `service` | `string` | Service slug (e.g. `google`, `github`) |
|
|
54
|
+
| `scopes` | `string` | Comma-separated OAuth scopes to request |
|
|
55
|
+
|
|
56
|
+
`user_id` and `app_client_id` are pre-configured at construction time, not agent-controlled.
|
|
57
|
+
|
|
58
|
+
Returns a JSON string with `access_token`, `token_type`, `expires_in`, `service`, `scopes`, and `delegation_id`.
|
|
59
|
+
|
|
60
|
+
## Handling Consent
|
|
61
|
+
|
|
62
|
+
When the user hasn't connected the service, the `delegate` function raises `ConsentRequiredError`.
|
|
63
|
+
The error's `consent_url` attribute contains the URL to redirect the user.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from cred import ConsentRequiredError
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
result = plugin.delegate(service="google", scopes="calendar.readonly")
|
|
70
|
+
except ConsentRequiredError as e:
|
|
71
|
+
print(f"Redirect user to: {e.consent_url}")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Cred Cloud (Coming Soon)
|
|
75
|
+
|
|
76
|
+
Managed cloud delegation is coming. [Join the waitlist](https://cred.ninja/waitlist).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# cred-semantic-kernel
|
|
2
|
+
|
|
3
|
+
Microsoft Semantic Kernel integration for Cred. OAuth2 credential delegation for AI agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install cred-semantic-kernel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import os
|
|
15
|
+
import semantic_kernel as sk
|
|
16
|
+
from cred_semantic_kernel import CredPlugin
|
|
17
|
+
|
|
18
|
+
kernel = sk.Kernel()
|
|
19
|
+
|
|
20
|
+
plugin = CredPlugin(
|
|
21
|
+
agent_token=os.environ["CRED_AGENT_TOKEN"],
|
|
22
|
+
user_id="user_123",
|
|
23
|
+
app_client_id="my_app_client_id",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
kernel.add_plugin(plugin, plugin_name="cred")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Plugin Functions
|
|
30
|
+
|
|
31
|
+
The `cred` plugin exposes one kernel function:
|
|
32
|
+
|
|
33
|
+
### `delegate`
|
|
34
|
+
|
|
35
|
+
| Parameter | Type | Description |
|
|
36
|
+
|-----------|------|-------------|
|
|
37
|
+
| `service` | `string` | Service slug (e.g. `google`, `github`) |
|
|
38
|
+
| `scopes` | `string` | Comma-separated OAuth scopes to request |
|
|
39
|
+
|
|
40
|
+
`user_id` and `app_client_id` are pre-configured at construction time, not agent-controlled.
|
|
41
|
+
|
|
42
|
+
Returns a JSON string with `access_token`, `token_type`, `expires_in`, `service`, `scopes`, and `delegation_id`.
|
|
43
|
+
|
|
44
|
+
## Handling Consent
|
|
45
|
+
|
|
46
|
+
When the user hasn't connected the service, the `delegate` function raises `ConsentRequiredError`.
|
|
47
|
+
The error's `consent_url` attribute contains the URL to redirect the user.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from cred import ConsentRequiredError
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
result = plugin.delegate(service="google", scopes="calendar.readonly")
|
|
54
|
+
except ConsentRequiredError as e:
|
|
55
|
+
print(f"Redirect user to: {e.consent_url}")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Cred Cloud (Coming Soon)
|
|
59
|
+
|
|
60
|
+
Managed cloud delegation is coming. [Join the waitlist](https://cred.ninja/waitlist).
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Cred Semantic Kernel Plugin.
|
|
2
|
+
|
|
3
|
+
Provides a kernel function that delegates OAuth credentials for AI agents.
|
|
4
|
+
The plugin is pre-configured with agent identity and app context at construction.
|
|
5
|
+
At runtime, the agent provides ``service`` and ``scopes`` as function parameters.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from typing import Annotated, Optional
|
|
12
|
+
|
|
13
|
+
from semantic_kernel.functions import kernel_function
|
|
14
|
+
|
|
15
|
+
from cred import Cred
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CredPlugin:
|
|
19
|
+
"""Semantic Kernel plugin for Cred credential delegation.
|
|
20
|
+
|
|
21
|
+
Pre-configured at construction with the agent token, user ID, and
|
|
22
|
+
app client ID. Exposes a single ``delegate`` kernel function.
|
|
23
|
+
|
|
24
|
+
Example::
|
|
25
|
+
|
|
26
|
+
from cred_semantic_kernel import CredPlugin
|
|
27
|
+
import semantic_kernel as sk
|
|
28
|
+
|
|
29
|
+
kernel = sk.Kernel()
|
|
30
|
+
plugin = CredPlugin(
|
|
31
|
+
agent_token=os.environ["CRED_AGENT_TOKEN"],
|
|
32
|
+
user_id="user_123",
|
|
33
|
+
app_client_id="my_app",
|
|
34
|
+
)
|
|
35
|
+
kernel.add_plugin(plugin, plugin_name="cred")
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
ConsentRequiredError: (from delegate at runtime) when the user hasn't
|
|
39
|
+
connected the requested service. The error message includes
|
|
40
|
+
``consent_url`` so the caller can redirect the user.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
agent_token: str,
|
|
46
|
+
user_id: str,
|
|
47
|
+
app_client_id: str,
|
|
48
|
+
base_url: str = "https://api.cred.ninja",
|
|
49
|
+
) -> None:
|
|
50
|
+
self._cred = Cred(agent_token=agent_token, base_url=base_url)
|
|
51
|
+
self._user_id = user_id
|
|
52
|
+
self._app_client_id = app_client_id
|
|
53
|
+
|
|
54
|
+
@kernel_function(
|
|
55
|
+
name="delegate",
|
|
56
|
+
description=(
|
|
57
|
+
"Get a delegated OAuth access token for a third-party service "
|
|
58
|
+
"on behalf of the current user. "
|
|
59
|
+
"Returns JSON with access_token, token_type, expires_in, service, scopes, "
|
|
60
|
+
"and delegation_id. "
|
|
61
|
+
"Raises an error with consent_url if the user hasn't connected the service."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
def delegate(
|
|
65
|
+
self,
|
|
66
|
+
service: Annotated[str, "Service slug to get a token for (e.g. 'google', 'github', 'google-calendar')."],
|
|
67
|
+
scopes: Annotated[str, "Comma-separated OAuth scopes to request (e.g. 'calendar.readonly,calendar.events'). Pass empty string to use all consented scopes."],
|
|
68
|
+
) -> str:
|
|
69
|
+
"""Delegate credentials and return JSON result."""
|
|
70
|
+
scope_list: Optional[list[str]] = None
|
|
71
|
+
if scopes:
|
|
72
|
+
scope_list = [s.strip() for s in scopes.split(",") if s.strip()]
|
|
73
|
+
|
|
74
|
+
result = self._cred.delegate(
|
|
75
|
+
service=service,
|
|
76
|
+
user_id=self._user_id,
|
|
77
|
+
app_client_id=self._app_client_id,
|
|
78
|
+
scopes=scope_list if scope_list else None,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return json.dumps({
|
|
82
|
+
"access_token": result.access_token,
|
|
83
|
+
"token_type": result.token_type,
|
|
84
|
+
"expires_in": result.expires_in,
|
|
85
|
+
"service": result.service,
|
|
86
|
+
"scopes": result.scopes,
|
|
87
|
+
"delegation_id": result.delegation_id,
|
|
88
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cred-semantic-kernel"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Cred Semantic Kernel integration — credential delegation plugin for Microsoft Semantic Kernel agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "Apache-2.0" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "Cred", email = "hello@cred.ninja" }]
|
|
13
|
+
keywords = ["cred", "semantic-kernel", "microsoft", "oauth", "delegation", "ai-agents"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"cred-auth>=0.1.0",
|
|
16
|
+
"semantic-kernel>=1.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = ["pytest>=7.0", "pytest-asyncio>=0.21", "hatchling"]
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["cred_semantic_kernel"]
|
|
24
|
+
|
|
25
|
+
[tool.pytest.ini_options]
|
|
26
|
+
testpaths = ["tests"]
|
|
File without changes
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Cred Semantic Kernel plugin tests.
|
|
2
|
+
|
|
3
|
+
Tests focus on: plugin construction, kernel_function metadata, invocation,
|
|
4
|
+
output JSON, and error propagation. The Cred client is mocked -- no real HTTP.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Optional
|
|
10
|
+
import pytest
|
|
11
|
+
import httpx
|
|
12
|
+
from unittest.mock import MagicMock, patch
|
|
13
|
+
|
|
14
|
+
from cred import DelegationResult, ConsentRequiredError, CredError
|
|
15
|
+
from cred_semantic_kernel import CredPlugin
|
|
16
|
+
|
|
17
|
+
TOKEN = "cred_at_test"
|
|
18
|
+
USER_ID = "user_123"
|
|
19
|
+
APP_CLIENT_ID = "app_1"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def make_delegation_result(
|
|
23
|
+
*,
|
|
24
|
+
access_token: str = "at",
|
|
25
|
+
token_type: str = "Bearer",
|
|
26
|
+
expires_in: int = 3600,
|
|
27
|
+
service: str = "google",
|
|
28
|
+
scopes: Optional[list[str]] = None,
|
|
29
|
+
delegation_id: str = "del_1",
|
|
30
|
+
) -> DelegationResult:
|
|
31
|
+
return DelegationResult(
|
|
32
|
+
access_token=access_token,
|
|
33
|
+
token_type=token_type,
|
|
34
|
+
expires_in=expires_in,
|
|
35
|
+
expires_at=datetime(2026, 3, 1, tzinfo=timezone.utc),
|
|
36
|
+
service=service,
|
|
37
|
+
scopes=scopes or [],
|
|
38
|
+
delegation_id=delegation_id,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def mock_cred():
|
|
44
|
+
return MagicMock()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def plugin(mock_cred):
|
|
49
|
+
with patch("cred_semantic_kernel.plugin.Cred", return_value=mock_cred):
|
|
50
|
+
return CredPlugin(
|
|
51
|
+
agent_token=TOKEN,
|
|
52
|
+
user_id=USER_ID,
|
|
53
|
+
app_client_id=APP_CLIENT_ID,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# -- constructor ---------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
class TestConstructor:
|
|
60
|
+
def test_creates_cred_client(self):
|
|
61
|
+
with patch("cred_semantic_kernel.plugin.Cred") as mock_cls:
|
|
62
|
+
CredPlugin(
|
|
63
|
+
agent_token=TOKEN,
|
|
64
|
+
user_id=USER_ID,
|
|
65
|
+
app_client_id=APP_CLIENT_ID,
|
|
66
|
+
base_url="http://localhost:3001",
|
|
67
|
+
)
|
|
68
|
+
mock_cls.assert_called_once_with(
|
|
69
|
+
agent_token=TOKEN,
|
|
70
|
+
base_url="http://localhost:3001",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def test_stores_user_id(self, plugin):
|
|
74
|
+
assert plugin._user_id == USER_ID
|
|
75
|
+
|
|
76
|
+
def test_stores_app_client_id(self, plugin):
|
|
77
|
+
assert plugin._app_client_id == APP_CLIENT_ID
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# -- kernel_function metadata --------------------------------------------------
|
|
81
|
+
|
|
82
|
+
class TestKernelFunction:
|
|
83
|
+
def test_delegate_has_kernel_function_metadata(self, plugin):
|
|
84
|
+
# kernel_function decorator adds __kernel_function_metadata__
|
|
85
|
+
assert hasattr(plugin.delegate, "__kernel_function__")
|
|
86
|
+
|
|
87
|
+
def test_delegate_is_callable(self, plugin):
|
|
88
|
+
assert callable(plugin.delegate)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# -- delegate invocation -------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
class TestDelegate:
|
|
94
|
+
def test_returns_json_with_access_token(self, plugin, mock_cred):
|
|
95
|
+
mock_cred.delegate.return_value = make_delegation_result(
|
|
96
|
+
access_token="ya29.mock",
|
|
97
|
+
expires_in=3600,
|
|
98
|
+
service="google",
|
|
99
|
+
scopes=["calendar.readonly"],
|
|
100
|
+
delegation_id="del_abc",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
result_str = plugin.delegate(service="google", scopes="calendar.readonly")
|
|
104
|
+
result = json.loads(result_str)
|
|
105
|
+
|
|
106
|
+
assert result["access_token"] == "ya29.mock"
|
|
107
|
+
assert result["token_type"] == "Bearer"
|
|
108
|
+
assert result["expires_in"] == 3600
|
|
109
|
+
assert result["service"] == "google"
|
|
110
|
+
assert result["scopes"] == ["calendar.readonly"]
|
|
111
|
+
assert result["delegation_id"] == "del_abc"
|
|
112
|
+
|
|
113
|
+
def test_passes_service_user_app_to_cred(self, plugin, mock_cred):
|
|
114
|
+
mock_cred.delegate.return_value = make_delegation_result(service="github")
|
|
115
|
+
|
|
116
|
+
plugin.delegate(service="github", scopes="repo")
|
|
117
|
+
|
|
118
|
+
mock_cred.delegate.assert_called_once_with(
|
|
119
|
+
service="github",
|
|
120
|
+
user_id=USER_ID,
|
|
121
|
+
app_client_id=APP_CLIENT_ID,
|
|
122
|
+
scopes=["repo"],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def test_parses_comma_separated_scopes(self, plugin, mock_cred):
|
|
126
|
+
mock_cred.delegate.return_value = make_delegation_result(
|
|
127
|
+
scopes=["calendar.readonly", "calendar.events"],
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
plugin.delegate(service="google", scopes="calendar.readonly, calendar.events")
|
|
131
|
+
|
|
132
|
+
call_kwargs = mock_cred.delegate.call_args.kwargs
|
|
133
|
+
assert call_kwargs["scopes"] == ["calendar.readonly", "calendar.events"]
|
|
134
|
+
|
|
135
|
+
def test_passes_none_scopes_when_empty_string(self, plugin, mock_cred):
|
|
136
|
+
mock_cred.delegate.return_value = make_delegation_result()
|
|
137
|
+
|
|
138
|
+
plugin.delegate(service="google", scopes="")
|
|
139
|
+
|
|
140
|
+
call_kwargs = mock_cred.delegate.call_args.kwargs
|
|
141
|
+
assert call_kwargs["scopes"] is None
|
|
142
|
+
|
|
143
|
+
def test_propagates_consent_required_error(self, plugin, mock_cred):
|
|
144
|
+
mock_cred.delegate.side_effect = ConsentRequiredError(
|
|
145
|
+
"User has not consented",
|
|
146
|
+
"https://api.cred.ninja/api/connect/google/authorize?app_client_id=app_1",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
with pytest.raises(ConsentRequiredError) as exc_info:
|
|
150
|
+
plugin.delegate(service="google", scopes="calendar.readonly")
|
|
151
|
+
|
|
152
|
+
assert "/api/connect/google/authorize" in exc_info.value.consent_url
|
|
153
|
+
|
|
154
|
+
def test_propagates_cred_error_on_401(self, plugin, mock_cred):
|
|
155
|
+
mock_cred.delegate.side_effect = CredError(
|
|
156
|
+
"Invalid agent token", "unauthorized", 401
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
with pytest.raises(CredError) as exc_info:
|
|
160
|
+
plugin.delegate(service="google", scopes="")
|
|
161
|
+
|
|
162
|
+
assert exc_info.value.status_code == 401
|
|
163
|
+
|
|
164
|
+
def test_real_cred_client_delegates_against_mock_server(self):
|
|
165
|
+
plugin = CredPlugin(
|
|
166
|
+
agent_token=TOKEN,
|
|
167
|
+
user_id=USER_ID,
|
|
168
|
+
app_client_id=APP_CLIENT_ID,
|
|
169
|
+
base_url="https://cred.example.com",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
with patch("httpx.Client.request", return_value=httpx.Response(
|
|
173
|
+
200,
|
|
174
|
+
json={
|
|
175
|
+
"access_token": "ya29.real",
|
|
176
|
+
"token_type": "Bearer",
|
|
177
|
+
"expires_in": 3600,
|
|
178
|
+
"service": "google",
|
|
179
|
+
"scopes": ["calendar.readonly"],
|
|
180
|
+
"delegation_id": "del_real",
|
|
181
|
+
},
|
|
182
|
+
)) as request_mock:
|
|
183
|
+
result_str = plugin.delegate(service="google", scopes="calendar.readonly")
|
|
184
|
+
|
|
185
|
+
payload = json.loads(result_str)
|
|
186
|
+
assert payload["access_token"] == "ya29.real"
|
|
187
|
+
assert request_mock.called
|