diaspora-event-sdk 0.4.1__py3-none-any.whl → 0.4.3__py3-none-any.whl

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.
@@ -1,4 +1,3 @@
1
- import json
2
1
  from typing import Optional
3
2
 
4
3
  import globus_sdk
@@ -25,131 +24,47 @@ class WebClient(globus_sdk.BaseClient):
25
24
  self._user_app_name = None
26
25
  self.user_app_name = app_name
27
26
 
28
- def create_key(self, subject: UUID_LIKE_T) -> globus_sdk.GlobusHTTPResponse:
29
- return self.get("/api/v2/create_key", headers={"Subject": str(subject)})
30
-
31
- def list_topics(self, subject: UUID_LIKE_T) -> globus_sdk.GlobusHTTPResponse:
32
- return self.get("/api/v2/topics", headers={"Subject": str(subject)})
33
-
34
- def register_topic(
35
- self, subject: UUID_LIKE_T, topic: str, action: str
36
- ) -> globus_sdk.GlobusHTTPResponse:
37
- return self.put(
38
- f"/api/v2/topic/{topic}",
39
- headers={"Subject": str(subject), "Action": action},
40
- )
27
+ def create_user(self, subject: UUID_LIKE_T) -> globus_sdk.GlobusHTTPResponse:
28
+ """Call the v3 create_user endpoint (POST /api/v3/user)."""
29
+ return self.post("/api/v3/user", headers={"Subject": str(subject)})
41
30
 
42
- def get_topic_configs(
43
- self, subject: UUID_LIKE_T, topic: str
44
- ) -> globus_sdk.GlobusHTTPResponse:
45
- return self.get(
46
- f"/api/v2/topic/{topic}", headers={"Subject": str(subject), "Topic": topic}
47
- )
31
+ def delete_user(self, subject: UUID_LIKE_T) -> globus_sdk.GlobusHTTPResponse:
32
+ """Call the v3 delete_user endpoint (DELETE /api/v3/user)."""
33
+ return self.delete("/api/v3/user", headers={"Subject": str(subject)})
48
34
 
49
- def update_topic_configs(
50
- self, subject: UUID_LIKE_T, topic: str, configs: dict
51
- ) -> globus_sdk.GlobusHTTPResponse:
52
- return self.post(
53
- f"/api/v2/topic/{topic}",
54
- headers={
55
- "Subject": str(subject),
56
- "Topic": topic,
57
- "Content-Type": "text/plain",
58
- },
59
- data=json.dumps(configs).encode("utf-8"),
60
- )
35
+ def create_key(self, subject: UUID_LIKE_T) -> globus_sdk.GlobusHTTPResponse:
36
+ """Call the v3 create_key endpoint (POST /api/v3/key)."""
37
+ return self.post("/api/v3/key", headers={"Subject": str(subject)})
61
38
 
62
- def update_topic_partitions(
63
- self, subject: UUID_LIKE_T, topic: str, new_partitions: int
64
- ) -> globus_sdk.GlobusHTTPResponse:
65
- return self.post(
66
- f"/api/v2/topic/{topic}/partitions",
67
- headers={
68
- "Subject": str(subject),
69
- "Topic": topic,
70
- "NewPartitions": str(new_partitions),
71
- },
72
- )
39
+ def delete_key(self, subject: UUID_LIKE_T) -> globus_sdk.GlobusHTTPResponse:
40
+ """Call the v3 delete_key endpoint (DELETE /api/v3/key)."""
41
+ return self.delete("/api/v3/key", headers={"Subject": str(subject)})
73
42
 
74
- def reset_topic(
75
- self, subject: UUID_LIKE_T, topic: str
76
- ) -> globus_sdk.GlobusHTTPResponse:
77
- return self.post(
78
- f"/api/v2/topic/{topic}/reset",
79
- headers={"Subject": str(subject), "Topic": topic},
80
- )
43
+ def list_namespaces(self, subject: UUID_LIKE_T) -> globus_sdk.GlobusHTTPResponse:
44
+ """Call the v3 list_namespaces endpoint (GET /api/v3/namespace)."""
45
+ return self.get("/api/v3/namespace", headers={"Subject": str(subject)})
81
46
 
82
- def grant_user_access(
83
- self, subject: UUID_LIKE_T, topic: str, user: UUID_LIKE_T, action: str
47
+ def create_topic(
48
+ self, subject: UUID_LIKE_T, namespace: str, topic: str
84
49
  ) -> globus_sdk.GlobusHTTPResponse:
50
+ """Call the v3 create_topic endpoint (POST /api/v3/{namespace}/{topic})."""
85
51
  return self.post(
86
- f"/api/v2/topic/{topic}/user",
87
- headers={
88
- "Subject": str(subject),
89
- "Action": action,
90
- "Topic": topic,
91
- "User": str(user),
92
- },
52
+ f"/api/v3/{namespace}/{topic}", headers={"Subject": str(subject)}
93
53
  )
94
54
 
95
- def list_topic_users(
96
- self, subject: UUID_LIKE_T, topic: str
55
+ def delete_topic(
56
+ self, subject: UUID_LIKE_T, namespace: str, topic: str
97
57
  ) -> globus_sdk.GlobusHTTPResponse:
98
- return self.get(
99
- f"/api/v2/topic/{topic}/users",
100
- headers={"Subject": str(subject), "Topic": topic},
58
+ """Call the v3 delete_topic endpoint (DELETE /api/v3/{namespace}/{topic})."""
59
+ return self.delete(
60
+ f"/api/v3/{namespace}/{topic}", headers={"Subject": str(subject)}
101
61
  )
102
62
 
103
- def list_triggers(self, subject: UUID_LIKE_T) -> globus_sdk.GlobusHTTPResponse:
104
- return self.get("/api/v2/triggers", headers={"Subject": str(subject)})
105
-
106
- def create_trigger(
107
- self,
108
- subject: UUID_LIKE_T,
109
- topic: str,
110
- function: str,
111
- action: str,
112
- function_configs: dict,
113
- trigger_configs: dict,
63
+ def recreate_topic(
64
+ self, subject: UUID_LIKE_T, namespace: str, topic: str
114
65
  ) -> globus_sdk.GlobusHTTPResponse:
66
+ """Call the v3 recreate_topic endpoint (PUT /api/v3/{namespace}/{topic}/recreate)."""
115
67
  return self.put(
116
- "/api/v2/trigger",
117
- headers={
118
- "Subject": str(subject),
119
- "Topic": topic,
120
- "Trigger": function,
121
- "Action": action,
122
- "Content-Type": "text/plain",
123
- },
124
- data=json.dumps(
125
- {"function": function_configs, "trigger": trigger_configs}
126
- ).encode("utf-8"),
127
- )
128
-
129
- def update_trigger(
130
- self, subject: UUID_LIKE_T, trigger_uuid: UUID_LIKE_T, trigger_configs: dict
131
- ) -> globus_sdk.GlobusHTTPResponse:
132
- return self.post(
133
- f"/api/v2/triggers/{trigger_uuid}",
134
- headers={
135
- "Subject": str(subject),
136
- "Trigger_id": str(trigger_uuid),
137
- "Content-Type": "text/plain",
138
- },
139
- data=json.dumps(trigger_configs).encode("utf-8"),
140
- )
141
-
142
- def list_log_streams(
143
- self, subject: UUID_LIKE_T, trigger: str
144
- ) -> globus_sdk.GlobusHTTPResponse:
145
- return self.get(
146
- "/api/v2/logs", headers={"Subject": str(subject), "Trigger": trigger}
147
- )
148
-
149
- def get_log_events(
150
- self, subject: UUID_LIKE_T, trigger: str, stream: str
151
- ) -> globus_sdk.GlobusHTTPResponse:
152
- return self.get(
153
- "/api/v2/log",
154
- headers={"Subject": str(subject), "Trigger": trigger, "Stream": stream},
68
+ f"/api/v3/{namespace}/{topic}/recreate",
69
+ headers={"Subject": str(subject)},
155
70
  )
@@ -1 +1 @@
1
- __version__ = "0.4.1"
1
+ __version__ = "0.4.3"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: diaspora-event-sdk
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Diaspora Event Fabric SDK
5
5
  Home-page: https://github.com/globus-labs/diaspora-event-sdk
6
6
  License: Apache 2.0
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
- Requires-Dist: globus-sdk<4,>=3.20.1
19
+ Requires-Dist: globus-sdk<4,>=3.59.0
20
20
  Provides-Extra: kafka-python
21
21
  Requires-Dist: kafka-python; extra == "kafka-python"
22
22
  Provides-Extra: test
@@ -27,6 +27,15 @@ Requires-Dist: mypy; extra == "test"
27
27
  Requires-Dist: tox; extra == "test"
28
28
  Requires-Dist: check-manifest; extra == "test"
29
29
  Requires-Dist: pre-commit; extra == "test"
30
+ Dynamic: classifier
31
+ Dynamic: description
32
+ Dynamic: description-content-type
33
+ Dynamic: home-page
34
+ Dynamic: license
35
+ Dynamic: license-file
36
+ Dynamic: provides-extra
37
+ Dynamic: requires-dist
38
+ Dynamic: summary
30
39
 
31
40
  # Diaspora Event Fabric SDK
32
41
 
@@ -1,12 +1,12 @@
1
- diaspora_event_sdk/__init__.py,sha256=x5A69JeA_iNi0i1XK3T2wuSTiOLxRhNdGYN4G39wtFw,455
2
- diaspora_event_sdk/version.py,sha256=pMtTmSUht-XtbR_7Doz6bsQqopJJd8rZ8I8zy2HwwoA,22
1
+ diaspora_event_sdk/__init__.py,sha256=Tayc-lBVFUjkbs-Qvxg_J6vBM0Mhl4yhef53cdkUoPI,426
2
+ diaspora_event_sdk/version.py,sha256=Nyg0pmk5ea9-SLCAFEIF96ByFx4-TJFtrqYPN-Zn6g4,22
3
3
  diaspora_event_sdk/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- diaspora_event_sdk/sdk/_environments.py,sha256=HH5mDqxmgryJfc_LOYkfSL6V9oSC6GmIvPvUV-qbn0c,697
4
+ diaspora_event_sdk/sdk/_environments.py,sha256=zuvyoMtGsUA3J0GI92NBn-z_1J4GbiE7WaUgKe8-dpI,501
5
5
  diaspora_event_sdk/sdk/aws_iam_msk.py,sha256=9fPH7lgdfZhxKohdZE7shGPYIA4-X-XRbA5KdY-Agjo,3933
6
- diaspora_event_sdk/sdk/client.py,sha256=6UuFuFX02Q62_iOkjRHDp7iFQs9vGRJ3J-QDa_rqJDc,8767
6
+ diaspora_event_sdk/sdk/client.py,sha256=RMSWVTsW6JQr9L2rp5LktMw5oztpRJkrIQk5GFo8Ses,4277
7
7
  diaspora_event_sdk/sdk/decorators.py,sha256=Gel8AyhIjbf4-FNintTNcOqvC9hHH_YwbOH257Nfmf0,884
8
- diaspora_event_sdk/sdk/kafka_client.py,sha256=sXkqGx3ckyvaXU35um7EgAFhmqpDd5szl7ZVUYZD2zM,4780
9
- diaspora_event_sdk/sdk/web_client.py,sha256=mc6OvYgq8Cl-irjOW5FfhU_1fl1nRF8OKlPPmaQc0zo,4992
8
+ diaspora_event_sdk/sdk/kafka_client.py,sha256=WSYkyGRmTtPtLQiPe-Slc3Zq2QqohVVX1d_qJu7h6ZE,6124
9
+ diaspora_event_sdk/sdk/web_client.py,sha256=VtEk4luVw7afc0vhtGhG-qd4vWvuBGLA6vkU_qriuoA,2791
10
10
  diaspora_event_sdk/sdk/botocore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  diaspora_event_sdk/sdk/botocore/auth.py,sha256=QXwCNMzM0wAqD64RlFd-K27atRpixti73VKConpb1kk,18699
12
12
  diaspora_event_sdk/sdk/botocore/awsrequest.py,sha256=fcR28YxbsCk2ZT1rGtgWxhje-sYt-SxIqixjOWAFLcM,9344
@@ -19,16 +19,15 @@ diaspora_event_sdk/sdk/login_manager/client_login.py,sha256=e8o6Tf40AW61auHNhQ_5
19
19
  diaspora_event_sdk/sdk/login_manager/decorators.py,sha256=JlCEwoceSMmFy6PLQ5kz5mpkKfitJgCVAA6I4I_k1nw,1013
20
20
  diaspora_event_sdk/sdk/login_manager/globus_auth.py,sha256=pbDy67Mdf4SKFf0IlXD3ZFGj4lG-xpzFTD1ygW-0H1k,430
21
21
  diaspora_event_sdk/sdk/login_manager/login_flow.py,sha256=NjmDtr9QWCG1SIPt9TL3pxcUgLfz2qzzWVHBBMJWvE0,978
22
- diaspora_event_sdk/sdk/login_manager/manager.py,sha256=AD3f8rx154oesgkpqG0K-eZsEGjntr08Vgrxr1m6uao,7040
22
+ diaspora_event_sdk/sdk/login_manager/manager.py,sha256=NU5uzYE8UTiQjMLhpO7Mrr3vbUZy5DYQ3u60wUEHZVc,9335
23
23
  diaspora_event_sdk/sdk/login_manager/protocol.py,sha256=ipAOUi7GYF5YfU-az1LWUbU_rNXHarDwUkKn6TUyjVY,678
24
24
  diaspora_event_sdk/sdk/login_manager/tokenstore.py,sha256=ImncC8EIxoAuGtDiZIwdtUgOD2fWo8oBP22G-fiZ5L4,2036
25
25
  diaspora_event_sdk/sdk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  diaspora_event_sdk/sdk/utils/uuid_like.py,sha256=xbxf0YXpDhdii16lwPLWRN21qFekHrNrqODSToMPtCg,470
27
+ diaspora_event_sdk-0.4.3.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
27
28
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- tests/unit/apis_test.py,sha256=Fo_WxVARfcqIPmKuLHJFAwRINW-3wzEWmttunOcOhMQ,4463
29
- tests/unit/client_test.py,sha256=kmvrZnxzri20BDO8g8IWiZvnQGNjD1b970kLr4BwEe4,3012
30
- diaspora_event_sdk-0.4.1.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
31
- diaspora_event_sdk-0.4.1.dist-info/METADATA,sha256=-AvJC2ZPBxMe7pGEYfNXSfE0lfYRqOsZqv6LS-0AhWQ,3631
32
- diaspora_event_sdk-0.4.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
33
- diaspora_event_sdk-0.4.1.dist-info/top_level.txt,sha256=OVun-67t3fkLFEIwvJuNINgFFvAc--bClYhXjLhMmvs,25
34
- diaspora_event_sdk-0.4.1.dist-info/RECORD,,
29
+ tests/unit/apis_test.py,sha256=x9Pd8Ss2COtXk7nKez4wHw0-Xp7GDeJT9bIWXJ1K5GQ,9140
30
+ diaspora_event_sdk-0.4.3.dist-info/METADATA,sha256=xbh2IbvzvA9GzImKof6slxSVw2E0OGe524Tcf1hEbI8,3828
31
+ diaspora_event_sdk-0.4.3.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
32
+ diaspora_event_sdk-0.4.3.dist-info/top_level.txt,sha256=OVun-67t3fkLFEIwvJuNINgFFvAc--bClYhXjLhMmvs,25
33
+ diaspora_event_sdk-0.4.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.45.1)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
tests/unit/apis_test.py CHANGED
@@ -1,9 +1,8 @@
1
1
  import pytest
2
2
  import os
3
3
  import logging
4
- from globus_sdk import ConfidentialAppAuthClient
4
+ import uuid
5
5
  from diaspora_event_sdk import Client
6
- from diaspora_event_sdk.sdk.login_manager import tokenstore
7
6
 
8
7
  # Configure module-level logger
9
8
  logging.basicConfig(
@@ -14,123 +13,277 @@ logger = logging.getLogger(__name__)
14
13
 
15
14
 
16
15
  @pytest.fixture(scope="module")
17
- def setup():
18
- client_id = os.environ["DIASPORA_SDK_CLIENT_ID"]
19
- client_secret = os.environ["DIASPORA_SDK_CLIENT_SECRET"]
20
- requested_scopes = os.environ["CLIENT_SCOPE"]
21
- # assert client_id
22
- # assert client_secret
23
- # assert requested_scopes
24
-
25
- ca = ConfidentialAppAuthClient(
26
- client_id=client_id,
27
- client_secret=client_secret,
16
+ def client():
17
+ """Create a Client instance for integration tests.
18
+
19
+ Requires environment variables:
20
+ - DIASPORA_SDK_CLIENT_ID
21
+ - DIASPORA_SDK_CLIENT_SECRET
22
+ - DIASPORA_SCOPE (optional, defaults to action_all scope)
23
+
24
+ Note: CLIENT_SCOPE is used for backward compatibility but should only
25
+ contain scopes for a single resource server.
26
+ """
27
+ # Ensure required environment variables are set
28
+ assert os.environ.get("DIASPORA_SDK_CLIENT_ID"), (
29
+ "DIASPORA_SDK_CLIENT_ID must be set"
28
30
  )
29
- token_response = ca.oauth2_client_credentials_tokens(
30
- requested_scopes=requested_scopes,
31
+ assert os.environ.get("DIASPORA_SDK_CLIENT_SECRET"), (
32
+ "DIASPORA_SDK_CLIENT_SECRET must be set"
31
33
  )
32
- token = token_response.by_resource_server[client_id]
33
34
 
34
- storage = tokenstore.get_token_storage_adapter()
35
- storage.store(token_response)
36
- storage.get_by_resource_server()
35
+ # Set DIASPORA_SCOPE if CLIENT_SCOPE is set (for backward compatibility)
36
+ # This ensures the LoginManager uses the correct scope for client credentials
37
+ if "CLIENT_SCOPE" in os.environ and "DIASPORA_SCOPE" not in os.environ:
38
+ os.environ["DIASPORA_SCOPE"] = os.environ["CLIENT_SCOPE"]
37
39
 
38
- return {
39
- "client_id": client_id,
40
- "client_secret": client_secret,
41
- "requested_scopes": requested_scopes,
42
- "token_response": token_response,
43
- "token": token,
44
- }
40
+ return Client()
45
41
 
46
42
 
47
- @pytest.fixture(scope="module")
48
- def client():
49
- return Client()
43
+ @pytest.mark.integration
44
+ def test_create_user(client):
45
+ """Test create_user API."""
46
+ result = client.create_user()
47
+ logger.info(f"create_user result: {result}")
48
+ assert result["status"] == "success"
49
+ assert "subject" in result
50
+ assert "namespace" in result
51
+ assert result["namespace"].startswith("ns-")
52
+
53
+
54
+ @pytest.mark.integration
55
+ def test_create_user_idempotent(client):
56
+ """Test create_user is idempotent (can be called multiple times)."""
57
+ result1 = client.create_user()
58
+ logger.info(f"create_user first call: {result1}")
59
+ assert result1["status"] == "success"
60
+
61
+ result2 = client.create_user()
62
+ logger.info(f"create_user second call: {result2}")
63
+ assert result2["status"] == "success"
64
+ # Should return same namespace
65
+ assert result1["namespace"] == result2["namespace"]
66
+
67
+
68
+ @pytest.mark.integration
69
+ def test_create_key(client):
70
+ """Test create_key API."""
71
+ # Ensure user exists first
72
+ client.create_user()
73
+
74
+ result = client.create_key()
75
+ logger.info(f"create_key result: {result}")
76
+ assert result["status"] == "success"
77
+ assert "access_key" in result
78
+ assert "secret_key" in result
79
+ assert "create_date" in result
80
+ assert "endpoint" in result
81
+ assert len(result["access_key"]) > 0
82
+ assert len(result["secret_key"]) > 0
83
+
84
+
85
+ @pytest.mark.integration
86
+ def test_create_key_idempotent(client):
87
+ """Test create_key is idempotent (returns existing key if present)."""
88
+ # Ensure user exists
89
+ client.create_user()
90
+
91
+ result1 = client.create_key()
92
+ logger.info(f"create_key first call: {result1}")
93
+ assert result1["status"] == "success"
94
+ access_key1 = result1["access_key"]
95
+
96
+ result2 = client.create_key()
97
+ logger.info(f"create_key second call: {result2}")
98
+ assert result2["status"] == "success"
99
+ # Should return same access key (idempotent)
100
+ assert result2["access_key"] == access_key1
101
+
102
+
103
+ @pytest.mark.integration
104
+ def test_list_namespaces(client):
105
+ """Test list_namespaces API."""
106
+ # Ensure user and namespace exist
107
+ client.create_user()
108
+
109
+ result = client.list_namespaces()
110
+ logger.info(f"list_namespaces result: {result}")
111
+ assert result["status"] == "success"
112
+ assert "namespaces" in result
113
+ assert isinstance(result["namespaces"], dict)
114
+ # Should include the default namespace
115
+ assert client.namespace in result["namespaces"]
116
+
117
+
118
+ @pytest.mark.integration
119
+ def test_create_topic(client):
120
+ """Test create_topic API."""
121
+ # Ensure user exists
122
+ client.create_user()
123
+
124
+ topic_name = f"test-topic-{str(uuid.uuid4())[:8]}"
125
+ result = client.create_topic(topic_name)
126
+ logger.info(f"create_topic result: {result}")
127
+ assert result["status"] == "success"
128
+ assert "topics" in result
129
+ assert isinstance(result["topics"], list)
130
+ assert topic_name in result["topics"]
131
+
132
+ # Cleanup
133
+ client.delete_topic(topic_name)
134
+
135
+
136
+ @pytest.mark.integration
137
+ def test_create_topic_idempotent(client):
138
+ """Test create_topic is idempotent (can be called multiple times)."""
139
+ # Ensure user exists
140
+ client.create_user()
141
+
142
+ topic_name = f"test-topic-{str(uuid.uuid4())[:8]}"
143
+ result1 = client.create_topic(topic_name)
144
+ logger.info(f"create_topic first call: {result1}")
145
+ assert result1["status"] == "success"
146
+
147
+ result2 = client.create_topic(topic_name)
148
+ logger.info(f"create_topic second call: {result2}")
149
+ assert result2["status"] == "success"
150
+ # Topic should still be in the list
151
+ assert topic_name in result2["topics"]
152
+
153
+ # Cleanup
154
+ client.delete_topic(topic_name)
155
+
156
+
157
+ @pytest.mark.integration
158
+ def test_delete_topic(client):
159
+ """Test delete_topic API."""
160
+ # Ensure user exists
161
+ client.create_user()
162
+
163
+ topic_name = f"test-topic-{str(uuid.uuid4())[:8]}"
164
+ # Create topic first
165
+ create_result = client.create_topic(topic_name)
166
+ assert create_result["status"] == "success"
167
+ assert topic_name in create_result["topics"]
168
+
169
+ # Delete topic
170
+ result = client.delete_topic(topic_name)
171
+ logger.info(f"delete_topic result: {result}")
172
+ assert result["status"] == "success"
173
+ assert "topics" in result
174
+ assert topic_name not in result["topics"]
175
+
176
+
177
+ @pytest.mark.integration
178
+ def test_delete_topic_idempotent(client):
179
+ """Test delete_topic is idempotent (can be called multiple times)."""
180
+ # Ensure user exists
181
+ client.create_user()
182
+
183
+ topic_name = f"test-topic-{str(uuid.uuid4())[:8]}"
184
+ # Create topic first
185
+ client.create_topic(topic_name)
186
+
187
+ # Delete topic first time
188
+ result1 = client.delete_topic(topic_name)
189
+ logger.info(f"delete_topic first call: {result1}")
190
+ assert result1["status"] == "success"
191
+
192
+ # Delete topic second time (should still succeed)
193
+ result2 = client.delete_topic(topic_name)
194
+ logger.info(f"delete_topic second call: {result2}")
195
+ assert result2["status"] in ("success", "failure") # May fail if already deleted
196
+
197
+
198
+ @pytest.mark.integration
199
+ def test_recreate_topic(client):
200
+ """Test recreate_topic API."""
201
+ # Ensure user exists
202
+ client.create_user()
203
+
204
+ topic_name = f"test-topic-{str(uuid.uuid4())[:8]}"
205
+ # Create topic first
206
+ create_result = client.create_topic(topic_name)
207
+ assert create_result["status"] == "success"
208
+
209
+ # Recreate topic
210
+ result = client.recreate_topic(topic_name)
211
+ logger.info(f"recreate_topic result: {result}")
212
+ assert result["status"] == "success"
213
+
214
+ # Cleanup
215
+ client.delete_topic(topic_name)
216
+
217
+
218
+ @pytest.mark.integration
219
+ def test_delete_key(client):
220
+ """Test delete_key API."""
221
+ # Ensure user and key exist
222
+ client.create_user()
223
+ client.create_key()
224
+
225
+ result = client.delete_key()
226
+ logger.info(f"delete_key result: {result}")
227
+ assert result["status"] == "success"
228
+
229
+
230
+ @pytest.mark.integration
231
+ def test_delete_key_idempotent(client):
232
+ """Test delete_key is idempotent (can be called multiple times)."""
233
+ # Ensure user and key exist
234
+ client.create_user()
235
+ client.create_key()
236
+
237
+ # Delete key first time
238
+ result1 = client.delete_key()
239
+ logger.info(f"delete_key first call: {result1}")
240
+ assert result1["status"] == "success"
241
+
242
+ # Delete key second time (should still succeed)
243
+ result2 = client.delete_key()
244
+ logger.info(f"delete_key second call: {result2}")
245
+ assert result2["status"] in ("success", "failure") # May fail if already deleted
246
+
247
+
248
+ @pytest.mark.integration
249
+ def test_full_lifecycle(client):
250
+ """Test full user lifecycle: create user, key, topic, then cleanup."""
251
+ # 1. Create user
252
+ user_result = client.create_user()
253
+ logger.info(f"Created user: {user_result}")
254
+ assert user_result["status"] == "success"
255
+ namespace = user_result["namespace"]
256
+
257
+ # 2. Create key
258
+ key_result = client.create_key()
259
+ logger.info(f"Created key: {key_result}")
260
+ assert key_result["status"] == "success"
261
+ assert "access_key" in key_result
262
+
263
+ # 3. List namespaces
264
+ namespaces_result = client.list_namespaces()
265
+ logger.info(f"Listed namespaces: {namespaces_result}")
266
+ assert namespaces_result["status"] == "success"
267
+ assert namespace in namespaces_result["namespaces"]
268
+
269
+ # 4. Create topic
270
+ topic_name = f"test-topic-{str(uuid.uuid4())[:8]}"
271
+ topic_result = client.create_topic(topic_name)
272
+ logger.info(f"Created topic: {topic_result}")
273
+ assert topic_result["status"] == "success"
274
+ assert topic_name in topic_result["topics"]
275
+
276
+ # 5. Delete topic
277
+ delete_topic_result = client.delete_topic(topic_name)
278
+ logger.info(f"Deleted topic: {delete_topic_result}")
279
+ assert delete_topic_result["status"] == "success"
50
280
 
281
+ # 6. Delete key
282
+ delete_key_result = client.delete_key()
283
+ logger.info(f"Deleted key: {delete_key_result}")
284
+ assert delete_key_result["status"] == "success"
51
285
 
52
- def test_create_key(setup, client):
53
- key_response = client.create_key()
54
- assert isinstance(key_response, dict)
55
- assert "access_key" in key_response
56
- assert "secret_key" in key_response
57
- assert "endpoint" in key_response
58
-
59
-
60
- def test_register_topic(setup, client):
61
- topic = "topic" + client.subject_openid[-12:]
62
- register_response = client.register_topic(topic)
63
- assert register_response["status"] in ["success", "no-op"]
64
- assert "message" in register_response
65
-
66
-
67
- def test_list_topics(setup, client):
68
- topics = client.list_topics()
69
- print(client.subject_openid, topics)
70
- assert topics["status"] == "success"
71
- assert isinstance(topics["topics"], list)
72
- assert len(topics["topics"]) > 0
73
- expected_topics = ["diaspora-cicd"]
74
- assert set(expected_topics).issubset(set(topics["topics"]))
75
-
76
-
77
- def test_get_topic_configs(setup, client):
78
- topic = "topic" + client.subject_openid[-12:]
79
- client.register_topic(topic)
80
- configs_response = client.get_topic_configs(topic)
81
- assert configs_response["status"] == "success"
82
- assert "configs" in configs_response
83
- assert isinstance(configs_response["configs"], dict)
84
-
85
-
86
- def test_update_topic_configs(setup, client):
87
- topic = "topic" + client.subject_openid[-12:]
88
- client.register_topic(topic)
89
- configs = {"min.insync.replicas": 1}
90
- update_response = client.update_topic_configs(topic, configs)
91
- assert update_response["status"] == "success"
92
- assert "before" in update_response
93
- assert "after" in update_response
94
- assert isinstance(update_response["before"], dict)
95
- assert isinstance(update_response["after"], dict)
96
-
97
-
98
- def test_update_topic_partitions(setup, client):
99
- topic = "topic" + client.subject_openid[-12:]
100
- client.register_topic(topic)
101
- new_partitions = 2
102
- partitions_response = client.update_topic_partitions(topic, new_partitions)
103
- assert partitions_response["status"] in ["success", "error"]
104
- if partitions_response["status"] == "error":
105
- assert "message" in partitions_response
106
-
107
-
108
- def test_reset_topic(setup, client):
109
- topic = "topic" + client.subject_openid[-12:]
110
- client.register_topic(topic)
111
- reset_response = client.reset_topic(topic)
112
- assert reset_response["status"] in ["success", "error"]
113
- if reset_response["status"] == "error":
114
- assert "message" in reset_response
115
-
116
-
117
- def test_user_access_management(setup, client):
118
- topic = "topic" + client.subject_openid[-12:]
119
- client.register_topic(topic)
120
- user_id = "diaspora-cicd"
121
- grant_response = client.grant_user_access(topic, user_id)
122
- assert grant_response["status"] in ["success", "no-op"]
123
- assert "message" in grant_response
124
-
125
- list_users_response = client.list_topic_users(topic)
126
- assert list_users_response["status"] == "success"
127
- assert "users" in list_users_response
128
- assert isinstance(list_users_response["users"], list)
129
-
130
- revoke_response = client.revoke_user_access(topic, user_id)
131
- assert revoke_response["status"] in ["success", "no-op"]
132
- assert "message" in revoke_response
133
-
134
-
135
- if __name__ == "__main__":
136
- pytest.main(["-s", "tests/unit/test_apis.py"])
286
+ # 7. Delete user
287
+ delete_user_result = client.delete_user()
288
+ logger.info(f"Deleted user: {delete_user_result}")
289
+ assert delete_user_result["status"] == "success"