2023-08-24 03:34:32 +00:00
|
|
|
"""Unit tests for atproto.py."""
|
2023-09-12 18:49:57 +00:00
|
|
|
import base64
|
2023-08-24 03:34:32 +00:00
|
|
|
import copy
|
|
|
|
import logging
|
|
|
|
from unittest import skip
|
2023-11-14 20:30:14 +00:00
|
|
|
from unittest.mock import ANY, call, MagicMock, patch
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-10-05 22:47:04 +00:00
|
|
|
from arroba.datastore_storage import AtpBlock, AtpRemoteBlob, AtpRepo, DatastoreStorage
|
2023-09-12 18:49:57 +00:00
|
|
|
from arroba.did import encode_did_key
|
2023-09-01 19:07:21 +00:00
|
|
|
from arroba.repo import Repo
|
|
|
|
import arroba.util
|
2023-09-13 04:52:21 +00:00
|
|
|
import dns.resolver
|
|
|
|
from dns.resolver import NXDOMAIN
|
2023-08-24 03:34:32 +00:00
|
|
|
from flask import g
|
2023-09-27 21:58:33 +00:00
|
|
|
from google.cloud.tasks_v2.types import Task
|
2023-09-01 19:07:21 +00:00
|
|
|
from granary.tests.test_bluesky import (
|
|
|
|
ACTOR_AS,
|
2023-10-03 23:52:21 +00:00
|
|
|
ACTOR_PROFILE_BSKY,
|
2023-09-01 19:07:21 +00:00
|
|
|
POST_AS,
|
|
|
|
POST_BSKY,
|
|
|
|
)
|
2023-10-05 22:47:04 +00:00
|
|
|
from multiformats import CID
|
2023-09-06 03:10:11 +00:00
|
|
|
from oauth_dropins.webutil.appengine_config import tasks_client
|
2023-08-31 03:59:37 +00:00
|
|
|
from oauth_dropins.webutil.testutil import requests_response
|
2023-10-03 23:52:21 +00:00
|
|
|
from oauth_dropins.webutil.util import json_dumps, json_loads, trim_nulls
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-09-06 03:10:11 +00:00
|
|
|
import atproto
|
2023-08-24 03:34:32 +00:00
|
|
|
from atproto import ATProto
|
2023-09-06 03:10:11 +00:00
|
|
|
import common
|
2023-10-18 04:50:19 +00:00
|
|
|
import hub
|
2023-09-14 17:20:04 +00:00
|
|
|
from models import Object, Target
|
2023-08-24 03:34:32 +00:00
|
|
|
import protocol
|
2023-11-13 21:30:15 +00:00
|
|
|
from .testutil import ATPROTO_KEY, Fake, TestCase
|
2023-09-26 20:45:54 +00:00
|
|
|
from . import test_activitypub
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-08-31 17:48:28 +00:00
|
|
|
DID_DOC = {
|
2023-11-02 02:29:39 +00:00
|
|
|
'id': 'did:plc:user',
|
2023-09-12 18:49:57 +00:00
|
|
|
'alsoKnownAs': ['at://han.dull'],
|
|
|
|
'verificationMethod': [{
|
2023-11-02 02:29:39 +00:00
|
|
|
'id': 'did:plc:user#atproto',
|
2023-09-12 18:49:57 +00:00
|
|
|
'type': 'Multikey',
|
2023-11-02 02:29:39 +00:00
|
|
|
'controller': 'did:plc:user',
|
2023-09-12 18:49:57 +00:00
|
|
|
'publicKeyMultibase': 'did:key:xyz',
|
|
|
|
}],
|
|
|
|
'service': [{
|
|
|
|
'id': '#atproto_pds',
|
|
|
|
'type': 'AtprotoPersonalDataServer',
|
|
|
|
'serviceEndpoint': 'https://some.pds',
|
|
|
|
}],
|
2023-08-31 17:48:28 +00:00
|
|
|
}
|
2023-10-05 22:47:04 +00:00
|
|
|
BLOB_CID = CID.decode('bafkreicqpqncshdd27sgztqgzocd3zhhqnnsv6slvzhs5uz6f57cq6lmtq')
|
2023-08-31 17:48:28 +00:00
|
|
|
|
2023-09-14 16:42:11 +00:00
|
|
|
|
2023-08-24 03:34:32 +00:00
|
|
|
class ATProtoTest(TestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
2023-09-09 04:08:12 +00:00
|
|
|
self.storage = DatastoreStorage()
|
2023-10-31 19:49:15 +00:00
|
|
|
common.RUN_TASKS_INLINE = False
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-11-03 00:41:31 +00:00
|
|
|
def make_user_and_repo(self):
|
2023-11-16 03:08:06 +00:00
|
|
|
user = self.make_user(id='fake:user', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:user', protocol='atproto')])
|
2023-11-03 00:41:31 +00:00
|
|
|
|
|
|
|
did_doc = copy.deepcopy(DID_DOC)
|
|
|
|
did_doc['service'][0]['serviceEndpoint'] = 'https://atproto.brid.gy/'
|
|
|
|
self.store_object(id='did:plc:user', raw=did_doc)
|
2023-11-13 21:30:15 +00:00
|
|
|
Repo.create(self.storage, 'did:plc:user', signing_key=ATPROTO_KEY)
|
2023-11-03 00:41:31 +00:00
|
|
|
|
|
|
|
return user
|
|
|
|
|
2023-09-26 21:46:08 +00:00
|
|
|
@patch('requests.get', return_value=requests_response(DID_DOC))
|
|
|
|
def test_put_validates_id(self, mock_get):
|
2023-08-24 03:44:42 +00:00
|
|
|
for bad in (
|
|
|
|
'',
|
|
|
|
'not a did',
|
|
|
|
'https://not.a/did',
|
|
|
|
'at://not.a/did',
|
|
|
|
'did:other:foo',
|
|
|
|
'did:web:foo', # not a domain
|
|
|
|
'did:web:fed.brid.gy',
|
|
|
|
'did:web:foo.ap.brid.gy',
|
|
|
|
'did:plc:' # blank
|
|
|
|
):
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
ATProto(id=bad).put()
|
|
|
|
|
|
|
|
ATProto(id='did:web:foo.com').put()
|
2023-11-02 02:29:39 +00:00
|
|
|
ATProto(id='did:plc:user').put()
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-09-26 21:46:08 +00:00
|
|
|
def test_handle(self):
|
2023-11-02 02:29:39 +00:00
|
|
|
self.store_object(id='did:plc:user', raw=DID_DOC)
|
|
|
|
self.assertEqual('han.dull', ATProto(id='did:plc:user').handle)
|
2023-09-26 21:46:08 +00:00
|
|
|
|
2023-09-29 17:48:59 +00:00
|
|
|
@patch('requests.get', return_value=requests_response(DID_DOC))
|
|
|
|
def test_get_or_create(self, _):
|
2023-11-02 02:29:39 +00:00
|
|
|
user = self.make_user('did:plc:user', cls=ATProto)
|
2023-09-29 17:48:59 +00:00
|
|
|
self.assertEqual('han.dull', user.key.get().handle)
|
|
|
|
|
2023-08-24 03:34:32 +00:00
|
|
|
def test_owns_id(self):
|
|
|
|
self.assertFalse(ATProto.owns_id('http://foo'))
|
|
|
|
self.assertFalse(ATProto.owns_id('https://bar.baz/biff'))
|
|
|
|
self.assertFalse(ATProto.owns_id('e45fab982'))
|
|
|
|
|
2023-11-02 02:29:39 +00:00
|
|
|
self.assertTrue(ATProto.owns_id('at://did:plc:user/bar/123'))
|
|
|
|
self.assertTrue(ATProto.owns_id('did:plc:user'))
|
2023-08-24 03:34:32 +00:00
|
|
|
self.assertTrue(ATProto.owns_id('did:web:bar.com'))
|
2023-09-13 04:52:21 +00:00
|
|
|
self.assertTrue(ATProto.owns_id(
|
|
|
|
'https://bsky.app/profile/snarfed.org/post/3k62u4ht77f2z'))
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-09-22 19:14:50 +00:00
|
|
|
def test_owns_handle(self):
|
|
|
|
self.assertIsNone(ATProto.owns_handle('foo.com'))
|
|
|
|
self.assertIsNone(ATProto.owns_handle('foo.bar.com'))
|
|
|
|
|
|
|
|
self.assertFalse(ATProto.owns_handle('foo'))
|
|
|
|
self.assertFalse(ATProto.owns_handle('@foo'))
|
|
|
|
self.assertFalse(ATProto.owns_handle('@foo.com'))
|
|
|
|
self.assertFalse(ATProto.owns_handle('@foo@bar.com'))
|
|
|
|
self.assertFalse(ATProto.owns_handle('foo@bar.com'))
|
|
|
|
|
2023-09-26 21:46:08 +00:00
|
|
|
def test_handle_to_id(self):
|
2023-11-02 02:29:39 +00:00
|
|
|
self.store_object(id='did:plc:user', raw=DID_DOC)
|
|
|
|
self.make_user('did:plc:user', cls=ATProto)
|
|
|
|
self.assertEqual('did:plc:user', ATProto.handle_to_id('han.dull'))
|
2023-09-22 20:11:15 +00:00
|
|
|
|
|
|
|
@patch('dns.resolver.resolve', side_effect=dns.resolver.NXDOMAIN())
|
2023-09-27 21:58:33 +00:00
|
|
|
# resolving handle, HTTPS method, not found
|
2023-09-22 20:11:15 +00:00
|
|
|
@patch('requests.get', return_value=requests_response('', status=404))
|
|
|
|
def test_handle_to_id_not_found(self, *_):
|
|
|
|
self.assertIsNone(ATProto.handle_to_id('han.dull'))
|
|
|
|
|
2023-10-18 19:01:17 +00:00
|
|
|
def test_pds_for_did_no_doc(self):
|
2023-11-02 02:29:39 +00:00
|
|
|
self.assertIsNone(ATProto.pds_for(Object(id='did:plc:user')))
|
2023-08-31 17:48:28 +00:00
|
|
|
|
2023-10-18 19:01:17 +00:00
|
|
|
def test_pds_for_stored_did(self):
|
2023-11-02 02:29:39 +00:00
|
|
|
obj = self.store_object(id='did:plc:user', raw=DID_DOC)
|
2023-10-18 19:01:17 +00:00
|
|
|
got = ATProto.pds_for(obj)
|
|
|
|
self.assertEqual('https://some.pds', got)
|
|
|
|
|
|
|
|
def test_pds_for_record_stored_did(self):
|
2023-11-02 02:29:39 +00:00
|
|
|
self.store_object(id='did:plc:user', raw=DID_DOC)
|
|
|
|
got = ATProto.pds_for(Object(id='at://did:plc:user/co.ll/123'))
|
2023-08-31 17:48:28 +00:00
|
|
|
self.assertEqual('https://some.pds', got)
|
|
|
|
|
2023-09-01 20:59:28 +00:00
|
|
|
@patch('requests.get', return_value=requests_response(DID_DOC))
|
2023-10-18 19:01:17 +00:00
|
|
|
def test_pds_for_fetch_did(self, mock_get):
|
2023-11-02 02:29:39 +00:00
|
|
|
got = ATProto.pds_for(Object(id='at://did:plc:user/co.ll/123'))
|
2023-09-01 20:59:28 +00:00
|
|
|
self.assertEqual('https://some.pds', got)
|
|
|
|
|
2023-10-18 19:01:17 +00:00
|
|
|
def test_pds_for_user_with_stored_did(self):
|
2023-11-02 02:29:39 +00:00
|
|
|
self.store_object(id='did:plc:user', raw=DID_DOC)
|
2023-11-16 03:08:06 +00:00
|
|
|
self.make_user('fake:user', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:user', protocol='atproto')])
|
2023-10-18 19:01:17 +00:00
|
|
|
got = ATProto.pds_for(Object(id='fake:post', our_as1={
|
2023-09-01 20:59:28 +00:00
|
|
|
**POST_AS,
|
|
|
|
'actor': 'fake:user',
|
|
|
|
}))
|
|
|
|
self.assertEqual('https://some.pds', got)
|
|
|
|
|
2023-10-18 19:01:17 +00:00
|
|
|
def test_pds_for_user_no_stored_did(self):
|
2023-09-13 04:52:21 +00:00
|
|
|
self.make_user('fake:user', cls=Fake)
|
2023-10-18 19:01:17 +00:00
|
|
|
self.assertIsNone(ATProto.pds_for(Object(id='fake:post', our_as1={
|
2023-09-01 20:59:28 +00:00
|
|
|
**POST_AS,
|
|
|
|
'actor': 'fake:user',
|
2023-10-18 19:01:17 +00:00
|
|
|
})))
|
2023-09-01 20:59:28 +00:00
|
|
|
|
2023-10-18 19:01:17 +00:00
|
|
|
def test_pds_for_bsky_app_url_did_stored(self):
|
2023-11-02 02:29:39 +00:00
|
|
|
self.store_object(id='did:plc:user', raw=DID_DOC)
|
2023-11-16 03:08:06 +00:00
|
|
|
self.make_user('fake:user', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:user', protocol='atproto')])
|
2023-09-13 04:52:21 +00:00
|
|
|
|
2023-10-18 19:01:17 +00:00
|
|
|
got = ATProto.pds_for(Object(
|
2023-11-02 02:29:39 +00:00
|
|
|
id='https://bsky.app/profile/did:plc:user/post/123'))
|
2023-09-13 04:52:21 +00:00
|
|
|
self.assertEqual('https://some.pds', got)
|
|
|
|
|
|
|
|
@patch('dns.resolver.resolve', side_effect=dns.resolver.NXDOMAIN())
|
|
|
|
@patch('requests.get', side_effect=[
|
|
|
|
# resolving handle, HTTPS method
|
2023-11-02 02:29:39 +00:00
|
|
|
requests_response('did:plc:user', content_type='text/plain'),
|
2023-09-13 04:52:21 +00:00
|
|
|
# fetching DID doc
|
|
|
|
requests_response(DID_DOC),
|
|
|
|
])
|
2023-10-18 19:01:17 +00:00
|
|
|
def test_pds_for_bsky_app_url_resolve_handle(self, mock_get, _):
|
|
|
|
got = ATProto.pds_for(Object(
|
2023-09-13 04:52:21 +00:00
|
|
|
id='https://bsky.app/profile/baz.com/post/123'))
|
|
|
|
self.assertEqual('https://some.pds', got)
|
|
|
|
|
|
|
|
mock_get.assert_has_calls((
|
|
|
|
self.req('https://baz.com/.well-known/atproto-did'),
|
2023-11-02 02:29:39 +00:00
|
|
|
self.req('https://plc.local/did:plc:user'),
|
2023-09-13 04:52:21 +00:00
|
|
|
))
|
|
|
|
|
2023-10-18 19:01:17 +00:00
|
|
|
def test_target_for_user_no_stored_did(self):
|
2023-10-20 05:17:52 +00:00
|
|
|
self.assertEqual('https://atproto.brid.gy/', ATProto.target_for(
|
2023-10-18 19:01:17 +00:00
|
|
|
Object(id='at://foo')))
|
|
|
|
self.assertIsNone(ATProto.target_for(Object(id='fake:post')))
|
|
|
|
|
2023-08-31 03:59:37 +00:00
|
|
|
@patch('requests.get', return_value=requests_response({'foo': 'bar'}))
|
|
|
|
def test_fetch_did_plc(self, mock_get):
|
|
|
|
obj = Object(id='did:plc:123')
|
2023-08-31 17:48:28 +00:00
|
|
|
self.assertTrue(ATProto.fetch(obj))
|
2023-08-31 03:59:37 +00:00
|
|
|
self.assertEqual({'foo': 'bar'}, obj.raw)
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-08-31 03:59:37 +00:00
|
|
|
mock_get.assert_has_calls((
|
|
|
|
self.req('https://plc.local/did:plc:123'),
|
|
|
|
))
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-08-31 03:59:37 +00:00
|
|
|
@patch('requests.get', return_value=requests_response({'foo': 'bar'}))
|
|
|
|
def test_fetch_did_web(self, mock_get):
|
|
|
|
obj = Object(id='did:web:user.com')
|
2023-08-31 17:48:28 +00:00
|
|
|
self.assertTrue(ATProto.fetch(obj))
|
2023-08-31 03:59:37 +00:00
|
|
|
self.assertEqual({'foo': 'bar'}, obj.raw)
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-08-31 03:59:37 +00:00
|
|
|
mock_get.assert_has_calls((
|
|
|
|
self.req('https://user.com/.well-known/did.json'),
|
|
|
|
))
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-08-31 17:48:28 +00:00
|
|
|
@patch('requests.get', return_value=requests_response('not json'))
|
|
|
|
def test_fetch_did_plc_not_json(self, mock_get):
|
|
|
|
obj = Object(id='did:web:user.com')
|
|
|
|
self.assertFalse(ATProto.fetch(obj))
|
|
|
|
self.assertIsNone(obj.raw)
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-09-23 20:52:49 +00:00
|
|
|
@patch('requests.get', return_value=requests_response({
|
|
|
|
'uri': 'at://did:plc:abc/app.bsky.feed.post/123',
|
|
|
|
'cid': 'bafy...',
|
|
|
|
'value': {'foo': 'bar'},
|
|
|
|
}))
|
2023-08-31 17:48:28 +00:00
|
|
|
def test_fetch_at_uri_record(self, mock_get):
|
|
|
|
self.store_object(id='did:plc:abc', raw=DID_DOC)
|
|
|
|
obj = Object(id='at://did:plc:abc/app.bsky.feed.post/123')
|
|
|
|
self.assertTrue(ATProto.fetch(obj))
|
2023-11-15 03:24:37 +00:00
|
|
|
self.assertEqual({
|
|
|
|
'foo': 'bar',
|
|
|
|
'cid': 'bafy...',
|
|
|
|
}, obj.bsky)
|
2023-08-31 17:48:28 +00:00
|
|
|
# eg https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=did:plc:s2koow7r6t7tozgd4slc3dsg&collection=app.bsky.feed.post&rkey=3jqcpv7bv2c2q
|
2023-09-09 14:50:53 +00:00
|
|
|
mock_get.assert_called_once_with(
|
2023-08-31 17:48:28 +00:00
|
|
|
'https://some.pds/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Aabc&collection=app.bsky.feed.post&rkey=123',
|
|
|
|
json=None,
|
|
|
|
headers={
|
|
|
|
'Content-Type': 'application/json',
|
2023-09-06 03:10:11 +00:00
|
|
|
'User-Agent': common.USER_AGENT,
|
2023-08-31 17:48:28 +00:00
|
|
|
},
|
|
|
|
)
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-11-03 00:41:31 +00:00
|
|
|
def test_convert_bsky_pass_through(self):
|
|
|
|
self.assertEqual({
|
|
|
|
'foo': 'bar',
|
|
|
|
}, ATProto.convert(Object(bsky={
|
|
|
|
'foo': 'bar',
|
|
|
|
})))
|
|
|
|
|
2023-11-15 03:24:37 +00:00
|
|
|
def test_convert_populate_cid(self):
|
|
|
|
self.store_object(id='did:plc:bob', raw={
|
|
|
|
**DID_DOC,
|
|
|
|
'id': 'did:plc:bob',
|
|
|
|
})
|
|
|
|
self.store_object(id='at://did:plc:bob/app.bsky.feed.post/tid', bsky={
|
|
|
|
'$type': 'app.bsky.feed.post',
|
|
|
|
'cid': 'my sidd',
|
|
|
|
})
|
|
|
|
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.feed.like',
|
|
|
|
'subject': {
|
|
|
|
'uri': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
'cid': 'my sidd',
|
|
|
|
},
|
|
|
|
'createdAt': '2022-01-02T03:04:05+00:00',
|
|
|
|
}, ATProto.convert(Object(our_as1={
|
|
|
|
'objectType': 'activity',
|
|
|
|
'verb': 'like',
|
|
|
|
'object': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
})))
|
|
|
|
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.feed.repost',
|
|
|
|
'subject': {
|
|
|
|
'uri': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
'cid': 'my sidd',
|
|
|
|
},
|
|
|
|
'createdAt': '2022-01-02T03:04:05+00:00',
|
|
|
|
}, ATProto.convert(Object(our_as1={
|
|
|
|
'objectType': 'activity',
|
|
|
|
'verb': 'share',
|
|
|
|
'object': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
})))
|
|
|
|
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.feed.post',
|
|
|
|
'text': 'foo',
|
|
|
|
'createdAt': '2022-01-02T03:04:05+00:00',
|
|
|
|
'reply': {
|
|
|
|
'$type': 'app.bsky.feed.post#replyRef',
|
|
|
|
'root': {
|
|
|
|
'$type': 'com.atproto.repo.strongRef',
|
|
|
|
'uri': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
'cid': 'my sidd',
|
|
|
|
},
|
|
|
|
'parent': {
|
|
|
|
'$type': 'com.atproto.repo.strongRef',
|
|
|
|
'uri': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
'cid': 'my sidd',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, ATProto.convert(Object(our_as1={
|
|
|
|
'objectType': 'comment',
|
|
|
|
'content': 'foo',
|
|
|
|
'inReplyTo': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
})))
|
|
|
|
|
2023-11-03 00:41:31 +00:00
|
|
|
def test_convert_blobs_false(self):
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.actor.profile',
|
|
|
|
'displayName': 'Alice',
|
|
|
|
}, ATProto.convert(Object(our_as1={
|
|
|
|
'objectType': 'person',
|
|
|
|
'id': 'did:web:alice.com',
|
|
|
|
'displayName': 'Alice',
|
|
|
|
'image': [{'url': 'http://my/pic'}],
|
|
|
|
})))
|
|
|
|
|
|
|
|
@patch('requests.get', return_value=requests_response(
|
|
|
|
'blob contents', content_type='image/png'))
|
|
|
|
def test_convert_fetch_blobs_true(self, mock_get):
|
|
|
|
cid = CID.decode('bafkreicqpqncshdd27sgztqgzocd3zhhqnnsv6slvzhs5uz6f57cq6lmtq')
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.actor.profile',
|
|
|
|
'displayName': 'Alice',
|
|
|
|
'avatar': {
|
|
|
|
'$type': 'blob',
|
|
|
|
'ref': cid,
|
|
|
|
'mimeType': 'image/png',
|
|
|
|
'size': 13,
|
|
|
|
},
|
|
|
|
}, ATProto.convert(Object(our_as1={
|
|
|
|
'objectType': 'person',
|
|
|
|
'id': 'did:web:alice.com',
|
|
|
|
'displayName': 'Alice',
|
|
|
|
'image': [{'url': 'http://my/pic'}],
|
|
|
|
}), fetch_blobs=True))
|
|
|
|
|
|
|
|
mock_get.assert_has_calls([self.req('http://my/pic')])
|
|
|
|
|
|
|
|
def test_convert_fetch_blobs_true_existing_atp_remote_blob(self):
|
|
|
|
cid = 'bafkreicqpqncshdd27sgztqgzocd3zhhqnnsv6slvzhs5uz6f57cq6lmtq'
|
|
|
|
AtpRemoteBlob(id='http://my/pic', cid=cid, size=8).put()
|
|
|
|
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.actor.profile',
|
|
|
|
'displayName': 'Alice',
|
|
|
|
'avatar': {
|
|
|
|
'$type': 'blob',
|
|
|
|
'ref': CID.decode(cid),
|
|
|
|
'mimeType': 'application/octet-stream',
|
|
|
|
'size': 8,
|
|
|
|
},
|
|
|
|
}, ATProto.convert(Object(our_as1={
|
|
|
|
'objectType': 'person',
|
|
|
|
'id': 'did:web:alice.com',
|
|
|
|
'displayName': 'Alice',
|
|
|
|
'image': [{'url': 'http://my/pic'}],
|
|
|
|
}), fetch_blobs=True))
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-09-26 21:46:08 +00:00
|
|
|
@patch('requests.get', return_value=requests_response('', status=404))
|
|
|
|
def test_web_url(self, mock_get):
|
2023-11-02 02:29:39 +00:00
|
|
|
user = self.make_user('did:plc:user', cls=ATProto)
|
|
|
|
self.assertEqual('https://bsky.app/profile/did:plc:user', user.web_url())
|
2023-08-31 18:50:36 +00:00
|
|
|
|
2023-11-02 02:29:39 +00:00
|
|
|
self.store_object(id='did:plc:user', raw=DID_DOC)
|
2023-08-31 18:50:36 +00:00
|
|
|
self.assertEqual('https://bsky.app/profile/han.dull', user.web_url())
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-08-31 17:58:24 +00:00
|
|
|
@patch('requests.get', return_value=requests_response('', status=404))
|
2023-09-25 19:33:24 +00:00
|
|
|
def test_handle_or_id(self, mock_get):
|
2023-11-02 02:29:39 +00:00
|
|
|
user = self.make_user('did:plc:user', cls=ATProto)
|
2023-09-25 22:08:14 +00:00
|
|
|
self.assertIsNone(user.handle)
|
2023-11-02 02:29:39 +00:00
|
|
|
self.assertEqual('did:plc:user', user.handle_or_id())
|
2023-08-24 03:34:32 +00:00
|
|
|
|
2023-11-02 02:29:39 +00:00
|
|
|
self.store_object(id='did:plc:user', raw=DID_DOC)
|
2023-09-25 22:08:14 +00:00
|
|
|
self.assertEqual('han.dull', user.handle)
|
2023-09-25 19:33:24 +00:00
|
|
|
self.assertEqual('han.dull', user.handle_or_id())
|
2023-08-31 18:19:57 +00:00
|
|
|
|
2023-09-26 21:46:08 +00:00
|
|
|
@patch('requests.get', return_value=requests_response('', status=404))
|
|
|
|
def test_ap_address(self, mock_get):
|
2023-11-02 02:29:39 +00:00
|
|
|
user = self.make_user('did:plc:user', cls=ATProto)
|
|
|
|
self.assertEqual('@did:plc:user@atproto.brid.gy', user.ap_address())
|
2023-08-31 18:19:57 +00:00
|
|
|
|
2023-11-02 02:29:39 +00:00
|
|
|
self.store_object(id='did:plc:user', raw=DID_DOC)
|
2023-08-31 18:19:57 +00:00
|
|
|
self.assertEqual('@han.dull@atproto.brid.gy', user.ap_address())
|
2023-09-01 19:07:21 +00:00
|
|
|
|
2023-09-26 21:46:08 +00:00
|
|
|
@patch('requests.get', return_value=requests_response(DID_DOC))
|
|
|
|
def test_profile_id(self, mock_get):
|
2023-11-02 02:29:39 +00:00
|
|
|
self.assertEqual('at://did:plc:user/app.bsky.actor.profile/self',
|
|
|
|
self.make_user('did:plc:user', cls=ATProto).profile_id())
|
2023-09-22 22:40:22 +00:00
|
|
|
|
2023-10-05 22:55:31 +00:00
|
|
|
@patch('atproto.DEBUG', new=False)
|
2023-10-04 20:54:20 +00:00
|
|
|
@patch('google.cloud.dns.client.ManagedZone', autospec=True)
|
2023-10-04 19:44:14 +00:00
|
|
|
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
|
|
|
@patch('requests.post',
|
|
|
|
return_value=requests_response('OK')) # create DID on PLC
|
2023-10-04 20:54:20 +00:00
|
|
|
def test_create_for(self, mock_post, mock_create_task, mock_zone):
|
|
|
|
mock_zone.return_value = zone = MagicMock()
|
|
|
|
zone.resource_record_set = MagicMock()
|
2023-10-04 19:44:14 +00:00
|
|
|
|
2023-10-04 20:54:20 +00:00
|
|
|
Fake.fetchable = {'fake:user': ACTOR_AS}
|
2023-10-04 19:44:14 +00:00
|
|
|
user = Fake(id='fake:user')
|
2023-10-05 22:47:04 +00:00
|
|
|
AtpRemoteBlob(id='https://alice.com/alice.jpg',
|
|
|
|
cid=BLOB_CID.encode('base32'), size=8).put()
|
2023-10-04 20:54:20 +00:00
|
|
|
|
2023-10-04 19:44:14 +00:00
|
|
|
ATProto.create_for(user)
|
|
|
|
|
|
|
|
# check user, repo
|
2023-11-16 03:08:06 +00:00
|
|
|
did = user.key.get().get_copy(ATProto)
|
|
|
|
self.assertEqual([Target(uri=did, protocol='atproto')], user.copies)
|
|
|
|
repo = arroba.server.storage.load_repo(did)
|
2023-10-04 19:44:14 +00:00
|
|
|
|
2023-10-04 20:54:20 +00:00
|
|
|
# check DNS record
|
|
|
|
zone.resource_record_set.assert_called_with(
|
|
|
|
name='_atproto.fake:handle:user.fa.brid.gy.', record_type='TXT',
|
2023-11-16 03:08:06 +00:00
|
|
|
ttl=atproto.DNS_TTL, rrdatas=[f'"did={did}"'])
|
2023-10-04 20:54:20 +00:00
|
|
|
|
2023-10-04 19:44:14 +00:00
|
|
|
# check profile record
|
|
|
|
profile = repo.get_record('app.bsky.actor.profile', 'self')
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.actor.profile',
|
|
|
|
'displayName': 'Alice',
|
|
|
|
'description': 'hi there',
|
2023-10-05 22:47:04 +00:00
|
|
|
'avatar': {
|
|
|
|
'$type': 'blob',
|
|
|
|
'mimeType': 'application/octet-stream',
|
|
|
|
'ref': BLOB_CID,
|
|
|
|
'size': 8,
|
|
|
|
},
|
2023-10-04 19:44:14 +00:00
|
|
|
}, profile)
|
|
|
|
|
2023-11-16 03:08:06 +00:00
|
|
|
uri = arroba.util.at_uri(did, 'app.bsky.actor.profile', 'self')
|
2023-10-04 19:44:14 +00:00
|
|
|
self.assertEqual([Target(uri=uri, protocol='atproto')],
|
|
|
|
Object.get_by_id(id='fake:user').copies)
|
|
|
|
|
|
|
|
mock_create_task.assert_called()
|
|
|
|
|
2023-10-04 20:54:20 +00:00
|
|
|
@patch('google.cloud.dns.client.ManagedZone', autospec=True)
|
2023-09-06 03:10:11 +00:00
|
|
|
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
2023-09-01 21:18:50 +00:00
|
|
|
@patch('requests.post',
|
|
|
|
return_value=requests_response('OK')) # create DID on PLC
|
2023-10-04 20:54:20 +00:00
|
|
|
def test_send_new_repo(self, mock_post, mock_create_task, _):
|
2023-09-01 19:07:21 +00:00
|
|
|
user = self.make_user(id='fake:user', cls=Fake)
|
|
|
|
obj = self.store_object(id='fake:post', source_protocol='fake', our_as1={
|
|
|
|
**POST_AS,
|
|
|
|
'actor': 'fake:user',
|
|
|
|
})
|
2023-10-05 22:47:04 +00:00
|
|
|
|
2023-10-20 05:17:52 +00:00
|
|
|
self.assertTrue(ATProto.send(obj, 'https://atproto.brid.gy/'))
|
2023-09-01 19:07:21 +00:00
|
|
|
|
|
|
|
# check DID doc
|
|
|
|
user = user.key.get()
|
2023-11-16 03:08:06 +00:00
|
|
|
did = user.get_copy(ATProto)
|
|
|
|
assert did
|
|
|
|
self.assertEqual([Target(uri=did, protocol='atproto')], user.copies)
|
|
|
|
did_obj = ATProto.load(did)
|
2023-10-20 05:17:52 +00:00
|
|
|
self.assertEqual('https://atproto.brid.gy/',
|
2023-09-12 18:49:57 +00:00
|
|
|
did_obj.raw['service'][0]['serviceEndpoint'])
|
2023-09-01 19:07:21 +00:00
|
|
|
|
|
|
|
# check repo, record
|
2023-11-16 03:08:06 +00:00
|
|
|
repo = self.storage.load_repo(did)
|
2023-11-11 18:38:48 +00:00
|
|
|
last_tid = arroba.util.int_to_tid(arroba.util._tid_ts_last)
|
|
|
|
record = repo.get_record('app.bsky.feed.post', last_tid)
|
2023-09-01 19:07:21 +00:00
|
|
|
self.assertEqual(POST_BSKY, record)
|
|
|
|
|
2023-11-16 03:08:06 +00:00
|
|
|
at_uri = f'at://{did}/app.bsky.feed.post/{last_tid}'
|
2023-09-14 17:20:04 +00:00
|
|
|
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
|
|
|
|
Object.get_by_id(id='fake:post').copies)
|
|
|
|
|
2023-09-12 18:49:57 +00:00
|
|
|
# check PLC directory call to create did:plc
|
2023-11-16 03:08:06 +00:00
|
|
|
self.assertEqual((f'https://plc.local/{did}',), mock_post.call_args.args)
|
2023-09-12 18:49:57 +00:00
|
|
|
genesis_op = mock_post.call_args.kwargs['json']
|
2023-11-16 03:08:06 +00:00
|
|
|
self.assertEqual(did, genesis_op.pop('did'))
|
2023-09-12 18:49:57 +00:00
|
|
|
genesis_op['sig'] = base64.urlsafe_b64decode(genesis_op['sig'])
|
|
|
|
assert arroba.util.verify_sig(genesis_op, repo.rotation_key.public_key())
|
|
|
|
|
|
|
|
del genesis_op['sig']
|
|
|
|
self.assertEqual({
|
|
|
|
'type': 'plc_operation',
|
|
|
|
'verificationMethods': {
|
|
|
|
'atproto': encode_did_key(repo.signing_key.public_key()),
|
|
|
|
},
|
|
|
|
'rotationKeys': [encode_did_key(repo.rotation_key.public_key())],
|
|
|
|
'alsoKnownAs': [
|
2023-09-25 17:57:16 +00:00
|
|
|
'at://fake:handle:user.fa.brid.gy',
|
2023-09-12 18:49:57 +00:00
|
|
|
],
|
|
|
|
'services': {
|
|
|
|
'atproto_pds': {
|
|
|
|
'type': 'AtprotoPersonalDataServer',
|
2023-10-20 05:17:52 +00:00
|
|
|
'endpoint': 'https://atproto.brid.gy/',
|
2023-09-12 18:49:57 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
'prev': None,
|
|
|
|
}, genesis_op)
|
|
|
|
|
2023-09-06 03:10:11 +00:00
|
|
|
# check atproto-commit task
|
2023-09-13 21:36:24 +00:00
|
|
|
self.assertEqual(2, mock_create_task.call_count)
|
|
|
|
self.assert_task(mock_create_task, 'atproto-commit',
|
2023-09-29 20:49:17 +00:00
|
|
|
'/queue/atproto-commit')
|
2023-09-06 03:10:11 +00:00
|
|
|
|
2023-10-05 22:47:04 +00:00
|
|
|
@patch('requests.get', return_value=requests_response(
|
|
|
|
'blob contents', content_type='image/png')) # image blob fetch
|
2023-10-04 20:54:20 +00:00
|
|
|
@patch('google.cloud.dns.client.ManagedZone', autospec=True)
|
2023-09-06 03:10:11 +00:00
|
|
|
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
2023-09-01 21:18:50 +00:00
|
|
|
@patch('requests.post',
|
|
|
|
return_value=requests_response('OK')) # create DID on PLC
|
2023-10-05 22:47:04 +00:00
|
|
|
def test_send_new_repo_includes_user_profile(self, mock_post, mock_create_task,
|
|
|
|
_, __):
|
2023-10-07 18:49:04 +00:00
|
|
|
Fake.fetchable = {'fake:user': ACTOR_AS}
|
|
|
|
|
2023-09-01 21:18:50 +00:00
|
|
|
obj = self.store_object(id='fake:post', source_protocol='fake', our_as1={
|
|
|
|
**POST_AS,
|
|
|
|
'actor': 'fake:user',
|
|
|
|
})
|
2023-10-20 05:17:52 +00:00
|
|
|
self.assertTrue(ATProto.send(obj, 'https://atproto.brid.gy/'))
|
2023-09-01 21:18:50 +00:00
|
|
|
|
|
|
|
# check profile, record
|
2023-10-20 18:23:43 +00:00
|
|
|
user = Fake.get_by_id('fake:user')
|
2023-11-16 03:08:06 +00:00
|
|
|
did = user.key.get().get_copy(ATProto)
|
2023-09-14 17:20:04 +00:00
|
|
|
repo = self.storage.load_repo(did)
|
2023-09-01 21:18:50 +00:00
|
|
|
profile = repo.get_record('app.bsky.actor.profile', 'self')
|
2023-10-03 23:52:21 +00:00
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.actor.profile',
|
|
|
|
'displayName': 'Alice',
|
|
|
|
'description': 'hi there',
|
2023-10-05 22:47:04 +00:00
|
|
|
'avatar': {
|
|
|
|
'$type': 'blob',
|
|
|
|
'ref': BLOB_CID,
|
|
|
|
'mimeType': 'image/png',
|
|
|
|
'size': 13,
|
|
|
|
},
|
2023-10-03 23:52:21 +00:00
|
|
|
}, profile)
|
2023-11-11 18:38:48 +00:00
|
|
|
last_tid = arroba.util.int_to_tid(arroba.util._tid_ts_last)
|
|
|
|
record = repo.get_record('app.bsky.feed.post', last_tid)
|
2023-09-01 21:18:50 +00:00
|
|
|
self.assertEqual(POST_BSKY, record)
|
|
|
|
|
2023-11-11 18:38:48 +00:00
|
|
|
at_uri = f'at://{did}/app.bsky.feed.post/{last_tid}'
|
2023-09-14 17:20:04 +00:00
|
|
|
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
|
|
|
|
Object.get_by_id(id='fake:post').copies)
|
|
|
|
|
2023-09-06 03:10:11 +00:00
|
|
|
mock_create_task.assert_called()
|
|
|
|
|
|
|
|
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
2023-11-14 18:54:36 +00:00
|
|
|
def test_send_note_existing_repo(self, mock_create_task):
|
2023-11-03 00:41:31 +00:00
|
|
|
user = self.make_user_and_repo()
|
2023-09-01 19:07:21 +00:00
|
|
|
obj = self.store_object(id='fake:post', source_protocol='fake', our_as1={
|
|
|
|
**POST_AS,
|
|
|
|
'actor': 'fake:user',
|
|
|
|
})
|
2023-10-20 05:17:52 +00:00
|
|
|
self.assertTrue(ATProto.send(obj, 'https://atproto.brid.gy/'))
|
2023-09-01 19:07:21 +00:00
|
|
|
|
|
|
|
# check repo, record
|
2023-11-16 03:08:06 +00:00
|
|
|
did = user.key.get().get_copy(ATProto)
|
|
|
|
repo = self.storage.load_repo(did)
|
2023-11-11 18:38:48 +00:00
|
|
|
last_tid = arroba.util.int_to_tid(arroba.util._tid_ts_last)
|
|
|
|
record = repo.get_record('app.bsky.feed.post', last_tid)
|
2023-09-01 19:07:21 +00:00
|
|
|
self.assertEqual(POST_BSKY, record)
|
|
|
|
|
2023-11-16 03:08:06 +00:00
|
|
|
at_uri = f'at://{did}/app.bsky.feed.post/{last_tid}'
|
2023-09-14 17:20:04 +00:00
|
|
|
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
|
|
|
|
Object.get_by_id(id='fake:post').copies)
|
|
|
|
|
2023-09-06 03:10:11 +00:00
|
|
|
mock_create_task.assert_called()
|
|
|
|
|
2023-11-14 18:54:36 +00:00
|
|
|
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
|
|
|
def test_send_like(self, mock_create_task):
|
|
|
|
user = self.make_user_and_repo()
|
2023-11-15 03:24:37 +00:00
|
|
|
self.store_object(id='did:plc:bob', raw={
|
|
|
|
**DID_DOC,
|
|
|
|
'id': 'did:plc:bob',
|
|
|
|
})
|
|
|
|
|
|
|
|
post_obj = self.store_object(id='at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
source_protocol='atproto', bsky={
|
|
|
|
'$type': 'app.bsky.feed.post',
|
|
|
|
'uri': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
'cid': 'bafyCID',
|
|
|
|
})
|
|
|
|
|
|
|
|
like_obj = self.store_object(id='fake:like', source_protocol='fake', our_as1={
|
2023-11-14 18:54:36 +00:00
|
|
|
'objectType': 'activity',
|
|
|
|
'verb': 'like',
|
|
|
|
'id': 'fake:like',
|
|
|
|
'actor': 'fake:user',
|
2023-11-15 03:24:37 +00:00
|
|
|
'object': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
2023-11-14 18:54:36 +00:00
|
|
|
})
|
2023-11-15 03:24:37 +00:00
|
|
|
self.assertTrue(ATProto.send(like_obj, 'https://atproto.brid.gy/'))
|
2023-11-14 18:54:36 +00:00
|
|
|
|
|
|
|
# check repo, record
|
2023-11-16 03:08:06 +00:00
|
|
|
did = user.get_copy(ATProto)
|
|
|
|
repo = self.storage.load_repo(did)
|
2023-11-14 18:54:36 +00:00
|
|
|
last_tid = arroba.util.int_to_tid(arroba.util._tid_ts_last)
|
|
|
|
record = repo.get_record('app.bsky.feed.like', last_tid)
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.feed.like',
|
|
|
|
'subject': {
|
2023-11-15 03:24:37 +00:00
|
|
|
'uri': 'at://did:plc:bob/app.bsky.feed.post/tid',
|
|
|
|
'cid': 'bafyCID',
|
2023-11-14 18:54:36 +00:00
|
|
|
},
|
2023-11-14 23:24:51 +00:00
|
|
|
'createdAt': '2022-01-02T03:04:05+00:00',
|
2023-11-14 18:54:36 +00:00
|
|
|
}, record)
|
|
|
|
|
2023-11-16 03:08:06 +00:00
|
|
|
at_uri = f'at://{did}/app.bsky.feed.like/{last_tid}'
|
2023-11-14 18:54:36 +00:00
|
|
|
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
|
|
|
|
Object.get_by_id(id='fake:like').copies)
|
|
|
|
|
|
|
|
mock_create_task.assert_called()
|
|
|
|
|
|
|
|
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
|
|
|
def test_send_repost(self, mock_create_task):
|
|
|
|
user = self.make_user_and_repo()
|
|
|
|
obj = self.store_object(id='fake:repost', source_protocol='fake', our_as1={
|
|
|
|
'objectType': 'activity',
|
|
|
|
'verb': 'share',
|
|
|
|
'id': 'fake:repost',
|
|
|
|
'actor': 'fake:user',
|
|
|
|
'object': 'at://did/app.bsky.feed.post/tid',
|
|
|
|
})
|
|
|
|
self.assertTrue(ATProto.send(obj, 'https://atproto.brid.gy/'))
|
|
|
|
|
|
|
|
# check repo, record
|
2023-11-16 03:08:06 +00:00
|
|
|
did = user.get_copy(ATProto)
|
|
|
|
repo = self.storage.load_repo(did)
|
2023-11-14 18:54:36 +00:00
|
|
|
last_tid = arroba.util.int_to_tid(arroba.util._tid_ts_last)
|
|
|
|
record = repo.get_record('app.bsky.feed.repost', last_tid)
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.feed.repost',
|
|
|
|
'subject': {
|
|
|
|
'uri': 'at://did/app.bsky.feed.post/tid',
|
|
|
|
'cid': 'TODO',
|
|
|
|
},
|
2023-11-14 23:24:51 +00:00
|
|
|
'createdAt': '2022-01-02T03:04:05+00:00',
|
2023-11-14 18:54:36 +00:00
|
|
|
}, record)
|
|
|
|
|
2023-11-16 03:08:06 +00:00
|
|
|
at_uri = f'at://{did}/app.bsky.feed.repost/{last_tid}'
|
2023-11-14 18:54:36 +00:00
|
|
|
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
|
|
|
|
Object.get_by_id(id='fake:repost').copies)
|
|
|
|
|
|
|
|
mock_create_task.assert_called()
|
|
|
|
|
|
|
|
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
|
|
|
def test_send_follow(self, mock_create_task):
|
|
|
|
user = self.make_user_and_repo()
|
|
|
|
obj = self.store_object(id='fake:follow', source_protocol='fake', our_as1={
|
|
|
|
'objectType': 'activity',
|
|
|
|
'verb': 'follow',
|
|
|
|
'id': 'fake:follow',
|
|
|
|
'actor': 'fake:user',
|
|
|
|
'object': 'did:plc:bob',
|
|
|
|
})
|
|
|
|
self.assertTrue(ATProto.send(obj, 'https://atproto.brid.gy/'))
|
|
|
|
|
|
|
|
# check repo, record
|
2023-11-16 03:08:06 +00:00
|
|
|
did = user.get_copy(ATProto)
|
|
|
|
repo = self.storage.load_repo(did)
|
2023-11-14 18:54:36 +00:00
|
|
|
last_tid = arroba.util.int_to_tid(arroba.util._tid_ts_last)
|
|
|
|
record = repo.get_record('app.bsky.graph.follow', last_tid)
|
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.graph.follow',
|
|
|
|
'subject': 'did:plc:bob',
|
2023-11-14 23:24:51 +00:00
|
|
|
'createdAt': '2022-01-02T03:04:05+00:00',
|
2023-11-14 18:54:36 +00:00
|
|
|
}, record)
|
|
|
|
|
2023-11-16 03:08:06 +00:00
|
|
|
at_uri = f'at://{did}/app.bsky.graph.follow/{last_tid}'
|
2023-11-14 18:54:36 +00:00
|
|
|
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
|
|
|
|
Object.get_by_id(id='fake:follow').copies)
|
|
|
|
|
|
|
|
mock_create_task.assert_called()
|
|
|
|
|
2023-09-06 03:10:11 +00:00
|
|
|
@patch.object(tasks_client, 'create_task')
|
|
|
|
def test_send_not_our_repo(self, mock_create_task):
|
2023-09-01 19:07:21 +00:00
|
|
|
self.assertFalse(ATProto.send(Object(id='fake:post'), 'http://other.pds/'))
|
|
|
|
self.assertEqual(0, AtpBlock.query().count())
|
|
|
|
self.assertEqual(0, AtpRepo.query().count())
|
2023-09-06 03:10:11 +00:00
|
|
|
mock_create_task.assert_not_called()
|
2023-09-01 19:07:21 +00:00
|
|
|
|
2023-09-06 03:10:11 +00:00
|
|
|
@patch.object(tasks_client, 'create_task')
|
|
|
|
def test_send_did_doc_not_our_repo(self, mock_create_task):
|
2023-11-02 02:29:39 +00:00
|
|
|
self.store_object(id='did:plc:user', raw=DID_DOC) # uses https://some.pds
|
2023-11-16 03:08:06 +00:00
|
|
|
user = self.make_user(id='fake:user', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:user', protocol='atproto')])
|
2023-09-01 19:07:21 +00:00
|
|
|
obj = self.store_object(id='fake:post', source_protocol='fake', our_as1={
|
|
|
|
'objectType': 'note',
|
|
|
|
'content': 'foo',
|
|
|
|
'actor': 'fake:user',
|
|
|
|
})
|
2023-10-20 05:17:52 +00:00
|
|
|
self.assertFalse(ATProto.send(obj, 'https://atproto.brid.gy/'))
|
2023-09-01 19:07:21 +00:00
|
|
|
self.assertEqual(0, AtpBlock.query().count())
|
|
|
|
self.assertEqual(0, AtpRepo.query().count())
|
2023-09-06 03:10:11 +00:00
|
|
|
mock_create_task.assert_not_called()
|
2023-09-14 16:42:11 +00:00
|
|
|
|
2023-09-26 20:45:54 +00:00
|
|
|
@patch.object(tasks_client, 'create_task')
|
|
|
|
def test_send_ignore_accept(self, mock_create_task):
|
|
|
|
obj = Object(id='fake:accept', as2=test_activitypub.ACCEPT)
|
2023-10-20 05:17:52 +00:00
|
|
|
self.assertFalse(ATProto.send(obj, 'https://atproto.brid.gy/'))
|
2023-09-26 20:45:54 +00:00
|
|
|
self.assertEqual(0, AtpBlock.query().count())
|
|
|
|
self.assertEqual(0, AtpRepo.query().count())
|
|
|
|
mock_create_task.assert_not_called()
|
|
|
|
|
2023-11-03 00:41:31 +00:00
|
|
|
@patch.object(tasks_client, 'create_task')
|
|
|
|
def test_send_translates_ids(self, mock_create_task):
|
|
|
|
user = self.make_user_and_repo()
|
2023-11-16 03:08:06 +00:00
|
|
|
alice = self.make_user(id='fake:alice', cls=Fake,
|
|
|
|
copies=[Target(uri='did:alice', protocol='atproto')])
|
2023-11-03 00:41:31 +00:00
|
|
|
post = self.store_object(
|
|
|
|
id='fake:post', source_protocol='fake',
|
|
|
|
copies=[Target(uri='at://did/coll/post', protocol='atproto')])
|
|
|
|
|
2023-11-09 22:06:59 +00:00
|
|
|
reply_as1 = {
|
|
|
|
'id': 'fake:reply',
|
|
|
|
'objectType': 'note',
|
|
|
|
'inReplyTo': 'fake:post',
|
|
|
|
'author': 'fake:user',
|
|
|
|
'content': 'foo',
|
|
|
|
'tags': [{
|
|
|
|
'objectType': 'mention',
|
|
|
|
'url': 'fake:alice',
|
|
|
|
}, {
|
|
|
|
'objectType': 'mention',
|
|
|
|
'url': 'fake:bob', # no ATProto user, should be dropped
|
|
|
|
}],
|
|
|
|
}
|
|
|
|
reply = self.store_object(id='fake:reply', source_protocol='fake',
|
|
|
|
our_as1=reply_as1)
|
|
|
|
|
|
|
|
create_as1 = {
|
2023-11-03 00:41:31 +00:00
|
|
|
'objectType': 'activity',
|
|
|
|
'verb': 'post',
|
2023-11-09 22:06:59 +00:00
|
|
|
'object': reply_as1,
|
|
|
|
}
|
|
|
|
create = self.store_object(id='fake:reply:post', source_protocol='fake',
|
|
|
|
our_as1=create_as1)
|
|
|
|
|
|
|
|
self.assertTrue(ATProto.send(create, 'https://atproto.brid.gy/'))
|
2023-11-03 00:41:31 +00:00
|
|
|
|
2023-11-16 03:08:06 +00:00
|
|
|
repo = self.storage.load_repo(user.get_copy(ATProto))
|
2023-11-11 18:38:48 +00:00
|
|
|
last_tid = arroba.util.int_to_tid(arroba.util._tid_ts_last)
|
|
|
|
record = repo.get_record('app.bsky.feed.post', last_tid)
|
2023-11-03 00:41:31 +00:00
|
|
|
self.assertEqual({
|
|
|
|
'$type': 'app.bsky.feed.post',
|
2023-11-14 23:24:51 +00:00
|
|
|
'createdAt': '2022-01-02T03:04:05+00:00',
|
2023-11-03 00:41:31 +00:00
|
|
|
'text': 'foo',
|
|
|
|
'reply': {
|
|
|
|
'$type': 'app.bsky.feed.post#replyRef',
|
|
|
|
'root': {
|
|
|
|
'$type': 'com.atproto.repo.strongRef',
|
2023-11-15 03:24:37 +00:00
|
|
|
'uri': 'at://did/coll/post',
|
2023-11-03 00:41:31 +00:00
|
|
|
'cid': 'TODO',
|
|
|
|
},
|
|
|
|
'parent': {
|
|
|
|
'$type': 'com.atproto.repo.strongRef',
|
|
|
|
'uri': 'at://did/coll/post',
|
|
|
|
'cid': 'TODO',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'facets': [{
|
|
|
|
'$type': 'app.bsky.richtext.facet',
|
|
|
|
'features': [{
|
|
|
|
'$type': 'app.bsky.richtext.facet#mention',
|
|
|
|
'did': 'did:alice',
|
|
|
|
}],
|
|
|
|
}],
|
|
|
|
}, record)
|
|
|
|
|
2023-11-11 18:38:48 +00:00
|
|
|
at_uri = f'at://did:plc:user/app.bsky.feed.post/{last_tid}'
|
2023-11-09 22:06:59 +00:00
|
|
|
self.assertEqual([], Object.get_by_id(id='fake:reply:post').copies)
|
2023-11-03 00:41:31 +00:00
|
|
|
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
|
|
|
|
Object.get_by_id(id='fake:reply').copies)
|
|
|
|
|
|
|
|
mock_create_task.assert_called()
|
|
|
|
|
2023-09-14 16:42:11 +00:00
|
|
|
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
|
|
|
@patch('requests.get')
|
|
|
|
def test_poll_notifications(self, mock_get, mock_create_task):
|
2023-11-16 03:08:06 +00:00
|
|
|
user_a = self.make_user(id='fake:user-a', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:a', protocol='atproto')])
|
|
|
|
user_b = self.make_user(id='fake:user-b', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:b', protocol='atproto')])
|
|
|
|
user_c = self.make_user(id='fake:user-c', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:c', protocol='atproto')])
|
2023-09-14 16:42:11 +00:00
|
|
|
|
2023-11-13 21:30:15 +00:00
|
|
|
Repo.create(self.storage, 'did:plc:a', signing_key=ATPROTO_KEY)
|
|
|
|
Repo.create(self.storage, 'did:plc:c', signing_key=ATPROTO_KEY)
|
2023-09-14 16:42:11 +00:00
|
|
|
|
|
|
|
like = {
|
|
|
|
'$type': 'app.bsky.feed.like',
|
|
|
|
'subject': {
|
|
|
|
'cid': '...',
|
|
|
|
'uri': 'at://did:plc:a/app.bsky.feed.post/999',
|
|
|
|
},
|
|
|
|
}
|
|
|
|
reply = {
|
|
|
|
'$type': 'app.bsky.feed.post',
|
|
|
|
'text': 'I hereby reply',
|
|
|
|
'reply': {
|
|
|
|
'root': {
|
|
|
|
'cid': '...',
|
|
|
|
'uri': 'at://did:plc:a/app.bsky.feed.post/987',
|
|
|
|
},
|
|
|
|
'parent': {
|
|
|
|
'cid': '...',
|
|
|
|
'uri': 'at://did:plc:a/app.bsky.feed.post/987',
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
follow = {
|
|
|
|
'$type': 'app.bsky.graph.follow',
|
|
|
|
'subject': 'did:plc:c',
|
|
|
|
}
|
|
|
|
eve = {
|
|
|
|
'$type': 'app.bsky.actor.defs#profileView',
|
|
|
|
'did': 'did:plc:eve',
|
|
|
|
'handle': 'eve.com',
|
|
|
|
}
|
|
|
|
alice = {
|
|
|
|
'$type': 'app.bsky.actor.defs#profileView',
|
|
|
|
'did': 'did:plc:a',
|
|
|
|
'handle': 'alice',
|
|
|
|
}
|
|
|
|
|
|
|
|
mock_get.side_effect = [
|
|
|
|
requests_response({
|
|
|
|
'cursor': '...',
|
|
|
|
'notifications': [{
|
2023-09-14 23:29:02 +00:00
|
|
|
'uri': 'at://did:plc:d/app.bsky.feed.like/123',
|
|
|
|
'cid': '...',
|
|
|
|
'author': eve,
|
|
|
|
'record': like,
|
|
|
|
'reason': 'like',
|
|
|
|
}, {
|
2023-09-14 16:42:11 +00:00
|
|
|
'uri': 'at://did:plc:d/app.bsky.feed.post/456',
|
|
|
|
'cid': '...',
|
|
|
|
'author': eve,
|
|
|
|
'record': reply,
|
|
|
|
'reason': 'reply',
|
|
|
|
}],
|
|
|
|
}),
|
2023-09-25 03:09:56 +00:00
|
|
|
requests_response(DID_DOC),
|
2023-09-14 16:42:11 +00:00
|
|
|
requests_response({
|
|
|
|
'cursor': '...',
|
|
|
|
'notifications': [{
|
|
|
|
'uri': 'at://did:plc:d/app.bsky.graph.follow/789',
|
|
|
|
'cid': '...',
|
|
|
|
'author': alice,
|
|
|
|
'record': follow,
|
|
|
|
'reason': 'follow',
|
|
|
|
}],
|
|
|
|
}),
|
|
|
|
]
|
|
|
|
|
2023-10-18 04:50:19 +00:00
|
|
|
resp = self.post('/queue/atproto-poll-notifs', client=hub.app.test_client())
|
2023-09-14 16:42:11 +00:00
|
|
|
self.assertEqual(200, resp.status_code)
|
|
|
|
|
2023-09-25 03:09:56 +00:00
|
|
|
expected_list_notifs = call(
|
2023-09-14 16:42:11 +00:00
|
|
|
'https://api.bsky-sandbox.dev/xrpc/app.bsky.notification.listNotifications',
|
|
|
|
json=None,
|
|
|
|
headers={
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
'User-Agent': common.USER_AGENT,
|
|
|
|
},
|
2023-09-25 03:09:56 +00:00
|
|
|
)
|
|
|
|
# just check that access token was set, then remove it before comparing
|
|
|
|
# for call in mock_get.call_args_list:
|
|
|
|
assert mock_get.call_args_list[0].kwargs['headers'].pop('Authorization')
|
|
|
|
self.assertEqual(expected_list_notifs, mock_get.call_args_list[0])
|
|
|
|
|
|
|
|
assert mock_get.call_args_list[2].kwargs['headers'].pop('Authorization')
|
|
|
|
self.assertEqual(expected_list_notifs, mock_get.call_args_list[2])
|
2023-09-14 16:42:11 +00:00
|
|
|
|
2023-09-14 23:29:02 +00:00
|
|
|
like_obj = Object.get_by_id('at://did:plc:d/app.bsky.feed.like/123')
|
|
|
|
self.assertEqual(like, like_obj.bsky)
|
2023-09-29 20:49:17 +00:00
|
|
|
self.assert_task(mock_create_task, 'receive', '/queue/receive',
|
2023-10-18 20:51:34 +00:00
|
|
|
obj=like_obj.key.urlsafe(), authed_as='did:plc:eve')
|
2023-09-14 16:42:11 +00:00
|
|
|
|
|
|
|
reply_obj = Object.get_by_id('at://did:plc:d/app.bsky.feed.post/456')
|
|
|
|
self.assertEqual(reply, reply_obj.bsky)
|
2023-09-29 20:49:17 +00:00
|
|
|
self.assert_task(mock_create_task, 'receive', '/queue/receive',
|
2023-10-18 20:51:34 +00:00
|
|
|
obj=reply_obj.key.urlsafe(), authed_as='did:plc:eve')
|
2023-09-14 16:42:11 +00:00
|
|
|
|
|
|
|
follow_obj = Object.get_by_id('at://did:plc:d/app.bsky.graph.follow/789')
|
|
|
|
self.assertEqual(follow, follow_obj.bsky)
|
2023-09-29 20:49:17 +00:00
|
|
|
self.assert_task(mock_create_task, 'receive', '/queue/receive',
|
2023-10-18 20:51:34 +00:00
|
|
|
obj=follow_obj.key.urlsafe(), authed_as='did:plc:a')
|
2023-11-14 20:30:14 +00:00
|
|
|
|
|
|
|
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
|
|
|
@patch('requests.get')
|
|
|
|
def test_poll_posts(self, mock_get, mock_create_task):
|
2023-11-16 03:08:06 +00:00
|
|
|
user_a = self.make_user(id='fake:user-a', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:a', protocol='atproto')])
|
|
|
|
user_b = self.make_user(id='fake:user-b', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:b', protocol='atproto')])
|
|
|
|
user_c = self.make_user(id='fake:user-c', cls=Fake,
|
|
|
|
copies=[Target(uri='did:plc:c', protocol='atproto')])
|
2023-11-14 20:30:14 +00:00
|
|
|
Repo.create(self.storage, 'did:plc:a', signing_key=ATPROTO_KEY)
|
|
|
|
Repo.create(self.storage, 'did:plc:b', signing_key=ATPROTO_KEY)
|
|
|
|
Repo.create(self.storage, 'did:plc:c', signing_key=ATPROTO_KEY)
|
|
|
|
|
2023-11-14 22:21:22 +00:00
|
|
|
post = {
|
|
|
|
'$type': 'app.bsky.feed.post',
|
|
|
|
'text': 'My original post',
|
|
|
|
'createdAt': '2007-07-07T03:04:05',
|
|
|
|
}
|
2023-11-14 20:30:14 +00:00
|
|
|
post_view = {
|
|
|
|
'$type': 'app.bsky.feed.defs#postView',
|
|
|
|
'uri': 'at://did:web:alice.com/app.bsky.feed.post/123',
|
|
|
|
'cid': 'TODO',
|
2023-11-14 22:21:22 +00:00
|
|
|
'record': post,
|
2023-11-14 20:30:14 +00:00
|
|
|
'author': {
|
|
|
|
'$type': 'app.bsky.actor.defs#profileViewBasic',
|
|
|
|
'did': 'did:web:alice.com',
|
|
|
|
'handle': 'alice.com',
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
mock_get.side_effect = [
|
|
|
|
requests_response({
|
|
|
|
'cursor': '...',
|
|
|
|
'feed': [{
|
|
|
|
'$type': 'app.bsky.feed.defs#feedViewPost',
|
|
|
|
'post': post_view,
|
|
|
|
}],
|
|
|
|
}),
|
|
|
|
requests_response({
|
|
|
|
**DID_DOC,
|
|
|
|
'id': 'did:plc:alice.com',
|
|
|
|
}),
|
|
|
|
requests_response({
|
|
|
|
'cursor': '...',
|
|
|
|
'feed': [],
|
|
|
|
}),
|
|
|
|
requests_response({
|
|
|
|
'cursor': '...',
|
|
|
|
'feed': [{
|
|
|
|
'$type': 'app.bsky.feed.defs#feedViewPost',
|
|
|
|
'post': post_view,
|
|
|
|
'reason': {
|
|
|
|
'$type': 'app.bsky.feed.defs#reasonRepost',
|
|
|
|
'by': {
|
|
|
|
'$type': 'app.bsky.actor.defs#profileViewBasic',
|
|
|
|
'did': 'did:web:bob.com',
|
|
|
|
'handle': 'bob.com',
|
|
|
|
},
|
|
|
|
'indexedAt': '2022-01-02T03:04:05+00:00',
|
|
|
|
},
|
|
|
|
}],
|
|
|
|
}),
|
|
|
|
]
|
|
|
|
|
|
|
|
resp = self.post('/queue/atproto-poll-posts', client=hub.app.test_client())
|
|
|
|
self.assertEqual(200, resp.status_code)
|
|
|
|
|
|
|
|
get_timeline = call(
|
|
|
|
'https://api.bsky-sandbox.dev/xrpc/app.bsky.feed.getTimeline',
|
|
|
|
json=None,
|
|
|
|
headers={
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
'User-Agent': common.USER_AGENT,
|
|
|
|
'Authorization': ANY,
|
|
|
|
})
|
|
|
|
self.assertEqual([
|
|
|
|
get_timeline,
|
|
|
|
self.req('https://alice.com/.well-known/did.json'),
|
|
|
|
get_timeline,
|
|
|
|
get_timeline,
|
|
|
|
], mock_get.call_args_list)
|
|
|
|
|
|
|
|
post_obj = Object.get_by_id('at://did:web:alice.com/app.bsky.feed.post/123')
|
2023-11-14 22:21:22 +00:00
|
|
|
self.assertEqual(post, post_obj.bsky)
|
2023-11-14 20:30:14 +00:00
|
|
|
self.assert_task(mock_create_task, 'receive', '/queue/receive',
|
|
|
|
obj=post_obj.key.urlsafe(), authed_as='did:plc:a')
|
|
|
|
|
2023-11-15 22:23:08 +00:00
|
|
|
# TODO: https://github.com/snarfed/bridgy-fed/issues/728
|
2023-11-14 20:30:14 +00:00
|
|
|
# repost_obj = Object.get_by_id('at://did:plc:d/app.bsky.feed.post/456')
|
|
|
|
# self.assertEqual(repost, repost_obj.bsky)
|
|
|
|
# self.assert_task(mock_create_task, 'receive', '/queue/receive',
|
|
|
|
# obj=repost_obj.key.urlsafe(), authed_as='did:plc:eve')
|