1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
"""
Editing a group's description.
A description could only be set when the group was created, so every group made
before anyone thought to write one stayed blank for good. What is deliberately
*not* editable is as much the point: name, visibility and join policy are the
terms members joined on.
"""
import base64
import hashlib
import pytest
def _auth_key(password: str, username: str) -> str:
salt = hashlib.sha256(f"meshbay:auth:v1:{username}".encode()).digest()
return base64.b64encode(
hashlib.pbkdf2_hmac("sha512", password.encode(), salt, 600_000, 32)).decode()
async def _user(client, username, password="a-long-enough-passphrase"):
await client.post("/v1/users/register", json={
"username": username, "email": f"{username}@example.com",
"auth_key": _auth_key(password, username)})
r = await client.post("/v1/users/login", json={
"username": username, "auth_key": _auth_key(password, username)})
return {"Authorization": f"Bearer {r.json()['access_token']}"}
async def _group(client, headers, name="described", **kw):
r = await client.post("/v1/groups", json={"name": name, **kw}, headers=headers)
return r.json()["group_id"]
@pytest.mark.asyncio
async def test_the_owner_can_write_a_description(client):
owner = await _user(client, "writer")
gid = await _group(client, owner)
r = await client.patch(f"/v1/groups/{gid}",
json={"description": "host grenoble"}, headers=owner)
assert r.status_code == 200, r.text
mine = await client.get("/v1/groups/mine", headers=owner)
group = next(g for g in mine.json()["groups"] if g["id"] == gid)
assert group["description"] == "host grenoble"
@pytest.mark.asyncio
async def test_a_member_cannot(client):
owner = await _user(client, "owner2")
member = await _user(client, "member2")
gid = await _group(client, owner, name="not-yours")
await client.post(f"/v1/groups/{gid}/members/member2", json={}, headers=owner)
r = await client.patch(f"/v1/groups/{gid}",
json={"description": "mine now"}, headers=member)
assert r.status_code == 403
@pytest.mark.asyncio
async def test_an_empty_description_clears_it(client):
owner = await _user(client, "clearer")
gid = await _group(client, owner, name="clearme", description="temporary")
r = await client.patch(f"/v1/groups/{gid}", json={"description": " "},
headers=owner)
assert r.status_code == 200
assert r.json()["description"] == ""
@pytest.mark.asyncio
async def test_the_terms_members_joined_on_are_not_editable(client):
"""
A private group that could quietly become public is not the group its
members agreed to be in. Changing that needs a decision about who gets told,
so the endpoint ignores it rather than half-implementing it.
"""
owner = await _user(client, "sneaky")
gid = await _group(client, owner, name="private-please", visibility="private")
await client.patch(f"/v1/groups/{gid}",
json={"description": "hi", "visibility": "public",
"join_policy": "open", "name": "renamed"},
headers=owner)
mine = await client.get("/v1/groups/mine", headers=owner)
group = next(g for g in mine.json()["groups"] if g["id"] == gid)
assert group["visibility"] == "private"
assert group["join_policy"] == "invite"
assert group["name"] == "private-please"
@pytest.mark.asyncio
async def test_a_long_description_is_truncated_not_refused(client):
owner = await _user(client, "verbose")
gid = await _group(client, owner, name="long")
r = await client.patch(f"/v1/groups/{gid}", json={"description": "x" * 900},
headers=owner)
assert r.status_code == 200
assert len(r.json()["description"]) == 512
|