diff --git a/follow.py b/follow.py index ab4667a..6781388 100644 --- a/follow.py +++ b/follow.py @@ -181,7 +181,7 @@ class FollowCallback(indieauth.Callback): class UnfollowStart(indieauth.Start): - """Starts the IndieAuth flow to add a follower to an existing user.""" + """Starts the IndieAuth flow to remove a follower from an existing user.""" def dispatch_request(self): key = request.form['key'] domain = request.form['me'] @@ -202,8 +202,10 @@ class UnfollowCallback(indieauth.Callback): return domain = util.domain_from_link(auth_entity.key.id()) - if not User.get_by_id(domain): + user = User.get_by_id(domain) + if not user: error(f'No user for domain {domain}') + domain = user.key.id() follower = Follower.get_by_id(state) if not follower: diff --git a/tests/test_follow.py b/tests/test_follow.py index 1161939..f70ca20 100644 --- a/tests/test_follow.py +++ b/tests/test_follow.py @@ -206,7 +206,7 @@ class FollowTest(testutil.TestCase): self.assertEqual(400, resp.status_code) def test_callback_user_use_instead(self, mock_get, mock_post): - user = User.get_or_create('real.snarfed.org') + user = User.get_or_create('www.snarfed.org') User.get_or_create('snarfed.org', use_instead=user.key) mock_get.side_effect = ( @@ -226,7 +226,28 @@ class FollowTest(testutil.TestCase): with self.client: resp = self.client.get(f'/follow/callback?code=my_code&state={state}') self.assertEqual(302, resp.status_code) - self.assertEqual('/user/real.snarfed.org/following',resp.headers['Location']) + self.assertEqual('/user/www.snarfed.org/following', resp.headers['Location']) + + id = 'http://localhost/user/www.snarfed.org/following#2022-01-02T03:04:05-https://bar/actor' + expected_follow = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'type': 'Follow', + 'id': id, + 'actor': 'http://localhost/www.snarfed.org', + 'object': FOLLOWEE, + 'to': [as2.PUBLIC_AUDIENCE], + } + followers = Follower.query().fetch() + self.assert_entities_equal( + Follower(id='https://bar/id www.snarfed.org', + last_follow=json_dumps(expected_follow, sort_keys=True), + src='www.snarfed.org', dest='https://bar/id', status='active'), + followers, + ignore=['created', 'updated']) + + self.assert_object(id, domains=['www.snarfed.org'], status='complete', + labels=['user', 'activity'], source_protocol='ui', + as2=expected_follow, as1=as2.to_as1(expected_follow)) @patch('requests.post') @@ -305,3 +326,51 @@ class UnfollowTest(testutil.TestCase): domains=['snarfed.org'], status='complete', source_protocol='ui', labels=['user', 'activity'], as2=expected_undo, as1=as2.to_as1(expected_undo)) + + def test_callback_user_use_instead(self, mock_get, mock_post): + user = User.get_or_create('www.snarfed.org') + User.get_or_create('snarfed.org', use_instead=user.key) + + self.follower = Follower( + id='https://bar/id www.snarfed.org', last_follow=json_dumps(FOLLOW_ADDRESS), + src='www.snarfed.org', dest='https://bar/id', status='active', + ).put() + + mock_get.side_effect = ( + requests_response(''), + self.as2_resp(FOLLOWEE), + ) + mock_post.side_effect = ( + requests_response('me=https://snarfed.org'), + requests_response('OK'), # AP Undo Follow to inbox + ) + + state = util.encode_oauth_state({ + 'endpoint': 'http://auth/endpoint', + 'me': 'https://snarfed.org', + 'state': self.follower.id(), + }) + with self.client: + resp = self.client.get(f'/unfollow/callback?code=my_code&state={state}') + self.assertEqual(302, resp.status_code) + self.assertEqual('/user/www.snarfed.org/following',resp.headers['Location']) + + id = 'http://localhost/user/www.snarfed.org/following#undo-2022-01-02T03:04:05-https://bar/id' + expected_undo = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'type': 'Undo', + 'id': id, + 'actor': 'http://localhost/www.snarfed.org', + 'object': FOLLOW_ADDRESS, + } + + inbox_args, inbox_kwargs = mock_post.call_args_list[1] + self.assertEqual(('http://bar/inbox',), inbox_args) + self.assert_equals(expected_undo, json_loads(inbox_kwargs['data'])) + + follower = Follower.get_by_id('https://bar/id www.snarfed.org') + self.assertEqual('inactive', follower.status) + + self.assert_object(id, domains=['www.snarfed.org'], status='complete', + source_protocol='ui', labels=['user', 'activity'], + as2=expected_undo, as1=as2.to_as1(expected_undo))