Merge branch 'develop' into feature/activitypub

This commit is contained in:
lain 2018-02-12 10:24:15 +01:00
commit b331cb449a
36 changed files with 139 additions and 115 deletions

View file

@ -21,4 +21,4 @@ before_script:
unit-testing:
stage: test
script:
- MIX_ENV=test mix test
- MIX_ENV=test mix test --trace

View file

@ -32,7 +32,7 @@ def update_stats do
domain_count = Enum.count(peers)
status_query = from(u in User.local_user_query,
select: fragment("sum((?->>'note_count')::int)", u.info))
status_count = Repo.one(status_query) |> IO.inspect
status_count = Repo.one(status_query)
user_count = Repo.aggregate(User.local_user_query, :count, :id)
Agent.update(__MODULE__, fn _ ->
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}

View file

@ -10,6 +10,8 @@ def to_simple_form(user, activities, _users) do
h = fn(str) -> [to_charlist(str)] end
last_activity = List.last(activities)
entries = activities
|> Enum.map(fn(activity) ->
{:entry, ActivityRepresenter.to_simple_form(activity, user)}
@ -32,7 +34,15 @@ def to_simple_form(user, activities, _users) do
{:link, [rel: 'salmon', href: h.(OStatus.salmon_path(user))], []},
{:link, [rel: 'self', href: h.(OStatus.feed_path(user)), type: 'application/atom+xml'], []},
{:author, UserRepresenter.to_simple_form(user)},
] ++ entries
] ++
if last_activity do
[{:link, [rel: 'next',
href: to_charlist(OStatus.feed_path(user)) ++ '?max_id=' ++ to_charlist(last_activity.id),
type: 'application/atom+xml'], []}]
else
[]
end
++ entries
}]
end
end

View file

@ -19,7 +19,7 @@ def feed_redirect(conn, %{"nickname" => nickname} = params) do
end
end
def feed(conn, %{"nickname" => nickname}) do
def feed(conn, %{"nickname" => nickname} = params) do
user = User.get_cached_by_nickname(nickname)
query = from activity in Activity,
where: fragment("?->>'actor' = ?", activity.data, ^user.ap_id),
@ -27,6 +27,7 @@ def feed(conn, %{"nickname" => nickname}) do
order_by: [desc: :id]
activities = query
|> restrict_max(params)
|> Repo.all
response = user
@ -56,6 +57,11 @@ defp decode_or_retry(body) do
end
end
defp restrict_max(query, %{"max_id" => max_id}) do
from activity in query, where: activity.id < ^max_id
end
defp restrict_max(query, _), do: query
def salmon_incoming(conn, _) do
{:ok, body, _conn} = read_body(conn)
{:ok, doc} = decode_or_retry(body)

View file

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.b3deb1dd44970d86cc6b368f36fd09d9.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.15dfe939c498cca9840c.js></script><script type=text/javascript src=/static/js/vendor.409059e5a814f448f5bc.js></script><script type=text/javascript src=/static/js/app.30c01d7540d43b760f03.js></script></body></html>
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.b3deb1dd44970d86cc6b368f36fd09d9.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.f8637fb66085728d1fbd.js></script><script type=text/javascript src=/static/js/vendor.6ceb4478f308829a2829.js></script><script type=text/javascript src=/static/js/app.23c1437e78f655a31c4a.js></script></body></html>

View file

@ -0,0 +1,4 @@
<div style="margin-left:12px; margin-right:12px">
<p>This is a <a href="https://pleroma.social" target="_blank">Pleroma</a> instance.</p>
</div>

View file

@ -3,5 +3,6 @@
"background": "/static/bg.jpg",
"logo": "/static/logo.png",
"defaultPath": "/main/all",
"chatDisabled": false
"chatDisabled": false,
"showInstanceSpecificPanel": false
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var r=window.webpackJsonp;window.webpackJsonp=function(o,c){for(var p,l,s=0,i=[];s<o.length;s++)l=o[s],a[l]&&i.push.apply(i,a[l]),a[l]=0;for(p in c)Object.prototype.hasOwnProperty.call(c,p)&&(e[p]=c[p]);for(r&&r(o,c);i.length;)i.shift().call(null,t);if(c[0])return n[0]=0,t(0)};var n={},a={0:0};t.e=function(e,r){if(0===a[e])return r.call(null,t);if(void 0!==a[e])a[e].push(r);else{a[e]=[r];var n=document.getElementsByTagName("head")[0],o=document.createElement("script");o.type="text/javascript",o.charset="utf-8",o.async=!0,o.src=t.p+"static/js/"+e+"."+{1:"409059e5a814f448f5bc",2:"30c01d7540d43b760f03"}[e]+".js",n.appendChild(o)}},t.m=e,t.c=n,t.p="/"}([]);
//# sourceMappingURL=manifest.15dfe939c498cca9840c.js.map

View file

@ -0,0 +1,2 @@
!function(e){function t(r){if(a[r])return a[r].exports;var n=a[r]={exports:{},id:r,loaded:!1};return e[r].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var r=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],n[l]&&i.push.apply(i,n[l]),n[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(r&&r(c,o);i.length;)i.shift().call(null,t);if(o[0])return a[0]=0,t(0)};var a={},n={0:0};t.e=function(e,r){if(0===n[e])return r.call(null,t);if(void 0!==n[e])n[e].push(r);else{n[e]=[r];var a=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"6ceb4478f308829a2829",2:"23c1437e78f655a31c4a"}[e]+".js",a.appendChild(c)}},t.m=e,t.c=a,t.p="/"}([]);
//# sourceMappingURL=manifest.f8637fb66085728d1fbd.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -50,7 +50,7 @@ test "it returns error if the notification doesn't belong to the user" do
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:error, notification} = Notification.get(user, notification.id)
{:error, _notification} = Notification.get(user, notification.id)
end
end
@ -72,7 +72,7 @@ test "it returns error if the notification doesn't belong to the user" do
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:error, notification} = Notification.dismiss(user, notification.id)
{:error, _notification} = Notification.dismiss(user, notification.id)
end
end

View file

@ -16,7 +16,7 @@ test "it ensures uniqueness of the id" do
cs = Object.change(%Object{}, %{data: %{id: object.data["id"]}})
assert cs.valid?
{:error, result} = Repo.insert(cs)
{:error, _result} = Repo.insert(cs)
end
end
end

View file

@ -0,0 +1,6 @@
defmodule Pleroma.Web.OStatusMock do
import Pleroma.Factory
def handle_incoming(_doc) do
insert(:note_activity)
end
end

View file

@ -326,7 +326,7 @@ test "get recipients from activity" do
assert [addressed] == User.get_recipients_from_activity(activity)
{:ok, user} = User.follow(user, actor)
{:ok, user_two} = User.follow(user_two, actor)
{:ok, _user_two} = User.follow(user_two, actor)
recipients = User.get_recipients_from_activity(activity)
assert length(recipients) == 2
assert user in recipients

View file

@ -123,7 +123,7 @@ test "doesn't return blocked activities" do
describe "public fetch activities" do
test "retrieves public activities" do
activities = ActivityPub.fetch_public_activities
_activities = ActivityPub.fetch_public_activities
%{public: public} = ActivityBuilder.public_and_non_public

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
alias Pleroma.Web.{OStatus, CommonAPI}
import Pleroma.Factory
import ExUnit.CaptureLog
test "the home timeline", %{conn: conn} do
user = insert(:user)
@ -31,6 +32,7 @@ test "the home timeline", %{conn: conn} do
test "the public timeline", %{conn: conn} do
following = insert(:user)
capture_log fn ->
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
{:ok, [_activity]} = OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
@ -49,6 +51,7 @@ test "the public timeline", %{conn: conn} do
assert [%{"content" => "test"}] = json_response(conn, 200)
end
end
test "posting a status", %{conn: conn} do
user = insert(:user)
@ -144,7 +147,7 @@ test "list of notifications", %{conn: conn} do
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:ok, [_notification]} = Notification.create_notifications(activity)
conn = conn
|> assign(:user, user)
@ -190,7 +193,7 @@ test "clearing all notifications", %{conn: conn} do
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:ok, [_notification]} = Notification.create_notifications(activity)
conn = conn
|> assign(:user, user)
@ -338,9 +341,9 @@ test "media upload", %{conn: conn} do
test "hashtag timeline", %{conn: conn} do
following = insert(:user)
capture_log fn ->
{:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
{:ok, [_activity]} = OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
conn = conn
|> get("/api/v1/timelines/tag/2hu")
@ -348,6 +351,7 @@ test "hashtag timeline", %{conn: conn} do
assert id == to_string(activity.id)
end
end
test "getting followers", %{conn: conn} do
user = insert(:user)
@ -381,14 +385,14 @@ test "following / unfollowing a user", %{conn: conn} do
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/follow")
assert %{"id" => id, "following" => true} = json_response(conn, 200)
assert %{"id" => _id, "following" => true} = json_response(conn, 200)
user = Repo.get(User, user.id)
conn = build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unfollow")
assert %{"id" => id, "following" => false} = json_response(conn, 200)
assert %{"id" => _id, "following" => false} = json_response(conn, 200)
user = Repo.get(User, user.id)
conn = build_conn()
@ -407,14 +411,14 @@ test "blocking / unblocking a user", %{conn: conn} do
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/block")
assert %{"id" => id, "blocking" => true} = json_response(conn, 200)
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
user = Repo.get(User, user.id)
conn = build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unblock")
assert %{"id" => id, "blocking" => false} = json_response(conn, 200)
assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
end
test "getting a list of blocks", %{conn: conn} do
@ -461,7 +465,7 @@ test "unimplemented mutes, follow_requests, blocks, domain blocks" do
test "account search", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, %{nickname: "shp@shitposter.club"})
_user_two = insert(:user, %{nickname: "shp@shitposter.club"})
user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
conn = conn
@ -495,6 +499,7 @@ test "search", %{conn: conn} do
end
test "search fetches remote statuses", %{conn: conn} do
capture_log fn ->
conn = conn
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
assert results = json_response(conn, 200)
@ -502,6 +507,7 @@ test "search fetches remote statuses", %{conn: conn} do
[status] = results["statuses"]
assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
end
end
test "search fetches remote accounts", %{conn: conn} do
conn = conn
@ -530,7 +536,7 @@ test "returns the favorites of a user", %{conn: conn} do
end
describe "updating credentials" do
test "updates the user's bio" do
test "updates the user's bio", %{conn: conn} do
user = insert(:user)
conn = conn
@ -541,7 +547,7 @@ test "updates the user's bio" do
assert user["note"] == "I drink #cofe"
end
test "updates the user's name" do
test "updates the user's name", %{conn: conn} do
user = insert(:user)
conn = conn
@ -552,7 +558,7 @@ test "updates the user's name" do
assert user["display_name"] == "markorepairs"
end
test "updates the user's avatar" do
test "updates the user's avatar", %{conn: conn} do
user = insert(:user)
new_avatar = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
@ -565,7 +571,7 @@ test "updates the user's avatar" do
assert user["avatar"] != "https://placehold.it/48x48"
end
test "updates the user's banner" do
test "updates the user's banner", %{conn: conn} do
user = insert(:user)
new_header = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
@ -579,7 +585,7 @@ test "updates the user's banner" do
end
end
test "get instance information" do
test "get instance information", %{conn: conn} do
insert(:user, %{local: true})
user = insert(:user, %{local: true})
insert(:user, %{local: false})

View file

@ -96,7 +96,7 @@ test "an announce activity" do
user = insert(:user)
object = Object.get_cached_by_ap_id(note.data["object"]["id"])
{:ok, announce, object} = ActivityPub.announce(user, object)
{:ok, announce, _object} = ActivityPub.announce(user, object)
announce = Repo.get(Activity, announce.id)

View file

@ -33,6 +33,7 @@ test "returns a feed of the last 20 items of the user" do
<author>
#{user_xml}
</author>
<link rel="next" href="#{OStatus.feed_path(user)}?max_id=#{note_activity.id}" type="application/atom+xml" />
<entry>
#{entry_xml}
</entry>

View file

@ -12,7 +12,7 @@ test "it removes the mentioned activity" do
user = insert(:user)
object = Object.get_by_ap_id(note.data["object"]["id"])
{:ok, like, object} = Pleroma.Web.ActivityPub.ActivityPub.like(user, object)
{:ok, like, _object} = Pleroma.Web.ActivityPub.ActivityPub.like(user, object)
incoming = File.read!("test/fixtures/delete.xml")
|> String.replace("tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status", note.data["object"]["id"])

View file

@ -84,10 +84,3 @@ test "gets a notice", %{conn: conn} do
assert response(conn, 200)
end
end
defmodule Pleroma.Web.OStatusMock do
import Pleroma.Factory
def handle_incoming(_doc) do
insert(:note_activity)
end
end

View file

@ -4,6 +4,7 @@ defmodule Pleroma.Web.OStatusTest do
alias Pleroma.Web.XML
alias Pleroma.{Object, Repo, User, Activity}
import Pleroma.Factory
import ExUnit.CaptureLog
test "don't insert create notes twice" do
incoming = File.read!("test/fixtures/incoming_note_activity.xml")
@ -91,7 +92,7 @@ test "handle incoming notes - Mastodon, with CW" do
test "handle incoming retweets - Mastodon, with CW" do
incoming = File.read!("test/fixtures/cw_retweet.xml")
{:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming)
{:ok, [[_activity, retweeted_activity]]} = OStatus.handle_incoming(incoming)
assert retweeted_activity.data["object"]["summary"] == "Hey."
end
@ -168,6 +169,7 @@ test "handle incoming retweets - Mastodon, salmon" do
end
test "handle incoming favorites - GS, websub" do
capture_log fn ->
incoming = File.read!("test/fixtures/favorite.xml")
{:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming)
@ -182,6 +184,7 @@ test "handle incoming favorites - GS, websub" do
assert favorited_activity.data["object"]["id"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
refute favorited_activity.local
end
end
test "handle conversation references" do
incoming = File.read!("test/fixtures/mastodon_conversation.xml")
@ -335,12 +338,14 @@ test "it works with the uri" do
describe "fetching a status by it's HTML url" do
test "it builds a missing status from an html url" do
capture_log fn ->
url = "https://shitposter.club/notice/2827873"
{:ok, [activity] } = OStatus.fetch_activity_from_url(url)
assert activity.data["actor"] == "https://shitposter.club/user/1"
assert activity.data["object"]["id"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
end
end
test "it works for atom notes, too" do
url = "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056"

View file

@ -35,7 +35,7 @@ test "it encodes a magic key from a public key" do
end
test "it decodes a friendica public key" do
key = Salmon.decode_key(@magickey_friendica)
_key = Salmon.decode_key(@magickey_friendica)
end
test "returns a public and private key from a pem" do
@ -90,7 +90,7 @@ test "it pushes an activity to remote accounts it's addressed to" do
user = Repo.get_by(User, ap_id: activity.data["actor"])
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
poster = fn (url, data, headers, options) ->
poster = fn (url, _data, _headers, _options) ->
assert url == "http://example.org/salmon"
end
Salmon.publish(user, activity, poster)

View file

@ -147,7 +147,7 @@ test "an undo for a follow" do
follower = insert(:user)
followed = insert(:user)
{:ok, follow} = ActivityPub.follow(follower, followed)
{:ok, _follow} = ActivityPub.follow(follower, followed)
{:ok, unfollow} = ActivityPub.unfollow(follower, followed)
map = ActivityRepresenter.to_map(unfollow, %{user: follower})

View file

@ -504,7 +504,7 @@ test "it returns a user's followers", %{conn: conn} do
user = insert(:user)
follower_one = insert(:user)
follower_two = insert(:user)
not_follower = insert(:user)
_not_follower = insert(:user)
{:ok, follower_one} = User.follow(follower_one, user)
{:ok, follower_two} = User.follow(follower_two, user)
@ -522,7 +522,7 @@ test "it returns the logged in user's friends", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
not_followed = insert(:user)
_not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
@ -538,7 +538,7 @@ test "it returns a given user's friends with user_id", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
not_followed = insert(:user)
_not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
@ -553,7 +553,7 @@ test "it returns a given user's friends with screen_name", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
not_followed = insert(:user)
_not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
@ -570,7 +570,7 @@ test "it returns a user's friends", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
not_followed = insert(:user)
_not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
@ -585,7 +585,7 @@ test "it returns a user's friends", %{conn: conn} do
end
describe "POST /api/account/update_profile.json" do
test "it updates a user's profile" do
test "it updates a user's profile", %{conn: conn} do
user = insert(:user)
conn = conn
@ -627,7 +627,7 @@ test "it returns search results", %{conn: conn} do
end
describe "GET /api/statusnet/tags/timeline/:tag.json" do
test "it returns the tags timeline" do
test "it returns the tags timeline", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, %{nickname: "shp@shitposter.club"})

View file

@ -247,7 +247,7 @@ test "Unblock another user using user_id" do
user = insert(:user)
User.block(user, unblocked)
{:ok, user, unblocked} = TwitterAPI.unblock(user, %{"user_id" => unblocked.id})
{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"user_id" => unblocked.id})
assert user.info["blocks"] == []
end
@ -256,7 +256,7 @@ test "Unblock another user using screen_name" do
user = insert(:user)
User.block(user, unblocked)
{:ok, user, unblocked} = TwitterAPI.unblock(user, %{"screen_name" => unblocked.nickname})
{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"screen_name" => unblocked.nickname})
assert user.info["blocks"] == []
end

View file

@ -130,7 +130,7 @@ test "A user that follows you", %{user: user} do
assert represented == UserView.render("show.json", %{user: follower, for: user})
end
test "A blocked user for the blocker", %{user: user} do
test "A blocked user for the blocker" do
user = insert(:user)
blocker = insert(:user)
User.block(blocker, user)

View file

@ -42,8 +42,7 @@ test "returns the info for a user" do
test "it works for friendica" do
user = "lain@squeet.me"
{:ok, data} = WebFinger.finger(user)
{:ok, _data} = WebFinger.finger(user)
end
test "it gets the xrd endpoint" do

View file

@ -74,10 +74,3 @@ test "rejects incoming feed updates with the wrong signature", %{conn: conn} do
assert length(Repo.all(Activity)) == 0
end
end
defmodule Pleroma.Web.OStatusMock do
import Pleroma.Factory
def handle_incoming(_doc) do
insert(:note_activity)
end
end

View file

@ -174,7 +174,7 @@ test "sign a text" do
signed = Websub.sign("secret", "text")
assert signed == "B8392C23690CCF871F37EC270BE1582DEC57A503" |> String.downcase
signed = Websub.sign("secret", [[""], ['']])
_signed = Websub.sign("secret", [[""], ['']])
end
describe "renewing subscriptions" do
@ -184,7 +184,7 @@ test "it renews subscriptions that have less than a day of time left" do
still_good = insert(:websub_client_subscription, %{valid_until: NaiveDateTime.add(now, 2 * day), topic: "http://example.org/still_good", state: "accepted"})
needs_refresh = insert(:websub_client_subscription, %{valid_until: NaiveDateTime.add(now, day - 100), topic: "http://example.org/needs_refresh", state: "accepted"})
refresh = Websub.refresh_subscriptions()
_refresh = Websub.refresh_subscriptions()
assert still_good == Repo.get(WebsubClientSubscription, still_good.id)
refute needs_refresh == Repo.get(WebsubClientSubscription, needs_refresh.id)