slack-objects 0.2.1__tar.gz → 0.3.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.
- {slack_objects-0.2.1 → slack_objects-0.3.0}/PKG-INFO +1 -1
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/_version.py +3 -3
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/idp_groups.py +113 -5
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects.egg-info/PKG-INFO +1 -1
- slack_objects-0.3.0/src/slack_objects.egg-info/scm_version.json +8 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/UnitTests/idp_groups_unit_test.py +128 -0
- slack_objects-0.2.1/src/slack_objects.egg-info/scm_version.json +0 -8
- {slack_objects-0.2.1 → slack_objects-0.3.0}/.github/copilot-instructions.md +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/.github/workflows/ci.yml +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/.github/workflows/publish-pypi.yml +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/.github/workflows/publish-testpypi.yml +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/.gitignore +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/LICENSE +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/README.md +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/pyproject.toml +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/pytest.ini +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/setup.cfg +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/__init__.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/api_caller.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/base.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/client.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/config.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/conversations.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/files.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/messages.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/rate_limits.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/scim_base.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/usergroups.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/users.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects/workspaces.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects.egg-info/SOURCES.txt +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects.egg-info/dependency_links.txt +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects.egg-info/requires.txt +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects.egg-info/scm_file_list.json +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/src/slack_objects.egg-info/top_level.txt +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Examples/examples_config.example.json +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Examples/users_search_by_email_example.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Examples/users_test_AzureKeyVault.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/conftest_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/run_all_scim_users_live_tests.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_idp_groups_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_users_create_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_users_deactivate_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_users_input_validation_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_users_make_guest_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_users_reactivate_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_users_update_attribute_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_users_update_email_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/_smoke_harness.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/api_caller_smoke_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/conversations_smoke_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/files_smoke_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/idp_groups_smoke_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/messages_smoke_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/run_all_smoke.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/security_smoke_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/usergroups_smoke_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/users_smoke_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Smoke/workspaces_smoke_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/UnitTests/config_unit_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/UnitTests/conversations_unit_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/UnitTests/files_unit_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/UnitTests/messages_unit_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/UnitTests/rate_limits_unit_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/UnitTests/usergroups_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/UnitTests/users_unit_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/UnitTests/workspaces_unit_test.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Users/conftest_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Users/run_all_users_live_tests.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Users/test_users_admin_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Users/test_users_classification_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Users/test_users_input_validation_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/Users/test_users_read_live.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/conftest.py +0 -0
- {slack_objects-0.2.1 → slack_objects-0.3.0}/tests/live_test_config.example.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slack-objects
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Opinionated, testable Python wrappers for Slack’s Web, Admin, and SCIM APIs, organized by object domain (users, conversations, messages, files, workspaces, and IdP groups). Designed for automation and administration workflows.
|
|
5
5
|
Author-email: "Marcos E. Mercado" <marcos_elias@hotmail.com>
|
|
6
6
|
Keywords: slack,objects,classes,slack objects,utilities,slack utilities,slack object types,slack types,types
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g88ac46367'
|
|
@@ -12,6 +12,7 @@ Manage Identity Provider (IdP) groups synced into Slack via SCIM.
|
|
|
12
12
|
This module implements the following functionality:
|
|
13
13
|
|
|
14
14
|
- list groups (paginated)
|
|
15
|
+
- get a single group's full SCIM record (name, members, metadata)
|
|
15
16
|
- get members of a given group
|
|
16
17
|
- check whether a user is a member of a group
|
|
17
18
|
|
|
@@ -20,6 +21,11 @@ Design decisions
|
|
|
20
21
|
- SCIM REST calls are centralized in ScimMixin._scim_request(); all public methods call endpoint wrappers.
|
|
21
22
|
- Uses an injectable `requests.Session` (`scim_session`) so tests can pass a fake session.
|
|
22
23
|
- Keeps legacy output shapes: lists of dicts for groups and members.
|
|
24
|
+
- ``get_group()`` is the single read path for GET Groups/{id}; ``get_members()``,
|
|
25
|
+
``is_member()`` and the ``display_name``/``members`` properties all compose on it
|
|
26
|
+
so the endpoint is only ever called from one place.
|
|
27
|
+
- Attributes are loaded lazily (like ``Users``): binding a group_id does no network I/O,
|
|
28
|
+
the fetch happens on first property access or on ``refresh()``.
|
|
23
29
|
- This module is SCIM-only. For Slack-native usergroups, see ``usergroups.py``.
|
|
24
30
|
"""
|
|
25
31
|
|
|
@@ -42,9 +48,18 @@ class IDP_groups(ScimMixin, SlackObjectBase):
|
|
|
42
48
|
idp = slack.idp_groups() # unbound
|
|
43
49
|
bound = slack.idp_groups("S123") # bound to a group_id
|
|
44
50
|
|
|
51
|
+
Binding a group_id performs no API call. `attributes` are loaded lazily via
|
|
52
|
+
`_require_attributes()` on first property access, so a single GET Groups/{id}
|
|
53
|
+
serves both the name and the members:
|
|
54
|
+
|
|
55
|
+
group = slack.idp_groups("S123")
|
|
56
|
+
name = group.display_name # triggers the fetch
|
|
57
|
+
members = group.members # served from the same cached response
|
|
58
|
+
|
|
45
59
|
The SCIM session can be replaced for unit tests by passing scim_session argument.
|
|
46
60
|
"""
|
|
47
61
|
group_id: Optional[str] = None
|
|
62
|
+
attributes: Dict[str, Any] = field(default_factory=dict)
|
|
48
63
|
scim_session: requests.Session = field(default_factory=requests.Session, repr=False)
|
|
49
64
|
|
|
50
65
|
# ---------- factory ----------
|
|
@@ -68,6 +83,41 @@ class IDP_groups(ScimMixin, SlackObjectBase):
|
|
|
68
83
|
raise ValueError("group_id is required (passed or bound)")
|
|
69
84
|
return gid
|
|
70
85
|
|
|
86
|
+
# ---------- lazily loaded attributes ----------
|
|
87
|
+
|
|
88
|
+
def refresh(self, group_id: Optional[str] = None) -> Dict[str, Any]:
|
|
89
|
+
"""
|
|
90
|
+
Refresh cached attributes for group_id (or self.group_id) using GET Groups/{id}.
|
|
91
|
+
|
|
92
|
+
Layered like ``Users.refresh()``: it calls the public ``get_group()``, which
|
|
93
|
+
calls the endpoint wrapper.
|
|
94
|
+
"""
|
|
95
|
+
if group_id:
|
|
96
|
+
self.group_id = group_id
|
|
97
|
+
if not self.group_id:
|
|
98
|
+
raise ValueError("refresh() requires group_id (passed or already set)")
|
|
99
|
+
|
|
100
|
+
self.attributes = self.get_group(self.group_id)
|
|
101
|
+
return self.attributes
|
|
102
|
+
|
|
103
|
+
def _require_attributes(self) -> Dict[str, Any]:
|
|
104
|
+
"""Ensure attributes are loaded (via refresh) before using helpers that read group fields."""
|
|
105
|
+
if self.attributes:
|
|
106
|
+
return self.attributes
|
|
107
|
+
if self.group_id:
|
|
108
|
+
return self.refresh()
|
|
109
|
+
raise ValueError("Group attributes not loaded and no group_id set (call refresh() or bind a group_id).")
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def display_name(self) -> str:
|
|
113
|
+
"""The group's SCIM ``displayName`` (loaded lazily)."""
|
|
114
|
+
return self._require_attributes().get("displayName", "")
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def members(self) -> List[Dict[str, str]]:
|
|
118
|
+
"""The group's members as ``{'value': <user id>, 'display': <name>}`` dicts (loaded lazily)."""
|
|
119
|
+
return self._require_attributes().get("members", []) or []
|
|
120
|
+
|
|
71
121
|
# ---------- endpoint wrappers (only these call _scim_request) ----------
|
|
72
122
|
|
|
73
123
|
def _scim_groups_list(self, *, count: int = 1000, start_index: Optional[int] = None) -> ScimResponse:
|
|
@@ -80,10 +130,21 @@ class IDP_groups(ScimMixin, SlackObjectBase):
|
|
|
80
130
|
params["startIndex"] = start_index
|
|
81
131
|
return self._scim_request(path="Groups", method="GET", params=params) # https://docs.slack.dev/reference/scim-api/#get-groups
|
|
82
132
|
|
|
83
|
-
def _scim_group_get(self, group_id: str) -> ScimResponse:
|
|
84
|
-
"""
|
|
133
|
+
def _scim_group_get(self, group_id: str, *, count: Optional[int] = None, start_index: Optional[int] = None) -> ScimResponse:
|
|
134
|
+
"""
|
|
135
|
+
Wrapper for GET Groups/{id}.
|
|
136
|
+
|
|
137
|
+
count/start_index page over the group's *members* sub-list, which matters for
|
|
138
|
+
groups larger than the server-side page size.
|
|
139
|
+
"""
|
|
85
140
|
validate_scim_id(group_id, "group_id")
|
|
86
|
-
|
|
141
|
+
params: Dict[str, Any] = {}
|
|
142
|
+
if count is not None:
|
|
143
|
+
params["count"] = count
|
|
144
|
+
if start_index is not None:
|
|
145
|
+
params["startIndex"] = start_index
|
|
146
|
+
# Pass params only when set, so the default call is byte-for-byte the legacy request.
|
|
147
|
+
return self._scim_request(path=f"Groups/{group_id}", method="GET", params=params or None) # https://docs.slack.dev/reference/scim-api/#get-groups-id
|
|
87
148
|
|
|
88
149
|
# ---------- public helpers ----------
|
|
89
150
|
|
|
@@ -130,16 +191,60 @@ class IDP_groups(ScimMixin, SlackObjectBase):
|
|
|
130
191
|
|
|
131
192
|
return groups_out
|
|
132
193
|
|
|
194
|
+
def get_group(self, group_id: Optional[str] = None, *, fetch_count: int = 1000) -> Dict[str, Any]:
|
|
195
|
+
"""
|
|
196
|
+
Return the full SCIM record for a single group (GET Groups/{id}).
|
|
197
|
+
|
|
198
|
+
One call yields both ``displayName`` and ``members``, so callers that need
|
|
199
|
+
the name do not have to pay for a second request. The complete payload is
|
|
200
|
+
returned (``id``, ``displayName``, ``members``, ``meta``, ``schemas``, ...) so
|
|
201
|
+
new SCIM fields are reachable without adding a method here.
|
|
202
|
+
|
|
203
|
+
When the response reports a ``totalResults`` larger than the members returned,
|
|
204
|
+
the remaining member pages are fetched and merged, so large groups are not
|
|
205
|
+
silently truncated.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
requests.HTTPError on non-2xx responses.
|
|
209
|
+
"""
|
|
210
|
+
gid = self._resolve_group_id(group_id)
|
|
211
|
+
|
|
212
|
+
# First request sends no pagination params, so it is identical to the legacy call.
|
|
213
|
+
# Copy so callers mutating the result cannot corrupt our cached attributes.
|
|
214
|
+
group = dict(self._scim_group_get(gid).data)
|
|
215
|
+
members: List[Dict[str, str]] = list(group.get("members") or [])
|
|
216
|
+
|
|
217
|
+
# totalResults is only present when the server actually paged the members;
|
|
218
|
+
# when it is absent the single response is complete and we skip the loop.
|
|
219
|
+
total_results = group.get("totalResults")
|
|
220
|
+
if total_results is not None:
|
|
221
|
+
while len(members) < int(total_results):
|
|
222
|
+
# SCIM startIndex is 1-based, and the server chooses the first page size,
|
|
223
|
+
# so derive the cursor from what we already have rather than from fetch_count.
|
|
224
|
+
start_index = len(members) + 1
|
|
225
|
+
page_members = self._scim_group_get(gid, count=fetch_count, start_index=start_index).data.get("members") or []
|
|
226
|
+
# An empty page means the server has nothing more; stop rather than loop forever.
|
|
227
|
+
if not page_members:
|
|
228
|
+
break
|
|
229
|
+
members.extend(page_members)
|
|
230
|
+
|
|
231
|
+
group["members"] = members
|
|
232
|
+
return group
|
|
233
|
+
|
|
133
234
|
def get_members(self, group_id: Optional[str] = None) -> List[Dict[str, str]]:
|
|
134
235
|
"""
|
|
135
236
|
Return group members via SCIM (GET Groups/{id}) as a list of dicts `{'value': <user_id>, 'display': <name>}`.
|
|
136
237
|
|
|
238
|
+
Composes on ``get_group()``; the cached attributes are reused when they already
|
|
239
|
+
describe the requested group, so property access and this method share one call.
|
|
240
|
+
|
|
137
241
|
This module is SCIM-only. For Slack-native usergroups, see
|
|
138
242
|
``Usergroups.get_members()`` in ``usergroups.py``.
|
|
139
243
|
"""
|
|
140
244
|
gid = self._resolve_group_id(group_id)
|
|
141
|
-
|
|
142
|
-
|
|
245
|
+
if self.attributes and self.attributes.get("id") == gid:
|
|
246
|
+
return self.attributes.get("members", []) or []
|
|
247
|
+
return self.get_group(gid).get("members", []) or []
|
|
143
248
|
|
|
144
249
|
def is_member(self, user_id: str, group_id: Optional[str] = None) -> bool:
|
|
145
250
|
"""
|
|
@@ -147,6 +252,9 @@ class IDP_groups(ScimMixin, SlackObjectBase):
|
|
|
147
252
|
|
|
148
253
|
Higher-level convenience that composes on ``get_members()``.
|
|
149
254
|
Preserves legacy semantics (scans the members list).
|
|
255
|
+
|
|
256
|
+
Each call fetches the group unless its attributes are already cached, so when
|
|
257
|
+
testing many users bind the group once (or hoist ``get_members()`` out of the loop).
|
|
150
258
|
"""
|
|
151
259
|
members = self.get_members(group_id=group_id)
|
|
152
260
|
for member in members:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slack-objects
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Opinionated, testable Python wrappers for Slack’s Web, Admin, and SCIM APIs, organized by object domain (users, conversations, messages, files, workspaces, and IdP groups). Designed for automation and administration workflows.
|
|
5
5
|
Author-email: "Marcos E. Mercado" <marcos_elias@hotmail.com>
|
|
6
6
|
Keywords: slack,objects,classes,slack objects,utilities,slack utilities,slack object types,slack types,types
|
|
@@ -225,6 +225,134 @@ def test_get_members_and_is_member_with_bound_group():
|
|
|
225
225
|
assert bound.is_member("U999") is False
|
|
226
226
|
|
|
227
227
|
|
|
228
|
+
def test_get_group_returns_full_record_in_one_call():
|
|
229
|
+
"""
|
|
230
|
+
Verifies:
|
|
231
|
+
- get_group() returns the complete SCIM payload (not just members)
|
|
232
|
+
- a single GET Groups/{id} serves both displayName and members
|
|
233
|
+
"""
|
|
234
|
+
from slack_objects.idp_groups import IDP_groups
|
|
235
|
+
|
|
236
|
+
cfg = DummyConfig()
|
|
237
|
+
base = _scim_base(cfg, "v1")
|
|
238
|
+
|
|
239
|
+
group_payload = {
|
|
240
|
+
"id": "S123",
|
|
241
|
+
"displayName": "Admins",
|
|
242
|
+
"meta": {"created": "2024-01-01T00:00:00Z"},
|
|
243
|
+
"members": [{"value": "U111", "display": "Alice"}],
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
sess = FakeScimSession({("GET", f"{base}Groups/S123"): (200, group_payload)})
|
|
247
|
+
|
|
248
|
+
idp = IDP_groups(cfg=cfg, client=DummySlackClient(), logger=logging.getLogger("test"), api=DummyApiCaller(), scim_session=sess)
|
|
249
|
+
idp.rate_policy = RateLimitPolicy(method_overrides={}, prefix_rules={}, default=0.0)
|
|
250
|
+
|
|
251
|
+
group = idp.get_group("S123")
|
|
252
|
+
|
|
253
|
+
assert group["displayName"] == "Admins"
|
|
254
|
+
assert group["members"] == group_payload["members"]
|
|
255
|
+
# Unmapped fields stay reachable, which is why the full payload is returned.
|
|
256
|
+
assert group["meta"]["created"] == "2024-01-01T00:00:00Z"
|
|
257
|
+
assert len(sess.calls) == 1
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_bound_properties_load_lazily_and_share_one_call():
|
|
261
|
+
"""
|
|
262
|
+
Verifies:
|
|
263
|
+
- binding a group_id performs no API call
|
|
264
|
+
- display_name triggers the fetch, and members reuses the cached response
|
|
265
|
+
"""
|
|
266
|
+
from slack_objects.idp_groups import IDP_groups
|
|
267
|
+
|
|
268
|
+
cfg = DummyConfig()
|
|
269
|
+
base = _scim_base(cfg, "v1")
|
|
270
|
+
|
|
271
|
+
group_payload = {
|
|
272
|
+
"id": "S123",
|
|
273
|
+
"displayName": "Admins",
|
|
274
|
+
"members": [{"value": "U111", "display": "Alice"}],
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
sess = FakeScimSession({("GET", f"{base}Groups/S123"): (200, group_payload)})
|
|
278
|
+
|
|
279
|
+
group = IDP_groups(cfg=cfg, client=DummySlackClient(), logger=logging.getLogger("test"), api=DummyApiCaller(), scim_session=sess, group_id="S123")
|
|
280
|
+
group.rate_policy = RateLimitPolicy(method_overrides={}, prefix_rules={}, default=0.0)
|
|
281
|
+
|
|
282
|
+
# Construction must not perform network I/O.
|
|
283
|
+
assert sess.calls == []
|
|
284
|
+
|
|
285
|
+
assert group.display_name == "Admins"
|
|
286
|
+
assert group.members == group_payload["members"]
|
|
287
|
+
# get_members() must reuse the cache rather than issue a second request.
|
|
288
|
+
assert group.get_members() == group_payload["members"]
|
|
289
|
+
assert len(sess.calls) == 1
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_properties_require_a_bound_group_id():
|
|
293
|
+
"""An unbound instance cannot lazily load, and must say so clearly."""
|
|
294
|
+
from slack_objects.idp_groups import IDP_groups
|
|
295
|
+
|
|
296
|
+
cfg = DummyConfig()
|
|
297
|
+
sess = FakeScimSession({})
|
|
298
|
+
|
|
299
|
+
idp = IDP_groups(cfg=cfg, client=DummySlackClient(), logger=logging.getLogger("test"), api=DummyApiCaller(), scim_session=sess)
|
|
300
|
+
idp.rate_policy = RateLimitPolicy(method_overrides={}, prefix_rules={}, default=0.0)
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
idp.display_name
|
|
304
|
+
except ValueError:
|
|
305
|
+
pass
|
|
306
|
+
else:
|
|
307
|
+
raise AssertionError("display_name should raise ValueError when no group_id is bound")
|
|
308
|
+
|
|
309
|
+
assert sess.calls == []
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_get_group_merges_paginated_members():
|
|
313
|
+
"""
|
|
314
|
+
Verifies:
|
|
315
|
+
- when totalResults exceeds the members returned, remaining pages are fetched
|
|
316
|
+
- members from all pages are merged, so large groups are not truncated
|
|
317
|
+
"""
|
|
318
|
+
from slack_objects.idp_groups import IDP_groups
|
|
319
|
+
|
|
320
|
+
cfg = DummyConfig()
|
|
321
|
+
|
|
322
|
+
page1 = {
|
|
323
|
+
"id": "S123",
|
|
324
|
+
"displayName": "Admins",
|
|
325
|
+
"totalResults": 3,
|
|
326
|
+
"members": [{"value": "U111", "display": "Alice"}, {"value": "U222", "display": "Bob"}],
|
|
327
|
+
}
|
|
328
|
+
page2 = {
|
|
329
|
+
"id": "S123",
|
|
330
|
+
"displayName": "Admins",
|
|
331
|
+
"totalResults": 3,
|
|
332
|
+
"members": [{"value": "U333", "display": "Carol"}],
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
sess = FakeScimSession({})
|
|
336
|
+
|
|
337
|
+
calls = []
|
|
338
|
+
|
|
339
|
+
def request_side_effect(method: str, url: str, **kwargs):
|
|
340
|
+
calls.append(kwargs.get("params"))
|
|
341
|
+
return FakeResponse(200, page1 if len(calls) == 1 else page2)
|
|
342
|
+
|
|
343
|
+
sess.request = request_side_effect # type: ignore[method-assign]
|
|
344
|
+
|
|
345
|
+
idp = IDP_groups(cfg=cfg, client=DummySlackClient(), logger=logging.getLogger("test"), api=DummyApiCaller(), scim_session=sess)
|
|
346
|
+
idp.rate_policy = RateLimitPolicy(method_overrides={}, prefix_rules={}, default=0.0)
|
|
347
|
+
|
|
348
|
+
group = idp.get_group("S123", fetch_count=2)
|
|
349
|
+
|
|
350
|
+
assert [m["value"] for m in group["members"]] == ["U111", "U222", "U333"]
|
|
351
|
+
# First call is the plain legacy request; the second resumes after what we already have.
|
|
352
|
+
assert calls[0] is None
|
|
353
|
+
assert calls[1]["startIndex"] == 3
|
|
354
|
+
|
|
355
|
+
|
|
228
356
|
# -----------------------------
|
|
229
357
|
# Optional "factory-style" demo
|
|
230
358
|
# -----------------------------
|
|
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
|
{slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_users_input_validation_live.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{slack_objects-0.2.1 → slack_objects-0.3.0}/tests/SCIM/test_scim_users_update_attribute_live.py
RENAMED
|
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
|