Skip to content

Commit 0469480

Browse files
committed
fix: segments contacts endpoint, empty API response handling, completion newline
- segments contacts: use alias-based search instead of non-existent /segments/{id}/contacts endpoint - client: handle empty response bodies and non-JSON responses gracefully instead of crashing with JSONDecodeError (Mautic returns empty body on 401 with application/json content-type) - completion: add newline before eval in shell rc append to prevent concatenation with previous line
1 parent 89ea62f commit 0469480

4 files changed

Lines changed: 36 additions & 5 deletions

File tree

src/mautic_cli/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def completion(shell):
167167
if shell == "fish":
168168
con.print(f" mautic completion fish > {rc_file}\n")
169169
else:
170-
con.print(f' echo \'eval "$(mautic completion {shell})"\' >> {rc_file}')
170+
con.print(f" echo '\\neval \"$(mautic completion {shell})\"' >> {rc_file}")
171171
con.print(f" source {rc_file}\n")
172172
else:
173173
click.echo(result.stdout, nl=False)

src/mautic_cli/client.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,21 @@ def _request(self, method: str, path: str, **kwargs) -> dict:
182182
if self.verbose:
183183
print(f"<< {response.status_code} (after token refresh)", file=sys.stderr)
184184

185-
data = response.json()
185+
if not response.content:
186+
content_type = response.headers.get("content-type", "")
187+
raise MauticApiError(
188+
message=f"Empty response body (HTTP {response.status_code}). Content-Type: {content_type}",
189+
code=response.status_code,
190+
error_type="empty_response",
191+
)
192+
try:
193+
data = response.json()
194+
except Exception:
195+
raise MauticApiError(
196+
message=f"Response is not valid JSON (HTTP {response.status_code})",
197+
code=response.status_code,
198+
error_type="invalid_response",
199+
)
186200
if response.status_code >= 400:
187201
self._raise_api_error(data, response.status_code)
188202
return data

src/mautic_cli/commands/segments.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,18 @@ def get(mctx: MauticContext, id):
5252
def contacts(mctx: MauticContext, id, limit):
5353
"""List contacts in a segment."""
5454
try:
55-
data = mctx.client.get(f"/segments/{id}/contacts", params={"limit": limit})
56-
mctx.output(data)
55+
# Fetch segment to get its alias for search
56+
segment_data = mctx.client.get(f"/segments/{id}")
57+
segment = segment_data.get("list", {})
58+
alias = segment.get("alias", "")
59+
if not alias:
60+
mctx.output({"total": 0, "contacts": {}})
61+
return
62+
data = mctx.client.get("/contacts", params={
63+
"search": f"segment:{alias}",
64+
"limit": limit,
65+
})
66+
mctx.output_list(data, "contacts")
5767
except MauticApiError as e:
5868
mctx.error(e)
5969
raise SystemExit(1)

tests/test_segments.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,14 @@ def test_create_segment(self):
5252

5353
@respx.mock
5454
def test_segment_contacts(self):
55-
respx.get("https://mautic.test/api/segments/1/contacts").mock(
55+
# First call: fetch segment to get alias
56+
respx.get("https://mautic.test/api/segments/1").mock(
57+
return_value=httpx.Response(200, json={
58+
"list": {"id": 1, "name": "Newsletter", "alias": "newsletter"},
59+
})
60+
)
61+
# Second call: search contacts by segment alias
62+
respx.get("https://mautic.test/api/contacts").mock(
5663
return_value=httpx.Response(200, json={
5764
"total": "1",
5865
"contacts": {"42": {"id": 42}},

0 commit comments

Comments
 (0)