User API cleanup
This commit is contained in:
@@ -5,6 +5,9 @@ import requests
|
||||
from html2text import html2text
|
||||
from utils import activitypub_utils
|
||||
|
||||
from typing import Tuple
|
||||
from typing import List
|
||||
|
||||
|
||||
def resp2plaintext(resp):
|
||||
"""Convert the body of a requests reponse to plain text in order to make basic assertions."""
|
||||
@@ -17,107 +20,149 @@ class Instance(object):
|
||||
def __init__(self, name, host_url, docker_url=None):
|
||||
self.host_url = host_url
|
||||
self.docker_url = docker_url or host_url
|
||||
self.session = requests.Session()
|
||||
self._create_delay = 10
|
||||
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), f'fixtures/{name}/config/admin_api_key.key')) as f:
|
||||
with open(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), f'fixtures/{name}/config/admin_api_key.key')
|
||||
) as f:
|
||||
api_key = f.read()
|
||||
self._auth_headers = {'Authorization': f'Bearer {api_key}'}
|
||||
|
||||
def _do_req(self, url, headers):
|
||||
"""Used to parse collection."""
|
||||
url = url.replace(self.docker_url, self.host_url)
|
||||
resp = requests.get(url, headers=headers)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
def _parse_collection(self, payload=None, url=None):
|
||||
"""Parses a collection (go through all the pages)."""
|
||||
return activitypub_utils.parse_collection(url=url, payload=payload, do_req=self._do_req)
|
||||
|
||||
def ping(self):
|
||||
"""Ensures the homepage is reachable."""
|
||||
resp = self.session.get(f'{self.host_url}/')
|
||||
resp = requests.get(f'{self.host_url}/')
|
||||
resp.raise_for_status()
|
||||
assert resp.status_code == 200
|
||||
|
||||
def debug(self):
|
||||
resp = self.session.get(f'{self.host_url}/api/debug', headers={'Accept': 'application/json'})
|
||||
"""Returns the debug infos (number of items in the inbox/outbox."""
|
||||
resp = requests.get(
|
||||
f'{self.host_url}/api/debug',
|
||||
headers={**self._auth_headers, 'Accept': 'application/json'},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
return resp.json()
|
||||
|
||||
|
||||
def drop_db(self):
|
||||
resp = self.session.delete(f'{self.host_url}/api/debug', headers={'Accept': 'application/json'})
|
||||
"""Drops the MongoDB DB."""
|
||||
resp = requests.delete(
|
||||
f'{self.host_url}/api/debug',
|
||||
headers={**self._auth_headers, 'Accept': 'application/json'},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
return resp.json()
|
||||
|
||||
def login(self):
|
||||
resp = self.session.post(f'{self.host_url}/login', data={'pass': 'hello'})
|
||||
resp.raise_for_status()
|
||||
assert resp.status_code == 200
|
||||
|
||||
def block(self, actor_url) -> None:
|
||||
"""Blocks an actor."""
|
||||
# Instance1 follows instance2
|
||||
resp = self.session.get(f'{self.host_url}/api/block', params={'actor': actor_url})
|
||||
resp = requests.post(
|
||||
f'{self.host_url}/api/block',
|
||||
params={'actor': actor_url},
|
||||
headers=self._auth_headers,
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
# We need to wait for the Follow/Accept dance
|
||||
time.sleep(self._create_delay/2)
|
||||
return resp.headers.get('microblogpub-created-activity')
|
||||
return resp.json().get('activity')
|
||||
|
||||
def follow(self, instance: 'Instance') -> None:
|
||||
def follow(self, instance: 'Instance') -> str:
|
||||
"""Follows another instance."""
|
||||
# Instance1 follows instance2
|
||||
resp = self.session.get(f'{self.host_url}/api/follow', params={'actor': instance.docker_url})
|
||||
resp = requests.post(
|
||||
f'{self.host_url}/api/follow',
|
||||
json={'actor': instance.docker_url},
|
||||
headers=self._auth_headers,
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
# We need to wait for the Follow/Accept dance
|
||||
time.sleep(self._create_delay)
|
||||
return resp.headers.get('microblogpub-created-activity')
|
||||
return resp.json().get('activity')
|
||||
|
||||
def new_note(self, content, reply=None):
|
||||
def new_note(self, content, reply=None) -> str:
|
||||
"""Creates a new note."""
|
||||
params = {'content': content}
|
||||
if reply:
|
||||
params['reply'] = reply
|
||||
resp = self.session.get(f'{self.host_url}/api/new_note', params=params)
|
||||
assert resp.status_code == 201
|
||||
|
||||
time.sleep(self._create_delay)
|
||||
return resp.headers.get('microblogpub-created-activity')
|
||||
|
||||
def boost(self, activity_id):
|
||||
resp = self.session.get(f'{self.host_url}/api/boost', params={'id': activity_id})
|
||||
assert resp.status_code == 201
|
||||
|
||||
time.sleep(self._create_delay)
|
||||
return resp.headers.get('microblogpub-created-activity')
|
||||
|
||||
def like(self, activity_id):
|
||||
resp = self.session.get(f'{self.host_url}/api/like', params={'id': activity_id})
|
||||
assert resp.status_code == 201
|
||||
|
||||
time.sleep(self._create_delay)
|
||||
return resp.headers.get('microblogpub-created-activity')
|
||||
|
||||
def delete(self, oid: str) -> None:
|
||||
resp = requests.post(
|
||||
f'{self.host_url}/api/note/delete',
|
||||
json={'id': oid},
|
||||
headers=self._auth_headers,
|
||||
f'{self.host_url}/api/new_note',
|
||||
json=params,
|
||||
headers=self._auth_headers,
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
time.sleep(self._create_delay)
|
||||
return resp.json().get('activity')
|
||||
|
||||
def undo(self, oid: str) -> None:
|
||||
resp = self.session.get(f'{self.host_url}/api/undo', params={'id': oid})
|
||||
def boost(self, oid: str) -> str:
|
||||
"""Creates an Announce activity."""
|
||||
resp = requests.post(
|
||||
f'{self.host_url}/api/boost',
|
||||
json={'id': oid},
|
||||
headers=self._auth_headers,
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
time.sleep(self._create_delay)
|
||||
return resp.json().get('activity')
|
||||
|
||||
def like(self, oid: str) -> str:
|
||||
"""Creates a Like activity."""
|
||||
resp = requests.post(
|
||||
f'{self.host_url}/api/like',
|
||||
json={'id': oid},
|
||||
headers=self._auth_headers,
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
time.sleep(self._create_delay)
|
||||
return resp.json().get('activity')
|
||||
|
||||
def delete(self, oid: str) -> str:
|
||||
"""Creates a Delete activity."""
|
||||
resp = requests.post(
|
||||
f'{self.host_url}/api/note/delete',
|
||||
json={'id': oid},
|
||||
headers=self._auth_headers,
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
time.sleep(self._create_delay)
|
||||
return resp.json().get('activity')
|
||||
|
||||
def undo(self, oid: str) -> str:
|
||||
"""Creates a Undo activity."""
|
||||
resp = requests.post(
|
||||
f'{self.host_url}/api/undo',
|
||||
json={'id': oid},
|
||||
headers=self._auth_headers,
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
# We need to wait for the Follow/Accept dance
|
||||
time.sleep(self._create_delay)
|
||||
return resp.headers.get('microblogpub-created-activity')
|
||||
return resp.json().get('activity')
|
||||
|
||||
def followers(self):
|
||||
resp = self.session.get(f'{self.host_url}/followers', headers={'Accept': 'application/activity+json'})
|
||||
def followers(self) -> List[str]:
|
||||
"""Parses the followers collection."""
|
||||
resp = requests.get(
|
||||
f'{self.host_url}/followers',
|
||||
headers={'Accept': 'application/activity+json'},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
data = resp.json()
|
||||
@@ -125,7 +170,11 @@ class Instance(object):
|
||||
return self._parse_collection(payload=data)
|
||||
|
||||
def following(self):
|
||||
resp = self.session.get(f'{self.host_url}/following', headers={'Accept': 'application/activity+json'})
|
||||
"""Parses the following collection."""
|
||||
resp = requests.get(
|
||||
f'{self.host_url}/following',
|
||||
headers={'Accept': 'application/activity+json'},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
data = resp.json()
|
||||
@@ -133,38 +182,50 @@ class Instance(object):
|
||||
return self._parse_collection(payload=data)
|
||||
|
||||
def outbox(self):
|
||||
resp = self.session.get(f'{self.host_url}/following', headers={'Accept': 'application/activity+json'})
|
||||
"""Returns the instance outbox."""
|
||||
resp = requests.get(
|
||||
f'{self.host_url}/following',
|
||||
headers={'Accept': 'application/activity+json'},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
def outbox_get(self, aid):
|
||||
resp = self.session.get(aid.replace(self.docker_url, self.host_url), headers={'Accept': 'application/activity+json'})
|
||||
"""Fetches a specific item from the instance outbox."""
|
||||
resp = requests.get(
|
||||
aid.replace(self.docker_url, self.host_url),
|
||||
headers={'Accept': 'application/activity+json'},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
def stream_jsonfeed(self):
|
||||
resp = self.session.get(f'{self.host_url}/api/stream', headers={'Accept': 'application/json'})
|
||||
"""Returns the "stream"'s JSON feed."""
|
||||
resp = requests.get(
|
||||
f'{self.host_url}/api/stream',
|
||||
headers={**self._auth_headers, 'Accept': 'application/json'},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
||||
def _instances():
|
||||
def _instances() -> Tuple[Instance, Instance]:
|
||||
"""Initializes the client for the two test instances."""
|
||||
instance1 = Instance('instance1', 'http://localhost:5006', 'http://instance1_web_1:5005')
|
||||
instance1.ping()
|
||||
|
||||
instance2 = Instance('instance2', 'http://localhost:5007', 'http://instance2_web_1:5005')
|
||||
instance2.ping()
|
||||
|
||||
# Login
|
||||
instance1.login()
|
||||
# Return the DB
|
||||
instance1.drop_db()
|
||||
instance2.login()
|
||||
instance2.drop_db()
|
||||
|
||||
|
||||
return instance1, instance2
|
||||
|
||||
|
||||
def test_follow():
|
||||
def test_follow() -> None:
|
||||
"""instance1 follows instance2."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -181,6 +242,7 @@ def test_follow():
|
||||
|
||||
|
||||
def test_follow_unfollow():
|
||||
"""instance1 follows instance2, then unfollows it."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
follow_id = instance1.follow(instance2)
|
||||
@@ -210,6 +272,7 @@ def test_follow_unfollow():
|
||||
|
||||
|
||||
def test_post_content():
|
||||
"""Instances follow each other, and instance1 creates a note."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -230,6 +293,7 @@ def test_post_content():
|
||||
|
||||
|
||||
def test_block_and_post_content():
|
||||
"""Instances follow each other, instance2 blocks instance1, instance1 creates a new note."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -251,6 +315,7 @@ def test_block_and_post_content():
|
||||
|
||||
|
||||
def test_post_content_and_delete():
|
||||
"""Instances follow each other, instance1 creates a new note, then deletes it."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -280,6 +345,7 @@ def test_post_content_and_delete():
|
||||
|
||||
|
||||
def test_post_content_and_like():
|
||||
"""Instances follow each other, instance1 creates a new note, instance2 likes it."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -302,10 +368,13 @@ def test_post_content_and_like():
|
||||
note = instance1.outbox_get(f'{create_id}/activity')
|
||||
assert 'likes' in note
|
||||
assert note['likes']['totalItems'] == 1
|
||||
# assert note['likes']['items'][0]['id'] == like_id
|
||||
likes = instance1._parse_collection(url=note['likes']['first'])
|
||||
assert len(likes) == 1
|
||||
assert likes[0]['id'] == like_id
|
||||
|
||||
|
||||
def test_post_content_and_like_unlike():
|
||||
def test_post_content_and_like_unlike() -> None:
|
||||
"""Instances follow each other, instance1 creates a new note, instance2 likes it, then unlikes it."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -328,8 +397,9 @@ def test_post_content_and_like_unlike():
|
||||
note = instance1.outbox_get(f'{create_id}/activity')
|
||||
assert 'likes' in note
|
||||
assert note['likes']['totalItems'] == 1
|
||||
# FIXME(tsileo): parse the collection
|
||||
# assert note['likes']['items'][0]['id'] == like_id
|
||||
likes = instance1._parse_collection(url=note['likes']['first'])
|
||||
assert len(likes) == 1
|
||||
assert likes[0]['id'] == like_id
|
||||
|
||||
instance2.undo(like_id)
|
||||
|
||||
@@ -342,7 +412,8 @@ def test_post_content_and_like_unlike():
|
||||
assert note['likes']['totalItems'] == 0
|
||||
|
||||
|
||||
def test_post_content_and_boost():
|
||||
def test_post_content_and_boost() -> None:
|
||||
"""Instances follow each other, instance1 creates a new note, instance2 "boost" it."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -365,11 +436,13 @@ def test_post_content_and_boost():
|
||||
note = instance1.outbox_get(f'{create_id}/activity')
|
||||
assert 'shares' in note
|
||||
assert note['shares']['totalItems'] == 1
|
||||
# FIXME(tsileo): parse the collection
|
||||
# assert note['shares']['items'][0]['id'] == boost_id
|
||||
shares = instance1._parse_collection(url=note['shares']['first'])
|
||||
assert len(shares) == 1
|
||||
assert shares[0]['id'] == boost_id
|
||||
|
||||
|
||||
def test_post_content_and_boost_unboost():
|
||||
def test_post_content_and_boost_unboost() -> None:
|
||||
"""Instances follow each other, instance1 creates a new note, instance2 "boost" it, then "unboost" it."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -392,8 +465,9 @@ def test_post_content_and_boost_unboost():
|
||||
note = instance1.outbox_get(f'{create_id}/activity')
|
||||
assert 'shares' in note
|
||||
assert note['shares']['totalItems'] == 1
|
||||
# FIXME(tsileo): parse the collection
|
||||
# assert note['shares']['items'][0]['id'] == boost_id
|
||||
shares = instance1._parse_collection(url=note['shares']['first'])
|
||||
assert len(shares) == 1
|
||||
assert shares[0]['id'] == boost_id
|
||||
|
||||
instance2.undo(boost_id)
|
||||
|
||||
@@ -406,7 +480,8 @@ def test_post_content_and_boost_unboost():
|
||||
assert note['shares']['totalItems'] == 0
|
||||
|
||||
|
||||
def test_post_content_and_post_reply():
|
||||
def test_post_content_and_post_reply() -> None:
|
||||
"""Instances follow each other, instance1 creates a new note, instance2 replies to it."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -425,7 +500,10 @@ def test_post_content_and_post_reply():
|
||||
assert len(instance2_inbox_stream['items']) == 1
|
||||
assert instance2_inbox_stream['items'][0]['id'] == instance1_create_id
|
||||
|
||||
instance2_create_id = instance2.new_note(f'hey @instance1@{instance1.docker_url}', reply=f'{instance1_create_id}/activity')
|
||||
instance2_create_id = instance2.new_note(
|
||||
f'hey @instance1@{instance1.docker_url}',
|
||||
reply=f'{instance1_create_id}/activity',
|
||||
)
|
||||
instance2_debug = instance2.debug()
|
||||
assert instance2_debug['inbox'] == 3 # An Follow, Accept and Create activity should be there
|
||||
assert instance2_debug['outbox'] == 3 # We've sent a Accept and a Follow and a Create activity
|
||||
@@ -441,10 +519,13 @@ def test_post_content_and_post_reply():
|
||||
instance1_note = instance1.outbox_get(f'{instance1_create_id}/activity')
|
||||
assert 'replies' in instance1_note
|
||||
assert instance1_note['replies']['totalItems'] == 1
|
||||
# TODO(tsileo): inspect the `replies` collection
|
||||
replies = instance1._parse_collection(url=instance1_note['replies']['first'])
|
||||
assert len(replies) == 1
|
||||
assert replies[0]['id'] == f'{instance2_create_id}/activity'
|
||||
|
||||
|
||||
def test_post_content_and_post_reply_and_delete():
|
||||
def test_post_content_and_post_reply_and_delete() -> None:
|
||||
"""Instances follow each other, instance1 creates a new note, instance2 replies to it, then deletes its reply."""
|
||||
instance1, instance2 = _instances()
|
||||
# Instance1 follows instance2
|
||||
instance1.follow(instance2)
|
||||
@@ -463,7 +544,10 @@ def test_post_content_and_post_reply_and_delete():
|
||||
assert len(instance2_inbox_stream['items']) == 1
|
||||
assert instance2_inbox_stream['items'][0]['id'] == instance1_create_id
|
||||
|
||||
instance2_create_id = instance2.new_note(f'hey @instance1@{instance1.docker_url}', reply=f'{instance1_create_id}/activity')
|
||||
instance2_create_id = instance2.new_note(
|
||||
f'hey @instance1@{instance1.docker_url}',
|
||||
reply=f'{instance1_create_id}/activity',
|
||||
)
|
||||
instance2_debug = instance2.debug()
|
||||
assert instance2_debug['inbox'] == 3 # An Follow, Accept and Create activity should be there
|
||||
assert instance2_debug['outbox'] == 3 # We've sent a Accept and a Follow and a Create activity
|
||||
|
Reference in New Issue
Block a user