Skip to content

Commit 0c298d8

Browse files
add/update tests
1 parent 8b12a43 commit 0c298d8

File tree

7 files changed

+187
-25
lines changed

7 files changed

+187
-25
lines changed

src/posit/connect/oauth/associations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ def delete(self) -> None:
7171
path = f"v1/content/{self.content_guid}/oauth/integrations/associations"
7272
self._ctx.client.put(path, json=data)
7373

74-
def update(self, integration_guid: list[str]) -> None:
74+
def update(self, integration_guids: list[str]) -> None:
7575
"""Set integration associations."""
76-
data = [{"oauth_integration_guid": guid} for guid in integration_guid]
76+
data = [{"oauth_integration_guid": guid} for guid in integration_guids]
7777

7878
path = f"v1/content/{self.content_guid}/oauth/integrations/associations"
7979
self._ctx.client.put(path, json=data)

src/posit/connect/oauth/integrations.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ def find(self) -> List[Integration]:
129129
# @requires("2025.07.0")
130130
def find_by(
131131
self,
132-
integration_type: Optional[types.OAuthIntegrationType] = None,
133-
auth_type: Optional[types.OAuthIntegrationAuthType] = None,
132+
integration_type: Optional[types.OAuthIntegrationType | str] = None,
133+
auth_type: Optional[types.OAuthIntegrationAuthType | str] = None,
134134
name: Optional[str] = None,
135135
description: Optional[str] = None,
136136
guid: Optional[str] = None,
@@ -140,14 +140,16 @@ def find_by(
140140
141141
Parameters
142142
----------
143-
integration_type : Optional[types.OAuthIntegrationType]
143+
integration_type : Optional[types.OAuthIntegrationType | str]
144144
The type of the integration (e.g., "aws", "azure").
145-
auth_type : Optional[types.OAuthIntegrationAuthType]
145+
auth_type : Optional[types.OAuthIntegrationAuthType | str]
146146
The authentication type of the integration (e.g., "Viewer", "Service Account").
147147
name : Optional[str]
148-
A regex pattern to match the integration name.
148+
A regex pattern to match the integration name. For exact matches, use `^` and `$`. For example,
149+
`^My Integration$` will match only "My Integration".
149150
description : Optional[str]
150-
A regex pattern to match the integration description.
151+
A regex pattern to match the integration description. For exact matches, use `^` and `$`. For example,
152+
`^My Integration Description$` will match only "My Integration Description".
151153
guid : Optional[str]
152154
The unique identifier of the integration.
153155
config : Optional[dict]
@@ -160,34 +162,32 @@ def find_by(
160162
The first matching integration, or None if no match is found.
161163
"""
162164
for integration in self.find():
163-
match = True
164-
165165
if integration_type is not None and integration.get("template") != integration_type:
166-
match = False
166+
continue
167167

168168
if auth_type is not None and integration.get("auth_type") != auth_type:
169-
match = False
169+
continue
170170

171171
if name is not None:
172172
integration_name = integration.get("name", "")
173173
if not re.search(name, integration_name):
174-
match = False
174+
continue
175175

176176
if description is not None:
177177
integration_description = integration.get("description", "")
178178
if not re.search(description, integration_description):
179-
match = False
179+
continue
180180

181181
if guid is not None and integration.get("guid") != guid:
182-
match = False
182+
continue
183183

184184
if config is not None:
185185
integration_config = integration.get("config", {})
186186
if not all(integration_config.get(k) == v for k, v in config.items()):
187-
match = False
187+
continue
188+
189+
return integration
188190

189-
if match:
190-
return integration
191191
return None
192192

193193
def get(self, guid: str) -> Integration:

tests/posit/connect/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/oauth/integrations/associations.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,13 @@
66
"oauth_integration_description": "integration description",
77
"oauth_integration_template": "custom",
88
"created_time": "2024-10-01T18:16:09Z"
9+
},
10+
{
11+
"app_guid": "f2f37341-e21d-3d80-c698-a935ad614066",
12+
"oauth_integration_guid": "00000000-a27b-4118-ad06-e24459b05126",
13+
"oauth_integration_name": "another integration",
14+
"oauth_integration_description": "another description",
15+
"oauth_integration_template": "custom",
16+
"created_time": "2024-10-02T18:16:09Z"
917
}
1018
]

tests/posit/connect/__api__/v1/oauth/integrations.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,17 @@
3232
"token_endpoint_auth_method": "client_secret_post",
3333
"token_uri": "http://keycloak:8080/realms/rsconnect/protocol/openid-connect/token"
3434
}
35+
},
36+
{
37+
"id": "5",
38+
"guid": "d32b3f4e-3f4a-4c5a-9f1e-8b6e2f7c9a2b",
39+
"created_time": "2025-07-15T20:35:11Z",
40+
"updated_time": "2025-07-15T20:35:11Z",
41+
"name": "Connect Visitor API Key",
42+
"description": "Allows access to Connect APIs on behalf of a Connect user.",
43+
"template": "connect",
44+
"config": {
45+
"scopes": "admin"
46+
}
3547
}
3648
]

tests/posit/connect/oauth/test_associations.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,12 @@ def test(self):
8989
associations = c.content.get(guid).oauth.associations.find()
9090

9191
# assert
92-
assert len(associations) == 1
93-
association = associations[0]
94-
assert association["app_guid"] == guid
92+
assert len(associations) == 2
93+
assert associations[0]["app_guid"] == guid
94+
assert associations[1]["app_guid"] == guid
95+
assert (
96+
associations[0]["oauth_integration_guid"] != associations[1]["oauth_integration_guid"]
97+
)
9598

9699
assert mock_get_content.call_count == 1
97100
assert mock_get_association.call_count == 1
@@ -108,19 +111,28 @@ def test(self):
108111
json=load_mock(f"v1/content/{guid}.json"),
109112
)
110113

111-
new_integration_guid = "00000000-a27b-4118-ad06-e24459b05126"
114+
new_integration_guid1 = "00000000-a27b-4118-ad06-e24459b05126"
115+
new_integration_guid2 = "00000001-a27b-4118-ad06-e24459b05126"
112116

113117
mock_put = responses.put(
114118
f"https://connect.example/__api__/v1/content/{guid}/oauth/integrations/associations",
115-
json=[{"oauth_integration_guid": new_integration_guid}],
119+
json=[
120+
{"oauth_integration_guid": new_integration_guid1},
121+
{"oauth_integration_guid": new_integration_guid2},
122+
],
116123
)
117124

118125
# setup
119126
c = Client("https://connect.example", "12345")
120127
c._ctx.version = None
121128

122129
# invoke
123-
c.content.get(guid).oauth.associations.update([new_integration_guid])
130+
c.content.get(guid).oauth.associations.update(
131+
[
132+
new_integration_guid1,
133+
new_integration_guid2,
134+
]
135+
)
124136

125137
# assert
126138
assert mock_put.call_count == 1

tests/posit/connect/oauth/test_integrations.py

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from responses import matchers
33

44
from posit.connect.client import Client
5+
from posit.connect.oauth import types
56

67
from ..api import load_mock, load_mock_dict
78

@@ -125,9 +126,10 @@ def test(self):
125126

126127
# assert
127128
assert mock_get.call_count == 1
128-
assert len(integrations) == 2
129+
assert len(integrations) == 3
129130
assert integrations[0]["id"] == "3"
130131
assert integrations[1]["id"] == "4"
132+
assert integrations[2]["id"] == "5"
131133

132134

133135
class TestIntegrationsGet:
@@ -148,3 +150,107 @@ def test(self):
148150

149151
assert mock_get.call_count == 1
150152
assert integration["guid"] == guid
153+
154+
155+
class TestIntegrationsFindBy:
156+
@responses.activate
157+
def test(self):
158+
# behavior
159+
responses.get(
160+
"https://connect.example/__api__/v1/oauth/integrations",
161+
json=load_mock("v1/oauth/integrations.json"),
162+
)
163+
164+
# setup
165+
c = Client("https://connect.example", "12345")
166+
c._ctx.version = None
167+
integrations = c.oauth.integrations
168+
169+
# invoke and assert
170+
# by guid
171+
found = integrations.find_by(guid="22644575-a27b-4118-ad06-e24459b05126")
172+
assert found is not None
173+
assert found["guid"] == "22644575-a27b-4118-ad06-e24459b05126"
174+
175+
# by name (exact match)
176+
found = integrations.find_by(name="^keycloak integration$")
177+
assert found is not None
178+
assert found["name"] == "keycloak integration"
179+
180+
# by name (partial match) - should find first match
181+
found = integrations.find_by(name="keycloak")
182+
assert found is not None
183+
assert found["name"] == "keycloak integration"
184+
185+
# by description (exact match)
186+
found = integrations.find_by(description="^integration description$")
187+
assert found is not None
188+
assert found["description"] == "integration description"
189+
190+
# by description (partial match)
191+
found = integrations.find_by(description="another")
192+
assert found is not None
193+
assert found["description"] == "another integration description"
194+
195+
# by integration_type
196+
found = integrations.find_by(integration_type=types.OAuthIntegrationType.CUSTOM)
197+
assert found is not None
198+
assert found["template"] == "custom"
199+
assert found["name"] == "keycloak integration" # first one
200+
201+
found = integrations.find_by(integration_type="custom")
202+
assert found is not None
203+
assert found["template"] == "custom"
204+
assert found["name"] == "keycloak integration" # first one
205+
206+
# by auth_type (no match)
207+
found = integrations.find_by(auth_type=types.OAuthIntegrationAuthType.VIEWER)
208+
assert found is None
209+
210+
found = integrations.find_by(auth_type="Viewer")
211+
assert found is None
212+
213+
# by config
214+
found = integrations.find_by(config={"client_id": "rsconnect-oidc"})
215+
assert found is not None
216+
assert found["config"]["client_id"] == "rsconnect-oidc"
217+
assert found["name"] == "keycloak integration" # first one
218+
219+
# by config to find second integration
220+
found = integrations.find_by(config={"token_endpoint_auth_method": "client_secret_post"})
221+
assert found is not None
222+
assert found["config"]["token_endpoint_auth_method"] == "client_secret_post"
223+
assert found["name"] == "keycloak-post"
224+
225+
# by multiple criteria
226+
found = integrations.find_by(integration_type="custom", name="keycloak-post")
227+
assert found is not None
228+
assert found["template"] == "custom"
229+
assert found["name"] == "keycloak-post"
230+
231+
# find connect integration
232+
found = integrations.find_by(integration_type=types.OAuthIntegrationType.CONNECT)
233+
assert found is not None
234+
assert found["template"] == "connect"
235+
assert found["name"] == "Connect Visitor API Key"
236+
237+
found = integrations.find_by(integration_type="connect")
238+
assert found is not None
239+
assert found["template"] == "connect"
240+
assert found["name"] == "Connect Visitor API Key"
241+
242+
found = integrations.find_by(name="Connect Visitor API Key")
243+
assert found is not None
244+
assert found["name"] == "Connect Visitor API Key"
245+
246+
found = integrations.find_by(description="Allows access to Connect APIs")
247+
assert found is not None
248+
assert found["description"] == "Allows access to Connect APIs on behalf of a Connect user."
249+
250+
found = integrations.find_by(config={"scopes": "admin"})
251+
assert found is not None
252+
assert found["config"]["scopes"] == "admin"
253+
254+
# no match
255+
found = integrations.find_by(name="Non-existent Integration")
256+
assert found is None

tests/posit/connect/test_content.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,30 @@ def test(self):
376376
assert get_one["name"] == "Performance-Data-1671216053560"
377377

378378

379+
class TestContentCurrent:
380+
@responses.activate
381+
def test_with_env_var(self, monkeypatch):
382+
guid = "f2f37341-e21d-3d80-c698-a935ad614066"
383+
monkeypatch.setenv("CONNECT_CONTENT_GUID", guid)
384+
385+
responses.get(
386+
f"https://connect.example/__api__/v1/content/{guid}",
387+
json=load_mock(f"v1/content/{guid}.json"),
388+
match=[matchers.query_param_matcher({"include": "owner,tags,vanity_url"})],
389+
)
390+
c = Client("https://connect.example", "12345")
391+
content_item = c.content.current
392+
assert content_item["guid"] == guid
393+
394+
def test_without_env_var(self, monkeypatch):
395+
monkeypatch.delenv("CONNECT_CONTENT_GUID", raising=False)
396+
c = Client("https://connect.example", "12345")
397+
with pytest.raises(
398+
RuntimeError, match="CONNECT_CONTENT_GUID environment variable is not set."
399+
):
400+
_ = c.content.current
401+
402+
379403
class TestContentsCount:
380404
@responses.activate
381405
def test(self):

0 commit comments

Comments
 (0)