Skip to content

Commit 9d3a48c

Browse files
committed
Fix Circles tests on CI: provision a peer user via fixture instead of relying on 'bob'
1 parent 8d2df0d commit 9d3a48c

1 file changed

Lines changed: 44 additions & 26 deletions

File tree

tests/integration/test_circles.py

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
"""Integration tests for Circles (Teams) tools against a real Nextcloud instance."""
22

3+
import contextlib
34
import json
5+
from collections.abc import AsyncGenerator
46
from typing import Any
57

68
import pytest
79
from mcp.server.fastmcp.exceptions import ToolError
810

9-
from nc_mcp_server.client import NextcloudError
11+
from nc_mcp_server.client import NextcloudClient, NextcloudError
12+
from nc_mcp_server.config import Config
1013

1114
from .conftest import McpTestHelper
1215

1316
pytestmark = pytest.mark.integration
1417

18+
CIRCLE_TEST_USER = "mcp-circle-test-user"
19+
20+
21+
@pytest.fixture
22+
async def circle_peer(nc_config: Config) -> AsyncGenerator[str]:
23+
"""Ensure a second user exists for membership tests. Yields the userid."""
24+
client = NextcloudClient(nc_config)
25+
with contextlib.suppress(Exception):
26+
await client.ocs_post(
27+
"cloud/users",
28+
data={"userid": CIRCLE_TEST_USER, "password": "mcp-Circle-Test-PWD-9X!"},
29+
)
30+
yield CIRCLE_TEST_USER
31+
with contextlib.suppress(Exception):
32+
await client.ocs_delete(f"cloud/users/{CIRCLE_TEST_USER}")
33+
await client.close()
34+
1535

1636
async def _make_circle(nc_mcp: McpTestHelper, name: str) -> dict[str, Any]:
1737
"""Create a circle and return its dict."""
@@ -105,12 +125,12 @@ async def test_owner_present_in_members(self, nc_mcp: McpTestHelper) -> None:
105125
assert admin_member["level"] == 9
106126

107127
@pytest.mark.asyncio
108-
async def test_add_and_list_member(self, nc_mcp: McpTestHelper) -> None:
128+
async def test_add_and_list_member(self, nc_mcp: McpTestHelper, circle_peer: str) -> None:
109129
circle = await _make_circle(nc_mcp, "mcp-test-circle-add-member")
110-
added = json.loads(await nc_mcp.call("add_circle_member", circle_id=circle["id"], user_id="bob"))
111-
assert added["userId"] == "bob"
130+
added = json.loads(await nc_mcp.call("add_circle_member", circle_id=circle["id"], user_id=circle_peer))
131+
assert added["userId"] == circle_peer
112132
members: list[dict[str, Any]] = json.loads(await nc_mcp.call("list_circle_members", circle_id=circle["id"]))
113-
assert any(m.get("userId") == "bob" for m in members)
133+
assert any(m.get("userId") == circle_peer for m in members)
114134

115135
@pytest.mark.asyncio
116136
async def test_add_member_rejects_bad_type(self, nc_mcp: McpTestHelper) -> None:
@@ -119,28 +139,28 @@ async def test_add_member_rejects_bad_type(self, nc_mcp: McpTestHelper) -> None:
119139
await nc_mcp.call(
120140
"add_circle_member",
121141
circle_id=circle["id"],
122-
user_id="bob",
142+
user_id="anyone",
123143
member_type="bogus",
124144
)
125145

126146
@pytest.mark.asyncio
127-
async def test_update_member_level(self, nc_mcp: McpTestHelper) -> None:
147+
async def test_update_member_level(self, nc_mcp: McpTestHelper, circle_peer: str) -> None:
128148
circle = await _make_circle(nc_mcp, "mcp-test-circle-promote")
129-
added = json.loads(await nc_mcp.call("add_circle_member", circle_id=circle["id"], user_id="bob"))
149+
added = json.loads(await nc_mcp.call("add_circle_member", circle_id=circle["id"], user_id=circle_peer))
130150
await nc_mcp.call(
131151
"update_circle_member_level",
132152
circle_id=circle["id"],
133153
member_id=added["id"],
134154
level="moderator",
135155
)
136156
members: list[dict[str, Any]] = json.loads(await nc_mcp.call("list_circle_members", circle_id=circle["id"]))
137-
bob = next(m for m in members if m.get("userId") == "bob")
138-
assert bob["level"] == 4
157+
peer = next(m for m in members if m.get("userId") == circle_peer)
158+
assert peer["level"] == 4
139159

140160
@pytest.mark.asyncio
141-
async def test_update_level_rejects_bad_value(self, nc_mcp: McpTestHelper) -> None:
161+
async def test_update_level_rejects_bad_value(self, nc_mcp: McpTestHelper, circle_peer: str) -> None:
142162
circle = await _make_circle(nc_mcp, "mcp-test-circle-bad-level")
143-
added = json.loads(await nc_mcp.call("add_circle_member", circle_id=circle["id"], user_id="bob"))
163+
added = json.loads(await nc_mcp.call("add_circle_member", circle_id=circle["id"], user_id=circle_peer))
144164
with pytest.raises((ToolError, ValueError), match=r"[Ii]nvalid level"):
145165
await nc_mcp.call(
146166
"update_circle_member_level",
@@ -150,9 +170,9 @@ async def test_update_level_rejects_bad_value(self, nc_mcp: McpTestHelper) -> No
150170
)
151171

152172
@pytest.mark.asyncio
153-
async def test_remove_member(self, nc_mcp: McpTestHelper) -> None:
173+
async def test_remove_member(self, nc_mcp: McpTestHelper, circle_peer: str) -> None:
154174
circle = await _make_circle(nc_mcp, "mcp-test-circle-kick")
155-
added = json.loads(await nc_mcp.call("add_circle_member", circle_id=circle["id"], user_id="bob"))
175+
added = json.loads(await nc_mcp.call("add_circle_member", circle_id=circle["id"], user_id=circle_peer))
156176
result = json.loads(
157177
await nc_mcp.call(
158178
"remove_circle_member",
@@ -162,34 +182,32 @@ async def test_remove_member(self, nc_mcp: McpTestHelper) -> None:
162182
)
163183
assert result == {"removed_member_id": added["id"]}
164184
members: list[dict[str, Any]] = json.loads(await nc_mcp.call("list_circle_members", circle_id=circle["id"]))
165-
assert not any(m.get("userId") == "bob" for m in members)
185+
assert not any(m.get("userId") == circle_peer for m in members)
166186

167187

168188
class TestJoinLeave:
169189
@pytest.mark.asyncio
170-
async def test_leave_as_non_owner(self, nc_mcp: McpTestHelper) -> None:
171-
"""Alice creates, admin is added, admin leaves — membership disappears."""
190+
async def test_leave_as_non_owner(self, nc_mcp: McpTestHelper, circle_peer: str) -> None:
191+
"""Admin adds peer then removes peer — membership disappears."""
172192
admin = nc_mcp
173193
circle = json.loads(await admin.call("create_circle", name="mcp-test-circle-leave"))
174-
# Make the circle joinable so owner can add bob then bob can leave
175-
# Admin adds bob
176-
bob_member = json.loads(await admin.call("add_circle_member", circle_id=circle["id"], user_id="bob"))
177-
# Admin removes bob (equivalent to bob leaving, same effect, without needing a second client)
194+
peer_member = json.loads(await admin.call("add_circle_member", circle_id=circle["id"], user_id=circle_peer))
195+
# Admin removes peer (equivalent to peer leaving, same effect)
178196
await admin.call(
179197
"remove_circle_member",
180198
circle_id=circle["id"],
181-
member_id=bob_member["id"],
199+
member_id=peer_member["id"],
182200
)
183201
members: list[dict[str, Any]] = json.loads(await admin.call("list_circle_members", circle_id=circle["id"]))
184-
assert not any(m.get("userId") == "bob" for m in members)
202+
assert not any(m.get("userId") == circle_peer for m in members)
185203

186204

187205
class TestSearch:
188206
@pytest.mark.asyncio
189-
async def test_search_finds_user(self, nc_mcp: McpTestHelper) -> None:
190-
"""Search by a common user id should return at least one hit."""
207+
async def test_search_returns_list(self, nc_mcp: McpTestHelper) -> None:
208+
"""Search should return a JSON list regardless of matches."""
191209
await _make_circle(nc_mcp, "mcp-test-circle-search")
192-
results = json.loads(await nc_mcp.call("search_circles", term="bob"))
210+
results = json.loads(await nc_mcp.call("search_circles", term="admin"))
193211
assert isinstance(results, list)
194212

195213

0 commit comments

Comments
 (0)