aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorrinpatch <rinpatch@sdf.org>2019-04-17 12:22:32 +0300
committerrinpatch <rinpatch@sdf.org>2019-04-17 12:22:32 +0300
commit627e5a0a4992cc19fc65a7e93a09c470c8e2bf33 (patch)
tree0f38b475e8554863a1cbbd7750c19d4cd1336eb1 /test
parentd6ab701a14f7c9fb4d59953648c425e04725fc62 (diff)
parent73df3046e014ae16e03f16a9c82921652cefcb54 (diff)
downloadpleroma-627e5a0a4992cc19fc65a7e93a09c470c8e2bf33.tar.gz
Merge branch 'develop' into feature/database-compaction
Diffstat (limited to 'test')
-rw-r--r--test/activity_test.exs26
-rw-r--r--test/captcha_test.exs46
-rw-r--r--test/config_test.exs4
-rw-r--r--test/emoji_test.exs106
-rw-r--r--test/filter_test.exs9
-rw-r--r--test/fixtures/activitypub-client-post-activity.json9
-rw-r--r--test/fixtures/httpoison_mock/emelie.atom306
-rw-r--r--test/fixtures/httpoison_mock/framatube.org_host_meta2
-rw-r--r--test/fixtures/httpoison_mock/gerzilla.de_host_meta10
-rw-r--r--test/fixtures/httpoison_mock/gnusocial.de_host_meta2
-rw-r--r--test/fixtures/httpoison_mock/status.emelie.json64
-rw-r--r--test/fixtures/httpoison_mock/webfinger_emelie.json36
-rw-r--r--test/fixtures/image.jpgbin291666 -> 284468 bytes
-rw-r--r--test/fixtures/lambadalambda.json64
-rw-r--r--test/fixtures/rel_me_anchor.html14
-rw-r--r--test/fixtures/rel_me_anchor_nofollow.html14
-rw-r--r--test/fixtures/rel_me_link.html14
-rw-r--r--test/fixtures/rel_me_null.html14
-rw-r--r--test/fixtures/rich_media/malformed-data.html4874
-rw-r--r--test/fixtures/rich_media/oembed.html3
-rw-r--r--test/fixtures/rich_media/oembed.json1
-rw-r--r--test/fixtures/rich_media/ogp.html9
-rw-r--r--test/fixtures/rich_media/twitter_card.html5
-rw-r--r--test/flake_id_test.exs42
-rw-r--r--test/formatter_test.exs164
-rw-r--r--test/html_test.exs12
-rw-r--r--test/http_test.exs59
-rw-r--r--test/integration/mastodon_websocket_test.exs101
-rw-r--r--test/list_test.exs9
-rw-r--r--test/media_proxy_test.exs50
-rw-r--r--test/notification_test.exs189
-rw-r--r--test/object_test.exs11
-rw-r--r--test/plugs/admin_secret_authentication_plug_test.exs42
-rw-r--r--test/plugs/authentication_plug_test.exs4
-rw-r--r--test/plugs/basic_auth_decoder_plug_test.exs4
-rw-r--r--test/plugs/ensure_authenticated_plug_test.exs4
-rw-r--r--test/plugs/ensure_user_key_plug_test.exs4
-rw-r--r--test/plugs/http_security_plug_test.exs4
-rw-r--r--test/plugs/http_signature_plug_test.exs4
-rw-r--r--test/plugs/instance_static_test.exs47
-rw-r--r--test/plugs/legacy_authentication_plug_test.exs22
-rw-r--r--test/plugs/oauth_plug_test.exs60
-rw-r--r--test/plugs/oauth_scopes_plug_test.exs122
-rw-r--r--test/plugs/session_authentication_plug_test.exs4
-rw-r--r--test/plugs/set_user_session_id_plug_test.exs6
-rw-r--r--test/plugs/uploaded_media_plug_test.exs43
-rw-r--r--test/plugs/user_enabled_plug_test.exs4
-rw-r--r--test/plugs/user_fetcher_plug_test.exs4
-rw-r--r--test/plugs/user_is_admin_plug_test.exs10
-rw-r--r--test/registration_test.exs59
-rw-r--r--test/scheduled_activity_test.exs64
-rw-r--r--test/scheduled_activity_worker_test.exs19
-rw-r--r--test/support/builders/activity_builder.ex1
-rw-r--r--test/support/builders/user_builder.ex3
-rw-r--r--test/support/captcha_mock.ex14
-rw-r--r--test/support/channel_case.ex4
-rw-r--r--test/support/conn_case.ex6
-rw-r--r--test/support/data_case.ex23
-rw-r--r--test/support/factory.ex133
-rw-r--r--test/support/helpers.ex29
-rw-r--r--test/support/http_request_mock.ex791
-rw-r--r--test/support/httpoison_mock.ex883
-rw-r--r--test/support/ostatus_mock.ex4
-rw-r--r--test/support/web_push_http_client_mock.ex23
-rw-r--r--test/support/websocket_client.ex62
-rw-r--r--test/support/websub_mock.ex9
-rw-r--r--test/tasks/instance.exs62
-rw-r--r--test/tasks/relay_test.exs72
-rw-r--r--test/tasks/uploads_test.exs56
-rw-r--r--test/tasks/user_test.exs341
-rw-r--r--test/test_helper.exs4
-rw-r--r--test/upload_test.exs54
-rw-r--r--test/user_invite_token_test.exs96
-rw-r--r--test/user_test.exs627
-rw-r--r--test/web/activity_pub/activity_pub_controller_test.exs402
-rw-r--r--test/web/activity_pub/activity_pub_test.exs601
-rw-r--r--test/web/activity_pub/mrf/anti_followbot_policy_test.exs72
-rw-r--r--test/web/activity_pub/mrf/hellthread_policy_test.exs73
-rw-r--r--test/web/activity_pub/mrf/keyword_policy_test.exs219
-rw-r--r--test/web/activity_pub/relay_test.exs4
-rw-r--r--test/web/activity_pub/transmogrifier_test.exs365
-rw-r--r--test/web/activity_pub/utils_test.exs208
-rw-r--r--test/web/activity_pub/views/object_view_test.exs2
-rw-r--r--test/web/activity_pub/views/user_view_test.exs62
-rw-r--r--test/web/activity_pub/visibilty_test.exs98
-rw-r--r--test/web/admin_api/admin_api_controller_test.exs679
-rw-r--r--test/web/admin_api/search_test.exs88
-rw-r--r--test/web/common_api/common_api_test.exs214
-rw-r--r--test/web/common_api/common_api_utils_test.exs139
-rw-r--r--test/web/federator_test.exs148
-rw-r--r--test/web/http_sigs/http_sig_test.exs13
-rw-r--r--test/web/instances/instance_test.exs107
-rw-r--r--test/web/instances/instances_test.exs132
-rw-r--r--test/web/mastodon_api/account_view_test.exs98
-rw-r--r--test/web/mastodon_api/list_view_test.exs5
-rw-r--r--test/web/mastodon_api/mastodon_api_controller_test.exs1610
-rw-r--r--test/web/mastodon_api/mastodon_socket_test.exs33
-rw-r--r--test/web/mastodon_api/notification_view_test.exs104
-rw-r--r--test/web/mastodon_api/push_subscription_view_test.exs23
-rw-r--r--test/web/mastodon_api/scheduled_activity_view_test.exs68
-rw-r--r--test/web/mastodon_api/status_view_test.exs194
-rw-r--r--test/web/mastodon_api/subscription_controller_test.exs192
-rw-r--r--test/web/metadata/opengraph_test.exs94
-rw-r--r--test/web/node_info_test.exs93
-rw-r--r--test/web/oauth/authorization_test.exs55
-rw-r--r--test/web/oauth/ldap_authorization_test.exs195
-rw-r--r--test/web/oauth/oauth_controller_test.exs731
-rw-r--r--test/web/oauth/token_test.exs24
-rw-r--r--test/web/ostatus/activity_representer_test.exs20
-rw-r--r--test/web/ostatus/feed_representer_test.exs8
-rw-r--r--test/web/ostatus/incoming_documents/delete_handling_test.exs18
-rw-r--r--test/web/ostatus/ostatus_controller_test.exs167
-rw-r--r--test/web/ostatus/ostatus_test.exs42
-rw-r--r--test/web/ostatus/user_representer_test.exs4
-rw-r--r--test/web/plugs/federating_plug_test.exs4
-rw-r--r--test/web/push/impl_test.exs147
-rw-r--r--test/web/rel_me_test.exs67
-rw-r--r--test/web/retry_queue_test.exs37
-rw-r--r--test/web/rich_media/helpers_test.exs62
-rw-r--r--test/web/rich_media/parser_test.exs95
-rw-r--r--test/web/salmon/salmon_test.exs21
-rw-r--r--test/web/streamer_test.exs69
-rw-r--r--test/web/twitter_api/representers/activity_representer_test.exs200
-rw-r--r--test/web/twitter_api/representers/object_representer_test.exs4
-rw-r--r--test/web/twitter_api/twitter_api_controller_test.exs971
-rw-r--r--test/web/twitter_api/twitter_api_test.exs462
-rw-r--r--test/web/twitter_api/util_controller_test.exs248
-rw-r--r--test/web/twitter_api/views/activity_view_test.exs158
-rw-r--r--test/web/twitter_api/views/notification_view_test.exs20
-rw-r--r--test/web/twitter_api/views/user_view_test.exs85
-rw-r--r--test/web/views/error_view_test.exs13
-rw-r--r--test/web/web_finger/web_finger_controller_test.exs46
-rw-r--r--test/web/web_finger/web_finger_test.exs10
-rw-r--r--test/web/websub/websub_controller_test.exs57
-rw-r--r--test/web/websub/websub_test.exs44
-rw-r--r--test/xml_builder_test.exs4
136 files changed, 18409 insertions, 1915 deletions
diff --git a/test/activity_test.exs b/test/activity_test.exs
index 55849c522..dc9c56a21 100644
--- a/test/activity_test.exs
+++ b/test/activity_test.exs
@@ -1,17 +1,22 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.ActivityTest do
use Pleroma.DataCase
+ alias Pleroma.Activity
import Pleroma.Factory
test "returns an activity by it's AP id" do
activity = insert(:note_activity)
- found_activity = Pleroma.Activity.get_by_ap_id(activity.data["id"])
+ found_activity = Activity.get_by_ap_id(activity.data["id"])
assert activity == found_activity
end
test "returns activities by it's objects AP ids" do
activity = insert(:note_activity)
- [found_activity] = Pleroma.Activity.all_by_object_ap_id(activity.data["object"]["id"])
+ [found_activity] = Activity.get_all_create_by_object_ap_id(activity.data["object"]["id"])
assert activity == found_activity
end
@@ -19,9 +24,22 @@ defmodule Pleroma.ActivityTest do
test "returns the activity that created an object" do
activity = insert(:note_activity)
- found_activity =
- Pleroma.Activity.get_create_activity_by_object_ap_id(activity.data["object"]["id"])
+ found_activity = Activity.get_create_by_object_ap_id(activity.data["object"]["id"])
assert activity == found_activity
end
+
+ test "reply count" do
+ %{id: id, data: %{"object" => %{"id" => object_ap_id}}} = activity = insert(:note_activity)
+
+ replies_count = activity.data["object"]["repliesCount"] || 0
+ expected_increase = replies_count + 1
+ Activity.increase_replies_count(object_ap_id)
+ %{data: %{"object" => %{"repliesCount" => actual_increase}}} = Activity.get_by_id(id)
+ assert expected_increase == actual_increase
+ expected_decrease = expected_increase - 1
+ Activity.decrease_replies_count(object_ap_id)
+ %{data: %{"object" => %{"repliesCount" => actual_decrease}}} = Activity.get_by_id(id)
+ assert expected_decrease == actual_decrease
+ end
end
diff --git a/test/captcha_test.exs b/test/captcha_test.exs
new file mode 100644
index 000000000..7ca9a4607
--- /dev/null
+++ b/test/captcha_test.exs
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.CaptchaTest do
+ use ExUnit.Case
+
+ import Tesla.Mock
+
+ alias Pleroma.Captcha.Kocaptcha
+
+ @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}]
+
+ describe "Kocaptcha" do
+ setup do
+ ets_name = Kocaptcha.Ets
+ ^ets_name = :ets.new(ets_name, @ets_options)
+
+ mock(fn
+ %{method: :get, url: "https://captcha.kotobank.ch/new"} ->
+ json(%{
+ md5: "63615261b77f5354fb8c4e4986477555",
+ token: "afa1815e14e29355e6c8f6b143a39fa2",
+ url: "/captchas/afa1815e14e29355e6c8f6b143a39fa2.png"
+ })
+ end)
+
+ :ok
+ end
+
+ test "new and validate" do
+ new = Kocaptcha.new()
+ assert new[:type] == :kocaptcha
+ assert new[:token] == "afa1815e14e29355e6c8f6b143a39fa2"
+
+ assert new[:url] ==
+ "https://captcha.kotobank.ch/captchas/afa1815e14e29355e6c8f6b143a39fa2.png"
+
+ assert Kocaptcha.validate(
+ new[:token],
+ "7oEy8c",
+ new[:answer_data]
+ ) == :ok
+ end
+ end
+end
diff --git a/test/config_test.exs b/test/config_test.exs
index 837cbb30c..0a6f0395a 100644
--- a/test/config_test.exs
+++ b/test/config_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.ConfigTest do
use ExUnit.Case
diff --git a/test/emoji_test.exs b/test/emoji_test.exs
new file mode 100644
index 000000000..cb1d62d00
--- /dev/null
+++ b/test/emoji_test.exs
@@ -0,0 +1,106 @@
+defmodule Pleroma.EmojiTest do
+ use ExUnit.Case, async: true
+ alias Pleroma.Emoji
+
+ describe "get_all/0" do
+ setup do
+ emoji_list = Emoji.get_all()
+ {:ok, emoji_list: emoji_list}
+ end
+
+ test "first emoji", %{emoji_list: emoji_list} do
+ [emoji | _others] = emoji_list
+ {code, path, tags} = emoji
+
+ assert tuple_size(emoji) == 3
+ assert is_binary(code)
+ assert is_binary(path)
+ assert is_binary(tags)
+ end
+
+ test "random emoji", %{emoji_list: emoji_list} do
+ emoji = Enum.random(emoji_list)
+ {code, path, tags} = emoji
+
+ assert tuple_size(emoji) == 3
+ assert is_binary(code)
+ assert is_binary(path)
+ assert is_binary(tags)
+ end
+ end
+
+ describe "match_extra/2" do
+ setup do
+ groups = [
+ "list of files": ["/emoji/custom/first_file.png", "/emoji/custom/second_file.png"],
+ "wildcard folder": "/emoji/custom/*/file.png",
+ "wildcard files": "/emoji/custom/folder/*.png",
+ "special file": "/emoji/custom/special.png"
+ ]
+
+ {:ok, groups: groups}
+ end
+
+ test "config for list of files", %{groups: groups} do
+ group =
+ groups
+ |> Emoji.match_extra("/emoji/custom/first_file.png")
+ |> to_string()
+
+ assert group == "list of files"
+ end
+
+ test "config with wildcard folder", %{groups: groups} do
+ group =
+ groups
+ |> Emoji.match_extra("/emoji/custom/some_folder/file.png")
+ |> to_string()
+
+ assert group == "wildcard folder"
+ end
+
+ test "config with wildcard folder and subfolders", %{groups: groups} do
+ group =
+ groups
+ |> Emoji.match_extra("/emoji/custom/some_folder/another_folder/file.png")
+ |> to_string()
+
+ assert group == "wildcard folder"
+ end
+
+ test "config with wildcard files", %{groups: groups} do
+ group =
+ groups
+ |> Emoji.match_extra("/emoji/custom/folder/some_file.png")
+ |> to_string()
+
+ assert group == "wildcard files"
+ end
+
+ test "config with wildcard files and subfolders", %{groups: groups} do
+ group =
+ groups
+ |> Emoji.match_extra("/emoji/custom/folder/another_folder/some_file.png")
+ |> to_string()
+
+ assert group == "wildcard files"
+ end
+
+ test "config for special file", %{groups: groups} do
+ group =
+ groups
+ |> Emoji.match_extra("/emoji/custom/special.png")
+ |> to_string()
+
+ assert group == "special file"
+ end
+
+ test "no mathing returns nil", %{groups: groups} do
+ group =
+ groups
+ |> Emoji.match_extra("/emoji/some_undefined.png")
+
+ refute group
+ end
+ end
+end
diff --git a/test/filter_test.exs b/test/filter_test.exs
index 509c15317..b31c68efd 100644
--- a/test/filter_test.exs
+++ b/test/filter_test.exs
@@ -1,9 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.FilterTest do
- alias Pleroma.{User, Repo}
+ alias Pleroma.Repo
use Pleroma.DataCase
import Pleroma.Factory
- import Ecto.Query
describe "creating filters" do
test "creating one filter" do
@@ -99,7 +102,7 @@ defmodule Pleroma.FilterTest do
context: ["home"]
}
- {:ok, filter} = Pleroma.Filter.create(query)
+ {:ok, _filter} = Pleroma.Filter.create(query)
{:ok, filter} = Pleroma.Filter.delete(query)
assert is_nil(Repo.get(Pleroma.Filter, filter.filter_id))
end
diff --git a/test/fixtures/activitypub-client-post-activity.json b/test/fixtures/activitypub-client-post-activity.json
new file mode 100644
index 000000000..c985e072b
--- /dev/null
+++ b/test/fixtures/activitypub-client-post-activity.json
@@ -0,0 +1,9 @@
+{
+ "@context": ["https://www.w3.org/ns/activitystreams", {"@language": "en-GB"}],
+ "type": "Create",
+ "object": {
+ "type": "Note",
+ "content": "It's a note"
+ },
+ "to": ["https://www.w3.org/ns/activitystreams#Public"]
+}
diff --git a/test/fixtures/httpoison_mock/emelie.atom b/test/fixtures/httpoison_mock/emelie.atom
new file mode 100644
index 000000000..ddaa1c6ca
--- /dev/null
+++ b/test/fixtures/httpoison_mock/emelie.atom
@@ -0,0 +1,306 @@
+<?xml version="1.0"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
+ <id>https://mastodon.social/users/emelie.atom</id>
+ <title>emelie 🎨</title>
+ <subtitle>23 / #Sweden / #Artist / #Equestrian / #GameDev
+
+If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰</subtitle>
+ <updated>2019-02-04T20:22:19Z</updated>
+ <logo>https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png</logo>
+ <author>
+ <id>https://mastodon.social/users/emelie</id>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
+ <uri>https://mastodon.social/users/emelie</uri>
+ <name>emelie</name>
+ <email>emelie@mastodon.social</email>
+ <summary type="html">&lt;p&gt;23 / &lt;a href="https://mastodon.social/tags/sweden" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;Sweden&lt;/span&gt;&lt;/a&gt; / &lt;a href="https://mastodon.social/tags/artist" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;Artist&lt;/span&gt;&lt;/a&gt; / &lt;a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;Equestrian&lt;/span&gt;&lt;/a&gt; / &lt;a href="https://mastodon.social/tags/gamedev" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;GameDev&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;If I ain&amp;apos;t spending time with my pets, I&amp;apos;m probably drawing. 🐴 🐱 🐰&lt;/p&gt;</summary>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie"/>
+ <link rel="avatar" type="image/png" media:width="120" media:height="120" href="https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png"/>
+ <link rel="header" type="image/png" media:width="700" media:height="335" href="https://files.mastodon.social/accounts/headers/000/015/657/original/847f331f3dd9e38b.png"/>
+ <poco:preferredUsername>emelie</poco:preferredUsername>
+ <poco:displayName>emelie 🎨</poco:displayName>
+ <poco:note>23 / #Sweden / #Artist / #Equestrian / #GameDev
+
+If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰</poco:note>
+ <mastodon:scope>public</mastodon:scope>
+ </author>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie.atom"/>
+ <link rel="hub" href="https://mastodon.social/api/push"/>
+ <link rel="salmon" href="https://mastodon.social/api/salmon/15657"/>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101850331907006641</id>
+ <published>2019-04-01T09:58:50Z</published>
+ <updated>2019-04-01T09:58:50Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101850331907006641"/>
+ <content type="html" xml:lang="en">&lt;p&gt;Me: I&amp;apos;m going to make this vital change to my world building in the morning, no way I&amp;apos;ll forget this, it&amp;apos;s too big of a deal&lt;br /&gt;Also me: forgets&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101850331907006641"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17854598.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94383214:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101849626603073336</id>
+ <published>2019-04-01T06:59:28Z</published>
+ <updated>2019-04-01T06:59:28Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849626603073336"/>
+ <content type="html" xml:lang="sv">&lt;p&gt;&lt;span class="h-card"&gt;&lt;a href="https://mastodon.social/@Fergant" class="u-url mention"&gt;@&lt;span&gt;Fergant&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; Dom är i stort sett religiös skrift vid det här laget 👏👏&lt;/p&gt;&lt;p&gt;har dock bara läst svenska översättningen, kanske är dags att jag läser dom på engelska&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://mastodon.social/users/Fergant"/>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849626603073336"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852590.atom"/>
+ <thr:in-reply-to ref="https://mastodon.social/users/Fergant/statuses/101849606513357387" href="https://mastodon.social/@Fergant/101849606513357387"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94362529:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101849580030237068</id>
+ <published>2019-04-01T06:47:37Z</published>
+ <updated>2019-04-01T06:47:37Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849580030237068"/>
+ <content type="html" xml:lang="en">&lt;p&gt;What&amp;apos;s you people&amp;apos;s favourite fantasy books? Give me some hot tips 🌞&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849580030237068"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852464.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94362529:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101849550599949363</id>
+ <published>2019-04-01T06:40:08Z</published>
+ <updated>2019-04-01T06:40:08Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849550599949363"/>
+ <content type="html" xml:lang="en">&lt;p&gt;Stick them legs out 💃 &lt;a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastocats&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <category term="mastocats"/>
+ <link rel="enclosure" type="image/jpeg" length="516384" href="https://files.mastodon.social/media_attachments/files/013/051/707/original/125a310abe9a34aa.jpeg"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849550599949363"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852407.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94361580:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101849191533152720</id>
+ <published>2019-04-01T05:08:49Z</published>
+ <updated>2019-04-01T05:08:49Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849191533152720"/>
+ <content type="html" xml:lang="en">&lt;p&gt;long 🐱 &lt;a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastocats&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <category term="mastocats"/>
+ <link rel="enclosure" type="image/jpeg" length="305208" href="https://files.mastodon.social/media_attachments/files/013/049/940/original/f2dbbfe7de3a17d2.jpeg"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849191533152720"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17851663.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94351141:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101849165031453009</id>
+ <published>2019-04-01T05:02:05Z</published>
+ <updated>2019-04-01T05:02:05Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849165031453009"/>
+ <content type="html" xml:lang="en">&lt;p&gt;You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 &lt;a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastocats&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <category term="mastocats"/>
+ <link rel="enclosure" type="video/mp4" length="9838915" href="https://files.mastodon.social/media_attachments/files/013/049/816/original/e7831178a5e0d6d4.mp4"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849165031453009"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17851558.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94350309:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101846512530748693</id>
+ <published>2019-03-31T17:47:31Z</published>
+ <updated>2019-03-31T17:47:31Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101846512530748693"/>
+ <content type="html" xml:lang="en">&lt;p&gt;Hello look at this boy having a decent haircut for once &lt;a href="https://mastodon.social/tags/mastohorses" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastohorses&lt;/span&gt;&lt;/a&gt; &lt;a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;equestrian&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <category term="equestrian"/>
+ <category term="mastohorses"/>
+ <link rel="enclosure" type="image/jpeg" length="461632" href="https://files.mastodon.social/media_attachments/files/013/033/387/original/301e8ab668cd61d2.jpeg"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101846512530748693"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17842424.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94256415:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101846181093805500</id>
+ <published>2019-03-31T16:23:14Z</published>
+ <updated>2019-03-31T16:23:14Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101846181093805500"/>
+ <content type="html" xml:lang="en">&lt;p&gt;Sorry did I disturb the who-is-the-longest-cat competition ? &lt;a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastocats&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <category term="mastocats"/>
+ <link rel="enclosure" type="image/jpeg" length="211384" href="https://files.mastodon.social/media_attachments/files/013/030/725/original/5b4886730cbbd25c.jpeg"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101846181093805500"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17841108.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94245239:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101845897513133849</id>
+ <published>2019-03-31T15:11:07Z</published>
+ <updated>2019-03-31T15:11:07Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101845897513133849"/>
+ <summary xml:lang="en">more earthsea ramblings</summary>
+ <content type="html" xml:lang="en">&lt;p&gt;I&amp;apos;m re-watching Tales from Earthsea for the first time since I read the books, and that Therru doesn&amp;apos;t squash Cob like a spider, as Orm Embar did is a wasted opportunity tbh&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101845897513133849"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17840088.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94232455:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101841219051533307</id>
+ <published>2019-03-30T19:21:19Z</published>
+ <updated>2019-03-30T19:21:19Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101841219051533307"/>
+ <content type="html" xml:lang="en">&lt;p&gt;I gave my cats some mackerel and they ate it all in 0.3 seconds, and now they won&amp;apos;t stop meowing for more, and I&amp;apos;m tired plz shut up&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101841219051533307"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17826587.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94075000:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101839949762341381</id>
+ <published>2019-03-30T13:58:31Z</published>
+ <updated>2019-03-30T13:58:31Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101839949762341381"/>
+ <content type="html" xml:lang="en">&lt;p&gt;yet I&amp;apos;m confused about this american dude with a gun, like the heck r ya doin in mah ghibli&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101839949762341381"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17821757.atom"/>
+ <thr:in-reply-to ref="https://mastodon.social/users/emelie/statuses/101839928677863590" href="https://mastodon.social/@emelie/101839928677863590"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94026360:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101839928677863590</id>
+ <published>2019-03-30T13:53:09Z</published>
+ <updated>2019-03-30T13:53:09Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101839928677863590"/>
+ <content type="html" xml:lang="en">&lt;p&gt;2 hours into Ni no Kuni 2 and I&amp;apos;ve already sold my soul to this game&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101839928677863590"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17821713.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94026360:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101836329521599438</id>
+ <published>2019-03-29T22:37:51Z</published>
+ <updated>2019-03-29T22:37:51Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101836329521599438"/>
+ <content type="html" xml:lang="en">&lt;p&gt;Pippi Longstocking the original one-punch /man&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101836329521599438"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17811608.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93907854:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101835905282948341</id>
+ <published>2019-03-29T20:49:57Z</published>
+ <updated>2019-03-29T20:49:57Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835905282948341"/>
+ <content type="html" xml:lang="en">&lt;p&gt;I&amp;apos;ve had so much wine I thought I had a 3rd brother&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835905282948341"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809862.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93892966:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101835878059204660</id>
+ <published>2019-03-29T20:43:02Z</published>
+ <updated>2019-03-29T20:43:02Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835878059204660"/>
+ <content type="html" xml:lang="en">&lt;p&gt;ååååhhh booi&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835878059204660"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809734.atom"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93892010:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101835848050598939</id>
+ <published>2019-03-29T20:35:24Z</published>
+ <updated>2019-03-29T20:35:24Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835848050598939"/>
+ <content type="html" xml:lang="en">&lt;p&gt;&lt;span class="h-card"&gt;&lt;a href="https://thraeryn.net/@thraeryn" class="u-url mention"&gt;@&lt;span&gt;thraeryn&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; if I spent 1 hour and a half watching this monstrosity, I need to&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://thraeryn.net/users/thraeryn"/>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835848050598939"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809591.atom"/>
+ <thr:in-reply-to ref="https://thraeryn.net/users/thraeryn/statuses/101835839202826007" href="https://thraeryn.net/@thraeryn/101835839202826007"/>
+ <ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93888827:objectType=Conversation"/>
+ </entry>
+ <entry>
+ <id>https://mastodon.social/users/emelie/statuses/101835823138262290</id>
+ <published>2019-03-29T20:29:04Z</published>
+ <updated>2019-03-29T20:29:04Z</updated>
+ <title>New status by emelie</title>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+ <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835823138262290"/>
+ <summary xml:lang="en">medical, fluids mention</summary>
+ <content type="html" xml:lang="en">&lt;p&gt;&lt;span class="h-card"&gt;&lt;a href="https://icosahedron.website/@Trev" class="u-url mention"&gt;@&lt;span&gt;Trev&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; *hugs* ✨&lt;/p&gt;</content>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://icosahedron.website/users/Trev"/>
+ <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
+ <mastodon:scope>public</mastodon:scope>
+ <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835823138262290"/>
+ <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809468.atom"/>
+ <thr:in-reply-to ref="https://icosahedron.website/users/Trev/statuses/101835812250051801" href="https://icosahedron.website/@Trev/101835812250051801"/>
+ <ostatus:conversation ref="tag:icosahedron.website,2019-03-29:objectId=12220882:objectType=Conversation"/>
+ </entry>
+</feed>
diff --git a/test/fixtures/httpoison_mock/framatube.org_host_meta b/test/fixtures/httpoison_mock/framatube.org_host_meta
new file mode 100644
index 000000000..91516ff6d
--- /dev/null
+++ b/test/fixtures/httpoison_mock/framatube.org_host_meta
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><hm:Host xmlns:hm="http://host-meta.net/xrd/1.0">framatube.org</hm:Host><Link rel="lrdd" template="http://framatube.org/main/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
diff --git a/test/fixtures/httpoison_mock/gerzilla.de_host_meta b/test/fixtures/httpoison_mock/gerzilla.de_host_meta
new file mode 100644
index 000000000..fae8f37eb
--- /dev/null
+++ b/test/fixtures/httpoison_mock/gerzilla.de_host_meta
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='UTF-8'?><XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'
+ xmlns:hm='http://host-meta.net/xrd/1.0'>
+
+ <hm:Host>gerzilla.de</hm:Host>
+
+ <Link rel='lrdd' type="application/xrd+xml" template='https://gerzilla.de/xrd/?uri={uri}' />
+ <Link rel="http://oexchange.org/spec/0.8/rel/resident-target" type="application/xrd+xml"
+ href="https://gerzilla.de/oexchange/xrd" />
+
+</XRD>
diff --git a/test/fixtures/httpoison_mock/gnusocial.de_host_meta b/test/fixtures/httpoison_mock/gnusocial.de_host_meta
new file mode 100644
index 000000000..a4affb102
--- /dev/null
+++ b/test/fixtures/httpoison_mock/gnusocial.de_host_meta
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><hm:Host xmlns:hm="http://host-meta.net/xrd/1.0">gnusocial.de</hm:Host><Link rel="lrdd" template="http://gnusocial.de/main/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
diff --git a/test/fixtures/httpoison_mock/status.emelie.json b/test/fixtures/httpoison_mock/status.emelie.json
new file mode 100644
index 000000000..4aada0377
--- /dev/null
+++ b/test/fixtures/httpoison_mock/status.emelie.json
@@ -0,0 +1,64 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "ostatus": "http://ostatus.org#",
+ "atomUri": "ostatus:atomUri",
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "sensitive": "as:sensitive",
+ "Hashtag": "as:Hashtag",
+ "toot": "http://joinmastodon.org/ns#",
+ "Emoji": "toot:Emoji",
+ "focalPoint": {
+ "@container": "@list",
+ "@id": "toot:focalPoint"
+ }
+ }
+ ],
+ "id": "https://mastodon.social/users/emelie/statuses/101849165031453009",
+ "type": "Note",
+ "summary": null,
+ "inReplyTo": null,
+ "published": "2019-04-01T05:02:05Z",
+ "url": "https://mastodon.social/@emelie/101849165031453009",
+ "attributedTo": "https://mastodon.social/users/emelie",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "cc": [
+ "https://mastodon.social/users/emelie/followers"
+ ],
+ "sensitive": false,
+ "atomUri": "https://mastodon.social/users/emelie/statuses/101849165031453009",
+ "inReplyToAtomUri": null,
+ "conversation": "tag:mastodon.social,2019-04-01:objectId=94350309:objectType=Conversation",
+ "content": "<p>You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 <a href=\"https://mastodon.social/tags/mastocats\" class=\"mention hashtag\" rel=\"tag\">#<span>mastocats</span></a></p>",
+ "contentMap": {
+ "en": "<p>You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 <a href=\"https://mastodon.social/tags/mastocats\" class=\"mention hashtag\" rel=\"tag\">#<span>mastocats</span></a></p>"
+ },
+ "attachment": [
+ {
+ "type": "Document",
+ "mediaType": "video/mp4",
+ "url": "https://files.mastodon.social/media_attachments/files/013/049/816/original/e7831178a5e0d6d4.mp4",
+ "name": null
+ }
+ ],
+ "tag": [
+ {
+ "type": "Hashtag",
+ "href": "https://mastodon.social/tags/mastocats",
+ "name": "#mastocats"
+ }
+ ],
+ "replies": {
+ "id": "https://mastodon.social/users/emelie/statuses/101849165031453009/replies",
+ "type": "Collection",
+ "first": {
+ "type": "CollectionPage",
+ "partOf": "https://mastodon.social/users/emelie/statuses/101849165031453009/replies",
+ "items": []
+ }
+ }
+}
diff --git a/test/fixtures/httpoison_mock/webfinger_emelie.json b/test/fixtures/httpoison_mock/webfinger_emelie.json
new file mode 100644
index 000000000..0b61cb618
--- /dev/null
+++ b/test/fixtures/httpoison_mock/webfinger_emelie.json
@@ -0,0 +1,36 @@
+{
+ "aliases": [
+ "https://mastodon.social/@emelie",
+ "https://mastodon.social/users/emelie"
+ ],
+ "links": [
+ {
+ "href": "https://mastodon.social/@emelie",
+ "rel": "http://webfinger.net/rel/profile-page",
+ "type": "text/html"
+ },
+ {
+ "href": "https://mastodon.social/users/emelie.atom",
+ "rel": "http://schemas.google.com/g/2010#updates-from",
+ "type": "application/atom+xml"
+ },
+ {
+ "href": "https://mastodon.social/users/emelie",
+ "rel": "self",
+ "type": "application/activity+json"
+ },
+ {
+ "href": "https://mastodon.social/api/salmon/15657",
+ "rel": "salmon"
+ },
+ {
+ "href": "data:application/magic-public-key,RSA.u3CWs1oAJPE3ZJ9sj6Ut_Mu-mTE7MOijsQc8_6c73XVVuhIEomiozJIH7l8a7S1n5SYL4UuiwcubSOi7u1bbGpYnp5TYhN-Cxvq_P80V4_ncNIPSQzS49it7nSLeG5pA21lGPDA44huquES1un6p9gSmbTwngVX9oe4MYuUeh0Z7vijjU13Llz1cRq_ZgPQPgfz-2NJf-VeXnvyDZDYxZPVBBlrMl3VoGbu0M5L8SjY35559KCZ3woIvqRolcoHXfgvJMdPcJgSZVYxlCw3dA95q9jQcn6s87CPSUs7bmYEQCrDVn5m5NER5TzwBmP4cgJl9AaDVWQtRd4jFZNTxlQ==.AQAB",
+ "rel": "magic-public-key"
+ },
+ {
+ "rel": "http://ostatus.org/schema/1.0/subscribe",
+ "template": "https://mastodon.social/authorize_interaction?uri={uri}"
+ }
+ ],
+ "subject": "acct:emelie@mastodon.social"
+}
diff --git a/test/fixtures/image.jpg b/test/fixtures/image.jpg
index 09834bb5c..edff6246b 100644
--- a/test/fixtures/image.jpg
+++ b/test/fixtures/image.jpg
Binary files differ
diff --git a/test/fixtures/lambadalambda.json b/test/fixtures/lambadalambda.json
new file mode 100644
index 000000000..1f09fb591
--- /dev/null
+++ b/test/fixtures/lambadalambda.json
@@ -0,0 +1,64 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "toot": "http://joinmastodon.org/ns#",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ },
+ "alsoKnownAs": {
+ "@id": "as:alsoKnownAs",
+ "@type": "@id"
+ },
+ "movedTo": {
+ "@id": "as:movedTo",
+ "@type": "@id"
+ },
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value",
+ "Hashtag": "as:Hashtag",
+ "Emoji": "toot:Emoji",
+ "IdentityProof": "toot:IdentityProof",
+ "focalPoint": {
+ "@container": "@list",
+ "@id": "toot:focalPoint"
+ }
+ }
+ ],
+ "id": "https://mastodon.social/users/lambadalambda",
+ "type": "Person",
+ "following": "https://mastodon.social/users/lambadalambda/following",
+ "followers": "https://mastodon.social/users/lambadalambda/followers",
+ "inbox": "https://mastodon.social/users/lambadalambda/inbox",
+ "outbox": "https://mastodon.social/users/lambadalambda/outbox",
+ "featured": "https://mastodon.social/users/lambadalambda/collections/featured",
+ "preferredUsername": "lambadalambda",
+ "name": "Critical Value",
+ "summary": "\u003cp\u003e\u003c/p\u003e",
+ "url": "https://mastodon.social/@lambadalambda",
+ "manuallyApprovesFollowers": false,
+ "publicKey": {
+ "id": "https://mastodon.social/users/lambadalambda#main-key",
+ "owner": "https://mastodon.social/users/lambadalambda",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0P/Tq4gb4G/QVuMGbJo\nC/AfMNcv+m7NfrlOwkVzcU47jgESuYI4UtJayissCdBycHUnfVUd9qol+eznSODz\nCJhfJloqEIC+aSnuEPGA0POtWad6DU0E6/Ho5zQn5WAWUwbRQqowbrsm/GHo2+3v\neR5jGenwA6sYhINg/c3QQbksyV0uJ20Umyx88w8+TJuv53twOfmyDWuYNoQ3y5cc\nHKOZcLHxYOhvwg3PFaGfFHMFiNmF40dTXt9K96r7sbzc44iLD+VphbMPJEjkMuf8\nPGEFOBzy8pm3wJZw2v32RNW2VESwMYyqDzwHXGSq1a73cS7hEnc79gXlELsK04L9\nQQIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "tag": [],
+ "attachment": [],
+ "endpoints": {
+ "sharedInbox": "https://mastodon.social/inbox"
+ },
+ "icon": {
+ "type": "Image",
+ "mediaType": "image/gif",
+ "url": "https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif"
+ },
+ "image": {
+ "type": "Image",
+ "mediaType": "image/gif",
+ "url": "https://files.mastodon.social/accounts/headers/000/000/264/original/28b26104f83747d2.gif"
+ }
+}
diff --git a/test/fixtures/rel_me_anchor.html b/test/fixtures/rel_me_anchor.html
new file mode 100644
index 000000000..5abcce129
--- /dev/null
+++ b/test/fixtures/rel_me_anchor.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Blog</title>
+ </head>
+ <body>
+ <article>
+ <h1>Lorem ipsum</h1>
+ <p>Lorem ipsum dolor sit ameph, …</p>
+ <a rel="me" href="https://social.example.org/users/lain">lain’s account</a>
+ </article>
+ </body>
+</html>
diff --git a/test/fixtures/rel_me_anchor_nofollow.html b/test/fixtures/rel_me_anchor_nofollow.html
new file mode 100644
index 000000000..c856f0091
--- /dev/null
+++ b/test/fixtures/rel_me_anchor_nofollow.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Blog</title>
+ </head>
+ <body>
+ <article>
+ <h1>Lorem ipsum</h1>
+ <p>Lorem ipsum dolor sit ameph, …</p>
+ <a rel="me nofollow" href="https://social.example.org/users/lain">lain’s account</a>
+ </article>
+ </body>
+</html>
diff --git a/test/fixtures/rel_me_link.html b/test/fixtures/rel_me_link.html
new file mode 100644
index 000000000..b9ff18f6e
--- /dev/null
+++ b/test/fixtures/rel_me_link.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Blog</title>
+ <link rel="me" href="https://social.example.org/users/lain"/>
+ </head>
+ <body>
+ <article>
+ <h1>Lorem ipsum</h1>
+ <p>Lorem ipsum dolor sit ameph, …</p>
+ </article>
+ </body>
+</html>
diff --git a/test/fixtures/rel_me_null.html b/test/fixtures/rel_me_null.html
new file mode 100644
index 000000000..5ab5f10c1
--- /dev/null
+++ b/test/fixtures/rel_me_null.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Blog</title>
+ </head>
+ <body>
+ <article>
+ <h1>Lorem ipsum</h1>
+ <p>Lorem ipsum dolor sit ameph, …</p>
+ <a rel="nofollow" href="https://social.example.org/users/lain">lain’s account</a>
+ </article>
+ </body>
+</html>
diff --git a/test/fixtures/rich_media/malformed-data.html b/test/fixtures/rich_media/malformed-data.html
new file mode 100644
index 000000000..dec628c16
--- /dev/null
+++ b/test/fixtures/rich_media/malformed-data.html
@@ -0,0 +1,4874 @@
+
+
+
+
+
+
+
+
+<!DOCTYPE html>
+<html lang="pl">
+<head>
+
+
+
+
+
+<!-- canonical_start -->
+
+ <link rel="canonical" href="http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html"/>
+
+
+<!-- canonical_end -->
+
+<!--10351348, [ /fix/modules/canonical.jsp ], canonicalModule-->
+
+
+
+<script>
+ var activeSubscription = false;
+</script>
+
+<!-- playerInfoModule v1.0 -->
+
+
+
+
+ <META NAME="ROBOTS" CONTENT="NOARCHIVE">
+
+
+
+
+<!-- robotsModule v1.0 -->
+
+
+<title>Pomys na biznes: chusta, ktra chroni przed smogiem</title>
+<meta charset="ISO-8859-2">
+<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+<link rel="shortcut icon" href="http://bi.gazeta.pl/im/5/7001/m7001335.gif">
+
+
+
+<link rel="apple-touch-icon-precomposed" sizes="144x144" href="http://static.im-g.pl/aliasy/foto/wyborcza/touch-icon-144x144.png">
+<link rel="apple-touch-icon-precomposed" sizes="114x114" href="http://static.im-g.pl/aliasy/foto/wyborcza/touch-icon-114x114.png">
+<link rel="apple-touch-icon-precomposed" sizes="72x72" href="http://static.im-g.pl/aliasy/foto/wyborcza/touch-icon-72x72.png">
+<link rel="apple-touch-icon-precomposed" href="http://static.im-g.pl/aliasy/foto/wyborcza/touch-icon-72x72.png">
+
+
+<!-- titleCharsetModule v1.0 -->
+
+
+
+
+
+
+
+ <!-- style preloader-test -->
+ <style>
+img[data-src]:not(.loaded){position:absolute !important;top:0 !important;left:0 !important;display:block !important;width:0 !important;height:0 !important;visibility:hidden !important;font-size:0 !important}.preloaderContainer{position:relative;z-index:1;display:block;width:100%;height:100%;min-height:56px;background:#fff url("data:image/gif;base64,R0lGODlhJgA4APEDAP+AgP8AAP///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFDQADACwAAAAAJgA4AAACcIyPicDt75RUsMKJjd0sY755E2iJElmZFPqoCdu6BxzJGt3ZAZ7bPKDb8YI/4lBXRB59S1mSiTNGlVMoTXqlZq0wbFcLE4jH5LL5jE6r1+y2+w2Py+f0uv2Oz+v3/L7/DxgoOEhYaHiImKi4yNjYVwAAIfkEBQ0AAgAsAAAAACYAHAAAAkaEj4nB7e+UVLDCiY3dLGO+eRNoiRJZmRT6qAnbugccyRrd2QCe23yg2/GCP+JQV0QefUtZkokzRpVTKE16pWatMGxXCysAACH5BAUNAAAALAAAAAATABwAAAIVlI+py+0Po5y02ouz3rz7D4biSCIFACH5BAUNAAEALBMAHAATABwAAAIVhI+py+0Po5y02ouz3rz7D4biSCIFACH5BAUNAAIALBMAAAATADgAAAIvhI+py+0Po5y02ouz3rz7D4biSCLBiabqyrbuC8fyTNf2jef6zvf+DwwKh8RiqgAAIfkEBQ0AAAAsEwAAABMAHAAAAhWUj6nL7Q+jnLTai7PevPsPhuJIIgUAIfkEBQ0AAQAsAAAcABMAHAAAAhWEj6nL7Q+jnLTai7PevPsPhuJIIgUAIfkEBQ0AAgAsAAAcACYAHAAAAkaMj4nA7e+UVLDCiY3dLGO+eRNoiRJZmRT6qAnbugccyRrd2QGe2zyg2/GCP+JQV0QefUtZkokzRpVTKE16pWatMGxXCysAACH5BAUNAAAALBMAHAATABwAAAIVlI+py+0Po5y02ouz3rz7D4biSCIFACH5BAUNAAEALAAAAAATABwAAAIVhI+py+0Po5y02ouz3rz7D4biSCIFACH5BAUNAAIALAAAAAATADgAAAIvjI+py+0Po5y02ouz3rz7D4biSCLAiabqyrbuC8fyTNf2jef6zvf+DwwKh8RiqgAAIfkEBQ0AAAAsAAAcABMAHAAAAhWUj6nL7Q+jnLTai7PevPsPhuJIIgUAOw==") center center no-repeat}
+
+</style>
+ <!-- /style preloader-test -->
+
+
+
+
+
+
+
+ <!-- head script -->
+ <script>function checkAdsElements(){if(void 0!==this.dfpArrLab&&this.dfpArrLab instanceof Array){for(var a=0;a<this.dfpArrLab.length;){var b=document.getElementById(this.dfpArrLab[a]);if(b){var c=b.clientHeight,d=b.querySelector("span.banLabel");c>40&&d.setAttribute("style","")}a++}return!0}return!1}function putBan(a){return!0}var wyborcza_pl=wyborcza_pl||{},gazeta_pl=gazeta_pl||{},_gaq=_gaq||[];this.dfpArrLab=["000-MAINBOARD","087-ADBOARD-A","087-ADBOARD-B","087-ADBOARD-C","087-ADBOARD-D","107-MAINBOARD-MOBI","150-ADBOARD-A-MOBI","150-ADBOARD-B-MOBI","150-ADBOARD-C-MOBI","150-ADBOARD-D-MOBI","003-RECTANGLE","035-RECTANGLE-BTF","042-FOOTBOARD","104-RECTANGLE-MOBI","108-FOOTBOARD-MOBI","ADBOARD-B","FOOTBOARDA"],document.addEventListener("DOMContentLoaded",function(a){setTimeout(function(){checkAdsElements()},1)}),wyborcza_pl.PlayerConfig={isMuted:!0},gazeta_pl.parseURI=function(){var a={},b=location.href.split(/\?/).pop().split(/&/),c=0;for(c=0;c<b.length;c++)b[c]=b[c].split(/=/),a[b[c].shift()]=b[c].join("=");return a},function(){var a=gazeta_pl.Player=gazeta_pl.Player||{resizeWrappers:["body","body > .main","#col_left","#col_right",".colLeft",".colRight"],jsonldMounted:0,init:function(){a.checkPageVisibilityPage(),a.initMessages(),a.initResizeHandler(),a.initShortcuts()},initMessages:function(){a.addEvent(window,"message",a.receiveMessage)},receiveMessage:function(b){var c=b.data;if(b.origin.match(/video\.gazeta\.pl$/)){try{c=JSON.parse(c)}catch(i){}if(c)switch(c.action){case"playerLoad":a.sendMessage(b.source,b.origin,{action:"playerInit",params:{mute:a.getMuteState()}}),a.resizeHandler();break;case"getAdParams":var d=window.AG&&window.AG.rodoAccepted||0,e={action:"setAdParams",referrer:document.referrer,rodoStatus:d};window.nobanner?e.nobanner=!0:"dfpParams"in window&&dfpParams&&dfpParams.video&&(e.url={preroll:dfpParams.video.preroll,postroll:dfpParams.video.postroll,inskin:dfpParams.video.skin,branding:dfpParams.video.skin,pausead:dfpParams.video.pausead,overlay:dfpParams.video.overlay}),a.sendMessage(b.source,b.origin,e);break;case"watchViewport":a.checkPlayerInViewport(b);break;case"resizeRequest":a.stretchSpace(b,c.width,c.height,function(c,d){a.sendMessage(b.source,b.origin,{action:"resizeDone",width:c,height:d})});break;case"hidePlayer":var f=a.findPlayerIframe(b);f&&(f.style.cssText+="display: none; visibility: hidden");break;case"getPlayerParams":var f=a.findPlayerIframe(b);a.sendMessage(b.source,b.origin,{action:"playerParams",pageUrl:document.URL,playerUrl:f?f.src:""});break;case"initFloatingPlayer":if(!window.jQuery)return;!a.FloatingPlayer.ready&&a.FloatingPlayer.isDesktopBrowser()&&!a.FloatingPlayer.isSafariBrowser()&&a.FloatingPlayer.isEmbededOnArticlePage()&&a.FloatingPlayer.init(),a.sendMessage(b.source,b.origin,{action:"setFloatingCapabilities",canFloat:!!a.FloatingPlayer.isFloatingPlayer(b.source)&&a.FloatingPlayer.ready});break;case"canPause":a.FloatingPlayer.ready&&a.FloatingPlayer.isFloatingPlayer(b.source)&&a.FloatingPlayer.closeButtonHandler(b.origin,c.canPauseState,c.playState);break;case"pushDataToDataLayer":a.pushDataToDataLayer(c.data);break;case"mediaInfo":if(!/\/1,|\/7,/.test(window.location.href)&&c.jsonld&&a.jsonldMounted<1){var g=document.querySelector("body"),h=document.createElement("script");h.setAttribute("type","application/ld+json"),h.innerHTML=JSON.stringify(c.jsonld),g.appendChild(h),a.jsonldMounted++}}}},playersInViewport:[],actualPlayed:null,sendMessage:function(a,b,c){a.postMessage(JSON.stringify(c),b)},stretchSpace:function(b,c,d,e){if(c){var f=a.findPlayerIframe(b);f&&(c&&(f.width=c),d&&(f.height=d),e(f.width,f.height))}},checkPlayerInViewport:function(b){if(b){new IntersectionObserver(function(c){c[0].intersectionRatio>=.5?a.enteredViewport(b):a.leavedViewport(b)},{threshold:[.5]}).observe(a.findPlayerIframe(b))}},checkPageVisibilityPage:function(){document.addEventListener("visibilitychange",function(){a.manageActivePlayer()})},manageActivePlayer:function(b,c){var d="visible"==document.visibilityState;if(!(!d||a.FloatingPlayer.ready&&a.actualPlayed)){var e=a.playersInViewport.length&&d?"enteredViewport":"leavedViewport";b=a.playersInViewport[0]?a.playersInViewport[0].source:b,c=a.playersInViewport[0]?a.playersInViewport[0].origin:c,b&&c&&(a.actualPlayed&&!gazeta_pl.playerStopsOutsideViewport&&a.actualPlayed[0]!=b&&a.sendMessage(a.actualPlayed[0],a.actualPlayed[1],{action:"leavedViewport"}),a.sendMessage(b,c,{action:e}),a.actualPlayed=[b,c])}},enteredViewport:function(b){b.origin,b.source;if(!gazeta_pl.playerStopsOutsideViewport&&!a.FloatingPlayer.ready)for(var c=0,d=a.playersInViewport.length;c<d;c++)a.sendMessage(a.playersInViewport[c].source,a.playersInViewport[c].origin,{action:"leavedViewport"});a.playersInViewport.push({source:b.source,origin:b.origin}),a.manageActivePlayer()},leavedViewport:function(b){for(var c=b.origin,d=b.source,e=(a.playersInViewport.length,a.playersInViewport.length-1);e>=0;e-=1)a.playersInViewport[e]&&a.playersInViewport[e].source==d&&a.playersInViewport.splice(e,1);(gazeta_pl.playerStopsOutsideViewport||a.playersInViewport.length)&&(a.FloatingPlayer.ready||a.manageActivePlayer(d,c))},initResizeHandler:function(){a.resizeHandler(),a.addEvent(window,"onorientationchange"in window?"orientationchange":"resize",a.resizeHandler)},resizeHandler:function(){clearTimeout(a.timeout),clearTimeout(a.fallbackTimeout),a.timeout=a.resizePlayer(50),a.fallbackTimeout=a.resizePlayer(500)},resizePlayer:function(b){var c=a.findPlayerIframes();return setTimeout(function(){for(var b=0;b<c.length;b++){var d=c[b],e=c[b].parentNode,f=980;e.getAttribute("data-pjs")&&(e.style.cssText="width: auto; height: auto",e=e.parentNode),f=Math.min(f,e.offsetWidth||f),a.resizeWrappers.forEach(function(a){document.querySelector(a)&&document.querySelector(a).querySelector('[src="'+d.src+'"]')&&(f=Math.min(f,document.querySelector(a).offsetWidth||f))}),f!=d.offsetWidth&&c[b].contentWindow.postMessage('{"action": "resize", "width": '+f+"}","*")}},b)},initShortcuts:function(){a.addEvent(window,"keydown",a.handleKeydown)},handleKeydown:function(b){var c="";b.altKey&&38==b.which?c="unmute":b.altKey&&40==b.which&&(c="mute"),c&&a.findPlayerIframes().forEach(function(b){a.sendMessage(b.contentWindow,"*",{action:c})})},addEvent:function(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,function(){c.call(a,window.event)})},findPlayerIframes:function(){return Array.prototype.filter.call(document.getElementsByTagName("iframe"),function(a){return a.src&&a.src.match(/www\.gazeta\.tv\/plej\/19,/)})},findPlayerIframe:function(b){for(var c=a.findPlayerIframes(),d=0,e=c.length;d<e;d++)if(c[d].contentWindow===b.source)return c[d];return null},pushDataToDataLayer:function(a){"undefined"!=typeof dataLayer&&dataLayer.push(a)},getMuteState:function(){return!!(gazeta_pl.playerMute||a.isAutoplayMutedVideo()||void 0!==wyborcza_pl&&wyborcza_pl.PlayerConfig&&wyborcza_pl.PlayerConfig.isMuted)},isAutoplayMutedVideo:function(){var a=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);return!!a&&parseInt(a[2],10)>=66},FloatingPlayer:{ready:!1,init:function(){var a,b,c,d,e,f,g,h=this;return a=$(window),b=$("body"),c=b.find("#gazeta_article_video"),c.length?(d=c.find('iframe[src*="//www.gazeta.tv/plej/19,"]'),e=b.height(),f={width:"30px",height:"30px",padding:"8.5px",position:"absolute",right:"0",top:"-40px",background:"#fff","border-radius":"3px","box-shadow":"0 0 20px 2px rgba(0, 0, 0, 0.3)","box-sizing":"border-box"},h.$closeButton=$('<div class="floatingCloseButton"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13"><polygon style="fill:#aaa;fill-rule:evenodd;clip-rule:evenodd;" points="12.9 1.6 11.4 0.2 6.5 5.1 1.5 0.2 0.1 1.6 5.1 6.5 0.1 11.5 1.5 12.9 6.5 7.9 11.4 12.9 12.9 11.5 7.9 6.5 "/></svg></div>'),h.$iframe=d,h.$iframeContainer=d.parent(),h.$mod=c,h.$window=a,h.$xIcon=h.$closeButton.find("polygon"),h.floated=!1,h.iframeHeight=d.height(),h.windowHeight=a.height(),h.$closeButton.css(f),e<=h.windowHeight+h.iframeHeight?h.ready=!1:(!h.floated&&h.isReadyToFloat()&&(h.floated=!0,h.floatPlayer()),h.$window.bind("scroll.floatingPlayer resize.floatingPlayer",function(a){g=!0}),h.intervalId=setInterval(function(){g&&(!h.floated&&h.isReadyToFloat()?(h.floated=!0,h.floatPlayer()):h.floated&&!h.isReadyToFloat()&&(h.floated=!1,h.floatPlayer()),g=!1)},100),h.ready=!0)):h.ready=!1},deinit:function(){var b=a.FloatingPlayer;clearInterval(b.intervalId),b.ready=!1,b.$window.unbind(".floatingPlayer"),b.floated=!1,b.floatPlayer()},floatPlayer:function(){var b,c,d,e,f,g,h,i,j=this,k=48,l=j.$iframe.width(),m=j.$mod[0].getBoundingClientRect(),n=j.$mod.height(),o=l/(j.$iframe.height()-k),p=j.$window.height(),q=j.$window.width();h=j.floated?300:document.getElementById("gazeta_article_video").offsetWidth,i=h/o+k,d={top:p-i-30-m.top-(j.floated?0:100),left:q-h-30-m.left},b={width:h,height:i},c={width:h,height:i,padding:"10px",position:"fixed",right:"20px",bottom:"20px",background:"#fff","border-radius":"3px","box-shadow":"0 0 20px 2px rgba(0, 0, 0, 0.3)","z-index":"2800"},e={width:h,height:i,position:"fixed",right:"30px",bottom:"30px","-webkit-transform":"translate(-"+d.left+"px,-"+d.top+"px)",transform:"translate(-"+d.left+"px,-"+d.top+"px)",transition:"all 0.2s linear","z-index":"2800"},f={width:h,height:i,position:"fixed",top:m.top,"-webkit-transform":"translate("+d.left+"px,"+d.top+"px)",transform:"translate("+d.left+"px,"+d.top+"px)",transition:"all 0.2s linear","z-index":"2800"},g={height:n,"background-color":"#f2f2f2","background-image":"url(//bi.im-g.pl/im/1/20781/m20781691,ZASLEPKA.png)","background-position":"bottom 30px right 30px","background-repeat":"no-repeat"},j.origin||(j.origin=window.location.protocol+"//video.gazeta.pl"),j.floated?(j.$mod.css(g),j.$iframe.css(f),j.$iframe.one("webkitTransitionEnd moztransitionend transitionend oTransitionEnd",function(a){j.$iframe.removeAttr("style"),j.$iframe.css(b),j.$iframeContainer.css(c),j.$iframeContainer.prepend(j.$closeButton)}),a.pushDataToDataLayer({category:"Plywajacy player",action:"Wyswietlenie",label:window.location.href,value:0,nonInteraction:!1,event:"zdarzenie"}),a.sendMessage(j.$iframe[0].contentWindow,j.origin,{action:"floatStatus",data:!0}),a.sendMessage(j.$iframe[0].contentWindow,j.origin,{action:"resize",width:300})):(j.$iframe.css(e),j.$iframeContainer.removeAttr("style"),j.$iframeContainer.find(".floatingCloseButton").detach(),j.$iframe.one("webkitTransitionEnd moztransitionend transitionend oTransitionEnd",function(a){j.$iframe.removeAttr("style"),j.$mod.removeAttr("style")}),a.sendMessage(j.$iframe[0].contentWindow,j.origin,{action:"floatStatus",data:!1}),a.sendMessage(j.$iframe[0].contentWindow,j.origin,{action:"resize",width:document.getElementById("gazeta_article_video").offsetWidth}))},closeFloatingPlayer:function(){var b=a.FloatingPlayer;if(void 0===b.$closeButton)return!1;a.sendMessage(b.$iframe[0].contentWindow,b.origin,{action:"callPause",data:"pause"}),b.deinit(),a.pushDataToDataLayer({category:"Plywajacy player",action:"Zamknij",label:window.location.href,value:0,nonInteraction:!1,event:"zdarzenie"})},closeButtonHandler:function(b,c,d){var e=a.FloatingPlayer;if(void 0===e.$closeButton)return!1;e.origin=b,e.playState=d,c||!c&&"play"!==d?(e.$xIcon.css("fill","#000"),e.$closeButton.css("cursor","pointer"),e.$closeButton.unbind("click",e.closeFloatingPlayer).bind("click",e.closeFloatingPlayer)):(e.$xIcon.css("fill","#aaa"),e.$closeButton.css("cursor","default"),e.$closeButton.unbind("click",e.closeFloatingPlayer))},isFloatingPlayer:function(b){var c=a.FloatingPlayer;return void 0!==c.$iframe&&c.$iframe[0].contentWindow===b},isReadyToFloat:function(){var b=a.FloatingPlayer;return b.$window.scrollTop()>b.$mod.offset().top+b.$mod.height()/4},isEmbededOnArticlePage:function(){return!!window.location.href.match(/\/[17],/)},isDesktopBrowser:function(){return void 0!==gazeta_pl.device?"NOT_MOBILE"==gazeta_pl.device:$("body").hasClass("desk")&&!$("body").hasClass("tablet")},isSafariBrowser:function(){return-1!=navigator.userAgent.indexOf("Safari")&&-1==navigator.userAgent.indexOf("Chrome")&&-1==navigator.userAgent.indexOf("Chromium")}}}}(),gazeta_pl.Player.init();</script>
+
+
+ <script type="text/javascript">var pfma_wyborcza_version = "1.1.16";</script>
+ <script type="text/javascript">var szpalty_wyborcza_version = "0.0.2";</script>
+ <!-- /head script -->
+
+
+
+<!-- preloaderModule v1.1 -->
+
+<script>
+ var now = new Date(2019, 0, 31, 17, 10, 30);
+</script>
+
+<!-- currentDateModule v1.0 -->
+
+
+<meta name="Description" content="Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. " />
+
+<!-- descriptionsModule v1.0 -->
+
+
+
+<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+
+
+ <script>
+ //<![CDATA[
+ var gazeta_pl = gazeta_pl || {};
+
+ gazeta_pl.documentParam = {"root": "/biznes/"};
+
+ gazeta_pl.mobileInfo = {
+ "isMobileDevice": false
+ };
+
+
+
+ gazeta_pl.rootSectionId = 100791;
+
+ //]]>
+ </script>
+
+
+<!-- Bigdata -->
+<script src="https://bis.gazeta.pl/info/bluewhale/2.5.0/main-min.jsgz"></script>
+
+
+
+<!-- portalDataModule v1.0 -->
+
+
+<!-- - Agree rd - -->
+
+
+
+<!-- v4 rdGid: 1 -->
+<!-- v4 Cookie : -->
+<script src="//rodo.agora.pl/agreement/check?gid=1&params=&skipMessageOnReject=false"></script>
+
+
+
+
+
+
+
+ <meta property="og:type" content="article" />
+
+
+
+
+<meta property="og:url" content="http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html" />
+
+
+
+<!-- Title -->
+<meta property="og:title"
+ content="Pomys na biznes: chusta, ktra chroni przed smogiem" />
+<meta name="twitter:title"
+ content="Pomys na biznes: chusta, ktra chroni przed smogiem" />
+
+
+ <!-- DESC -->
+ <meta property="og:description"
+ content="Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. " />
+ <meta name="twitter:description"
+ content="Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. " />
+
+
+
+
+
+
+
+
+
+ <!-- IMAGE -->
+ <meta property="og:image" content="https://bi.im-g.pl/im/f7/49/17/z24418295FBW,Prace-nad-projektem-chusty-antysmogowej-rozpoczely.jpg" />
+ <meta name="twitter:image" content="https://bi.im-g.pl/im/f7/49/17/z24418295FBW,Prace-nad-projektem-chusty-antysmogowej-rozpoczely.jpg" />
+ <meta name="twitter:card" content="summary_large_image" />
+
+
+
+ <meta property="og:site_name" content="wyborcza.biz" />
+
+
+
+ <meta property="fb:app_id" content="515714931781741" />
+
+
+
+
+<meta property="og:locale" content="pl_PL" />
+
+
+
+<!--facebookIdFanpages start -->
+<meta property="fb:pages" content="182216700504" />
+<meta property="fb:pages" content="63563473556" />
+<meta property="fb:pages" content="211137058789" />
+<meta property="fb:pages" content="158571784110" />
+<meta property="fb:pages" content="1123290301110277" />
+<meta property="fb:pages" content="110402292339268" />
+<meta property="fb:pages" content="228168274881" />
+<meta property="fb:pages" content="134754663619446" />
+<meta property="fb:pages" content="174480419336" />
+<meta property="fb:pages" content="116460718114" />
+<meta property="fb:pages" content="172897561400" />
+<meta property="fb:pages" content="297584282665" />
+<meta property="fb:pages" content="115721688487368" />
+<meta property="fb:pages" content="178164578911" />
+<meta property="fb:pages" content="336438736394843" />
+<meta property="fb:pages" content="145218379355757" />
+<meta property="fb:pages" content="217794324954867" />
+<meta property="fb:pages" content="145231052239934" />
+<meta property="fb:pages" content="259626760890136">
+<meta property="fb:pages" content="224446291000703" />
+<meta property="fb:pages" content="169554536536974" />
+<meta property="fb:pages" content="189371001102606" />
+<meta property="fb:pages" content="202936473194614" />
+<meta property="fb:pages" content="750649501650020" />
+<meta property="fb:pages" content="732718100095535" />
+<meta property="fb:pages" content="212900872097857" />
+<meta property="fb:pages" content="451218768269373" />
+<meta property="fb:pages" content="164720961114" />
+<meta property="fb:pages" content="1472966049586410" />
+<meta property="fb:pages" content="212665112154600" />
+<meta property="fb:pages" content="186699381425534" />
+<meta property="fb:pages" content="701673749899603" />
+<meta property="fb:pages" content="182114553798" />
+<meta property="fb:pages" content="1509388655954951" />
+<meta property="fb:pages" content="212912818721293" />
+<meta property="fb:pages" content="346589322185" />
+<meta property="fb:pages" content="149902472546" />
+<meta property="fb:pages" content="282927535056444" />
+<meta property="fb:pages" content="196338433729597" />
+<meta property="fb:pages" content="133385026676460" />
+<meta property="fb:pages" content="354565604586" />
+<meta property="fb:pages" content="277864075631246" />
+<meta property="fb:pages" content="175270922504122" />
+<meta property="fb:pages" content="211916968882547" />
+<meta property="fb:pages" content="127659060591519" />
+<meta property="fb:pages" content="217981241618571" />
+<meta property="fb:pages" content="198606720154883" />
+<meta property="fb:pages" content="105171796186333" />
+<meta property="fb:pages" content="1800655190193744" />
+<meta property="fb:pages" content="501324603247131" />
+<meta property="fb:pages" content="159338220786135" />
+<meta property="fb:pages" content="151677518189583" />
+<meta property="fb:pages" content="302385839814255" />
+<meta property="fb:pages" content="200768423330804" />
+<meta property="fb:pages" content="288018984602680" />
+<!--facebookIdFanpages end -->
+
+
+<!-- facebookModule v1.1 -->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <!-- (C)2000-2014 Gemius SA - gemiusAudience -->
+ <script type="text/javascript">
+ <!--//--><![CDATA[//><!--
+ var pp_gemius_identifier = new String('citFBk7kdQKEPMwqCmPRXqSc.IlGzFiuWwxH9NXy4Zb.a7/arg=147743');
+
+ function gemius_pending(i) { window[i] = window[i] || function() {var x = window[i+'_pdata'] = window[i+'_pdata'] || []; x[x.length]=arguments;};};
+ setTimeout(function() {
+ gemius_pending('gemius_hit'); gemius_pending('gemius_event'); gemius_pending('pp_gemius_hit'); gemius_pending('pp_gemius_event');
+ (function(d,t) {try {var gt=d.createElement(t),s=d.getElementsByTagName(t)[0],l=((location.protocol=='https:')?'https://bis.gazeta.pl/info/http/xgemius-min.jsgz':'http://static.gazeta.pl/info/http/xgemius-min.jsgz'); gt.setAttribute('async','async');
+ gt.setAttribute('defer','defer'); gt.src=l; s.parentNode.insertBefore(gt,s);} catch (e) {}})(document,'script');
+ }, 50);
+ //--><!]]>
+ </script>
+
+
+
+<!-- gemiusModule v1.1 -->
+
+
+
+<meta name="Keywords" content="Konsument i zakupy">
+
+ <meta name="news_keywords" content="Konsument i zakupy">
+
+
+<!-- keywordsModule v1.0 -->
+
+
+
+
+<meta name="google-site-verification" content="3P4BE3hLw82QWqtseIE60qQcOtrpMxMnCNkcv62pjTA" />
+
+
+
+ <!-- Google Tag Manager --> <noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-W4DZP5" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-W4DZP5');</script> <!-- End Google Tag Manager -->
+
+
+
+
+<!-- googleModule v1.0 -->
+
+
+
+
+
+
+ <meta name="viewport" content="width=device-width, user-scalable=yes">
+
+
+
+<!-- viewportModule v1.0 -->
+
+
+
+
+
+
+
+ <!-- wyborcza_common_style not set -->
+
+
+ <link rel="stylesheet" href="//static.im-g.pl/css/wyborcza-common/builds/18.0.6/style-min.cssgz" />
+
+
+
+
+
+
+<!-- wyborcza_common_style /css/wyborcza-biz/version-mobile.htm -->
+<!-- wyborcza_common_style /css/wyborcza-biz/version-desktop.htm -->
+
+
+ <link rel="stylesheet" href="//static.im-g.pl/wyborcza/wyborcza-biz/css/1.0.9/style-min.cssgz" />
+
+
+
+
+<!-- stylesModule v1.0 -->
+
+
+
+
+ <link rel="alternate" type="application/rss+xml" title="Wiadomoci gospodarcze" href="http://wyborcza.biz/pub/rss/wyborcza_biz_wiadomosci.htm">
+
+
+<!-- rssModule v1.0 -->
+
+
+<!-- Facebook Pixel Code -->
+<script>
+ !function(f, b, e, v, n, t, s) {
+ if (f.fbq)
+ return;
+ n = f.fbq = function() {
+ n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments)
+ };
+ if (!f._fbq)
+ f._fbq = n;
+ n.push = n;
+ n.loaded = !0;
+ n.version = '2.0';
+ n.queue = [];
+ t = b.createElement(e);
+ t.async = !0;
+ t.src = v;
+ s = b.getElementsByTagName(e)[0];
+ s.parentNode.insertBefore(t, s)
+ } (window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
+ fbq('init', '534530363346991'); // Insert your pixel ID here.
+ fbq('track', 'PageView');
+</script>
+
+<noscript>
+ <img height="1" width="1" style="display: none" src="https://www.facebook.com/tr?id=534530363346991&ev=PageView&noscript=1" />
+</noscript>
+
+<!-- DO NOT MODIFY -->
+<!-- End Facebook Pixel Code -->
+
+<!-- facebookPixelModule v1.0 -->
+
+
+<script>
+ var wyborcza_pl = wyborcza_pl || {};
+ wyborcza_pl.LokaleGeolocationCityId = '';
+</script>
+
+<!-- geolocationModule v1.0 -->
+
+
+
+
+
+<!-- browserNotificationEnableModule -->
+
+
+
+
+
+
+
+ <script type="text/javascript">
+ var _sf_async_config = _sf_async_config || {};
+ _sf_async_config.uid = 14371;
+ _sf_async_config.domain = 'wyborcza.biz';
+ _sf_async_config.flickerControl = false;
+
+
+
+ _sf_async_config.useCanonical = true;
+
+
+
+
+ var _sf_startpt = (new Date()).getTime();
+ </script>
+
+ <script async src="//static.chartbeat.com/js/chartbeat_mab.js"></script>
+
+
+<!-- chartbeatHeadModule v1.0 -->
+
+<!--10184895, [ null ], aggregatorModule-->
+
+
+
+
+
+
+
+
+
+
+
+
+<script type='text/javascript'>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ if(!window.AG){
+ window.AG = window.AG || {};
+ window.AG.rodoAccepted = -1;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ var
+ adviewDFP = adviewDFP || {};
+adviewDFP.DFPTargeting = [];
+
+
+
+
+
+ // [HB] Core-prod-1 - HEADER_START
+var PREBID_TIMEOUT = (window.AG && window.AG.rodoAccepted === 1) ? 500 : 0;
+var pbjs_currency = 1; // -> to $1
+var pbjs_ga = true;
+var pbjs_yb_hb = false;
+var eur2usd = 1.1;
+var yb_dosamplerate = 100;
+var yb_seg = '';
+var _st0=new Date();
+
+// BIG data
+BigData = {
+ notify: function(eventName){
+ var event = BigData.createEvent(eventName);
+ BigData.fireEvent(event);
+ },
+ createEvent: function(eventName) {
+ var event;
+ if (document.createEvent) {
+ event = document.createEvent("HTMLEvents");
+ event.initEvent(eventName, true, true);
+ } else {
+ event = document.createEventObject();
+ event.eventType = eventName;
+ }
+ event.eventName = eventName;
+ return event;
+ },
+ fireEvent: function(event) {
+ if (document.createEvent) {
+ document.dispatchEvent(event);
+ } else {
+ document.fireEvent("on" + event.eventType, event);
+ }
+ }
+};
+//bwGuidv2
+function getCookie(cname) {
+ var name = cname + "=";
+ var ca = document.cookie.split(';');
+ for(var i=0; i<ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0)==' ') c = c.substring(1);
+ if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
+ }
+ return "";
+}
+function getJSON (url, callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("get", url, true);
+ xhr.responseType = "json";
+ xhr.onload = function() {
+ var status = xhr.status;
+ if (status == 200) {
+ callback(null, xhr.response);
+ } else {
+ callback(status);
+ }
+ };
+ xhr.send();
+};
+//*****************************************
+
+if(Math.random()*100 <= yb_dosamplerate ) {
+ var bwGuidv2 = getCookie("bwGuidv2");
+ if (bwGuidv2 != "") {
+ getJSON("https://squid.gazeta.pl/do/p/v1/gpfgo?g=" + bwGuidv2 + "&p=30000",
+ function(err, data) {
+ if (err != null) {
+ console.log("getJSON: Something went wrong: " + err);
+ } else {
+ try{
+ var segments = [];
+ for(index in data.profile.features){
+ segments.push(String(index));
+ }
+ yb_seg = segments;
+ }catch(e){}
+ }
+ });
+ }
+}
+document.write('\x3Cscript type="text/javascript" src="https://bi.adview.pl/static/adview/front/master/dfp/tools/adview/prebid.js">\x3C/script>');
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ // 20181218
+
+var adUnits = [{
+ //wyborcza.biz_001-TOPBOARD
+ code: 'div-gpt-ad-001-TOPBOARD-0',
+ sizes: [[970,250]],
+ bids: [{
+ bidder: 'appnexus',
+ params: { placementId: '11549538' }
+ },{
+ bidder: 'adform',
+ params: { mid: '427595' }
+ },{
+ bidder: 'pubmatic',
+ params: { publisherId: '155949', adSlot: 'wyborcza.biz_001-TOPBOARD@970x250' }
+ },{
+ bidder: 'ix',
+ params: { size: [970,250], siteId: '227220' }
+ },{
+ bidder: 'rtbhouse',
+ params: { region: 'prebid-eu', publisherId: 'da39a3ee5e6b4b0d' }
+ },{
+ bidder: 'connectad',
+ params: { networkId: '10047', siteId: '1013785' }
+ }]
+},{
+ //wyborcza.biz_003-RECTANGLE
+ code: 'div-gpt-ad-003-RECTANGLE-0',
+ sizes: [[300,600]],
+ bids: [{
+ bidder: 'appnexus',
+ params: { placementId: '11549537' }
+ },{
+ bidder: 'adform',
+ params: { mid: '427594' }
+ },{
+ bidder: 'pubmatic',
+ params: { publisherId: '155949', adSlot: 'wyborcza.biz_003-RECTANGLE@300x600' }
+ },{
+ bidder: 'ix',
+ params: { size: [300,600], siteId: '227219' }
+ },{
+ bidder: 'rtbhouse',
+ params: { region: 'prebid-eu', publisherId: 'da39a3ee5e6b4b0d' }
+ },{
+ bidder: 'connectad',
+ params: { networkId: '10047', siteId: '1013784' }
+ }]
+}];
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ // [HB] Core-prod-2 - OVER_GT
+if(pbjs_yb_hb){
+ var yb_hb = (pbjs_yb_hb ? Math.round(Math.random()) : 1);
+ PREBID_TIMEOUT *= yb_hb;
+ }
+ var pbjs = pbjs || {};
+ pbjs.que = pbjs.que || [];
+ pbjs.que.push(function() {
+ for (var i = 0; i < adUnits.length; i++){shuffle(adUnits[i].bids);}
+ if(PREBID_TIMEOUT>0) pbjs.addAdUnits(adUnits);
+
+ /* pass consent strings - START */
+
+ getCookie = function (name) {
+ var value = "; " + document.cookie;
+ var parts = value.split("; " + name + "=");
+ if (parts.length == 2) return parts.pop().split(";").shift();
+ }
+
+ pbjs.setConfig({
+ consentManagement: {
+ cmpApi: 'static',
+ allowAuctionWithoutConsent: true,
+ consentData: {
+ getConsentData: {
+ 'gdprApplies': false,
+ 'hasGlobalScope': false,
+ 'consentData': getCookie('euconsent')
+ },
+ getVendorConsents: {
+ 'metadata': getCookie('euconsent')
+ }
+ }
+ }
+ });
+ /* pass consent string - END */
+
+ pbjs.requestBids({
+ bidsBackHandler: function(bidResponses) {
+ pbjs.dataPrebidReady=true;
+ BigData.notify("data-prebid-ready");
+ }
+ });
+ pbjs.bidderSettings = {
+ standard: {
+ adserverTargeting: [{
+ key: "hb_bidder", val: function(bidResponse) { return bidResponse.bidderCode; }},{
+ key: "hb_adid", val: function(bidResponse) { return bidResponse.adId; }},{
+ key: "hb_pb", val: function(bidResponse) { return (bidResponse.pbHg*pbjs_currency).toFixed(2); }},{
+ key: "hb_size", val: function(bidResponse) { return bidResponse.size; }
+ }]
+ },
+ audienceNetwork: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.75); }},
+ districtmDMX: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.85); }},
+ aol: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.8); }},
+ adform: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.9)*eur2usd; }},
+ rubicon: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.82)*eur2usd; }},
+ smartadserver: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.9); }},
+ pubmatic: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.9); }},
+ appnexusAst: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.65); }}
+ };
+ if(pbjs_ga) {
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+ })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+ ga('create', 'UA-84607356-7', 'auto', 'yb', {'sampleRate': 0.1});
+ pbjs.enableAnalytics({
+ provider: 'ga',
+ options: {
+ global: 'ga', // String name of GA global. Default is 'ga'
+ enableDistribution: false,
+ trackerName: 'yb'
+ }
+ });
+ }
+ });
+ function shuffle(a) {
+ var i = 0, j = 0, temp = null;
+ for (i = a.length-1; i > 0; i--) {
+ j = Math.floor(Math.random() * (i + 1));
+ temp = a[i];
+ a[i] = a[j];
+ a[j] = temp;
+ }
+ }
+
+var lazyGPT = true;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ var googletag = googletag || {};
+ googletag.cmd = googletag.cmd || [];
+ var lazyGPT = lazyGPT || false;
+
+ if(!lazyGPT) {
+
+ (function() {
+ var useSSL = 'https:' == document.location.protocol;
+ var src = (useSSL ? 'https:' : 'http:') +
+ '//www.googletagservices.com/tag/js/gpt.js';
+ document.write('<scr' + 'ipt src="' + src + '"></scr' + 'ipt>');
+ })();
+ }
+
+ var dfpParams = dfpParams || {};
+ dfpParams.video = dfpParams.video || {};
+ dfpParams.slots = dfpParams.slots || {};
+ dfpParams.jsp = 23;
+ dfpParams.dir = 'biznes';
+ dfpParams.dx = '147743';
+
+ dfpParams.prefix = '/75224259/AGORA-IN/Wyborcza.biz';
+
+
+ dfpParams.video.preroll = '//pubads.g.doubleclick.net/gampad/ads?sz=400x300|640x480&iu=/75224259/AGORA-IN/Wyborcza.biz/090-PREROLL&cust_params=pos%3D090-PREROLL%26dx%3D147743%26jsp%3D23%26dir%3Dbiznes%26kw%3D[brandsafe]%26dystrybutor%3D[distributor_id]%26passback_id%3D[passback_id]%26domena%3D[adview_hostname]%26cb%3D[cb]&url=[locationhref]&description_url=[locationhref]&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&correlator=[timestamp]';
+
+ dfpParams.video.skin = '//pubads.g.doubleclick.net/gampad/ads?sz=400x300|640x480&iu=/75224259/AGORA-IN/Wyborcza.biz/120-SKIN&cust_params=pos%3D120-SKIN%26dx%3D147743%26jsp%3D23%26dir%3Dbiznes%26kw%3D[brandsafe]%26dystrybutor%3D[distributor_id]%26passback_id%3D[passback_id]%26domena%3D[adview_hostname]%26cb%3D[cb]&url=[locationhref]&description_url=[locationhref]&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&correlator=[timestamp]';
+
+ dfpParams.video.overlay = '//pubads.g.doubleclick.net/gampad/ads?sz=400x300|640x480&iu=/75224259/AGORA-IN/Wyborcza.biz/123-OVERLAY&cust_params=pos%3D123-OVERLAY%26dx%3D147743%26jsp%3D23%26dir%3Dbiznes%26kw%3D[brandsafe]%26dystrybutor%3D[distributor_id]%26passback_id%3D[passback_id]%26domena%3D[adview_hostname]%26cb%3D[cb]&url=[locationhref]&description_url=[locationhref]&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&correlator=[timestamp]';
+
+ dfpParams.video.pausead = '//pubads.g.doubleclick.net/gampad/ads?sz=400x300|640x480&iu=/75224259/AGORA-IN/Wyborcza.biz/126-PAUSEAD&cust_params=pos%3D126-PAUSEAD%26dx%3D147743%26jsp%3D23%26dir%3Dbiznes%26kw%3D[brandsafe]%26dystrybutor%3D[distributor_id]%26passback_id%3D[passback_id]%26domena%3D[adview_hostname]%26cb%3D[cb]&url=[locationhref]&description_url=[locationhref]&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&correlator=[timestamp]';
+
+
+
+
+ dfpParams.slots['021-IMK'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/021-IMK&adUnitCode=021-IMK&adUnitSize=[[300,90],'fluid']&dx=147743&dir=biznes&jsp=23", sizes: [[300,90],'fluid'], autoLoad: false, autoLoadMargin: 0 };
+
+ dfpParams.slots['003-RECTANGLE'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/003-RECTANGLE&adUnitCode=003-RECTANGLE&adUnitSize=[[300,250],[300,600],[160,600],[120,600]]&dx=147743&dir=biznes&jsp=23", sizes: [[300,250],[300,600],[160,600],[120,600]], autoLoad: false, autoLoadMargin: 200 };
+
+ dfpParams.slots['007-CONTENTBOARD'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/007-CONTENTBOARD&adUnitCode=007-CONTENTBOARD&adUnitSize=[[300,250],[320,250],[336,280],[620,200],'fluid']&dx=147743&dir=biznes&jsp=23", sizes: [[300,250],[320,250],[336,280],[620,200],'fluid'], autoLoad: false, autoLoadMargin: 200 };
+
+ dfpParams.slots['071-WINIETA'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/071-WINIETA&adUnitCode=071-WINIETA&adUnitSize=[[940,140],[940,70]]&dx=147743&dir=biznes&jsp=23", sizes: [[940,140],[940,70]], autoLoad: false, autoLoadMargin: 200 };
+
+ dfpParams.slots['035-RECTANGLE-BTF'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/035-RECTANGLE-BTF&adUnitCode=035-RECTANGLE-BTF&adUnitSize=[[300,250]]&dx=147743&dir=biznes&jsp=23", sizes: [[300,250]], autoLoad: false, autoLoadMargin: 0 };
+
+ dfpParams.slots['067-RECTANGLE-BTF'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/067-RECTANGLE-BTF&adUnitCode=067-RECTANGLE-BTF&adUnitSize=[[300,250]]&dx=147743&dir=biznes&jsp=23", sizes: [[300,250]], autoLoad: false, autoLoadMargin: 0 };
+
+ dfpParams.slots['091-RELATED'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/091-RELATED&adUnitCode=091-RELATED&adUnitSize=[[155,290],'fluid']&dx=147743&dir=biznes&jsp=23", sizes: [[155,290],'fluid'], autoLoad: false, autoLoadMargin: 200 };
+
+ dfpParams.slots['019-TOPLAYER'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/019-TOPLAYER&adUnitCode=019-TOPLAYER&adUnitSize=[]&dx=147743&dir=biznes&jsp=23", sizes: [], autoLoad: false, autoLoadMargin: 200 };
+
+ dfpParams.slots['001-TOPBOARD'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/001-TOPBOARD&adUnitCode=001-TOPBOARD&adUnitSize=[[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[1170,300]]&dx=147743&dir=biznes&jsp=23", sizes: [[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[1170,300]], autoLoad: false, autoLoadMargin: 200 };
+
+ dfpParams.slots['042-FOOTBOARD'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/042-FOOTBOARD&adUnitCode=042-FOOTBOARD&adUnitSize=[[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[940,1200]]&dx=147743&dir=biznes&jsp=23", sizes: [[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[940,1200]], autoLoad: true, autoLoadMargin: 20 };
+
+
+
+
+</script>
+
+<!-- IBA Analytics -->
+<!-- 20180116 -->
+<script type="text/javascript">
+ var IBA=IBA||{};IBA.dfp={adUnits:[],rendered:[],exmplUrl:null,onSlotRendered:function(t){null===IBA.dfp.exmplUrl&&(IBA.dfp.exmplUrl=t.slot.ba);var e=t.slot.getAdUnitPath();IBA.dfp.adUnits.push(e),t.isEmpty===!1&&IBA.dfp.rendered.push(e),IBA.adb.update(),"4"==IBA.adb.status&&IBA.adb.onReady()},update:function(){var t=[],e=[];if("undefined"!=typeof googletag&&"function"==typeof googletag.pubads&&"undefined"!=typeof googletag.pubads().getSlots)for(var a=googletag.pubads().getSlots(),n=0;n<a.length;n++)null!==a[n].J&&e.push(a[n].getAdUnitPath()),t.push(a[n].getAdUnitPath());e.length>IBA.dfp.rendered.length&&(IBA.dfp.rendered=e),t.length>IBA.dfp.adUnits.length&&(IBA.dfp.adUnits=t),IBA.adb.onReady()},testUrlAndDispatch:function(){var t=!1,e=new XMLHttpRequest;if(e.open("HEAD",IBA.dfp.exmplUrl,!0),e.onreadystatechange=function(){console.log(e.readyState+" | status| "+e.status),0===e.status&&(IBA.adb.status="5"),IBA.adb.dispatch()},IBA.dfp.exmplUrl)try{e.send(),t=!0}catch(a){IBA.adb.status="5",IBA.adb.dispatch()}else console.warn("IBA exmplUrl"+IBA.dfp.exmplUrl)}},IBA.adb={states:{0:"Not available",1:"Partially available but not rendered",2:"Partially available at least one rendered",3:"Available but not rendered",4:"Available at least one rendered",5:"Not available but lib loaded",9:"Default : unknown"},gem:{0:"AfHgvarrP4PC7igzOLWh7dVpTGP109BuRIffksuYjfT.c7",1:"ofhA3bsKT7xY8Cgj4cuRX8cQ.qiY_fsP1VURxgCyYv3.F7",2:"ofhA3bsKT7xY8Cgj4cuRX8cQ.qiY_fsP1VURxgCyYv3.F7",3:"nF5K4QQDK9TNGw_ql.fdc9U7XrD1irt.GoVhWXaFu6b.Z7",4:"nF5K4QQDK9TNGw_ql.fdc9U7XrD1irt.GoVhWXaFu6b.Z7",5:"ofhA3bsKT7xY8Cgj4cuRX8cQ.qiY_fsP1VURxgCyYv3.F7",9:"__unknown__"},status:"9",update:function(){var t=IBA.dfp.adUnits.length,e=IBA.dfp.rendered.length,a=window.abp||!1;0===t?IBA.adb.status="0":0===e&&a===!0?IBA.adb.status="1":e>0&&a===!0?IBA.adb.status="2":0===e?IBA.adb.status="3":e>0&&(IBA.adb.status="4")},onReady:function(){IBA.adb.ready!==!0&&(IBA.adb.ready=!0,IBA.adb.update(),"3"==IBA.adb.status?IBA.dfp.testUrlAndDispatch():IBA.adb.dispatch())},ready:!1,dispatch:function(){var t=IBA.adb.status;window.gemius_identifier=IBA.adb.gem[t];var e=document.createElement("script"),a=document.getElementsByTagName("script")[0];e.onerror=function(t){},e.src="//gazeta.hit.gemius.pl/gemius.js",a.parentNode.insertBefore(e,a),IBA.BigData.notify("adblockdataready")}},IBA.BigData={notify:function(t){var e=IBA.BigData.createEvent(t);IBA.BigData.fireEvent(e)},createEvent:function(t){var e;return document.createEvent?(e=document.createEvent("HTMLEvents"),e.initEvent(t,!0,!0)):(e=document.createEventObject(),e.eventType=t),e.eventName=t,e},fireEvent:function(t){document.createEvent?document.dispatchEvent(t):document.fireEvent("on"+t.eventType,t)}},setTimeout(function(){try{"complete"===document.readyState?IBA.dfp.update():window.addEventListener("load",IBA.dfp.update,!1)}catch(t){}},20);
+</script>
+
+
+
+
+
+
+
+
+ <!-- eyeoH -->
+<!-- eyeoHeadScripts 1.0 -->
+<script type='text/javascript'>var abp;</script>
+<script type='text/javascript' src='/aliasy/adp/px.js?ch=1'></script>
+<script type='text/javascript' src='/aliasy/adp/px.js?ch=2'></script>
+<!-- /eyeoH -->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <script type='text/javascript'>
+ googletag.cmd.push(function() {
+
+ var mobileSizeMap = googletag.sizeMapping()
+ .addSize([336,0], [[1,1],[300,250],[320,250],[300,150],[320,150],[336,280],[267,310],[155,290],[320,100],[320,50],[320,125],[320,266],[320,300],[200,130],[300,600], 'fluid'])
+ .addSize([0,0], [[1,1],[300,250],[320,250],[300,150],[320,150],[267,310],[155,290],[320,100],[320,50],[320,125],[320,266],[320,300],[200,130],[300,600], 'fluid'])
+ .build();
+
+
+
+
+ var mobileSizeMap_101_topboard_mobi = googletag.sizeMapping().addSize([336,0], [[300,250],[320,250],[300,150],[320,150],[336,280]]).addSize([0,0], [[300,250],[320,250],[300,150],[320,150]]).build();
+
+ var mobileSizeMap_104_rectangle_mobi = googletag.sizeMapping().addSize([336,0], [[300,250],[320,250],[300,150],[320,150],[336,280],[300,600],'fluid']).addSize([0,0], [[300,250],[320,250],[300,150],[320,150],[300,600],'fluid']).build();
+
+ var mobileSizeMap_108_footboard_mobi = googletag.sizeMapping().addSize([336,0], [[300,250],[320,250],[300,150],[320,150],[336,280]]).addSize([0,0], [[300,250],[320,250],[300,150],[320,150]]).build();
+
+ var mobileSizeMap_091_related = googletag.sizeMapping().addSize([336,0], [[155,290],'fluid']).addSize([0,0], [[155,290],'fluid']).build();
+
+ var mobileSizeMap_150_adboard_a_mobi = googletag.sizeMapping().addSize([336,0], [[300,250],[320,250],[300,150],[320,150],[336,280]]).addSize([0,0], [[300,250],[320,250],[300,150],[320,150]]).build();
+
+ var mobileSizeMap_019_toplayer_mobi = googletag.sizeMapping().addSize([336,0], []).addSize([0,0], []).build();
+
+
+
+
+ googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/001-TOPBOARD', [[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[1170,300]], 'div-gpt-ad-001-TOPBOARD-0').setTargeting('pos', ['001-TOPBOARD']).addService(googletag.pubads());
+googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/003-RECTANGLE', [[300,250],[300,600],[160,600],[120,600]], 'div-gpt-ad-003-RECTANGLE-0').setTargeting('pos', ['003-RECTANGLE']).addService(googletag.pubads());
+googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/067-RECTANGLE-BTF', [[300,250]], 'div-gpt-ad-067-RECTANGLE-BTF-0').setTargeting('pos', ['067-RECTANGLE-BTF']).addService(googletag.pubads());
+googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/071-WINIETA', [[940,140],[940,70]], 'div-gpt-ad-071-WINIETA-0').setTargeting('pos', ['071-WINIETA']).addService(googletag.pubads());
+googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/007-CONTENTBOARD', [[300,250],[320,250],[336,280],[620,200],'fluid'], 'div-gpt-ad-007-CONTENTBOARD-0').setTargeting('pos', ['007-CONTENTBOARD']).addService(googletag.pubads());
+googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/035-RECTANGLE-BTF', [[300,250]], 'div-gpt-ad-035-RECTANGLE-BTF-0').setTargeting('pos', ['035-RECTANGLE-BTF']).addService(googletag.pubads());
+
+
+ googletag.pubads().setTargeting('dx','147743');
+
+ googletag.pubads().setTargeting('dir','biznes');
+
+ googletag.pubads().setTargeting('jsp','23');
+
+ googletag.pubads().setTargeting('cb', [''+window.AG.rodoAccepted]);
+ googletag.pubads().setTargeting('uponit','false');
+
+
+
+
+
+
+
+
+ var _YB=_YB||{ab:function(){return (Math.random()>=0.1?"b":"a"+Math.floor(Math.random()*10));}};
+ googletag.pubads().getSlots().forEach(function(slot){slot.setTargeting('yb_ab', _YB.ab());});
+
+
+
+ var
+ cookieIfr = null;
+if (document.referrer) {
+
+ var
+ url_domain = function(data) {
+ var a = document.createElement('a');
+ a.href = data;
+ return a.protocol + '//' + a.hostname;
+ };
+
+ var xhttp = new XMLHttpRequest();
+ xhttp.onreadystatechange = function() {
+ if (this.readyState == 4 && this.status == 200) {
+ cookieIfr = JSON.parse(this.responseText).dfpreferrer;
+ }
+ };
+ xhttp.withCredentials = true;
+ xhttp.open('GET', '//dfp.gazeta.pl/c.servlet?cn=dfpreferrer', false);
+ try {
+ xhttp.send();
+ } catch (ex) {
+ console.info('Error xhttp DFP');
+ }
+}
+
+function _setCookieDFP(name) {
+ var xhttp = new XMLHttpRequest();
+ xhttp.withCredentials = true;
+ xhttp.open('GET', '//dfp.gazeta.pl/c.servlet?cn=dfpreferrer&cv=' + name, true);
+ try {
+ xhttp.send();
+ } catch (ex) {
+ console.info('Error xhttp DFP');
+ }
+}
+
+function _getCookieValueDFP(a) {
+ var b = document.cookie.match('(^|;)\\s*' + a + '\\s*=\\s*([^;]+)');
+ return b ? b.pop() : '';
+}
+
+
+
+
+// 20180410
+var hashArrDFP = ['BoxBizCzol1','BoxBizImg1','BoxBizImg2','BoxBizImg3','BoxBizLink','BoxBizLinkImg','BoxBizMT','BoxBizNav','BoxGWImg','BoxHor','BoxMTPromoImg','BoxMTPromo','BoxKultIko','BoxKultImg1','BoxKultImg2','BoxKultImg3','BoxKultLink','BoxKultLinkImg','BoxKultNav','BoxKultNavLink','BoxLoBbImg1','BoxLoBbImg2','BoxLoBbImg3','BoxLoBbImg4','BoxLoBbLink','BoxLoBbMT','BoxLoBbNavLink','BoxLoBiImg1','BoxLoBiImg2','BoxLoBiImg3','BoxLoBiImg4','BoxLoBiLink','BoxLoBiMT','BoxLoByImg1','BoxLoByImg2','BoxLoByImg3','BoxLoByImg4','BoxLoByLink','BoxLoByMT','BoxLoByNavLink','BoxLoCzImg1','BoxLoCzImg2','BoxLoCzImg3','BoxLoCzImg4','BoxLoCzNav','BoxLoCzLink','BoxLoCzMT','BoxLoCzNavLink','BoxLoGLImg1','BoxLoGLImg2','BoxLoGLImg3','BoxLoGLImg4','BoxLoGLLink','BoxLoGLMT','BoxLoGLNavLink','BoxLoGwImg1','BoxLoGwImg2','BoxLoGwImg3','BoxLoGwImg4','BoxLoGwLink','BoxLoGwMT','BoxLoKaImg1','BoxLoKaImg2','BoxLoKaImg3','BoxLoKaImg4','BoxLoKaLink','BoxLoKaMT','BoxLoKaNavLink','BoxLoKiImg1','BoxLoKiImg2','BoxLoKiImg3','BoxLoKiImg4','BoxLoKiLink','BoxLoKiMT','BoxLoKiNavLink','BoxLoKrImg1','BoxLoKrImg2','BoxLoKrImg3','BoxLoKrImg4','BoxLoKrLink','BoxLoKrMT','BoxLoKrNavLink','BoxLoLoImg1','BoxLoLoImg2','BoxLoLoImg3','BoxLoLoImg4','BoxLoLoLink','BoxLoLoMT','BoxLoLoNavLink','BoxLoLuImg1','BoxLoLuImg2','BoxLoLuImg3','BoxLoLuImg4','BoxLoLuLink','BoxLoLuMT','BoxLoLuNavLink','BoxLoOlsztynImg1','BoxLoOlsztynImg2','BoxLoOlsztynImg3','BoxLoOlsztynImg4','BoxLoOlsztynLink','BoxLoOlsztynMT','BoxLoOlsztynNavLink','BoxLoOlsztynNav','BoxLoOpImg1','BoxLoOpImg2','BoxLoOpImg3','BoxLoOpImg4','BoxLoOpLink','BoxLoOpMT','BoxLoOpNavLink','BoxLoPlImg1','BoxLoPlImg2','BoxLoPlImg3','BoxLoPlImg4','BoxLoPlLink','BoxLoPlMT','BoxLoPoImg1','BoxLoPoImg2','BoxLoPoImg3','BoxLoPoImg4','BoxLoPoLink','BoxLoPoMT','BoxLoPoNavLink','BoxLoRaImg1','BoxLoRaImg2','BoxLoRaImg3','BoxLoRaImg4','BoxLoRaLink','BoxLoRaMT','BoxLoRaNavLink','BoxLoRzImg2','BoxLoRzImg3','BoxLoRzImg4','BoxLoRzLink','BoxLoRzNavLink','BoxLoSoImg1','BoxLoSoImg2','BoxLoSoImg3','BoxLoSoImg4','BoxLoSoLink','BoxLoSoMT','BoxLoSoNavLink','BoxLoSzImg1','BoxLoSzImg2','BoxLoSzImg3','BoxLoSzImg4','BoxLoSzLink','BoxLoSzMT','BoxLoSzNavLink','BoxLoTrImg1','BoxLoTrImg2','BoxLoTrImg3','BoxLoTrImg4','BoxLoTrLink','BoxLoTrMT','BoxLoTrNavLink','BoxLoTrNav','BoxLoWaImg1','BoxLoWaImg2','BoxLoWaImg3','BoxLoWaImg4','BoxLoWaLink','BoxLoWaMT','BoxLoWaNav','BoxLoWrImg1','BoxLoWrImg2','BoxLoWrImg3','BoxLoWrImg4','BoxLoWrLink','BoxLoWrMT','BoxLoWrNavLink','BoxLoZgImg1','BoxLoZgImg2','BoxLoZgImg3','BoxLoZgImg4','BoxLoZgLink','BoxLoZgMT','BoxLoZgNavLink','BoxLSCzol1','BoxLSCzol2','BoxMotMT','BoxMotImg1','BoxMotImg2','BoxMotImg3','BoxMotImg4','BoxMotLink','BoxMotLinkImg','BoxMotNav','BoxLSFCZOL1','BoxLSFImg1','BoxLSFImg2','BoxLSFImg3','BoxLSFImg4','BoxLSFLink','BoxLSFMT','BoxLSImg1','BoxLSImg2','BoxLSLink','BoxLSLinkImg','BoxLSMT','BoxLSNav','ECON','BoxNewestNav','BoxNewsImg1','BoxNewsImg2','BoxNewsImg3','BoxNewsImg4','BoxNewsLink','BoxNewsLinkImg','BoxNewsLinkMore','BoxNewsMT','BoxNewsNav','BoxNewsNavLink','BoxNPrzeCzol1','BoxNPrzeCzol10','BoxNPrzeCzol11','BoxNPrzeCzol12','BoxNPrzeCzol13','BoxNPrzeCzol14','BoxNPrzeCzol15','BoxNPrzeCzol16','BoxNPrzeCzol17','BoxNPrzeCzol18','BoxNPrzeCzol19','BoxNPrzeCzol2','BoxNPrzeCzol20','BoxNPrzeCzol21','BoxNPrzeCzol22','BoxNPrzeCzol23','BoxNPrzeCzol24','BoxNPrzeCzol25','BoxNPrzeCzol26','BoxNPrzeCzol27','BoxNPrzeCzol3','BoxNPrzeCzol4','BoxNPrzeCzol5','BoxNPrzeCzol6','BoxNPrzeCzol7','BoxNPrzeCzol8','BoxNPrzeCzol9','BoxOpCzol1','BoxOpCzol2','BoxOpCzol3','BoxOpImg1','BoxOpImg2','BoxOpImg3','BoxOpImg4','BoxOpImg5','BoxOpImg6','BoxOpImg7','BoxOpImg8','BoxOpImg9','BoxOpImg10','BoxOpImg11','BoxOpLink','BoxOpMT','BoxPloCzol1','BoxPloImg1','BoxPloImg2','BoxPloImg3','BoxPloLink','BoxPloLinkImg','BoxPloMT','BoxPloNavLink','BoxRozCzol1','BoxRozImg1','BoxRozImg2','BoxRozImg3','BoxRozImg4','BoxRozMT','BoxRozNavLink','BoxSportImg1','BoxSportImg2','BoxSportImg3','BoxSportLink','BoxSportLinkImg','BoxSportMT','BoxSportNav','BoxWyboImg1','BoxWyboImg2','BoxWyboImg3','BoxWyboImg4','BoxWyboLink','BoxWyboLinkImg','BoxWyboMT','BoxWyboNav','NavMoreSeo','PublioBoxEcom1','PublioBoxEcom2','PublioBoxEcom3','PublioBoxEcom4','NavLinks','NavLinksSm','NavIco','NavIcoSm','BoxOpMT1','BoxOpMT2','BoxOpMT3','BoxOpMT4','BoxPogTer','BoxPogNast','BoxPogJut','BoxPog7','BoxPogMi','BoxSportImg4','BoxSportCzol1','BoxSportLinkMore','BoxBizImg4','BoxBizLinkMore','BoxLSImg3','BoxLSImg4','BoxLSLinkMore','BoxPloNav','BoxPloLinkMore','BoxLSFCzol1','BoxLSFNav','BoxLSFLinkMore','BoxKultLinkMore','BoxRozNav','BoxRozLink','BoxRozLinkMore','BoxWyboNavLink','BoxWyboLinkMore','BoxLoWaNavLink','BoxLoWaLinkMore','BoxLoKrNav','BoxLoKrLinkMore','BoxLoKiNav','BoxLoKiLinkMore','BoxLoBiNav','BoxLoBiNavLink','BoxLoBiLinkMore','BoxLoBbNav','BoxLoBbLinkMore','BoxLoByNav','BoxLoByLinkMore','BoxLoGlMT','BoxLoGlImg1','BoxLoGlImg2','BoxLoGlImg3','BoxLoGlImg4','BoxLoGlLink','BoxLoGlNav','BoxLoGlNavLink','BoxLoGlLinkMore','BoxLoTrLinkMore','BoxLoGwNav','BoxLoGwNavLink','BoxLoGwLinkMore','BoxLoKaNav','BoxLoKaLinkMore','BoxLoLuNav','BoxLoLuLinkMore','BoxLoLoNav','BoxLoLoLinkMore','BoxLoOlMT','BoxLoOlImg1','BoxLoOlImg2','BoxLoOlImg3','BoxLoOlImg4','BoxLoOlLink','BoxLoOlNav','BoxLoOlNavLink','BoxLoOlLinkMore','BoxLoOpNav','BoxLoOpLinkMore','BoxLoPlNav','BoxLoPlNavLink','BoxLoPlLinkMore','BoxLoPoNav','BoxLoPoLinkMore','BoxLoCzLinkMore','BoxLoRaNav','BoxLoRaLinkMore','BoxLoRzMT','BoxLoRzImg1','BoxLoRzNav','BoxLoRzLinkMore','BoxLoSoNav','BoxLoSoLinkMore','BoxLoSzNav','BoxLoSzLinkMore','BoxLoToMT','BoxLoToImg1','BoxLoToImg2','BoxLoToImg3','BoxLoToImg4','BoxLoToLink','BoxLoToNav','BoxLoToNavLink','BoxLoToLinkMore','BoxLoWrNav','BoxLoWrLinkMore','BoxLoZgNav','BoxLoZgLinkMore','BoxLoCpMT','BoxLoCpImg1','BoxLoCpImg2','BoxLoCpImg3','BoxLoCpImg4','BoxLoCpLink','BoxLoCpNav','BoxLoCpNavLink','BoxLoCpLinkMore','BoxC2COp1','BoxC2COp2','BoxC2COp10','BoxC2COp3','BoxC2COp4','BoxC2COp5','BoxC2COp6','BoxC2COp7','BoxC2COp8','BoxC2COp9','BoxC2CLS1','BoxC2CLS10','BoxC2CLS2','BoxC2CLS3','BoxC2CLS4','BoxC2CLS5','BoxC2CLS6','BoxC2CLS7','BoxC2CLS8','BoxC2CLS9','BoxC2CMot1','BoxC2CMot10','BoxC2CMot2','BoxC2CMot3','BoxC2CMot4','BoxC2CMot5','BoxC2CMot6','BoxC2CMot7','BoxC2CMot8','BoxC2CMot9','BoxC2CPlo1','BoxC2CPlo10','BoxC2CPlo2','BoxC2CPlo3','BoxC2CPlo4','BoxC2CPlo5','BoxC2CPlo6','BoxC2CPlo7','BoxC2CPlo8','BoxC2CPlo9','BoxC2CSport1','BoxC2CSport10','BoxC2CSport2','BoxC2CSport3','BoxC2CSport4','BoxC2CSport5','BoxC2CSport6','BoxC2CSport7','BoxC2CSport8','BoxC2CSport9','BoxWeSl_1_1','BoxWeSl_1_2','BoxWeSl_2_1','BoxWeSl_2_2','BoxWeSl_3_1','BoxWeSl_3_2','BoxWeSl_4_1','BoxWeSl_4_2','BoxWeSl_5_1','BoxWeSl_5_2','BoxWeFr','BoxLoBbLinkImg','BoxLoBiLinkImg','BoxLoByLinkImg','BoxLoCzLinkImg','BoxLoGLLinkImg','BoxLoGwLinkImg','BoxLoKaLinkImg','BoxLoKiLinkImg','BoxLoKrLinkImg','BoxLoLoLinkImg','BoxLoLuLinkImg','BoxLoOlsztynLinkImg','BoxLoOpLinkImg','BoxLoPlLinkImg','BoxLoPoLinkImg','BoxLoRaLinkImg','BoxLoRzLinkImg','BoxLoSoLinkImg','BoxLoSzLinkImg','BoxLoTrLinkImg','BoxLoWaLinkImg','BoxLoWrLinkImg','BoxLoZgLinkImg','BoxLoGlLinkImg','BoxLoOlLinkImg','BoxLoToLinkImg','BoxLoCpLinkImg','BoxBizCz','BoxBizImg','BoxDesCz','BoxDesImg','BoxDesNav','BoxDWCz','BoxDWImg','BoxDWNav','BoxLSECom','BoxFiBImg','BoxFiBNav','FiBImg','FiBNav','BoxGWNav','BoxHorNav','BoxIkoBook','BoxIkoGie','BoxIkoGry','BoxIkoHor','BoxIkoKina','BoxIkoLotto','BoxIkoPog','BoxIkoTV','BoxKultImg','BoxLSCz','BoxLSImg','BoxLokKrajImg','BoxLokKrajLink','BoxLokKrajLinkImg','BoxLokBialImg','BoxLokBialLink','BoxLokBialNav','BoxLokBialLinkImg','BoxLokBieImg','BoxLokBieLink','BoxLokBieLinkImg','BoxLokBieNav','BoxLokBydImg','BoxLokBydLink','BoxLokBydLinkImg','BoxLokBydNav','BoxLokCzeImg','BoxLokCzeLink','BoxLokCzeLinkImg','BoxLokCzeNav','BoxLokGlwImg','BoxLokGlwLink','BoxLokGlwLinkImg','BoxLokGorzImg','BoxLokGorzLink','BoxLokGorzLinkImg','BoxLokKatImg','BoxLokKatLink','BoxLokKatLinkImg','BoxLokKatNav','BoxLokKielImg','BoxLokKielLink','BoxLokKielLinkImg','BoxLokKielNav','BoxLokKrakImg','BoxLokKrakLink','BoxLokKrakLinkImg','BoxLokKrakNav','BoxLokLubImg','BoxLokLubLink','BoxLokLubLinkImg','BoxLokLubNav','BoxLokLodzImg','BoxLokLodzLink','BoxLokLodzLinkImg','BoxLokLodzNav','BoxLokOlsImg','BoxLokOlsLink','BoxLokOlsNav','BoxLokOlsLinkImg','BoxLokOpoImg','BoxLokOpoLink','BoxLokOpoLinkImg','BoxLokOpoNav','BoxLokPloImg','BoxLokPloLink','BoxLokPloNav','BoxLokPloLinkImg','BoxLokPozImg','BoxLokPozLink','BoxLokPozNav','BoxLokPozLinkImg','BoxLokRadImg','BoxLokRadLink','BoxLokRadNav','BoxLokRadLinkImg','BoxLokRzeImg','BoxLokRzeLink','BoxLokRzeLinkImg','BoxLokRzeNav','BoxLokSosImg','BoxLokSosLink','BoxLokSosLinkImg','BoxLokSosNav','BoxLokSznImg','BoxLokSznLink','BoxLokSznLinkImg','BoxLokSznNav','BoxLokTorImg','BoxLokTorLink','BoxLokTorLinkImg','BoxLokTorNav','BoxLokTrojImg','BoxLokTrojLink','BoxLokTrojLinkImg','BoxLokTrojNav','BoxLokWawImg','BoxLokWawNav','BoxLokWawLink','BoxLokWawLinkImg','BoxLokWrocImg','BoxLokWrocLink','BoxLokWrocLinkImg','BoxLokWrocNav','BoxLokZielImg','BoxLokZielLink','BoxLokZielLinkImg','BoxLokZielNav','MegaMT','BoxMU2Img','ModaUroda2Img','BoxMUCz','BoxMUImg','BoxMotoImg','BoxMotoNav','MotoImg','MTstream','mBoxNajcz','BoxNiePrzeg','NiePrzeg','BoxOferty','BoxNextImg','Czolka3Img','MT','MT2','Prze','BoxPloCz2','BoxPloCz3','BoxPloPrze','BoxTripImg','BoxTrvCz','BoxTrvImg','BoxTrvNav','PodrozeCz','PodrozeImg','TRPog','BoxPubImg','BoxRekrCz','BoxRekrImg','BoxRekrNav','BoxSpecImg','BoxSpecLink','BoxSpecialNav','BoxSpecial','BoxSportCz','BoxSportImg','BoxSportPrze','Weekend','TRwknd','BoxNewsImg','BoxVidImg','BoxVidLifeImg','BoxVODImg','BoxWN','gospoIco','Img','Ogl_all','Czolka3Img2','Prze2','HatNav','NavLinksCz','HatNavCz','NavMore','TROgl','BoxPubNav','A_BoxBizCzol1','A_BoxBizImg1','A_BoxBizImg2','A_BoxBizImg3','A_BoxBizLink','A_BoxBizLinkImg','A_BoxBizMT','A_BoxBizNav','A_BoxGWImg','A_BoxHor','A_BoxMTPromoImg','A_BoxMTPromo','A_BoxKultIko','A_BoxKultImg1','A_BoxKultImg2','A_BoxKultImg3','A_BoxKultLink','A_BoxKultLinkImg','A_BoxKultNav','A_BoxKultNavLink','A_BoxLoBbImg1','A_BoxLoBbImg2','A_BoxLoBbImg3','A_BoxLoBbImg4','A_BoxLoBbLink','A_BoxLoBbMT','A_BoxLoBbNavLink','A_BoxLoBiImg1','A_BoxLoBiImg2','A_BoxLoBiImg3','A_BoxLoBiImg4','A_BoxLoBiLink','A_BoxLoBiMT','A_BoxLoByImg1','A_BoxLoByImg2','A_BoxLoByImg3','A_BoxLoByImg4','A_BoxLoByLink','A_BoxLoByMT','A_BoxLoByNavLink','A_BoxLoCzImg1','A_BoxLoCzImg2','A_BoxLoCzImg3','A_BoxLoCzImg4','A_BoxLoCzNav','A_BoxLoCzLink','A_BoxLoCzMT','A_BoxLoCzNavLink','A_BoxLoGLImg1','A_BoxLoGLImg2','A_BoxLoGLImg3','A_BoxLoGLImg4','A_BoxLoGLLink','A_BoxLoGLMT','A_BoxLoGLNavLink','A_BoxLoGwImg1','A_BoxLoGwImg2','A_BoxLoGwImg3','A_BoxLoGwImg4','A_BoxLoGwLink','A_BoxLoGwMT','A_BoxLoKaImg1','A_BoxLoKaImg2','A_BoxLoKaImg3','A_BoxLoKaImg4','A_BoxLoKaLink','A_BoxLoKaMT','A_BoxLoKaNavLink','A_BoxLoKiImg1','A_BoxLoKiImg2','A_BoxLoKiImg3','A_BoxLoKiImg4','A_BoxLoKiLink','A_BoxLoKiMT','A_BoxLoKiNavLink','A_BoxLoKrImg1','A_BoxLoKrImg2','A_BoxLoKrImg3','A_BoxLoKrImg4','A_BoxLoKrLink','A_BoxLoKrMT','A_BoxLoKrNavLink','A_BoxLoLoImg1','A_BoxLoLoImg2','A_BoxLoLoImg3','A_BoxLoLoImg4','A_BoxLoLoLink','A_BoxLoLoMT','A_BoxLoLoNavLink','A_BoxLoLuImg1','A_BoxLoLuImg2','A_BoxLoLuImg3','A_BoxLoLuImg4','A_BoxLoLuLink','A_BoxLoLuMT','A_BoxLoLuNavLink','A_BoxLoOlsztynImg1','A_BoxLoOlsztynImg2','A_BoxLoOlsztynImg3','A_BoxLoOlsztynImg4','A_BoxLoOlsztynLink','A_BoxLoOlsztynMT','A_BoxLoOlsztynNavLink','A_BoxLoOlsztynNav','A_BoxLoOpImg1','A_BoxLoOpImg2','A_BoxLoOpImg3','A_BoxLoOpImg4','A_BoxLoOpLink','A_BoxLoOpMT','A_BoxLoOpNavLink','A_BoxLoPlImg1','A_BoxLoPlImg2','A_BoxLoPlImg3','A_BoxLoPlImg4','A_BoxLoPlLink','A_BoxLoPlMT','A_BoxLoPoImg1','A_BoxLoPoImg2','A_BoxLoPoImg3','A_BoxLoPoImg4','A_BoxLoPoLink','A_BoxLoPoMT','A_BoxLoPoNavLink','A_BoxLoRaImg1','A_BoxLoRaImg2','A_BoxLoRaImg3','A_BoxLoRaImg4','A_BoxLoRaLink','A_BoxLoRaMT','A_BoxLoRaNavLink','A_BoxLoRzImg2','A_BoxLoRzImg3','A_BoxLoRzImg4','A_BoxLoRzLink','A_BoxLoRzNavLink','A_BoxLoSoImg1','A_BoxLoSoImg2','A_BoxLoSoImg3','A_BoxLoSoImg4','A_BoxLoSoLink','A_BoxLoSoMT','A_BoxLoSoNavLink','A_BoxLoSzImg1','A_BoxLoSzImg2','A_BoxLoSzImg3','A_BoxLoSzImg4','A_BoxLoSzLink','A_BoxLoSzMT','A_BoxLoSzNavLink','A_BoxLoTrImg1','A_BoxLoTrImg2','A_BoxLoTrImg3','A_BoxLoTrImg4','A_BoxLoTrLink','A_BoxLoTrMT','A_BoxLoTrNavLink','A_BoxLoTrNav','A_BoxLoWaImg1','A_BoxLoWaImg2','A_BoxLoWaImg3','A_BoxLoWaImg4','A_BoxLoWaLink','A_BoxLoWaMT','A_BoxLoWaNav','A_BoxLoWrImg1','A_BoxLoWrImg2','A_BoxLoWrImg3','A_BoxLoWrImg4','A_BoxLoWrLink','A_BoxLoWrMT','A_BoxLoWrNavLink','A_BoxLoZgImg1','A_BoxLoZgImg2','A_BoxLoZgImg3','A_BoxLoZgImg4','A_BoxLoZgLink','A_BoxLoZgMT','A_BoxLoZgNavLink','A_BoxLSCzol1','A_BoxLSCzol2','A_BoxMotMT','A_BoxMotImg1','A_BoxMotImg2','A_BoxMotImg3','A_BoxMotImg4','A_BoxMotLink','A_BoxMotLinkImg','A_BoxMotNav','A_BoxLSFCZOL1','A_BoxLSFImg1','A_BoxLSFImg2','A_BoxLSFImg3','A_BoxLSFImg4','A_BoxLSFLink','A_BoxLSFMT','A_BoxLSImg1','A_BoxLSImg2','A_BoxLSLink','A_BoxLSLinkImg','A_BoxLSMT','A_BoxLSNav','A_ECON','A_BoxNewestNav','A_BoxNewsImg1','A_BoxNewsImg2','A_BoxNewsImg3','A_BoxNewsImg4','A_BoxNewsLink','A_BoxNewsLinkImg','A_BoxNewsLinkMore','A_BoxNewsMT','A_BoxNewsNav','A_BoxNewsNavLink','A_BoxNPrzeCzol1','A_BoxNPrzeCzol10','A_BoxNPrzeCzol11','A_BoxNPrzeCzol12','A_BoxNPrzeCzol13','A_BoxNPrzeCzol14','A_BoxNPrzeCzol15','A_BoxNPrzeCzol16','A_BoxNPrzeCzol17','A_BoxNPrzeCzol18','A_BoxNPrzeCzol19','A_BoxNPrzeCzol2','A_BoxNPrzeCzol20','A_BoxNPrzeCzol21','A_BoxNPrzeCzol22','A_BoxNPrzeCzol23','A_BoxNPrzeCzol24','A_BoxNPrzeCzol25','A_BoxNPrzeCzol26','A_BoxNPrzeCzol27','A_BoxNPrzeCzol3','A_BoxNPrzeCzol4','A_BoxNPrzeCzol5','A_BoxNPrzeCzol6','A_BoxNPrzeCzol7','A_BoxNPrzeCzol8','A_BoxNPrzeCzol9','A_BoxOpCzol1','A_BoxOpCzol2','A_BoxOpCzol3','A_BoxOpImg1','A_BoxOpImg2','A_BoxOpImg3','A_BoxOpImg4','A_BoxOpImg5','A_BoxOpImg6','A_BoxOpImg7','A_BoxOpImg8','A_BoxOpImg9','A_BoxOpImg10','A_BoxOpImg11','A_BoxOpLink','A_BoxOpMT','A_BoxPloCzol1','A_BoxPloImg1','A_BoxPloImg2','A_BoxPloImg3','A_BoxPloLink','A_BoxPloLinkImg','A_BoxPloMT','A_BoxPloNavLink','A_BoxRozCzol1','A_BoxRozImg1','A_BoxRozImg2','A_BoxRozImg3','A_BoxRozImg4','A_BoxRozMT','A_BoxRozNavLink','A_BoxSportImg1','A_BoxSportImg2','A_BoxSportImg3','A_BoxSportLink','A_BoxSportLinkImg','A_BoxSportMT','A_BoxSportNav','A_BoxWyboImg1','A_BoxWyboImg2','A_BoxWyboImg3','A_BoxWyboImg4','A_BoxWyboLink','A_BoxWyboLinkImg','A_BoxWyboMT','A_BoxWyboNav','A_NavMoreSeo','A_PublioBoxEcom1','A_PublioBoxEcom2','A_PublioBoxEcom3','A_PublioBoxEcom4','A_NavLinks','A_NavLinksSm','A_NavIco','A_NavIcoSm','A_BoxOpMT1','A_BoxOpMT2','A_BoxOpMT3','A_BoxOpMT4','A_BoxPogTer','A_BoxPogNast','A_BoxPogJut','A_BoxPog7','A_BoxPogMi','A_BoxSportImg4','A_BoxSportCzol1','A_BoxSportLinkMore','A_BoxBizImg4','A_BoxBizLinkMore','A_BoxLSImg3','A_BoxLSImg4','A_BoxLSLinkMore','A_BoxPloNav','A_BoxPloLinkMore','A_BoxLSFCzol1','A_BoxLSFNav','A_BoxLSFLinkMore','A_BoxKultLinkMore','A_BoxRozNav','A_BoxRozLink','A_BoxRozLinkMore','A_BoxWyboNavLink','A_BoxWyboLinkMore','A_BoxLoWaNavLink','A_BoxLoWaLinkMore','A_BoxLoKrNav','A_BoxLoKrLinkMore','A_BoxLoKiNav','A_BoxLoKiLinkMore','A_BoxLoBiNav','A_BoxLoBiNavLink','A_BoxLoBiLinkMore','A_BoxLoBbNav','A_BoxLoBbLinkMore','A_BoxLoByNav','A_BoxLoByLinkMore','A_BoxLoGlMT','A_BoxLoGlImg1','A_BoxLoGlImg2','A_BoxLoGlImg3','A_BoxLoGlImg4','A_BoxLoGlLink','A_BoxLoGlNav','A_BoxLoGlNavLink','A_BoxLoGlLinkMore','A_BoxLoTrLinkMore','A_BoxLoGwNav','A_BoxLoGwNavLink','A_BoxLoGwLinkMore','A_BoxLoKaNav','A_BoxLoKaLinkMore','A_BoxLoLuNav','A_BoxLoLuLinkMore','A_BoxLoLoNav','A_BoxLoLoLinkMore','A_BoxLoOlMT','A_BoxLoOlImg1','A_BoxLoOlImg2','A_BoxLoOlImg3','A_BoxLoOlImg4','A_BoxLoOlLink','A_BoxLoOlNav','A_BoxLoOlNavLink','A_BoxLoOlLinkMore','A_BoxLoOpNav','A_BoxLoOpLinkMore','A_BoxLoPlNav','A_BoxLoPlNavLink','A_BoxLoPlLinkMore','A_BoxLoPoNav','A_BoxLoPoLinkMore','A_BoxLoCzLinkMore','A_BoxLoRaNav','A_BoxLoRaLinkMore','A_BoxLoRzMT','A_BoxLoRzImg1','A_BoxLoRzNav','A_BoxLoRzLinkMore','A_BoxLoSoNav','A_BoxLoSoLinkMore','A_BoxLoSzNav','A_BoxLoSzLinkMore','A_BoxLoToMT','A_BoxLoToImg1','A_BoxLoToImg2','A_BoxLoToImg3','A_BoxLoToImg4','A_BoxLoToLink','A_BoxLoToNav','A_BoxLoToNavLink','A_BoxLoToLinkMore','A_BoxLoWrNav','A_BoxLoWrLinkMore','A_BoxLoZgNav','A_BoxLoZgLinkMore','A_BoxLoCpMT','A_BoxLoCpImg1','A_BoxLoCpImg2','A_BoxLoCpImg3','A_BoxLoCpImg4','A_BoxLoCpLink','A_BoxLoCpNav','A_BoxLoCpNavLink','A_BoxLoCpLinkMore','A_BoxC2COp1','A_BoxC2COp2','A_BoxC2COp10','A_BoxC2COp3','A_BoxC2COp4','A_BoxC2COp5','A_BoxC2COp6','A_BoxC2COp7','A_BoxC2COp8','A_BoxC2COp9','A_BoxC2CLS1','A_BoxC2CLS10','A_BoxC2CLS2','A_BoxC2CLS3','A_BoxC2CLS4','A_BoxC2CLS5','A_BoxC2CLS6','A_BoxC2CLS7','A_BoxC2CLS8','A_BoxC2CLS9','A_BoxC2CMot1','A_BoxC2CMot10','A_BoxC2CMot2','A_BoxC2CMot3','A_BoxC2CMot4','A_BoxC2CMot5','A_BoxC2CMot6','A_BoxC2CMot7','A_BoxC2CMot8','A_BoxC2CMot9','A_BoxC2CPlo1','A_BoxC2CPlo10','A_BoxC2CPlo2','A_BoxC2CPlo3','A_BoxC2CPlo4','A_BoxC2CPlo5','A_BoxC2CPlo6','A_BoxC2CPlo7','A_BoxC2CPlo8','A_BoxC2CPlo9','A_BoxC2CSport1','A_BoxC2CSport10','A_BoxC2CSport2','A_BoxC2CSport3','A_BoxC2CSport4','A_BoxC2CSport5','A_BoxC2CSport6','A_BoxC2CSport7','A_BoxC2CSport8','A_BoxC2CSport9','A_BoxWeSl_1_1','A_BoxWeSl_1_2','A_BoxWeSl_2_1','A_BoxWeSl_2_2','A_BoxWeSl_3_1','A_BoxWeSl_3_2','A_BoxWeSl_4_1','A_BoxWeSl_4_2','A_BoxWeSl_5_1','A_BoxWeSl_5_2','A_BoxWeFr','A_BoxLoBbLinkImg','A_BoxLoBiLinkImg','A_BoxLoByLinkImg','A_BoxLoCzLinkImg','A_BoxLoGLLinkImg','A_BoxLoGwLinkImg','A_BoxLoKaLinkImg','A_BoxLoKiLinkImg','A_BoxLoKrLinkImg','A_BoxLoLoLinkImg','A_BoxLoLuLinkImg','A_BoxLoOlsztynLinkImg','A_BoxLoOpLinkImg','A_BoxLoPlLinkImg','A_BoxLoPoLinkImg','A_BoxLoRaLinkImg','A_BoxLoRzLinkImg','A_BoxLoSoLinkImg','A_BoxLoSzLinkImg','A_BoxLoTrLinkImg','A_BoxLoWaLinkImg','A_BoxLoWrLinkImg','A_BoxLoZgLinkImg','A_BoxLoGlLinkImg','A_BoxLoOlLinkImg','A_BoxLoToLinkImg','A_BoxLoCpLinkImg','Z_BoxBizCz','Z_BoxBizImg','Z_BoxBizLink','Z_BoxBizLinkImg','Z_BoxBizNav','Z_BoxDesCz','Z_BoxDesImg','Z_BoxDesNav','Z_BoxDWCz','Z_BoxDWImg','Z_BoxDWNav','Z_BoxLSECom','Z_BoxFiBImg','Z_BoxFiBNav','Z_FiBImg','Z_FiBNav','Z_BoxGWImg','Z_BoxGWNav','Z_BoxHor','Z_BoxHorNav','Z_BoxIkoBook','Z_BoxIkoGie','Z_BoxIkoGry','Z_BoxIkoHor','Z_BoxIkoKina','Z_BoxIkoLotto','Z_BoxIkoPog','Z_BoxIkoTV','Z_BoxKultImg','Z_BoxKultNav','Z_BoxLSCz','Z_BoxLSImg','Z_BoxLSLink','Z_BoxLSLinkImg','Z_BoxLSNav','Z_BoxLokKrajImg','Z_BoxLokKrajLink','Z_BoxLokKrajLinkImg','Z_BoxLokBialImg','Z_BoxLokBialLink','Z_BoxLokBialNav','Z_BoxLokBialLinkImg','Z_BoxLokBieImg','Z_BoxLokBieLink','Z_BoxLokBieLinkImg','Z_BoxLokBieNav','Z_BoxLokBydImg','Z_BoxLokBydLink','Z_BoxLokBydLinkImg','Z_BoxLokBydNav','Z_BoxLokCzeImg','Z_BoxLokCzeLink','Z_BoxLokCzeLinkImg','Z_BoxLokCzeNav','Z_BoxLokGlwImg','Z_BoxLokGlwLink','Z_BoxLokGlwLinkImg','Z_BoxLokGorzImg','Z_BoxLokGorzLink','Z_BoxLokGorzLinkImg','Z_BoxLokKatImg','Z_BoxLokKatLink','Z_BoxLokKatLinkImg','Z_BoxLokKatNav','Z_BoxLokKielImg','Z_BoxLokKielLink','Z_BoxLokKielLinkImg','Z_BoxLokKielNav','Z_BoxLokKrakImg','Z_BoxLokKrakLink','Z_BoxLokKrakLinkImg','Z_BoxLokKrakNav','Z_BoxLokLubImg','Z_BoxLokLubLink','Z_BoxLokLubLinkImg','Z_BoxLokLubNav','Z_BoxLokLodzImg','Z_BoxLokLodzLink','Z_BoxLokLodzLinkImg','Z_BoxLokLodzNav','Z_BoxLokOlsImg','Z_BoxLokOlsLink','Z_BoxLokOlsNav','Z_BoxLokOlsLinkImg','Z_BoxLokOpoImg','Z_BoxLokOpoLink','Z_BoxLokOpoLinkImg','Z_BoxLokOpoNav','Z_BoxLokPloImg','Z_BoxLokPloLink','Z_BoxLokPloNav','Z_BoxLokPloLinkImg','Z_BoxLokPozImg','Z_BoxLokPozLink','Z_BoxLokPozNav','Z_BoxLokPozLinkImg','Z_BoxLokRadImg','Z_BoxLokRadLink','Z_BoxLokRadNav','Z_BoxLokRadLinkImg','Z_BoxLokRzeImg','Z_BoxLokRzeLink','Z_BoxLokRzeLinkImg','Z_BoxLokRzeNav','Z_BoxLokSosImg','Z_BoxLokSosLink','Z_BoxLokSosLinkImg','Z_BoxLokSosNav','Z_BoxLokSznImg','Z_BoxLokSznLink','Z_BoxLokSznLinkImg','Z_BoxLokSznNav','Z_BoxLokTorImg','Z_BoxLokTorLink','Z_BoxLokTorLinkImg','Z_BoxLokTorNav','Z_BoxLokTrojImg','Z_BoxLokTrojLink','Z_BoxLokTrojLinkImg','Z_BoxLokTrojNav','Z_BoxLokWawImg','Z_BoxLokWawNav','Z_BoxLokWawLink','Z_BoxLokWawLinkImg','Z_BoxLokWrocImg','Z_BoxLokWrocLink','Z_BoxLokWrocLinkImg','Z_BoxLokWrocNav','Z_BoxLokZielImg','Z_BoxLokZielLink','Z_BoxLokZielLinkImg','Z_BoxLokZielNav','Z_MegaMT','Z_BoxMU2Img','Z_ModaUroda2Img','Z_BoxMUCz','Z_BoxMUImg','Z_BoxMotoImg','Z_BoxMotoNav','Z_MotoImg','Z_BoxMTPromoImg','Z_BoxMTPromo','Z_MTstream','Z_mBoxNajcz','Z_BoxNiePrzeg','Z_NiePrzeg','Z_BoxOferty','Z_BoxNextImg','Z_Czolka3Img','Z_MT','Z_MT2','Z_Prze','Z_BoxPloCz2','Z_BoxPloCz3','Z_BoxPloMT','Z_BoxPloPrze','Z_BoxPloNav','Z_BoxSportNav','Z_BoxTripImg','Z_BoxTrvCz','Z_BoxTrvImg','Z_BoxTrvNav','Z_PodrozeCz','Z_PodrozeImg','Z_BoxPogJut','Z_BoxPogMi','Z_BoxPogTer','Z_TRPog','Z_BoxPubImg','Z_BoxRekrCz','Z_BoxRekrImg','Z_BoxRekrNav','Z_BoxSpecImg','Z_BoxSpecLink','Z_BoxSpecialNav','Z_BoxSpecial','Z_BoxSportCz','Z_BoxSportImg','Z_BoxSportLink','Z_BoxSportLinkImg','Z_BoxSportPrze','Z_Weekend','Z_TRwknd','Z_BoxNewsImg','Z_BoxNewsNav','Z_BoxNewsLink','Z_BoxNewsLinkImg','Z_BoxVidImg','Z_BoxVidLifeImg','Z_BoxVODImg','Z_BoxWN','Z_gospoIco','Z_Img','Z_Ogl_all','Z_Czolka3Img2','Z_Prze2','Z_NavLinks','Z_HatNav','Z_NavLinksCz','Z_HatNavCz','Z_NavIco','Z_NavMore','Z_TROgl','Z_BoxPubNav','BoxMTPPush','Z_BoxMTPPush','A_BoxMTPPush','BoxMTPAuto','A_BoxMTPAuto','Z_BoxMTPAuto'];
+
+
+
+
+if (( !document.referrer && (window.location.host === 'www.gazeta.pl' || window.location.host === 'm.gazeta.pl') &&
+ (window.location.href.indexOf('0,0.html?p=') !== -1 || window.location.href.indexOf('0,0.html?promocja=') !== -1)) ||
+ ((document.referrer.indexOf('www.gazeta.pl') !== -1 || document.referrer.indexOf('m.gazeta.pl') !== -1) &&
+ (document.referrer.indexOf('0,0.html?p=') !== -1 || document.referrer.indexOf('0,0.html?promocja=') !== -1))) {
+
+ var hashTag = '';
+ if (window.location.hash) {
+ var
+ hash = window.location.hash,
+ n = hash.indexOf('&');
+ hash = hash.substring(0, n != -1 ? n : hash.length);
+ var getHash = hash.split('#').pop();
+ if (hashArrDFP.indexOf(getHash) !== -1) { hashTag = '_' + getHash; }
+ }
+
+ googletag.pubads().setTargeting('src','HPGAZETA-DHP' + hashTag);
+ _setCookieDFP('HPGAZETA-DHP' + hashTag);
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|HPGAZETA-DHP" + hashTag);
+
+} else if ( document.referrer.split("?")[0].indexOf('facebook.com') !== -1 ||
+ ( !document.referrer && document.location.href.indexOf('utm_source=facebook.com') !== -1 ) ) {
+
+ if (window.location.href.indexOf('utm_content') !== -1 || document.referrer.indexOf('utm_content') !== -1) {
+
+ var paramsObject = function ( name, url ) {
+ if (!url) url = location.href;
+ name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
+ var regexS = "[\\?&]"+name+"=([^&#]*)";
+ var regex = new RegExp( regexS );
+ var results = regex.exec( url );
+ return results == null ? null : results[1];
+ },
+ checkHrefDFP = paramsObject('utm_content', window.location.href),
+ checkReferrerDFP = paramsObject('utm_content', document.referrer),
+ newValueDFP = checkHrefDFP || checkReferrerDFP;
+
+ googletag.pubads().setTargeting('src', 'FACEBOOK_CPC_' + newValueDFP);
+ _setCookieDFP('FACEBOOK_CPC_' + newValueDFP);
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|FACEBOOK_CPC_" + newValueDFP);
+
+ } else {
+ googletag.pubads().setTargeting('src','FACEBOOK');
+ _setCookieDFP('FACEBOOK');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|FACEBOOK");
+ }
+
+
+} else if ( ( document.location.href.indexOf('utm_medium=cpc') !== -1 && document.location.href.indexOf('utm_source=facebook.com') === -1) ||
+ (document.location.href.indexOf('adw=1') !== -1 || document.location.href.indexOf('gclid=') !== -1) ||
+ (document.referrer.indexOf('googleads.g.doubleclick.net') !== -1 || document.referrer.indexOf('googleadservices.com') !== -1) ) {
+
+ googletag.pubads().setTargeting('src','CPC');
+ _setCookieDFP('CPC');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|CPC");
+
+} else if( (document.location.href.indexOf('www.gazeta.pl') !== -1 || document.location.href.indexOf('m.gazeta.pl') !== -1) ||
+ (document.referrer.indexOf('www.gazeta.pl') !== -1 || document.referrer.indexOf('m.gazeta.pl') !== -1) ) {
+
+ var hashTag = '';
+ if (window.location.hash) {
+ var
+ hash = window.location.hash,
+ n = hash.indexOf('&');
+ hash = hash.substring(0, n != -1 ? n : hash.length);
+ var getHash = hash.split('#').pop();
+ if (hashArrDFP.indexOf(getHash) !== -1) { hashTag = '_' + getHash; }
+ }
+
+ googletag.pubads().setTargeting('src','HPGAZETA' + hashTag);
+ _setCookieDFP('HPGAZETA' + hashTag);
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|HPGAZETA" + hashTag);
+
+} else if (!document.referrer) {
+
+ googletag.pubads().setTargeting('src','DIRECT');
+ _setCookieDFP('DIRECT');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|DIRECT");
+
+} else if ( (document.referrer.indexOf('szukaj.gazeta.pl') === -1) &&
+ (document.referrer.indexOf('?q=') !== -1 || document.referrer.indexOf('&q=') !== -1 || document.referrer.indexOf('?query=') !== -1 || document.referrer.indexOf('search.yahoo.com') !== -1 || document.referrer.indexOf('&query=') !== -1 || document.referrer.indexOf('?szukaj=') !== -1 || document.referrer.indexOf('&szukaj') !== -1 || (document.referrer.indexOf('//www.google.') !== -1 && document.referrer.indexOf('logout') === -1)) &&
+ document.location.href.indexOf('0,0.html') !== -1 || window.location.href.slice(-3) === '.pl' || window.location.href.slice(-4) === '.biz' || window.location.href.slice(-3) === '.tv' || window.location.href.slice(-8) === '.pl/html') {
+
+ googletag.pubads().setTargeting('src','DIRECT-SEO');
+ _setCookieDFP('DIRECT-SEO');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|DIRECT-SEO");
+
+} else if ( document.referrer.indexOf('szukaj.gazeta.pl') === -1 &&
+ (document.referrer.indexOf('?q=') !== -1 || document.referrer.indexOf('&q=') !== -1 || document.referrer.indexOf('?query=') !== -1 || document.referrer.indexOf('search.yahoo.com') !== -1 || document.referrer.indexOf('&query=') !== -1 || document.referrer.indexOf('?szukaj=') !== -1 || document.referrer.indexOf('&szukaj') !== -1 || (document.referrer.indexOf('//www.google.') !== -1 && document.referrer.indexOf('logout') === -1)) ) {
+
+ googletag.pubads().setTargeting('src','SEO');
+ _setCookieDFP('SEO');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|SEO");
+
+} else if (cookieIfr) {
+
+ googletag.pubads().setTargeting('src', cookieIfr);
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|" + cookieIfr);
+
+}
+
+
+if ((window.location.host == "www.gazeta.pl") || (window.location.host == "m.gazeta.pl")) {
+ if (window.location.href.indexOf('pushpushgo')!== -1 && window.location.href.indexOf('mtp=')!== -1) {
+ googletag.pubads().setTargeting('test','MTP_PUSH');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_PUSH");
+ } else if ((window.location.href.indexOf('utm_source=facebook')!== -1 && window.location.href.indexOf('mtp=')!== -1) || window.location.href.indexOf('boxmtpautosp=')!== -1) {
+ googletag.pubads().setTargeting('test','MTP_FBCPC');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_FBCPC");
+ } else if (window.location.href.indexOf('boxmtpauto=')!== -1) {
+ googletag.pubads().setTargeting('test','MTP_FBORG');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_FBORG");
+ } else if ((window.location.href.indexOf('?p=')!== -1 || window.location.href.indexOf('?promocja=')!== -1) && document.referrer == "") {
+ googletag.pubads().setTargeting('test','MTP_DHP');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_DHP");
+ } else if (window.location.href.indexOf('utm_campaign=amtp')!== -1) {
+ googletag.pubads().setTargeting('test','MTP_AMTP');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_AMTP4");
+ } else if (window.location.href.indexOf('?404')!== -1) {
+ googletag.pubads().setTargeting('test','MTP_404');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_404");
+ }else {
+ googletag.pubads().setTargeting('test','MTP_NULL');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_NULL");
+ }
+}
+
+
+
+
+
+
+ // [HB] Core-prod-3 - AFTER_SLOTS
+if(window.yb_seg && yb_seg!='') {
+ googletag.pubads().setTargeting('yb_seg', yb_seg);
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_seg|" + yb_seg);
+}
+if(window.pbjs_yb_hb) {
+ googletag.pubads().getSlots().forEach(function(slot){
+ slot.setTargeting('yb_hb', String(yb_hb));
+ });
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_hb|" + String(yb_hb));
+}
+if(window.pbjs) pbjs.que.push(function() {
+ pbjs.setTargetingForGPTAsync();
+});
+
+
+
+
+
+
+
+ var yb_ff = String(Math.round(Math.random()));
+googletag.pubads().setTargeting('yb_ff', yb_ff);
+var _yt=new Date(),yb_th=_yt.getUTCHours()-8,yb_tm=_yt.getUTCMinutes(),yb_wd=_yt.getUTCDay();
+if(yb_th<0){yb_th=24+yb_th;yb_wd-=1;};if(yb_wd<0){yb_wd=7+yb_wd};
+googletag.pubads().setTargeting('yb_th', yb_th.toString());
+googletag.pubads().setTargeting('yb_tm', yb_tm.toString());
+googletag.pubads().setTargeting('yb_wd', yb_wd.toString());
+
+if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_th|" + yb_th.toString());
+if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_tm|" + yb_tm.toString());
+if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_wd|" + yb_wd.toString());
+
+
+adviewDFP.getQueryParams = function(e){
+ var n = new RegExp("[\\?&]"+e+"=([^&#]*)").exec(window.location.href);
+ return null !== n ? n[1] : null
+};
+
+
+if (adviewDFP.getQueryParams('dfp_target_kw')) adviewDFP.DFPTargeting.push('kw|'+adviewDFP.getQueryParams('dfp_target_kw'));
+
+var
+ kwString = '',
+ paramString = '';
+
+for(var i = 0; i < adviewDFP.DFPTargeting.length; i++) {
+ var
+ _this = adviewDFP.DFPTargeting[i],
+ _thisParam = _this.split('|');
+
+ if (_this.indexOf('kw') !== -1) {
+ if (kwString.indexOf(_thisParam[1]) === -1) kwString += '%2C' + _thisParam[1];
+ } else {
+ paramString += '%26'+_thisParam[0]+'%3D'+_thisParam[1];
+ }
+}
+
+//_YB
+var _YB = window.top._YB || { ab:function(){return (Math.random()>=0.1?"b":"a"+Math.floor(Math.random()*10));} };
+
+paramString += '%26yb_ab%3D' + _YB.ab();
+//_YB
+paramString += '%26yb_ff%3D' + String(Math.round(Math.random()));
+
+if (typeof activeSubscription !== 'undefined' && activeSubscription) {
+ paramString += '%26subscription%3Dtrue';
+}
+
+if (dfpParams && dfpParams.video && dfpParams.video.preroll) {
+ dfpParams.video.preroll = dfpParams.video.preroll.replace('%26dystrybutor', kwString + paramString + '%26dystrybutor');
+ dfpParams.video.preroll = dfpParams.video.preroll.replace('[adview_hostname]', escape(location.host));
+ dfpParams.video.preroll = dfpParams.video.preroll.replace('[cb]', window.AG.rodoAccepted);
+ dfpParams.video.preroll = dfpParams.video.preroll.replace('[locationhref]', escape(window.location.href));
+}
+
+if(dfpParams && dfpParams.video && dfpParams.video.skin) dfpParams.video.skin = dfpParams.video.skin.replace('%26dystrybutor', kwString + paramString + '%26dystrybutor');
+if(dfpParams && dfpParams.video && dfpParams.video.overlay) dfpParams.video.overlay = dfpParams.video.overlay.replace('%26dystrybutor', kwString + paramString + '%26dystrybutor');
+if(dfpParams && dfpParams.video && dfpParams.video.pausead) dfpParams.video.pausead = dfpParams.video.pausead.replace('%26dystrybutor', kwString + paramString + '%26dystrybutor');
+
+
+
+
+
+
+
+
+
+ if (typeof randRodoAB !== 'undefined') {
+ googletag.pubads().setTargeting("kw", "testyab_rodo_" + randRodoAB);
+ adviewDFP.DFPTargeting.push('kw|testyab_rodo_' + randRodoAB);
+}
+
+(function(){
+ var _pm = window.location.search.match(/\?i=([0-9]+)/);
+ if (window.adviewDFP && adviewDFP.DFPTargeting && _pm) adviewDFP.DFPTargeting.push("yb_page|" + String(_pm[1]));
+ if (_pm) return googletag.pubads().setTargeting('yb_page', String(_pm[1]));
+})();
+
+googletag.pubads().setTargeting('domena', ""+window.top.location.hostname+"");
+
+var
+ adviewKW = [];
+
+if(typeof window.gExVariation !== 'undefined') {
+ adviewKW.push('testyab_' + gExVariation);
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|testyab_' + gExVariation);
+}
+if (/\/2,|\/14,/.test(window.location.href)) {
+ adviewKW.push('relacja');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|relacja');
+}
+if (/\/15,/.test(window.location.href)) {
+ adviewKW.push('quiz-new');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|quiz-new');
+}
+if (/\/13,/.test(window.location.href)) {
+ adviewKW.push('quiz-old');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|quiz-old');
+}
+if (/\/5,/.test(window.location.href)) {
+ adviewKW.push('jsp-5');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|jsp-5');
+}
+if (/\/51,/.test(window.location.href)) {
+ adviewKW.push('jsp-51');
+ if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|jsp-51');
+}
+if (typeof adviewKW !== 'undefined' && adviewKW.length) {
+ googletag.pubads().setTargeting('kw', adviewKW);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ if(window.AG.rodoAccepted === -1) {
+ googletag.pubads().setRequestNonPersonalizedAds(1);
+ }
+
+ googletag.pubads().enableSingleRequest();
+ googletag.pubads().enableSyncRendering();
+ googletag.pubads().collapseEmptyDivs();
+ <!-- IBA Analytics -->
+ googletag.pubads().addEventListener('slotRenderEnded', function(data) {
+ adviewDFP.adviewRendered(data);
+ if(typeof IBA != 'undefined' && typeof IBA.dfp != 'undefined') IBA.dfp.onSlotRendered(data);
+ });
+ if(window.AG.rodoAccepted !== 0) {
+ googletag.enableServices();
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ });
+
+ function putBanDFP(a,b,c,d){if(!b||"string"!=typeof b)return!1;if(!dfpParams.slots[a])return console.warn("dfpParams slot: "+a+" not exists"),!1;var e=window.location.protocol+"//"+window.location.host+"/dfptools/adview/",f=e+dfpParams.slots[a].url;c&&"object"==typeof c&&(f=f.replace(/adUnitSize=\[\[(.*?)\]]/g,"adUnitSize="+JSON.stringify(c))),d&&"string"==typeof d&&(f+="&addKeysKw="+d),adviewDFP.iframeBan(b,f,e+"autoslot.js",0,0)}function putBanDFPInView(a,b,c,d){var e=parseInt(c)?parseInt(c):300;adviewDFP.DFPTargeting.push("kw|oldScroll"),window.addEventListener("scroll",function(){adviewDFP.isElementInViewport(b,e)&&(putBanDFP(a,b,d),this.removeEventListener("scroll",arguments.callee,!1))})}function putBanDFPInViewObject(a){if(!a.slot||!a.divId)return a.slot||console.warn("Slot ID is empty"),a.divId||console.warn("divId id empty"),!1;"021-IMK"!==a.slot&&"067-RECTANGLE-BTF"!==a.slot&&"035-RECTANGLE-BTF"!==a.slot||(a.margin=parseInt(adviewDFP.randomMargin),a.kw?a.kw+=", margin_"+adviewDFP.randomMargin:a.kw="margin_"+adviewDFP.randomMargin);var b=parseInt(a.margin)?parseInt(a.margin):300;window.addEventListener("scroll",function(){adviewDFP.isElementInViewport(a.divId,b)&&(putBanDFP(a.slot,a.divId,a.unitSize,a.kw),this.removeEventListener("scroll",arguments.callee,!1))})}function putBan(){}var adviewDFP=adviewDFP||{};adviewDFP.scrollSlot=[],adviewDFP.DFPTargeting=adviewDFP.DFPTargeting||[],adviewDFP.allSlotsForRODO=[],adviewDFP.arrayLabel=["001-TOPBOARD","003-RECTANGLE","000-MAINBOARD","042-FOOTBOARD","087-ADBOARD-A","071-WINIETA","004-PAYPER","007-CONTENTBOARD","021-IMK","059-BUTTON","078-STYLBOARD","044-BIZBOARD","035-RECTANGLE-BTF","067-RECTANGLE-BTF","101-TOPBOARD-MOBI","104-RECTANGLE-MOBI","107-MAINBOARD-MOBI","150-BIZBOARD-MOBI","151-FUNBOARD-MOBI","152-STYLBOARD-MOBI","153-MOTOBOARD-MOBI","150-ADBOARD-A-MOBI","150-ADBOARD-B-MOBI","150-ADBOARD-C-MOBI","150-ADBOARD-D-MOBI","108-FOOTBOARD-MOBI","000-SPORTBOARD","076-MAINBUTTON-0","111-BIZBUTTON","076-MAINBUTTON","116-SPORTBUTTON"],adviewDFP.check=!0,adviewDFP.init=function(){var a=setInterval(function(){if("complete"===document.readyState){clearInterval(a);var b=document.getElementsByClassName("activeBan"),c=document.getElementsByTagName("body")[0];-1!==window.navigator.userAgent.indexOf("Firefox")&&adviewDFP.firefox(),adviewDFP.check&&adviewDFP.checkAgain(),adviewDFP.checkBan(),adviewDFP.adBlock();for(var d=0;d<b.length;d++){var e=b[d],f=[parseInt(e.getAttribute("data-ad-width")),parseInt(e.getAttribute("data-ad-height"))];if(1===f[0]&&(f=[e.scrollWidth,e.scrollHeight]),"007-CONTENTBOARD"!==e.id||750!==f[0]||"pageTypeId_7"!==c.id&&"pageTypeId_1"!==c.id||-1!==window.location.href.indexOf("0,0.")?"007-CONTENTBOARD"!==e.id||750===f[0]||"pageTypeId_7"!==c.id&&"pageTypeId_1"!==c.id||adviewDFP.cleanWideContentBoard(e.id):adviewDFP.wideContentBoard(e.id,f),("pageTypeId_7"===c.id||"pageTypeId_1"===c.id)&&"007-CONTENTBOARD"===e.id){750!==document.getElementById("div-gpt-ad-007-CONTENTBOARD-0").clientWidth&&adviewDFP.cleanWideContentBoard(e.id)}}}},100)},adviewDFP.adviewRendered=function(a){var b=a.slot,c=null;Object.keys(b).map(function(a,d){var e=b[a];"string"==typeof e&&e.length&&-1!==e.indexOf("/75224259/")&&(c=e)}),"string"==typeof c&&c.length||console.warn("DFP slot is empty");var d=/[0-9]{3}-(.+)/,e=d.exec(c)[0],f=document.getElementById(e),g=document.getElementById("div-gpt-ad-"+e+"-0"),h=f.getElementsByTagName("span")[0],i=f.getElementsByTagName("iframe")[0],j=document.getElementsByTagName("body")[0];if(void 0!==window.top.dfpRendered&&window.top.dfpRendered({active:a.isEmpty,id:e,height:a.size?a.size[1]:"creative"}),a.isEmpty)return!1;-1===f.className.indexOf("activeBan")&&(f.className+=" activeBan"),f.setAttribute("data-ad-width",a.size[0]),f.setAttribute("data-ad-height",a.size[1]),a.size[0]>10&&(g.style.maxWidth=a.size[0]+"px",h.style.maxWidth=a.size[0]+"px"),h.style.marginLeft="auto",h.style.marginRight="auto",window.dfpParams.adsType&&window.dfpParams.adsType.scrollBanner&&adviewDFP.setClassToBody("scrollBannerDFP"),adviewDFP.setLabel(f),dfpParams.iframeResponsive&&i.getAttribute("responsive-iframe")&&adviewDFP.setIframeResponsive(i),"007-CONTENTBOARD"!==e||750!==a.size[0]||"pageTypeId_7"!==j.id&&"pageTypeId_1"!==j.id||-1!==window.location.href.indexOf("0,0.")?"007-CONTENTBOARD"!==ban.id||750===banSize[0]||"pageTypeId_7"!==j.id&&"pageTypeId_1"!==j.id||adviewDFP.cleanWideContentBoard(e):adviewDFP.wideContentBoard(e,a.size),"071-WINIETA"===e&&-1!==adviewDFP.arrayLabel.indexOf(e)&&adviewDFP.setWinieta()},adviewDFP.setClassToBody=function(a){var b=document.getElementsByTagName("body")[0];-1===b.className.indexOf(a)&&(b.className+=" "+a)},adviewDFP.wideContentBoard=function(a,b){var c=document.getElementById(a),d=document.getElementById("div-gpt-ad-"+a+"-0"),e=c.getElementsByClassName("banLabel")[0];-1===c.className.indexOf("wideContentBoard")&&(c.className+=" wideContentBoard"),c.style.marginBottom="30px",c.style.height=b[1]+e.clientHeight+"px",d.style.position="absolute",d.style.right=0},adviewDFP.cleanWideContentBoard=function(a){var b=document.getElementById(a),c=document.getElementById("div-gpt-ad-"+a+"-0");b.style.cssText="",c.style.cssText="",b.classList.remove("wideContentBoard")},adviewDFP.setWinieta=function(){var a=document.getElementById("pageHead"),b=document.createElement("span");b.className="banLabel winieta-lab",b.appendChild(document.createTextNode("REKLAMA")),a.appendChild(b)},adviewDFP.checkBan=function(){for(var a=document.getElementsByClassName("adviewDFPBanner"),b=0;b<a.length;b++){var c=a[b];c.offsetHeight>40&&adviewDFP.setLabel(c)}},adviewDFP.setLabel=function(a){var b=a,c=b.getElementsByClassName("banLabel")[0];if(c){if(-1!==adviewDFP.arrayLabel.indexOf(b.id)&&b.offsetHeight>40){c.style.display="block";for(var d=b.getElementsByTagName("*"),e=0,f=0;f<d.length;f++){var g=d[f],h=parseInt(g.style.width);h&&h>e?e=h:parseInt(g.width)&&parseInt(g.width)>e&&(e=g.width)}c.style.maxWidth=e+"px"}"undefined"!=typeof abp&&abp&&-1===b.className.indexOf("adblock")&&(b.className+=" adblock"),-1===b.className.indexOf("activeBan")&&b.offsetHeight>40&&(b.className+=" activeBan")}},adviewDFP.firefox=function(){var a=document.getElementsByClassName("banIndexDFP")[0];a&&(a.style.display="block",setTimeout(function(){"0px"===a.getElementsByTagName("iframe")[0].style.height&&a.parentNode.removeChild(a)},500))},adviewDFP.adBlock=function(){for(var a=["div-001-TOPBOARD-ABP","div-003-RECTANGLE-ABP","div-000-MAINBOARD-ABP","div-087-ADBOARD-A-ABP","div-087-ADBOARD-B-ABP","div-111-BIZBUTTON-ABP"],b=0;b<a.length;b++){var c=document.getElementById(a[b]);if(c&&c.clientHeight>40){var d=document.createElement("span");d.className="banLabel",d.appendChild(document.createTextNode("Reklama")),c.insertBefore(d,c.getElementsByTagName("div")[0]),-1===c.className.indexOf("activeBan")&&(c.className+="activeBan")}}},adviewDFP.iframeBan=function(a,b,c,d,e){if(!a)return!1;for(var f=document,g=f.getElementById(a);g.hasChildNodes();)g.removeChild(g.lastChild);var h=f.createElement("iframe"),i=f.createElement("div");i.className="fifContainer fif-container-"+a,h.src=b,h.style.width=d+"px",h.style.height=e+"px",h.style.margin=0,h.style.borderWidth=0,h.style.padding=0,h.scrolling="no",h.frameBorder=0,h.allowTransparency=!0,h.className="banDfpFIF",h.id=a+"_FIF",h.EAS_src=c,h.setAttribute("data-type","fif"),i.appendChild(h),g.appendChild(i)},adviewDFP.checkAgain=function(){setTimeout(function(){adviewDFP.checkBan()},3e3)},adviewDFP.onElementHeightChange=function(a,b){var c,d=a.clientHeight;!function e(){c=a.clientHeight,d!=c&&b(c),d=c,a.onElementHeightChangeTimer&&clearTimeout(a.onElementHeightChangeTimer),a.onElementHeightChangeTimer=setTimeout(e,200)}()},adviewDFP.scrollRect=function(){var a=document.getElementById("col_left");if(!a)return!1;var b=a.clientHeight,c=document.getElementById("col_right"),d=document.getElementById("holder_301"),e=document.getElementsByTagName("body")[0];c.style.minHeight=b+"px",d.style.minHeight=b+"px",document.getElementById("page").style.overflow="initial",-1===e.className.indexOf("scroll035Rectangle")&&(e.className+=" scroll035Rectangle")},adviewDFP.findClosestClass=function(a,b){for(;(a=a.parentElement)&&!a.classList.contains(b););return a},adviewDFP.setIframeResponsive=function(a){var b=a,c=b.getAttribute("data-prop"),d=parseInt(b.getAttribute("width")),e=parseInt(b.contentWindow.document.body.clientWidth);document.getElementsByTagName("body")[0].className,parseInt(adviewDFP.findClosestClass(b,"adviewDFPBanner").getAttribute("data-ad-height"));if(c||(c=parseInt(b.getAttribute("height"))/d,b.setAttribute("data-prop",c)),e!=d){var f=Math.round(e*c);b.style.height=f+"px"}},adviewDFP.getMobileIframe=function(){var a=document.querySelectorAll('iframe[id^="google_ads_iframe"]');if(!a.length&&!dfpParams.iframeResponsive)return!1;for(var b=0;b<a.length;b++){var c=a[b];c.clientHeight>40&&c.getAttribute("responsive-iframe")&&adviewDFP.setIframeResponsive(c)}},adviewDFP.removeDefaultLabel=function(a){var b=adviewDFP.arrayLabel.indexOf(a);-1!==adviewDFP.arrayLabel&&adviewDFP.arrayLabel.splice(b,1)},adviewDFP.isElementInViewport=function(a,b){var c=document.getElementById(a);if(!c)return console.warn('(DFP) ID: "'+a+'" not exists'),!1;var d=c.getBoundingClientRect();return b=b?Number(b):300,d.bottom>0&&d.right>0&&d.left<(window.innerWidth||document.documentElement.clientWidth)&&d.top<(window.innerHeight||document.documentElement.clientHeight)+b},window.onresize=function(){document.getElementsByTagName("body")[0];dfpParams.iframeResponsive&&adviewDFP.getMobileIframe()},adviewDFP.init(),document.addEventListener("DOMContentLoaded",function(){document.getElementsByTagName("body")[0].className+=" dfp-"+dfpParams.dir,adviewDFP.randomLoadMargin(),dfpParams.isMobile||-1!==window.location.href.indexOf("wyborcza.pl")||document.getElementById("content_wrap")&&(adviewDFP.scrollRect(),adviewDFP.onElementHeightChange(document.getElementById("content_wrap"),function(){-1===window.location.href.indexOf("bryla.pl")&&adviewDFP.scrollRect()})),adviewDFP.scrollSlot.forEach(function(a){putBanDFPInViewObject({slot:a,divId:a})},this)}),adviewDFP.randomLoadMargin=function(){var a=[0,100,200,300,400,500,600,700,800,900,1e3];adviewDFP.randomMargin=a[Math.floor(Math.random()*a.length)]},adviewDFP.setAdsForRodo=function(){};var head=document.head||document.getElementsByTagName("head")[0],style=document.createElement("style"),css=".innerContentWrapper button:nth-of-type(2){ display: {%VALUE%} !important; }";css=-1!==window.location.href.indexOf("ugotuj.to")||-1!==window.location.href.indexOf("sport.pl")?css.replace("{%VALUE%}","block"):css.replace("{%VALUE%}","none"),style.type="text/css",style.styleSheet?style.styleSheet.cssText=css:style.appendChild(document.createTextNode(css)),head.appendChild(style);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ // [HB] Core-prod-4 - HEADER_END
+var _sts = new Date();
+var _sleeptime = PREBID_TIMEOUT - (_sts - _st0);
+if(_sleeptime > 0){
+ document.write('<scr'+'ipt type="text/javascript" src="https://g.gazeta.pl/przerwa?ttl=' + _sleeptime + '"></scr'+'ipt>');
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ if(lazyGPT) {
+
+ (function() {
+ var useSSL = 'https:' == document.location.protocol;
+ var src = (useSSL ? 'https:' : 'http:') +
+ '//www.googletagservices.com/tag/js/gpt.js';
+ document.write('<scr' + 'ipt src="' + src + '"></scr' + 'ipt>');
+ })();
+ }
+
+ </script>
+
+
+
+
+
+
+<style type="text/css">.adviewDFPBanner{text-align:center}.banLabel{text-transform:uppercase;margin-top:6px;font:normal 10px Arial,sans-serif;padding-bottom:2px;text-align:left;color:#999}.adviewDFPBanner.activeBan,.adviewDFPBanner.activeBan .banLabel{display:block}.adviewDFPBanner.activeBan{padding:5px 0}.adviewDFPBanner.activeBan>div{margin-left:auto;margin-right:auto}body.screeningLabel-001-TOPBOARD .DFP-001-TOPBOARD{padding-top:5px}body.screeningLabel-001-TOPBOARD .DFP-001-TOPBOARD .banLabel{position:absolute;top:35px;left:10px;max-width:none!important}@media screen and (max-width:1320px){body#pageTypeId_7.screeningLabel-001-TOPBOARD .DFP-001-TOPBOARD{position:relative}body#pageTypeId_7.screeningLabel-001-TOPBOARD .DFP-001-TOPBOARD .banLabel{top:0;left:-53px}}.adviewDFPBanner.activeBan a:hover{background:0 0!important}.DFP-007-CONTENTBOARD{width:100%;position:relative}body.winieta #page-top:not(.fixed){position:relative}body.winieta #page-top:not(.fixed) #pageHead .imgw img{position:absolute;top:0;left:0;z-index:2}body.winieta #page-top:not(.fixed) #pageHead.hasBanner{min-height:90px}#pageHead.hasBanner .banLabel{position:absolute;top:20px;right:-40px;padding:3px 5px;margin:0;transform:rotate(-90deg);background:#fff}body.dfp-forum #pageHead.hasBanner .banLabel{right:-36px}body.dfp-forum.winieta{width:auto!important}#pageHead.hasBanner .column.col1{display:inline-block;position:relative;z-index:2;height:100%}body.dfp-gazetawyborcza #pageHead.hasBanner .c0{top:40px;position:absolute}.DFP-091-RELATED .kd_ns_logo{background:0 0!important;width:auto!important;height:auto!important;margin:auto!important}#div-gpt-ad-091-RELATED-0{max-width:100%!important}body.screeningADFP #page{position:initial}.isScreening #page-top{cursor:pointer}body.screeningADFP.desk .DFP-001-TOPBOARD.activeBan{background:0 0}body.scrollBannerDFP .DFP-001-TOPBOARD .banLabel{position:absolute}#article-list .banner,.indexBanner,div.banIndexDFP,li.banIndexDFP,li.entry.banIndexDFP{display:none}div[id*="-MOBI"].adviewDFPBanner a img{width:100%;height:auto}div[id*="-MOBI"].adviewDFPBanner iframe[responsive-iframe=true]{max-width:100%;width:100%!important;height:auto}body.dfp-mobiHP .banLabel,body.dfp-mobiHP div[id*=div-gpt-ad-]{max-width:100%!important}.dfp-video-bg #page{overflow:initial}.dfp-video-bg .photostoryNextPage,.dfp-video-bg .photostoryPrevPage{z-index:1!important}#div-001-TOPBOARD-ABP .banLabel,#div-003-RECTANGLE-ABP .banLabel{display:block!important}body.scroll035Rectangle .DFP-035-RECTANGLE-BTF{position:sticky!important;position:-webkit-sticky!important;position:-moz-sticky!important;top:40px!important}body.socialContent .DFP-001-TOPBOARD,body.socialContent .DFP-101-TOPBOARD-MOBI{position:relative}body.socialContent .DFP-001-TOPBOARD.activeBan .banLabel,body.socialContent .DFP-101-TOPBOARD-MOBI.activeBan .banLabel{width:100%;position:absolute;left:0;top:0;margin:0;padding:10px;box-sizing:border-box;font-family:Roboto,sans-serif;font-size:8px;letter-spacing:.7px;max-width:none!important;background:-webkit-linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0));background:-o-linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0));background:-moz-linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0));background:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0))}body.socialContent .DFP-007-CONTENTBOARD .banLabel,body.socialContent .DFP-104-RECTANGLE-MOBI .banLabel{display:none!important}body.socialContent .DFP-101-TOPBOARD-MOBI>div,body.socialContent .DFP-104-RECTANGLE-MOBI>div{max-width:none!important}body[id^=pageTypeId_0] .adviewDFPBanner.activeBan,body[id^=pageTypeId_56] .adviewDFPBanner.activeBan,body[id^=pageTypeId_7] .adviewDFPBanner.activeBan{margin-bottom:30px;padding-bottom:0}.desk .adviewDFPBanner.DFP-001-TOPBOARD.activeBan{margin-bottom:15px}body.alternative .adviewDFPBanner.activeBan{padding-top:0!important;padding-bottom:20px!important;margin-top:0!important;margin-bottom:0!important}body.alternative .adviewDFPBanner.activeBan .banLabel{padding-top:15px!important;padding-bottom:10px!important;margin-top:0!important;margin-bottom:0!important}body.alternative .DFP-003-RECTANGLE.adviewDFPBanner.activeBan .banLabel{padding-top:0!important}body.alternative .article .adviewDFPBanner.activeBan{margin-bottom:20px!important}body.alternative .DFP-035-RECTANGLE-BTF,body.alternative .DFP-067-RECTANGLE-BTF{position:sticky;top:30px}#banC1.activeBan,#banC2.activeBan,#banC3.activeBan,#banC4.activeBan,#banC9.activeBan{position:relative}#banC1.wideContentBoard>iframe,#banC2.wideContentBoard>iframe,#banC3.wideContentBoard>iframe,#banC4.wideContentBoard>iframe,#banC9.wideContentBoard>iframe{position:absolute;right:0}#article_body #banC1.wideContentBoard iframe,#article_body #banC2.wideContentBoard iframe,#article_body #banC3.wideContentBoard iframe,#article_body #banC4.wideContentBoard iframe,#article_body #banC9.wideContentBoard iframe{max-width:initial}.DFP-201-PREMIUMBOARD-MOBI.activeBan,.DFP-201-PREMIUMBOARD.activeBan{display:none}@media screen and (orientation:portrait){div.oaoa__interstitial{height:100%!important}}@media all and (max-width:330px){.responsive .adviewDFPBanner.activeBan{padding-left:0!important;padding-right:0!important}}.DFP-019-TOPLAYER{margin:0!important}body.path_sport-hp .DFP-087-ADBOARD-A{position:relative;z-index:9}body.path_sport-hp .DFP-087-ADBOARD-A .banLabel{background:linear-gradient(rgba(255,255,255,0),#fff)}body.path_sport-hp .DFP-087-ADBOARD-A:before{content:"";position:absolute;bottom:-7px;left:0;width:100%;height:13px;background:linear-gradient(#fff,rgba(255,255,255,0))}.desk.screeningADFP.none #content #content_wrap{margin-top:0!important}body.screeningADFP.dfp-czterykaty #bottom_wrapper,body.screeningADFP.dfp-czterykaty #main_wrapper,body.screeningADFP.dfp-czterykaty #top_wrapper,body.screeningADFP.dfp-czterykaty .aside_wrapper{max-width:1242px;margin:0 auto}.fifContainer{font-size:.1px;line-height:0}.DFP-113-LOKALBUTTON{overflow:visible!important}</style>
+<!-- v2.6 -->
+
+
+<!-- v2.8 adapter biznes /biznes-->
+
+<!--10185226, [ /tpl/ads/prod/dfpHeader.jsp ], dfpBanersHeaderBean-->
+
+
+
+
+
+<!-- facebookConnect v1.41 -->
+
+
+
+
+
+
+<!--[if IE]><script>(function(t) { for (var i = 0, l = t.length; i < l; i++) document.createElement(t[i]); })(['fb','og']);</script><![endif]-->
+
+
+
+ <div id="fb-root"></div>
+ <script>
+ window.gazeta_pl = window.gazeta_pl || {};
+ window.gazeta_pl.functionQueue = window.gazeta_pl.functionQueue || [];
+ window.gazeta_pl.functionQueue.push(function(){
+ setTimeout(function() {
+ (function(d){
+ var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
+ js = d.createElement('script'); js.id = id; js.async = true;
+ js.src = "//connect.facebook.net/pl_PL/all.js";
+ d.getElementsByTagName('head')[0].appendChild(js);
+ }(document));
+ }, 2000);
+ function facebookReady(){
+ FB.init({
+ appId : 515714931781741,
+ status : true,
+ cookie : true,
+ xfbml : true,
+ oauth : true,
+ version: 'v2.2'
+ });
+ }
+ if(window.FB) {
+ facebookReady();
+ } else {
+ window.fbAsyncInit = facebookReady;
+ }
+ });
+ </script>
+
+
+
+<!--9638937, [ /fix/modules/facebook/facebookConnect.jsp ], null-->
+
+</head>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<body id="pagetype_art" class=" awd path_147743 path_biznes simpleArt unknown Other unknown LINUX ">
+
+ <!-- pattern#ms - Article 7 - app18:tomcat-wyborcza: - - 3200118 - s3200007_P20_wyb_2017_desk.jsp - v1.0.1 -->
+
+
+
+<!-- WyborczaSVG version: 17.0.28 compiled: 2018-08-28 10:52:39 --> <svg id="svgicons" style="display:block;width:0;height:0;"><symbol id="admin-censore" viewBox="0 0 19.5 19.5"><path d="M438,444.25a9.75,9.75,0,1,1,9.75-9.75A9.76,9.76,0,0,1,438,444.25Zm0-17a7.25,7.25,0,1,0,7.25,7.25A7.26,7.26,0,0,0,438,427.25Z" transform="translate(-428.25 -424.75)"/><path d="M430.52,436.84L443.74,429l1.59,2.71-13.22,7.81Z" transform="translate(-428.25 -424.75)"/></symbol><symbol id="admin-clear" viewBox="0 0 19.63 16.99"><path d="M446.86,418.74v-2h-2.61l-6.12-6.22-4.76,4.71h0l-1.41,1.41h0l-4.71,4.66,6.12,6.22,4.76-4.71h0l1.41-1.41h0l2.68-2.65h4.64Zm-13.48,5.51-2.9-2.94,3.09-3.06,2.92,2.92Zm4.53-4.48L435,416.85l3.11-3.08,2.9,2.94Z" transform="translate(-427.24 -410.51)"/></symbol><symbol id="admin-delete" viewBox="0 0 166.86 170"><polygon points="73.5 170 0 170 0 31.87 30.6 0 141 0 141 86.28 118 86.28 118 23 40.4 23 23 41.13 23 147 73.5 147 73.5 170"/><rect x="33.5" y="40.5" width="75" height="13"/><rect x="33.5" y="65.5" width="75" height="13"/><polygon points="166.86 158.06 140.19 132.39 166.86 104.71 155.67 93.53 129 120.2 102.33 93.53 91.14 104.71 117.81 131.39 91.14 158.06 102.33 169.25 129 142.57 155.67 169.25 166.86 158.06"/></symbol><symbol id="admin-expose" viewBox="0 0 36.54 34.75"><polygon points="18.27 0 23.92 11.44 36.54 13.27 27.41 22.18 29.56 34.75 18.27 28.81 6.98 34.75 9.13 22.18 0 13.27 12.62 11.44 18.27 0"/></symbol><symbol id="admin-publish" viewBox="0 0 19.5 19.5"><polygon points="14.44 8.91 10.59 8.91 10.59 5.06 8.91 5.06 8.91 8.91 5.06 8.91 5.06 10.59 8.91 10.59 8.91 14.44 10.59 14.44 10.59 10.59 14.44 10.59 14.44 8.91"/><path d="M423,489.33a9.75,9.75,0,1,1,9.75-9.75A9.75,9.75,0,0,1,423,489.33Zm0-17a7.25,7.25,0,1,0,7.25,7.25h0A7.26,7.26,0,0,0,423,472.33Z" transform="translate(-413.25 -469.83)"/></symbol><symbol id="admin-undo-expose" viewBox="0 0 16 13.99"><polygon points="16 2.54 6.47 2.54 7.38 1.5 6.08 0 0 7 1.3 8.49 1.3 8.49 6.08 13.99 7.38 12.49 6.47 11.45 16 11.45 16 2.54"/></symbol><symbol id="app-store" viewBox="0 0 135 40"><g><path class="store-frame" d="m130.2 40h-125.47c-2.608 0-4.73-2.128-4.73-4.733v-30.541c0-2.606 2.122-4.726 4.729-4.726h125.47c2.6 0 4.8 2.12 4.8 4.726v30.541c0 2.605-2.2 4.733-4.8 4.733z" fill="#A6A6A6"/><path d="m134.03 35.268c0 2.116-1.714 3.83-3.834 3.83h-125.47c-2.119 0-3.839-1.714-3.839-3.83v-30.543c0-2.115 1.72-3.835 3.839-3.835h125.47c2.121 0 3.834 1.72 3.834 3.835v30.543z" class="store-bkg"/></g><g fill="#fff"><path d="m30.128 19.795c-0.029-3.223 2.639-4.791 2.761-4.864-1.511-2.203-3.853-2.504-4.676-2.528-1.967-0.207-3.875 1.177-4.877 1.177-1.022 0-2.565-1.157-4.228-1.123-2.14 0.033-4.142 1.272-5.24 3.196-2.266 3.923-0.576 9.688 1.595 12.858 1.086 1.553 2.355 3.287 4.016 3.226 1.625-0.067 2.232-1.036 4.193-1.036 1.943 0 2.513 1.036 4.207 0.997 1.744-0.028 2.842-1.56 3.89-3.127 1.255-1.78 1.759-3.533 1.779-3.623-0.04-0.013-3.386-1.29-3.42-5.153z"/><path d="m26.928 10.317c0.874-1.093 1.472-2.58 1.306-4.089-1.265 0.056-2.847 0.875-3.758 1.944-0.806 0.942-1.526 2.486-1.34 3.938 1.421 0.106 2.88-0.716 3.792-1.793z"/></g><g fill="#fff"><path d="m53.646 31.516h-2.271l-1.244-3.91h-4.324l-1.185 3.91h-2.211l4.284-13.308h2.646l4.305 13.308zm-3.89-5.549l-1.125-3.475c-0.119-0.355-0.342-1.191-0.671-2.508h-0.04c-0.131 0.566-0.342 1.402-0.632 2.508l-1.105 3.475h3.573z"/><path d="m64.663 26.599c0 1.632-0.441 2.923-1.323 3.87-0.79 0.842-1.771 1.264-2.942 1.264-1.264 0-2.172-0.455-2.725-1.363h-0.04v5.055h-2.132v-10.347c0-1.026-0.027-2.079-0.079-3.158h1.875l0.119 1.52h0.04c0.711-1.145 1.79-1.717 3.238-1.717 1.132 0 2.077 0.447 2.833 1.342 0.757 0.895 1.136 2.073 1.136 3.534zm-2.172 0.079c0-0.935-0.21-1.705-0.632-2.311-0.461-0.631-1.08-0.947-1.856-0.947-0.526 0-1.004 0.175-1.431 0.523-0.428 0.349-0.708 0.807-0.839 1.372-0.066 0.264-0.099 0.48-0.099 0.651v1.6c0 0.697 0.214 1.287 0.642 1.768s0.984 0.721 1.668 0.721c0.803 0 1.428-0.311 1.875-0.928 0.448-0.619 0.672-1.436 0.672-2.449z"/><path d="m75.7 26.599c0 1.632-0.441 2.923-1.324 3.87-0.789 0.842-1.77 1.264-2.941 1.264-1.264 0-2.172-0.455-2.725-1.363h-0.039v5.055h-2.132v-10.347c0-1.026-0.027-2.079-0.079-3.158h1.875l0.119 1.52h0.04c0.711-1.145 1.789-1.717 3.238-1.717 1.131 0 2.076 0.447 2.834 1.342 0.755 0.895 1.134 2.073 1.134 3.534zm-2.172 0.079c0-0.935-0.211-1.705-0.633-2.311-0.461-0.631-1.078-0.947-1.855-0.947-0.527 0-1.004 0.175-1.432 0.523s-0.707 0.807-0.839 1.372c-0.065 0.264-0.099 0.48-0.099 0.651v1.6c0 0.697 0.214 1.287 0.641 1.768 0.428 0.48 0.984 0.721 1.67 0.721 0.803 0 1.428-0.311 1.875-0.928 0.448-0.619 0.672-1.436 0.672-2.449z"/><path d="m88.04 27.783c0 1.133-0.394 2.053-1.182 2.764-0.867 0.777-2.075 1.166-3.625 1.166-1.432 0-2.581-0.277-3.449-0.83l0.494-1.777c0.935 0.566 1.962 0.85 3.081 0.85 0.804 0 1.429-0.182 1.877-0.543 0.447-0.363 0.671-0.848 0.671-1.455 0-0.539-0.185-0.994-0.553-1.363s-0.98-0.713-1.836-1.029c-2.33-0.869-3.495-2.143-3.495-3.816 0-1.094 0.408-1.991 1.225-2.69 0.815-0.699 1.901-1.048 3.258-1.048 1.211 0 2.218 0.211 3.021 0.632l-0.533 1.738c-0.75-0.408-1.599-0.612-2.547-0.612-0.75 0-1.336 0.185-1.757 0.553-0.355 0.329-0.533 0.73-0.533 1.204 0 0.526 0.204 0.961 0.612 1.304 0.355 0.315 1 0.658 1.935 1.026 1.146 0.461 1.987 1.001 2.527 1.618s0.809 1.387 0.809 2.308z"/><path d="m95.088 23.519h-2.35v4.659c0 1.186 0.415 1.777 1.244 1.777 0.382 0 0.697-0.033 0.948-0.098l0.059 1.619c-0.421 0.156-0.974 0.236-1.658 0.236-0.843 0-1.501-0.258-1.975-0.77-0.474-0.514-0.711-1.377-0.711-2.588v-4.837h-1.401v-1.597h1.401v-1.758l2.093-0.632v2.39h2.35v1.599z"/><path d="m105.69 26.639c0 1.475-0.422 2.686-1.264 3.633-0.882 0.975-2.054 1.461-3.515 1.461-1.409 0-2.53-0.467-3.366-1.402-0.836-0.934-1.254-2.113-1.254-3.533 0-1.488 0.431-2.705 1.293-3.653s2.024-1.421 3.485-1.421c1.408 0 2.54 0.467 3.396 1.401 0.8 0.907 1.21 2.078 1.21 3.514zm-2.21 0.068c0-0.885-0.19-1.643-0.572-2.277-0.447-0.766-1.086-1.148-1.915-1.148-0.856 0-1.508 0.383-1.955 1.148-0.382 0.635-0.572 1.406-0.572 2.317 0 0.885 0.19 1.644 0.572 2.276 0.461 0.766 1.105 1.148 1.936 1.148 0.815 0 1.454-0.389 1.915-1.168 0.38-0.646 0.58-1.411 0.58-2.296z"/><path d="m112.62 23.795c-0.211-0.039-0.435-0.059-0.671-0.059-0.751 0-1.33 0.283-1.738 0.849-0.355 0.501-0.533 1.132-0.533 1.896v5.035h-2.132l0.02-6.575c0-1.106-0.026-2.112-0.079-3.021h1.856l0.079 1.836h0.059c0.224-0.632 0.579-1.139 1.066-1.521 0.474-0.343 0.987-0.513 1.54-0.513 0.197 0 0.375 0.013 0.533 0.039v2.034z"/><path d="m122.16 26.264c0 0.381-0.026 0.703-0.079 0.967h-6.396c0.025 0.947 0.335 1.672 0.928 2.172 0.539 0.447 1.237 0.672 2.093 0.672 0.947 0 1.81-0.152 2.587-0.455l0.335 1.48c-0.908 0.396-1.981 0.594-3.218 0.594-1.488 0-2.656-0.438-3.505-1.314-0.849-0.875-1.273-2.049-1.273-3.523 0-1.447 0.395-2.652 1.185-3.613 0.829-1.027 1.948-1.54 3.356-1.54 1.382 0 2.429 0.513 3.14 1.54 0.55 0.815 0.84 1.821 0.84 3.02zm-2.04-0.553c0.013-0.633-0.126-1.179-0.415-1.64-0.368-0.593-0.935-0.888-1.698-0.888-0.697 0-1.264 0.289-1.697 0.868-0.355 0.461-0.566 1.014-0.632 1.659h4.45z"/></g><g fill="#fff"><path d="m47.867 8.808c0 0.602-0.178 1.083-0.533 1.445-0.459 0.472-1.129 0.708-2.008 0.708-0.259 0-0.46-0.016-0.602-0.048v2.532h-1.048v-6.451c0.499-0.09 1.069-0.136 1.711-0.136 0.83 0 1.455 0.178 1.877 0.534 0.402 0.349 0.603 0.821 0.603 1.416zm-1.048 0.048c0-0.382-0.122-0.674-0.366-0.878-0.245-0.204-0.586-0.306-1.023-0.306-0.29 0-0.525 0.02-0.705 0.058v2.348c0.147 0.039 0.351 0.058 0.608 0.058 0.463 0 0.827-0.113 1.091-0.339s0.395-0.54 0.395-0.941z"/><path d="m53.727 11.048c0 0.725-0.207 1.319-0.621 1.785-0.434 0.479-1.009 0.718-1.727 0.718-0.692 0-1.243-0.229-1.654-0.689-0.41-0.459-0.615-1.038-0.615-1.736 0-0.73 0.211-1.329 0.635-1.794s0.994-0.698 1.712-0.698c0.692 0 1.248 0.229 1.669 0.688 0.399 0.446 0.601 1.022 0.601 1.726zm-1.087 0.035c0-0.435-0.094-0.808-0.281-1.119-0.22-0.376-0.533-0.564-0.94-0.564-0.421 0-0.741 0.188-0.961 0.564-0.188 0.311-0.281 0.69-0.281 1.138 0 0.435 0.094 0.808 0.281 1.119 0.227 0.376 0.543 0.564 0.951 0.564 0.4 0 0.714-0.191 0.94-0.574 0.194-0.318 0.291-0.694 0.291-1.128z"/><path d="m59.769 11.02c0 0.795-0.22 1.429-0.659 1.901-0.388 0.42-0.863 0.631-1.426 0.631-0.673 0-1.168-0.278-1.484-0.834h-0.02l-0.058 0.728h-0.893c0.025-0.381 0.038-0.805 0.038-1.271v-5.608h1.048v2.852h0.02c0.311-0.524 0.812-0.786 1.504-0.786 0.568 0 1.032 0.218 1.392 0.655 0.358 0.437 0.538 1.014 0.538 1.732zm-1.067 0.038c0-0.459-0.104-0.834-0.311-1.125-0.227-0.317-0.534-0.476-0.922-0.476-0.259 0-0.491 0.084-0.698 0.252s-0.346 0.391-0.417 0.669c-0.026 0.11-0.039 0.22-0.039 0.33v0.824c0 0.324 0.108 0.602 0.325 0.834s0.486 0.349 0.81 0.349c0.395 0 0.702-0.148 0.922-0.446 0.22-0.296 0.33-0.7 0.33-1.211z"/><path d="m62.485 7.324c0 0.188-0.062 0.339-0.184 0.456-0.123 0.117-0.281 0.175-0.476 0.175-0.175 0-0.322-0.06-0.441-0.179-0.12-0.12-0.18-0.27-0.18-0.451s0.062-0.33 0.185-0.446 0.274-0.175 0.456-0.175c0.181 0 0.333 0.059 0.456 0.175 0.123 0.115 0.184 0.264 0.184 0.445zm-0.116 6.12h-1.048v-4.714h1.048v4.714z"/><path d="m68.111 10.864c0 0.188-0.014 0.346-0.039 0.475h-3.142c0.013 0.466 0.164 0.821 0.455 1.067 0.266 0.22 0.608 0.33 1.028 0.33 0.466 0 0.89-0.074 1.271-0.223l0.164 0.728c-0.446 0.194-0.973 0.291-1.581 0.291-0.73 0-1.305-0.215-1.722-0.645s-0.625-1.007-0.625-1.731c0-0.711 0.193-1.303 0.582-1.775 0.407-0.504 0.956-0.756 1.648-0.756 0.679 0 1.193 0.252 1.542 0.756 0.28 0.4 0.419 0.895 0.419 1.483zm-1-0.271c0.007-0.311-0.062-0.579-0.203-0.805-0.182-0.291-0.459-0.437-0.834-0.437-0.343 0-0.621 0.142-0.835 0.427-0.174 0.227-0.277 0.498-0.31 0.815h2.182z"/><path d="m72.126 9.652c-0.104-0.02-0.213-0.029-0.33-0.029-0.368 0-0.652 0.139-0.854 0.417-0.174 0.246-0.262 0.556-0.262 0.931v2.474h-1.048l0.01-3.23c0-0.543-0.013-1.038-0.038-1.484h0.911l0.039 0.902h0.029c0.109-0.311 0.284-0.56 0.523-0.747 0.233-0.168 0.485-0.252 0.757-0.252 0.097 0 0.185 0.006 0.262 0.019v0.999z"/><path d="m76.83 13.444h-3.841v-0.611l1.882-2.473c0.116-0.155 0.329-0.411 0.64-0.767v-0.019h-2.338v-0.844h3.608v0.65l-1.843 2.435c-0.207 0.265-0.42 0.521-0.64 0.766v0.02h2.531v0.843z"/><path d="m87.365 8.73l-1.475 4.714h-0.96l-0.611-2.047c-0.155-0.511-0.281-1.019-0.379-1.523h-0.019c-0.091 0.518-0.217 1.025-0.379 1.523l-0.649 2.047h-0.971l-1.386-4.714h1.077l0.533 2.241c0.129 0.53 0.235 1.035 0.32 1.513h0.019c0.078-0.394 0.207-0.896 0.389-1.503l0.669-2.25h0.854l0.641 2.202c0.155 0.537 0.281 1.054 0.378 1.552h0.029c0.071-0.485 0.178-1.002 0.32-1.552l0.572-2.202h1.028z"/></g></symbol><symbol id="arrow-back" viewBox="0 0 17 9"><path d="M0,4.5L5.7,0v2.2C15.9,2.2,17,7.9,17,9C14.7,4.5,6.8,6.8,5.7,6.8V9L0,4.5z"/></symbol><symbol id="bell" viewBox="0 0 32 32"><path d="M15.7,29.5c-2.8,0-4-1.9-4.2-2.9c-0.1-0.2,0.1-0.5,0.3-0.5c0.2-0.1,0.5,0.1,0.5,0.3
+ c0.2,0.8,1.1,2.2,3.4,2.2c2.3,0,3.2-1.4,3.4-2.2c0.1-0.2,0.3-0.4,0.5-0.3c0.2,0.1,0.4,0.3,0.3,0.5C19.7,27.6,18.5,29.5,15.7,29.5z"/><path d="M26.2,22.8c-1.2-1-3.7-3.1-3.7-7.7c0-4.2-1.6-6.5-2.9-7.6c-1.2-1.1-2.6-1.5-3.4-1.6V3.8
+ c0-0.2-0.2-0.4-0.4-0.4c-0.2,0-0.4,0.2-0.4,0.4v2.1c-0.8,0.1-2.2,0.5-3.4,1.6C10.5,8.7,9,10.9,9,15.1c0,4.5-2.5,6.6-3.7,7.7
+ c-0.5,0.5-0.9,0.7-0.7,1.1c0.2,0.5,0.7,0.5,2.4,0.5h17.7c1.7,0,2.2,0,2.4-0.5C27,23.5,26.7,23.3,26.2,22.8z M24.5,23.6H6.8
+ c-0.3,0-0.8,0-1.2,0c0,0,0.1-0.1,0.1-0.1c1.2-1,4.1-3.4,4.1-8.3c0-3.1,0.9-5.5,2.6-7c1.4-1.2,2.8-1.4,3.3-1.4
+ c0.5,0,1.9,0.3,3.3,1.4c1.7,1.5,2.6,3.8,2.6,7c0,4.9,2.8,7.3,4.1,8.3c0,0,0.1,0.1,0.1,0.1C25.3,23.6,24.8,23.6,24.5,23.6z"/></symbol><symbol id="blog" viewBox="0 0 38.2 39.4"><path d="m 30.9,37.8 -23.5,0 -4.3,1.6 27.8,0 c 0.4,0 0.8,-0.3 0.8,-0.8 0,-0.5 -0.3,-0.8 -0.8,-0.8 z"/><path d="M 38.2,5.6 32.8,0 28.9,3.9 25.3,0.3 18.1,7.6 19.2,8.7 25.4,2.4 27.8,5 3.1,29.7 c 0,0 0,0 0,0 0,0 0,0 0,0 L 2.9,29.9 2.8,30 0,38.3 8.2,35.6 38.2,5.6 Z M 32.8,2.1 36.2,5.5 33.4,8.3 29.9,5 32.8,2.1 Z M 2.4,35.9 3.8,31.6 6.7,34.5 2.4,35.9 Z M 4.6,30.3 28.9,6 32.3,9.4 8,33.6 4.6,30.3 Z"/></symbol><symbol id="burger" viewBox="-338 41.4 32 32"><rect x="-330.4" y="61.7" width="16.8" height="1.5"/><rect x="-330.4" y="56.6" width="16.8" height="1.5"/><rect x="-330.4" y="51.6" width="16.8" height="1.5"/></symbol><symbol viewBox="0 0 19.5 30" id="chevron"><path d="M 15,0 0,15 15,30 19.5,25.5 9,15 19.5,4.5 Z M 15,1 18.5,4.5 8,15 18.5,25.5 15,29 1,15 Z"/></symbol><symbol id="clock" viewBox="0 0 15 15"><path d="M7.5,0A7.5,7.5,0,1,0,15,7.5,7.5,7.5,0,0,0,7.5,0Zm4.91,8H7V7.5H7V2.59H8V7h4.41Z"/></symbol><symbol id="close-symbol" viewBox="0 0 18 18"><path d="M 0.7,18 0,17.2 17.3,0 18,0.8 0.7,18 z M 0,0.7 0.8,0 18,17.3 17.2,18 0,0.7 z"/></symbol><symbol id="comment" viewBox="0 0 20 20"><path d="M16.7,13.6c1.4,0,2.5-1.1,2.5-2.5V3.5c0-1.3-1.2-2.5-2.5-2.5H3.8C2.3,0.9,1.3,2,1.3,3.5V11 c0,1.3,1.2,2.5,2.5,2.5h1.7V19l4.8-5.4C10.2,13.6,16.7,13.6,16.7,13.6z"/></symbol><symbol id="commentary" viewBox="0 0 17.9 18"><path d="m 15.4,12.606309 c 1.4,0 2.5,-1.1 2.5,-2.5 V 2.5063091 c 0,-1.3 -1.2,-2.49999999 -2.5,-2.49999999 H 2.5 C 1,-0.09369089 0,1.0063091 0,2.5063091 v 7.4999999 c 0,1.3 1.2,2.5 2.5,2.5 h 1.7 v 5.5 l 4.8,-5.4 c -0.1,0 6.4,0 6.4,0 z"/></symbol><symbol id="comments-v2" viewBox="0 0 34 34"><path d="M11,29v-6H5V8h24v15H17L11,29z"/><path d="M13.3,14.7c0.4,0,0.7,0.3,0.7,0.7s-0.3,0.7-0.7,0.7s-0.7-0.3-0.7-0.7S12.9,14.7,13.3,14.7z"/><path d="M17,14.7c0.4,0,0.7,0.3,0.7,0.7s-0.3,0.7-0.7,0.7s-0.7-0.3-0.7-0.7S16.6,14.7,17,14.7z"/><path d="M20.7,14.7c0.4,0,0.7,0.3,0.7,0.7s-0.3,0.7-0.7,0.7S20,15.8,20,15.4S20.3,14.7,20.7,14.7z"/></symbol><symbol id="diamond" viewBox="0 0 32 32"><path d="M16,29.2L0.9,14v-1.2l5.7-6.5h18.9l5.7,6.5V14L16,29.2z M1.9,13.6L16,27.8l14.1-14.1v-0.4L25,7.4H7l-5.1,5.8V13.6z"/><rect x="1.4" y="12.6" width="29.3" height="1"/><polygon points="16.5,28.7 15.5,28.3 22.4,13.1 19.4,7.1 20.3,6.7 23.5,13 "/><polygon points="15.5,28.7 8.5,13 11.7,6.7 12.6,7.1 9.6,13.1 16.5,28.3 "/></symbol><symbol id="dots" viewBox="0 0 27 7"><path d="M 3.5,0 C 1.6,0 0,1.6 0,3.5 0,5.4 1.6,7 3.5,7 5.4,7 7,5.4 7,3.5 7,1.6 5.4,0 3.5,0 z m 10,0 C 11.6,0 10,1.6 10,3.5 10,5.4 11.6,7 13.5,7 15.4,7 17,5.4 17,3.5 17,1.6 15.4,0 13.5,0 z m 10,0 C 21.6,0 20,1.6 20,3.5 20,5.4 21.6,7 23.5,7 25.4,7 27,5.4 27,3.5 27,1.6 25.4,0 23.5,0 z"/></symbol><symbol id="down-arrow" viewBox="0 0 21.2 14.1"><rect x="6.7" y="4.6" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -0.8498 12.1094)" width="15" height="5"/><rect x="4.6" y="-0.4" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -2.917 7.1)" width="5" height="15"/></symbol><symbol id="envelope" viewBox="0 0 50 32"><path d="M 48.1,-2.4512111e-7 H 2.1 1.7 0.7 0 V 32 H 50 V -2.4512111e-7 H 48.3 48.1 z M 46.1,1.9999998 26.7,21.3 c -0.9,0.9 -2.4,0.9 -3.3,0 L 4.1,1.9999998 h 42 z m -44,0.8 L 15.1,15.8 2.1,28.8 V 2.7999998 z M 3.7,30 16.5,17.2 22,22.7 c 0.8,0.8 1.9,1.3 3.1,1.3 1.2,0 2.2,-0.5 3.1,-1.3 L 33.7,17.2 46.5,30 H 3.7 z m 44.4,-1.2 -13,-13 13,-13.0000002 V 28.8 z"/></symbol><symbol id="eye" viewBox="0 0 32 20"><path d="M 16,0 C 7.2,0 0,8 0,10 0,11.8 7.2,20 16,20 24.8,20 32,12 32,10 32,8 24.8,0 16,0 z m 0,16 c -3.3,0 -6,-2.7 -6,-6 0,-3.3 2.2,-6 7,-6 l -1,6 6,-2 c 0,5.6 -2.7,8 -6,8 z"/></symbol><symbol id="face-v2" viewBox="0 0 34 34"><path d="M19,31h-5V19h-3v-4h3V8c0-1.8,0.6-2.9,1.4-3.7C16.8,3,19.5,3,19.9,3L23,3v5h-2c-1.1,0-2,0.4-2,2v5h4v4h-4V31z M22,7"/></symbol><symbol id="face" viewBox="0 0 13.4 29.4"><path d="M 8.1,10.508657 V 6.0086565 c 0,0 0,-1.8 1.9,-1.8 h 2.8 V 0.00865651 H 9.3 c 0,0 -5.9,-0.5 -5.9,6.09999999 0,1.4 0,4.5000005 0,4.5000005 H 0 v 3.4 h 3.4 v 15.4 h 4.7 v -15.4 h 4.4 l 0.9,-3.5 H 8.1 z"/></symbol><symbol id="facebook-logo" viewBox="0 0 28 28"><path fill="inherit" d="m 18.339322,13.572791 h -3.27 v 9.42 h -3.52 v -9.43 H 9.0493224 v -2.13 H 11.559322 V 8.7027914 c 0,-4 4.42,-3.69 4.42,-3.69 h 2.6 v 2.57 h -2.1 a 1.17,1.17 0 0 0 -1.41,1.06 v 2.7899996 h 4 z"></path></symbol><symbol id="gallery" viewBox="0 0 20 15"><path d="M 18.1,1.7999998 H 13.9 L 13.5,0.89999981 C 13.2,0.39999981 12.6,-1.9073486e-7 12.1,-1.9073486e-7 H 7.7 C 7.2,-1.9073486e-7 6.6,0.29999981 6.4,0.89999981 L 6,1.7999998 H 1.8 c -1.2,0 -1.8,0.8 -1.8,1.9 V 13.3 c 0,1 0.6,1.8 1.8,1.8 h 16.4 c 1.1,0 1.8,-0.8 1.8,-1.8 V 3.5999998 c -0.1,-1.2 -0.8,-1.8 -1.9,-1.8 z M 10,13.2 c -3,0 -5.5,-2.4 -5.5,-5.3000002 0.1,-2.9 2.5,-5.3 5.5,-5.3 3,0 5.5,2.4 5.5,5.3 C 15.4,10.9 12.9,13.2 10,13.2 z m 0,-8.8000002 c -2,0 -3.7,1.6 -3.7,3.6 0,2 1.7,3.6000002 3.7,3.6000002 2,0 3.7,-1.6000002 3.7,-3.6000002 0,-2 -1.7,-3.6 -3.7,-3.6 z"/></symbol><symbol id="google-play" viewBox="0 0 135 40"><g><path d="m130 40h-125c-2.8 0-5-2.2-5-5v-30c0-2.7 2.2-5 5-5h125c2.8 0 5 2.2 5 5v30c0 2.7-2.3 5-5 5z" class="store-bkg"/><path class="store-frame" d="m130 0.8c2.3 0 4.2 1.9 4.2 4.2v30c0 2.3-1.9 4.2-4.2 4.2h-125c-2.3 0-4.2-1.9-4.2-4.2v-30c0-2.3 1.9-4.2 4.2-4.2h125m0-0.8h-125c-2.8 0-5 2.3-5 5v30c0 2.8 2.2 5 5 5h125c2.8 0 5-2.2 5-5v-30c0-2.7-2.3-5-5-5z" fill="#A6A6A6"/></g><g><path d="m68.1 21.8c-2.4 0-4.3 1.8-4.3 4.3 0 2.4 1.9 4.3 4.3 4.3s4.3-1.8 4.3-4.3c0-2.6-2-4.3-4.3-4.3zm0 6.8c-1.3 0-2.4-1.1-2.4-2.6s1.1-2.6 2.4-2.6 2.4 1 2.4 2.6c0 1.5-1.1 2.6-2.4 2.6zm-9.3-6.8c-2.4 0-4.3 1.8-4.3 4.3 0 2.4 1.9 4.3 4.3 4.3s4.3-1.8 4.3-4.3c0-2.6-2-4.3-4.3-4.3zm0 6.8c-1.3 0-2.4-1.1-2.4-2.6s1.1-2.6 2.4-2.6 2.4 1 2.4 2.6c0 1.5-1.1 2.6-2.4 2.6zm-11.1-5.5v1.8h4.3c-0.1 1-0.5 1.8-1 2.3-0.6 0.6-1.6 1.3-3.3 1.3-2.7 0-4.7-2.1-4.7-4.8s2.1-4.8 4.7-4.8c1.4 0 2.5 0.6 3.3 1.3l1.3-1.3c-1.1-1-2.5-1.8-4.5-1.8-3.6 0-6.7 3-6.7 6.6s3.1 6.6 6.7 6.6c2 0 3.4-0.6 4.6-1.9 1.2-1.2 1.6-2.9 1.6-4.2 0-0.4 0-0.8-0.1-1.1h-6.2zm45.3 1.4c-0.4-1-1.4-2.7-3.6-2.7s-4 1.7-4 4.3c0 2.4 1.8 4.3 4.2 4.3 1.9 0 3.1-1.2 3.5-1.9l-1.4-1c-0.5 0.7-1.1 1.2-2.1 1.2s-1.6-0.4-2.1-1.3l5.7-2.4-0.2-0.5zm-5.8 1.4c0-1.6 1.3-2.5 2.2-2.5 0.7 0 1.4 0.4 1.6 0.9l-3.8 1.6zm-4.6 4.1h1.9v-12.5h-1.9v12.5zm-3.1-7.3c-0.5-0.5-1.3-1-2.3-1-2.1 0-4.1 1.9-4.1 4.3s1.9 4.2 4.1 4.2c1 0 1.8-0.5 2.2-1h0.1v0.6c0 1.6-0.9 2.5-2.3 2.5-1.1 0-1.9-0.8-2.1-1.5l-1.6 0.7c0.5 1.1 1.7 2.5 3.8 2.5 2.2 0 4-1.3 4-4.4v-7.6h-1.8v0.7zm-2.1 5.9c-1.3 0-2.4-1.1-2.4-2.6s1.1-2.6 2.4-2.6 2.3 1.1 2.3 2.6-1 2.6-2.3 2.6zm24.4-11.1h-4.5v12.5h1.9v-4.7h2.6c2.1 0 4.1-1.5 4.1-3.9s-2.1-3.9-4.1-3.9zm0 6h-2.7v-4.3h2.7c1.4 0 2.2 1.2 2.2 2.1 0 1.1-0.8 2.2-2.2 2.2zm11.5-1.8c-1.4 0-2.8 0.6-3.3 1.9l1.7 0.7c0.4-0.7 1-0.9 1.7-0.9 1 0 1.9 0.6 2 1.6v0.1c-0.3-0.2-1.1-0.5-1.9-0.5-1.8 0-3.6 1-3.6 2.8 0 1.7 1.5 2.8 3.1 2.8 1.3 0 1.9-0.6 2.4-1.2h0.1v1h1.8v-4.8c-0.2-2.2-1.8-3.5-4-3.5zm-0.2 6.9c-0.6 0-1.5-0.3-1.5-1.1 0-1 1.1-1.3 2-1.3 0.8 0 1.2 0.2 1.7 0.4-0.1 1.2-1.1 2-2.2 2zm10.6-6.6l-2.1 5.4h-0.1l-2.2-5.4h-2l3.3 7.6-1.9 4.2h1.9l5.1-11.8h-2zm-16.8 8h1.9v-12.5h-1.9v12.5z" fill="#fff"/></g><g><path d="m10.4 7.5c-0.3 0.3-0.5 0.8-0.5 1.4v22.1c0 0.6 0.2 1.1 0.5 1.4l0.1 0.1 12.4-12.4v-0.2l-12.5-12.4z" fill="#00d3ff"/><path d="m27 24.3l-4.1-4.1v-0.3l4.1-4.1 0.1 0.1 4.9 2.8c1.4 0.8 1.4 2.1 0 2.9l-5 2.7z" fill="#ffb500"/><path d="m27.1 24.2l-4.2-4.2-12.5 12.5c0.5 0.5 1.2 0.5 2.1 0.1l14.6-8.4" fill="#f73448"/><path d="m27.1 15.8l-14.6-8.3c-0.9-0.5-1.6-0.4-2.1 0.1l12.5 12.4 4.2-4.2z" fill="#00f076"/></g><g fill="#fff"><path d="m47.9 8.8c0 0.6-0.2 1.1-0.5 1.4-0.5 0.5-1.1 0.7-2 0.7h-0.6v2.5h-1v-6.4c0.5-0.1 1.1-0.1 1.7-0.1 0.8 0 1.5 0.2 1.9 0.5 0.3 0.3 0.5 0.8 0.5 1.4zm-1.1 0.1c0-0.4-0.1-0.7-0.4-0.9-0.2-0.2-0.6-0.3-1-0.3-0.3 0-0.5 0-0.7 0.1v2.3c0.1 0 0.4 0.1 0.6 0.1 0.5 0 0.8-0.1 1.1-0.3s0.4-0.6 0.4-1z"/><path d="m53.7 11c0 0.7-0.2 1.3-0.6 1.8s-1 0.7-1.7 0.7-1.2-0.2-1.7-0.7c-0.4-0.5-0.6-1-0.6-1.7s0.2-1.3 0.6-1.8 1-0.7 1.7-0.7c0.7 0 1.2 0.2 1.7 0.7 0.4 0.5 0.6 1 0.6 1.7zm-1.1 0.1c0-0.4-0.1-0.8-0.3-1.1-0.2-0.4-0.5-0.6-0.9-0.6s-0.7 0.2-1 0.6c-0.2 0.3-0.3 0.7-0.3 1.1s0.1 0.8 0.3 1.1c0.2 0.4 0.5 0.6 1 0.6 0.4 0 0.7-0.2 0.9-0.6 0.2-0.3 0.3-0.7 0.3-1.1z"/><path d="m59.8 11c0 0.8-0.2 1.4-0.7 1.9-0.4 0.4-0.9 0.6-1.4 0.6-0.7 0-1.2-0.3-1.5-0.8l-0.1 0.7h-0.9v-1.3-5.5h1v2.9c0.3-0.5 0.8-0.8 1.5-0.8 0.6 0 1 0.2 1.4 0.7 0.5 0.3 0.7 0.9 0.7 1.6zm-1.1 0.1c0-0.5-0.1-0.8-0.3-1.1s-0.5-0.5-0.9-0.5c-0.3 0-0.5 0.1-0.7 0.3s-0.3 0.4-0.4 0.7v0.3 0.8c0 0.3 0.1 0.6 0.3 0.8s0.5 0.3 0.8 0.3c0.4 0 0.7-0.1 0.9-0.4s0.3-0.7 0.3-1.2z"/><path d="m62.5 7.3c0 0.2-0.1 0.3-0.2 0.5-0.1 0.1-0.3 0.2-0.5 0.2s-0.3-0.1-0.4-0.2-0.2-0.3-0.2-0.5 0.1-0.3 0.2-0.4 0.3-0.2 0.5-0.2c0.2 0 0.3 0.1 0.5 0.2 0 0.1 0.1 0.2 0.1 0.4zm-0.1 6.1h-1v-4.7h1v4.7z"/><path d="m68.1 10.9v0.5h-3.1c0 0.5 0.2 0.8 0.5 1.1 0.3 0.2 0.6 0.3 1 0.3 0.5 0 0.9-0.1 1.3-0.2l0.2 0.7c-0.4 0.2-1 0.3-1.6 0.3-0.7 0-1.3-0.2-1.7-0.6s-0.6-1-0.6-1.7c0-0.7 0.2-1.3 0.6-1.8s1-0.8 1.6-0.8c0.7 0 1.2 0.3 1.5 0.8 0.2 0.3 0.3 0.8 0.3 1.4zm-1-0.3c0-0.3-0.1-0.6-0.2-0.8-0.2-0.3-0.5-0.4-0.8-0.4s-0.6 0.1-0.8 0.4c-0.2 0.2-0.3 0.5-0.3 0.8h2.1z"/><path d="m72.1 9.7h-0.3c-0.4 0-0.7 0.1-0.9 0.4-0.2 0.2-0.3 0.6-0.3 0.9v2.5h-1v-3.2-1.5h0.9v0.9c0.1-0.3 0.3-0.6 0.5-0.7 0.2-0.2 0.5-0.3 0.8-0.3h0.3v1z"/><path d="m76.8 13.4h-3.8v-0.6l1.9-2.5c0.1-0.2 0.3-0.4 0.6-0.8h-2.3v-0.8h3.6v0.6l-1.8 2.4c-0.2 0.3-0.4 0.5-0.6 0.8h2.5l-0.1 0.9z"/><path d="m87.4 8.7l-1.5 4.7h-1l-0.6-2c-0.2-0.5-0.3-1-0.4-1.5-0.1 0.5-0.2 1-0.4 1.5l-0.6 2h-1l-1.4-4.7h1.1l0.5 2.2 0.3 1.5c0.1-0.4 0.2-0.9 0.4-1.5l0.7-2.2h0.9l0.6 2.2c0.2 0.5 0.3 1.1 0.4 1.6 0.1-0.5 0.2-1 0.3-1.6l0.6-2.2h1.1z"/></g></symbol><symbol id="google-plus" viewBox="0 0 28 18"><path d="M385.909,2756a9,9,0,0,0,0,18c5.142,0,8.553-3.65,8.553-8.79a8.417,8.417,0,0,0-.14-1.5h-8.413v3.09h5.053a4.827,4.827,0,0,1-5.053,3.88,5.682,5.682,0,0,1,0-11.36,5,5,0,0,1,3.551,1.38l2.418-2.35a8.515,8.515,0,0,0-5.969-2.35m16.546,5.14h-2.546v2.57h-2.545v2.57h2.545v2.58h2.546v-2.58H405v-2.57h-2.545v-2.57" transform="translate(-377 -2756)"/></symbol><symbol id="gplus-logo" viewBox="0 0 28 28"><path fill="inherit" d="m 24,14.86 h -1.82 v 1.71 h -1.82 v -1.71 h -1.81 v -1.72 h 1.82 v -1.71 h 1.82 v 1.71 H 24 v 1.71 z M 10.36,20 A 6.18,6.18 0 0 1 4,14 6.18,6.18 0 0 1 10.36,8 6.31,6.31 0 0 1 14.62,9.57 L 12.9,11.14 A 3.7,3.7 0 0 0 10.36,10.21 3.87,3.87 0 0 0 6.42,14 3.87,3.87 0 0 0 10.37,17.79 3.39,3.39 0 0 0 14,15.2 h -3.64 v -2.06 h 6 a 5.23,5.23 0 0 1 0.1,1 C 16.47,17.57 14,20 10.36,20 Z"/></symbol><symbol id="guidebook" viewBox="0 0 20.1 20"><path d="m13.2 9.5c-0.8-0.8-2.5-1.8-2.5-1.8s0.4-2.9 0-2.9-2.2 1.4-2.2 1.4c-1.7-0.7-2.9-0.7-2.9-0.7s0 1.2 0.7 2.9c0 0-1.4 1.8-1.4 2.2s2.9 0 2.9 0 1 1.7 1.8 2.5c0 0 0.7-2.9 0.4-3.3 0.4 0.5 3.2-0.3 3.2-0.3zm6.6 8.7-7.3-7.3c-0.4-0.4-1.1-0.4-1.5 0s-0.4 1.1 0 1.5l7.3 7.3c0.4 0.4 1.1 0.4 1.5 0s0.4-1.1 0-1.5zm-12.4-14.9s0.6 1 1 1.5c0 0 0.4-1.7 0.2-1.9 0.2 0.2 1.9-0.2 1.9-0.2-0.5-0.5-1.5-1-1.5-1s0.3-1.7 0-1.7c-0.2 0-1.2 0.8-1.2 0.8-1-0.4-1.7-0.4-1.7-0.4s0 0.7 0.4 1.7c0 0-0.8 1-0.8 1.2 0 0.3 1.7 0 1.7 0zm7.2 3.2s0.6 1 1 1.5c0 0 0.4-1.7 0.2-1.9 0.2 0.2 1.9-0.2 1.9-0.2-0.5-0.5-1.5-1-1.5-1s0.2-1.7 0-1.7-1.3 0.8-1.3 0.8c-1-0.4-1.7-0.4-1.7-0.4s0 0.7 0.4 1.7c0 0-0.8 1-0.8 1.3 0.1 0.2 1.8-0.1 1.8-0.1zm-7.2 7.1c-0.2 0-1.3 0.8-1.3 0.8-0.9-0.4-1.6-0.4-1.6-0.4s0 0.7 0.4 1.7c0 0-0.8 1-0.8 1.3 0 0.2 1.7 0 1.7 0s0.6 1 1 1.5c0 0 0.4-1.7 0.2-1.9 0.2 0.2 1.9-0.2 1.9-0.2-0.5-0.5-1.5-1-1.5-1s0.3-1.8 0-1.8zm-2.5-6.1c-0.5-0.5-1.5-1-1.5-1s0.2-1.7 0-1.7-1.3 0.8-1.3 0.8c-1-0.4-1.7-0.4-1.7-0.4s0 0.7 0.4 1.7c0 0-0.8 1-0.8 1.3 0 0.2 1.7 0 1.7 0s0.6 1 1 1.5c0 0 0.4-1.7 0.2-1.9 0.3 0.2 2-0.3 2-0.3z"/></symbol><symbol id="hamburger" viewBox="0 0 40 34"><path d="M20,32H60v6H20V32Zm0,14H60v6H20V46Zm0,14H60v6H20V60Z" transform="translate(-20 -32)"/></symbol><symbol id="infographic" viewBox="0 0 13 17"><path d="M 0,17 H 3 V 0 H 0 v 17 z m 5,0 H 8 V 8 H 5 v 9 z M 10,3 v 14 h 3 V 3 h -3 z"/></symbol><symbol id="instagram-v2" viewBox="0 0 30 30"><path d="M 15 3 C 11.75 3 11.340547 2.9996094 10.060547 3.0996094 A 8.73 8.73 0 0 0 7.1601562 3.6601562 A 5.53 5.53 0 0 0 5 5 A 5.77 5.77 0 0 0 3.6308594 7.1601562 A 8.73 8.73 0 0 0 3.0703125 10.060547 C 3.0003125 11.340547 3 11.75 3 15 C 3 18.25 2.9996094 18.660703 3.0996094 19.970703 A 8.73 8.73 0 0 0 3.6601562 22.869141 A 6.11 6.11 0 0 0 7.1601562 26.369141 A 8.73 8.73 0 0 0 10.060547 26.929688 C 11.340547 26.999688 11.75 27 15 27 C 18.25 27 18.659453 27.000391 19.939453 26.900391 A 8.73 8.73 0 0 0 22.839844 26.339844 A 6.1 6.1 0 0 0 26.339844 22.839844 A 8.73 8.73 0 0 0 26.900391 19.939453 C 27.000391 18.659453 27 18.25 27 15 C 27 11.75 27.000391 11.339297 26.900391 10.029297 A 8.73 8.73 0 0 0 26.339844 7.1308594 A 6.11 6.11 0 0 0 22.839844 3.6308594 A 8.73 8.73 0 0 0 19.939453 3.0703125 C 18.659453 3.0003125 18.25 3 15 3 z M 15 5.1699219 C 18.2 5.1699219 18.579844 5.2402344 19.839844 5.2402344 A 6.48 6.48 0 0 1 22.060547 5.6601562 A 3.69 3.69 0 0 1 23.439453 6.5507812 A 3.84 3.84 0 0 1 24.339844 7.9296875 A 6.71 6.71 0 0 1 24.75 10.150391 C 24.82 11.420391 24.820312 11.790234 24.820312 14.990234 C 24.820312 18.190234 24.8 18.570078 24.75 19.830078 A 6.71 6.71 0 0 1 24.339844 22.050781 A 4 4 0 0 1 22.060547 24.330078 A 6.71 6.71 0 0 1 19.839844 24.740234 C 18.579844 24.810234 18.2 24.810547 15 24.810547 C 11.8 24.810547 11.430156 24.790234 10.160156 24.740234 A 6.71 6.71 0 0 1 7.9394531 24.330078 A 3.84 3.84 0 0 1 6.5605469 23.429688 A 3.69 3.69 0 0 1 5.6699219 22.050781 A 6.48 6.48 0 0 1 5.25 19.830078 C 5.18 18.570078 5.1796875 18.190234 5.1796875 14.990234 C 5.1796875 11.790234 5.25 11.410391 5.25 10.150391 A 6.48 6.48 0 0 1 5.6699219 7.9296875 A 3.69 3.69 0 0 1 6.5605469 6.5507812 A 3.84 3.84 0 0 1 7.9394531 5.6503906 A 6.71 6.71 0 0 1 10.160156 5.2402344 C 11.430156 5.1702344 11.8 5.1699219 15 5.1699219 z "/><path d="M 14.744141 8.8554688 A 6.15 6.15 0 0 0 15 21.150391 A 6.15 6.15 0 0 0 21.150391 15 A 6.15 6.15 0 0 0 14.744141 8.8554688 z M 15 11 A 4 4 0 0 1 19 15 A 4 4 0 1 1 15 11 z "/><path d="m 21.39,10.05 a 1.44,1.44 0 1 1 1.44,-1.44 1.43,1.43 0 0 1 -1.44,1.44 z"/></symbol><symbol id="instagram" viewBox="0 0 19.8 19.4"><path class="st0" d="M17.3,0H2.6C1.2,0,0,1.1,0,2.5v14.4c0,1.4,1.2,2.5,2.5,2.5h14.7c1.4,0,2.5-1.1,2.5-2.5V2.5 C19.8,1.1,18.7,0,17.3,0z M14.2,3C14.2,3,14.2,3,14.2,3c0-0.4,0.3-0.7,0.8-0.7h1.8c0.4,0,0.7,0.3,0.8,0.7v1.8c0,0.4-0.3,0.7-0.8,0.7 h-1.8c0,0,0,0,0,0c-0.4,0-0.7-0.3-0.7-0.7V3z M9.9,5.5L9.9,5.5L9.9,5.5L9.9,5.5c2.3,0,4.1,1.9,4.1,4.2s-1.9,4.1-4.2,4.1 c-2.3,0-4.1-1.9-4.1-4.2C5.7,7.4,7.6,5.5,9.9,5.5z M17.9,16.7c0,0.4-0.4,0.8-0.8,0.8H2.7c-0.4,0-0.8-0.3-0.8-0.8V8.3h2 C3.8,8.7,3.8,9.2,3.8,9.7c0.1,3.3,2.7,6,6,6c3.4,0.1,6.2-2.6,6.3-6c0-0.5-0.1-0.9-0.2-1.4h2.1h0V16.7z"/></symbol><symbol id="interview-2" viewBox="0 0 30 30"><path d="m9.01,18.9a18.312,18.312,0,0,1-5.04,2.04c-1.05.21-.73-0.08-0.47-0.29a9.884,9.884,0,0,0,2.74-2.87,5.366,5.366,0,0,1-2.97-4.43c0-3.27,3.95-5.92,8.82-5.92s8.82,2.65,8.82,5.92-3.95,5.92-8.82,5.92Z m17.42,3.64a10.81,10.81,0,0,1-3.28-1.5,8.077,8.077,0,0,1-2.42.37,6.77,6.77,0,0,1-4.8-1.75c3.33-.97,5.66-3.2,5.88-5.89,2.66,0.34,4.66,1.9,4.66,3.78a3.317,3.317,0,0,1-1.52,2.62c0.06,0.44.26,1.04,1.61,2.1Z"/></symbol><symbol id="interview" viewBox="0 0 20 16"><path d="m 13.3,10.4 c 1.2,0 2.2,-0.9 2.2,-2.1 V 2.1 C 15.5,1 14.5,0 13.3,0 H 2.2 C 1,0 0,0.9 0,2.1 v 6.2 c 0,1.1 1,2.1 2.2,2.1 H 3.6 L 4.5,13.9 9,10.4 h 4.3 z m 4.5,-7.6 h -1.4 l -0.1,7 c 0,1.3 -1.5,1.3 -1.5,1.3 H 9.4 L 8.1,12.5 H 11 l 4.4,3.5 0.9,-3.5 h 1.4 c 1.3,0 2.3,-1 2.3,-2.1 V 4.8 c 0,-1.1 -1,-2 -2.2,-2 z"/></symbol><symbol id="left-arrow" viewBox="0 0 19.200001 32"><path d="M17.13 32l2.07-1.93L4.15 16 19.2 1.94 17.13 0 0 16"/></symbol><symbol id="letter" viewBox="0 0 29 21"><path d="M 0,18.6 C 0,19.9 1.1,21 2.4,21 H 26.6 C 27.9,21 29,19.9 29,18.6 V 2.4 C 29,1.1 27.9,0 26.6,0 H 2.4 C 1.1,0 0,1.1 0,2.4 V 18.6 z M 8.9,10.5 3.4,5 C 2.9,4.5 2.9,3.8 3.4,3.4 3.9,2.9 4.6,2.9 5.1,3.4 l 8.8,8.8 c 0.4,0.3 0.9,0.3 1.3,0 L 24,3.4 c 0.5,-0.5 1.2,-0.5 1.7,0 0.5,0.5 0.5,1.2 0,1.6 l -5.6,5.5 5.5,5.5 c 0.5,0.5 0.5,1.2 0,1.6 -0.5,0.5 -1.2,0.5 -1.7,0 l -5.5,-5.5 c 0,0 -1.7,1.7 -2,2.1 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.7,0 -1.4,-0.3 -1.9,-0.8 -0.3,-0.3 -2,-2 -2,-2 l -5.5,5.5 c -0.5,0.5 -1.2,0.5 -1.7,0 -0.5,-0.5 -0.5,-1.2 0,-1.6 l 5.5,-5.6 z"/></symbol><symbol id="linkedin" viewBox="0 0 30 30"><path d="m8.89,25.56h-4.45v-14.31h4.45v14.31Zm-2.22-16.27h0a2.58,2.58,0,1,1,2.57-2.58Zm14.44,16.27v-6.96c0-1.66-.03-3.79-2.31-3.79s-2.69,1.81-2.69,3.67v7.08h-4.44v-14.31h4.26v1.96h0.06a4.7,4.7,0,0,1,4.22-2.32c4.5,0,5.33,2.97,5.33,6.83l0.02,7.84h-4.45Z"/></symbol><symbol id="magnifier" viewBox="0 0 28.1875 28.19375"><path d="M27.944 26.756l-6.852-6.852c1.838-2.113 2.95-4.87 2.95-7.883C24.043 5.39 18.65 0 12.023 0 5.388 0 0 5.395 0 12.02c0 6.628 5.395 12.023 12.02 12.023 3.015 0 5.77-1.113 7.884-2.95l6.852 6.85c.162.163.38.25.594.25.212 0 .43-.08.594-.25.325-.324.325-.862 0-1.187zM1.682 12.02c0-5.7 4.638-10.332 10.333-10.332 5.7 0 10.334 4.638 10.334 10.333 0 5.696-4.634 10.34-10.335 10.34-5.695 0-10.333-4.637-10.333-10.34z"/></symbol><symbol id="mail-v2" viewBox="0 0 34 34"><polygon points="5,9 17,20 29,9 29,25 5,25"/><polygon points="17,18.5 27.5,9 6.5,9"/></symbol><symbol id="newspaper" viewBox="0 0 20 16"><path d="M3894,2213h4v4h-4v-4Zm-5-3h9v2h-9v-2Zm0,3h4v1h-4v-1Zm0,2h4v1h-4v-1Zm11-8h-1v-2a0.979,0.979,0,0,0-1-1h-16a0.979,0.979,0,0,0-1,1v14a0.979,0.979,0,0,0,1,1h18a0.979,0.979,0,0,0,1-1v-11A0.979,0.979,0,0,0,3900,2207Zm-14.5,9v-10H3897v2h-10v10h-4v-12h1.5v10h1Zm13.5-7v9h-11v-9h11Z" transform="translate(-3881 -2204)"/></symbol><symbol id="obituaries" viewBox="0 0 32 8.4407187"><path d="M4.465 3.478c-1.342 0-2.712.94-2.712 2.108 0 .43.154.91.575 1.197.21.154.21.393-.03.652-.286.297-.698.536-1.12.536C.67 7.97 0 7.51 0 6.62c0-.498.335-1.552 1.648-2.52 1.39-1.025 2.577-1.37 3.938-1.456C8.46.402 10.788 0 12.484 0c1.85 0 3.497.575 4.33.89.748.28 3.65 1.467 5.7 2.34 2.157.87 4.015 1.398 5.29 1.398 1.015 0 2.49-.355 2.49-1.58 0-.452-.18-.758-.44-.94-.144-.115-.25-.288-.25-.44 0-.45.413-1.14 1.14-1.14C31.56.527 32 1.32 32 1.925c0 1.628-1.83 2.73-3.056 3.228-2.26 2.348-4.55 3.287-6.764 3.287-1.63 0-3.19-.43-5.26-1.16-1.562-.554-6.458-2.51-8.422-3.112-1.81-.546-2.845-.69-4.014-.69h-.02zM10.778.88c-1.504 0-2.93.74-3.63 1.83.852.02 2.145.203 4.292.96 2.146.708 6.103 2.356 7.482 2.826 1.543.488 3.01.9 4.474.9 1.64 0 3.095-.527 3.86-1.877-1.522.19-2.96-.05-5.076-.854-1.63-.604-6.113-2.616-7.627-3.114-1.274-.42-2.51-.67-3.746-.67h-.03z"/></symbol><symbol id="padlock-close" viewBox="0 0 28.1 37.5"><path d="M28.1,17H0v20.5h28.1V17z M15.8,28.1l0,5.9l-3.6,0v-5.9c-1.1-0.6-1.9-1.8-1.9-3.2c0-2,1.7-3.7,3.7-3.7 c2,0,3.7,1.7,3.7,3.7C17.7,26.3,17,27.5,15.8,28.1z"/><path d="M7.8,15.1c0-0.3-0.1-3.6,0.1-4.9c0.3-2.9,3.1-5.3,6.2-5.3c2.9,0,5.5,2.2,6.1,5.2c0.3,1.2,0.1,4.7,0.1,5h4.9 c0-0.3,0.1-5.1-0.5-7.1C23.4,3.4,19,0.1,14.1,0C9.4-0.1,5.2,2.9,3.5,7.5c-0.8,2.1-0.6,7.3-0.6,7.6H7.8z"/></symbol><symbol id="padlock-open" viewBox="0 0 42.6 37.5"><path d="M42.6,17H14.5v20.5h28.1V17z M30.3,28.1V34h-3.6v-5.9c-1.1-0.6-1.9-1.8-1.9-3.2c0-2,1.7-3.7,3.7-3.7 s3.7,1.7,3.7,3.7C32.2,26.3,31.5,27.5,30.3,28.1z"/><path d="M4.9,15.1c0-0.3-0.1-3.6,0.1-4.9c0.3-2.9,3.1-5.3,6.2-5.3c2.9,0,5.5,2.2,6.1,5.2c0.3,1.2,0.1,4.7,0.1,5h4.9 c0-0.3,0.1-5.1-0.5-7.1c-1.3-4.6-5.7-7.9-10.6-8C6.5-0.1,2.3,2.9,0.6,7.5C-0.2,9.6,0,14.8,0,15.1H4.9z"/></symbol><symbol id="padlock" viewBox="0 0 45 56"><path d="M 38,21 H 36.3 V 14.3 C 36.3,6.3 30.6,0 22.4,0 14.2,0 8.6,6.4 8.6,14.3 V 21 H 6.9 C 3.1,21 0,24.1 0,28 v 21 c 0,3.9 3.1,7 6.9,7 H 38.1 C 41.9,56 45,52.9 45,49 V 28 C 44.9,24.1 41.8,21 38,21 z M 12,14.3 c 0,-6 4,-10.8 10.3,-10.8 6.2,0 10.4,4.8 10.4,10.8 V 21 H 12 V 14.3 z M 41.4,49 c 0,1.9 -1.6,3.5 -3.5,3.5 H 6.8 C 4.9,52.5 3.3,50.9 3.3,49 V 28 c 0,-1.9 1.6,-3.5 3.5,-3.5 H 38 c 1.9,0 3.5,1.6 3.5,3.5 v 21 h -0.1 z m -19,-17.5 c -1.9,0 -3.5,1.6 -3.5,3.5 0,1.3 0.7,2.4 1.7,3 v 5.7 c 0,1 0.8,1.7 1.7,1.7 1,0 1.7,-0.8 1.7,-1.7 V 38 c 1,-0.6 1.7,-1.7 1.7,-3 0.2,-1.9 -1.4,-3.5 -3.3,-3.5 z"/></symbol><symbol id="post-delete" viewBox="0 0 14 14"><path d="M588.55,437a7,7,0,1,1,7-7A7,7,0,0,1,588.55,437Zm0-13a6,6,0,1,0,6,6A6,6,0,0,0,588.55,424Z" transform="translate(-581.55 -423)"/><polygon points="10.31 4.66 9.35 3.69 7 6.03 4.66 3.69 3.69 4.66 6.04 7 3.69 9.34 4.66 10.31 7 7.96 9.35 10.31 10.31 9.34 7.96 7 10.31 4.66"/></symbol><symbol id="questionnaire" viewBox="0 0 20 20"><path d="M2.5,2.5v15h15v-15H2.5z M8.8,13.5l-1.2-1.2L5.1,10l1.2-1.2l2.4,2.4l4.9-4.7l1.2,1.2L8.8,13.5z"/></symbol><symbol id="quiz" viewBox="0 0 15 19.5"><path d="m6.9 3.3c-0.1-0.2-0.1-0.5-0.1-0.7 0-0.3 0.1-0.5 0.2-0.6 0.1-0.2 0.3-0.3 0.5-0.3s0.4 0.1 0.5 0.2 0.2 0.3 0.2 0.6-0.1 0.6-0.2 0.9-0.3 0.6-0.5 0.8c-0.2 0.3-0.4 0.5-0.6 0.8-0.3 0.2-0.5 0.4-0.8 0.5v2.2h1.9v-1.5c0.6-0.4 1.1-0.9 1.5-1.5s0.5-1.3 0.5-2.1-0.2-1.5-0.7-1.9c-0.4-0.5-1-0.7-1.9-0.7-0.8 0-1.4 0.2-1.9 0.7-0.4 0.5-0.6 1-0.6 1.8 0 0.4 0.1 0.8 0.2 1.2l1.8-0.4zm7.4 3.4c-0.5-0.4-1.1-0.7-2-0.7-0.8 0-1.5 0.2-1.9 0.7s-0.6 1-0.6 1.8c0 0.4 0.1 0.8 0.2 1.2l1.8-0.3c-0.1-0.2-0.1-0.5-0.1-0.7 0-0.3 0.1-0.5 0.2-0.7s0.3-0.3 0.5-0.3 0.4 0.1 0.5 0.2 0.2 0.3 0.2 0.6-0.1 0.6-0.2 0.9-0.3 0.6-0.5 0.9-0.4 0.5-0.6 0.7-0.5 0.4-0.7 0.6v2.2h1.9v-1.6c0.6-0.4 1.1-0.9 1.5-1.5s0.5-1.3 0.5-2.1-0.3-1.4-0.7-1.9zm-3.3 9.9h1.9v-1.9h-1.9v1.9zm-3.6-7.6c-0.8 0-1.5 0.2-1.9 0.7-0.4 0.4-0.6 1-0.6 1.8 0 0.4 0.1 0.8 0.2 1.2l1.8-0.3c-0.1-0.2-0.1-0.5-0.1-0.7 0-0.3 0.1-0.5 0.2-0.7s0.3-0.3 0.5-0.3 0.4 0.1 0.5 0.2 0.2 0.3 0.2 0.6-0.1 0.6-0.2 0.9-0.3 0.6-0.5 0.9-0.4 0.5-0.6 0.7-0.5 0.4-0.7 0.6v2.2h1.9v-1.6c0.6-0.4 1.1-0.9 1.5-1.5s0.5-1.3 0.5-2.1-0.2-1.5-0.7-1.9c-0.5-0.5-1.2-0.7-2-0.7zm-1.3 10.5h1.9v-1.9h-1.9v1.9zm-4.9-3.9h1.9v-1.9h-1.9v1.9zm3.3-9.9c-0.5-0.4-1.1-0.6-2-0.6-0.8 0-1.5 0.2-1.9 0.7-0.4 0.4-0.6 1-0.6 1.8 0 0.4 0.1 0.8 0.2 1.2l1.8-0.3c-0.1-0.4-0.1-0.6-0.1-0.8 0-0.3 0.1-0.5 0.2-0.7s0.2-0.3 0.4-0.3 0.4 0.1 0.5 0.2 0.2 0.3 0.2 0.6-0.1 0.6-0.2 0.9c0 0.4-0.2 0.6-0.4 0.9s-0.4 0.5-0.6 0.7-0.5 0.4-0.7 0.6v2.2h1.9v-1.6c0.6-0.4 1.1-0.9 1.5-1.5s0.5-1.3 0.5-2.1-0.2-1.4-0.7-1.9z"/></symbol><symbol id="read-later" viewBox="0 0 34 34"><path d="M26,31l-9-8l-9,8V3h18V31z"/></symbol><symbol id="reply" viewBox="0 0 17 9"><path d="M0,4.5L5.7,0v2.2C15.9,2.2,17,7.9,17,9C14.7,4.5,6.8,6.8,5.7,6.8V9L0,4.5z"/></symbol><symbol id="right-arrow" viewBox="0 0 19.200001 32"><path d="M15.05 16L0 1.94 2.07 0 19.2 16 2.07 32 0 30.07"/></symbol><symbol id="snapchat" viewBox="0 0 21 20"><path d="M570.633,2775a0.86,0.86,0,0,1-.161-0.01h0c-0.034,0-.069.01-0.105,0.01a4.524,4.524,0,0,1-2.814-1.13,4.123,4.123,0,0,0-1.63-.84,4.9,4.9,0,0,0-.855-0.08,5.92,5.92,0,0,0-1.186.14,2.191,2.191,0,0,1-.441.07,0.282,0.282,0,0,1-.307-0.23c-0.049-.16-0.084-0.33-0.119-0.49a0.8,0.8,0,0,0-.32-0.69c-1.973-.31-2.538-0.73-2.664-1.02a0.479,0.479,0,0,1-.03-0.13,0.227,0.227,0,0,1,.186-0.24c3.033-.5,4.393-3.65,4.45-3.78l0-.02a1.175,1.175,0,0,0,.109-0.98,2.024,2.024,0,0,0-1.337-.86c-0.11-.04-0.214-0.07-0.3-0.1-0.9-.37-0.971-0.73-0.936-0.92a0.851,0.851,0,0,1,.824-0.55,0.531,0.531,0,0,1,.245.06,2.582,2.582,0,0,0,1.081.28,0.853,0.853,0,0,0,.645-0.21c-0.01-.2-0.024-0.42-0.038-0.65a10.884,10.884,0,0,1,.251-4.29,5.473,5.473,0,0,1,5.1-3.34h0.427a5.483,5.483,0,0,1,5.1,3.34,10.9,10.9,0,0,1,.251,4.29l0,0.06c-0.012.21-.024,0.4-0.034,0.59a0.891,0.891,0,0,0,.588.21h0a2.644,2.644,0,0,0,1.019-.29,0.853,0.853,0,0,1,.316-0.06,0.908,0.908,0,0,1,.364.07h0.007a0.682,0.682,0,0,1,.509.56,1.112,1.112,0,0,1-.944.84,1.728,1.728,0,0,1-.3.1,2.1,2.1,0,0,0-1.337.86,1.2,1.2,0,0,0,.109.99,0.042,0.042,0,0,0,0,.01c0.057,0.13,1.416,3.28,4.45,3.79a0.224,0.224,0,0,1,.186.23,0.336,0.336,0,0,1-.031.13c-0.125.3-.689,0.72-2.663,1.03a0.748,0.748,0,0,0-.32.68c-0.035.17-.07,0.33-0.119,0.49a0.266,0.266,0,0,1-.283.22H577.56a2.2,2.2,0,0,1-.442-0.06,6.738,6.738,0,0,0-1.186-.13,6.137,6.137,0,0,0-.856.08,4.093,4.093,0,0,0-1.627.84A4.534,4.534,0,0,1,570.633,2775Z" transform="translate(-560 -2755)"/></symbol><symbol id="spam" viewBox="0 0 11 14.5"><path d="M2.3,14.5h6.5c0.8,0,1.4-0.6,1.4-1.5V2.9H0.8v10.2C0.8,13.9,1.5,14.5,2.3,14.5z M7.7,4.7 c0-0.3,0.2-0.5,0.6-0.5c0.3,0,0.6,0.2,0.6,0.5v7.9c0,0.3-0.3,0.5-0.6,0.5s-0.6-0.2-0.6-0.5C7.7,12.6,7.7,4.7,7.7,4.7z M5,4.7 c0-0.3,0.3-0.5,0.6-0.5c0.3,0,0.6,0.2,0.6,0.5v7.9c0,0.3-0.3,0.5-0.6,0.5c-0.3,0-0.6-0.2-0.6-0.5V4.7z M2.2,4.7 c0-0.3,0.2-0.5,0.6-0.5s0.6,0.2,0.6,0.5v7.9c0,0.3-0.3,0.5-0.6,0.5s-0.6-0.2-0.6-0.5C2.2,12.6,2.2,4.7,2.2,4.7z"/><path d="M3.7,0h-2C0.8,0,0,0.8,0,1.7h11c0-1-0.8-1.7-1.8-1.7H3.7"/></symbol><symbol id="star" viewBox="0 0 15 14"><path d="M500.943,3644.22a1.056,1.056,0,0,0-.865-0.68l-3.9-.56-1.695-3.4a1.12,1.12,0,0,0-1.962,0l-1.7,3.4-3.9.56a1.058,1.058,0,0,0-.864.68,0.971,0.971,0,0,0,.251,1.03l2.861,2.75-0.664,3.82a0.981,0.981,0,0,0,.446.99,1.118,1.118,0,0,0,1.147.06l3.4-1.76,3.4,1.76a1.117,1.117,0,0,0,1.146-.06,0.98,0.98,0,0,0,.444-0.99l-0.663-3.82,2.86-2.75A0.964,0.964,0,0,0,500.943,3644.22Z" transform="translate(-486 -3639)"/></symbol><symbol id="survey" viewBox="0 0 20 20"><path d="m 8.9,2.1 0,0 c -5,0 -8.9,4 -8.9,9 0,4.9 4,9 8.9,9 4.9,0 8.9,-4 8.9,-9 -2.3,0 -8.9,0 -8.9,0 0,0 0,-6.6 0,-9 z M 20,9.6 C 20,4.3 15.7,0 10.4,0 V 9.7 L 20,9.6 z"/></symbol><symbol id="tweet" viewBox="0 0 28 24"><path d="M 28.1,2.8 C 27.1,3.3 25.9,3.6 24.8,3.7 26,3 26.9,1.8 27.4,0.4 26.3,1.1 25,1.6 23.7,1.9 22.6,0.7 21.1,0 19.5,0 c -3.2,0 -5.8,2.7 -5.8,6 0,0.5 0.1,0.9 0.2,1.4 C 9.1,7.1 4.8,4.8 1.9,1.1 1.4,2 1.1,3 1.1,4.1 c 0,2.1 1,3.9 2.6,5 -1,0 -1.9,-0.3 -2.6,-0.8 v 0.1 c 0,2.9 2,5.3 4.7,5.9 -0.5,0.1 -1,0.2 -1.5,0.2 -0.4,0 -0.7,0 -1.1,-0.1 0.7,2.4 2.9,4.1 5.4,4.2 -2,1.6 -4.5,2.6 -7.2,2.6 -0.5,0 -0.9,0 -1.4,-0.1 2.6,1.7 5.6,2.7 8.9,2.7 10.7,0 16.6,-9.2 16.6,-17.1 0,-0.3 0,-0.5 0,-0.8 0.8,-0.8 1.8,-1.9 2.6,-3.1 z"/></symbol><symbol id="twitter-v2" viewBox="0 0 34 34"><path d="M12.9,27.8c0,0-0.1,0-0.1,0c-5,0-8-2.4-8.1-2.5l-1.5-1.3l1.9,0.4c0,0,3,0.6,6.6-1.5C8.2,22,7.4,19,7.4,19
+ l-0.2-0.7l0.7,0.1c0.1,0,0.2,0,0.3,0c-2.2-1.4-2.8-4-2.7-5l0.1-0.7L6.2,13c0.2,0.1,0.5,0.2,0.7,0.3c-1.1-1.3-2.2-3.6-0.7-6.7L6.6,6
+ L7,6.6c3.9,4.4,8.6,5.4,10.2,5.4c-0.4-3.2,2-5.9,4.5-6.5c2.5-0.6,4.5,0.6,5.5,1.5l3.7-1.5c0,0-0.7,1.8-2.1,3.3l2.9-1.1l-0.4,0.9
+ c0,0.1-0.8,1.3-2.2,2.5c0.1,5.6-2.8,10.4-5.3,12.6C20.7,26.5,17.2,27.8,12.9,27.8z"/></symbol><symbol id="up-arrow" viewBox="0 0 21.2 14.1"><rect x="-0.4" y="4.6" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -2.9289 7.0711)" width="15" height="5"/><rect x="6.6" y="4.6" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 19.1421 22.0711)" width="15" height="5"/></symbol><symbol id="user" viewBox="0 0 32 32"><path d="M8.4,23.9c-0.2,0-0.4-0.1-0.4-0.3c-0.1-0.2,0.1-0.5,0.3-0.5c1.6-0.4,3.9-1.2,4.4-1.7
+ c0.1-0.2,0.1-1,0-1.9c-0.7-0.6-1.2-1.5-1.4-2.4c-0.2-0.9-0.5-2.5-0.6-3.3c-0.1-1-0.1-1.4,0-2.1l0-0.1c0.3-2.2,2.5-3.9,5.2-3.9
+ c2.7,0,4.9,1.7,5.2,3.9l0,0.1c0.1,0.6,0.1,1.1,0,2.1c-0.1,0.8-0.4,2.5-0.6,3.3c-0.2,0.9-0.7,1.7-1.4,2.4c-0.1,0.8-0.1,1.7,0,1.9
+ c0.5,0.5,3,1.3,4.5,1.7c0.2,0.1,0.4,0.3,0.3,0.5s-0.3,0.4-0.5,0.3c-0.7-0.2-4.2-1-5-2c-0.5-0.6-0.3-2.1-0.2-2.7
+ c0-0.1,0.1-0.2,0.1-0.3c0.6-0.5,1-1.2,1.2-2c0.2-0.8,0.5-2.4,0.6-3.2c0.1-0.9,0.1-1.2,0-1.8l0-0.1C20,10,18.2,8.6,15.9,8.6
+ c-2.2,0-4.1,1.3-4.3,3.1l0,0.1c-0.1,0.6-0.1,1,0,1.9c0.1,0.8,0.4,2.4,0.6,3.2c0.2,0.8,0.6,1.5,1.2,2c0.1,0.1,0.1,0.2,0.1,0.3
+ c0.1,0.6,0.3,2.2-0.2,2.7c-0.8,0.9-4.1,1.8-4.8,2C8.5,23.9,8.4,23.9,8.4,23.9z"/><path d="M16,30.3c-7.7,0-13.9-6.2-13.9-13.9C2.1,8.8,8.3,2.5,16,2.5c7.7,0,13.9,6.2,13.9,13.9
+ C29.9,24.1,23.7,30.3,16,30.3z M16,3.4c-7.2,0-13,5.8-13,13s5.8,13,13,13s13-5.8,13-13S23.2,3.4,16,3.4z"/></symbol><symbol id="video-round" viewBox="0 0 20 20"><path d="M10,20A10,10,0,1,1,20,10,10,10,0,0,1,10,20ZM10,.83A9.17,9.17,0,1,0,19.17,10,9.19,9.19,0,0,0,10,.83Z" style="fill:currentColor"/><polygon points="16.17 10 5.83 15.97 5.83 4.06 16.17 10"/></symbol><symbol id="video" viewBox="0 0 14.6 15.85"><path d="M 14.1,7.05 1.5,0.15 c -0.3,-0.2 -0.7,-0.2 -1,0 C 0.2,0.35 0,0.65 0,1.05 v 13.8 c 0,0.4 0.2,0.7 0.5,0.9 0.2,0.1 0.3,0.1 0.5,0.1 0.2,0 0.3,0 0.5,-0.1 l 12.6,-6.9 c 0.3,-0.2 0.5,-0.5 0.5,-0.9 0,-0.4 -0.2,-0.7 -0.5,-0.9 z"/></symbol><symbol id="vote-down" viewBox="0 0 14.44 14.11"><path d="M12.22-.24a0.66,0.66,0,0,0-.61.43v0l-1.45-.07A7.88,7.88,0,0,0,8-.9a13,13,0,0,0-4.42,0S2.1-.84,1.64,1.23c0,0-1.29,4.1.07,5.88,0,0,.62,1.16,3,0.93H5.51s-1.22,3.18.21,4.54c0,0,1.54,1,2.1-.1l0-.1C8.37,11.14,9.34,7.62,11.59,7h3.52s0.41,0,.41-0.6V0.57a0.87,0.87,0,0,0-.76-0.8H12.22Z" transform="translate(-1.09 1.11)"/></symbol><symbol id="vote-up" viewBox="0 0 14.44 14.11"><path d="M4.17,9.17a0.66,0.66,0,0,0,.61-0.43v0L6.24,8.77A7.88,7.88,0,0,0,8.39,9.83a13,13,0,0,0,4.42,0s1.47-.07,1.94-2.14c0,0,1.29-4.1-.07-5.88,0,0-.62-1.16-3-0.93H10.88s1.22-3.18-.21-4.54c0,0-1.54-1-2.1.1l0,0.1C8-2.21,7.05,1.31,4.79,1.89H1.27s-0.41,0-.41.6V8.36a0.87,0.87,0,0,0,.76.8H4.17Z" transform="translate(-0.86 4.06)"/></symbol><symbol id="windows-store" viewBox="0 0 135 40"><g><path class="store-frame" d="m130.2 40h-125.5c-2.6 0-4.7-2.1-4.7-4.7v-30.6c0-2.6 2.1-4.7 4.7-4.7h125.5c2.6 0 4.8 2.1 4.8 4.7v30.5c0 2.7-2.2 4.8-4.8 4.8z" fill="#A6A6A6"/><path d="m134 35.3c0 2.1-1.7 3.8-3.8 3.8h-125.5c-2.1 0-3.8-1.7-3.8-3.8v-30.6c0-2.1 1.7-3.8 3.8-3.8h125.5c2.1 0 3.8 1.7 3.8 3.8v30.6z" class="store-bkg"/></g><g fill="#fff"><path d="m63.5 20.8c0-0.4 0.1-0.7 0.4-1s0.6-0.4 1-0.4 0.8 0.1 1 0.4c0.3 0.3 0.4 0.6 0.4 1s-0.1 0.7-0.4 1-0.6 0.4-1 0.4-0.7-0.1-1-0.4c-0.2-0.3-0.4-0.7-0.4-1m2.6 12.7h-2.3v-9.8h2.3v9.8z"/><path d="m73.1 31.8c0.3 0 0.7-0.1 1.1-0.2l1.2-0.6v2.2c-0.4 0.2-0.8 0.4-1.3 0.5s-1 0.2-1.5 0.2c-1.5 0-2.6-0.5-3.5-1.4s-1.4-2.1-1.4-3.5c0-1.6 0.5-2.9 1.4-3.9s2.2-1.5 3.9-1.5c0.4 0 0.9 0.1 1.3 0.2s0.8 0.2 1.1 0.4v2.2c-0.4-0.3-0.7-0.5-1.1-0.6s-0.8-0.2-1.1-0.2c-0.9 0-1.7 0.3-2.2 0.9s-0.8 1.4-0.8 2.4c0 1 0.3 1.8 0.8 2.3 0.5 0.3 1.2 0.6 2.1 0.6"/><path d="m82 23.5h0.5c0.1 0 0.3 0.1 0.4 0.1v2.3c-0.1-0.1-0.3-0.2-0.5-0.3s-0.5-0.1-0.8-0.1c-0.6 0-1 0.2-1.4 0.7s-0.6 1.2-0.6 2.2v5h-2.3v-9.8h2.3v1.5c0.2-0.5 0.5-1 1-1.3 0.3-0.2 0.8-0.3 1.4-0.3"/><path d="m83 28.7c0-1.6 0.5-2.9 1.4-3.9s2.2-1.4 3.8-1.4c1.5 0 2.7 0.5 3.6 1.4s1.3 2.2 1.3 3.7c0 1.6-0.5 2.9-1.4 3.8-0.9 1-2.2 1.4-3.8 1.4-1.5 0-2.7-0.5-3.6-1.3-0.9-0.9-1.3-2.2-1.3-3.7m2.4-0.1c0 1 0.2 1.8 0.7 2.4s1.1 0.8 2 0.8c0.8 0 1.5-0.3 1.9-0.8s0.7-1.3 0.7-2.4c0-1.1-0.2-1.9-0.7-2.4s-1.1-0.8-1.9-0.8-1.5 0.3-2 0.8c-0.5 0.6-0.7 1.4-0.7 2.4"/><path d="m96.6 26.2c0 0.3 0.1 0.6 0.3 0.8s0.7 0.4 1.4 0.7c0.9 0.4 1.6 0.8 2 1.2 0.4 0.5 0.6 1 0.6 1.7 0 0.9-0.4 1.7-1.1 2.2-0.7 0.6-1.7 0.8-2.9 0.8-0.4 0-0.9 0-1.4-0.2-0.5-0.1-0.9-0.2-1.3-0.4v-2.3c0.4 0.3 0.9 0.5 1.4 0.7s0.9 0.3 1.3 0.3c0.5 0 0.9-0.1 1.2-0.2 0.2-0.1 0.4-0.4 0.4-0.7s-0.1-0.6-0.4-0.8-0.7-0.5-1.5-0.8c-0.9-0.4-1.5-0.8-1.8-1.2-0.4-0.5-0.5-1-0.5-1.7 0-0.9 0.4-1.6 1.1-2.2s1.6-0.9 2.7-0.9c0.3 0 0.7 0 1.2 0.1 0.4 0.1 0.8 0.2 1.1 0.3v2.4c-0.3-0.2-0.7-0.4-1.1-0.5s-0.8-0.2-1.2-0.2-0.8 0.1-1 0.3c-0.4 0.1-0.5 0.3-0.5 0.6"/><path d="m101.8 28.7c0-1.6 0.5-2.9 1.4-3.9s2.2-1.4 3.8-1.4c1.5 0 2.7 0.5 3.6 1.4s1.3 2.2 1.3 3.7c0 1.6-0.5 2.9-1.4 3.8s-2.2 1.4-3.8 1.4c-1.5 0-2.7-0.5-3.6-1.3-0.9-0.9-1.3-2.2-1.3-3.7m2.4-0.1c0 1 0.2 1.8 0.7 2.4s1.1 0.8 2 0.8c0.8 0 1.5-0.3 1.9-0.8s0.7-1.3 0.7-2.4c0-1.1-0.2-1.9-0.7-2.4s-1.1-0.8-1.9-0.8-1.5 0.3-2 0.8c-0.5 0.6-0.7 1.4-0.7 2.4"/><path d="m119.6 25.5v5.1c0 1 0.2 1.8 0.7 2.3s1.2 0.8 2.2 0.8c0.3 0 0.7 0 1-0.1s0.6-0.1 0.7-0.2v-1.9c-0.1 0.1-0.3 0.2-0.5 0.2-0.2 0.1-0.3 0.1-0.5 0.1-0.5 0-0.8-0.1-1-0.4s-0.3-0.7-0.3-1.3v-4.6h2.3v-1.9h-2.3v-2.9l-2.3 0.7v2.2h-3.5v-1.2c0-0.6 0.1-1 0.4-1.3s0.6-0.5 1.1-0.5c0.2 0 0.5 0 0.7 0.1s0.3 0.1 0.4 0.2v-2 1c-0.2-0.1-0.4-0.1-0.6-0.1h-0.8c-1 0-1.9 0.3-2.6 1s-1 1.5-1 2.5v1.4h-1.6v1.9h1.6v8h2.3v-8l3.6-1.1z"/></g><g><polygon points="61.5 19.7 61.5 33.5 59.1 33.5 59.1 22.7 59.1 22.7 54.8 33.5 53.3 33.5 48.9 22.7 48.9 22.7 48.9 33.5 46.7 33.5 46.7 19.7 50.1 19.7 54 29.9 54.1 29.9 58.2 19.7" fill="#fff"/></g><g><rect y="6.1" x="11.1" height="13" width="13" fill="#F25022"/><rect y="6.1" x="25.5" height="13" width="13" fill="#7FBA00"/><rect y="20.5" x="11.1" height="13" width="13" fill="#00A4EF"/><rect y="20.5" x="25.5" height="13" width="13" fill="#FFB900"/></g><g fill="#fff"><path d="m46.1 13.7v-7.4h2.8c0.5 0 0.9 0 1.1 0.1 0.4 0.1 0.7 0.2 0.9 0.3 0.2 0.2 0.4 0.4 0.6 0.7 0.1 0.3 0.2 0.6 0.2 1 0 0.6-0.2 1.2-0.6 1.6s-1.1 0.7-2.2 0.7h-1.9v3h-0.9zm1-3.9h1.9c0.6 0 1.1-0.1 1.3-0.4 0.3-0.2 0.4-0.6 0.4-1 0-0.3-0.1-0.6-0.2-0.8-0.2-0.2-0.4-0.4-0.6-0.4s-0.5-0.1-0.9-0.1h-1.9v2.7z"/><path d="m52.5 11c0-1 0.3-1.7 0.8-2.2 0.5-0.4 1-0.6 1.7-0.6s1.3 0.2 1.8 0.7 0.7 1.1 0.7 2c0 0.7-0.1 1.2-0.3 1.6s-0.5 0.7-0.9 0.9-0.8 0.3-1.3 0.3c-0.7 0-1.4-0.2-1.8-0.7-0.4-0.4-0.7-1.1-0.7-2zm1 0c0 0.7 0.1 1.2 0.4 1.5s0.7 0.5 1.1 0.5 0.8-0.2 1.1-0.5 0.4-0.9 0.4-1.6-0.2-1.2-0.5-1.5-0.7-0.5-1.1-0.5c-0.5 0-0.8 0.2-1.1 0.5s-0.3 0.9-0.3 1.6z"/><path d="m59.4 13.7h-0.8v-7.4h0.9v2.6c0.4-0.5 0.9-0.7 1.5-0.7 0.3 0 0.6 0.1 0.9 0.2s0.5 0.3 0.7 0.6c0.2 0.2 0.3 0.5 0.4 0.9 0.1 0.3 0.2 0.7 0.2 1.1 0 0.9-0.2 1.6-0.7 2.1s-1 0.8-1.6 0.8-1.1-0.3-1.5-0.8v0.6zm0-2.8c0 0.6 0.1 1.1 0.3 1.4 0.3 0.5 0.7 0.7 1.2 0.7 0.4 0 0.7-0.2 1-0.5s0.4-0.9 0.4-1.5c0-0.7-0.1-1.2-0.4-1.5s-0.6-0.5-1-0.5-0.7 0.2-1 0.5-0.5 0.8-0.5 1.4z"/><path d="m64.3 7.3v-1h0.9v1h-0.9zm0 6.4v-5.4h0.9v5.3h-0.9z"/><path d="m70.3 11.9l0.9 0.1c-0.1 0.5-0.4 1-0.8 1.3s-0.9 0.5-1.5 0.5c-0.8 0-1.4-0.2-1.9-0.7s-0.7-1.2-0.7-2c0-0.9 0.2-1.6 0.7-2.1s1.1-0.7 1.8-0.7 1.3 0.2 1.8 0.7 0.7 1.2 0.7 2.1v0.2h-4c0 0.6 0.2 1 0.5 1.3s0.7 0.5 1.1 0.5c0.3 0 0.6-0.1 0.9-0.3 0.2-0.2 0.4-0.5 0.5-0.9zm-3-1.4h3c0-0.4-0.2-0.8-0.3-1-0.3-0.3-0.7-0.5-1.1-0.5s-0.8 0.1-1 0.4c-0.4 0.2-0.6 0.6-0.6 1.1z"/><path d="m72.4 13.7v-5.4h0.8v0.8c0.2-0.4 0.4-0.6 0.6-0.7s0.4-0.2 0.6-0.2c0.3 0 0.6 0.1 0.9 0.3l-0.3 0.8c-0.2-0.1-0.4-0.2-0.7-0.2-0.2 0-0.4 0.1-0.5 0.2-0.2 0.1-0.3 0.3-0.3 0.5-0.1 0.3-0.2 0.7-0.2 1.1v2.8h-0.9z"/><path d="m75.3 13.7v-0.7l3.4-4h-1-2.2v-0.7h4.4v0.6l-2.9 3.4-0.6 0.6h1.1 2.5v0.8h-4.7z"/><path d="m83.3 13.7v-0.7l3.4-4h-1-2.2v-0.7h4.4v0.6l-2.9 3.4-0.6 0.6h1.1 2.5v0.8h-4.7z"/><path d="m92.8 13.7l-1.6-5.3h0.9l0.9 3.1 0.3 1.1c0-0.1 0.1-0.4 0.3-1.1l0.9-3.1h0.9l0.8 3.1 0.3 1 0.3-1 0.9-3.1h0.9l-1.7 5.3h-0.9l-0.9-3.2-0.2-0.9-1.1 4.1h-1z"/><path d="m99.3 7.3v-1h0.9v1h-0.9zm0 6.4v-5.4h0.9v5.3h-0.9z"/><path d="m103.6 12.9l0.1 0.8c-0.3 0.1-0.5 0.1-0.7 0.1-0.3 0-0.6-0.1-0.8-0.2s-0.3-0.2-0.4-0.4-0.1-0.5-0.1-1.1v-3.1h-0.7v-0.7h0.7v-1.3l0.9-0.5v1.9h0.9v0.6h-0.9v3.1 0.5c0 0.1 0.1 0.1 0.2 0.2 0.1 0 0.2 0.1 0.3 0.1h0.5z"/><path d="m104.4 13.7v-5.4h0.8v0.8c0.2-0.4 0.4-0.6 0.6-0.7s0.4-0.2 0.6-0.2c0.3 0 0.6 0.1 0.9 0.3l-0.3 0.8c-0.2-0.1-0.4-0.2-0.7-0.2-0.2 0-0.4 0.1-0.5 0.2-0.2 0.1-0.3 0.3-0.3 0.5-0.1 0.3-0.2 0.7-0.2 1.1v2.8h-0.9z"/><path d="m107.8 15.7l-0.1-0.9c0.2 0.1 0.4 0.1 0.5 0.1 0.2 0 0.4 0 0.5-0.1s0.2-0.2 0.3-0.3c0.1-0.1 0.1-0.3 0.3-0.7 0-0.1 0-0.1 0.1-0.2l-2-5.4h1l1.1 3.1c0.1 0.4 0.3 0.8 0.4 1.2 0.1-0.4 0.2-0.8 0.4-1.2l1.1-3.1h0.9l-2 5.4c-0.2 0.6-0.4 1-0.5 1.2-0.2 0.3-0.3 0.5-0.6 0.7-0.2 0.1-0.5 0.2-0.7 0.2-0.3 0.1-0.5 0.1-0.7 0z"/><path d="m113 13.7v-5.4h0.8v0.8c0.4-0.6 1-0.9 1.7-0.9 0.3 0 0.6 0.1 0.9 0.2s0.5 0.3 0.6 0.5 0.2 0.4 0.3 0.7c0 0.2 0.1 0.5 0.1 0.9v3.3h-0.9v-3.3c0-0.4 0-0.6-0.1-0.8s-0.2-0.3-0.4-0.4c-0.2-0.3-0.4-0.3-0.6-0.3-0.4 0-0.7 0.1-1 0.4-0.3 0.2-0.4 0.7-0.4 1.4v2.9h-1z"/><path d="m118.7 15.7l-0.1-0.9c0.2 0.1 0.4 0.1 0.5 0.1 0.2 0 0.4 0 0.5-0.1s0.2-0.2 0.3-0.3c0.1-0.1 0.1-0.3 0.3-0.7 0-0.1 0-0.1 0.1-0.2l-2-5.4h1l1.1 3.1c0.1 0.4 0.3 0.8 0.4 1.2 0.1-0.4 0.2-0.8 0.4-1.2l1.1-3.1h0.9l-2 5.4c-0.2 0.6-0.4 1-0.5 1.2-0.2 0.3-0.3 0.5-0.6 0.7s-0.5 0.2-0.7 0.2c-0.3 0.1-0.5 0.1-0.7 0z"/></g></symbol><symbol id="wyborcza-logo-square" viewBox="0 0 28 28"><rect fill="currentColor" width="12.22" height="18.84" x="3.56" y="4.58"/><polygon fill="inherit" points="26.73 10.38 25.81 10.38 24.16 16.62 24.03 16.61 22.14 11.2 23.26 10.54 23.26 10.38 18.55 10.38 18.55 10.54 19.29 11.21 22.25 19.53 23.95 19.53 25.31 14.44 25.45 14.44 27.25 19.53 28 19.53 28 14.03 26.73 10.38"/></symbol><symbol id="wyborcza-logo" viewBox="0 0 32.371646 48.557468"><path d="m 58.099892,473.7838 32.371645,0 0,-48.55747 -32.371645,0 0,48.55747 z"/></symbol></svg>
+
+<!-- svgModule v1.0.4 -->
+
+<!--10185197, [ null ], aggregatorModule-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<script type="text/javascript">
+(function(){"undefined"!==typeof gazeta_pl&&"undefined"!==typeof gazeta_pl.mobileInfo&&gazeta_pl.mobileInfo.hasOwnProperty("isMobileDevice")&&(document.body.className+=!0===gazeta_pl.mobileInfo.isMobileDevice?" mobile_client":" desktop_client")})();
+</script>
+
+<script>
+
+
+
+
+ var userStateCls = 'theme_white';
+
+
+var htmlTag = document.getElementsByTagName('html')[0];
+htmlTag.className = htmlTag.className + ' ' + userStateCls;
+</script>
+
+<div id="theme_holder" class="mcBan"></div>
+
+
+
+
+<script>
+ var wyborcza_pl = wyborcza_pl || {};
+
+ wyborcza_pl.userInfo = {
+ loggedIn: false,
+ loggedInSoft: false,
+ email: "",
+ channel: "",
+ nick: "",
+ nickConfirmationPending: false
+ };
+</script>
+
+
+
+
+
+
+
+
+
+<!-- x16 capParams: pl.com.agora.squid.portal2.dto.CapParameters@35596587[active=false,inProgress=false,validUntil=<null>,validPeriod=<null>,recurring=false,paymentUnfinished=false,unfinishedOrderId=<null>,offerId=<null>,lastPayment=<null>,inProgressOrderId=<null>,consentOrderId=<null>,defaultSalesProductId=true,capVisible=false,email=<null>,emailHash=<null>,marketingMessage=<null>,loginChannel=<null>,loginMode=<null>,nick=<null>,nickConfirmationPending=false,loggedIn=false,loggedInSoft=false] -->
+
+
+
+
+ <nav id="wyborczaHat">
+ <div class="container-outer page-cap">
+ <div class="container-inner">
+ <div class="grid-row">
+ <div id="wH_container">
+
+ <section id="wH_nav">
+<div id="wH_logo">
+<a href="http://www.wyborcza.pl/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka2015', 'czapeczka_logo', 'klik_wyborcza.pl',,false]);">
+<span>Wyborcza.pl</span>
+<img alt="Wyborcza.pl" src="https://static.im-g.pl/i/obrazki/wyborcza/wyborcza_pl.svg" data-fallback="https://bis.gazeta.pl/im/2/19701/m19701792.png" width="110" height="20" />
+</a>
+</div>
+<div id="wH_menu">
+<div id="wH_menu_icon">
+<svg class="badge-symbol">
+<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#burger"></use>
+</svg>
+</div>
+<div id="wH_menu_wrapper">
+<div id="wH_links_nav">
+<header><h5>Wyborcza.pl</h5></header>
+<ul>
+</ul>
+</div>
+<div id="wH_links_main">
+<header><h5>Nasze serwisy</h5></header>
+<ul>
+<li class="wH_more">
+<span>Magazyny</span>
+<div class="wH_links_sub">
+<ul>
+<li>
+<a href="http://wyborcza.pl/duzyformat/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Duy Format',,false]);">
+Duy Format
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/magazyn/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Magazyn witeczny',,false]);">
+Magazyn witeczny
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/alehistoria/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Ale Historia',,false]);">
+Ale Historia
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/TylkoZdrowie/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tylko Zdrowie',,false]);">
+Tylko Zdrowie
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/ksiazki/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Ksiki. Magazyn do czytania',,false]);">
+Ksiki. Magazyn do czytania
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/osiemdziewiec/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Osiem Dziewi',,false]);">
+Osiem Dziewi
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/Opiekun/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Opiekun',,false]);">
+Opiekun
+</a>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<a href="http://wyborcza.biz/biznes/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wyborcza.biz',,false]);">Wyborcza.biz</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/0,156282.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wyborcza.Tech',,false]);">Wyborcza.Tech</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/0,160795.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wyborcza Classic',,false]);">Wyborcza Classic</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/wiecejswiata/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wicej wiata',,false]);">Wicej wiata</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/0,87647.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Witamy w Polsce',,false]);">Witamy w Polsce</a>
+</li>
+<li>
+<a href="http://biqdata.wyborcza.pl/biqdata/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_BIQdata',,false]);">BIQdata</a>
+</li>
+<li>
+<a href="http://sonar.wyborcza.pl/sonar/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Sonar',,false]);">Sonar</a>
+</li>
+<li>
+<a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wysokie Obcasy',,false]);">Wysokie Obcasy</a>
+</li>
+<li>
+<a href="http://cojestgrane24.wyborcza.pl/cjg24/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Co Jest Grane 24',,false]);">Co Jest Grane 24</a>
+</li>
+<li>
+<a href="http://www.logo24.pl/Logo24/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Logo 24',,false]);">Logo 24</a>
+</li>
+<li>
+<a href="http://magazyn-kuchnia.pl/magazyn-kuchnia/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Magazyn Kuchnia',,false]);">Magazyn Kuchnia</a>
+</li>
+<li>
+<a href="http://www.gazeta.pl/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Gazeta.pl',,false]);">Gazeta.pl</a>
+</li>
+<li class="wH_more">
+<span>Wiadomoci</span>
+<div class="wH_links_sub">
+<ul>
+<li>
+<a href="http://wyborcza.pl/7,101707,24410372,michael-jackson-bral-z-dziecmi-sluby-na-niby-po-premierze.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Michael Jackson: Leaving Neverland',,false]);">
+Michael Jackson: Leaving Neverland
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75398,24418381,rada-mediow-narodowych-nie-odwola-kurskiego-wniosek-nie-zostal.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Rada Mediw Narodowych: Jarosaw Kurski',,false]);">
+Rada Mediw Narodowych: Jarosaw Kurski
+</a>
+</li>
+<li>
+<a href="http://warszawa.wyborcza.pl/warszawa/7,54420,24417372,reprywatyzacja-adwokat-robert-nowaczyk-zeznaje-przed-komisja.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Komisja reprywatyzacja: Robert Nowaczyk',,false]);">
+Komisja reprywatyzacja: Robert Nowaczyk
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75399,24416982,szesc-ofiar-dziennie-na-morzu-srodziemnym.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Morze rdziemne',,false]);">
+Morze rdziemne
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75399,24416239,wir-polarny-w-usa-sa-ofiary-smiertelne.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_USA: wir polarny',,false]);">
+USA: wir polarny
+</a>
+</li>
+<li>
+<a href="http://warszawa.wyborcza.pl/warszawa/7,54420,24415953,bialoleka-w-klebach-dymu-plonie-hala-ewakuowano-setke-osob.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Biaoka: hala na eraniu',,false]);">
+Biaoka: hala na eraniu
+</a>
+</li>
+<li>
+<a href="http://rzeszow.wyborcza.pl/rzeszow/7,34962,24416275,ponad-100-osob-zamknietych-w-sadzie-w-przemyslu-jest-informacja.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Przemyl: wglik',,false]);">
+Przemyl: wglik
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75398,24415430,tasmy-kaczynskiego-wiadomosci-tvp-o-pazernej-wyborczej.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wiadomoci TVP: tamy Kaczyskiego',,false]);">
+Wiadomoci TVP: tamy Kaczyskiego
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75398,24415498,tasmy-kaczynskiego-nowe-nagranie-prezesa-pis-beda-mowic-ze.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tamy Kaczyskiego',,false]);">
+Tamy Kaczyskiego
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75398,24415440,w-sejmie-debata-o-nbp-poslanka-po-zablokowala-mownice.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Sejm: NBP',,false]);">
+Sejm: NBP
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75398,24415454,tasmy-kaczynskiego-sprawdzamy-czy-prezes-pis-zlamal-prawo.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Jarosaw Kaczyski: tamy Kaczyskiego',,false]);">
+Jarosaw Kaczyski: tamy Kaczyskiego
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75398,24415443,tasmy-kaczynskiego-partia-buduje-wiezowiec-drugie-nagranie.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tamy Kaczyskiego - drugie nagranie audio i stenogram',,false]);">
+Tamy Kaczyskiego - drugie nagranie audio i stenogram
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,155287,24414148,europejski-raport-o-smogu-polska-walczy-za-slabo.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Smog',,false]);">
+Smog
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,155287,24414565,zlote-dziecko-pis-na-zakrecie-kariery.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Micha Krupiski: tamy Kaczyskiego',,false]);">
+Micha Krupiski: tamy Kaczyskiego
+</a>
+</li>
+<li>
+<a href="http://krakow.wyborcza.pl/krakow/7,44425,24415702,wysadzony-bankomat-przy-ul-teligi-powazne-utrudnienia.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Bankomat: ul. Tegli w Krakowie',,false]);">
+Bankomat: ul. Tegli w Krakowie
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75398,24415472,tasmy-kaczynskiego-pekao-sa-nie-zaprzecza-ze-prezes-krupinski.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tamy Kaczyskiego: Pekao SA, Micha Krupiski',,false]);">
+Tamy Kaczyskiego: Pekao SA, Micha Krupiski
+</a>
+</li>
+<li>
+<a href="http://krakow.wyborcza.pl/krakow/7,44425,24412575,smiertelny-wypadek-w-babicach-kierowca-zginal-na-obwodnicy.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wypadek w Babicach',,false]);">
+Wypadek w Babicach
+</a>
+</li>
+<li>
+<a href="http://poznan.wyborcza.pl/poznan/7,36001,24413925,prezydent-poznania-jacek-jaskowiak-w-szpitalu-na-prawicowych.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Jacek Jakowiak',,false]);">
+Jacek Jakowiak
+</a>
+</li>
+<li>
+<a href="http://krakow.wyborcza.pl/krakow/7,44425,24412932,tatry-pod-rysami-zeszla-lawina.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tatry: lawina pod Rysami',,false]);">
+Tatry: lawina pod Rysami
+</a>
+</li>
+<li>
+<a href="http://warszawa.wyborcza.pl/warszawa/7,54420,24411986,pierwszy-pociag-metra-dojechal-na-targowek-kiedy-otwarcie.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Metro Targwek',,false]);">
+Metro Targwek
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/7,75398,24408844,tasmy-kaczynskiego-partia-buduje-wiezowiec-nagranie-audio.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tamy Kaczyskiego: audio i stenogram',,false]);">
+Tamy Kaczyskiego: audio i stenogram
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/alehistoria/7,162090,24414810,31-stycznia-niemcy-do-amerykanow-dajcie-nam-trzy-miesiace.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_31 stycznia',,false]);">
+31 stycznia
+</a>
+</li>
+<li>
+<a href="http://wyborcza.pl/TylkoZdrowie/7,157387,24415511,pogoda-w-czwartek-31-stycznia-2019-r.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Pogoda w czwartek 31 stycznia 2019 r.',,false]);">
+Pogoda w czwartek 31 stycznia 2019 r.
+</a>
+</li>
+</ul>
+</div>
+</li>
+<li class="wH_more">
+<span>Serwisy lokalne</span>
+<div class="wH_links_sub">
+<ul>
+<li>
+<a href="http://bialystok.wyborcza.pl/bialystok/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Biaystok',,false]);">
+Biaystok
+</a>
+</li>
+<li>
+<a href="http://bielskobiala.wyborcza.pl/bielskobiala/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Bielsko-Biaa',,false]);">
+Bielsko-Biaa
+</a>
+</li>
+<li>
+<a href="http://bydgoszcz.wyborcza.pl/bydgoszcz/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Bydgoszcz',,false]);">
+Bydgoszcz
+</a>
+</li>
+<li>
+<a href="http://czestochowa.wyborcza.pl/czestochowa/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Czstochowa',,false]);">
+Czstochowa
+</a>
+</li>
+<li>
+<a href="http://gliwice.wyborcza.pl/gliwice/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Gliwice',,false]);">
+Gliwice
+</a>
+</li>
+<li>
+<a href="http://gorzow.wyborcza.pl/gorzow/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Gorzw Wielkopolski',,false]);">
+Gorzw Wielkopolski
+</a>
+</li>
+<li>
+<a href="http://katowice.wyborcza.pl/katowice/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Katowice',,false]);">
+Katowice
+</a>
+</li>
+<li>
+<a href="http://kielce.wyborcza.pl/kielce/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Kielce',,false]);">
+Kielce
+</a>
+</li>
+<li>
+<a href="http://krakow.wyborcza.pl/krakow/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Krakw',,false]);">
+Krakw
+</a>
+</li>
+<li>
+<a href="http://lublin.wyborcza.pl/lublin/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Lublin',,false]);">
+Lublin
+</a>
+</li>
+<li>
+<a href="http://lodz.wyborcza.pl/lodz/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_d',,false]);">
+d
+</a>
+</li>
+<li>
+<a href="http://olsztyn.wyborcza.pl/olsztyn/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Olsztyn',,false]);">
+Olsztyn
+</a>
+</li>
+<li>
+<a href="http://opole.wyborcza.pl/opole/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Opole',,false]);">
+Opole
+</a>
+</li>
+<li>
+<a href="http://plock.wyborcza.pl/plock/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Pock',,false]);">
+Pock
+</a>
+</li>
+<li>
+<a href="http://poznan.wyborcza.pl/poznan/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Pozna',,false]);">
+Pozna
+</a>
+</li>
+<li>
+<a href="http://radom.wyborcza.pl/radom/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Radom',,false]);">
+Radom
+</a>
+</li>
+<li>
+<a href="http://rzeszow.wyborcza.pl/rzeszow/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Rzeszw',,false]);">
+Rzeszw
+</a>
+</li>
+<li>
+<a href="http://sosnowiec.wyborcza.pl/sosnowiec/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Sosnowiec',,false]);">
+Sosnowiec
+</a>
+</li>
+<li>
+<a href="http://szczecin.wyborcza.pl/szczecin/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Szczecin',,false]);">
+Szczecin
+</a>
+</li>
+<li>
+<a href="http://torun.wyborcza.pl/torun/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Toru',,false]);">
+Toru
+</a>
+</li>
+<li>
+<a href="http://trojmiasto.wyborcza.pl/trojmiasto/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Trjmiasto',,false]);">
+Trjmiasto
+</a>
+</li>
+<li>
+<a href="http://warszawa.wyborcza.pl/warszawa/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Warszawa',,false]);">
+Warszawa
+</a>
+</li>
+<li>
+<a href="http://wroclaw.wyborcza.pl/wroclaw/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wrocaw',,false]);">
+Wrocaw
+</a>
+</li>
+<li>
+<a href="http://zielonagora.wyborcza.pl/zielonagora/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Zielona Gra',,false]);">
+Zielona Gra
+</a>
+</li>
+</ul>
+</div>
+</li>
+<li class="wH_more">
+<span>Inne serwisy</span>
+<div class="wH_links_sub">
+<ul>
+<li>
+<a href="http://www.tokfm.pl/Tokfm/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tok FM',,false]);">
+Tok FM
+</a>
+</li>
+<li>
+<a href="http://www.edziecko.pl/edziecko/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Dziecko',,false]);">
+Dziecko
+</a>
+</li>
+<li>
+<a href="http://zdrowie.gazeta.pl/Zdrowie/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Zdrowie',,false]);">
+Zdrowie
+</a>
+</li>
+<li>
+<a href="http://www.blox.pl/html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Blogi',,false]);">
+Blogi
+</a>
+</li>
+<li>
+<a href="http://pogoda.gazeta.pl/" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Pogoda',,false]);">
+Pogoda
+</a>
+</li>
+<li>
+<a href="http://www.sport.pl/sport/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Sport',,false]);">
+Sport
+</a>
+</li>
+<li>
+<a href="http://podroze.gazeta.pl/podroze/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Podre',,false]);">
+Podre
+</a>
+</li>
+<li>
+<a href="http://forum.gazeta.pl/forum/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Forum',,false]);">
+Forum
+</a>
+</li>
+<li>
+<a href="http://forum.gazeta.pl/forum/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Forum',,false]);">
+Forum
+</a>
+</li>
+</ul>
+</div>
+</li></ul>
+</div>
+</div>
+</div>
+</section><!-- UZREditor --><!--22573586,aliasOf--><!-- htmEOF -->
+
+
+<div id="wH_messages">
+
+
+
+
+
+
+
+
+ <div id="wH_user_msg" class="mcBan">
+ <div id="wH_user_msg_content" data-cta="1sqbl-5basic" data-cta-category="czapeczka napis">
+ <a href="http://prenumerata.wyborcza.pl/lp/0,145442.html?cta=1sqbl-2an-5basic">Wyprbuj od 19,90 z</a>
+ </div>
+ </div>
+ <svg class="badge-symbol">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#bell"></use>
+ </svg>
+ <div id="wH_action" class="mcBan">
+ <a id="wH_action_btn" class="wH_btn" data-cta="1sqbl-2an-5basic" data-cta-category="czapeczka przycisk" href="http://prenumerata.wyborcza.pl/lp/0,145442.html?cta=1sqbl-2an-5basic">Kup teraz</a>
+ </div>
+
+<!-- <div id="wH_user_msg" class="mcBan">
+ <div id="wH_user_msg_content" data-cta="1sqbl-5basic" data-cta-category="czapeczka napis">
+ <a href="http://prenumerata.wyborcza.pl/lp/0,166591,24048616.html?cta=1sqbl-3an-550journalists?actionId=7518533d-2606-4905-bdaf-00a9eb088c7b&campaignId=4e827602-54e2-404d-8b18-d9e98d19ee3c">Twoje sprawy, nasza praca. Subskrybuj za p ceny i czytaj Wyborcza.pl</a>
+ </div>
+ </div>
+ <svg class="badge-symbol">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#bell"></use>
+ </svg>
+ <div id="wH_action" class="mcBan">
+ <a id="wH_action_btn" class="wH_btn" data-cta="1sqbl-2an-5basic" data-cta-category="czapeczka przycisk" href="http://prenumerata.wyborcza.pl/lp/0,166591,24048616.html?cta=1sqbb-3an-550journalists?actionId=e246120c-339b-4506-ab5c-1a28c277b636&campaignId=4e827602-54e2-404d-8b18-d9e98d19ee3c">Skorzystaj z promocji -50%</a>
+ </div> -->
+
+
+
+
+</div>
+
+
+<div id="wH_activity">
+
+
+
+
+
+ <div id="wH_user">
+ <form id="wH_login_form" method="POST">
+ <input type="hidden" name="SsoSessionPermanent">
+ <input type="hidden" name="host">
+ <div id="wH_login_btn" class="hat-log-in" onclick="_gaq.push(['_trackEvent', 'CzapeczkaRWD', 'Logowanie', location.hostname, , true]);">
+ Logowanie
+ <svg class="badge-symbol">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#user"></use>
+ </svg>
+ </div>
+ </form>
+ </div>
+
+
+
+
+</div>
+
+
+ </div>
+ </div>
+ </div>
+ </div>
+ </nav>
+
+
+
+
+
+
+
+
+
+
+<!-- squidCapBean v1.1 -->
+
+<!--9638935, [ /fix/modules/wyborcza/cap/cap.jsp ], squidCapBean-->
+
+
+<script type="text/javascript">
+ //<![CDATA[
+ try {
+ var domain_marketing_params = {
+ counter: null,
+ disable: false,
+ differentiateDomainMarketing: 'wsj2017biz',
+ source: '',
+ tags: ["jako powietrza","smog","wgiel","zanieczyszczenie powietrza"],
+ clsarticle: '',
+ marketingDomainRequestParams: '',
+ isActive: 'false',
+ validPeriod: '',
+ subnetCompany: '',
+ freeActivation: 'unknown',
+ agreementState: 'false',
+ loginChannel: '',
+ dmcampaign: '',
+ hardPaywall: true,
+ recoveryMethod: '',
+ rodo22: 'false'
+ };
+ } catch (e) { };
+ //]]>
+</script>
+
+<script>
+ customDataLayer = window.customDataLayer || {};
+ customDataLayer.customer = customDataLayer.customer || {};
+ customDataLayer.customer.state = 'anonymous';
+ customDataLayer.customer.validPeriod = '';
+ document.cookie = 'customDataLayer_customer=' + encodeURIComponent(JSON.stringify(customDataLayer.customer)) + '; domain=wyborcza.pl; path=/';
+</script>
+
+<!-- domain-marketing-module v1.0 -->
+
+<!--13289421, [ /fix/modules/wyborcza/portal/domainMarketingModule.jsp ], domainMarketingModule-->
+
+
+
+
+ <div class="container-outer page-top">
+ <div class="container-inner">
+ <div class="grid-row">
+ <div class="grid-col-4-4 column-58">
+
+
+
+ <div class="winieta-promo">
+ <header id="pageHead" data-like-shift="1">
+<div class="c0">
+<div class="imgw">
+<a id="LinkArea:winietabiz" href="http://wyborcza.biz/biznes/0,0.html" title="Wyborcza.biz"><img src="http://static.im-g.pl/i/obrazki/wyborcza2017/winiety_themes/wyborcza_biz.svg" data-fallback="https://bis.gazeta.pl/im/7/22716/m22716587.png" alt="Wyborcza.biz"></a>
+</div>
+<div class="header-date" id="header-day"><span class="day" id="header-date-day"></span><span class="date" id="header-date-date"></span></div>
+<script>
+(function() {
+var a=document.getElementById("header-date-day"),b=document.getElementById("header-date-date"),c=(now && now.getUTCDate()>1)?new Date(now):new Date,d="niedziela poniedzia\u0142ek wtorek \u015broda czwartek pi\u0105tek sobota".split(" ")[c.getDay()],e=c.getDate(),f=c.getMonth()+1,g=c.getFullYear(),h="";10>e&&(e="0"+e.toString());10>f&&(f="0"+f.toString());h=e+"."+f+"."+g;a.innerHTML=d;b.innerHTML=h;
+})();
+</script>
+</div>
+</header><!-- UZREditor --><!-- htmEOF -->
+<!--22776313, [ /htm/22776/j22776313.htm ], null-->
+
+
+
+
+<div id="071-WINIETA" class="adviewDFPBanner DFP-071-WINIETA">
+ <span class="banLabel" style="display: none;">REKLAMA</span>
+ <div id='div-gpt-ad-071-WINIETA-0'>
+ <script type='text/javascript'>
+ if(dfpParams.slots['071-WINIETA'] && dfpParams.slots['071-WINIETA'].autoLoad) {
+ if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('071-WINIETA');
+ } else {
+ googletag.cmd.push(function() { googletag.display('div-gpt-ad-071-WINIETA-0'); });
+ }
+
+ </script>
+ </div>
+</div>
+
+
+<!-- v2.2 -->
+<!--11070029, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean-->
+
+ </div>
+
+
+
+
+
+
+
+
+<!-- Menubar20_nst API ver 1.0 -->
+
+ <!-- ver 0.45 newMenuBar:false, menuType: 0, nst:, szukaj:false, szukajNSTAlias:null -->
+
+ <nav data-position="nawigacja" id="navH" class="level0"><ul class='p0'><li id='e1' class=' '><a href='http://wyborcza.pl/0,155287.html#TRNavSST' title='Aktualnoci '>Aktualnoci</a></li><li id='e2' class=' '><a href='http://wyborcza.biz/biznes/0,147768.html#TRNavSST' title='Podatki '>Podatki</a></li><li id='e3' class=' '><a href='http://wyborcza.biz/biznes/0,147582.html#TRNavSST' title='Finanse osobiste '>Finanse osobiste</a></li><li id='e4' class=' '><a href='http://wyborcza.biz/biznes/0,147880.html#TRNavSST' title='ZUS i emerytury '>ZUS i emerytury</a></li><li id='e5' class=' '><a href='http://wyborcza.biz/Gieldy/0,114514.html#TRNavSST' title='Gieda '>Gieda</a></li><li id='e6' class=' '><a href='http://wyborcza.biz/Waluty/0,111138,8932151,,,Kursy_srednie_walut_NBP,A.html#TRNavSST' title='Kursy walut '>Kursy walut</a></li><li id='e7' class=' active'><a href='http://wyborcza.biz/biznes/0,147743.html#TRNavSST' title='Konsument i zakupy '>Konsument i zakupy</a></li><li id='e8' class=' '><a href='http://wyborcza.biz/pieniadzeekstra/0,0.html#TRNavSST' title='Pienidze Ekstra '>Pienidze Ekstra</a></li><li id='e9' class=' '><a href='http://wyborcza.biz/biznes/0,147758.html#TRNavSST' title='Nieruchomoci '>Nieruchomoci</a></li><li id='e10' class=' '><a href='http://wyborcza.biz/biznes/0,159911.html#TRNavSST' title='Praca '>Praca</a></li><li id='e11' class=' '><a href='http://www.komunikaty.pl/komunikaty/#TRNavSST' title='Komunikaty '>Komunikaty</a></li><li id='e12' class=' '><a href='##TRNavSST' title='Wicej '>Wicej</a><ul class='p1'><li id='e12_1' class=' '><a href='http://wyborcza.pl/0,155290.html#TRNavSST' title='Opinie '>Opinie</a></li><li id='e12_2' class=' '><a href='http://wyborcza.biz/biznes/0,163992.html#TRNavSST' title='Finanse maej firmy '>Finanse maej firmy</a></li><li id='e12_3' class=' '><a href='http://wyborcza.biz/biznes/0,147584.html#TRNavSST' title='Wasna firma '>Wasna firma</a></li><li id='e12_4' class=' '><a href='http://wyborcza.pl/0,156282.html#TRNavSST' title='Technologie '>Technologie</a></li><li id='e12_5' class=' '><a href='http://wyborcza.biz/biznes/0,156481.html#TRNavSST' title='Motoryzacja i podre '>Motoryzacja i podre</a></li><li id='e12_6' class=' '><a href='http://wyborcza.biz/akcjespecjalne/0,154807.html#TRNavSST' title='Przedsibiorca Roku '>Przedsibiorca Roku</a></li></ul></li></ul></nav>
+
+
+ <div class="mod mod_search" id="pageSearch"><div class="visible"><form><fieldset><input id="pageSearchQ"><input value="Szukaj" type="submit"></fieldset></form></div><div class="hidden"><form data-target="serwis" data-default method="get" action="http://wyborcza.biz/biznes/wyszukaj/artykul"><p><input type="hidden" data-query name="query"/><input value="" name="" type="hidden"></p></form></div></div>
+<!--21407274, [ /htm/21407/n21407274.htm ], null-->
+
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+
+ <section class="ads ads-top container-outer">
+ <div class="container-inner">
+ <div class="grid-row">
+ <div class="grid-col-wide">
+
+
+
+<div id="001-TOPBOARD" class="adviewDFPBanner DFP-001-TOPBOARD">
+ <span class="banLabel" style="display: none;">REKLAMA</span>
+ <div id='div-gpt-ad-001-TOPBOARD-0'>
+ <script type='text/javascript'>
+ if(dfpParams.slots['001-TOPBOARD'] && dfpParams.slots['001-TOPBOARD'].autoLoad) {
+ if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('001-TOPBOARD');
+ } else {
+ googletag.cmd.push(function() { googletag.display('div-gpt-ad-001-TOPBOARD-0'); });
+ }
+
+ </script>
+ </div>
+</div>
+
+
+<!-- v2.2 -->
+<!--10185259, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean-->
+
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <main class="content container-inner">
+
+
+
+ <div class="grid-row">
+ <div class="grid-col-wide">
+
+
+
+
+
+
+
+
+
+
+ <header id="art-header" class="art-header ">
+ <div id="art-tags" class="art-tags">
+
+
+
+
+
+ <a href="http://wyborcza.biz/biznes/0,147743.html" title="Konsument i zakupy"
+ class="art-headline-tag"> <span class="art-tag-label">Konsument i zakupy</span></a>
+
+
+
+
+
+ </div>
+
+ <div class="art-headline" role="headline">
+
+
+
+
+ <h1 class="art-title">Pomys na biznes: chusta, ktra chroni przed smogiem</h1>
+ </div>
+
+ <div class="art-header-meta">
+
+
+ <div class="art-authors">
+ <span class="art-author">Micha Frk</span>
+ </div>
+
+
+
+ <div class="art-author-meta-container">
+
+ <time id="art-datetime" class="art-datetime"
+ datetime="2019-01-31">31 stycznia 2019 | 15:32</time>
+
+ </div>
+ </div>
+ </header>
+
+
+
+<!-- articleMetadata v1.4 -->
+
+<!--9534510, [ /fix/modules/wyborcza/articleMetadata.jsp ], articleMetadata-->
+
+ </div>
+ </div>
+
+
+
+
+ <div class="grid-row splint-parent">
+
+
+
+ <section class="article-and-social grid-col-3-4" >
+ <div class="article">
+ <article class="article-content">
+
+
+
+ <div class="article-image">
+
+
+
+
+
+
+
+
+<!-- 0/4,24414148 -->
+<!-- 1/4,24376685 -->
+<!-- 2/4,24349296 -->
+<!-- 3/4,24302580 -->
+<!-- 0/4,24414148 -->
+<!-- 1/4,24376685 -->
+<!-- 2/4,24349296 -->
+<!-- 3/4,24302580 -->
+
+
+
+
+
+
+
+ <div id="gazeta_article_image"><!-- rel max = 4 -->
+ <div class="article-image-photo">
+
+ <img border="0" src="https://bi.im-g.pl/im/f7/49/17/z24418295Q,Prace-nad-projektem-chusty-antysmogowej-rozpoczely.jpg" title="Prace nad projektem chusty antysmogowej rozpoczy si w lutym 2018 roku, a w styczniu 2019 pojawia si na rynku" alt="Prace nad projektem chusty antysmogowej rozpoczy si w lutym 2018 roku, a w styczniu 2019 pojawia si na rynku">
+ <figure class="box-label-related"><svg class="icon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#gallery"></use></svg><figcaption>1 ZDJ&#280CIE</figcaption></figure>
+
+
+
+
+
+
+
+ </div>
+ <div class="article-image-desc">
+ <p class="desc">Prace nad projektem chusty antysmogowej rozpoczy si w lutym 2018 roku, a w styczniu 2019 pojawia si na rynku<span> (MATERIAY PRASOWE)</span></p>
+
+
+
+ </div>
+
+ </div><!-- #gazeta_article_image -->
+
+
+ <!-- bl sstx -->
+
+
+
+
+
+
+
+
+
+
+
+
+
+<!-- plistaRelated v 1.04 -->
+
+<!--9638972, [ /fix/modules/plistaRelated.jsp ], null-->
+
+ </div>
+
+
+
+
+
+
+
+<section class="article-lead">Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. </section>
+
+
+
+
+
+
+
+ <section class="article-text">
+
+
+
+
+<!-- SQUID_PARAMS: SquidPaywallLock [subnetAccessGranted=false, cookieDisabled=false, javaScriptDisabled=false, referrer=null, saleInProgress=false, paymentUnfinished=false, consentOrderId=null, unfinishedOrderId=null, validUntil=null, loggedInHard=false, loggedInSoft=false, counterShowIndicator=null, remainingVisitTime=null, exclusiveContent=true, loopholeDetected=false, isAssetUnlocked=false, closeByUrlParameter=null, aliasLock=/aliasy/dm/planbhardpaywallclosearticleall.htm, aliasUpper=, aliasLower=, aliasLink=, placeholders={standardLock=/aliasy/dm/planbclosearticleanonymous.htm, specialSectionLowerOpen=/aliasy/paywall/v12/empty.htm, standardLockUserLoggedIn=/aliasy/dm/planbclosearticleinactive.htm, activeOpenUpper=/aliasy/paywall/v12/empty.htm, upperOpen=/aliasy/dm/planbopenarticleupperzajawka.htm, subnetOpenLower=/aliasy/paywall/v12/empty.htm, closeByUrl=/aliasy/wejsciezmailingu.htm, loopholeDetected=/aliasy/dm/planbclosearticleloophole.htm, googleReferrerAnonymous=/aliasy/dm/planbopenarticlegoogleanonymous.htm, lowerOpenExclusive=/aliasy/paywall/v12/empty.htm, gazetaReferrerWeekendInactive=/aliasy/dm/planbclosearticlegaplweekend.htm, facebookReferrerAnonymous=/aliasy/dm/planbopenarticlefacebookanonymous.htm, cookieDisabled=/aliasy/wyborcza/nocookiesbox.htm, standardOpenUserLoggedIn=/aliasy/dm/pustazajawkav2.htm, gazetaReferrerNormalInactive=/aliasy/dm/planbopenarticlegaplpnpt.htm, specialSectionUpperOpen=/aliasy/paywall/v12/empty.htm, gazetaReferrerNormal=/aliasy/dm/planbopenarticlegaplpnpt.htm, subnetOpenApper=/aliasy/paywall/v12/empty.htm, activeOpenLower=/aliasy/paywall/v12/empty.htm, lowerOpen=/aliasy/dm/planbopenarticleanonymous.htm, exclusiveContentLock=/aliasy/dm/planbhardpaywallclosearticleall.htm, paymentFailed=/aliasy/paywall/v12/nieudana.jsp, serviceNotStarted=/aliasy/paywall/v12/uruchom.jsp, inProgressLock=/aliasy/paywall/v12/czekamy.htm, gazetaReferrerWeekend=/aliasy/dm/planbclosearticlegaplweekend.htm, specialSectionClose=/aliasy/dm/closearticleanonymousroot.htm, facebookReferrerInactive=/aliasy/dm/planbopenarticlefacebookinactive.htm, javascriptDisabled=/aliasy/paywall/v12/nojavascript.htm, upperOpenExclusive=/aliasy/dm/openarticleupperhardpaywall.htm, googleReferrerInactive=/aliasy/dm/planbopenarticlegoogleinactive.htm}, aliasEnabled=true, isPaywallException=false, hashedLogin=null, defaultSalesProductId=true] ; accessEnabled=false ; accessPaid=false ; locked=true -->
+
+<script type="text/javascript">
+ //<![CDATA[
+ try {
+
+ var pw_application_data = {
+ UID: '',
+ RemainingAccess: '',
+ Alias: '/aliasy/dm/planbhardpaywallclosearticleall.htm',
+ Paid: 'false',
+ PaywallType: 'Hard',
+ Access: 'false',
+ AccessTo: ''
+ };
+
+ var pw_article = {
+ url: 'wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html',
+ title: '',
+ lead: 'Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. '
+ };
+
+ var wyborcza_pl = wyborcza_pl || {};
+ wyborcza_pl.bigData = {
+ EVENT_NAME: "pwdataready",
+ init: function () {
+ var a = "undefined" != typeof pw_application_data;
+ a && this.createEvent()
+ },
+ createEvent: function () {
+ var a = wyborcza_pl.bigData,
+ b = {};
+ document.createEvent ? (b = document.createEvent("HTMLEvents"), b.initEvent(a.EVENT_NAME, !0, !0)) : (b = document.createEventObject(), b.eventType = a.EVENT_NAME), b.eventName = a.EVENT_NAME, document.createEvent ? document.dispatchEvent(b) : document.fireEvent("on" + b.eventType, b)
+ }
+ }, wyborcza_pl.bigData.init();
+
+ } catch (e) {
+ }
+ ;
+ //]]>
+</script>
+
+
+
+<script>
+ //<![CDATA[
+ var gazeta_pl = gazeta_pl || {};
+ gazeta_pl.Config = gazeta_pl.Config || {};
+ gazeta_pl.Config.Squid = {
+ domain: 'GQOnEDUr8VUlxDfzolv2hQ',
+ luid: '',
+ dc: ''
+ };
+ //]]>
+</script>
+
+<!-- paywallScriptModule 1.1 -->
+
+<!--13124793, [ /fix/modules/wyborcza/paywall/paywallScriptModule.jsp ], paywallScriptModule-->
+
+
+
+
+
+
+<!-- paywallUpperPlaceholder 1.1 -->
+
+<!--13124791, [ /fix/modules/wyborcza/paywall/paywallUpperPlaceholder.jsp ], paywallUpperPlaceholder-->
+
+
+
+
+<html>
+ <head></head>
+ <body>
+ <section class="art_content" itemprop="articleSection">
+ <p class="art_paragraph">Cho&#x107; Wroc&#x142;aw to pi&#x119;kne miasto, cz&#x119;sto pojawia si&#x119; w polskiej czo&#x142;&oacute;wce tych najbardziej ogarni&#x119;tych smogiem. Zdarza si&#x119;, &#x17c;e i w &#x15b;wiatowym rankingu zajmuje wysokie lokaty. Wszystko zale&#x17c;y od tego, jaka akurat jest pogoda.</p>
+ <p class="art_paragraph">Tr&oacute;jka zaniepokojonych wroc&#x142;awian od dawna obserwuje wskazania dw&oacute;ch oficjalnych stacji pomiarowych. I cz&#x119;sto a&#x17c; strach jest wyj&#x15b;&#x107; z domu. Zreszt&#x105; w nim te&#x17c; nie jest du&#x17c;o lepiej.</p>
+ <p class="art_paragraph">&#x2013; Szkodliwe cz&#x105;steczki unosz&#x105;ce si&#x119; w smogu s&#x105; bardzo ma&#x142;e. Gdy ich st&#x119;&#x17c;enie jest wysokie, dostaj&#x105; si&#x119; r&oacute;wnie&#x17c; do wewn&#x105;trz mieszka&#x144;. A to oznacza, &#x17c;e przed smogiem nie da si&#x119; ca&#x142;kowicie ochroni&#x107;, zamykaj&#x105;c drzwi i okna &#x2013; opowiada Adam Muszy&#x144;ski oraz Diana i Przemek Jaworscy, za&#x142;o&#x17c;yciele firmy produkuj&#x105;cej innowacyjny element ubioru chroni&#x105;cy przed smogiem.</p>
+ <p class="art_paragraph">Oni, jak i wielu ich znajomych, chcieli zadba&#x107; o zdrowie. Jednym ze sposob&oacute;w na to jest noszenie specjalnej maseczki z filtrem. Do obrazka, na kt&oacute;rym ludzie maj&#x105; pozas&#x142;aniane bia&#x142;ymi maseczkami twarze, przywykli&#x15b;my ogl&#x105;daj&#x105;c relacje z najwi&#x119;kszych miast Azji, gdzie problem smogu jest szczeg&oacute;lnie widoczny.</p>
+ </section>
+ </body>
+</html>
+
+
+<!--13006101, [ /tpl/prod/content/article/modules/article_body_sst.jsp ], awdArticleBodySstModuleTomcat-->
+
+
+
+
+
+
+
+<!-- percentageOfArticleBehindPaywallModule 1.1 -->
+
+<!--13270618, [ /fix/modules/wyborcza/percentageOfArticleBehindPaywallModule.jsp ], percentageOfArticleBehindPaywallModule-->
+
+
+
+
+
+ <div class="mcBan" id="mcBan_1">
+ <section class="padlock
+padlock-article-title-align-center
+locked
+padlock-text-align-center"
+data-cta="1pbox-2hd-5basic" data-cta-category="zajawka zamkniecia">
+<header class="padlock-header">
+<figure class="padlock-header-symbol">
+<svg class="icon">
+<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#diamond"></use>
+</svg>
+</figure>
+<span class="padlock-header-msg">Artyku dostpny tylko w prenumeracie cyfrowej Wyborczej</span>
+</header>
+<div class="padlock-main">
+<div class="padlock-body-container">
+<article class="padlock-article">
+<h3 class="padlock-article-title">Wyprbuj cyfrow Wyborcz</h3>
+<p class="padlock-article-lead">Nieograniczony dostp do serwisw informacyjnych, biznesowych, lokalnych i wszystkich magazynw Wyborczej</p>
+</article>
+<div class="padlock-action" data-utilize-child="true" data-utilize="x">
+<a class="padlock-action-url" href="http://prenumerata.wyborcza.pl/lp/0,145442.html?cta=1pbox-2hd-5basic">
+Wyprbuj teraz od 19,90 z
+</a>
+</div>
+<footer class="padlock-footer">
+<ul class="padlock-footer-list">
+<li class="padlock-footer-list-login">
+<a class="pianoLoginBoxBtn" href="#">
+Zaloguj si
+</a>
+</li>
+<li class="padlock-footer-list-contact">
+<a href="mailto:pomoc@wyborcza.pl">
+Kontakt
+</a>
+</li>
+</ul>
+</footer>
+</div>
+</div>
+</section><!-- UZREditor --><!--22768337,aliasOf--><!-- htmEOF -->
+ </div>
+
+
+<!-- paywallBottomPlaceholder 1.1 -->
+
+<!--13124792, [ /fix/modules/wyborcza/paywall/paywallBottomPlaceholder.jsp ], paywallBottomPlaceholder-->
+
+
+ </section>
+
+
+
+
+<!-- articleModule v1.5 -->
+
+<!--9638918, [ /fix/modules/wyborcza/articleModule.jsp ], wyborczaArticleModule-->
+
+
+
+
+
+
+<!-- partnerInfoModule -->
+
+<!--11917370, [ /fix/modules/wyborcza/portal/partnerInfoModule.jsp ], emptyBean-->
+
+
+
+
+
+
+
+<!-- ContentHolderExtractorModule v1.0.5 -->
+
+<!--13198008, [ /fix/modules/wyborcza/portal/contentHolderExtractorModule.jsp ], contentHolderExtractorModule-->
+
+
+
+
+ </article>
+ <div class="show-survey-quiz">
+
+ </div>
+ </div>
+
+
+
+
+
+
+ <!--supertag-start -->
+
+
+
+
+
+
+
+
+
+
+
+
+ <!-- supertag-end -->
+
+ <section class="post-article">
+
+
+
+ <div class="post-article-content-holder column-54" data-position="54,1-5">
+
+
+
+
+
+
+<!-- (~MODSTART : 7 :21407481) DFP 1.2 -->
+
+
+
+
+<div class="index zi_index_read_other">
+
+ <div class="head">
+
+ <h3>
+ Inne
+ </h3>
+
+
+ </div>
+ <div class="body">
+ <ul>
+
+
+
+
+
+
+
+ <li class="entry even article" >
+
+
+
+
+
+
+
+
+ <div class="imgw">
+
+
+
+ <ul>
+ <li >
+ <a href="http://wyborcza.biz/biznes/7,147743,24418098,inwestujemy-w-oczyszczacz-powietrza-podpowiadamy-jak-dobrac.html">
+ <img src="https://bi.im-g.pl/im/f9/49/17/z24418297II,Oczyszczacze-powietrza-pozwola-nam-zdrowo-oddychac.jpg"/>
+ </a>
+
+
+ </li>
+ </ul>
+
+
+ </div>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <span class="base"><a href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a>
+ </span>
+
+
+
+
+
+
+
+
+ <h4>
+ <a title="Inwestujemy w oczyszczacz powietrza. Podpowiadamy, jak dobra urzdzenie i ile to kosztuje" href="http://wyborcza.biz/biznes/7,147743,24418098,inwestujemy-w-oczyszczacz-powietrza-podpowiadamy-jak-dobrac.html">Inwestujemy w oczyszczacz powietrza. Podpowiadamy, jak dobra urzdzenie i ile to kosztuje</a>
+ </h4>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </li>
+
+
+
+
+
+
+
+
+
+
+
+ <li class="entry odd article" >
+
+
+
+
+
+
+
+
+ <div class="imgw">
+
+
+
+ <ul>
+ <li >
+ <a href="http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html">
+ <img src="https://bi.im-g.pl/im/f7/49/17/z24418295II,Prace-nad-projektem-chusty-antysmogowej-rozpoczely.jpg"/>
+ </a>
+
+
+ </li>
+ </ul>
+
+
+ </div>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <span class="base"><a href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a>
+ </span>
+
+
+
+
+
+
+
+
+ <h4>
+ <a title="Pomys na biznes: chusta, ktra chroni przed smogiem" href="http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html">Pomys na biznes: chusta, ktra chroni przed smogiem</a>
+ </h4>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </li>
+
+
+
+
+
+
+
+
+
+
+
+ <li class="entry even article" >
+
+
+
+
+
+
+
+
+ <div class="imgw">
+
+
+
+ <ul>
+ <li >
+ <a href="http://wyborcza.biz/biznes/7,147743,24314790,polskie-kasjerki-zaczynaja-sie-bac-kiedy-straca-prace-przez.html">
+ <img src="https://bi.im-g.pl/im/3c/32/17/z24325436II,Kasa-samoobslugowa-w-drogerii-Rossmann-.jpg"/>
+ </a>
+
+
+ </li>
+ </ul>
+
+
+ </div>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <span class="base"><a href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a>
+ </span>
+
+
+
+
+
+
+
+
+ <h4>
+ <a title="Kasjerki i kasjerzy si boj. Kiedy strac prac przez automaty?" href="http://wyborcza.biz/biznes/7,147743,24314790,polskie-kasjerki-zaczynaja-sie-bac-kiedy-straca-prace-przez.html">Kasjerki i kasjerzy si boj. Kiedy strac prac przez automaty?</a>
+ </h4>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </li>
+
+
+
+
+
+
+
+
+
+
+
+ <li class="entry odd article" >
+
+
+
+
+
+
+
+
+ <div class="imgw">
+
+
+
+ <ul>
+ <li >
+ <a href="http://wyborcza.biz/biznes/7,147743,24323989,zywnosc-gaz-prad-paliwa-ceny-pietruszki-moga-byc-rekordowe.html">
+ <img src="https://bi.im-g.pl/im/e3/32/17/z24324067II,Warzywa.jpg"/>
+ </a>
+
+
+ </li>
+ </ul>
+
+
+ </div>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <span class="base"><a href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a>
+ </span>
+
+
+
+
+
+
+
+
+ <h4>
+ <a title="ywno, gaz, prd, paliwa... Ceny pietruszki mog by rekordowe. Co jeszcze zdroeje w 2019? A co stanieje?" href="http://wyborcza.biz/biznes/7,147743,24323989,zywnosc-gaz-prad-paliwa-ceny-pietruszki-moga-byc-rekordowe.html">ywno, gaz, prd, paliwa... Ceny pietruszki mog by rekordowe. Co jeszcze zdroeje w 2019? A co stanieje?</a>
+ </h4>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </li>
+
+
+
+
+
+
+
+
+
+
+
+ <li class="entry even article" >
+
+
+
+
+
+
+
+
+ <div class="imgw">
+
+
+
+ <ul>
+ <li >
+ <a href="http://wyborcza.biz/biznes/7,147743,24303027,eksperyment-sklepowy-z-slodyczy-rezygnowac-nie-lubimy-przy.html">
+ <img src="https://bi.im-g.pl/im/70/17/17/z24212592II.jpg"/>
+ </a>
+
+
+ </li>
+ </ul>
+
+
+ </div>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <span class="base"><a href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a>
+ </span>
+
+
+
+
+
+
+
+
+ <h4>
+ <a title="Ze sodyczy rezygnowa nie lubimy. Przy rybie robimy si asertywni. Na zakupach nie zawsze kierujemy si cen" href="http://wyborcza.biz/biznes/7,147743,24303027,eksperyment-sklepowy-z-slodyczy-rezygnowac-nie-lubimy-przy.html">Ze sodyczy rezygnowa nie lubimy. Przy rybie robimy si asertywni. Na zakupach nie zawsze kierujemy si cen</a>
+ </h4>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </li>
+
+
+
+
+
+
+
+
+
+
+
+ <li class="entry odd article" >
+
+
+
+
+
+
+
+
+ <div class="imgw">
+
+
+
+ <ul>
+ <li >
+ <a href="http://wyborcza.biz/biznes/7,147743,24293124,idzie-boom-kawowy-bedzie-taniej-i-zdrowiej.html">
+ <img src="https://bi.im-g.pl/im/bf/0a/17/z24161983II.jpg"/>
+ </a>
+
+
+ </li>
+ </ul>
+
+
+ </div>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <span class="base"><a href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a>
+ </span>
+
+
+
+
+
+
+
+
+ <h4>
+ <a title="Idzie boom kawowy. Bdzie taniej i zdrowiej" href="http://wyborcza.biz/biznes/7,147743,24293124,idzie-boom-kawowy-bedzie-taniej-i-zdrowiej.html">Idzie boom kawowy. Bdzie taniej i zdrowiej</a>
+ </h4>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </li>
+
+
+
+
+
+
+ </ul>
+ </div>
+ <div class="footer">
+
+
+
+ </div>
+</div>
+<!-- (~MODEND:7:21407481 s43a:1,313,063) -->
+
+<!--21407481, [ /tpl/prod/universalIndex/universalIndex.jsp ], universalIndexBean-->
+
+
+
+ </div>
+ <section class="post-article-related">
+
+
+
+
+
+
+
+
+<!-- related debug: RelatedResultDto{relatedItems=[RelatedItem [xx=24414148, title=Duo smogu, mao alarmw. Polska "mistrzem" rakotwrczych wyzieww, url=http://wyborcza.pl/7,155287,24414148,europejski-raport-o-smogu-polska-walczy-za-slabo.html, photoUrl=https://bi.im-g.pl/im/3e/29/17/z24289086D.jpg, photoTitle=null], RelatedItem [xx=24376685, title=Miliony Polakw mog wreszcie odliczy od podatku panele soneczne. I... dalej dorzuca do kopciuchw, url=http://wyborcza.pl/7,155287,24376685,miliony-polakow-moga-wreszcie-odliczyc-od-podatku-panele-sloneczne.html, photoUrl=https://bi.im-g.pl/im/24/3f/17/z24376868D,Panele-fotowoltaiczne.jpg, photoTitle=Panele fotowoltaiczne], RelatedItem [xx=24349296, title=Kolejny milion uywanych aut z importu. Dodatkowa dostawa smogu z Zachodu, url=http://wyborcza.pl/7,155287,24349296,kolejny-milion-uzywanych-aut-z-importu-dodatkowa-dostawa-smogu.html, photoUrl=https://bi.im-g.pl/im/39/39/17/z24351545D,Uzywane-auta-z-Niemiec---w-drodze-do-polskiego-nab.jpg, photoTitle=Uywane auta z Niemiec - w drodze do polskiego nabywcy. A4, 29 stycznia 2017], RelatedItem [xx=24302580, title=Jaki oczyszczacz powietrza kupi do domu?, url=http://wyborcza.biz/pieniadzeekstra/7,134263,24302580,jaki-oczyszczacz-powietrza-kupic-do-domu.html, photoUrl=https://bi.im-g.pl/im/1f/2d/17/z24302879D,Oczyszczacz-powietrza.jpg, photoTitle=Oczyszczacz powietrza]], putBanPosition=2, articleAmount=4, itemsNoWithPutban=4} -->
+
+
+ <div class="gazeta_article_related_new">
+ <div class="rel_head" >
+ Zobacz take
+ </div>
+
+
+
+
+
+
+
+ <ul>
+
+
+ <li class="fLeft ">
+
+
+
+
+ <a href="http://wyborcza.pl/7,155287,24414148,europejski-raport-o-smogu-polska-walczy-za-slabo.html" title="Duo smogu, mao alarmw. Polska &#034;mistrzem&#034; rakotwrczych wyzieww">
+ <img src="https://bi.im-g.pl/im/3e/29/17/z24289086II.jpg" alt=""/>
+ </a>
+ <a class="t" href="http://wyborcza.pl/7,155287,24414148,europejski-raport-o-smogu-polska-walczy-za-slabo.html" title="Duo smogu, mao alarmw. Polska &#034;mistrzem&#034; rakotwrczych wyzieww" >Duo smogu, mao alarmw. Polska &#034;mistrzem&#034; rakotwrczych wyzieww</a>
+
+
+
+ </li>
+
+ <li class="">
+
+
+
+
+ <a href="http://wyborcza.pl/7,155287,24376685,miliony-polakow-moga-wreszcie-odliczyc-od-podatku-panele-sloneczne.html" title="Miliony Polakw mog wreszcie odliczy od podatku panele soneczne. I... dalej dorzuca do kopciuchw">
+ <img src="https://bi.im-g.pl/im/24/3f/17/z24376868II,Panele-fotowoltaiczne.jpg" alt="Panele fotowoltaiczne"/>
+ </a>
+ <a class="t" href="http://wyborcza.pl/7,155287,24376685,miliony-polakow-moga-wreszcie-odliczyc-od-podatku-panele-sloneczne.html" title="Miliony Polakw mog wreszcie odliczy od podatku panele soneczne. I... dalej dorzuca do kopciuchw" >Miliony Polakw mog wreszcie odliczy od podatku panele soneczne. I... dalej dorzuca do kopciuchw</a>
+
+
+
+ </li>
+
+ <li class="">
+
+
+
+
+
+
+
+
+
+
+
+
+ <script src='https://www.googletagservices.com/tag/js/gpt.js'>
+ var _YB = _YB || {
+ ab: function() {
+ return (Math.random() >= 0.1 ? 'b' : 'a' + Math.floor(Math.random() * 10));
+ }
+ };
+ var _yt = new Date(), yb_th = _yt.getUTCHours() - 8, yb_tm = _yt.getUTCMinutes(), yb_wd = _yt.getUTCDay();
+ if (yb_th < 0) {
+ yb_th = 24 + yb_th;
+ yb_wd -= 1;
+ };
+ if (yb_wd < 0) {
+ yb_wd = 7 + yb_wd
+ };
+ googletag.pubads().definePassback('/52555387/wyborcza.pl_native_desktop', ['fluid']).setTargeting('yb_ab', _YB.ab()).setTargeting('yb_ff', String(Math.round(Math.random()))).setTargeting('yb_th', yb_th.toString()).setTargeting('yb_tm', yb_tm.toString()).setTargeting('yb_wd', yb_wd.toString()).display();
+ </script>
+
+
+
+
+
+
+
+ </li>
+
+ <li class="">
+
+
+
+
+ <a href="http://wyborcza.pl/7,155287,24349296,kolejny-milion-uzywanych-aut-z-importu-dodatkowa-dostawa-smogu.html" title="Kolejny milion uywanych aut z importu. Dodatkowa dostawa smogu z Zachodu">
+ <img src="https://bi.im-g.pl/im/39/39/17/z24351545II,Uzywane-auta-z-Niemiec---w-drodze-do-polskiego-nab.jpg" alt="Uywane auta z Niemiec - w drodze do polskiego nabywcy. A4, 29 stycznia 2017"/>
+ </a>
+ <a class="t" href="http://wyborcza.pl/7,155287,24349296,kolejny-milion-uzywanych-aut-z-importu-dodatkowa-dostawa-smogu.html" title="Kolejny milion uywanych aut z importu. Dodatkowa dostawa smogu z Zachodu" >Kolejny milion uywanych aut z importu. Dodatkowa dostawa smogu z Zachodu</a>
+
+
+
+ </li>
+
+
+ </ul>
+ </div>
+
+
+
+<!-- /relatedModule v 2.0 -->
+
+
+<!--9638939, [ /fix/modules/wyborcza/relatedModule_dfp.jsp ], relatedArticlesModule-->
+
+ </section>
+ <div class="post-article-content-holder column-48" data-position="48,1-5 12-15">
+
+
+
+
+
+
+
+
+
+ </div>
+
+
+
+
+
+
+ <div id="tags" class="tags">
+ <span class="tags-label">Wicej na ten temat:</span>
+
+ <a href="/biznes/0,104259.html?tag=smog" title="smog" class="tag-anchor"><span class="tag-label">smog</span></a>,
+
+ <a href="/biznes/0,104259.html?tag=w%EAgiel" title="wgiel" class="tag-anchor"><span class="tag-label">wgiel</span></a>,
+
+ <a href="/biznes/0,104259.html?tag=jako%B6%E6+powietrza" title="jako powietrza" class="tag-anchor"><span class="tag-label">jako powietrza</span></a>,
+
+ <a href="/biznes/0,104259.html?tag=zanieczyszczenie+powietrza" title="zanieczyszczenie powietrza" class="tag-anchor"><span class="tag-label">zanieczyszczenie powietrza</span></a>
+
+ </div>
+
+
+<!-- universalTags v1.0 -->
+
+<!--410352220, [ /fix/modules/wyborcza/universalTags.jsp ], seoTagController-->
+
+ </section>
+
+ <div class="social">
+ <div class="social-bar">
+
+
+
+
+
+
+
+ <div class="socialBar">
+ <ul>
+ <li class="readLater">
+ <a href="#" title="Dodaj do schowka">
+ <figure class="badge-ico">
+ <svg class="badge-symbol">
+ <use xlink:href="#read-later"></use>
+ </svg>
+ </figure>
+ </a>
+ <span class="sharesCount"></span>
+ </li>
+
+
+ <li class="fbShare">
+ <a href="#" title="Udostpnij na Facebooku">
+ <figure class="badge-ico">
+ <svg class="badge-symbol">
+ <use xlink:href="#face-v2"></use>
+ </svg>
+ </figure>
+ </a>
+ <span class="sharesCount"></span>
+ </li>
+
+
+
+ <li class="twitter">
+ <a href="#" title="Udostpnij na Twitterze">
+ <figure class="badge-ico">
+ <svg class="badge-symbol">
+ <use xlink:href="#twitter-v2"></use>
+ </svg>
+ </figure>
+ </a>
+ <span class="tweetsCount"></span>
+ </li>
+
+
+
+
+ <li class="email">
+ <a href="http://gazeta.hit.gemius.pl/hitredir/id=nF6a2AyFf_IJ3woQ3DRcKcewPxLw1u7SUeW3YDM9WRb.P7/stparam=wkionrdgis/url=http://wyborcza.biz/2029030,75247.html?b=1&x=24417936&d=0&utm_source=recommend&utm_medium=email&utm_campaign=recommend&home=http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html?disableRedirects=true" title="Udostpnij e-mailem">
+ <figure class="badge-ico">
+ <svg class="badge-symbol">
+ <use xlink:href="#mail-v2"></use>
+ </svg>
+ </figure>
+ </a>
+ </li>
+
+
+
+ <li class="comments">
+ <a href="#" title="Zobacz komentarze">
+ <figure class="badge-ico">
+ <svg class="badge-symbol">
+ <use xlink:href="#comments-v2"></use>
+ </svg>
+ </figure>
+ </a>
+ <span class="commentsCount"></span>
+ </li>
+
+
+ </ul>
+ </div>
+
+
+<!-- socialBar v1.1 -->
+
+<!--9638922, [ /fix/modules/wyborcza/socialBar.jsp ], toolsSocialMediaPojoBean-->
+
+ </div>
+ <div class="comments-container">
+
+
+
+
+
+
+
+<!-- opinions20 -->
+<!-- serwis: 2 -->
+<!-- role: -->
+
+
+
+
+
+
+
+
+
+<script>
+ wyborcza_pl.commentsUserData = Object.freeze((function () {
+ var opts = {
+ roles: {
+ admin: false,
+ editor: false
+ },
+ userNick: "",
+ loginChannel: '',
+ userLink: '',
+ wyborcza_pl_service_id: 2,
+ voting: true,
+ writing: false,
+ commentedObjectId: 24417936
+ };
+
+ function getParam(paramName) {
+ return opts[paramName];
+ }
+
+ function getRole(roleName) {
+ return opts['roles'][roleName];
+ }
+
+ return {
+ getParam: getParam,
+ isRole: getRole
+ };
+
+ })());
+</script>
+
+
+
+ <!-- komentarze ON -->
+ <!-- test session: '' / '' : '' : '' -->
+
+
+ <section class="comments">
+
+
+
+ <header class="oHead">
+ <div class="oCntInf">
+ <span class="ocntHead">Komentarze</span>
+ <div class="oCntWrap">
+ <svg class="oCntBg">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#comment"></use>
+ </svg>
+ <span id="oCnt"></span>
+ </div>
+ </div>
+
+ <!-- komentarze_pisanie: 'false' -->
+ <div class="oFormBox">
+ <form action="#" method="post" class="oForm">
+ <div class="row rTop">
+
+
+
+
+ <div class="subscribeToWriteComment">Chcesz doczy do dyskusji? <a href="//wyborcza.pl/komentarzeoferta">Zosta naszym prenumeratorem</a></div>
+
+
+ </div>
+
+ <div class="row rBtm">
+
+
+ <div class="cLogin">
+ <span class="cLoginLink">Zaloguj si</span>
+ <div class="logOpts">
+
+ <a data-action-login="wyborcza" id="oLoginWyborcza" title="Zaloguj uywajc konta Wyborcza">
+ <svg xmlns="http://www.w3.org/2000/svg" class="logOptsWybprcza" viewBox="0 0 28 28">
+ <defs>
+ <style>
+ #wyborcza_PL_rect1 { fill: #fff; } #wyborcza_PL_rect2, #wyborcza_PL_polygon1 { fill: #a1a1a1; } #wyborcza_PL:hover #wyborcza_PL_polygon1 { fill: #000; } #wyborcza_PL:hover #wyborcza_PL_rect1 { fill: #f1f1f1; } #wyborcza_PL:hover #wyborcza_PL_rect2 { fill: #df012d; }
+ </style>
+ </defs>
+ <g id="wyborcza_PL">
+ <rect id="wyborcza_PL_rect1" width="28" height="28"/>
+ <rect id="wyborcza_PL_rect2" width="12.22" height="18.84" x="3.56" y="4.58"/>
+ <polygon id="wyborcza_PL_polygon1" points="26.73 10.38 25.81 10.38 24.16 16.62 24.03 16.61 22.14 11.2 23.26 10.54 23.26 10.38 18.55 10.38 18.55 10.54 19.29 11.21 22.25 19.53 23.95 19.53 25.31 14.44 25.45 14.44 27.25 19.53 28 19.53 28 14.03 26.73 10.38"/>
+ </g>
+ </svg>
+ </a>
+
+ <a data-action-login="facebook" id="oLoginFacebook" title="Zaloguj uywajc Facebooka">
+ <svg xmlns="http://www.w3.org/2000/svg" class="logOptsFacebook" viewBox="0 0 28 28">
+ <defs>
+ <style>
+ #Facebook_Ico #Facebook_Ico_path1 { fill: #fff; fill-rule: evenodd; } #Facebook_Ico:hover #Facebook_Ico_path1 { fill: #3664a2; fill-rule: evenodd; } #Facebook_Ico #Facebook_Ico_rect1 { fill: #a1a1a1; } #Facebook_Ico:hover #Facebook_Ico_rect1 { fill: #fff; }
+ </style>
+ </defs>
+ <g id="Facebook_Ico">
+ <rect id="Facebook_Ico_rect1" width="28" height="28"/>
+ <path id="Facebook_Ico_path1" d="M-9.28-4.3v28h28v-28h-28ZM9,9.28H5.73v9.42H2.21V9.27H-0.29V7.14H2.22V4.41c0-4,4.42-3.69,4.42-3.69h2.6V3.29H7.14A1.17,1.17,0,0,0,5.73,4.35V7.14h4Z" transform="translate(9.28 4.3)"/>
+ </g>
+ </svg>
+ </a>
+
+ <a data-action-login="google" id="oLoginGoogle" title="Zaloguj uywajc Google+">
+ <svg xmlns="http://www.w3.org/2000/svg" class="logOptsFacebook" viewBox="0 0 28 28">
+ <defs>
+ <style>
+ #GooglePlus_Ico #GooglePlus_Ico_path1 { fill: #fff; fill-rule: evenodd; } #GooglePlus_Ico:hover #GooglePlus_Ico_path1 { fill-rule: evenodd; fill: #d34836; } #GooglePlus_Ico:hover #GooglePlus_Ico_rect1 { fill: #fff; } #GooglePlus_Ico #GooglePlus_Ico_rect1 { fill: #a1a1a1; }
+ </style>
+ </defs>
+ <g id="GooglePlus_Ico">
+ <rect id="GooglePlus_Ico_rect1" width="28" height="28"/>
+ <path id="GooglePlus_Ico_path1" d="M0,0V28H28V0H0ZM10.36,20A6.18,6.18,0,0,1,4,14a6.18,6.18,0,0,1,6.36-6,6.31,6.31,0,0,1,4.26,1.57L12.9,11.14a3.7,3.7,0,0,0-2.54-.93A3.87,3.87,0,0,0,6.42,14a3.87,3.87,0,0,0,3.95,3.79A3.39,3.39,0,0,0,14,15.2H10.36V13.14h6a5.23,5.23,0,0,1,.1,1C16.47,17.57,14,20,10.36,20ZM24,14.86H22.18v1.71H20.36V14.86H18.55V13.14h1.82V11.43h1.82v1.71H24v1.71Z"/>
+ </g>
+ </svg>
+ </a>
+
+ </div>
+
+
+
+ </div>
+
+
+
+
+
+
+ <div class="cSubmit">
+ <button id="oFormSubmit" class="sendCommentButton" disabled="disabled" name="submit">Skomentuj</button>
+ </div>
+ </div>
+ </form>
+ </div>
+
+ <!-- komentarze_sortowanie: PopularityDesc -->
+ <div class="oSortOpts" data-sort="Popularity-Desc">
+ <a data-action="sortComments" data-sort-by="Time">Najnowsze</a>
+ <a data-action="sortComments" data-sort-by="Popularity">Popularne</a>
+ </div>
+ </header><!-- /header -->
+
+
+
+
+
+
+ <!-- komentarze_liczba_pod_art: 8 -->
+
+ <section class="oBody">
+
+
+ <!-- komentarz -->
+
+ <div class="cRow cResHidden flag_PUBLISHED" id="opinion101252015" itemscope
+ itemtype="http://schema.org/Comment" itemid="#opinion:101252015">
+ <div class="cHead">
+
+ <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
+
+
+
+ sceptyczny_debunk
+
+
+ </span></span>
+
+ <span class="cDate" itemprop="datePublished" content="2019-01-31">31.01.2019, 16:16</span>
+
+
+ </div>
+ <div class="cBody">
+
+
+
+ <span itemprop="text">W Azji maski nosi si nie z powodu smogu, tylko SARS, ptasiej grypy i z powodu przepisw sanitarnych, ktre nakazuj nosi maski w miejscach publicznych osobom kaszlcym czy kichajcym. Z tego samego powodu w wielu miejscach publicznych s montowane kamery mierzce temperatur ciaa przechodniw, np. w metrze w Hongkongu czy w Tajpej. Jak si patrzy na polski autobus w styczniu peen zasmarkanych, kichajcych i kaszlcych ludzi, ktrzy nawet doni nie zasaniaj ust, to trzeba si zastanowi, jakim cudem ten nard jeszcze nie wymar.</span></div>
+ <div class="cFt">
+
+ <!-- komentarze_ocenianie ON -->
+ <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
+ <div class="cVoteUp">
+ <a data-action="vote-addPlus" data-post-id="101252015">
+ <svg class="voteUpIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
+ </svg>
+ </a>
+ <span itemprop="upvoteCount">9</span>
+ </div>
+
+ <div class="cVoteDown">
+ <a data-action="vote-addMinus" data-post-id="101252015">
+ <svg class="voteDownIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
+ </svg>
+ </a>
+ <span itemprop="downvoteCount">1</span>
+ </div>
+
+ <div class="cVoteSpam">
+ <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252015">
+ <svg class="voteSpamIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
+ </svg>
+ </a>
+ </div>
+ <div class="cResWrite">
+ <a data-child-for="101252015" data-reply-to="sceptyczny_debunk">
+ <svg class="resWriteIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
+ </svg>
+ Odpowiedz</a>
+ </div>
+
+
+ <div class="cResShow">
+ <a data-res-counter="1" data-state="show">
+ <svg class="resShowIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#comment"></use>
+ </svg>
+
+
+
+ Poka odpowiedzi (1)
+
+
+ </a>
+ </div>
+
+ </div>
+
+
+
+ <!-- Odpowiedzi na komentarz -->
+
+ <!-- komentarz zagniezdzony -->
+ <div class="cRow cResComment flag_PUBLISHED" id="opinion101252118"
+ itemscope itemtype="http://schema.org/Comment" itemid="#opinion:101252118">
+ <div class="cHead">
+
+ <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
+
+
+
+ lizabet
+
+ </span></span>
+
+ <span class="cDate" itemprop="datePublished"
+ content="2019-01-31">31.01.2019, 16:39</span>
+
+
+ </div>
+ <div class="cBody">
+
+
+
+ <span itemprop="text">@sceptyczny_debunk<br/>Czsto o tym myl. Dziki za te celne uwagi.</span></div>
+ <div class="cFt">
+
+ <!-- komentarze_ocenianie ON -->
+ <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
+ <div class="cVoteUp">
+ <a data-action="vote-addPlus" data-post-id="101252118">
+ <svg class="voteUpIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
+ </svg>
+ </a>
+ <span itemprop="upvoteCount">1</span>
+ </div>
+
+ <div class="cVoteDown">
+ <a data-action="vote-addMinus" data-post-id="101252118">
+ <svg class="voteDownIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
+ </svg>
+ </a>
+ <span itemprop="downvoteCount">0</span>
+ </div>
+
+ <div class="cVoteSpam">
+ <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252118">
+ <svg class="voteSpamIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
+ </svg>
+ </a>
+ </div>
+ <div class="cResWrite">
+ <a data-child-for="101252015" data-reply-to="lizabet">
+ <svg class="resWriteIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
+ </svg>
+ Odpowiedz</a>
+ </div>
+ </div>
+ </div>
+ <!-- / komentarz zagniezdzony -->
+
+
+
+
+
+ </div>
+ <!-- / komentarz -->
+
+
+
+ <!-- komentarz -->
+
+ <div class="cRow cResHidden flag_PUBLISHED" id="opinion101252057" itemscope
+ itemtype="http://schema.org/Comment" itemid="#opinion:101252057">
+ <div class="cHead">
+
+ <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
+
+
+
+ tegonielubie
+
+
+ </span></span>
+
+ <span class="cDate" itemprop="datePublished" content="2019-01-31">31.01.2019, 16:25</span>
+
+
+ </div>
+ <div class="cBody">
+
+
+
+ <span itemprop="text">cena - 65 euro</span></div>
+ <div class="cFt">
+
+ <!-- komentarze_ocenianie ON -->
+ <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
+ <div class="cVoteUp">
+ <a data-action="vote-addPlus" data-post-id="101252057">
+ <svg class="voteUpIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
+ </svg>
+ </a>
+ <span itemprop="upvoteCount">2</span>
+ </div>
+
+ <div class="cVoteDown">
+ <a data-action="vote-addMinus" data-post-id="101252057">
+ <svg class="voteDownIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
+ </svg>
+ </a>
+ <span itemprop="downvoteCount">1</span>
+ </div>
+
+ <div class="cVoteSpam">
+ <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252057">
+ <svg class="voteSpamIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
+ </svg>
+ </a>
+ </div>
+ <div class="cResWrite">
+ <a data-child-for="101252057" data-reply-to="tegonielubie">
+ <svg class="resWriteIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
+ </svg>
+ Odpowiedz</a>
+ </div>
+
+
+ </div>
+
+
+
+
+
+ </div>
+ <!-- / komentarz -->
+
+
+
+ <!-- komentarz -->
+
+ <div class="cRow cResHidden flag_PUBLISHED" id="opinion101251975" itemscope
+ itemtype="http://schema.org/Comment" itemid="#opinion:101251975">
+ <div class="cHead">
+
+ <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
+
+
+
+ baza
+
+
+ </span></span>
+
+ <span class="cDate" itemprop="datePublished" content="2019-01-31">31.01.2019, 16:06</span>
+
+
+ </div>
+ <div class="cBody">
+
+
+
+ <span itemprop="text">Zwyky wentylator nakryj filtrem wglowym i w domu oczyci powietrze.</span></div>
+ <div class="cFt">
+
+ <!-- komentarze_ocenianie ON -->
+ <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
+ <div class="cVoteUp">
+ <a data-action="vote-addPlus" data-post-id="101251975">
+ <svg class="voteUpIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
+ </svg>
+ </a>
+ <span itemprop="upvoteCount">1</span>
+ </div>
+
+ <div class="cVoteDown">
+ <a data-action="vote-addMinus" data-post-id="101251975">
+ <svg class="voteDownIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
+ </svg>
+ </a>
+ <span itemprop="downvoteCount">1</span>
+ </div>
+
+ <div class="cVoteSpam">
+ <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101251975">
+ <svg class="voteSpamIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
+ </svg>
+ </a>
+ </div>
+ <div class="cResWrite">
+ <a data-child-for="101251975" data-reply-to="baza">
+ <svg class="resWriteIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
+ </svg>
+ Odpowiedz</a>
+ </div>
+
+
+ <div class="cResShow">
+ <a data-res-counter="2" data-state="show">
+ <svg class="resShowIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#comment"></use>
+ </svg>
+
+
+
+ Poka odpowiedzi (2)
+
+
+ </a>
+ </div>
+
+ </div>
+
+
+
+ <!-- Odpowiedzi na komentarz -->
+
+ <!-- komentarz zagniezdzony -->
+ <div class="cRow cResComment flag_PUBLISHED" id="opinion101252012"
+ itemscope itemtype="http://schema.org/Comment" itemid="#opinion:101252012">
+ <div class="cHead">
+
+ <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
+
+
+
+ vocativus
+
+ </span></span>
+
+ <span class="cDate" itemprop="datePublished"
+ content="2019-01-31">31.01.2019, 16:15</span>
+
+
+ </div>
+ <div class="cBody">
+
+
+
+ <span itemprop="text">@baza<br/>oczyci nieznacznie z substancji lotnych, ale nie z pyw</span></div>
+ <div class="cFt">
+
+ <!-- komentarze_ocenianie ON -->
+ <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
+ <div class="cVoteUp">
+ <a data-action="vote-addPlus" data-post-id="101252012">
+ <svg class="voteUpIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
+ </svg>
+ </a>
+ <span itemprop="upvoteCount">0</span>
+ </div>
+
+ <div class="cVoteDown">
+ <a data-action="vote-addMinus" data-post-id="101252012">
+ <svg class="voteDownIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
+ </svg>
+ </a>
+ <span itemprop="downvoteCount">0</span>
+ </div>
+
+ <div class="cVoteSpam">
+ <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252012">
+ <svg class="voteSpamIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
+ </svg>
+ </a>
+ </div>
+ <div class="cResWrite">
+ <a data-child-for="101251975" data-reply-to="vocativus">
+ <svg class="resWriteIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
+ </svg>
+ Odpowiedz</a>
+ </div>
+ </div>
+ </div>
+ <!-- / komentarz zagniezdzony -->
+
+ <!-- komentarz zagniezdzony -->
+ <div class="cRow cResComment flag_PUBLISHED" id="opinion101252134"
+ itemscope itemtype="http://schema.org/Comment" itemid="#opinion:101252134">
+ <div class="cHead">
+
+ <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
+
+
+
+ Ardalis
+
+ </span></span>
+
+ <span class="cDate" itemprop="datePublished"
+ content="2019-01-31">31.01.2019, 16:44</span>
+
+
+ </div>
+ <div class="cBody">
+
+
+
+ <span itemprop="text">@baza<br/>Filtr wglowy nie suy do zatrzymywania pyw. Jeeli sam chcesz zrobi filtr smogu, czyli czstek okoo 1 mikrona potrzebujesz wkniny min klasy F9 a lepiej E10-11. Wtedy niestety wzrastaj opory przepywu i bdziesz potrzebowa wentylatora nieco wyszych cinie, np limakowego. Wszystko to oczywicie trzeba uszczelini. Taniej moe by jednak kupi gotowy.</span></div>
+ <div class="cFt">
+
+ <!-- komentarze_ocenianie ON -->
+ <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
+ <div class="cVoteUp">
+ <a data-action="vote-addPlus" data-post-id="101252134">
+ <svg class="voteUpIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
+ </svg>
+ </a>
+ <span itemprop="upvoteCount">1</span>
+ </div>
+
+ <div class="cVoteDown">
+ <a data-action="vote-addMinus" data-post-id="101252134">
+ <svg class="voteDownIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
+ </svg>
+ </a>
+ <span itemprop="downvoteCount">0</span>
+ </div>
+
+ <div class="cVoteSpam">
+ <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252134">
+ <svg class="voteSpamIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
+ </svg>
+ </a>
+ </div>
+ <div class="cResWrite">
+ <a data-child-for="101251975" data-reply-to="Ardalis">
+ <svg class="resWriteIcon">
+ <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
+ </svg>
+ Odpowiedz</a>
+ </div>
+ </div>
+ </div>
+ <!-- / komentarz zagniezdzony -->
+
+
+
+
+
+ </div>
+ <!-- / komentarz -->
+
+
+ </section>
+ <!-- /.oBody -->
+
+
+ <footer class="oFooter">
+ <div class="oFooterContainer">
+ <p class="oFooterParagraph">
+ <a data-action="loadAllComments" title="Zobacz wicej" class="oMoreBtn">Wicej</a>
+ </p>
+ </div>
+ </footer>
+ <!-- /.oFooter -->
+
+ </section>
+ <!-- / KOMENTARZE -->
+
+
+<img id="activity-check" src="/fix/modules/wyborcza/comments/activity.jsp">
+
+<!--9638926, [ /fix/modules/opinions20.jsp ], opinions20PojoBean-->
+
+ </div>
+ </div>
+
+ <section class="complementary-entries">
+ <div class="first-bottom-content-holder column-54" data-position="54,12-15">
+
+
+
+
+ </div>
+ </section>
+ </section>
+
+
+ <aside class="article-extra grid-col-1-4 sidebar">
+ <div class="right-content-holder column-5 splint-glow" data-position="5,1-5 12-15">
+ <div class="mcBan" id="newsletterDM"></div>
+
+ <!--n.cz., tr:#TRNajCzytSST--><article class="mod mod_most_read mod_most_read1 stare-najczesciej-czytane"><header>Najczciej Czytane</header><section class="body"><ul><li class=""><span class="number">1.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/1,147758,14592095,Pokazali_apartamenty_w_Zlotej_44__Cena__65_tys__za.html#TRNajCzytSST" title="Pokazali apartamenty w Zotej 44. Cena: 65 tys. za metr">Pokazali apartamenty w Zotej 44. Cena: 65 tys. za metr</a></h3></li><li class=""><span class="number">2.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/1,147758,12670651,Najwezszy_dom_swiata_prawie_gotowy__ZDJECIA_.html#TRNajCzytSST" title="Najwszy dom wiata prawie gotowy [ZDJCIA]">Najwszy dom wiata prawie gotowy [ZDJCIA]</a></h3></li><li class=""><span class="number">3.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/7,147758,24397604,galopujace-ceny-mieszkan-zderza-sie-ze-sciana-eksperci-podpowiadaja.html#TRNajCzytSST" title="Deweloperzy buduj coraz drosze mieszkania. Klienci kupuj nawet dziury w ziemi">Deweloperzy buduj coraz drosze mieszkania. Klienci kupuj nawet dziury w ziemi</a></h3></li><li class=""><span class="number">4.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/1,147582,18379055,najwieksze-ofiary-amber-gold-kto-stracil-miliony-w-parabanku.html#TRNajCzytSST" title="Najwiksze ofiary Amber Gold. Sprawdzilimy, kto straci miliony w parabanku">Najwiksze ofiary Amber Gold. Sprawdzilimy, kto straci miliony w parabanku</a></h3></li><li class=""><span class="number">5.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/1,147758,17989403,Najlepszy_projekt_wnetrza_biurowca_na_wodzie.html#TRNajCzytSST" title="Najlepszy projekt wntrza biurowca na wodzie">Najlepszy projekt wntrza biurowca na wodzie</a></h3></li><li class=""><span class="number">6.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/7,147743,22836294,ile-kosztuja-wlosy-nawet-8-tys-zl-za-kg-tyle-mozna-zarobic.html#TRNajCzytSST" title="Ile kosztuj wosy? Nawet 8 tys. z za 1 kg. Tyle mona zarobi ">Ile kosztuj wosy? Nawet 8 tys. z za 1 kg. Tyle mona zarobi </a></h3></li></ul></section></article>
+<!--21406650, [ /htm/21406/j21406650.htm ], null-->
+
+
+ <!-- reklama -->
+ <div id="rectangle-splint" class="splint-element">
+
+
+
+<div id="003-RECTANGLE" class="adviewDFPBanner DFP-003-RECTANGLE">
+ <span class="banLabel" style="display: none;">REKLAMA</span>
+ <div id='div-gpt-ad-003-RECTANGLE-0'>
+ <script type='text/javascript'>
+ if(dfpParams.slots['003-RECTANGLE'] && dfpParams.slots['003-RECTANGLE'].autoLoad) {
+ if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('003-RECTANGLE');
+ } else {
+ googletag.cmd.push(function() { googletag.display('div-gpt-ad-003-RECTANGLE-0'); });
+ }
+
+ </script>
+ </div>
+</div>
+
+
+<!-- v2.2 -->
+<!--10185261, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean-->
+
+ </div>
+
+ <section class="mod-generic mod-generic-type-b-no-paddings mod-generic-type-b box-cor mod-opinions top-margin-layout la-http://wyborcza.pl/0,75968.html#TRNavSST">
+
+<ul class="mod-generic-items">
+<li class="mod-generic-item
+mod-generic-item-other-with-photo
+">
+<div class="mod-generic-item-content">
+<figure class="mod-generic-item-photo-wrapper">
+<a id="LinkArea:http://wyborcza.pl/0,75968.html#TRNavSST" href="http://wyborcza.biz/biznes/7,157729,24377175,kolejne-branze-na-celowniku-fiskusa.html" class="mod-generic-item-photo-wrapper" title="Kolejne brane na celowniku fiskusa Budowlanka, gastronomia, motoryzacja &#x2013; objte specjalnym nadzorem fiskalnym">
+<img class="mod-generic-item-photo-wrapper-image" data-src="//bi.im-g.pl/im/e5/3f/17/z24377317J.jpg" alt="Kolejne brane na celowniku fiskusa Budowlanka, gastronomia, motoryzacja &#x2013; objte specjalnym nadzorem fiskalnym" />
+</a>
+</figure>
+<span class="rekl-text">Materiay promocyjne partnera</span>
+<a class="mod-generic-title-container" id="LinkArea:http://wyborcza.pl/0,75968.html#TRNavSST" href="http://wyborcza.biz/biznes/7,157729,24377175,kolejne-branze-na-celowniku-fiskusa.html" title="Kolejne brane na celowniku fiskusa Budowlanka, gastronomia, motoryzacja &#x2013; objte specjalnym nadzorem fiskalnym">
+<span class="mod-generic-title">Kolejne brane na celowniku fiskusa Budowlanka, gastronomia, motoryzacja &#x2013; objte specjalnym nadzorem fiskalnym</span>
+</a>
+</div>
+</li>
+</ul>
+</section><!-- UZREditor --><!-- htmEOF -->
+<!--22243333, [ /htm/22243/j22243333.htm ], null-->
+
+
+ <div id="rectangle-splint-067" class="splint-element-small">
+
+
+
+<div id="067-RECTANGLE-BTF" class="adviewDFPBanner DFP-067-RECTANGLE-BTF">
+ <span class="banLabel" style="display: none;">REKLAMA</span>
+ <div id='div-gpt-ad-067-RECTANGLE-BTF-0'>
+ <script type='text/javascript'>
+ if(dfpParams.slots['067-RECTANGLE-BTF'] && dfpParams.slots['067-RECTANGLE-BTF'].autoLoad) {
+ if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('067-RECTANGLE-BTF');
+ } else {
+ googletag.cmd.push(function() { googletag.display('div-gpt-ad-067-RECTANGLE-BTF-0'); });
+ }
+
+ </script>
+ </div>
+</div>
+
+
+<!-- v2.2 -->
+<!--11455919, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean-->
+
+ </div>
+
+
+
+ <!-- reklama -->
+ <div id="rectangle-splint-035" class="splint-element-small">
+
+
+
+<div id="035-RECTANGLE-BTF" class="adviewDFPBanner DFP-035-RECTANGLE-BTF">
+ <span class="banLabel" style="display: none;">REKLAMA</span>
+ <div id='div-gpt-ad-035-RECTANGLE-BTF-0'>
+ <script type='text/javascript'>
+ if(dfpParams.slots['035-RECTANGLE-BTF'] && dfpParams.slots['035-RECTANGLE-BTF'].autoLoad) {
+ if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('035-RECTANGLE-BTF');
+ } else {
+ googletag.cmd.push(function() { googletag.display('div-gpt-ad-035-RECTANGLE-BTF-0'); });
+ }
+
+ </script>
+ </div>
+</div>
+
+
+<!-- v2.2 -->
+<!--10185263, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean-->
+
+ </div>
+
+
+
+ </div>
+ </aside>
+
+
+
+
+
+ </div>
+ </main>
+
+
+
+
+ <section class="ads container-outer">
+ <div class="container-inner">
+ <div class="grid-row">
+ <div class="grid-col-wide">
+
+
+
+<div id="042-FOOTBOARD" class="adviewDFPBanner DFP-042-FOOTBOARD">
+ <span class="banLabel" style="display: none;">REKLAMA</span>
+ <div id='div-gpt-ad-042-FOOTBOARD-0'>
+ <script type='text/javascript'>
+ if(dfpParams.slots['042-FOOTBOARD'] && dfpParams.slots['042-FOOTBOARD'].autoLoad) {
+ if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('042-FOOTBOARD');
+ } else {
+ googletag.cmd.push(function() { googletag.display('div-gpt-ad-042-FOOTBOARD-0'); });
+ }
+
+ </script>
+ </div>
+</div>
+
+
+<!-- v2.2 -->
+<!--10185264, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean-->
+
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <div class="container-inner">
+
+ <div class="grid-row">
+ <div class="grid-col-3-4">
+ <section class="complementary-entries">
+ <div class="second-bottom-content-holder column-57" data-position="57,1-5 12-15">
+
+
+
+
+
+
+
+
+
+ </div>
+ </section>
+ </div>
+ </div>
+
+
+
+ <div class="grid-row">
+ <div class="grid-col-4-4 column-61" data-position="61,1-15">
+ <div class="karaluch">
+<header>
+<span>Wiadomoci - najwaniejsze informacje</span>
+<i class="l"></i>
+<i class="r"></i>
+</header>
+<section>
+<header>
+<span>Wiadomoci</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.pl/TylkoZdrowie/7,137474,23080156,dash-najzdrowsza-dieta-swiata.html" title="Dash: najzdrowsza dieta">Dash: najzdrowsza dieta</a></li><li><a href="http://biqdata.wyborcza.pl/biqdata/7,159116,23102688,ta-pierwsza-niehandlowa-niedziela-czyli-kiedy-nie-zrobisz-zakupow.html" title="Zakaz handlu w niedziele: kalendarz">Zakaz handlu w niedziele: kalendarz</a></li><li><a href="http://cojestgrane24.wyborcza.pl/cjg24/1,42.html" title="Muzea i galerie">Muzea i galerie</a></li><li><a href="http://wyborcza.pl/7,155287,22875272,teresa-czerwinska-bezpartyjny-fachowiec-kim-jest-nowa-ministra.html" title="Teresa Czerwiska">Teresa Czerwiska</a></li><li><a href="http://wyborcza.pl/7,75398,22876010,deklaracja-wiary-prof-szumowskiego-nowy-minister-zdrowia-wrogiem.html" title="ukasz Szumowski">ukasz Szumowski</a></li><li><a href="http://wyborcza.pl/7,155287,22873972,andrzej-adamczyk-minister-infrastruktury.html" title="Andrzej Adamczyk">Andrzej Adamczyk</a></li><li><a href="http://wyborcza.pl/7,155287,22874245,jadwiga-emilewicz-kim-jest-nowa-ministra-przedsiebiorczosci.html" title="Jadwiga Emilewicz">Jadwiga Emilewicz</a></li><li><a href="http://wyborcza.pl/7,155287,22875049,henryk-kowalczyk-szara-eminencja-pis-odpowiada-teraz-za-lasy.html" title="Henryk Kowalczyk">Henryk Kowalczyk</a></li><li><a href="http://wyborcza.pl/7,155287,22875507,jacek-sasin-nowym-szefem-komitetu-stalego-rady-ministrow-to.html" title="Jacek Sasin">Jacek Sasin</a></li><li><a href="http://wyborcza.pl/7,75410,22875881,paszporty-polityki-2017-tegoroczni-laureaci-jagoda-szelc.html" title="Paszporty Polityki 2017: laureaci">Paszporty Polityki 2017: laureaci</a></li><li><a href="http://bydgoszcz.wyborcza.pl/bydgoszcz/7,48722,22875851,szarza-z-widelcem-zakonczona-kownacki-na-wylocie-z-rzadu.html" title="Bartosz Kownacki">Bartosz Kownacki</a></li><li><a href="http://wyborcza.pl/7,75400,22873943,wajrak-minister-jan-szyszko-przejdzie-do-historii-jako-wyjatkowy.html?disableRedirects=true" title="Jan Szyszko">Jan Szyszko</a></li><li><a href="http://wyborcza.pl/7,156282,22874046,totalne-zaskoczenie-w-resorcie-kazdy-chwycil-za-telefon.html?disableRedirects=true" title="Ministerstwo cyfryzacji: Anna Streyska">Ministerstwo cyfryzacji: Anna Streyska</a></li><li><a href="http://wyborcza.pl/7,155287,22874886,nowy-minister-inwestycji-i-rozwoju-unijny-ekspert-jerzy-kwiecinski.html" title="Jerzy Kwieciski">Jerzy Kwieciski</a></li><li><a href="http://lodz.wyborcza.pl/lodz/7,35136,22875801,cezary-grabarczyk-zrezygnowalem-z-obrony-immunitetem.html" title="Cezary Grabarczyk">Cezary Grabarczyk</a></li><li><a href="http://wyborcza.pl/ksiazki/7,154165,22643551,wybralismy-10-ksiazek-roku-2017.html" title="Ksiki roku 2017">Ksiki roku 2017</a></li>
+</ul>
+</section><section>
+<header>
+<span>Tematy</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.pl/0,128956.html?tag=barbara+dziuk" title="Barbara Dziuk">Barbara Dziuk</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=Marek+P%EAk" title="Marek Pk">Marek Pk</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=Miros%B3aw+Pampuch" title="Mirosaw Pampuch">Mirosaw Pampuch</a></li><li><a href="http://wyborcza.pl/1,75398,19217826,posel-marek-opiola-o-nocnym-odwolaniu-szefow-sluzb-poslowie.html" title="Marek Opioa">Marek Opioa</a></li><li><a href="http://wyborcza.pl/magazyn/7,124059,22222760,czy-czeslaw-milosz-moze-byc-rzeczywiscie-zagrozeniem-dla.html" title="Czesaw Miosz">Czesaw Miosz</a></li><li><a href="http://wyborcza.pl/0,143644.html" title="Wybory samorzdowe 2018">Wybory samorzdowe 2018</a></li><li><a href="http://wyborcza.pl/56,140981,21698092,brigitte-macron.html" title="Brigitte Macron">Brigitte Macron</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=kleszcze" title="Kleszcze">Kleszcze</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=brexit+pytania" title="Brexit: pytania i odpowiedzi">Brexit: pytania i odpowiedzi</a></li><li><a href="http://wyborcza.pl/7,156282,21960784,od-dzisiaj-roaming-w-unii-europejskiej-przestal-obowiazywac.html" title="Roaming">Roaming</a></li><li><a href="http://warszawa.wyborcza.pl/warszawa/7,54420,21888478,rekrutacja-do-liceow-rekord-to-55-chetnych-na-miejsce.html" title="Rekrutacja do licew">Rekrutacja do licew</a></li><li><a href="http://wyborcza.biz/biznes/7,147743,21939448,abonament-rtv-wszystko-co-trzeba-wiedziec-w-kilku-prostych.html" title="Abonament RTV">Abonament RTV</a></li><li><a href="http://wyborcza.pl/7,75398,20946585,marks-spencer-zamyka-wszystkie-sklepy-w-polsce.html" title="Marks &amp; Spencer">Marks &amp; Spencer</a></li><li><a href="http://wyborcza.pl/alehistoria/1,121681,17844725,Ile_milionow_zginelo__Ofiary_II_wojny_swiatowej.html" title="Ofiary II wojny wiatowej">Ofiary II wojny wiatowej</a></li><li><a href="http://wyborcza.pl/1,97654,21220741,uzytkowanie-wieczyste-2017.html" title="Uytkowanie wieczyste 2017">Uytkowanie wieczyste 2017</a></li>
+</ul>
+</section><section>
+<header>
+<span>Informatory</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.pl/1,97654,21282743,zasilki-na-dzieci-w-2017-roku.html" title="Zasiek na dziecko 2017">Zasiek na dziecko 2017</a></li><li><a href="http://wyborcza.pl/7,97654,22392210,koniec-uzytkowania-wieczystego-informator.html" title="Uytkowanie wieczyste">Uytkowanie wieczyste</a></li><li><a href="http://wyborcza.biz/biznes/7,147880,21895075,waloryzacja-emerytur-w-2018-r-juz-wiemy-ile-wyniesie.html" title="Waloryzacja emerytur">Waloryzacja emerytur</a></li><li><a href="http://wyborcza.pl/7,75398,21968688,egzamin-gimnazjalny-2017-centralna-komisja-egzaminacyjna-podala.html" title="Egzamin gimnazjalny 2017: wyniki">Egzamin gimnazjalny 2017: wyniki</a></li><li><a href="http://wyborcza.pl/7,155287,21921609,placa-minimalna-w-2018-r-2080-zl-stawka-za-godzine-13-5.html" title="Paca minimalna 2018">Paca minimalna 2018</a></li><li><a href="http://wyborcza.pl/7,75398,20946585,marks-spencer-zamyka-wszystkie-sklepy-w-polsce.html" title="Marks &amp; Spencer">Marks &amp; Spencer</a></li><li><a href="http://wyborcza.pl/1,75398,20349918,jak-swiadek-koronny-masa-zostal-odtajniony-czy-doszlo-do.html" title="Masa odtajniony">Masa odtajniony</a></li><li><a href="http://wyborcza.pl/1,75399,20330335,egipt-jechac-czy-nie-jechac.html" title="Egipt: czy jecha?">Egipt: czy jecha?</a></li><li><a href="http://wyborcza.pl/1,75398,20046370,litewska-zbrodnia-majora-lupaszki.html" title="upaszka">upaszka</a></li><li><a href="http://wyborcza.pl/alehistoria/1,121681,17844725,Ile_milionow_zginelo__Ofiary_II_wojny_swiatowej.html" title="Ofiary II wojny wiatowej">Ofiary II wojny wiatowej</a></li><li><a href="http://wyborcza.pl/1,97654,21220741,uzytkowanie-wieczyste-2017.html" title="Uytkowanie wieczyste 2017">Uytkowanie wieczyste 2017</a></li><li><a href="http://wyborcza.pl/1,75248,15262278,Szybszy_internet_w_siedmiu_krokach__Co_zrobic__by.html" title="Szybszy internet">Szybszy internet</a></li><li><a href="http://wyborcza.pl/nekrologi/1,101500,18747235,kondolencje-podziekowania-wzory.html" title="Wzory: kondolencje - podzikowania">Wzory: kondolencje - podzikowania</a></li><li><a href="http://wyborcza.pl/1,97654,20849294,zmiany-w-emeryturach-z-krus.html" title="KRUS emerytura">KRUS emerytura</a></li><li><a href="http://wyborcza.pl/1,97654,20345805,komu-sie-nalezy-swiadczenie-przedemerytalne.html" title="wiadczenie przedemerytalne">wiadczenie przedemerytalne</a></li><li><a href="http://wyborcza.pl/1,97654,19802750,wczesniejsza-emerytura-nauczyciela.html" title="Wczeniejsza emerytura nauczyciela">Wczeniejsza emerytura nauczyciela</a></li><li><a href="http://wyborcza.pl/1,97654,19697904,jaki-podatek-od-nieruchomosci.html" title="Jaki podatek od nieruchomoci">Jaki podatek od nieruchomoci</a></li><li><a href="http://wyborcza.pl/1,97654,19533208,komu-sie-nalezy-renta-socjalna.html" title="Renta socjalna">Renta socjalna</a></li><li><a href="http://wyborcza.pl/1,97654,19344883,po-spadek-do-sadu-czy-do-notariusza.html" title="Spadek: sd czy notariusz">Spadek: sd czy notariusz</a></li><li><a href="http://wyborcza.pl/1,97654,19526974,komu-sie-nalezy-renta-rodzinna.html" title="Renta rodzinna">Renta rodzinna</a></li>
+</ul>
+</section><section>
+<header>
+<span>Nauka dla Kadego</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.pl/1,75248,6497648,Kleszcze__czyli_lesne_potwory.html" title="Kleszcze">Kleszcze</a></li><li><a href="http://wyborcza.pl/7,75400,21915146,polacy-rozszyfrowali-tajemnice-stwardnienia-rozsianego-mega-odkrycie.html" title="Stwardnienie rozsiane">Stwardnienie rozsiane</a></li><li><a href="http://wyborcza.pl/1,97654,19501961,przedawnienie-dlugow.html" title="Przedawnienie dugw">Przedawnienie dugw</a></li><li><a href="http://wyborcza.pl/1,97654,19139246,jak-czytac-wyniki-badan.html" title="Jak czyta wyniki bada">Jak czyta wyniki bada</a></li><li><a href="http://wyborcza.pl/1,97654,18726130,komu-sie-nalezy-dodatek-mieszkaniowy.html" title="Dodatek mieszkaniowy">Dodatek mieszkaniowy</a></li><li><a href="http://wyborcza.pl/1,97654,18083478,Jak_odzyskac_prawo_jazdy.html" title="Jak odzyska prawo jazdy">Jak odzyska prawo jazdy</a></li>
+</ul>
+</section><section>
+<header>
+<span>Tylko Zdrowie</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.pl/TylkoZdrowie/7,137474,22522336,ile-sie-zarabia-w-polskim-szpitalu-lista-plac.html" title="Szpital: Lista pac">Szpital: Lista pac</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/7,137474,22330718,rak-jelita-grubego-mozesz-mu-zapobiec-czy-badanie-jelita.html" title="Rak jelita grubego">Rak jelita grubego</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/7,137474,21879629,lody-weganskie-jak-je-zrobic.html" title="Lody wegaskie">Lody wegaskie</a></li><li><a href="http://wyborcza.pl/1,97654,17797883,Wazne_zmiany_w_kapitale_poczatkowym.html" title="Kapita pocztkowy zmiany">Kapita pocztkowy zmiany</a></li><li><a href="http://wyborcza.pl/1,97654,17787551,Emerytura_nauczyciela.html" title="Emerytura nauczyciela">Emerytura nauczyciela</a></li><li><a href="http://wyborcza.pl/1,97654,17414393,Za_ile_lat_emerytura_.html" title="Za ile lat emerytura?">Za ile lat emerytura?</a></li><li><a href="http://wyborcza.pl/1,97654,17264085,Spadek_i_darowizna_bez_placenia.html" title="Spadek i darowizna bez pacenia">Spadek i darowizna bez pacenia</a></li><li><a href="http://wyborcza.pl/1,97654,16322969,Testament_czy_darowizna__jak_przekazac_mieszkanie_.html" title="Testament czy darowizna">Testament czy darowizna</a></li><li><a href="http://wyborcza.pl/1,75398,19097048,235-miejsc-w-sejmie-dla-pis-pkw-podala-oficjalny-podzial-mandatow.html" title="Wyniki wyborw 2015 mandaty">Wyniki wyborw 2015 mandaty</a></li><li><a href="http://wyborcza.pl/7,75400,21726755,nadchodzi-wielki-atak-kleszczy.html" title="Kleszcze">Kleszcze</a></li><li><a href="http://wyborcza.pl/1,76842,7622197,Szumy_w_uszach__czyli_cisza__ktora_dzwoni_w_glowie.html" title="Szum w uszach">Szum w uszach</a></li>
+</ul>
+</section><section>
+<header>
+<span>Duy Format</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.pl/duzyformat/7,127290,22507549,angela-merkel-nas-zdradzila-dlaczego-6-milionow-niemcow.html" title="Angela Merkel">Angela Merkel</a></li><li><a href="http://wyborcza.pl/duzyformat/7,127290,22505059,policja-pod-willa-jaroslawa-kaczynskiego-policjanci-chronia.html" title="Jarosaw Kaczyski">Jarosaw Kaczyski</a></li><li><a href="http://wyborcza.pl/duzyformat/7,127290,21833158,jak-wstapilem-do-obrony-terytorialnej.html" title="Obrona Terytorialna">Obrona Terytorialna</a></li><li><a href="http://wyborcza.pl/1,76842,17920848,Fotelik_w_samochodzie___wazny_wzrost__a_nie_wiek_dziecka.html" title="Fotelik w samochodzie">Fotelik w samochodzie</a></li><li><a href="http://wyborcza.pl/1,97654,20039409,waloryzacja-emerytur-2017.html" title="Waloryzacja emerytur">Waloryzacja emerytur</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/1,137474,17101744,Dlaczego_masz_wzdety_brzuch.html" title="Wzdty brzuch">Wzdty brzuch</a></li>
+</ul>
+</section><section>
+<header>
+<span>Telewizyjna</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.pl/7,90535,20887556,wszystko-co-wiemy-o-3-sezonie-narcos-netflix-oglasza-obsade.html" title="Narcos">Narcos</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/1,137474,17101744,Dlaczego_masz_wzdety_brzuch.html" title="Wzdty brzuch">Wzdty brzuch</a></li>
+</ul>
+</section>
+</div><!-- UZREditor --><!-- htmEOF -->
+<!--17823192, [ /htm/17823/j17823192.htm ], null-->
+
+
+ <div class="karaluch">
+<header>
+<span>Tematy</span>
+<i class="l"></i>
+<i class="r"></i>
+</header>
+<section>
+<header>
+<span>Wiadomoci</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.biz/biznes/7,147768,24265091,wazne-zmiany-jak-w-nowym-roku-rozliczyc-sie-z-urzedem-skarbowym.html" title="PIT 2018">PIT 2018</a></li><li><a href="http://wyborcza.biz/biznes/0,100969.html" title="Depesze agencyjne">Depesze agencyjne</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=500+z%B3+na+dziecko" title="500 z na dziecko">500 z na dziecko</a></li><li><a href="http://wyborcza.pl/0,148081.html" title="Uchodcy">Uchodcy</a></li><li><a href="http://wyborcza.biz/Gieldy/1,114507,19778208,ceny-ropy-ostro-w-gore-w-kwietniu-wielki-naftowy-pakt-rosji.html" title="Cena ropy">Cena ropy</a></li><li><a href="http://wyborcza.pl/0,105742.html" title="Katastrofa smoleska">Katastrofa smoleska</a></li>
+</ul>
+</section><section>
+<header>
+<span>Gieda i Waluty</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.biz/Gieldy/0,114507.html" title="Wiadomoci giedowe">Wiadomoci giedowe</a></li><li><a href="http://wyborcza.biz/Gieldy/0,114514.html" title="Notowania GPW">Notowania GPW</a></li><li><a href="http://wyborcza.biz/Waluty/0,111138,8932151,,,Kursy_srednie_walut_NBP,A.html" title="Kursy walut NBP">Kursy walut NBP</a></li><li><a href="http://wyborcza.biz/Gieldy/0,125867.html" title="Surowce">Surowce</a></li><li><a href="http://wyborcza.biz/Gieldy/0,114544.html" title="Sownik giedowy">Sownik giedowy</a></li>
+</ul>
+</section><section>
+<header>
+<span>Firma</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.biz/biznes/0,147582.html" title="Wiadomoci">Wiadomoci</a></li>
+</ul>
+</section><section>
+<header>
+<span>Przetargi</span>
+</header>
+<ul>
+<li><a href="http://www.komunikaty.pl/komunikaty/0,80849.html" title="Przetargi Wiadomoci">Przetargi Wiadomoci</a></li><li><a href="http://www.komunikaty.pl/komunikaty/0,79968.html" title="Przetargi Vademecum">Przetargi Vademecum</a></li><li><a href="http://www.komunikaty.pl/komunikaty/i/kategoria-Przetargi/1" title="Przetargi ogoszenia">Przetargi ogoszenia</a></li><li><a href="http://www.komunikaty.pl/komunikaty/i/kategoria-Przetargi/przedmiot-roboty+budowlane/1" title="Przetargi budowlane">Przetargi budowlane</a></li><li><a href="http://www.komunikaty.pl/komunikaty/i/kategoria-Nieruchomo%C5%9Bci/procedura-licytacja/1" title="Licytacje nieruchomoci">Licytacje nieruchomoci</a></li><li><a href="http://www.komunikaty.pl/komunikaty/i/kategoria-Ruchomo%C5%9Bci/procedura-licytacja/1" title="Licytacje ruchomoci">Licytacje ruchomoci</a></li>
+</ul>
+</section><section>
+<header>
+<span>Gieda i waluty</span>
+</header>
+<ul>
+<li><a href="http://wyborcza.biz/Gieldy/0,116736,,,,NAME,A.html" title="Katalog spek">Katalog spek</a></li><li><a href="http://wyborcza.biz/Waluty/0,111026.html" title="Stopy procentowe">Stopy procentowe</a></li><li><a href="http://wyborcza.biz/Gieldy/0,114543.html" title="Analiza techniczna">Analiza techniczna</a></li>
+</ul>
+</section>
+</div><!-- UZREditor --><!-- htmEOF -->
+<!--17661432, [ /htm/17661/j17661432.htm ], null-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </div>
+ </div>
+
+ </div>
+
+ <div class="container-outer page-footer">
+ <div class="container-inner">
+ <div class="grid-row column-59" data-position="59,1-15">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<div class="container-outer page-footer">
+<div class="container-inner">
+<div class="grid-row">
+
+ <div id="footer2">
+<section class="foot-summary">
+<header class="foot-summary-head">
+<h2 class="foot-summary-head-title">Wyborcza.pl</h2>
+<a href="http://wyborcza.pl" title="Wyborcza.pl">
+<img src="http://static.im-g.pl/i/obrazki/wyborcza2015/svg/new_wyborcza_pl.svg" alt="Wyborcza.pl">
+</a>
+</header>
+<div class="foot-summary-big">
+<input type="checkbox" id="foot-summary-big-services-show" class="foot-summary-big-services-show">
+<nav class="foot-summary-big-services">
+<div class="foot-summary-big-services-block">
+<p class="foot-summary-big-services-block-name">
+<label for="foot-summary-big-services-block-show-0" title="Wyborcza.pl">Wyborcza.pl</label>
+</p>
+<input type="checkbox" id="foot-summary-big-services-block-show-0" class="foot-summary-big-services-block-show">
+<ul class="foot-summary-big-services-block-list">
+<li><a href="http://wyborcza.pl/0,75398.html" title="Kraj" >Kraj</a></li><li><a href="http://wyborcza.pl/0,75399.html" title="wiat" >wiat</a></li><li><a href="http://wyborcza.pl/0,75968.html" title="Opinie" >Opinie</a></li><li><a href="http://wyborcza.pl/0,155287.html" title="Gospodarka" >Gospodarka</a></li><li><a href="http://wyborcza.pl/0,75400.html" title="Nauka" >Nauka</a></li><li><a href="http://wyborcza.pl/0,156282.html" title="Technologia" >Technologia</a></li><li><a href="http://wyborcza.pl/0,75410.html" title="Kultura" >Kultura</a></li><li><a href="http://wyborcza.pl/0,154903.html" title="Sport" >Sport</a></li><li><a href="http://wyborcza.pl/0,82983.html" title="Wideo" >Wideo</a></li><li><a href="http://wyborcza.pl/0,160910.html" title="The Wall Street Journal" >The Wall Street Journal</a></li><li><a href="http://wyborcza.pl/0,87647.html" title="Witamy w Polsce" >Witamy w Polsce</a></li><li><a href="http://wyborcza.pl/0,160795.html" title="Wyborcza Classic" >Wyborcza Classic</a></li>
+</ul>
+</div><div class="foot-summary-big-services-block">
+<p class="foot-summary-big-services-block-name">
+<label for="foot-summary-big-services-block-show-1" title="Wyborcza.biz">Wyborcza.biz</label>
+</p>
+<input type="checkbox" id="foot-summary-big-services-block-show-1" class="foot-summary-big-services-block-show">
+<ul class="foot-summary-big-services-block-list">
+<li><a href="http://wyborcza.biz/biznes/0,149543.html" title="Aktualnoci" >Aktualnoci</a></li><li><a href="http://wyborcza.biz/Gieldy/0,114514.html" title="Gieda" >Gieda</a></li><li><a href="http://wyborcza.biz/Waluty/0,111138,8932151,,,Kursy_srednie_walut_NBP,A.html" title="Wymiana walut" >Wymiana walut</a></li><li><a href="http://wyborcza.biz/biznes/0,147582.html" title="Zakupy i finanse" >Zakupy i finanse</a></li><li><a href="http://wyborcza.biz/biznes/0,147880.html" title="ZUS i emerytury" >ZUS i emerytury</a></li><li><a href="http://wyborcza.biz/biznes/0,147768.html" title="Podatki" >Podatki</a></li><li><a href="http://wyborcza.biz/biznes/0,159911.html" title="Praca" >Praca</a></li><li><a href="http://wyborcza.biz/biznes/0,156481.html#TRNavSST" title="Motoryzacja i podre" >Motoryzacja i podre</a></li><li><a href="http://wyborcza.biz/biznes/0,147758.html" title="Nieruchomoci" >Nieruchomoci</a></li>
+</ul>
+</div><div class="foot-summary-big-services-block">
+<p class="foot-summary-big-services-block-name">
+<label for="foot-summary-big-services-block-show-2" title="Serwisy lokalne">Serwisy lokalne</label>
+</p>
+<input type="checkbox" id="foot-summary-big-services-block-show-2" class="foot-summary-big-services-block-show">
+<ul class="foot-summary-big-services-block-list">
+<li><a href="http://bialystok.wyborcza.pl/bialystok/0,0.html" title="Biaystok" >Biaystok</a></li><li><a href="http://bielskobiala.wyborcza.pl/bielskobiala/0,0.html" title="Bielsko-Biaa" >Bielsko-Biaa</a></li><li><a href="http://bydgoszcz.wyborcza.pl/bydgoszcz/0,0.html" title="Bydgoszcz" >Bydgoszcz</a></li><li><a href="http://czestochowa.wyborcza.pl/czestochowa/0,0.html" title="Czstochowa" >Czstochowa</a></li><li><a href="http://gliwice.wyborcza.pl/gliwice/0,0.html" title="Gliwice" >Gliwice</a></li><li><a href="http://gorzow.wyborcza.pl/gorzow/0,0.html" title="Gorzw Wlkp." >Gorzw Wlkp.</a></li><li><a href="http://katowice.wyborcza.pl/katowice/0,0.html" title="Katowice" >Katowice</a></li><li><a href="http://kielce.wyborcza.pl/kielce/0,0.html" title="Kielce" >Kielce</a></li><li><a href="http://krakow.wyborcza.pl/krakow/0,0.html" title="Krakw" >Krakw</a></li><li><a href="http://lublin.wyborcza.pl/lublin/0,0.html" title="Lublin" >Lublin</a></li><li><a href="http://lodz.wyborcza.pl/lodz/0,0.html" title="d" >d</a></li><li><a href="http://olsztyn.wyborcza.pl/olsztyn/0,0.html" title="Olsztyn" >Olsztyn</a></li><li><a href="http://opole.wyborcza.pl/opole/0,0.html" title="Opole" >Opole</a></li><li><a href="http://plock.wyborcza.pl/plock/0,0.html" title="Pock" >Pock</a></li><li><a href="http://poznan.wyborcza.pl/poznan/0,0.html" title="Pozna" >Pozna</a></li><li><a href="http://radom.wyborcza.pl/radom/0,0.html" title="Radom" >Radom</a></li><li><a href="http://rzeszow.wyborcza.pl/rzeszow/0,0.html" title="Rzeszw" >Rzeszw</a></li><li><a href="http://sosnowiec.wyborcza.pl/sosnowiec/0,0.html" title="Sosnowiec" >Sosnowiec</a></li><li><a href="http://szczecin.wyborcza.pl/szczecin/0,0.html" title="Szczecin" >Szczecin</a></li><li><a href="http://torun.wyborcza.pl/torun/0,0.html" title="Toru" >Toru</a></li><li><a href="http://trojmiasto.wyborcza.pl/trojmiasto/0,0.html" title="Trjmiasto" >Trjmiasto</a></li><li><a href="http://warszawa.wyborcza.pl/warszawa/0,0.html" title="Warszawa" >Warszawa</a></li><li><a href="http://wroclaw.wyborcza.pl/wroclaw/0,0.html" title="Wrocaw" >Wrocaw</a></li><li><a href="http://zielonagora.wyborcza.pl/zielonagora/0,0.html" title="Zielona Gra" >Zielona Gra</a></li>
+</ul>
+</div><div class="foot-summary-big-services-block">
+<p class="foot-summary-big-services-block-name">
+<label for="foot-summary-big-services-block-show-3" title="Wysokieobcasy.pl">Wysokieobcasy.pl</label>
+</p>
+<input type="checkbox" id="foot-summary-big-services-block-show-3" class="foot-summary-big-services-block-show">
+<ul class="foot-summary-big-services-block-list">
+<li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,114757.html" title="Najnowsze" >Najnowsze</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,100865.html" title="Gosy Kobiet" >Gosy Kobiet</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,53664.html" title="Psychologia" >Psychologia</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,66725.html" title="Wasze listy" >Wasze listy</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,53662.html" title="Portrety Kobiet" >Portrety Kobiet</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,53664.html" title="Psychologia" >Psychologia</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,127763.html" title="Nowy Numer" >Nowy Numer</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,152731.html" title="Wysokie Obcasy Extra" >Wysokie Obcasy Extra</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,100961.html" title="Zdrowie i Uroda" >Zdrowie i Uroda</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,53667.html" title="Jedzenie" >Jedzenie</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,158669.html" title="IT Girls" >IT Girls</a></li>
+</ul>
+</div><div class="foot-summary-big-services-block">
+<p class="foot-summary-big-services-block-name">
+<label for="foot-summary-big-services-block-show-4" title="Magazyny">Magazyny</label>
+</p>
+<input type="checkbox" id="foot-summary-big-services-block-show-4" class="foot-summary-big-services-block-show">
+<ul class="foot-summary-big-services-block-list">
+<li><a href="http://wyborcza.pl/duzyformat/0,0.html#TRNavSST" title="Duy Format" >Duy Format</a></li><li><a href="http://wyborcza.pl/magazyn/0,0.html" title="Magazyn witeczny" >Magazyn witeczny</a></li><li><a href="http://wyborcza.pl/alehistoria/0,0.html" title="Ale Historia" >Ale Historia</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/0,0.html" title="Tylko zdrowie" >Tylko zdrowie</a></li><li><a href="http://wyborcza.pl/0,90535.html" title="Telewizyjna" >Telewizyjna</a></li><li><a href="http://wyborcza.pl/ksiazki/0,0.html" title="Ksiki" >Ksiki</a></li><li><a href="http://wyborcza.pl/osiemdziewiec/0,0.html" title="Osiem Dziewi" >Osiem Dziewi</a></li><li><a href="http://wyborcza.pl/0,79078.html" title="Poradniki" >Poradniki</a></li>
+</ul>
+</div>
+</nav>
+<div class="foot-summary-big-services-more">
+<label for="foot-summary-big-services-show">Wicej</label>
+</div>
+</div><div class="foot-summary-short">
+<ul class="foot-summary-short-links">
+<li><a href="http://biqdata.wyborcza.pl/" title="BIQdata.pl" >BIQdata.pl</a></li><li><a href="http://classic.wyborcza.pl/archiwumGW/0,0.html" title="Archiwum" >Archiwum</a></li><li><a href="http://www.komunikaty.pl" title="Komunikaty.pl" >Komunikaty.pl</a></li><li><a href="http://cojestgrane24.wyborcza.pl/cjg24/0,0.html" title="Cojestgrane24.pl" >Cojestgrane24.pl</a></li><li><a href="http://nekrologi.wyborcza.pl/0,0.html" title="Nekrologi" >Nekrologi</a></li>
+</ul><div class="foot-summary-short-partners">
+<p class="foot-summary-short-partners-labels">Serwisy partnerskie</p>
+<ul class="foot-summary-short-partners-list">
+<li><a href="http://gazeta.pl" title="Gazeta.pl" >Gazeta.pl</a></li><li><a href="http://www.tokfm.pl/Tokfm/0,0.html" title="TOK.fm" >TOK.fm</a></li><li><a href="http://sport.pl" title="Sport.pl" >Sport.pl</a></li><li><a href="http://publio.pl" title="Publio.pl" >Publio.pl</a></li><li><a href="http://kulturalnysklep.pl" title="Kulturalnysklep.pl" >Kulturalnysklep.pl</a></li>
+</ul>
+</div>
+<a class="foot-summary-short-contact-link" href="http://wyborcza.pl/centrumpomocygw/0,134959.html" title="Napisz do redakcji">
+<figure>
+<svg>
+<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#letter"></use>
+</svg>
+</figure>
+Napisz do redakcji
+</a>
+<div class="foot-summary-short-contact">
+<a class="foot-summary-short-contact-mail" href="mailto:redakcja@wyborcza.pl" title="Napisz do redakcji">
+<figure>
+<svg>
+<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#letter"></use>
+</svg>
+</figure>
+Napisz do redakcji
+</a>
+</div>
+</div><div class="foot-summary-buttons">
+<div class="foot-summary-buttons-subscription">
+<a class="foot-summary-buttons-subscription-link" title="Kup prenumerat" href="http://prenumerata.wyborcza.pl">Kup prenumerat</a>
+</div>
+<div class="foot-summary-buttons-stores"><a
+href="https://itunes.apple.com/pl/app/gazeta-wyborcza/id530078918"
+title="Aplikacja wyborcza.pl"
+><svg>
+<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#app-store"></use>
+</svg></a><a
+href="https://play.google.com/store/apps/details?id=pl.wyborcza.android.google&amp;hl=pl"
+title="Aplikacja wyborcza.pl"
+><svg>
+<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#google-play"></use>
+</svg></a></div><!-- UZREditor --><!-- htmEOF -->
+
+
+
+
+ <ul class="foot-summary-buttons-share">
+<li class="foot-summary-buttons-share-face">
+<a href="https://www.facebook.com/wyborczabiznes" >
+<figure>
+<svg>
+<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#face"></use>
+</svg>
+</figure>
+</a>
+</li><li class="foot-summary-buttons-share-tweet">
+<a href="https://twitter.com/wyborcza_biz" >
+<figure>
+<svg>
+<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#tweet"></use>
+</svg>
+</figure>
+</a>
+</li>
+<li class="foot-summary-buttons-share-newsletter" title="newsletter">
+<a href="http://wyborcza.pl/0,157210.html?fromnlt=stopka&amp;refnlt=http://wyborcza.biz/">Newsletter</a>
+</li>
+</ul><!-- UZREditor --><!-- htmEOF -->
+ <!-- test stopki modul jest-->
+
+
+
+
+
+ </div>
+ <footer class="foot-menu">
+ <nav class="foot-menu-links">
+ <p class="foot-menu-links-copyright"><a href="http://www.agora.pl/" title="Agora SA">Copyright &copy; Agora SA</a></p>
+ <ul>
+
+ <li><a href="http://wyborcza.pl/centrumpomocygw/0,134955.html" title="O nas" >O nas</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/7,134956,23413995,polityka-prywatnosci.html" title="Prywatno" >Prywatno</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/1,134957,8856780,Ogolne_zasady_udzielania_licencji_do_Materialow_Redakcyjnych.html" title="Licencje/Kontent" >Licencje/Kontent</a></li><li><a href="http://wyborcza.pl/reklamaGW/0,156164.html" title="Reklama w Internecie" >Reklama w Internecie</a></li><li><a href="http://wyborcza.pl/reklamaGW/0,104163.html" title="Reklama w papierze" >Reklama w papierze</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/0,134959.html" title="Kontakt" >Kontakt</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/0,134958.html" title="Zgo bd" rel="nofollow">Zgo bd</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/0,134958.html" title="Pomoc" >Pomoc</a></li><!-- UZREditor --><!-- htmEOF -->
+
+ <!-- 21755068 -->
+ <li><a href="http://wyborcza.biz/biznes/3660000,0.html" title="Wszystkie artykuy">Wszystkie artykuy</a></li>
+ </ul>
+ </nav>
+ </footer>
+</div>
+</div>
+</div>
+</div>
+
+<!-- footerModule v1.0 -->
+<!--22442678, [ /fix/modules/wyborcza/portal/footerModule.jsp ], wyborczaFooterModule-->
+
+ </div>
+ </div>
+ </div>
+
+
+
+
+
+
+
+
+
+<!--13289483, [ /fix/modules/wyborcza/rodoAgreement.jsp ], rodoAgreementModule-->
+
+
+
+
+
+
+ <script type='text/javascript'>
+ var _sf_async_config = _sf_async_config || {};
+
+ _sf_async_config.sections = '/biznes, kraj';
+ _sf_async_config.authors = 'Micha Frk';
+ var _cbq = window._cbq = (window._cbq || []);
+ _cbq.push(['_acct', 'anon']);
+
+ (function() {
+ function loadChartbeat() {
+ window._sf_endpt = (new Date()).getTime();
+ var e = document.createElement('script');
+ e.setAttribute('language', 'javascript');
+ e.setAttribute('type', 'text/javascript');
+ e.setAttribute('src', '//static.chartbeat.com/js/chartbeat_video.js');
+ document.body.appendChild(e);
+ }
+ var oldonload = window.onload;
+ window.onload = (typeof window.onload != 'function') ? loadChartbeat
+ : function() {
+ oldonload();
+ loadChartbeat();
+ };
+ })();
+ </script>
+
+
+
+<!-- chartbeatModule v1.3 -->
+
+
+
+
+
+
+<div style="display:none;">
+<img height="1" width="1" style="border-style:none;" alt="" src="http://www.googleadservices.com/pagead/conversion/1039774788/?label=avQqCOa77QEQxOjm7wM&amp;guid=ON&amp;script=0"/>
+</div>
+
+
+<!-- remarketingModule -->
+
+
+
+
+
+
+ <!-- pre-footer script -->
+ <script>!function(){function a(){k(),b(),l()}function b(){var a=document.querySelectorAll("img["+p+"]:not(."+m+"):not(."+n+")");[].forEach.call(a,function(a){c(a)})}function c(a){var b,c,f=e(a);i(a)&&f&&!h(a,f)&&(a.addClass(n),c=document.createElement("div"),c.addClass(o),j(c,a),b=new Image,b.src=f,b.onload=function(e){d(a,b,c)})}function d(a,b,c){a.setAttribute("src",b.src),a.addClass(m),a.removeClass(n),a.removeAttribute(p),a.removeAttribute(q),c.remove()}function e(a){var b=a.getAttribute(p)||!1,c=a.getAttribute(q)||!1;return f()&&!g()&&c&&(b=c),b}function f(){var a=!1;return"undefined"!=typeof gazeta_pl&&void 0!==gazeta_pl.mobileInfo&&gazeta_pl.mobileInfo.hasOwnProperty("isMobileDevice")&&gazeta_pl.mobileInfo.isMobileDevice&&(a=!0),a}function g(){var a=!1;return"undefined"!=typeof gazeta_pl&&void 0!==gazeta_pl.tabletInfo&&gazeta_pl.tabletInfo.hasOwnProperty("isTabletDevice")&&!0===gazeta_pl.tabletInfo.isTabletDevice&&(a=!0),a}function h(a,b){return a.src===b&&(a.complete&&0!==a.naturalHeight)}function i(a){var b=a.getBoundingClientRect(),c=document.documentElement.scrollTop,d=b.top+c,e=a.clientHeight,f=window.innerHeight,g=c-f,h=c+f+f,i=d+e;return d>g&&i<h}function j(a,b){b.parentNode.insertBefore(a,b.nextSibling)}function k(){"remove"in Element.prototype||(Element.prototype.remove=function(){this.parentNode&&this.parentNode.removeChild(this)}),Element.prototype.addClass=function(a){this.classList.add(a)},Element.prototype.removeClass=function(a){this.classList.remove(a)}}function l(){["scroll","resize"].map(function(a){window.addEventListener(a,b)})}var m="loaded",n="processing",o="preloaderContainer",p="data-src",q="data-src-mobile";a()}();</script>
+ <!-- /pre-footer script -->
+
+
+
+
+
+
+ <!-- wyborcza_common_skrypt not set -->
+
+
+ <script type="text/javascript" src="//static.im-g.pl/info/wyborcza-common/builds/17.4.108/main-min.jsgz"></script>
+
+
+
+
+
+
+
+
+ <!-- desktop script -->
+ <script type="text/javascript" src="//static.im-g.pl/wyborcza/wyborcza-biz/js/1.0.4/main-min.jsgz"></script>
+ <!-- /desktop script -->
+
+
+
+
+
+
+
+ <!-- footer script -->
+ <script></script>
+ <!-- /footer script -->
+
+
+
+<!-- scriptsModule v1.1 -->
+
+
+
+ <img src="https://pubads.g.doubleclick.net/activity;dc_iu=/75224259/DFPAudiencePixel;ord=1;dc_seg=692406053?" width=1 height=1 border=0/>
+
+<!-- singleViewModule -->
+
+
+
+
+
+
+
+<!-- deutscheWelleModule -->
+
+
+
+
+
+
+
+<!-- gemiusEfekt -->
+
+
+
+
+
+
+
+<!-- plistaModule-->
+
+
+
+<!--10185200, [ null ], aggregatorModule-->
+
+
+
+
+<div>
+ <script id="adblock-modal" type="text/template">
+<div class="noadinfo">
+<div class="noadinfo-modal">
+<img src="https://bis.gazeta.pl/im/8/22327/m22327058.png" alt="" class="noadinfo-modal-image noadinfo-modal-image-desktop"/>
+<img src="https://bis.gazeta.pl/im/7/22327/m22327057.png" alt="" class="noadinfo-modal-image noadinfo-modal-image-mobile"/>
+<div class="noadinfo-modal-subscription">Jeeli jednak nie chcesz wycza adblocka, <a href="http://wyborcza.pl/prenumerataadb">kup prenumerat.</a> Zdecydowao si na to ju 100 tysicy internautw.</div>
+<div class="noadinfo-modal-instruction">
+<div class="noadinfo-modal-show noadinfo-chrome">
+<label class="noadinfo-modal-instruction-tab noadinfo-modal-instruction-tab-active noadinfo-modal-instruction-tab-ab" for="option1">Mam Adblock</label>
+<label class="noadinfo-modal-instruction-tab noadinfo-modal-instruction-tab-ap" for="option2">Mam Adblock Plus</label>
+<div class="noadinfo-modal-instruction-holder">
+<input class="noadinfo-modal-input" type="radio" name="input-chrome" id="option1" checked />
+<div class="noadinfo-modal-detail">
+<p><b>Krok 1.</b> Kliknij w ikonk Adblocka obok pola do adresu oraz wybierz opcj: &#8222;Wstrzymaj blokowanie na stronach w tej domenie&#8221;.</p>
+<img src="http://bi.gazeta.pl/im/4/22139/m22139974,CHROME-AB1.png" title="Instrukcja">
+<p><b>Krok 2.</b> Kliknij &#8222;Wyklucz&#8221;.</p>
+<img src="http://bi.gazeta.pl/im/4/22156/m22156554,CHROME-AB2.png" title="Instrukcja">
+<p>Strona sama si odwiey i dostaniesz darmowe artykuy.</p>
+</div>
+<input class="noadinfo-modal-input" type="radio" name="input-chrome" id="option2" />
+<div class="noadinfo-modal-detail">
+<p><b>Krok 1.</b> Kliknij w ikonk Adblocka Plus obok pola do adresu oraz kliknij &#8222;Wczony na tej stronie&#8221;.</p>
+<img src="http://bi.gazeta.pl/im/1/22139/m22139971,CHROME-AP1.png" title="Instrukcja">
+<p><b>Krok 2.</b> <a href="#" class="noadinfo-modal-instruction-reload" title="Odwie stron">Odwie stron</a>, aby dosta darmowe artykuy!</p>
+</div>
+</div>
+</div>
+<div class="noadinfo-modal-show noadinfo-firefox">
+<label class="noadinfo-modal-instruction-tab noadinfo-modal-instruction-tab-active noadinfo-modal-instruction-tab-ap" for="option3">Mam Adblock</label>
+<label class="noadinfo-modal-instruction-tab noadinfo-modal-instruction-tab-ub" for="option4">Mam Ublock Origin</label>
+<div class="noadinfo-modal-instruction-holder">
+<input class="noadinfo-modal-input" type="radio" name="input-firefox" id="option3" checked />
+<div class="noadinfo-modal-detail">
+<p><b>Krok 1.</b> Kliknij w ikonk Adblocka obok pola do adresu oraz kliknij: "Wycz blokowanie tylko na tej stronie".</p>
+<img src="http://bi.gazeta.pl/im/2/22139/m22139972,FIREFOX-AP1.png" title="Instrukcja">
+<p><b>Krok 2.</b> <a href="#" class="noadinfo-modal-instruction-reload" title="Odwie stron">Odwie stron</a>, aby dosta darmowe artykuy!</p>
+</div>
+<input class="noadinfo-modal-input" type="radio" name="input-firefox" id="option4" />
+<div class="noadinfo-modal-detail">
+<p><b>Krok 1.</b> Kliknij w ikonk uBlock Origin obok pola do adresu oraz kliknij du niebiesk ikonk.</p>
+<img src="http://bi.gazeta.pl/im/3/22139/m22139973,FIREFOX-UB1.png" title="Instrukcja">
+<p><b>Krok 2.</b> <a href="#" class="noadinfo-modal-instruction-reload" title="Odwie stron">Odwie stron</a>, aby dosta darmowe artykuy!</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</script><!-- UZREditor --><!--22060362,aliasOf--><!-- htmEOF -->
+</div>
+
+<!-- adBlockModule -->
+
+<!--12059452, [ /fix/modules/wyborcza/portal/adBlockModule.jsp ], emptyBean-->
+
+
+
+ <script type="text/javascript" src="https://bis.gazeta.pl/info/mapa2.js"></script>
+ <!-- gemiusHeatMap -->
+
+ <div class="mcBan" id="mcBan_4"></div>
+ <div class="mcBan" id="mcBan_6"></div>
+
+
+ <div class="mcBan" id="glider1"></div>
+ <div class="mcBan" id="crawler1"></div>
+
+
+
+
+
+
+
+<!-- iSlayModule -->
+
+<!--14021450, [ /fix/modules/wyborcza/portal/iSlayModule.jsp ], iSlayModule-->
+
+</body>
+</html>
diff --git a/test/fixtures/rich_media/oembed.html b/test/fixtures/rich_media/oembed.html
new file mode 100644
index 000000000..55f17004b
--- /dev/null
+++ b/test/fixtures/rich_media/oembed.html
@@ -0,0 +1,3 @@
+<link rel="alternate" type="application/json+oembed"
+ href="http://example.com/oembed.json"
+ title="Bacon Lollys oEmbed Profile" />
diff --git a/test/fixtures/rich_media/oembed.json b/test/fixtures/rich_media/oembed.json
new file mode 100644
index 000000000..2a5f7a771
--- /dev/null
+++ b/test/fixtures/rich_media/oembed.json
@@ -0,0 +1 @@
+{"type":"photo","flickr_type":"photo","title":"Bacon Lollys","author_name":"\u202e\u202d\u202cbees\u202c","author_url":"https:\/\/www.flickr.com\/photos\/bees\/","width":"1024","height":"768","url":"https:\/\/farm4.staticflickr.com\/3040\/2362225867_4a87ab8baf_b.jpg","web_page":"https:\/\/www.flickr.com\/photos\/bees\/2362225867\/","thumbnail_url":"https:\/\/farm4.staticflickr.com\/3040\/2362225867_4a87ab8baf_q.jpg","thumbnail_width":150,"thumbnail_height":150,"web_page_short_url":"https:\/\/flic.kr\/p\/4AK2sc","license":"All Rights Reserved","license_id":0,"html":"<a data-flickr-embed=\"true\" href=\"https:\/\/www.flickr.com\/photos\/bees\/2362225867\/\" title=\"Bacon Lollys by \u202e\u202d\u202cbees\u202c, on Flickr\"><img src=\"https:\/\/farm4.staticflickr.com\/3040\/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"><\/a><script async src=\"https:\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script>","version":"1.0","cache_age":3600,"provider_name":"Flickr","provider_url":"https:\/\/www.flickr.com\/"}
diff --git a/test/fixtures/rich_media/ogp.html b/test/fixtures/rich_media/ogp.html
new file mode 100644
index 000000000..c886b5871
--- /dev/null
+++ b/test/fixtures/rich_media/ogp.html
@@ -0,0 +1,9 @@
+<html prefix="og: http://ogp.me/ns#">
+ <head>
+ <title>The Rock (1996)</title>
+ <meta property="og:title" content="The Rock" />
+ <meta property="og:type" content="video.movie" />
+ <meta property="og:url" content="http://www.imdb.com/title/tt0117500/" />
+ <meta property="og:image" content="http://ia.media-imdb.com/images/rock.jpg" />
+ </head>
+</html>
diff --git a/test/fixtures/rich_media/twitter_card.html b/test/fixtures/rich_media/twitter_card.html
new file mode 100644
index 000000000..34c7c6ccd
--- /dev/null
+++ b/test/fixtures/rich_media/twitter_card.html
@@ -0,0 +1,5 @@
+<meta name="twitter:card" content="summary" />
+<meta name="twitter:site" content="@flickr" />
+<meta name="twitter:title" content="Small Island Developing States Photo Submission" />
+<meta name="twitter:description" content="View the album on Flickr." />
+<meta name="twitter:image" content="https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg" />
diff --git a/test/flake_id_test.exs b/test/flake_id_test.exs
new file mode 100644
index 000000000..ca2338041
--- /dev/null
+++ b/test/flake_id_test.exs
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.FlakeIdTest do
+ use Pleroma.DataCase
+ import Kernel, except: [to_string: 1]
+ import Pleroma.FlakeId
+
+ describe "fake flakes (compatibility with older serial integers)" do
+ test "from_string/1" do
+ fake_flake = <<0::integer-size(64), 42::integer-size(64)>>
+ assert from_string("42") == fake_flake
+ assert from_string(42) == fake_flake
+ end
+
+ test "zero or -1 is a null flake" do
+ fake_flake = <<0::integer-size(128)>>
+ assert from_string("0") == fake_flake
+ assert from_string("-1") == fake_flake
+ end
+
+ test "to_string/1" do
+ fake_flake = <<0::integer-size(64), 42::integer-size(64)>>
+ assert to_string(fake_flake) == "42"
+ end
+ end
+
+ test "ecto type behaviour" do
+ flake = <<0, 0, 1, 104, 80, 229, 2, 235, 140, 22, 69, 201, 53, 210, 0, 0>>
+ flake_s = "9eoozpwTul5mjSEDRI"
+
+ assert cast(flake) == {:ok, flake_s}
+ assert cast(flake_s) == {:ok, flake_s}
+
+ assert load(flake) == {:ok, flake_s}
+ assert load(flake_s) == {:ok, flake_s}
+
+ assert dump(flake_s) == {:ok, flake}
+ assert dump(flake) == {:ok, flake}
+ end
+end
diff --git a/test/formatter_test.exs b/test/formatter_test.exs
index e4da84c10..e74985c4e 100644
--- a/test/formatter_test.exs
+++ b/test/formatter_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.FormatterTest do
alias Pleroma.Formatter
alias Pleroma.User
@@ -5,17 +9,28 @@ defmodule Pleroma.FormatterTest do
import Pleroma.Factory
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
describe ".add_hashtag_links" do
test "turns hashtags into links" do
text = "I love #cofe and #2hu"
expected_text =
- "I love <a href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>"
+ "I love <a class='hashtag' data-tag='cofe' href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>"
- tags = Formatter.parse_tags(text)
+ assert {^expected_text, [], _tags} = Formatter.linkify(text)
+ end
- assert expected_text ==
- Formatter.add_hashtag_links({[], text}, tags) |> Formatter.finalize()
+ test "does not turn html characters to tags" do
+ text = "#fact_3: pleroma does what mastodon't"
+
+ expected_text =
+ "<a class='hashtag' data-tag='fact_3' href='http://localhost:4001/tag/fact_3' rel='tag'>#fact_3</a>: pleroma does what mastodon't"
+
+ assert {^expected_text, [], _tags} = Formatter.linkify(text)
end
end
@@ -26,110 +41,109 @@ defmodule Pleroma.FormatterTest do
expected =
"Hey, check out <a href=\"https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla\">https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a> ."
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "https://mastodon.social/@lambadalambda"
expected =
"<a href=\"https://mastodon.social/@lambadalambda\">https://mastodon.social/@lambadalambda</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "https://mastodon.social:4000/@lambadalambda"
expected =
"<a href=\"https://mastodon.social:4000/@lambadalambda\">https://mastodon.social:4000/@lambadalambda</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "@lambadalambda"
expected = "@lambadalambda"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "http://www.cs.vu.nl/~ast/intel/"
expected = "<a href=\"http://www.cs.vu.nl/~ast/intel/\">http://www.cs.vu.nl/~ast/intel/</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
expected =
"<a href=\"https://forum.zdoom.org/viewtopic.php?f=44&t=57087\">https://forum.zdoom.org/viewtopic.php?f=44&t=57087</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
expected =
"<a href=\"https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul\">https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "https://www.google.co.jp/search?q=Nasim+Aghdam"
expected =
"<a href=\"https://www.google.co.jp/search?q=Nasim+Aghdam\">https://www.google.co.jp/search?q=Nasim+Aghdam</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "https://en.wikipedia.org/wiki/Duff's_device"
expected =
"<a href=\"https://en.wikipedia.org/wiki/Duff's_device\">https://en.wikipedia.org/wiki/Duff's_device</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "https://pleroma.com https://pleroma.com/sucks"
expected =
"<a href=\"https://pleroma.com\">https://pleroma.com</a> <a href=\"https://pleroma.com/sucks\">https://pleroma.com/sucks</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text = "xmpp:contact@hacktivis.me"
expected = "<a href=\"xmpp:contact@hacktivis.me\">xmpp:contact@hacktivis.me</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
text =
"magnet:?xt=urn:btih:7ec9d298e91d6e4394d1379caf073c77ff3e3136&tr=udp%3A%2F%2Fopentor.org%3A2710&tr=udp%3A%2F%2Ftracker.blackunicorn.xyz%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com"
expected = "<a href=\"#{text}\">#{text}</a>"
- assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
+ assert {^expected, [], []} = Formatter.linkify(text)
end
end
describe "add_user_links" do
- test "gives a replacement for user links" do
- text = "@gsimg According to @archaeme, that is @daggsy. Also hello @archaeme@archae.me"
+ test "gives a replacement for user links, using local nicknames in user links text" do
+ text = "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme@archae.me"
gsimg = insert(:user, %{nickname: "gsimg"})
archaeme =
insert(:user, %{
- nickname: "archaeme",
- info: %Pleroma.User.Info{source_data: %{"url" => "https://archeme/@archaeme"}}
+ nickname: "archa_eme_",
+ info: %Pleroma.User.Info{source_data: %{"url" => "https://archeme/@archa_eme_"}}
})
archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})
- mentions = Pleroma.Formatter.parse_mentions(text)
-
- {subs, text} = Formatter.add_user_links({[], text}, mentions)
+ {text, mentions, []} = Formatter.linkify(text)
- assert length(subs) == 3
- Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
+ assert length(mentions) == 3
expected_text =
- "<span><a class='mention' href='#{gsimg.ap_id}'>@<span>gsimg</span></a></span> According to <span><a class='mention' href='#{
- "https://archeme/@archaeme"
- }'>@<span>archaeme</span></a></span>, that is @daggsy. Also hello <span><a class='mention' href='#{
- archaeme_remote.ap_id
- }'>@<span>archaeme</span></a></span>"
-
- assert expected_text == Formatter.finalize({subs, text})
+ "<span class='h-card'><a data-user='#{gsimg.id}' class='u-url mention' href='#{
+ gsimg.ap_id
+ }'>@<span>gsimg</span></a></span> According to <span class='h-card'><a data-user='#{
+ archaeme.id
+ }' class='u-url mention' href='#{"https://archeme/@archa_eme_"}'>@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class='h-card'><a data-user='#{
+ archaeme_remote.id
+ }' class='u-url mention' href='#{archaeme_remote.ap_id}'>@<span>archaeme</span></a></span>"
+
+ assert expected_text == text
end
test "gives a replacement for user links when the user is using Osada" do
@@ -137,46 +151,60 @@ defmodule Pleroma.FormatterTest do
text = "@mike@osada.macgirvin.com test"
- mentions = Formatter.parse_mentions(text)
+ {text, mentions, []} = Formatter.linkify(text)
- {subs, text} = Formatter.add_user_links({[], text}, mentions)
-
- assert length(subs) == 1
- Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
+ assert length(mentions) == 1
expected_text =
- "<span><a class='mention' href='#{mike.ap_id}'>@<span>mike</span></a></span> test"
+ "<span class='h-card'><a data-user='#{mike.id}' class='u-url mention' href='#{mike.ap_id}'>@<span>mike</span></a></span> test"
- assert expected_text == Formatter.finalize({subs, text})
+ assert expected_text == text
end
test "gives a replacement for single-character local nicknames" do
text = "@o hi"
o = insert(:user, %{nickname: "o"})
- mentions = Formatter.parse_mentions(text)
+ {text, mentions, []} = Formatter.linkify(text)
- {subs, text} = Formatter.add_user_links({[], text}, mentions)
+ assert length(mentions) == 1
- assert length(subs) == 1
- Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
+ expected_text =
+ "<span class='h-card'><a data-user='#{o.id}' class='u-url mention' href='#{o.ap_id}'>@<span>o</span></a></span> hi"
- expected_text = "<span><a class='mention' href='#{o.ap_id}'>@<span>o</span></a></span> hi"
- assert expected_text == Formatter.finalize({subs, text})
+ assert expected_text == text
end
test "does not give a replacement for single-character local nicknames who don't exist" do
text = "@a hi"
- mentions = Formatter.parse_mentions(text)
+ expected_text = "@a hi"
+ assert {^expected_text, [] = _mentions, [] = _tags} = Formatter.linkify(text)
+ end
- {subs, text} = Formatter.add_user_links({[], text}, mentions)
+ test "given the 'safe_mention' option, it will only mention people in the beginning" do
+ user = insert(:user)
+ _other_user = insert(:user)
+ third_user = insert(:user)
+ text = " @#{user.nickname} hey dude i hate @#{third_user.nickname}"
+ {expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true)
- assert length(subs) == 0
- Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
+ assert mentions == [{"@#{user.nickname}", user}]
- expected_text = "@a hi"
- assert expected_text == Formatter.finalize({subs, text})
+ assert expected_text ==
+ "<span class='h-card'><a data-user='#{user.id}' class='u-url mention' href='#{
+ user.ap_id
+ }'>@<span>#{user.nickname}</span></a></span> hey dude i hate <span class='h-card'><a data-user='#{
+ third_user.id
+ }' class='u-url mention' href='#{third_user.ap_id}'>@<span>#{third_user.nickname}</span></a></span>"
+ end
+
+ test "given the 'safe_mention' option, it will still work without any mention" do
+ text = "A post without any mention"
+ {expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true)
+
+ assert mentions == []
+ assert expected_text == text
end
end
@@ -184,31 +212,36 @@ defmodule Pleroma.FormatterTest do
test "parses tags in the text" do
text = "Here's a #Test. Maybe these are #working or not. What about #漢字? And #は。"
- expected = [
+ expected_tags = [
{"#Test", "test"},
{"#working", "working"},
- {"#漢字", "漢字"},
- {"#は", "は"}
+ {"#は", "は"},
+ {"#漢字", "漢字"}
]
- assert Formatter.parse_tags(text) == expected
+ assert {_text, [], ^expected_tags} = Formatter.linkify(text)
end
end
test "it can parse mentions and return the relevant users" do
- text = "@gsimg According to @archaeme, that is @daggsy. Also hello @archaeme@archae.me"
+ text =
+ "@@gsimg According to @archaeme, that is @daggsy. Also hello @archaeme@archae.me and @o and @@@jimm"
+ o = insert(:user, %{nickname: "o"})
+ jimm = insert(:user, %{nickname: "jimm"})
gsimg = insert(:user, %{nickname: "gsimg"})
archaeme = insert(:user, %{nickname: "archaeme"})
archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})
- expected_result = [
- {"@gsimg", gsimg},
+ expected_mentions = [
{"@archaeme", archaeme},
- {"@archaeme@archae.me", archaeme_remote}
+ {"@archaeme@archae.me", archaeme_remote},
+ {"@gsimg", gsimg},
+ {"@jimm", jimm},
+ {"@o", o}
]
- assert Formatter.parse_mentions(text) == expected_result
+ assert {_text, ^expected_mentions, []} = Formatter.linkify(text)
end
test "it adds cool emoji" do
@@ -238,7 +271,9 @@ defmodule Pleroma.FormatterTest do
test "it returns the emoji used in the text" do
text = "I love :moominmamma:"
- assert Formatter.get_emoji(text) == [{"moominmamma", "/finmoji/128px/moominmamma-128.png"}]
+ assert Formatter.get_emoji(text) == [
+ {"moominmamma", "/finmoji/128px/moominmamma-128.png", "Finmoji"}
+ ]
end
test "it returns a nice empty result when no emojis are present" do
@@ -250,4 +285,11 @@ defmodule Pleroma.FormatterTest do
text = nil
assert Formatter.get_emoji(text) == []
end
+
+ test "it escapes HTML in plain text" do
+ text = "hello & world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1"
+ expected = "hello &amp; world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1"
+
+ assert Formatter.html_escape(text, "text/plain") == expected
+ end
end
diff --git a/test/html_test.exs b/test/html_test.exs
index f7150759b..0b5d3d892 100644
--- a/test/html_test.exs
+++ b/test/html_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.HTMLTest do
alias Pleroma.HTML
use Pleroma.DataCase
@@ -6,6 +10,8 @@ defmodule Pleroma.HTMLTest do
<b>this is in bold</b>
<p>this is a paragraph</p>
this is a linebreak<br />
+ this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a>
+ this is a link with not allowed "rel" attribute: <a href="http://example.com/" rel="tag noallowed">example.com</a>
this is an image: <img src="http://example.com/image.jpg"><br />
<script>alert('hacked')</script>
"""
@@ -20,6 +26,8 @@ defmodule Pleroma.HTMLTest do
this is in bold
this is a paragraph
this is a linebreak
+ this is a link with allowed "rel" attribute: example.com
+ this is a link with not allowed "rel" attribute: example.com
this is an image:
alert('hacked')
"""
@@ -40,6 +48,8 @@ defmodule Pleroma.HTMLTest do
this is in bold
<p>this is a paragraph</p>
this is a linebreak<br />
+ this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a>
+ this is a link with not allowed "rel" attribute: <a href="http://example.com/">example.com</a>
this is an image: <img src="http://example.com/image.jpg" /><br />
alert('hacked')
"""
@@ -62,6 +72,8 @@ defmodule Pleroma.HTMLTest do
<b>this is in bold</b>
<p>this is a paragraph</p>
this is a linebreak<br />
+ this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a>
+ this is a link with not allowed "rel" attribute: <a href="http://example.com/">example.com</a>
this is an image: <img src="http://example.com/image.jpg" /><br />
alert('hacked')
"""
diff --git a/test/http_test.exs b/test/http_test.exs
new file mode 100644
index 000000000..5f9522cf0
--- /dev/null
+++ b/test/http_test.exs
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTPTest do
+ use Pleroma.DataCase
+ import Tesla.Mock
+
+ setup do
+ mock(fn
+ %{
+ method: :get,
+ url: "http://example.com/hello",
+ headers: [{"content-type", "application/json"}]
+ } ->
+ json(%{"my" => "data"})
+
+ %{method: :get, url: "http://example.com/hello"} ->
+ %Tesla.Env{status: 200, body: "hello"}
+
+ %{method: :post, url: "http://example.com/world"} ->
+ %Tesla.Env{status: 200, body: "world"}
+ end)
+
+ :ok
+ end
+
+ describe "get/1" do
+ test "returns successfully result" do
+ assert Pleroma.HTTP.get("http://example.com/hello") == {
+ :ok,
+ %Tesla.Env{status: 200, body: "hello"}
+ }
+ end
+ end
+
+ describe "get/2 (with headers)" do
+ test "returns successfully result for json content-type" do
+ assert Pleroma.HTTP.get("http://example.com/hello", [{"content-type", "application/json"}]) ==
+ {
+ :ok,
+ %Tesla.Env{
+ status: 200,
+ body: "{\"my\":\"data\"}",
+ headers: [{"content-type", "application/json"}]
+ }
+ }
+ end
+ end
+
+ describe "post/2" do
+ test "returns successfully result" do
+ assert Pleroma.HTTP.post("http://example.com/world", "") == {
+ :ok,
+ %Tesla.Env{status: 200, body: "world"}
+ }
+ end
+ end
+end
diff --git a/test/integration/mastodon_websocket_test.exs b/test/integration/mastodon_websocket_test.exs
new file mode 100644
index 000000000..b42c9ef07
--- /dev/null
+++ b/test/integration/mastodon_websocket_test.exs
@@ -0,0 +1,101 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Integration.MastodonWebsocketTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Integration.WebsocketClient
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.OAuth
+ alias Pleroma.Web.Streamer
+
+ @path Pleroma.Web.Endpoint.url()
+ |> URI.parse()
+ |> Map.put(:scheme, "ws")
+ |> Map.put(:path, "/api/v1/streaming")
+ |> URI.to_string()
+
+ setup do
+ GenServer.start(Streamer, %{}, name: Streamer)
+
+ on_exit(fn ->
+ if pid = Process.whereis(Streamer) do
+ Process.exit(pid, :kill)
+ end
+ end)
+ end
+
+ def start_socket(qs \\ nil, headers \\ []) do
+ path =
+ case qs do
+ nil -> @path
+ qs -> @path <> qs
+ end
+
+ WebsocketClient.start_link(self(), path, headers)
+ end
+
+ test "refuses invalid requests" do
+ assert {:error, {400, _}} = start_socket()
+ assert {:error, {404, _}} = start_socket("?stream=ncjdk")
+ end
+
+ test "requires authentication and a valid token for protected streams" do
+ assert {:error, {403, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa")
+ assert {:error, {403, _}} = start_socket("?stream=user")
+ end
+
+ test "allows public streams without authentication" do
+ assert {:ok, _} = start_socket("?stream=public")
+ assert {:ok, _} = start_socket("?stream=public:local")
+ assert {:ok, _} = start_socket("?stream=hashtag&tag=lain")
+ end
+
+ test "receives well formatted events" do
+ user = insert(:user)
+ {:ok, _} = start_socket("?stream=public")
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "nice echo chamber"})
+
+ assert_receive {:text, raw_json}, 1_000
+ assert {:ok, json} = Jason.decode(raw_json)
+
+ assert "update" == json["event"]
+ assert json["payload"]
+ assert {:ok, json} = Jason.decode(json["payload"])
+
+ view_json =
+ Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: activity, for: nil)
+ |> Jason.encode!()
+ |> Jason.decode!()
+
+ assert json == view_json
+ end
+
+ describe "with a valid user token" do
+ setup do
+ {:ok, app} =
+ Pleroma.Repo.insert(
+ OAuth.App.register_changeset(%OAuth.App{}, %{
+ client_name: "client",
+ scopes: ["scope"],
+ redirect_uris: "url"
+ })
+ )
+
+ user = insert(:user)
+
+ {:ok, auth} = OAuth.Authorization.create_authorization(app, user)
+
+ {:ok, token} = OAuth.Token.exchange_token(app, auth)
+
+ %{user: user, token: token}
+ end
+
+ test "accepts valid tokens", state do
+ assert {:ok, _} = start_socket("?stream=user&access_token=#{state.token.token}")
+ end
+ end
+end
diff --git a/test/list_test.exs b/test/list_test.exs
index 19eef8f6b..1909c0cd9 100644
--- a/test/list_test.exs
+++ b/test/list_test.exs
@@ -1,9 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.ListTest do
- alias Pleroma.{User, Repo}
+ alias Pleroma.Repo
use Pleroma.DataCase
import Pleroma.Factory
- import Ecto.Query
test "creating a list" do
user = insert(:user)
@@ -32,7 +35,7 @@ defmodule Pleroma.ListTest do
user = insert(:user)
other_user = insert(:user)
{:ok, list} = Pleroma.List.create("title", user)
- {:ok, %{following: following}} = Pleroma.List.follow(list, other_user)
+ {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user)
{:ok, %{following: following}} = Pleroma.List.unfollow(list, other_user)
assert [] == following
end
diff --git a/test/media_proxy_test.exs b/test/media_proxy_test.exs
index d71f9f13a..ddbadfbf5 100644
--- a/test/media_proxy_test.exs
+++ b/test/media_proxy_test.exs
@@ -1,6 +1,11 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.MediaProxyTest do
use ExUnit.Case
import Pleroma.Web.MediaProxy
+ alias Pleroma.Web.MediaProxy.MediaProxyController
describe "when enabled" do
setup do
@@ -65,6 +70,14 @@ defmodule Pleroma.MediaProxyTest do
assert decode_result(encoded) == url
end
+ test "ensures urls are url-encoded" do
+ assert decode_result(url("https://pleroma.social/Hello world.jpg")) ==
+ "https://pleroma.social/Hello%20world.jpg"
+
+ assert decode_result(url("https://pleroma.social/Hello%20world.jpg")) ==
+ "https://pleroma.social/Hello%20world.jpg"
+ end
+
test "validates signature" do
secret_key_base = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
@@ -83,6 +96,34 @@ defmodule Pleroma.MediaProxyTest do
assert decode_url(sig, base64) == {:error, :invalid_signature}
end
+ test "filename_matches matches url encoded paths" do
+ assert MediaProxyController.filename_matches(
+ true,
+ "/Hello%20world.jpg",
+ "http://pleroma.social/Hello world.jpg"
+ ) == :ok
+
+ assert MediaProxyController.filename_matches(
+ true,
+ "/Hello%20world.jpg",
+ "http://pleroma.social/Hello%20world.jpg"
+ ) == :ok
+ end
+
+ test "filename_matches matches non-url encoded paths" do
+ assert MediaProxyController.filename_matches(
+ true,
+ "/Hello world.jpg",
+ "http://pleroma.social/Hello%20world.jpg"
+ ) == :ok
+
+ assert MediaProxyController.filename_matches(
+ true,
+ "/Hello world.jpg",
+ "http://pleroma.social/Hello world.jpg"
+ ) == :ok
+ end
+
test "uses the configured base_url" do
base_url = Pleroma.Config.get([:media_proxy, :base_url])
@@ -99,6 +140,15 @@ defmodule Pleroma.MediaProxyTest do
assert String.starts_with?(encoded, Pleroma.Config.get([:media_proxy, :base_url]))
end
+
+ # https://git.pleroma.social/pleroma/pleroma/issues/580
+ test "encoding S3 links (must preserve `%2F`)" do
+ url =
+ "https://s3.amazonaws.com/example/test.png?X-Amz-Credential=your-access-key-id%2F20130721%2Fus-east-1%2Fs3%2Faws4_request"
+
+ encoded = url(url)
+ assert decode_result(encoded) == url
+ end
end
describe "when disabled" do
diff --git a/test/notification_test.exs b/test/notification_test.exs
index a36ed5bb8..c3db77b6c 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -1,9 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.NotificationTest do
use Pleroma.DataCase
- alias Pleroma.Web.TwitterAPI.TwitterAPI
- alias Pleroma.Web.CommonAPI
- alias Pleroma.{User, Notification}
+ alias Pleroma.Notification
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.TwitterAPI.TwitterAPI
import Pleroma.Factory
describe "create_notifications" do
@@ -24,6 +29,18 @@ defmodule Pleroma.NotificationTest do
assert notification.activity_id == activity.id
assert other_notification.activity_id == activity.id
end
+
+ test "it creates a notification for subscribed users" do
+ user = insert(:user)
+ subscriber = insert(:user)
+
+ User.subscribe(subscriber, user)
+
+ {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
+ {:ok, [notification]} = Notification.create_notifications(status)
+
+ assert notification.user_id == subscriber.id
+ end
end
describe "create_notification" do
@@ -36,12 +53,140 @@ defmodule Pleroma.NotificationTest do
assert nil == Notification.create_notification(activity, user)
end
+ test "it doesn't create a notificatin for the user if the user mutes the activity author" do
+ muter = insert(:user)
+ muted = insert(:user)
+ {:ok, _} = User.mute(muter, muted)
+ muter = Repo.get(User, muter.id)
+ {:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
+
+ assert nil == Notification.create_notification(activity, muter)
+ end
+
+ test "it doesn't create a notification for an activity from a muted thread" do
+ muter = insert(:user)
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"})
+ CommonAPI.add_mute(muter, activity)
+
+ {:ok, activity} =
+ CommonAPI.post(other_user, %{
+ "status" => "Hi @#{muter.nickname}",
+ "in_reply_to_status_id" => activity.id
+ })
+
+ assert nil == Notification.create_notification(activity, muter)
+ end
+
+ test "it disables notifications from people on remote instances" do
+ user = insert(:user, info: %{notification_settings: %{"remote" => false}})
+ other_user = insert(:user)
+
+ create_activity = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "actor" => other_user.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "content" => "Hi @#{user.nickname}",
+ "attributedTo" => other_user.ap_id
+ }
+ }
+
+ {:ok, %{local: false} = activity} = Transmogrifier.handle_incoming(create_activity)
+ assert nil == Notification.create_notification(activity, user)
+ end
+
+ test "it disables notifications from people on the local instance" do
+ user = insert(:user, info: %{notification_settings: %{"local" => false}})
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
+ assert nil == Notification.create_notification(activity, user)
+ end
+
+ test "it disables notifications from followers" do
+ follower = insert(:user)
+ followed = insert(:user, info: %{notification_settings: %{"followers" => false}})
+ User.follow(follower, followed)
+ {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
+ assert nil == Notification.create_notification(activity, followed)
+ end
+
+ test "it disables notifications from people the user follows" do
+ follower = insert(:user, info: %{notification_settings: %{"follows" => false}})
+ followed = insert(:user)
+ User.follow(follower, followed)
+ follower = Repo.get(User, follower.id)
+ {:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
+ assert nil == Notification.create_notification(activity, follower)
+ end
+
test "it doesn't create a notification for user if he is the activity author" do
activity = insert(:note_activity)
author = User.get_by_ap_id(activity.data["actor"])
assert nil == Notification.create_notification(activity, author)
end
+
+ test "it doesn't create a notification for follow-unfollow-follow chains" do
+ user = insert(:user)
+ followed_user = insert(:user)
+ {:ok, _, _, activity} = TwitterAPI.follow(user, %{"user_id" => followed_user.id})
+ Notification.create_notification(activity, followed_user)
+ TwitterAPI.unfollow(user, %{"user_id" => followed_user.id})
+ {:ok, _, _, activity_dupe} = TwitterAPI.follow(user, %{"user_id" => followed_user.id})
+ assert nil == Notification.create_notification(activity_dupe, followed_user)
+ end
+
+ test "it doesn't create a notification for like-unlike-like chains" do
+ user = insert(:user)
+ liked_user = insert(:user)
+ {:ok, status} = TwitterAPI.create_status(liked_user, %{"status" => "Yui is best yuru"})
+ {:ok, fav_status} = TwitterAPI.fav(user, status.id)
+ Notification.create_notification(fav_status, liked_user)
+ TwitterAPI.unfav(user, status.id)
+ {:ok, dupe} = TwitterAPI.fav(user, status.id)
+ assert nil == Notification.create_notification(dupe, liked_user)
+ end
+
+ test "it doesn't create a notification for repeat-unrepeat-repeat chains" do
+ user = insert(:user)
+ retweeted_user = insert(:user)
+
+ {:ok, status} =
+ TwitterAPI.create_status(retweeted_user, %{
+ "status" => "Send dupe notifications to the shadow realm"
+ })
+
+ {:ok, retweeted_activity} = TwitterAPI.repeat(user, status.id)
+ Notification.create_notification(retweeted_activity, retweeted_user)
+ TwitterAPI.unrepeat(user, status.id)
+ {:ok, dupe} = TwitterAPI.repeat(user, status.id)
+ assert nil == Notification.create_notification(dupe, retweeted_user)
+ end
+
+ test "it doesn't create duplicate notifications for follow+subscribed users" do
+ user = insert(:user)
+ subscriber = insert(:user)
+
+ {:ok, _, _, _} = TwitterAPI.follow(subscriber, %{"user_id" => user.id})
+ User.subscribe(subscriber, user)
+ {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
+ {:ok, [_notif]} = Notification.create_notifications(status)
+ end
+
+ test "it doesn't create subscription notifications if the recipient cannot see the status" do
+ user = insert(:user)
+ subscriber = insert(:user)
+
+ User.subscribe(subscriber, user)
+
+ {:ok, status} =
+ TwitterAPI.create_status(user, %{"status" => "inwisible", "visibility" => "direct"})
+
+ assert {:ok, []} == Notification.create_notifications(status)
+ end
end
describe "get notification" do
@@ -127,12 +272,12 @@ defmodule Pleroma.NotificationTest do
user = insert(:user)
other_user = insert(:user)
- {:ok, activity} =
+ {:ok, _activity} =
TwitterAPI.create_status(user, %{
"status" => "hey @#{other_user.nickname}!"
})
- {:ok, activity} =
+ {:ok, _activity} =
TwitterAPI.create_status(user, %{
"status" => "hey again @#{other_user.nickname}!"
})
@@ -142,14 +287,14 @@ defmodule Pleroma.NotificationTest do
assert n2.id > n1.id
- {:ok, activity} =
+ {:ok, _activity} =
TwitterAPI.create_status(user, %{
"status" => "hey yet again @#{other_user.nickname}!"
})
Notification.set_read_up_to(other_user, n2.id)
- [n3, n2, n1] = notifs = Notification.for_user(other_user)
+ [n3, n2, n1] = Notification.for_user(other_user)
assert n1.seen == true
assert n2.seen == true
@@ -258,7 +403,7 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
@@ -266,7 +411,7 @@ defmodule Pleroma.NotificationTest do
{:ok, _} = CommonAPI.delete(activity.id, user)
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
end
test "liking an activity results in 1 notification, then 0 if the activity is unliked" do
@@ -275,7 +420,7 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
@@ -283,7 +428,7 @@ defmodule Pleroma.NotificationTest do
{:ok, _, _, _} = CommonAPI.unfavorite(activity.id, other_user)
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
end
test "repeating an activity results in 1 notification, then 0 if the activity is deleted" do
@@ -292,7 +437,7 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
@@ -300,7 +445,7 @@ defmodule Pleroma.NotificationTest do
{:ok, _} = CommonAPI.delete(activity.id, user)
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
end
test "repeating an activity results in 1 notification, then 0 if the activity is unrepeated" do
@@ -309,7 +454,7 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
@@ -317,7 +462,7 @@ defmodule Pleroma.NotificationTest do
{:ok, _, _} = CommonAPI.unrepeat(activity.id, other_user)
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
end
test "liking an activity which is already deleted does not generate a notification" do
@@ -326,15 +471,15 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
{:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
{:error, _} = CommonAPI.favorite(activity.id, other_user)
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
end
test "repeating an activity which is already deleted does not generate a notification" do
@@ -343,15 +488,15 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
{:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
{:error, _} = CommonAPI.repeat(activity.id, other_user)
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
end
test "replying to a deleted post without tagging does not generate a notification" do
@@ -367,7 +512,7 @@ defmodule Pleroma.NotificationTest do
"in_reply_to_status_id" => activity.id
})
- assert length(Notification.for_user(user)) == 0
+ assert Enum.empty?(Notification.for_user(user))
end
end
end
diff --git a/test/object_test.exs b/test/object_test.exs
index dac6c3be7..a30efd48c 100644
--- a/test/object_test.exs
+++ b/test/object_test.exs
@@ -1,7 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.ObjectTest do
use Pleroma.DataCase
import Pleroma.Factory
- alias Pleroma.{Repo, Object}
+ alias Pleroma.Object
+ alias Pleroma.Repo
test "returns an object by it's AP id" do
object = insert(:note)
@@ -32,6 +37,8 @@ defmodule Pleroma.ObjectTest do
found_object = Object.get_by_ap_id(object.data["id"])
refute object == found_object
+
+ assert found_object.data["type"] == "Tombstone"
end
test "ensures cache is cleared for the object" do
@@ -47,6 +54,8 @@ defmodule Pleroma.ObjectTest do
cached_object = Object.get_cached_by_ap_id(object.data["id"])
refute object == cached_object
+
+ assert cached_object.data["type"] == "Tombstone"
end
end
diff --git a/test/plugs/admin_secret_authentication_plug_test.exs b/test/plugs/admin_secret_authentication_plug_test.exs
new file mode 100644
index 000000000..e1d4b391f
--- /dev/null
+++ b/test/plugs/admin_secret_authentication_plug_test.exs
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.AdminSecretAuthenticationPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+ import Pleroma.Factory
+
+ alias Pleroma.Plugs.AdminSecretAuthenticationPlug
+
+ test "does nothing if a user is assigned", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+
+ ret_conn =
+ conn
+ |> AdminSecretAuthenticationPlug.call(%{})
+
+ assert conn == ret_conn
+ end
+
+ test "with secret set and given in the 'admin_token' parameter, it assigns an admin user", %{
+ conn: conn
+ } do
+ Pleroma.Config.put(:admin_token, "password123")
+
+ conn =
+ %{conn | params: %{"admin_token" => "wrong_password"}}
+ |> AdminSecretAuthenticationPlug.call(%{})
+
+ refute conn.assigns[:user]
+
+ conn =
+ %{conn | params: %{"admin_token" => "password123"}}
+ |> AdminSecretAuthenticationPlug.call(%{})
+
+ assert conn.assigns[:user].info.is_admin
+ end
+end
diff --git a/test/plugs/authentication_plug_test.exs b/test/plugs/authentication_plug_test.exs
index 061fa0cac..6158086ea 100644
--- a/test/plugs/authentication_plug_test.exs
+++ b/test/plugs/authentication_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.AuthenticationPlugTest do
use Pleroma.Web.ConnCase, async: true
diff --git a/test/plugs/basic_auth_decoder_plug_test.exs b/test/plugs/basic_auth_decoder_plug_test.exs
index a4876fef7..4d7728e93 100644
--- a/test/plugs/basic_auth_decoder_plug_test.exs
+++ b/test/plugs/basic_auth_decoder_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.BasicAuthDecoderPlugTest do
use Pleroma.Web.ConnCase, async: true
diff --git a/test/plugs/ensure_authenticated_plug_test.exs b/test/plugs/ensure_authenticated_plug_test.exs
index b32817fef..37ab5213a 100644
--- a/test/plugs/ensure_authenticated_plug_test.exs
+++ b/test/plugs/ensure_authenticated_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest do
use Pleroma.Web.ConnCase, async: true
diff --git a/test/plugs/ensure_user_key_plug_test.exs b/test/plugs/ensure_user_key_plug_test.exs
index 9beda838e..6a9627f6a 100644
--- a/test/plugs/ensure_user_key_plug_test.exs
+++ b/test/plugs/ensure_user_key_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.EnsureUserKeyPlugTest do
use Pleroma.Web.ConnCase, async: true
diff --git a/test/plugs/http_security_plug_test.exs b/test/plugs/http_security_plug_test.exs
index 169c3b3a8..0cbb7e4b1 100644
--- a/test/plugs/http_security_plug_test.exs
+++ b/test/plugs/http_security_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
use Pleroma.Web.ConnCase
alias Pleroma.Config
diff --git a/test/plugs/http_signature_plug_test.exs b/test/plugs/http_signature_plug_test.exs
index a15c5b470..6a00dd4fd 100644
--- a/test/plugs/http_signature_plug_test.exs
+++ b/test/plugs/http_signature_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
use Pleroma.Web.ConnCase
alias Pleroma.Web.HTTPSignatures
diff --git a/test/plugs/instance_static_test.exs b/test/plugs/instance_static_test.exs
new file mode 100644
index 000000000..e2dcfa3d8
--- /dev/null
+++ b/test/plugs/instance_static_test.exs
@@ -0,0 +1,47 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RuntimeStaticPlugTest do
+ use Pleroma.Web.ConnCase
+
+ @dir "test/tmp/instance_static"
+
+ setup do
+ static_dir = Pleroma.Config.get([:instance, :static_dir])
+ Pleroma.Config.put([:instance, :static_dir], @dir)
+ File.mkdir_p!(@dir)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :static_dir], static_dir)
+ File.rm_rf(@dir)
+ end)
+ end
+
+ test "overrides index" do
+ bundled_index = get(build_conn(), "/")
+ assert html_response(bundled_index, 200) == File.read!("priv/static/index.html")
+
+ File.write!(@dir <> "/index.html", "hello world")
+
+ index = get(build_conn(), "/")
+ assert html_response(index, 200) == "hello world"
+ end
+
+ test "overrides any file in static/static" do
+ bundled_index = get(build_conn(), "/static/terms-of-service.html")
+
+ assert html_response(bundled_index, 200) ==
+ File.read!("priv/static/static/terms-of-service.html")
+
+ File.mkdir!(@dir <> "/static")
+ File.write!(@dir <> "/static/terms-of-service.html", "plz be kind")
+
+ index = get(build_conn(), "/static/terms-of-service.html")
+ assert html_response(index, 200) == "plz be kind"
+
+ File.write!(@dir <> "/static/kaniini.html", "<h1>rabbit hugs as a service</h1>")
+ index = get(build_conn(), "/static/kaniini.html")
+ assert html_response(index, 200) == "<h1>rabbit hugs as a service</h1>"
+ end
+end
diff --git a/test/plugs/legacy_authentication_plug_test.exs b/test/plugs/legacy_authentication_plug_test.exs
index 383a22ff8..8b0b06772 100644
--- a/test/plugs/legacy_authentication_plug_test.exs
+++ b/test/plugs/legacy_authentication_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
use Pleroma.Web.ConnCase, async: true
@@ -43,16 +47,18 @@ defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
|> assign(:auth_user, user)
conn =
- with_mock User,
- reset_password: fn user, %{password: password, password_confirmation: password} ->
- send(self(), :reset_password)
- {:ok, user}
- end do
- conn
- |> LegacyAuthenticationPlug.call(%{})
+ with_mocks([
+ {:crypt, [], [crypt: fn _password, password_hash -> password_hash end]},
+ {User, [],
+ [
+ reset_password: fn user, %{password: password, password_confirmation: password} ->
+ {:ok, user}
+ end
+ ]}
+ ]) do
+ LegacyAuthenticationPlug.call(conn, %{})
end
- assert_received :reset_password
assert conn.assigns.user == user
end
diff --git a/test/plugs/oauth_plug_test.exs b/test/plugs/oauth_plug_test.exs
new file mode 100644
index 000000000..17fdba916
--- /dev/null
+++ b/test/plugs/oauth_plug_test.exs
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.OAuthPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Plugs.OAuthPlug
+ import Pleroma.Factory
+
+ @session_opts [
+ store: :cookie,
+ key: "_test",
+ signing_salt: "cooldude"
+ ]
+
+ setup %{conn: conn} do
+ user = insert(:user)
+ {:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create_token(insert(:oauth_app), user)
+ %{user: user, token: token, conn: conn}
+ end
+
+ test "with valid token(uppercase), it assigns the user", %{conn: conn} = opts do
+ conn =
+ conn
+ |> put_req_header("authorization", "BEARER #{opts[:token]}")
+ |> OAuthPlug.call(%{})
+
+ assert conn.assigns[:user] == opts[:user]
+ end
+
+ test "with valid token(downcase), it assigns the user", %{conn: conn} = opts do
+ conn =
+ conn
+ |> put_req_header("authorization", "bearer #{opts[:token]}")
+ |> OAuthPlug.call(%{})
+
+ assert conn.assigns[:user] == opts[:user]
+ end
+
+ test "with invalid token, it not assigns the user", %{conn: conn} do
+ conn =
+ conn
+ |> put_req_header("authorization", "bearer TTTTT")
+ |> OAuthPlug.call(%{})
+
+ refute conn.assigns[:user]
+ end
+
+ test "when token is missed but token in session, it assigns the user", %{conn: conn} = opts do
+ conn =
+ conn
+ |> Plug.Session.call(Plug.Session.init(@session_opts))
+ |> fetch_session()
+ |> put_session(:oauth_token, opts[:token])
+ |> OAuthPlug.call(%{})
+
+ assert conn.assigns[:user] == opts[:user]
+ end
+end
diff --git a/test/plugs/oauth_scopes_plug_test.exs b/test/plugs/oauth_scopes_plug_test.exs
new file mode 100644
index 000000000..f328026df
--- /dev/null
+++ b/test/plugs/oauth_scopes_plug_test.exs
@@ -0,0 +1,122 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.OAuthScopesPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Repo
+
+ import Pleroma.Factory
+
+ test "proceeds with no op if `assigns[:token]` is nil", %{conn: conn} do
+ conn =
+ conn
+ |> assign(:user, insert(:user))
+ |> OAuthScopesPlug.call(%{scopes: ["read"]})
+
+ refute conn.halted
+ assert conn.assigns[:user]
+ end
+
+ test "proceeds with no op if `token.scopes` fulfill specified 'any of' conditions", %{
+ conn: conn
+ } do
+ token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: ["read"]})
+
+ refute conn.halted
+ assert conn.assigns[:user]
+ end
+
+ test "proceeds with no op if `token.scopes` fulfill specified 'all of' conditions", %{
+ conn: conn
+ } do
+ token = insert(:oauth_token, scopes: ["scope1", "scope2", "scope3"]) |> Repo.preload(:user)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: ["scope2", "scope3"], op: :&})
+
+ refute conn.halted
+ assert conn.assigns[:user]
+ end
+
+ test "proceeds with cleared `assigns[:user]` if `token.scopes` doesn't fulfill specified 'any of' conditions " <>
+ "and `fallback: :proceed_unauthenticated` option is specified",
+ %{conn: conn} do
+ token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: ["follow"], fallback: :proceed_unauthenticated})
+
+ refute conn.halted
+ refute conn.assigns[:user]
+ end
+
+ test "proceeds with cleared `assigns[:user]` if `token.scopes` doesn't fulfill specified 'all of' conditions " <>
+ "and `fallback: :proceed_unauthenticated` option is specified",
+ %{conn: conn} do
+ token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{
+ scopes: ["read", "follow"],
+ op: :&,
+ fallback: :proceed_unauthenticated
+ })
+
+ refute conn.halted
+ refute conn.assigns[:user]
+ end
+
+ test "returns 403 and halts in case of no :fallback option and `token.scopes` not fulfilling specified 'any of' conditions",
+ %{conn: conn} do
+ token = insert(:oauth_token, scopes: ["read", "write"])
+ any_of_scopes = ["follow"]
+
+ conn =
+ conn
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: any_of_scopes})
+
+ assert conn.halted
+ assert 403 == conn.status
+
+ expected_error = "Insufficient permissions: #{Enum.join(any_of_scopes, ", ")}."
+ assert Jason.encode!(%{error: expected_error}) == conn.resp_body
+ end
+
+ test "returns 403 and halts in case of no :fallback option and `token.scopes` not fulfilling specified 'all of' conditions",
+ %{conn: conn} do
+ token = insert(:oauth_token, scopes: ["read", "write"])
+ all_of_scopes = ["write", "follow"]
+
+ conn =
+ conn
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: all_of_scopes, op: :&})
+
+ assert conn.halted
+ assert 403 == conn.status
+
+ expected_error =
+ "Insufficient permissions: #{Enum.join(all_of_scopes -- token.scopes, ", ")}."
+
+ assert Jason.encode!(%{error: expected_error}) == conn.resp_body
+ end
+end
diff --git a/test/plugs/session_authentication_plug_test.exs b/test/plugs/session_authentication_plug_test.exs
index bb51bc0db..0000f4258 100644
--- a/test/plugs/session_authentication_plug_test.exs
+++ b/test/plugs/session_authentication_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.SessionAuthenticationPlugTest do
use Pleroma.Web.ConnCase, async: true
diff --git a/test/plugs/set_user_session_id_plug_test.exs b/test/plugs/set_user_session_id_plug_test.exs
index 5edc0dab8..f8bfde039 100644
--- a/test/plugs/set_user_session_id_plug_test.exs
+++ b/test/plugs/set_user_session_id_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.SetUserSessionIdPlugTest do
use Pleroma.Web.ConnCase, async: true
@@ -28,6 +32,8 @@ defmodule Pleroma.Plugs.SetUserSessionIdPlugTest do
end
test "sets the user_id in the session to the user id of the user assign", %{conn: conn} do
+ Code.ensure_compiled(Pleroma.User)
+
conn =
conn
|> assign(:user, %User{id: 1})
diff --git a/test/plugs/uploaded_media_plug_test.exs b/test/plugs/uploaded_media_plug_test.exs
new file mode 100644
index 000000000..49cf5396a
--- /dev/null
+++ b/test/plugs/uploaded_media_plug_test.exs
@@ -0,0 +1,43 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.UploadedMediaPlugTest do
+ use Pleroma.Web.ConnCase
+ alias Pleroma.Upload
+
+ defp upload_file(context) do
+ Pleroma.DataCase.ensure_local_uploader(context)
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ filename: "nice_tf.jpg"
+ }
+
+ {:ok, data} = Upload.store(file)
+ [%{"href" => attachment_url} | _] = data["url"]
+ [attachment_url: attachment_url]
+ end
+
+ setup_all :upload_file
+
+ test "does not send Content-Disposition header when name param is not set", %{
+ attachment_url: attachment_url
+ } do
+ conn = get(build_conn(), attachment_url)
+ refute Enum.any?(conn.resp_headers, &(elem(&1, 0) == "content-disposition"))
+ end
+
+ test "sends Content-Disposition header when name param is set", %{
+ attachment_url: attachment_url
+ } do
+ conn = get(build_conn(), attachment_url <> "?name=\"cofe\".gif")
+
+ assert Enum.any?(
+ conn.resp_headers,
+ &(&1 == {"content-disposition", "filename=\"\\\"cofe\\\".gif\""})
+ )
+ end
+end
diff --git a/test/plugs/user_enabled_plug_test.exs b/test/plugs/user_enabled_plug_test.exs
index eeb167933..c0fafcab1 100644
--- a/test/plugs/user_enabled_plug_test.exs
+++ b/test/plugs/user_enabled_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.UserEnabledPlugTest do
use Pleroma.Web.ConnCase, async: true
diff --git a/test/plugs/user_fetcher_plug_test.exs b/test/plugs/user_fetcher_plug_test.exs
index 5195a0c4a..262eb8d93 100644
--- a/test/plugs/user_fetcher_plug_test.exs
+++ b/test/plugs/user_fetcher_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.UserFetcherPlugTest do
use Pleroma.Web.ConnCase, async: true
diff --git a/test/plugs/user_is_admin_plug_test.exs b/test/plugs/user_is_admin_plug_test.exs
index 031b2f466..9e05fff18 100644
--- a/test/plugs/user_is_admin_plug_test.exs
+++ b/test/plugs/user_is_admin_plug_test.exs
@@ -1,10 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.UserIsAdminPlugTest do
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Plugs.UserIsAdminPlug
import Pleroma.Factory
- test "accepts a user that is admin", %{conn: conn} do
+ test "accepts a user that is admin" do
user = insert(:user, info: %{is_admin: true})
conn =
@@ -18,7 +22,7 @@ defmodule Pleroma.Plugs.UserIsAdminPlugTest do
assert conn == ret_conn
end
- test "denies a user that isn't admin", %{conn: conn} do
+ test "denies a user that isn't admin" do
user = insert(:user)
conn =
@@ -29,7 +33,7 @@ defmodule Pleroma.Plugs.UserIsAdminPlugTest do
assert conn.status == 403
end
- test "denies when a user isn't set", %{conn: conn} do
+ test "denies when a user isn't set" do
conn =
build_conn()
|> UserIsAdminPlug.call(%{})
diff --git a/test/registration_test.exs b/test/registration_test.exs
new file mode 100644
index 000000000..6143b82c7
--- /dev/null
+++ b/test/registration_test.exs
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.RegistrationTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Registration
+ alias Pleroma.Repo
+
+ describe "generic changeset" do
+ test "requires :provider, :uid" do
+ registration = build(:registration, provider: nil, uid: nil)
+
+ cs = Registration.changeset(registration, %{})
+ refute cs.valid?
+
+ assert [
+ provider: {"can't be blank", [validation: :required]},
+ uid: {"can't be blank", [validation: :required]}
+ ] == cs.errors
+ end
+
+ test "ensures uniqueness of [:provider, :uid]" do
+ registration = insert(:registration)
+ registration2 = build(:registration, provider: registration.provider, uid: registration.uid)
+
+ cs = Registration.changeset(registration2, %{})
+ assert cs.valid?
+
+ assert {:error,
+ %Ecto.Changeset{
+ errors: [
+ uid:
+ {"has already been taken",
+ [constraint: :unique, constraint_name: "registrations_provider_uid_index"]}
+ ]
+ }} = Repo.insert(cs)
+
+ # Note: multiple :uid values per [:user_id, :provider] are intentionally allowed
+ cs2 = Registration.changeset(registration2, %{uid: "available.uid"})
+ assert cs2.valid?
+ assert {:ok, _} = Repo.insert(cs2)
+
+ cs3 = Registration.changeset(registration2, %{provider: "provider2"})
+ assert cs3.valid?
+ assert {:ok, _} = Repo.insert(cs3)
+ end
+
+ test "allows `nil` :user_id (user-unbound registration)" do
+ registration = build(:registration, user_id: nil)
+ cs = Registration.changeset(registration, %{})
+ assert cs.valid?
+ assert {:ok, _} = Repo.insert(cs)
+ end
+ end
+end
diff --git a/test/scheduled_activity_test.exs b/test/scheduled_activity_test.exs
new file mode 100644
index 000000000..edc7cc3f9
--- /dev/null
+++ b/test/scheduled_activity_test.exs
@@ -0,0 +1,64 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ScheduledActivityTest do
+ use Pleroma.DataCase
+ alias Pleroma.DataCase
+ alias Pleroma.ScheduledActivity
+ import Pleroma.Factory
+
+ setup context do
+ DataCase.ensure_local_uploader(context)
+ end
+
+ describe "creation" do
+ test "when daily user limit is exceeded" do
+ user = insert(:user)
+
+ today =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
+ |> NaiveDateTime.to_iso8601()
+
+ attrs = %{params: %{}, scheduled_at: today}
+ {:ok, _} = ScheduledActivity.create(user, attrs)
+ {:ok, _} = ScheduledActivity.create(user, attrs)
+ {:error, changeset} = ScheduledActivity.create(user, attrs)
+ assert changeset.errors == [scheduled_at: {"daily limit exceeded", []}]
+ end
+
+ test "when total user limit is exceeded" do
+ user = insert(:user)
+
+ today =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
+ |> NaiveDateTime.to_iso8601()
+
+ tomorrow =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(:timer.hours(36), :millisecond)
+ |> NaiveDateTime.to_iso8601()
+
+ {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
+ {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
+ {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
+ {:error, changeset} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
+ assert changeset.errors == [scheduled_at: {"total limit exceeded", []}]
+ end
+
+ test "when scheduled_at is earlier than 5 minute from now" do
+ user = insert(:user)
+
+ scheduled_at =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(:timer.minutes(4), :millisecond)
+ |> NaiveDateTime.to_iso8601()
+
+ attrs = %{params: %{}, scheduled_at: scheduled_at}
+ {:error, changeset} = ScheduledActivity.create(user, attrs)
+ assert changeset.errors == [scheduled_at: {"must be at least 5 minutes from now", []}]
+ end
+ end
+end
diff --git a/test/scheduled_activity_worker_test.exs b/test/scheduled_activity_worker_test.exs
new file mode 100644
index 000000000..b9c91dda6
--- /dev/null
+++ b/test/scheduled_activity_worker_test.exs
@@ -0,0 +1,19 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ScheduledActivityWorkerTest do
+ use Pleroma.DataCase
+ alias Pleroma.ScheduledActivity
+ import Pleroma.Factory
+
+ test "creates a status from the scheduled activity" do
+ user = insert(:user)
+ scheduled_activity = insert(:scheduled_activity, user: user, params: %{status: "hi"})
+ Pleroma.ScheduledActivityWorker.perform(:execute, scheduled_activity.id)
+
+ refute Repo.get(ScheduledActivity, scheduled_activity.id)
+ activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id))
+ assert activity.data["object"]["content"] == "hi"
+ end
+end
diff --git a/test/support/builders/activity_builder.ex b/test/support/builders/activity_builder.ex
index eb72d5ba9..6e5a8e059 100644
--- a/test/support/builders/activity_builder.ex
+++ b/test/support/builders/activity_builder.ex
@@ -1,5 +1,4 @@
defmodule Pleroma.Builders.ActivityBuilder do
- alias Pleroma.Builders.UserBuilder
alias Pleroma.Web.ActivityPub.ActivityPub
def build(data \\ %{}, opts \\ %{}) do
diff --git a/test/support/builders/user_builder.ex b/test/support/builders/user_builder.ex
index 7a1ca79b5..f58e1b0ad 100644
--- a/test/support/builders/user_builder.ex
+++ b/test/support/builders/user_builder.ex
@@ -1,5 +1,6 @@
defmodule Pleroma.Builders.UserBuilder do
- alias Pleroma.{User, Repo}
+ alias Pleroma.Repo
+ alias Pleroma.User
def build(data \\ %{}) do
user = %User{
diff --git a/test/support/captcha_mock.ex b/test/support/captcha_mock.ex
new file mode 100644
index 000000000..ef4e68bc5
--- /dev/null
+++ b/test/support/captcha_mock.ex
@@ -0,0 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Captcha.Mock do
+ alias Pleroma.Captcha.Service
+ @behaviour Service
+
+ @impl Service
+ def new, do: %{type: :mock}
+
+ @impl Service
+ def validate(_token, _captcha, _data), do: :ok
+end
diff --git a/test/support/channel_case.ex b/test/support/channel_case.ex
index 68995a01d..466d8986f 100644
--- a/test/support/channel_case.ex
+++ b/test/support/channel_case.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ChannelCase do
@moduledoc """
This module defines the test case to be used by
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 2e6707087..ec5892ff5 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ConnCase do
@moduledoc """
This module defines the test case to be used by
@@ -19,6 +23,7 @@ defmodule Pleroma.Web.ConnCase do
quote do
# Import conveniences for testing with connections
use Phoenix.ConnTest
+ use Pleroma.Tests.Helpers
import Pleroma.Web.Router.Helpers
# The default endpoint for testing
@@ -28,6 +33,7 @@ defmodule Pleroma.Web.ConnCase do
setup tags do
Cachex.clear(:user_cache)
+ Cachex.clear(:object_cache)
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
unless tags[:async] do
diff --git a/test/support/data_case.ex b/test/support/data_case.ex
index 8eff0fd94..df260bd3f 100644
--- a/test/support/data_case.ex
+++ b/test/support/data_case.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.DataCase do
@moduledoc """
This module defines the setup for tests requiring
@@ -22,11 +26,13 @@ defmodule Pleroma.DataCase do
import Ecto.Changeset
import Ecto.Query
import Pleroma.DataCase
+ use Pleroma.Tests.Helpers
end
end
setup tags do
Cachex.clear(:user_cache)
+ Cachex.clear(:object_cache)
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
unless tags[:async] do
@@ -36,6 +42,23 @@ defmodule Pleroma.DataCase do
:ok
end
+ def ensure_local_uploader(_context) do
+ uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
+ filters = Pleroma.Config.get([Pleroma.Upload, :filters])
+
+ unless uploader == Pleroma.Uploaders.Local || filters != [] do
+ Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
+ Pleroma.Config.put([Pleroma.Upload, :filters], [])
+
+ on_exit(fn ->
+ Pleroma.Config.put([Pleroma.Upload, :uploader], uploader)
+ Pleroma.Config.put([Pleroma.Upload, :filters], filters)
+ end)
+ end
+
+ :ok
+ end
+
@doc """
A helper that transform changeset errors to a map of messages.
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 2889d8977..ea59912cf 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Factory do
use ExMachina.Ecto, repo: Pleroma.Repo
@@ -19,7 +23,7 @@ defmodule Pleroma.Factory do
}
end
- def note_factory do
+ def note_factory(attrs \\ %{}) do
text = sequence(:text, &"This is :moominmamma: note #{&1}")
user = insert(:user)
@@ -42,7 +46,7 @@ defmodule Pleroma.Factory do
}
%Pleroma.Object{
- data: data
+ data: merge_attributes(data, Map.get(attrs, :data, %{}))
}
end
@@ -53,6 +57,24 @@ defmodule Pleroma.Factory do
%Pleroma.Object{data: Map.merge(data, %{"to" => [user2.ap_id]})}
end
+ def article_factory do
+ note_factory()
+ |> Map.put("type", "Article")
+ end
+
+ def tombstone_factory do
+ data = %{
+ "type" => "Tombstone",
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+ "formerType" => "Note",
+ "deleted" => DateTime.utc_now() |> DateTime.to_iso8601()
+ }
+
+ %Pleroma.Object{
+ data: data
+ }
+ end
+
def direct_note_activity_factory do
dm = insert(:direct_note)
@@ -73,8 +95,8 @@ defmodule Pleroma.Factory do
}
end
- def note_activity_factory do
- note = insert(:note)
+ def note_activity_factory(attrs \\ %{}) do
+ note = attrs[:note] || insert(:note)
data = %{
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
@@ -93,9 +115,29 @@ defmodule Pleroma.Factory do
}
end
- def announce_activity_factory do
- note_activity = insert(:note_activity)
- user = insert(:user)
+ def article_activity_factory do
+ article = insert(:article)
+
+ data = %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Create",
+ "actor" => article.data["actor"],
+ "to" => article.data["to"],
+ "object" => article.data,
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "context" => article.data["context"]
+ }
+
+ %Pleroma.Activity{
+ data: data,
+ actor: data["actor"],
+ recipients: data["to"]
+ }
+ end
+
+ def announce_activity_factory(attrs \\ %{}) do
+ note_activity = attrs[:note_activity] || insert(:note_activity)
+ user = attrs[:user] || insert(:user)
data = %{
"type" => "Announce",
@@ -151,7 +193,7 @@ defmodule Pleroma.Factory do
def websub_subscription_factory do
%Pleroma.Web.Websub.WebsubServerSubscription{
topic: "http://example.org",
- callback: "http://example/org/callback",
+ callback: "http://example.org/callback",
secret: "here's a secret",
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 100),
state: "requested"
@@ -172,10 +214,81 @@ defmodule Pleroma.Factory do
%Pleroma.Web.OAuth.App{
client_name: "Some client",
redirect_uris: "https://example.com/callback",
- scopes: "read",
+ scopes: ["read", "write", "follow", "push"],
website: "https://example.com",
- client_id: "aaabbb==",
+ client_id: Ecto.UUID.generate(),
client_secret: "aaa;/&bbb"
}
end
+
+ def instance_factory do
+ %Pleroma.Instances.Instance{
+ host: "domain.com",
+ unreachable_since: nil
+ }
+ end
+
+ def oauth_token_factory do
+ oauth_app = insert(:oauth_app)
+
+ %Pleroma.Web.OAuth.Token{
+ token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(),
+ refresh_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(),
+ user: build(:user),
+ app_id: oauth_app.id,
+ valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
+ }
+ end
+
+ def oauth_authorization_factory do
+ %Pleroma.Web.OAuth.Authorization{
+ token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false),
+ scopes: ["read", "write", "follow", "push"],
+ valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10),
+ user: build(:user),
+ app: build(:oauth_app)
+ }
+ end
+
+ def push_subscription_factory do
+ %Pleroma.Web.Push.Subscription{
+ user: build(:user),
+ token: build(:oauth_token),
+ endpoint: "https://example.com/example/1234",
+ key_auth: "8eDyX_uCN0XRhSbY5hs7Hg==",
+ key_p256dh:
+ "BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA=",
+ data: %{}
+ }
+ end
+
+ def notification_factory do
+ %Pleroma.Notification{
+ user: build(:user)
+ }
+ end
+
+ def scheduled_activity_factory do
+ %Pleroma.ScheduledActivity{
+ user: build(:user),
+ scheduled_at: NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(60), :millisecond),
+ params: build(:note) |> Map.from_struct() |> Map.get(:data)
+ }
+ end
+
+ def registration_factory do
+ user = insert(:user)
+
+ %Pleroma.Registration{
+ user: user,
+ provider: "twitter",
+ uid: "171799000",
+ info: %{
+ "name" => "John Doe",
+ "email" => "john@doe.com",
+ "nickname" => "johndoe",
+ "description" => "My bio"
+ }
+ }
+ end
end
diff --git a/test/support/helpers.ex b/test/support/helpers.ex
new file mode 100644
index 000000000..6e389ce52
--- /dev/null
+++ b/test/support/helpers.ex
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Tests.Helpers do
+ @moduledoc """
+ Helpers for use in tests.
+ """
+
+ defmacro __using__(_opts) do
+ quote do
+ def refresh_record(%{id: id, __struct__: model} = _),
+ do: refresh_record(model, %{id: id})
+
+ def refresh_record(model, %{id: id} = _) do
+ Pleroma.Repo.get_by(model, id: id)
+ end
+
+ # Used for comparing json rendering during tests.
+ def render_json(view, template, assigns) do
+ assigns = Map.new(assigns)
+
+ view.render(template, assigns)
+ |> Poison.encode!()
+ |> Poison.decode!()
+ end
+ end
+ end
+end
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
new file mode 100644
index 000000000..5b355bfe6
--- /dev/null
+++ b/test/support/http_request_mock.ex
@@ -0,0 +1,791 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule HttpRequestMock do
+ require Logger
+
+ def request(
+ %Tesla.Env{
+ url: url,
+ method: method,
+ headers: headers,
+ query: query,
+ body: body
+ } = _env
+ ) do
+ with {:ok, res} <- apply(__MODULE__, method, [url, query, body, headers]) do
+ res
+ else
+ {_, _r} = error ->
+ # Logger.warn(r)
+ error
+ end
+ end
+
+ # GET Requests
+ #
+ def get(url, query \\ [], body \\ [], headers \\ [])
+
+ def get("https://osada.macgirvin.com/channel/mike", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!("test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json")
+ }}
+ end
+
+ def get("https://mastodon.social/users/emelie/statuses/101849165031453009", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/status.emelie.json")
+ }}
+ end
+
+ def get("https://mastodon.social/users/emelie", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/emelie.json")
+ }}
+ end
+
+ def get(
+ "https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/webfinger_emelie.json")
+ }}
+ end
+
+ def get("https://mastodon.social/users/emelie.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/emelie.atom")
+ }}
+ end
+
+ def get(
+ "https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json")
+ }}
+ end
+
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/29191",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml")
+ }}
+ end
+
+ def get("https://pawoo.net/users/pekorino.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.atom")
+ }}
+ end
+
+ def get(
+ "https://pawoo.net/.well-known/webfinger?resource=acct:https://pawoo.net/users/pekorino",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml")
+ }}
+ end
+
+ def get(
+ "https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/atarifrosch_feed.xml")
+ }}
+ end
+
+ def get(
+ "https://social.stopwatchingus-heidelberg.de/.well-known/webfinger?resource=acct:https://social.stopwatchingus-heidelberg.de/user/18330",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/atarifrosch_webfinger.xml")
+ }}
+ end
+
+ def get("https://mamot.fr/users/Skruyb.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/https___mamot.fr_users_Skruyb.atom")
+ }}
+ end
+
+ def get(
+ "https://mamot.fr/.well-known/webfinger?resource=acct:https://mamot.fr/users/Skruyb",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/skruyb@mamot.fr.atom")
+ }}
+ end
+
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=nonexistant@social.heldscal.la",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/nonexistant@social.heldscal.la.xml")
+ }}
+ end
+
+ def get(
+ "https://squeet.me/xrd/?uri=lain@squeet.me",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
+ }}
+ end
+
+ def get(
+ "https://mst3k.interlinked.me/users/luciferMysticus",
+ _,
+ _,
+ Accept: "application/activity+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/lucifermysticus.json")
+ }}
+ end
+
+ def get("https://prismo.news/@mxb", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/https___prismo.news__mxb.json")
+ }}
+ end
+
+ def get(
+ "https://hubzilla.example.org/channel/kaniini",
+ _,
+ _,
+ Accept: "application/activity+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/kaniini@hubzilla.example.org.json")
+ }}
+ end
+
+ def get("https://niu.moe/users/rye", _, _, Accept: "application/activity+json") do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/rye.json")
+ }}
+ end
+
+ def get("http://mastodon.example.org/users/admin/statuses/100787282858396771", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/httpoison_mock/http___mastodon.example.org_users_admin_status_1234.json"
+ )
+ }}
+ end
+
+ def get("https://puckipedia.com/", _, _, Accept: "application/activity+json") do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/puckipedia.com.json")
+ }}
+ end
+
+ def get("https://peertube.moe/accounts/7even", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/7even.json")
+ }}
+ end
+
+ def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/peertube.moe-vid.json")
+ }}
+ end
+
+ def get("https://baptiste.gelez.xyz/@/BaptisteGelez", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json")
+ }}
+ end
+
+ def get("https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json")
+ }}
+ end
+
+ def get("http://mastodon.example.org/users/admin", _, _, Accept: "application/activity+json") do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/admin@mastdon.example.org.json")
+ }}
+ end
+
+ def get(
+ "http://mastodon.example.org/@admin/99541947525187367",
+ _,
+ _,
+ Accept: "application/activity+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/mastodon-note-object.json")
+ }}
+ end
+
+ def get("https://shitposter.club/notice/7369654", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/7369654.html")
+ }}
+ end
+
+ def get("https://mstdn.io/users/mayuutann", _, _, Accept: "application/activity+json") do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/mayumayu.json")
+ }}
+ end
+
+ def get(
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394",
+ _,
+ _,
+ Accept: "application/activity+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/mayumayupost.json")
+ }}
+ end
+
+ def get("https://pleroma.soykaf.com/users/lain/feed.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml"
+ )
+ }}
+ end
+
+ def get(url, _, _, Accept: "application/xrd+xml,application/jrd+json")
+ when url in [
+ "https://pleroma.soykaf.com/.well-known/webfinger?resource=acct:https://pleroma.soykaf.com/users/lain",
+ "https://pleroma.soykaf.com/.well-known/webfinger?resource=https://pleroma.soykaf.com/users/lain"
+ ] do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml")
+ }}
+ end
+
+ def get("https://shitposter.club/api/statuses/user_timeline/1.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml"
+ )
+ }}
+ end
+
+ def get(
+ "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/1",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml")
+ }}
+ end
+
+ def get("https://shitposter.club/notice/2827873", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!("test/fixtures/httpoison_mock/https___shitposter.club_notice_2827873.html")
+ }}
+ end
+
+ def get("https://shitposter.club/api/statuses/show/2827873.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml"
+ )
+ }}
+ end
+
+ def get("https://testing.pleroma.lol/objects/b319022a-4946-44c5-9de9-34801f95507b", _, _, _) do
+ {:ok, %Tesla.Env{status: 200}}
+ end
+
+ def get("https://shitposter.club/api/statuses/user_timeline/5381.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/spc_5381.atom")
+ }}
+ end
+
+ def get(
+ "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/5381",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/spc_5381_xrd.xml")
+ }}
+ end
+
+ def get("http://shitposter.club/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/shitposter.club_host_meta")
+ }}
+ end
+
+ def get("https://shitposter.club/api/statuses/show/7369654.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/7369654.atom")
+ }}
+ end
+
+ def get("https://shitposter.club/notice/4027863", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/7369654.html")
+ }}
+ end
+
+ def get("https://social.sakamoto.gq/users/eal/feed.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/sakamoto_eal_feed.atom")
+ }}
+ end
+
+ def get("http://social.sakamoto.gq/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/social.sakamoto.gq_host_meta")
+ }}
+ end
+
+ def get(
+ "https://social.sakamoto.gq/.well-known/webfinger?resource=https://social.sakamoto.gq/users/eal",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/eal_sakamoto.xml")
+ }}
+ end
+
+ def get(
+ "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056",
+ _,
+ _,
+ Accept: "application/atom+xml"
+ ) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/sakamoto.atom")}}
+ end
+
+ def get("http://mastodon.social/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/mastodon.social_host_meta")
+ }}
+ end
+
+ def get(
+ "https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/lambadalambda",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml"
+ )
+ }}
+ end
+
+ def get("http://gs.example.org/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/gs.example.org_host_meta")
+ }}
+ end
+
+ def get(
+ "http://gs.example.org/.well-known/webfinger?resource=http://gs.example.org:4040/index.php/user/1",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml"
+ )
+ }}
+ end
+
+ def get("http://gs.example.org/index.php/api/statuses/user_timeline/1.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/httpoison_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml"
+ )
+ }}
+ end
+
+ def get("https://social.heldscal.la/api/statuses/user_timeline/29191.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml"
+ )
+ }}
+ end
+
+ def get("http://squeet.me/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/squeet.me_host_meta")}}
+ end
+
+ def get(
+ "https://squeet.me/xrd?uri=lain@squeet.me",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
+ }}
+ end
+
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=shp@social.heldscal.la",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/shp@social.heldscal.la.xml")
+ }}
+ end
+
+ def get("http://framatube.org/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/framatube.org_host_meta")
+ }}
+ end
+
+ def get(
+ "http://framatube.org/main/xrd?uri=framasoft@framatube.org",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ headers: [{"content-type", "application/json"}],
+ body: File.read!("test/fixtures/httpoison_mock/framasoft@framatube.org.json")
+ }}
+ end
+
+ def get("http://gnusocial.de/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/gnusocial.de_host_meta")
+ }}
+ end
+
+ def get(
+ "http://gnusocial.de/main/xrd?uri=winterdienst@gnusocial.de",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/winterdienst_webfinger.json")
+ }}
+ end
+
+ def get("http://status.alpicola.com/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/status.alpicola.com_host_meta")
+ }}
+ end
+
+ def get("http://macgirvin.com/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/macgirvin.com_host_meta")
+ }}
+ end
+
+ def get("http://gerzilla.de/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/gerzilla.de_host_meta")
+ }}
+ end
+
+ def get(
+ "https://gerzilla.de/xrd/?uri=kaniini@gerzilla.de",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ headers: [{"content-type", "application/json"}],
+ body: File.read!("test/fixtures/httpoison_mock/kaniini@gerzilla.de.json")
+ }}
+ end
+
+ def get("https://social.heldscal.la/api/statuses/user_timeline/23211.atom", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml"
+ )
+ }}
+ end
+
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/23211",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml")
+ }}
+ end
+
+ def get("http://social.heldscal.la/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
+ }}
+ end
+
+ def get("https://social.heldscal.la/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
+ }}
+ end
+
+ def get("https://mastodon.social/users/lambadalambda.atom", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.atom")}}
+ end
+
+ def get("https://mastodon.social/users/lambadalambda", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.json")}}
+ end
+
+ def get("https://social.heldscal.la/user/23211", _, _, Accept: "application/activity+json") do
+ {:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)}
+ end
+
+ def get("http://example.com/ogp", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
+ end
+
+ def get("http://example.com/malformed", _, _, _) do
+ {:ok,
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}}
+ end
+
+ def get("http://example.com/empty", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: "hello"}}
+ end
+
+ def get("http://404.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
+ def get(url, query, body, headers) do
+ {:error,
+ "Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{
+ inspect(headers)
+ }"}
+ end
+
+ # POST Requests
+ #
+
+ def post(url, query \\ [], body \\ [], headers \\ [])
+
+ def post("http://example.org/needs_refresh", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("http://200.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("http://connrefused.site" <> _, _, _, _) do
+ {:error, :connrefused}
+ end
+
+ def post("http://404.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
+ def post(url, _query, _body, _headers) do
+ {:error, "Not implemented the mock response for post #{inspect(url)}"}
+ end
+end
diff --git a/test/support/httpoison_mock.ex b/test/support/httpoison_mock.ex
deleted file mode 100644
index e7344500f..000000000
--- a/test/support/httpoison_mock.ex
+++ /dev/null
@@ -1,883 +0,0 @@
-defmodule HTTPoisonMock do
- alias HTTPoison.Response
-
- def process_request_options(options), do: options
-
- def get(url, body \\ [], headers \\ [])
-
- def get("https://prismo.news/@mxb", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___prismo.news__mxb.json")
- }}
- end
-
- def get("https://osada.macgirvin.com/channel/mike", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!("test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json")
- }}
- end
-
- def get(
- "https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
- _,
- _
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json")
- }}
- end
-
- def get("https://info.pleroma.site/activity.json", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity.json")
- }}
- end
-
- def get("https://info.pleroma.site/activity2.json", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity2.json")
- }}
- end
-
- def get("https://info.pleroma.site/activity3.json", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity3.json")
- }}
- end
-
- def get("https://info.pleroma.site/activity4.json", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity4.json")
- }}
- end
-
- def get("https://info.pleroma.site/actor.json", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___info.pleroma.site_actor.json")
- }}
- end
-
- def get("https://puckipedia.com/", [Accept: "application/activity+json"], _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/puckipedia.com.json")
- }}
- end
-
- def get(
- "https://gerzilla.de/.well-known/webfinger?resource=acct:kaniini@gerzilla.de",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/kaniini@gerzilla.de.json")
- }}
- end
-
- def get(
- "https://framatube.org/.well-known/webfinger?resource=acct:framasoft@framatube.org",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/framasoft@framatube.org.json")
- }}
- end
-
- def get(
- "https://gnusocial.de/.well-known/webfinger?resource=acct:winterdienst@gnusocial.de",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/winterdienst_webfinger.json")
- }}
- end
-
- def get(
- "https://social.heldscal.la/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "nonexistant@social.heldscal.la"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 500,
- body: File.read!("test/fixtures/httpoison_mock/nonexistant@social.heldscal.la.xml")
- }}
- end
-
- def get(
- "https://social.heldscal.la/.well-known/webfinger?resource=shp@social.heldscal.la",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/shp@social.heldscal.la.xml")
- }}
- end
-
- def get(
- "https://social.heldscal.la/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "shp@social.heldscal.la"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/shp@social.heldscal.la.xml")
- }}
- end
-
- def get(
- "https://social.heldscal.la/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "https://social.heldscal.la/user/23211"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml")
- }}
- end
-
- def get(
- "https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/23211",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml")
- }}
- end
-
- def get(
- "https://social.heldscal.la/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "https://social.heldscal.la/user/29191"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml")
- }}
- end
-
- def get(
- "https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/29191",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml")
- }}
- end
-
- def get(
- "https://mastodon.social/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "https://mastodon.social/users/lambadalambda"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml"
- )
- }}
- end
-
- def get(
- "https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/lambadalambda",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml"
- )
- }}
- end
-
- def get(
- "https://shitposter.club/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "https://shitposter.club/user/1"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml")
- }}
- end
-
- def get(
- "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/1",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml")
- }}
- end
-
- def get(
- "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/5381",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/spc_5381_xrd.xml")
- }}
- end
-
- def get(
- "http://gs.example.org/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "http://gs.example.org:4040/index.php/user/1"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml"
- )
- }}
- end
-
- def get(
- "http://gs.example.org/.well-known/webfinger?resource=http://gs.example.org:4040/index.php/user/1",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml"
- )
- }}
- end
-
- def get(
- "https://social.stopwatchingus-heidelberg.de/.well-known/webfinger?resource=https://social.stopwatchingus-heidelberg.de/user/18330",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/atarifrosch_webfinger.xml")
- }}
- end
-
- def get(
- "https://pleroma.soykaf.com/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "https://pleroma.soykaf.com/users/lain"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml")
- }}
- end
-
- def get(
- "https://pleroma.soykaf.com/.well-known/webfinger?resource=https://pleroma.soykaf.com/users/lain",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml")
- }}
- end
-
- def get("https://social.heldscal.la/api/statuses/user_timeline/29191.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml"
- )
- }}
- end
-
- def get("https://shitposter.club/api/statuses/user_timeline/5381.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/spc_5381.atom")
- }}
- end
-
- def get("https://social.heldscal.la/api/statuses/user_timeline/23211.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml"
- )
- }}
- end
-
- def get("https://mastodon.social/users/lambadalambda.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.atom"
- )
- }}
- end
-
- def get(
- "https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom",
- _body,
- _headers
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/atarifrosch_feed.xml")
- }}
- end
-
- def get("https://pleroma.soykaf.com/users/lain/feed.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml"
- )
- }}
- end
-
- def get("https://social.sakamoto.gq/users/eal/feed.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/sakamoto_eal_feed.atom")
- }}
- end
-
- def get("http://gs.example.org/index.php/api/statuses/user_timeline/1.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml"
- )
- }}
- end
-
- def get("https://shitposter.club/notice/2827873", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!("test/fixtures/httpoison_mock/https___shitposter.club_notice_2827873.html")
- }}
- end
-
- def get("https://shitposter.club/api/statuses/show/2827873.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml"
- )
- }}
- end
-
- def get("https://shitposter.club/api/statuses/user_timeline/1.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml"
- )
- }}
- end
-
- def post(
- "https://social.heldscal.la/main/push/hub",
- {:form, _data},
- "Content-type": "application/x-www-form-urlencoded"
- ) do
- {:ok,
- %Response{
- status_code: 202
- }}
- end
-
- def get("http://mastodon.example.org/users/admin/statuses/100787282858396771", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!(
- "test/fixtures/httpoison_mock/http___mastodon.example.org_users_admin_status_1234.json"
- )
- }}
- end
-
- def get(
- "https://pawoo.net/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "https://pawoo.net/users/pekorino"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml")
- }}
- end
-
- def get(
- "https://pawoo.net/.well-known/webfinger?resource=https://pawoo.net/users/pekorino",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml")
- }}
- end
-
- def get("https://pawoo.net/users/pekorino.atom", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.atom")
- }}
- end
-
- def get(
- "https://mamot.fr/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "https://mamot.fr/users/Skruyb"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/skruyb@mamot.fr.atom")
- }}
- end
-
- def get(
- "https://mamot.fr/.well-known/webfinger?resource=https://mamot.fr/users/Skruyb",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/skruyb@mamot.fr.atom")
- }}
- end
-
- def get(
- "https://social.sakamoto.gq/.well-known/webfinger",
- [Accept: "application/xrd+xml,application/jrd+json"],
- params: [resource: "https://social.sakamoto.gq/users/eal"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/eal_sakamoto.xml")
- }}
- end
-
- def get(
- "https://social.sakamoto.gq/.well-known/webfinger?resource=https://social.sakamoto.gq/users/eal",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/eal_sakamoto.xml")
- }}
- end
-
- def get(
- "https://pleroma.soykaf.com/.well-known/webfinger?resource=https://pleroma.soykaf.com/users/shp",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.webfigner")
- }}
- end
-
- def get(
- "https://squeet.me/xrd/?uri=lain@squeet.me",
- [Accept: "application/xrd+xml,application/jrd+json"],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
- }}
- end
-
- def get("https://mamot.fr/users/Skruyb.atom", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___mamot.fr_users_Skruyb.atom")
- }}
- end
-
- def get(
- "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056",
- [Accept: "application/atom+xml"],
- _
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/sakamoto.atom")
- }}
- end
-
- def get("https://pleroma.soykaf.com/users/shp/feed.atom", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.feed")
- }}
- end
-
- def get("http://social.heldscal.la/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
- }}
- end
-
- def get("http://status.alpicola.com/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/status.alpicola.com_host_meta")
- }}
- end
-
- def get("http://macgirvin.com/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/macgirvin.com_host_meta")
- }}
- end
-
- def get("http://mastodon.social/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/mastodon.social_host_meta")
- }}
- end
-
- def get("http://shitposter.club/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/shitposter.club_host_meta")
- }}
- end
-
- def get("http://pleroma.soykaf.com/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/pleroma.soykaf.com_host_meta")
- }}
- end
-
- def get("http://social.sakamoto.gq/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/social.sakamoto.gq_host_meta")
- }}
- end
-
- def get("http://gs.example.org/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/gs.example.org_host_meta")
- }}
- end
-
- def get("http://pawoo.net/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/pawoo.net_host_meta")
- }}
- end
-
- def get("http://mamot.fr/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/mamot.fr_host_meta")
- }}
- end
-
- def get("http://mastodon.xyz/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/mastodon.xyz_host_meta")
- }}
- end
-
- def get("http://social.wxcafe.net/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/social.wxcafe.net_host_meta")
- }}
- end
-
- def get("http://squeet.me/.well-known/host-meta", [], follow_redirect: true) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/squeet.me_host_meta")
- }}
- end
-
- def get(
- "http://social.stopwatchingus-heidelberg.de/.well-known/host-meta",
- [],
- follow_redirect: true
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body:
- File.read!("test/fixtures/httpoison_mock/social.stopwatchingus-heidelberg.de_host_meta")
- }}
- end
-
- def get("http://mastodon.example.org/users/admin", [Accept: "application/activity+json"], _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/admin@mastdon.example.org.json")
- }}
- end
-
- def get(
- "https://hubzilla.example.org/channel/kaniini",
- [Accept: "application/activity+json"],
- _
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/kaniini@hubzilla.example.org.json")
- }}
- end
-
- def get("https://masto.quad.moe/users/_HellPie", [Accept: "application/activity+json"], _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/hellpie.json")
- }}
- end
-
- def get("https://niu.moe/users/rye", [Accept: "application/activity+json"], _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/rye.json")
- }}
- end
-
- def get("https://n1u.moe/users/rye", [Accept: "application/activity+json"], _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/rye.json")
- }}
- end
-
- def get(
- "https://mst3k.interlinked.me/users/luciferMysticus",
- [Accept: "application/activity+json"],
- _
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/lucifermysticus.json")
- }}
- end
-
- def get("https://mstdn.io/users/mayuutann", [Accept: "application/activity+json"], _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/mayumayu.json")
- }}
- end
-
- def get(
- "http://mastodon.example.org/@admin/99541947525187367",
- [Accept: "application/activity+json"],
- _
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/mastodon-note-object.json")
- }}
- end
-
- def get(
- "https://mstdn.io/users/mayuutann/statuses/99568293732299394",
- [Accept: "application/activity+json"],
- _
- ) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/mayumayupost.json")
- }}
- end
-
- def get("https://shitposter.club/notice/7369654", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/7369654.html")
- }}
- end
-
- def get("https://shitposter.club/api/statuses/show/7369654.atom", _body, _headers) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/7369654.atom")
- }}
- end
-
- def get("https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json")
- }}
- end
-
- def get("https://baptiste.gelez.xyz/@/BaptisteGelez", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json")
- }}
- end
-
- def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/peertube.moe-vid.json")
- }}
- end
-
- def get("https://peertube.moe/accounts/7even", _, _) do
- {:ok,
- %Response{
- status_code: 200,
- body: File.read!("test/fixtures/httpoison_mock/7even.json")
- }}
- end
-
- def get(url, body, headers) do
- {:error,
- "Not implemented the mock response for get #{inspect(url)}, #{inspect(body)}, #{
- inspect(headers)
- }"}
- end
-
- def post(url, _body, _headers) do
- {:error, "Not implemented the mock response for post #{inspect(url)}"}
- end
-
- def post(url, _body, _headers, _options) do
- {:error, "Not implemented the mock response for post #{inspect(url)}"}
- end
-end
diff --git a/test/support/ostatus_mock.ex b/test/support/ostatus_mock.ex
index 36865ae02..9c0f2f323 100644
--- a/test/support/ostatus_mock.ex
+++ b/test/support/ostatus_mock.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OStatusMock do
import Pleroma.Factory
diff --git a/test/support/web_push_http_client_mock.ex b/test/support/web_push_http_client_mock.ex
new file mode 100644
index 000000000..d8accd21c
--- /dev/null
+++ b/test/support/web_push_http_client_mock.ex
@@ -0,0 +1,23 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.WebPushHttpClientMock do
+ def get(url, headers \\ [], options \\ []) do
+ {
+ res,
+ %Tesla.Env{status: status}
+ } = Pleroma.HTTP.request(:get, url, "", headers, options)
+
+ {res, %{status_code: status}}
+ end
+
+ def post(url, body, headers \\ [], options \\ []) do
+ {
+ res,
+ %Tesla.Env{status: status}
+ } = Pleroma.HTTP.request(:post, url, body, headers, options)
+
+ {res, %{status_code: status}}
+ end
+end
diff --git a/test/support/websocket_client.ex b/test/support/websocket_client.ex
new file mode 100644
index 000000000..121231452
--- /dev/null
+++ b/test/support/websocket_client.ex
@@ -0,0 +1,62 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Integration.WebsocketClient do
+ # https://github.com/phoenixframework/phoenix/blob/master/test/support/websocket_client.exs
+
+ @doc """
+ Starts the WebSocket server for given ws URL. Received Socket.Message's
+ are forwarded to the sender pid
+ """
+ def start_link(sender, url, headers \\ []) do
+ :crypto.start()
+ :ssl.start()
+
+ :websocket_client.start_link(
+ String.to_charlist(url),
+ __MODULE__,
+ [sender],
+ extra_headers: headers
+ )
+ end
+
+ @doc """
+ Closes the socket
+ """
+ def close(socket) do
+ send(socket, :close)
+ end
+
+ @doc """
+ Sends a low-level text message to the client.
+ """
+ def send_text(server_pid, msg) do
+ send(server_pid, {:text, msg})
+ end
+
+ @doc false
+ def init([sender], _conn_state) do
+ {:ok, %{sender: sender}}
+ end
+
+ @doc false
+ def websocket_handle(frame, _conn_state, state) do
+ send(state.sender, frame)
+ {:ok, state}
+ end
+
+ @doc false
+ def websocket_info({:text, msg}, _conn_state, state) do
+ {:reply, {:text, msg}, state}
+ end
+
+ def websocket_info(:close, _conn_state, _state) do
+ {:close, <<>>, "done"}
+ end
+
+ @doc false
+ def websocket_terminate(_reason, _conn_state, _state) do
+ :ok
+ end
+end
diff --git a/test/support/websub_mock.ex b/test/support/websub_mock.ex
new file mode 100644
index 000000000..e3d5a5b16
--- /dev/null
+++ b/test/support/websub_mock.ex
@@ -0,0 +1,9 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.WebsubMock do
+ def verify(sub) do
+ {:ok, sub}
+ end
+end
diff --git a/test/tasks/instance.exs b/test/tasks/instance.exs
new file mode 100644
index 000000000..6917a2376
--- /dev/null
+++ b/test/tasks/instance.exs
@@ -0,0 +1,62 @@
+defmodule Pleroma.InstanceTest do
+ use ExUnit.Case, async: true
+
+ setup do
+ File.mkdir_p!(tmp_path())
+ on_exit(fn -> File.rm_rf(tmp_path()) end)
+ :ok
+ end
+
+ defp tmp_path do
+ "/tmp/generated_files/"
+ end
+
+ test "running gen" do
+ mix_task = fn ->
+ Mix.Tasks.Pleroma.Instance.run([
+ "gen",
+ "--output",
+ tmp_path() <> "generated_config.exs",
+ "--output-psql",
+ tmp_path() <> "setup.psql",
+ "--domain",
+ "test.pleroma.social",
+ "--instance-name",
+ "Pleroma",
+ "--admin-email",
+ "admin@example.com",
+ "--notify-email",
+ "notify@example.com",
+ "--dbhost",
+ "dbhost",
+ "--dbname",
+ "dbname",
+ "--dbuser",
+ "dbuser",
+ "--dbpass",
+ "dbpass",
+ "--indexable",
+ "y"
+ ])
+ end
+
+ ExUnit.CaptureIO.capture_io(fn ->
+ mix_task.()
+ end)
+
+ generated_config = File.read!(tmp_path() <> "generated_config.exs")
+ assert generated_config =~ "host: \"test.pleroma.social\""
+ assert generated_config =~ "name: \"Pleroma\""
+ assert generated_config =~ "email: \"admin@example.com\""
+ assert generated_config =~ "notify_email: \"notify@example.com\""
+ assert generated_config =~ "hostname: \"dbhost\""
+ assert generated_config =~ "database: \"dbname\""
+ assert generated_config =~ "username: \"dbuser\""
+ assert generated_config =~ "password: \"dbpass\""
+ assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
+ end
+
+ defp generated_setup_psql do
+ ~s(CREATE USER dbuser WITH ENCRYPTED PASSWORD 'dbpass';\nCREATE DATABASE dbname OWNER dbuser;\n\\c dbname;\n--Extensions made by ecto.migrate that need superuser access\nCREATE EXTENSION IF NOT EXISTS citext;\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\nCREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\n)
+ end
+end
diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs
new file mode 100644
index 000000000..535dc3756
--- /dev/null
+++ b/test/tasks/relay_test.exs
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.RelayTest do
+ alias Pleroma.Activity
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Relay
+ alias Pleroma.Web.ActivityPub.Utils
+ use Pleroma.DataCase
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ Mix.shell(Mix.Shell.Process)
+
+ on_exit(fn ->
+ Mix.shell(Mix.Shell.IO)
+ end)
+
+ :ok
+ end
+
+ describe "running follow" do
+ test "relay is followed" do
+ target_instance = "http://mastodon.example.org/users/admin"
+
+ Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
+
+ local_user = Relay.get_actor()
+ assert local_user.ap_id =~ "/relay"
+
+ target_user = User.get_by_ap_id(target_instance)
+ refute target_user.local
+
+ activity = Utils.fetch_latest_follow(local_user, target_user)
+ assert activity.data["type"] == "Follow"
+ assert activity.data["actor"] == local_user.ap_id
+ assert activity.data["object"] == target_user.ap_id
+ end
+ end
+
+ describe "running unfollow" do
+ test "relay is unfollowed" do
+ target_instance = "http://mastodon.example.org/users/admin"
+
+ Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
+
+ %User{ap_id: follower_id} = local_user = Relay.get_actor()
+ target_user = User.get_by_ap_id(target_instance)
+ follow_activity = Utils.fetch_latest_follow(local_user, target_user)
+
+ Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance])
+
+ cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"])
+ assert cancelled_activity.data["state"] == "cancelled"
+
+ [undo_activity] =
+ ActivityPub.fetch_activities([], %{
+ "type" => "Undo",
+ "actor_id" => follower_id,
+ "limit" => 1,
+ "skip_preload" => true
+ })
+
+ assert undo_activity.data["type"] == "Undo"
+ assert undo_activity.data["actor"] == local_user.ap_id
+ assert undo_activity.data["object"] == cancelled_activity.data
+ end
+ end
+end
diff --git a/test/tasks/uploads_test.exs b/test/tasks/uploads_test.exs
new file mode 100644
index 000000000..b0b8eda11
--- /dev/null
+++ b/test/tasks/uploads_test.exs
@@ -0,0 +1,56 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.UploadsTest do
+ alias Pleroma.Upload
+ use Pleroma.DataCase
+
+ import Mock
+
+ setup_all do
+ Mix.shell(Mix.Shell.Process)
+
+ on_exit(fn ->
+ Mix.shell(Mix.Shell.IO)
+ end)
+
+ :ok
+ end
+
+ describe "running migrate_local" do
+ test "uploads migrated" do
+ with_mock Upload,
+ store: fn %Upload{name: _file, path: _path}, _opts -> {:ok, %{}} end do
+ Mix.Tasks.Pleroma.Uploads.run(["migrate_local", "S3"])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Migrating files from local"
+
+ assert_received {:mix_shell, :info, [message]}
+
+ assert %{"total_count" => total_count} =
+ Regex.named_captures(~r"^Found (?<total_count>\d+) uploads$", message)
+
+ assert_received {:mix_shell, :info, [message]}
+
+ # @logevery in Mix.Tasks.Pleroma.Uploads
+ count =
+ min(50, String.to_integer(total_count))
+ |> to_string()
+
+ assert %{"count" => ^count, "total_count" => ^total_count} =
+ Regex.named_captures(
+ ~r"^Uploaded (?<count>\d+)/(?<total_count>\d+) files$",
+ message
+ )
+ end
+ end
+
+ test "nonexistent uploader" do
+ assert_raise RuntimeError, ~r/The uploader .* is not an existing/, fn ->
+ Mix.Tasks.Pleroma.Uploads.run(["migrate_local", "nonexistent"])
+ end
+ end
+ end
+end
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
new file mode 100644
index 000000000..242265da5
--- /dev/null
+++ b/test/tasks/user_test.exs
@@ -0,0 +1,341 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.UserTest do
+ alias Pleroma.User
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+ import ExUnit.CaptureIO
+
+ setup_all do
+ Mix.shell(Mix.Shell.Process)
+
+ on_exit(fn ->
+ Mix.shell(Mix.Shell.IO)
+ end)
+
+ :ok
+ end
+
+ describe "running new" do
+ test "user is created" do
+ # just get random data
+ unsaved = build(:user)
+
+ # prepare to answer yes
+ send(self(), {:mix_shell_input, :yes?, true})
+
+ Mix.Tasks.Pleroma.User.run([
+ "new",
+ unsaved.nickname,
+ unsaved.email,
+ "--name",
+ unsaved.name,
+ "--bio",
+ unsaved.bio,
+ "--password",
+ "test",
+ "--moderator",
+ "--admin"
+ ])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "user will be created"
+
+ assert_received {:mix_shell, :yes?, [message]}
+ assert message =~ "Continue"
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "created"
+
+ user = User.get_by_nickname(unsaved.nickname)
+ assert user.name == unsaved.name
+ assert user.email == unsaved.email
+ assert user.bio == unsaved.bio
+ assert user.info.is_moderator
+ assert user.info.is_admin
+ end
+
+ test "user is not created" do
+ unsaved = build(:user)
+
+ # prepare to answer no
+ send(self(), {:mix_shell_input, :yes?, false})
+
+ Mix.Tasks.Pleroma.User.run(["new", unsaved.nickname, unsaved.email])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "user will be created"
+
+ assert_received {:mix_shell, :yes?, [message]}
+ assert message =~ "Continue"
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "will not be created"
+
+ refute User.get_by_nickname(unsaved.nickname)
+ end
+ end
+
+ describe "running rm" do
+ test "user is deleted" do
+ user = insert(:user)
+
+ Mix.Tasks.Pleroma.User.run(["rm", user.nickname])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ " deleted"
+
+ user = User.get_by_nickname(user.nickname)
+ assert user.info.deactivated
+ end
+
+ test "no user to delete" do
+ Mix.Tasks.Pleroma.User.run(["rm", "nonexistent"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No local user"
+ end
+ end
+
+ describe "running toggle_activated" do
+ test "user is deactivated" do
+ user = insert(:user)
+
+ Mix.Tasks.Pleroma.User.run(["toggle_activated", user.nickname])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ " deactivated"
+
+ user = User.get_by_nickname(user.nickname)
+ assert user.info.deactivated
+ end
+
+ test "user is activated" do
+ user = insert(:user, info: %{deactivated: true})
+
+ Mix.Tasks.Pleroma.User.run(["toggle_activated", user.nickname])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ " activated"
+
+ user = User.get_by_nickname(user.nickname)
+ refute user.info.deactivated
+ end
+
+ test "no user to toggle" do
+ Mix.Tasks.Pleroma.User.run(["toggle_activated", "nonexistent"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No user"
+ end
+ end
+
+ describe "running unsubscribe" do
+ test "user is unsubscribed" do
+ followed = insert(:user)
+ user = insert(:user, %{following: [User.ap_followers(followed)]})
+
+ Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Deactivating"
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Unsubscribing"
+
+ # Note that the task has delay :timer.sleep(500)
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Successfully unsubscribed"
+
+ user = User.get_by_nickname(user.nickname)
+ assert Enum.empty?(user.following)
+ assert user.info.deactivated
+ end
+
+ test "no user to unsubscribe" do
+ Mix.Tasks.Pleroma.User.run(["unsubscribe", "nonexistent"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No user"
+ end
+ end
+
+ describe "running set" do
+ test "All statuses set" do
+ user = insert(:user)
+
+ Mix.Tasks.Pleroma.User.run(["set", user.nickname, "--moderator", "--admin", "--locked"])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ ~r/Moderator status .* true/
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ ~r/Locked status .* true/
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ ~r/Admin status .* true/
+
+ user = User.get_by_nickname(user.nickname)
+ assert user.info.is_moderator
+ assert user.info.locked
+ assert user.info.is_admin
+ end
+
+ test "All statuses unset" do
+ user = insert(:user, info: %{is_moderator: true, locked: true, is_admin: true})
+
+ Mix.Tasks.Pleroma.User.run([
+ "set",
+ user.nickname,
+ "--no-moderator",
+ "--no-admin",
+ "--no-locked"
+ ])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ ~r/Moderator status .* false/
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ ~r/Locked status .* false/
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ ~r/Admin status .* false/
+
+ user = User.get_by_nickname(user.nickname)
+ refute user.info.is_moderator
+ refute user.info.locked
+ refute user.info.is_admin
+ end
+
+ test "no user to set status" do
+ Mix.Tasks.Pleroma.User.run(["set", "nonexistent", "--moderator"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No local user"
+ end
+ end
+
+ describe "running reset_password" do
+ test "password reset token is generated" do
+ user = insert(:user)
+
+ assert capture_io(fn ->
+ Mix.Tasks.Pleroma.User.run(["reset_password", user.nickname])
+ end) =~ "URL:"
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Generated"
+ end
+
+ test "no user to reset password" do
+ Mix.Tasks.Pleroma.User.run(["reset_password", "nonexistent"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No local user"
+ end
+ end
+
+ describe "running invite" do
+ test "invite token is generated" do
+ assert capture_io(fn ->
+ Mix.Tasks.Pleroma.User.run(["invite"])
+ end) =~ "http"
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Generated user invite token one time"
+ end
+
+ test "token is generated with expires_at" do
+ assert capture_io(fn ->
+ Mix.Tasks.Pleroma.User.run([
+ "invite",
+ "--expires-at",
+ Date.to_string(Date.utc_today())
+ ])
+ end)
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Generated user invite token date limited"
+ end
+
+ test "token is generated with max use" do
+ assert capture_io(fn ->
+ Mix.Tasks.Pleroma.User.run([
+ "invite",
+ "--max-use",
+ "5"
+ ])
+ end)
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Generated user invite token reusable"
+ end
+
+ test "token is generated with max use and expires date" do
+ assert capture_io(fn ->
+ Mix.Tasks.Pleroma.User.run([
+ "invite",
+ "--max-use",
+ "5",
+ "--expires-at",
+ Date.to_string(Date.utc_today())
+ ])
+ end)
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Generated user invite token reusable date limited"
+ end
+ end
+
+ describe "running invites" do
+ test "invites are listed" do
+ {:ok, invite} = Pleroma.UserInviteToken.create_invite()
+
+ {:ok, invite2} =
+ Pleroma.UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 15})
+
+ # assert capture_io(fn ->
+ Mix.Tasks.Pleroma.User.run([
+ "invites"
+ ])
+
+ # end)
+
+ assert_received {:mix_shell, :info, [message]}
+ assert_received {:mix_shell, :info, [message2]}
+ assert_received {:mix_shell, :info, [message3]}
+ assert message =~ "Invites list:"
+ assert message2 =~ invite.invite_type
+ assert message3 =~ invite2.invite_type
+ end
+ end
+
+ describe "running revoke_invite" do
+ test "invite is revoked" do
+ {:ok, invite} = Pleroma.UserInviteToken.create_invite(%{expires_at: Date.utc_today()})
+
+ assert capture_io(fn ->
+ Mix.Tasks.Pleroma.User.run([
+ "revoke_invite",
+ invite.token
+ ])
+ end)
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ "Invite for token #{invite.token} was revoked."
+ end
+ end
+
+ describe "running delete_activities" do
+ test "activities are deleted" do
+ %{nickname: nickname} = insert(:user)
+
+ assert :ok == Mix.Tasks.Pleroma.User.run(["delete_activities", nickname])
+ assert_received {:mix_shell, :info, [message]}
+ assert message == "User #{nickname} statuses deleted."
+ end
+ end
+end
diff --git a/test/test_helper.exs b/test/test_helper.exs
index 94ba68ff8..f604ba63d 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
diff --git a/test/upload_test.exs b/test/upload_test.exs
index b2ce755d2..946ebcb5a 100644
--- a/test/upload_test.exs
+++ b/test/upload_test.exs
@@ -1,24 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.UploadTest do
alias Pleroma.Upload
use Pleroma.DataCase
describe "Storing a file with the Local uploader" do
- setup do
- uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
- filters = Pleroma.Config.get([Pleroma.Upload, :filters])
-
- unless uploader == Pleroma.Uploaders.Local || filters != [] do
- Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
- Pleroma.Config.put([Pleroma.Upload, :filters], [])
-
- on_exit(fn ->
- Pleroma.Config.put([Pleroma.Upload, :uploader], uploader)
- Pleroma.Config.put([Pleroma.Upload, :filters], filters)
- end)
- end
-
- :ok
- end
+ setup [:ensure_local_uploader]
test "returns a media url" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
@@ -67,7 +56,7 @@ defmodule Pleroma.UploadTest do
assert List.first(data["url"])["href"] ==
Pleroma.Web.base_url() <>
- "/media/e7a6d0cf595bff76f14c9a98b6c199539559e8b844e02e51e5efcfd1f614a2df.jpg"
+ "/media/e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781.jpg"
end
test "copies the file to the configured folder without deduping" do
@@ -148,5 +137,36 @@ defmodule Pleroma.UploadTest do
refute data["name"] == "an [image.jpg"
end
+
+ test "escapes invalid characters in url" do
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ filename: "an… image.jpg"
+ }
+
+ {:ok, data} = Upload.store(file)
+ [attachment_url | _] = data["url"]
+
+ assert Path.basename(attachment_url["href"]) == "an%E2%80%A6%20image.jpg"
+ end
+
+ test "escapes reserved uri characters" do
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ filename: ":?#[]@!$&\\'()*+,;=.jpg"
+ }
+
+ {:ok, data} = Upload.store(file)
+ [attachment_url | _] = data["url"]
+
+ assert Path.basename(attachment_url["href"]) ==
+ "%3A%3F%23%5B%5D%40%21%24%26%5C%27%28%29%2A%2B%2C%3B%3D.jpg"
+ end
end
end
diff --git a/test/user_invite_token_test.exs b/test/user_invite_token_test.exs
new file mode 100644
index 000000000..276788254
--- /dev/null
+++ b/test/user_invite_token_test.exs
@@ -0,0 +1,96 @@
+defmodule Pleroma.UserInviteTokenTest do
+ use ExUnit.Case, async: true
+ use Pleroma.DataCase
+ alias Pleroma.UserInviteToken
+
+ describe "valid_invite?/1 one time invites" do
+ setup do
+ invite = %UserInviteToken{invite_type: "one_time"}
+
+ {:ok, invite: invite}
+ end
+
+ test "not used returns true", %{invite: invite} do
+ invite = %{invite | used: false}
+ assert UserInviteToken.valid_invite?(invite)
+ end
+
+ test "used returns false", %{invite: invite} do
+ invite = %{invite | used: true}
+ refute UserInviteToken.valid_invite?(invite)
+ end
+ end
+
+ describe "valid_invite?/1 reusable invites" do
+ setup do
+ invite = %UserInviteToken{
+ invite_type: "reusable",
+ max_use: 5
+ }
+
+ {:ok, invite: invite}
+ end
+
+ test "with less uses then max use returns true", %{invite: invite} do
+ invite = %{invite | uses: 4}
+ assert UserInviteToken.valid_invite?(invite)
+ end
+
+ test "with equal or more uses then max use returns false", %{invite: invite} do
+ invite = %{invite | uses: 5}
+
+ refute UserInviteToken.valid_invite?(invite)
+
+ invite = %{invite | uses: 6}
+
+ refute UserInviteToken.valid_invite?(invite)
+ end
+ end
+
+ describe "valid_token?/1 date limited invites" do
+ setup do
+ invite = %UserInviteToken{invite_type: "date_limited"}
+ {:ok, invite: invite}
+ end
+
+ test "expires today returns true", %{invite: invite} do
+ invite = %{invite | expires_at: Date.utc_today()}
+ assert UserInviteToken.valid_invite?(invite)
+ end
+
+ test "expires yesterday returns false", %{invite: invite} do
+ invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)}
+ invite = Repo.insert!(invite)
+ refute UserInviteToken.valid_invite?(invite)
+ end
+ end
+
+ describe "valid_token?/1 reusable date limited invites" do
+ setup do
+ invite = %UserInviteToken{invite_type: "reusable_date_limited", max_use: 5}
+ {:ok, invite: invite}
+ end
+
+ test "not overdue date and less uses returns true", %{invite: invite} do
+ invite = %{invite | expires_at: Date.utc_today(), uses: 4}
+ assert UserInviteToken.valid_invite?(invite)
+ end
+
+ test "overdue date and less uses returns false", %{invite: invite} do
+ invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)}
+ invite = Repo.insert!(invite)
+ refute UserInviteToken.valid_invite?(invite)
+ end
+
+ test "not overdue date with more uses returns false", %{invite: invite} do
+ invite = %{invite | expires_at: Date.utc_today(), uses: 5}
+ refute UserInviteToken.valid_invite?(invite)
+ end
+
+ test "overdue date with more uses returns false", %{invite: invite} do
+ invite = %{invite | expires_at: Date.add(Date.utc_today(), -1), uses: 5}
+ invite = Repo.insert!(invite)
+ refute UserInviteToken.valid_invite?(invite)
+ end
+ end
+end
diff --git a/test/user_test.exs b/test/user_test.exs
index 62104df90..d2167a970 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -1,13 +1,39 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.UserTest do
+ alias Pleroma.Activity
alias Pleroma.Builders.UserBuilder
- alias Pleroma.{User, Repo, Activity}
- alias Pleroma.Web.OStatus
- alias Pleroma.Web.Websub.WebsubClientSubscription
+ alias Pleroma.Repo
+ alias Pleroma.User
alias Pleroma.Web.CommonAPI
+
use Pleroma.DataCase
import Pleroma.Factory
- import Ecto.Query
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ describe "when tags are nil" do
+ test "tagging a user" do
+ user = insert(:user, %{tags: nil})
+ user = User.tag(user, ["cool", "dude"])
+
+ assert "cool" in user.tags
+ assert "dude" in user.tags
+ end
+
+ test "untagging a user" do
+ user = insert(:user, %{tags: nil})
+ user = User.untag(user, ["cool", "dude"])
+
+ assert user.tags == []
+ end
+ end
test "ap_id returns the activity pub id for the user" do
user = UserBuilder.build()
@@ -25,13 +51,78 @@ defmodule Pleroma.UserTest do
assert expected_followers_collection == User.ap_followers(user)
end
+ test "returns all pending follow requests" do
+ unlocked = insert(:user)
+ locked = insert(:user, %{info: %{locked: true}})
+ follower = insert(:user)
+
+ Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => unlocked.id})
+ Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => locked.id})
+
+ assert {:ok, []} = User.get_follow_requests(unlocked)
+ assert {:ok, [activity]} = User.get_follow_requests(locked)
+
+ assert activity
+ end
+
+ test "doesn't return already accepted or duplicate follow requests" do
+ locked = insert(:user, %{info: %{locked: true}})
+ pending_follower = insert(:user)
+ accepted_follower = insert(:user)
+
+ Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
+ Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
+ Pleroma.Web.TwitterAPI.TwitterAPI.follow(accepted_follower, %{"user_id" => locked.id})
+ User.maybe_follow(accepted_follower, locked)
+
+ assert {:ok, [activity]} = User.get_follow_requests(locked)
+ assert activity
+ end
+
+ test "follow_all follows mutliple users" do
+ user = insert(:user)
+ followed_zero = insert(:user)
+ followed_one = insert(:user)
+ followed_two = insert(:user)
+ blocked = insert(:user)
+ not_followed = insert(:user)
+ reverse_blocked = insert(:user)
+
+ {:ok, user} = User.block(user, blocked)
+ {:ok, reverse_blocked} = User.block(reverse_blocked, user)
+
+ {:ok, user} = User.follow(user, followed_zero)
+
+ {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked])
+
+ assert User.following?(user, followed_one)
+ assert User.following?(user, followed_two)
+ assert User.following?(user, followed_zero)
+ refute User.following?(user, not_followed)
+ refute User.following?(user, blocked)
+ refute User.following?(user, reverse_blocked)
+ end
+
+ test "follow_all follows mutliple users without duplicating" do
+ user = insert(:user)
+ followed_zero = insert(:user)
+ followed_one = insert(:user)
+ followed_two = insert(:user)
+
+ {:ok, user} = User.follow_all(user, [followed_zero, followed_one])
+ assert length(user.following) == 3
+
+ {:ok, user} = User.follow_all(user, [followed_one, followed_two])
+ assert length(user.following) == 4
+ end
+
test "follow takes a user and another user" do
user = insert(:user)
followed = insert(:user)
{:ok, user} = User.follow(user, followed)
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
followed = User.get_by_ap_id(followed.ap_id)
assert followed.info.follower_count == 1
@@ -55,6 +146,15 @@ defmodule Pleroma.UserTest do
{:error, _} = User.follow(blockee, blocker)
end
+ test "can't subscribe to a user who blocked us" do
+ blocker = insert(:user)
+ blocked = insert(:user)
+
+ {:ok, blocker} = User.block(blocker, blocked)
+
+ {:error, _} = User.subscribe(blocked, blocker)
+ end
+
test "local users do not automatically follow local locked accounts" do
follower = insert(:user, info: %{locked: true})
followed = insert(:user, info: %{locked: true})
@@ -87,7 +187,7 @@ defmodule Pleroma.UserTest do
{:ok, user, _activity} = User.unfollow(user, followed)
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
assert user.following == []
end
@@ -97,7 +197,7 @@ defmodule Pleroma.UserTest do
{:error, _} = User.unfollow(user, user)
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
assert user.following == [user.ap_id]
end
@@ -109,6 +209,13 @@ defmodule Pleroma.UserTest do
refute User.following?(followed, user)
end
+ test "fetches correct profile for nickname beginning with number" do
+ # Use old-style integer ID to try to reproduce the problem
+ user = insert(:user, %{id: 1080})
+ userwithnumbers = insert(:user, %{nickname: "#{user.id}garbage"})
+ assert userwithnumbers == User.get_cached_by_nickname_or_id(userwithnumbers.nickname)
+ end
+
describe "user registration" do
@full_user_data %{
bio: "A guy",
@@ -119,6 +226,43 @@ defmodule Pleroma.UserTest do
email: "email@example.com"
}
+ test "it autofollows accounts that are set for it" do
+ user = insert(:user)
+ remote_user = insert(:user, %{local: false})
+
+ Pleroma.Config.put([:instance, :autofollowed_nicknames], [
+ user.nickname,
+ remote_user.nickname
+ ])
+
+ cng = User.register_changeset(%User{}, @full_user_data)
+
+ {:ok, registered_user} = User.register(cng)
+
+ assert User.following?(registered_user, user)
+ refute User.following?(registered_user, remote_user)
+
+ Pleroma.Config.put([:instance, :autofollowed_nicknames], [])
+ end
+
+ test "it sends a welcome message if it is set" do
+ welcome_user = insert(:user)
+
+ Pleroma.Config.put([:instance, :welcome_user_nickname], welcome_user.nickname)
+ Pleroma.Config.put([:instance, :welcome_message], "Hello, this is a cool site")
+
+ cng = User.register_changeset(%User{}, @full_user_data)
+ {:ok, registered_user} = User.register(cng)
+
+ activity = Repo.one(Pleroma.Activity)
+ assert registered_user.ap_id in activity.recipients
+ assert activity.data["object"]["content"] =~ "cool site"
+ assert activity.actor == welcome_user.ap_id
+
+ Pleroma.Config.put([:instance, :welcome_user_nickname], nil)
+ Pleroma.Config.put([:instance, :welcome_message], nil)
+ end
+
test "it requires an email, name, nickname and password, bio is optional" do
@full_user_data
|> Map.keys()
@@ -130,6 +274,20 @@ defmodule Pleroma.UserTest do
end)
end
+ test "it restricts certain nicknames" do
+ [restricted_name | _] = Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
+
+ assert is_bitstring(restricted_name)
+
+ params =
+ @full_user_data
+ |> Map.put(:nickname, restricted_name)
+
+ changeset = User.register_changeset(%User{}, params)
+
+ refute changeset.valid?
+ end
+
test "it sets the password_hash, ap_id and following fields" do
changeset = User.register_changeset(%User{}, @full_user_data)
@@ -144,6 +302,86 @@ defmodule Pleroma.UserTest do
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
end
+
+ test "it ensures info is not nil" do
+ changeset = User.register_changeset(%User{}, @full_user_data)
+
+ assert changeset.valid?
+
+ {:ok, user} =
+ changeset
+ |> Repo.insert()
+
+ refute is_nil(user.info)
+ end
+ end
+
+ describe "user registration, with :account_activation_required" do
+ @full_user_data %{
+ bio: "A guy",
+ name: "my name",
+ nickname: "nick",
+ password: "test",
+ password_confirmation: "test",
+ email: "email@example.com"
+ }
+
+ setup do
+ setting = Pleroma.Config.get([:instance, :account_activation_required])
+
+ unless setting do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+ on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
+ end
+
+ :ok
+ end
+
+ test "it creates unconfirmed user" do
+ changeset = User.register_changeset(%User{}, @full_user_data)
+ assert changeset.valid?
+
+ {:ok, user} = Repo.insert(changeset)
+
+ assert user.info.confirmation_pending
+ assert user.info.confirmation_token
+ end
+
+ test "it creates confirmed user if :confirmed option is given" do
+ changeset = User.register_changeset(%User{}, @full_user_data, confirmed: true)
+ assert changeset.valid?
+
+ {:ok, user} = Repo.insert(changeset)
+
+ refute user.info.confirmation_pending
+ refute user.info.confirmation_token
+ end
+ end
+
+ describe "get_or_fetch/1" do
+ test "gets an existing user by nickname" do
+ user = insert(:user)
+ fetched_user = User.get_or_fetch(user.nickname)
+
+ assert user == fetched_user
+ end
+
+ test "gets an existing user by ap_id" do
+ ap_id = "http://mastodon.example.org/users/admin"
+
+ user =
+ insert(
+ :user,
+ local: false,
+ nickname: "admin@mastodon.example.org",
+ ap_id: ap_id,
+ info: %{}
+ )
+
+ fetched_user = User.get_or_fetch(ap_id)
+ freshed_user = refresh_record(user)
+ assert freshed_user == fetched_user
+ end
end
describe "fetching a user from nickname or trying to build one" do
@@ -161,6 +399,24 @@ defmodule Pleroma.UserTest do
assert user == fetched_user
end
+ test "gets an existing user by fully qualified nickname" do
+ user = insert(:user)
+
+ fetched_user =
+ User.get_or_fetch_by_nickname(user.nickname <> "@" <> Pleroma.Web.Endpoint.host())
+
+ assert user == fetched_user
+ end
+
+ test "gets an existing user by fully qualified nickname, case insensitive" do
+ user = insert(:user, nickname: "nick")
+ casing_altered_fqn = String.upcase(user.nickname <> "@" <> Pleroma.Web.Endpoint.host())
+
+ fetched_user = User.get_or_fetch_by_nickname(casing_altered_fqn)
+
+ assert user == fetched_user
+ end
+
test "fetches an external user via ostatus if no user exists" do
fetched_user = User.get_or_fetch_by_nickname("shp@social.heldscal.la")
assert fetched_user.nickname == "shp@social.heldscal.la"
@@ -368,6 +624,44 @@ defmodule Pleroma.UserTest do
end
end
+ describe "follow_import" do
+ test "it imports user followings from list" do
+ [user1, user2, user3] = insert_list(3, :user)
+
+ identifiers = [
+ user2.ap_id,
+ user3.nickname
+ ]
+
+ result = User.follow_import(user1, identifiers)
+ assert is_list(result)
+ assert result == [user2, user3]
+ end
+ end
+
+ describe "mutes" do
+ test "it mutes people" do
+ user = insert(:user)
+ muted_user = insert(:user)
+
+ refute User.mutes?(user, muted_user)
+
+ {:ok, user} = User.mute(user, muted_user)
+
+ assert User.mutes?(user, muted_user)
+ end
+
+ test "it unmutes users" do
+ user = insert(:user)
+ muted_user = insert(:user)
+
+ {:ok, user} = User.mute(user, muted_user)
+ {:ok, user} = User.unmute(user, muted_user)
+
+ refute User.mutes?(user, muted_user)
+ end
+ end
+
describe "blocks" do
test "it blocks people" do
user = insert(:user)
@@ -401,7 +695,7 @@ defmodule Pleroma.UserTest do
assert User.following?(blocked, blocker)
{:ok, blocker} = User.block(blocker, blocked)
- blocked = Repo.get(User, blocked.id)
+ blocked = User.get_by_id(blocked.id)
assert User.blocks?(blocker, blocked)
@@ -419,7 +713,7 @@ defmodule Pleroma.UserTest do
refute User.following?(blocked, blocker)
{:ok, blocker} = User.block(blocker, blocked)
- blocked = Repo.get(User, blocked.id)
+ blocked = User.get_by_id(blocked.id)
assert User.blocks?(blocker, blocked)
@@ -437,13 +731,29 @@ defmodule Pleroma.UserTest do
assert User.following?(blocked, blocker)
{:ok, blocker} = User.block(blocker, blocked)
- blocked = Repo.get(User, blocked.id)
+ blocked = User.get_by_id(blocked.id)
assert User.blocks?(blocker, blocked)
refute User.following?(blocker, blocked)
refute User.following?(blocked, blocker)
end
+
+ test "blocks tear down blocked->blocker subscription relationships" do
+ blocker = insert(:user)
+ blocked = insert(:user)
+
+ {:ok, blocker} = User.subscribe(blocked, blocker)
+
+ assert User.subscribed_to?(blocked, blocker)
+ refute User.subscribed_to?(blocker, blocked)
+
+ {:ok, blocker} = User.block(blocker, blocked)
+
+ assert User.blocks?(blocker, blocked)
+ refute User.subscribed_to?(blocker, blocked)
+ refute User.subscribed_to?(blocked, blocker)
+ end
end
describe "domain blocking" do
@@ -467,6 +777,21 @@ defmodule Pleroma.UserTest do
end
end
+ describe "blocks_import" do
+ test "it imports user blocks from list" do
+ [user1, user2, user3] = insert_list(3, :user)
+
+ identifiers = [
+ user2.ap_id,
+ user3.nickname
+ ]
+
+ result = User.blocks_import(user1, identifiers)
+ assert is_list(result)
+ assert result == [user2, user3]
+ end
+ end
+
test "get recipients from activity" do
actor = insert(:user)
user = insert(:user, local: true)
@@ -479,12 +804,13 @@ defmodule Pleroma.UserTest do
"status" => "hey @#{addressed.nickname} @#{addressed_remote.nickname}"
})
- assert [addressed] == User.get_recipients_from_activity(activity)
+ assert Enum.map([actor, addressed], & &1.ap_id) --
+ Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == []
{:ok, user} = User.follow(user, actor)
{:ok, _user_two} = User.follow(user_two, actor)
recipients = User.get_recipients_from_activity(activity)
- assert length(recipients) == 2
+ assert length(recipients) == 3
assert user in recipients
assert addressed in recipients
end
@@ -498,6 +824,16 @@ defmodule Pleroma.UserTest do
assert false == user.info.deactivated
end
+ test ".delete_user_activities deletes all create activities" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
+ {:ok, _} = User.delete_user_activities(user)
+
+ # TODO: Remove favorites, repeats, delete activities.
+ refute Activity.get_by_id(activity.id)
+ end
+
test ".delete deactivates a user, all follow relationships and all create activities" do
user = insert(:user)
followed = insert(:user)
@@ -515,9 +851,9 @@ defmodule Pleroma.UserTest do
{:ok, _} = User.delete(user)
- followed = Repo.get(User, followed.id)
- follower = Repo.get(User, follower.id)
- user = Repo.get(User, user.id)
+ followed = User.get_by_id(followed.id)
+ follower = User.get_by_id(follower.id)
+ user = User.get_by_id(user.id)
assert user.info.deactivated
@@ -526,7 +862,7 @@ defmodule Pleroma.UserTest do
# TODO: Remove favorites, repeats, delete activities.
- refute Repo.get(Activity, activity.id)
+ refute Activity.get_by_id(activity.id)
end
test "get_public_key_for_ap_id fetches a user that's not in the db" do
@@ -541,10 +877,10 @@ defmodule Pleroma.UserTest do
end
describe "per-user rich-text filtering" do
- test "html_filter_policy returns nil when rich-text is enabled" do
+ test "html_filter_policy returns default policies, when rich-text is enabled" do
user = insert(:user)
- assert nil == User.html_filter_policy(user)
+ assert Pleroma.Config.get([:markup, :scrub_policy]) == User.html_filter_policy(user)
end
test "html_filter_policy returns TwitterText scrubber when rich-text is disabled" do
@@ -557,7 +893,7 @@ defmodule Pleroma.UserTest do
describe "caching" do
test "invalidate_cache works" do
user = insert(:user)
- user_info = User.get_cached_user_info(user)
+ _user_info = User.get_cached_user_info(user)
User.invalidate_cache(user)
@@ -582,14 +918,253 @@ defmodule Pleroma.UserTest do
end
describe "User.search" do
- test "finds a user, ranking by similarity" do
- user = insert(:user, %{name: "lain"})
- user_two = insert(:user, %{name: "ean"})
- user_three = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social"})
- user_four = insert(:user, %{nickname: "lain@pleroma.soykaf.com"})
+ test "finds a user by full or partial nickname" do
+ user = insert(:user, %{nickname: "john"})
+
+ Enum.each(["john", "jo", "j"], fn query ->
+ assert user ==
+ User.search(query)
+ |> List.first()
+ |> Map.put(:search_rank, nil)
+ |> Map.put(:search_type, nil)
+ end)
+ end
+
+ test "finds a user by full or partial name" do
+ user = insert(:user, %{name: "John Doe"})
+
+ Enum.each(["John Doe", "JOHN", "doe", "j d", "j", "d"], fn query ->
+ assert user ==
+ User.search(query)
+ |> List.first()
+ |> Map.put(:search_rank, nil)
+ |> Map.put(:search_type, nil)
+ end)
+ end
+
+ test "finds users, preferring nickname matches over name matches" do
+ u1 = insert(:user, %{name: "lain", nickname: "nick1"})
+ u2 = insert(:user, %{nickname: "lain", name: "nick1"})
+
+ assert [u2.id, u1.id] == Enum.map(User.search("lain"), & &1.id)
+ end
+
+ test "finds users, considering density of matched tokens" do
+ u1 = insert(:user, %{name: "Bar Bar plus Word Word"})
+ u2 = insert(:user, %{name: "Word Word Bar Bar Bar"})
+
+ assert [u2.id, u1.id] == Enum.map(User.search("bar word"), & &1.id)
+ end
+
+ test "finds users, ranking by similarity" do
+ u1 = insert(:user, %{name: "lain"})
+ _u2 = insert(:user, %{name: "ean"})
+ u3 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social"})
+ u4 = insert(:user, %{nickname: "lain@pleroma.soykaf.com"})
+
+ assert [u4.id, u3.id, u1.id] == Enum.map(User.search("lain@ple"), & &1.id)
+ end
+
+ test "finds users, handling misspelled requests" do
+ u1 = insert(:user, %{name: "lain"})
+
+ assert [u1.id] == Enum.map(User.search("laiin"), & &1.id)
+ end
+
+ test "finds users, boosting ranks of friends and followers" do
+ u1 = insert(:user)
+ u2 = insert(:user, %{name: "Doe"})
+ follower = insert(:user, %{name: "Doe"})
+ friend = insert(:user, %{name: "Doe"})
+
+ {:ok, follower} = User.follow(follower, u1)
+ {:ok, u1} = User.follow(u1, friend)
+
+ assert [friend.id, follower.id, u2.id] --
+ Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
+ end
+
+ test "finds a user whose name is nil" do
+ _user = insert(:user, %{name: "notamatch", nickname: "testuser@pleroma.amplifie.red"})
+ user_two = insert(:user, %{name: nil, nickname: "lain@pleroma.soykaf.com"})
+
+ assert user_two ==
+ User.search("lain@pleroma.soykaf.com")
+ |> List.first()
+ |> Map.put(:search_rank, nil)
+ |> Map.put(:search_type, nil)
+ end
+
+ test "does not yield false-positive matches" do
+ insert(:user, %{name: "John Doe"})
+
+ Enum.each(["mary", "a", ""], fn query ->
+ assert [] == User.search(query)
+ end)
+ end
+
+ test "works with URIs" do
+ results = User.search("http://mastodon.example.org/users/admin", resolve: true)
+ result = results |> List.first()
+
+ user = User.get_by_ap_id("http://mastodon.example.org/users/admin")
+
+ assert length(results) == 1
+ assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
+ end
+ end
- assert user_four ==
- User.search("lain@ple") |> List.first() |> Map.put(:search_distance, nil)
+ test "auth_active?/1 works correctly" do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+
+ local_user = insert(:user, local: true, info: %{confirmation_pending: true})
+ confirmed_user = insert(:user, local: true, info: %{confirmation_pending: false})
+ remote_user = insert(:user, local: false)
+
+ refute User.auth_active?(local_user)
+ assert User.auth_active?(confirmed_user)
+ assert User.auth_active?(remote_user)
+
+ Pleroma.Config.put([:instance, :account_activation_required], false)
+ end
+
+ describe "superuser?/1" do
+ test "returns false for unprivileged users" do
+ user = insert(:user, local: true)
+
+ refute User.superuser?(user)
+ end
+
+ test "returns false for remote users" do
+ user = insert(:user, local: false)
+ remote_admin_user = insert(:user, local: false, info: %{is_admin: true})
+
+ refute User.superuser?(user)
+ refute User.superuser?(remote_admin_user)
+ end
+
+ test "returns true for local moderators" do
+ user = insert(:user, local: true, info: %{is_moderator: true})
+
+ assert User.superuser?(user)
+ end
+
+ test "returns true for local admins" do
+ user = insert(:user, local: true, info: %{is_admin: true})
+
+ assert User.superuser?(user)
+ end
+ end
+
+ describe "visible_for?/2" do
+ test "returns true when the account is itself" do
+ user = insert(:user, local: true)
+
+ assert User.visible_for?(user, user)
+ end
+
+ test "returns false when the account is unauthenticated and auth is required" do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+
+ user = insert(:user, local: true, info: %{confirmation_pending: true})
+ other_user = insert(:user, local: true)
+
+ refute User.visible_for?(user, other_user)
+
+ Pleroma.Config.put([:instance, :account_activation_required], false)
+ end
+
+ test "returns true when the account is unauthenticated and auth is not required" do
+ user = insert(:user, local: true, info: %{confirmation_pending: true})
+ other_user = insert(:user, local: true)
+
+ assert User.visible_for?(user, other_user)
+ end
+
+ test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+
+ user = insert(:user, local: true, info: %{confirmation_pending: true})
+ other_user = insert(:user, local: true, info: %{is_admin: true})
+
+ assert User.visible_for?(user, other_user)
+
+ Pleroma.Config.put([:instance, :account_activation_required], false)
end
end
+
+ describe "parse_bio/2" do
+ test "preserves hosts in user links text" do
+ remote_user = insert(:user, local: false, nickname: "nick@domain.com")
+ user = insert(:user)
+ bio = "A.k.a. @nick@domain.com"
+
+ expected_text =
+ "A.k.a. <span class='h-card'><a data-user='#{remote_user.id}' class='u-url mention' href='#{
+ remote_user.ap_id
+ }'>" <> "@<span>nick@domain.com</span></a></span>"
+
+ assert expected_text == User.parse_bio(bio, user)
+ end
+
+ test "Adds rel=me on linkbacked urls" do
+ user = insert(:user, ap_id: "http://social.example.org/users/lain")
+
+ bio = "http://example.org/rel_me/null"
+ expected_text = "<a href=\"#{bio}\">#{bio}</a>"
+ assert expected_text == User.parse_bio(bio, user)
+
+ bio = "http://example.org/rel_me/link"
+ expected_text = "<a href=\"#{bio}\">#{bio}</a>"
+ assert expected_text == User.parse_bio(bio, user)
+
+ bio = "http://example.org/rel_me/anchor"
+ expected_text = "<a href=\"#{bio}\">#{bio}</a>"
+ assert expected_text == User.parse_bio(bio, user)
+ end
+ end
+
+ test "bookmarks" do
+ user = insert(:user)
+
+ {:ok, activity1} =
+ CommonAPI.post(user, %{
+ "status" => "heweoo!"
+ })
+
+ id1 = activity1.data["object"]["id"]
+
+ {:ok, activity2} =
+ CommonAPI.post(user, %{
+ "status" => "heweoo!"
+ })
+
+ id2 = activity2.data["object"]["id"]
+
+ assert {:ok, user_state1} = User.bookmark(user, id1)
+ assert user_state1.bookmarks == [id1]
+
+ assert {:ok, user_state2} = User.unbookmark(user, id1)
+ assert user_state2.bookmarks == []
+
+ assert {:ok, user_state3} = User.bookmark(user, id2)
+ assert user_state3.bookmarks == [id2]
+ end
+
+ test "follower count is updated when a follower is blocked" do
+ user = insert(:user)
+ follower = insert(:user)
+ follower2 = insert(:user)
+ follower3 = insert(:user)
+
+ {:ok, follower} = Pleroma.User.follow(follower, user)
+ {:ok, _follower2} = Pleroma.User.follow(follower2, user)
+ {:ok, _follower3} = Pleroma.User.follow(follower3, user)
+
+ {:ok, _} = Pleroma.User.block(user, follower)
+
+ user_show = Pleroma.Web.TwitterAPI.UserView.render("show.json", %{user: user})
+
+ assert Map.get(user_show, "followers_count") == 2
+ end
end
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 1c24b348c..7b1c60f15 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -1,9 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
- alias Pleroma.Web.ActivityPub.{UserView, ObjectView}
- alias Pleroma.{Repo, User}
alias Pleroma.Activity
+ alias Pleroma.Instances
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ObjectView
+ alias Pleroma.Web.ActivityPub.UserView
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
describe "/relay" do
test "with the relay active, it returns the relay user", %{conn: conn} do
@@ -18,17 +30,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
test "with the relay disabled, it returns 404", %{conn: conn} do
Pleroma.Config.put([:instance, :allow_relay], false)
- res =
- conn
- |> get(activity_pub_path(conn, :relay))
- |> json_response(404)
+ conn
+ |> get(activity_pub_path(conn, :relay))
+ |> json_response(404)
+ |> assert
Pleroma.Config.put([:instance, :allow_relay], true)
end
end
describe "/users/:nickname" do
- test "it returns a json representation of the user", %{conn: conn} do
+ test "it returns a json representation of the user with accept application/json", %{
+ conn: conn
+ } do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/users/#{user.nickname}")
+
+ user = User.get_by_id(user.id)
+
+ assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
+ end
+
+ test "it returns a json representation of the user with accept application/activity+json", %{
+ conn: conn
+ } do
user = insert(:user)
conn =
@@ -36,14 +65,47 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}")
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
+
+ assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
+ end
+
+ test "it returns a json representation of the user with accept application/ld+json", %{
+ conn: conn
+ } do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header(
+ "accept",
+ "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+ )
+ |> get("/users/#{user.nickname}")
+
+ user = User.get_by_id(user.id)
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
end
end
describe "/object/:uuid" do
- test "it returns a json representation of the object", %{conn: conn} do
+ test "it returns a json representation of the object with accept application/json", %{
+ conn: conn
+ } do
+ note = insert(:note)
+ uuid = String.split(note.data["id"], "/") |> List.last()
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/objects/#{uuid}")
+
+ assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
+ end
+
+ test "it returns a json representation of the object with accept application/activity+json",
+ %{conn: conn} do
note = insert(:note)
uuid = String.split(note.data["id"], "/") |> List.last()
@@ -55,6 +117,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
end
+ test "it returns a json representation of the object with accept application/ld+json", %{
+ conn: conn
+ } do
+ note = insert(:note)
+ uuid = String.split(note.data["id"], "/") |> List.last()
+
+ conn =
+ conn
+ |> put_req_header(
+ "accept",
+ "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+ )
+ |> get("/objects/#{uuid}")
+
+ assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
+ end
+
test "it returns 404 for non-public messages", %{conn: conn} do
note = insert(:direct_note)
uuid = String.split(note.data["id"], "/") |> List.last()
@@ -66,6 +145,59 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert json_response(conn, 404)
end
+
+ test "it returns 404 for tombstone objects", %{conn: conn} do
+ tombstone = insert(:tombstone)
+ uuid = String.split(tombstone.data["id"], "/") |> List.last()
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/objects/#{uuid}")
+
+ assert json_response(conn, 404)
+ end
+ end
+
+ describe "/object/:uuid/likes" do
+ test "it returns the like activities in a collection", %{conn: conn} do
+ like = insert(:like_activity)
+ uuid = String.split(like.data["object"], "/") |> List.last()
+
+ result =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/objects/#{uuid}/likes")
+ |> json_response(200)
+
+ assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
+ end
+ end
+
+ describe "/activities/:uuid" do
+ test "it returns a json representation of the activity", %{conn: conn} do
+ activity = insert(:note_activity)
+ uuid = String.split(activity.data["id"], "/") |> List.last()
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/activities/#{uuid}")
+
+ assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
+ end
+
+ test "it returns 404 for non-public activities", %{conn: conn} do
+ activity = insert(:direct_note_activity)
+ uuid = String.split(activity.data["id"], "/") |> List.last()
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/activities/#{uuid}")
+
+ assert json_response(conn, 404)
+ end
end
describe "/inbox" do
@@ -82,6 +214,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
:timer.sleep(500)
assert Activity.get_by_ap_id(data["id"])
end
+
+ test "it clears `unreachable` federation status of the sender", %{conn: conn} do
+ data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
+
+ sender_url = data["actor"]
+ Instances.set_consistently_unreachable(sender_url)
+ refute Instances.reachable?(sender_url)
+
+ conn =
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/inbox", data)
+
+ assert "ok" == json_response(conn, 200)
+ assert Instances.reachable?(sender_url)
+ end
end
describe "/users/:nickname/inbox" do
@@ -103,9 +252,99 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
:timer.sleep(500)
assert Activity.get_by_ap_id(data["id"])
end
+
+ test "it accepts messages from actors that are followed by the user", %{conn: conn} do
+ recipient = insert(:user)
+ actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
+
+ {:ok, recipient} = User.follow(recipient, actor)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+
+ object =
+ data["object"]
+ |> Map.put("attributedTo", actor.ap_id)
+
+ data =
+ data
+ |> Map.put("actor", actor.ap_id)
+ |> Map.put("object", object)
+
+ conn =
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{recipient.nickname}/inbox", data)
+
+ assert "ok" == json_response(conn, 200)
+ :timer.sleep(500)
+ assert Activity.get_by_ap_id(data["id"])
+ end
+
+ test "it rejects reads from other users", %{conn: conn} do
+ user = insert(:user)
+ otheruser = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, otheruser)
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/users/#{user.nickname}/inbox")
+
+ assert json_response(conn, 403)
+ end
+
+ test "it returns a note activity in a collection", %{conn: conn} do
+ note_activity = insert(:direct_note_activity)
+ user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/users/#{user.nickname}/inbox")
+
+ assert response(conn, 200) =~ note_activity.data["object"]["content"]
+ end
+
+ test "it clears `unreachable` federation status of the sender", %{conn: conn} do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Map.put("bcc", [user.ap_id])
+
+ sender_host = URI.parse(data["actor"]).host
+ Instances.set_consistently_unreachable(sender_host)
+ refute Instances.reachable?(sender_host)
+
+ conn =
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/inbox", data)
+
+ assert "ok" == json_response(conn, 200)
+ assert Instances.reachable?(sender_host)
+ end
end
describe "/users/:nickname/outbox" do
+ test "it will not bomb when there is no activity", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/users/#{user.nickname}/outbox")
+
+ result = json_response(conn, 200)
+ assert user.ap_id <> "/outbox" == result["id"]
+ end
+
test "it returns a note activity in a collection", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
@@ -129,6 +368,121 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert response(conn, 200) =~ announce_activity.data["object"]
end
+
+ test "it rejects posts from other users", %{conn: conn} do
+ data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
+ user = insert(:user)
+ otheruser = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, otheruser)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/outbox", data)
+
+ assert json_response(conn, 403)
+ end
+
+ test "it inserts an incoming create activity into the database", %{conn: conn} do
+ data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/outbox", data)
+
+ result = json_response(conn, 201)
+ assert Activity.get_by_ap_id(result["id"])
+ end
+
+ test "it rejects an incoming activity with bogus type", %{conn: conn} do
+ data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
+ user = insert(:user)
+
+ data =
+ data
+ |> Map.put("type", "BadType")
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/outbox", data)
+
+ assert json_response(conn, 400)
+ end
+
+ test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ data = %{
+ type: "Delete",
+ object: %{
+ id: note_activity.data["object"]["id"]
+ }
+ }
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/outbox", data)
+
+ result = json_response(conn, 201)
+ assert Activity.get_by_ap_id(result["id"])
+
+ object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+ assert object
+ assert object.data["type"] == "Tombstone"
+ end
+
+ test "it rejects delete activity of object from other actor", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = insert(:user)
+
+ data = %{
+ type: "Delete",
+ object: %{
+ id: note_activity.data["object"]["id"]
+ }
+ }
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/outbox", data)
+
+ assert json_response(conn, 400)
+ end
+
+ test "it increases like count when receiving a like action", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ data = %{
+ type: "Like",
+ object: %{
+ id: note_activity.data["object"]["id"]
+ }
+ }
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/outbox", data)
+
+ result = json_response(conn, 201)
+ assert Activity.get_by_ap_id(result["id"])
+
+ object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+ assert object
+ assert object.data["like_count"] == 1
+ end
end
describe "/users/:nickname/followers" do
@@ -145,6 +499,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert result["first"]["orderedItems"] == [user.ap_id]
end
+ test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} do
+ user = insert(:user)
+ user_two = insert(:user, %{info: %{hide_followers: true}})
+ User.follow(user, user_two)
+
+ result =
+ conn
+ |> get("/users/#{user_two.nickname}/followers")
+ |> json_response(200)
+
+ assert result["first"]["orderedItems"] == []
+ assert result["totalItems"] == 0
+ end
+
test "it works for more than 10 users", %{conn: conn} do
user = insert(:user)
@@ -186,11 +554,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert result["first"]["orderedItems"] == [user_two.ap_id]
end
+ test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn} do
+ user = insert(:user, %{info: %{hide_follows: true}})
+ user_two = insert(:user)
+ User.follow(user, user_two)
+
+ result =
+ conn
+ |> get("/users/#{user.nickname}/following")
+ |> json_response(200)
+
+ assert result["first"]["orderedItems"] == []
+ assert result["totalItems"] == 0
+ end
+
test "it works for more than 10 users", %{conn: conn} do
user = insert(:user)
Enum.each(1..15, fn _ ->
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
other_user = insert(:user)
User.follow(user, other_user)
end)
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index bc9fcc75d..68bfb3858 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -1,12 +1,70 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
use Pleroma.DataCase
+ alias Pleroma.Activity
+ alias Pleroma.Builders.ActivityBuilder
+ alias Pleroma.Instances
+ alias Pleroma.Object
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
- alias Pleroma.{Activity, Object, User}
- alias Pleroma.Builders.ActivityBuilder
import Pleroma.Factory
+ import Tesla.Mock
+ import Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ describe "fetching restricted by visibility" do
+ test "it restricts by the appropriate visibility" do
+ user = insert(:user)
+
+ {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
+
+ {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
+
+ {:ok, unlisted_activity} =
+ CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
+
+ {:ok, private_activity} =
+ CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
+
+ activities =
+ ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id})
+
+ assert activities == [direct_activity]
+
+ activities =
+ ActivityPub.fetch_activities([], %{:visibility => "unlisted", "actor_id" => user.ap_id})
+
+ assert activities == [unlisted_activity]
+
+ activities =
+ ActivityPub.fetch_activities([], %{:visibility => "private", "actor_id" => user.ap_id})
+
+ assert activities == [private_activity]
+
+ activities =
+ ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id})
+
+ assert activities == [public_activity]
+
+ activities =
+ ActivityPub.fetch_activities([], %{
+ :visibility => ~w[private public],
+ "actor_id" => user.ap_id
+ })
+
+ assert activities == [public_activity, private_activity]
+ end
+ end
describe "building a user from his ap id" do
test "it returns a user" do
@@ -18,14 +76,71 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert user.info.ap_enabled
assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
end
+
+ test "it fetches the appropriate tag-restricted posts" do
+ user = insert(:user)
+
+ {:ok, status_one} = CommonAPI.post(user, %{"status" => ". #test"})
+ {:ok, status_two} = CommonAPI.post(user, %{"status" => ". #essais"})
+ {:ok, status_three} = CommonAPI.post(user, %{"status" => ". #test #reject"})
+
+ fetch_one = ActivityPub.fetch_activities([], %{"tag" => "test"})
+ fetch_two = ActivityPub.fetch_activities([], %{"tag" => ["test", "essais"]})
+
+ fetch_three =
+ ActivityPub.fetch_activities([], %{
+ "tag" => ["test", "essais"],
+ "tag_reject" => ["reject"]
+ })
+
+ fetch_four =
+ ActivityPub.fetch_activities([], %{
+ "tag" => ["test"],
+ "tag_all" => ["test", "reject"]
+ })
+
+ assert fetch_one == [status_one, status_three]
+ assert fetch_two == [status_one, status_two, status_three]
+ assert fetch_three == [status_one, status_two]
+ assert fetch_four == [status_three]
+ end
end
describe "insertion" do
+ test "drops activities beyond a certain limit" do
+ limit = Pleroma.Config.get([:instance, :remote_limit])
+
+ random_text =
+ :crypto.strong_rand_bytes(limit + 1)
+ |> Base.encode64()
+ |> binary_part(0, limit + 1)
+
+ data = %{
+ "ok" => true,
+ "object" => %{
+ "content" => random_text
+ }
+ }
+
+ assert {:error, {:remote_limit_error, _}} = ActivityPub.insert(data)
+ end
+
+ test "doesn't drop activities with content being null" do
+ data = %{
+ "ok" => true,
+ "object" => %{
+ "content" => nil
+ }
+ }
+
+ assert {:ok, _} = ActivityPub.insert(data)
+ end
+
test "returns the activity if one with the same id is already in" do
activity = insert(:note_activity)
{:ok, new_activity} = ActivityPub.insert(activity.data)
- assert activity == new_activity
+ assert activity.id == new_activity.id
end
test "inserts a given map into the activity database, giving it an id if it has none." do
@@ -102,7 +217,59 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert activity.data["to"] == ["user1", "user2"]
assert activity.actor == user.ap_id
- assert activity.recipients == ["user1", "user2"]
+ assert activity.recipients == ["user1", "user2", user.ap_id]
+ end
+
+ test "increases user note count only for public activities" do
+ user = insert(:user)
+
+ {:ok, _} =
+ CommonAPI.post(User.get_by_id(user.id), %{"status" => "1", "visibility" => "public"})
+
+ {:ok, _} =
+ CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "unlisted"})
+
+ {:ok, _} =
+ CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "private"})
+
+ {:ok, _} =
+ CommonAPI.post(User.get_by_id(user.id), %{"status" => "3", "visibility" => "direct"})
+
+ user = User.get_by_id(user.id)
+ assert user.info.note_count == 2
+ end
+
+ test "increases replies count" do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
+ ap_id = activity.data["id"]
+ reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
+
+ # public
+ {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
+ assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
+ assert data["object"]["repliesCount"] == 1
+ assert object.data["repliesCount"] == 1
+
+ # unlisted
+ {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
+ assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
+ assert data["object"]["repliesCount"] == 2
+ assert object.data["repliesCount"] == 2
+
+ # private
+ {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
+ assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
+ assert data["object"]["repliesCount"] == 2
+ assert object.data["repliesCount"] == 2
+
+ # direct
+ {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
+ assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
+ assert data["object"]["repliesCount"] == 2
+ assert object.data["repliesCount"] == 2
end
end
@@ -142,7 +309,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
booster = insert(:user)
{:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]})
- activities = ActivityPub.fetch_activities([], %{"blocking_user" => user})
+ activities =
+ ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
assert Enum.member?(activities, activity_two)
assert Enum.member?(activities, activity_three)
@@ -150,7 +318,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
{:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
- activities = ActivityPub.fetch_activities([], %{"blocking_user" => user})
+ activities =
+ ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
assert Enum.member?(activities, activity_two)
assert Enum.member?(activities, activity_three)
@@ -158,17 +327,76 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
{:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
- %Activity{} = boost_activity = Activity.get_create_activity_by_object_ap_id(id)
- activity_three = Repo.get(Activity, activity_three.id)
+ %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
+ activity_three = Activity.get_by_id(activity_three.id)
+
+ activities =
+ ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+
+ assert Enum.member?(activities, activity_two)
+ refute Enum.member?(activities, activity_three)
+ refute Enum.member?(activities, boost_activity)
+ assert Enum.member?(activities, activity_one)
+
+ activities =
+ ActivityPub.fetch_activities([], %{"blocking_user" => nil, "skip_preload" => true})
+
+ assert Enum.member?(activities, activity_two)
+ assert Enum.member?(activities, activity_three)
+ assert Enum.member?(activities, boost_activity)
+ assert Enum.member?(activities, activity_one)
+ end
+
+ test "doesn't return muted activities" do
+ activity_one = insert(:note_activity)
+ activity_two = insert(:note_activity)
+ activity_three = insert(:note_activity)
+ user = insert(:user)
+ booster = insert(:user)
+ {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]})
+
+ activities =
+ ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
+
+ assert Enum.member?(activities, activity_two)
+ assert Enum.member?(activities, activity_three)
+ refute Enum.member?(activities, activity_one)
+
+ # Calling with 'with_muted' will deliver muted activities, too.
+ activities =
+ ActivityPub.fetch_activities([], %{
+ "muting_user" => user,
+ "with_muted" => true,
+ "skip_preload" => true
+ })
+
+ assert Enum.member?(activities, activity_two)
+ assert Enum.member?(activities, activity_three)
+ assert Enum.member?(activities, activity_one)
+
+ {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})
+
+ activities =
+ ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
+
+ assert Enum.member?(activities, activity_two)
+ assert Enum.member?(activities, activity_three)
+ assert Enum.member?(activities, activity_one)
+
+ {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
+ {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
+ %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
+ activity_three = Activity.get_by_id(activity_three.id)
- activities = ActivityPub.fetch_activities([], %{"blocking_user" => user})
+ activities =
+ ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
assert Enum.member?(activities, activity_two)
refute Enum.member?(activities, activity_three)
refute Enum.member?(activities, boost_activity)
assert Enum.member?(activities, activity_one)
- activities = ActivityPub.fetch_activities([], %{"blocking_user" => nil})
+ activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true})
assert Enum.member?(activities, activity_two)
assert Enum.member?(activities, activity_three)
@@ -176,11 +404,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert Enum.member?(activities, activity_one)
end
+ test "does include announces on request" do
+ activity_three = insert(:note_activity)
+ user = insert(:user)
+ booster = insert(:user)
+
+ {:ok, user} = User.follow(user, booster)
+
+ {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
+
+ [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following])
+
+ assert announce_activity.id == announce.id
+ end
+
+ test "excludes reblogs on request" do
+ user = insert(:user)
+ {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
+ {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
+
+ [activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"})
+
+ assert activity == expected_activity
+ end
+
describe "public fetch activities" do
test "doesn't retrieve unlisted activities" do
user = insert(:user)
- {:ok, unlisted_activity} =
+ {:ok, _unlisted_activity} =
CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"})
{:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"})
@@ -237,6 +489,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert length(activities) == 20
assert last == last_expected
end
+
+ test "doesn't return reblogs for users for whom reblogs have been muted" do
+ activity = insert(:note_activity)
+ user = insert(:user)
+ booster = insert(:user)
+ {:ok, user} = CommonAPI.hide_reblogs(user, booster)
+
+ {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
+
+ activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
+
+ refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
+ end
+
+ test "returns reblogs for users for whom reblogs have not been muted" do
+ activity = insert(:note_activity)
+ user = insert(:user)
+ booster = insert(:user)
+ {:ok, user} = CommonAPI.hide_reblogs(user, booster)
+ {:ok, user} = CommonAPI.show_reblogs(user, booster)
+
+ {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
+
+ activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
+
+ assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
+ end
end
describe "like an object" do
@@ -262,7 +541,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert like_activity == same_like_activity
assert object.data["likes"] == [user.ap_id]
- [note_activity] = Activity.all_by_object_ap_id(object.data["id"])
+ [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"])
assert note_activity.data["object"]["like_count"] == 1
{:ok, _like_activity, object} = ActivityPub.like(user_two, object)
@@ -286,7 +565,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
{:ok, _, _, object} = ActivityPub.unlike(user, object)
assert object.data["like_count"] == 0
- assert Repo.get(Activity, like_activity.id) == nil
+ assert Activity.get_by_id(like_activity.id) == nil
end
end
@@ -337,7 +616,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert unannounce_activity.data["actor"] == user.ap_id
assert unannounce_activity.data["context"] == announce_activity.data["context"]
- assert Repo.get(Activity, announce_activity.id) == nil
+ assert Activity.get_by_id(announce_activity.id) == nil
end
end
@@ -372,6 +651,43 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
end
end
+ describe "fetching an object" do
+ test "it fetches an object" do
+ {:ok, object} =
+ ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
+
+ assert activity = Activity.get_create_by_object_ap_id(object.data["id"])
+ assert activity.data["id"]
+
+ {:ok, object_again} =
+ ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
+
+ assert [attachment] = object.data["attachment"]
+ assert is_list(attachment["url"])
+
+ assert object == object_again
+ end
+
+ test "it works with objects only available via Ostatus" do
+ {:ok, object} = ActivityPub.fetch_object_from_id("https://shitposter.club/notice/2827873")
+ assert activity = Activity.get_create_by_object_ap_id(object.data["id"])
+ assert activity.data["id"]
+
+ {:ok, object_again} =
+ ActivityPub.fetch_object_from_id("https://shitposter.club/notice/2827873")
+
+ assert object == object_again
+ end
+
+ test "it correctly stitches up conversations between ostatus and ap" do
+ last = "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+ {:ok, object} = ActivityPub.fetch_object_from_id(last)
+
+ object = Object.get_by_ap_id(object.data["inReplyTo"])
+ assert object
+ end
+ end
+
describe "following / unfollowing" do
test "creates a follow activity" do
follower = insert(:user)
@@ -439,9 +755,88 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert delete.data["actor"] == note.data["actor"]
assert delete.data["object"] == note.data["object"]["id"]
- assert Repo.get(Activity, delete.id) != nil
+ assert Activity.get_by_id(delete.id) != nil
- assert Repo.get(Object, object.id) == nil
+ assert Repo.get(Object, object.id).data["type"] == "Tombstone"
+ end
+
+ test "decrements user note count only for public activities" do
+ user = insert(:user, info: %{note_count: 10})
+
+ {:ok, a1} =
+ CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "public"})
+
+ {:ok, a2} =
+ CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "unlisted"})
+
+ {:ok, a3} =
+ CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "private"})
+
+ {:ok, a4} =
+ CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "direct"})
+
+ {:ok, _} = a1.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
+ {:ok, _} = a2.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
+ {:ok, _} = a3.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
+ {:ok, _} = a4.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
+
+ user = User.get_by_id(user.id)
+ assert user.info.note_count == 10
+ end
+
+ test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
+ user = insert(:user)
+ note = insert(:note_activity)
+
+ {:ok, object} =
+ Object.get_by_ap_id(note.data["object"]["id"])
+ |> Object.change(%{
+ data: %{
+ "actor" => note.data["object"]["actor"],
+ "id" => note.data["object"]["id"],
+ "to" => [user.ap_id],
+ "type" => "Note"
+ }
+ })
+ |> Object.update_and_set_cache()
+
+ {:ok, delete} = ActivityPub.delete(object)
+
+ assert user.ap_id in delete.data["to"]
+ end
+
+ test "decreases reply count" do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
+ reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
+ ap_id = activity.data["id"]
+
+ {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
+ {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
+ {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
+ {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
+
+ _ = CommonAPI.delete(direct_reply.id, user2)
+ assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
+ assert data["object"]["repliesCount"] == 2
+ assert object.data["repliesCount"] == 2
+
+ _ = CommonAPI.delete(private_reply.id, user2)
+ assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
+ assert data["object"]["repliesCount"] == 2
+ assert object.data["repliesCount"] == 2
+
+ _ = CommonAPI.delete(public_reply.id, user2)
+ assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
+ assert data["object"]["repliesCount"] == 1
+ assert object.data["repliesCount"] == 1
+
+ _ = CommonAPI.delete(unlisted_reply.id, user2)
+ assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
+ assert data["object"]["repliesCount"] == 0
+ assert object.data["repliesCount"] == 0
end
end
@@ -479,10 +874,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
"in_reply_to_status_id" => private_activity_2.id
})
- assert user1.following == [user3.ap_id <> "/followers", user1.ap_id]
-
activities = ActivityPub.fetch_activities([user1.ap_id | user1.following])
+ private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
assert [public_activity, private_activity_1, private_activity_3] == activities
assert length(activities) == 3
@@ -514,6 +908,177 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
end
end
+ test "it can fetch peertube videos" do
+ {:ok, object} =
+ ActivityPub.fetch_object_from_id(
+ "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
+ )
+
+ assert object
+ end
+
+ test "returned pinned statuses" do
+ Pleroma.Config.put([:instance, :max_pinned_statuses], 3)
+ user = insert(:user)
+
+ {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
+ {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
+ {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+ CommonAPI.pin(activity_one.id, user)
+ user = refresh_record(user)
+
+ CommonAPI.pin(activity_two.id, user)
+ user = refresh_record(user)
+
+ CommonAPI.pin(activity_three.id, user)
+ user = refresh_record(user)
+
+ activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
+
+ assert 3 = length(activities)
+ end
+
+ test "it can create a Flag activity" do
+ reporter = insert(:user)
+ target_account = insert(:user)
+ {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"})
+ context = Utils.generate_context_id()
+ content = "foobar"
+
+ reporter_ap_id = reporter.ap_id
+ target_ap_id = target_account.ap_id
+ activity_ap_id = activity.data["id"]
+
+ assert {:ok, activity} =
+ ActivityPub.flag(%{
+ actor: reporter,
+ context: context,
+ account: target_account,
+ statuses: [activity],
+ content: content
+ })
+
+ assert %Activity{
+ actor: ^reporter_ap_id,
+ data: %{
+ "type" => "Flag",
+ "content" => ^content,
+ "context" => ^context,
+ "object" => [^target_ap_id, ^activity_ap_id]
+ }
+ } = activity
+ end
+
+ describe "publish_one/1" do
+ test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_reachable(inbox))
+ end
+
+ test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} =
+ ActivityPub.publish_one(%{
+ inbox: inbox,
+ json: "{}",
+ actor: actor,
+ id: 1,
+ unreachable_since: NaiveDateTime.utc_now()
+ })
+
+ assert called(Instances.set_reachable(inbox))
+ end
+
+ test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} =
+ ActivityPub.publish_one(%{
+ inbox: inbox,
+ json: "{}",
+ actor: actor,
+ id: 1,
+ unreachable_since: nil
+ })
+
+ refute called(Instances.set_reachable(inbox))
+ end
+
+ test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://404.site/users/nick1/inbox"
+
+ assert {:error, _} =
+ ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://connrefused.site/users/nick1/inbox"
+
+ assert {:error, _} =
+ ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ refute called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://connrefused.site/users/nick1/inbox"
+
+ assert {:error, _} =
+ ActivityPub.publish_one(%{
+ inbox: inbox,
+ json: "{}",
+ actor: actor,
+ id: 1,
+ unreachable_since: NaiveDateTime.utc_now()
+ })
+
+ refute called(Instances.set_unreachable(inbox))
+ end
+ end
+
def data_uri do
File.read!("test/fixtures/avatar_data_uri")
end
diff --git a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs
new file mode 100644
index 000000000..37a7bfcf7
--- /dev/null
+++ b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ alias Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy
+
+ describe "blocking based on attributes" do
+ test "matches followbots by nickname" do
+ actor = insert(:user, %{nickname: "followbot@example.com"})
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ {:reject, nil} = AntiFollowbotPolicy.filter(message)
+ end
+
+ test "matches followbots by display name" do
+ actor = insert(:user, %{name: "Federation Bot"})
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ {:reject, nil} = AntiFollowbotPolicy.filter(message)
+ end
+ end
+
+ test "it allows non-followbots" do
+ actor = insert(:user)
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ {:ok, _} = AntiFollowbotPolicy.filter(message)
+ end
+
+ test "it gracefully handles nil display names" do
+ actor = insert(:user, %{name: nil})
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ {:ok, _} = AntiFollowbotPolicy.filter(message)
+ end
+end
diff --git a/test/web/activity_pub/mrf/hellthread_policy_test.exs b/test/web/activity_pub/mrf/hellthread_policy_test.exs
new file mode 100644
index 000000000..eb6ee4d04
--- /dev/null
+++ b/test/web/activity_pub/mrf/hellthread_policy_test.exs
@@ -0,0 +1,73 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy
+
+ setup do
+ user = insert(:user)
+
+ message = %{
+ "actor" => user.ap_id,
+ "cc" => [user.follower_address],
+ "type" => "Create",
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "https://instance.tld/users/user1",
+ "https://instance.tld/users/user2",
+ "https://instance.tld/users/user3"
+ ]
+ }
+
+ [user: user, message: message]
+ end
+
+ describe "reject" do
+ test "rejects the message if the recipient count is above reject_threshold", %{
+ message: message
+ } do
+ Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 2})
+
+ {:reject, nil} = filter(message)
+ end
+
+ test "does not reject the message if the recipient count is below reject_threshold", %{
+ message: message
+ } do
+ Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3})
+
+ assert {:ok, ^message} = filter(message)
+ end
+ end
+
+ describe "delist" do
+ test "delists the message if the recipient count is above delist_threshold", %{
+ user: user,
+ message: message
+ } do
+ Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 2, reject_threshold: 0})
+
+ {:ok, message} = filter(message)
+ assert user.follower_address in message["to"]
+ assert "https://www.w3.org/ns/activitystreams#Public" in message["cc"]
+ end
+
+ test "does not delist the message if the recipient count is below delist_threshold", %{
+ message: message
+ } do
+ Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 4, reject_threshold: 0})
+
+ assert {:ok, ^message} = filter(message)
+ end
+ end
+
+ test "excludes follower collection and public URI from threshold count", %{message: message} do
+ Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3})
+
+ assert {:ok, ^message} = filter(message)
+ end
+end
diff --git a/test/web/activity_pub/mrf/keyword_policy_test.exs b/test/web/activity_pub/mrf/keyword_policy_test.exs
new file mode 100644
index 000000000..602892a37
--- /dev/null
+++ b/test/web/activity_pub/mrf/keyword_policy_test.exs
@@ -0,0 +1,219 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.MRF.KeywordPolicy
+
+ setup do
+ Pleroma.Config.put([:mrf_keyword], %{reject: [], federated_timeline_removal: [], replace: []})
+ end
+
+ describe "rejecting based on keywords" do
+ test "rejects if string matches in content" do
+ Pleroma.Config.put([:mrf_keyword, :reject], ["pun"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "just a daily reminder that compLAINer is a good pun",
+ "summary" => ""
+ }
+ }
+
+ assert {:reject, nil} == KeywordPolicy.filter(message)
+ end
+
+ test "rejects if string matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :reject], ["pun"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "just a daily reminder that compLAINer is a good pun",
+ "content" => ""
+ }
+ }
+
+ assert {:reject, nil} == KeywordPolicy.filter(message)
+ end
+
+ test "rejects if regex matches in content" do
+ Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "just a daily reminder that #{content} is a good pun",
+ "summary" => ""
+ }
+ }
+
+ {:reject, nil} == KeywordPolicy.filter(message)
+ end)
+ end
+
+ test "rejects if regex matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "just a daily reminder that #{content} is a good pun",
+ "content" => ""
+ }
+ }
+
+ {:reject, nil} == KeywordPolicy.filter(message)
+ end)
+ end
+ end
+
+ describe "delisting from ftl based on keywords" do
+ test "delists if string matches in content" do
+ Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"])
+
+ message = %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "content" => "just a daily reminder that compLAINer is a good pun",
+ "summary" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"]
+ refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
+ end
+
+ test "delists if string matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"])
+
+ message = %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "summary" => "just a daily reminder that compLAINer is a good pun",
+ "content" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"]
+ refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
+ end
+
+ test "delists if regex matches in content" do
+ Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{
+ "content" => "just a daily reminder that #{content} is a good pun",
+ "summary" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+
+ ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and
+ not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"])
+ end)
+ end
+
+ test "delists if regex matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{
+ "summary" => "just a daily reminder that #{content} is a good pun",
+ "content" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+
+ ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and
+ not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"])
+ end)
+ end
+ end
+
+ describe "replacing keywords" do
+ test "replaces keyword if string matches in content" do
+ Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}])
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"content" => "ZFS is opensource", "summary" => ""}
+ }
+
+ {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
+ assert result == "ZFS is free software"
+ end
+
+ test "replaces keyword if string matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}])
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"summary" => "ZFS is opensource", "content" => ""}
+ }
+
+ {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message)
+ assert result == "ZFS is free software"
+ end
+
+ test "replaces keyword if regex matches in content" do
+ Pleroma.Config.put([:mrf_keyword, :replace], [
+ {~r/open(-|\s)?source\s?(software)?/, "free software"}
+ ])
+
+ assert true ==
+ Enum.all?(["opensource", "open-source", "open source"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"content" => "ZFS is #{content}", "summary" => ""}
+ }
+
+ {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
+ result == "ZFS is free software"
+ end)
+ end
+
+ test "replaces keyword if regex matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :replace], [
+ {~r/open(-|\s)?source\s?(software)?/, "free software"}
+ ])
+
+ assert true ==
+ Enum.all?(["opensource", "open-source", "open source"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"summary" => "ZFS is #{content}", "content" => ""}
+ }
+
+ {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message)
+ result == "ZFS is free software"
+ end)
+ end
+ end
+end
diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs
index 41d13e055..21a63c493 100644
--- a/test/web/activity_pub/relay_test.exs
+++ b/test/web/activity_pub/relay_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.RelayTest do
use Pleroma.DataCase
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index ea9d9fe58..5559cdf87 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -1,17 +1,27 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
use Pleroma.DataCase
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.OStatus
- alias Pleroma.{Activity, Object}
- alias Pleroma.User
- alias Pleroma.Repo
alias Pleroma.Web.Websub.WebsubClientSubscription
import Pleroma.Factory
alias Pleroma.Web.CommonAPI
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
describe "handle_incoming" do
test "it ignores an incoming notice if we already have it" do
activity = insert(:note_activity)
@@ -43,13 +53,11 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
returned_object = Object.normalize(returned_activity.data["object"])
assert activity =
- Activity.get_create_activity_by_object_ap_id(
+ Activity.get_create_by_object_ap_id(
"tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
)
assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
-
- assert returned_object.data["inReplyToStatusId"] == activity.id
end
test "it works for incoming notices" do
@@ -160,6 +168,36 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert object.data["url"] == "https://prismo.news/posts/83"
end
+ test "it cleans up incoming notices which are not really DMs" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ to = [user.ap_id, other_user.ap_id]
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Map.put("to", to)
+ |> Map.put("cc", [])
+
+ object =
+ data["object"]
+ |> Map.put("to", to)
+ |> Map.put("cc", [])
+
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["to"] == []
+ assert data["cc"] == to
+
+ object = data["object"]
+
+ assert object["to"] == []
+ assert object["cc"] == to
+ end
+
test "it works for incoming follow requests" do
user = insert(:user)
@@ -261,7 +299,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert data["object"] ==
"http://mastodon.example.org/users/admin/statuses/99541947525187367"
- assert Activity.get_create_activity_by_object_ap_id(data["object"])
+ assert Activity.get_create_by_object_ap_id(data["object"])
end
test "it works for incoming announces with an existing activity" do
@@ -283,7 +321,70 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert data["object"] == activity.data["object"]
- assert Activity.get_create_activity_by_object_ap_id(data["object"]).id == activity.id
+ assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
+ end
+
+ test "it does not clobber the addressing on announce activities" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
+
+ data =
+ File.read!("test/fixtures/mastodon-announce.json")
+ |> Poison.decode!()
+ |> Map.put("object", activity.data["object"]["id"])
+ |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
+ |> Map.put("cc", [])
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
+ end
+
+ test "it ensures that as:Public activities make it to their followers collection" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
+ |> Map.put("cc", [])
+
+ object =
+ data["object"]
+ |> Map.put("attributedTo", user.ap_id)
+ |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
+ |> Map.put("cc", [])
+
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["cc"] == [User.ap_followers(user)]
+ end
+
+ test "it ensures that address fields become lists" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("to", nil)
+ |> Map.put("cc", nil)
+
+ object =
+ data["object"]
+ |> Map.put("attributedTo", user.ap_id)
+ |> Map.put("to", nil)
+ |> Map.put("cc", nil)
+
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert !is_nil(data["to"])
+ assert !is_nil(data["cc"])
end
test "it works for incoming update activities" do
@@ -365,7 +466,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
- refute Repo.get(Activity, activity.id)
+ refute Activity.get_by_id(activity.id)
end
test "it fails for incoming deletes with spoofed origin" do
@@ -385,7 +486,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
:error = Transmogrifier.handle_incoming(data)
- assert Repo.get(Activity, activity.id)
+ assert Activity.get_by_id(activity.id)
end
test "it works for incoming unannounces with an existing notice" do
@@ -543,7 +644,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert activity.data["object"] == follow_activity.data["id"]
- follower = Repo.get(User, follower.id)
+ follower = User.get_by_id(follower.id)
assert User.following?(follower, followed) == true
end
@@ -565,7 +666,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
assert activity.data["object"] == follow_activity.data["id"]
- follower = Repo.get(User, follower.id)
+ follower = User.get_by_id(follower.id)
assert User.following?(follower, followed) == true
end
@@ -585,7 +686,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
assert activity.data["object"] == follow_activity.data["id"]
- follower = Repo.get(User, follower.id)
+ follower = User.get_by_id(follower.id)
assert User.following?(follower, followed) == true
end
@@ -604,7 +705,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
:error = Transmogrifier.handle_incoming(accept_data)
- follower = Repo.get(User, follower.id)
+ follower = User.get_by_id(follower.id)
refute User.following?(follower, followed) == true
end
@@ -623,7 +724,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
:error = Transmogrifier.handle_incoming(accept_data)
- follower = Repo.get(User, follower.id)
+ follower = User.get_by_id(follower.id)
refute User.following?(follower, followed) == true
end
@@ -648,7 +749,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, activity} = Transmogrifier.handle_incoming(reject_data)
refute activity.local
- follower = Repo.get(User, follower.id)
+ follower = User.get_by_id(follower.id)
assert User.following?(follower, followed) == false
end
@@ -670,7 +771,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
- follower = Repo.get(User, follower.id)
+ follower = User.get_by_id(follower.id)
assert User.following?(follower, followed) == false
end
@@ -686,6 +787,60 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
:error = Transmogrifier.handle_incoming(data)
end
+
+ test "it remaps video URLs as attachments if necessary" do
+ {:ok, object} =
+ ActivityPub.fetch_object_from_id(
+ "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
+ )
+
+ attachment = %{
+ "type" => "Link",
+ "mediaType" => "video/mp4",
+ "href" =>
+ "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
+ "mimeType" => "video/mp4",
+ "size" => 5_015_880,
+ "url" => [
+ %{
+ "href" =>
+ "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ],
+ "width" => 480
+ }
+
+ assert object.data["url"] ==
+ "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
+
+ assert object.data["attachment"] == [attachment]
+ end
+
+ test "it accepts Flag activities" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+ object = Object.normalize(activity.data["object"])
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "cc" => [user.ap_id],
+ "object" => [user.ap_id, object.data["id"]],
+ "type" => "Flag",
+ "content" => "blocked AND reported!!!",
+ "actor" => other_user.ap_id
+ }
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ assert activity.data["object"] == [user.ap_id, object.data["id"]]
+ assert activity.data["content"] == "blocked AND reported!!!"
+ assert activity.data["actor"] == other_user.ap_id
+ assert activity.data["cc"] == [user.ap_id]
+ end
end
describe "prepare outgoing" do
@@ -797,12 +952,61 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert length(modified["object"]["tag"]) == 2
assert is_nil(modified["object"]["emoji"])
- assert is_nil(modified["object"]["likes"])
assert is_nil(modified["object"]["like_count"])
assert is_nil(modified["object"]["announcements"])
assert is_nil(modified["object"]["announcement_count"])
assert is_nil(modified["object"]["context_id"])
end
+
+ test "it strips internal fields of article" do
+ activity = insert(:article_activity)
+
+ {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+ assert length(modified["object"]["tag"]) == 2
+
+ assert is_nil(modified["object"]["emoji"])
+ assert is_nil(modified["object"]["like_count"])
+ assert is_nil(modified["object"]["announcements"])
+ assert is_nil(modified["object"]["announcement_count"])
+ assert is_nil(modified["object"]["context_id"])
+ end
+
+ test "it adds like collection to object" do
+ activity = insert(:note_activity)
+ {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+ assert modified["object"]["likes"]["type"] == "OrderedCollection"
+ assert modified["object"]["likes"]["totalItems"] == 0
+ end
+
+ test "the directMessage flag is present" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
+
+ {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+ assert modified["directMessage"] == false
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
+
+ {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+ assert modified["directMessage"] == false
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "@#{other_user.nickname} :moominmamma:",
+ "visibility" => "direct"
+ })
+
+ {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+ assert modified["directMessage"] == true
+ end
end
describe "user upgrade" do
@@ -821,7 +1025,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
assert user.info.note_count == 1
{:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
@@ -829,13 +1033,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert user.info.note_count == 1
assert user.follower_address == "https://niu.moe/users/rye/followers"
- # Wait for the background task
- :timer.sleep(1000)
-
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
assert user.info.note_count == 1
- activity = Repo.get(Activity, activity.id)
+ activity = Activity.get_by_id(activity.id)
assert user.follower_address in activity.recipients
assert %{
@@ -858,10 +1059,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
refute "..." in activity.recipients
- unrelated_activity = Repo.get(Activity, unrelated_activity.id)
+ unrelated_activity = Activity.get_by_id(unrelated_activity.id)
refute user.follower_address in unrelated_activity.recipients
- user_two = Repo.get(User, user_two.id)
+ user_two = User.get_by_id(user_two.id)
assert user.follower_address in user_two.following
refute "..." in user_two.following
end
@@ -933,4 +1134,114 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
:error = Transmogrifier.handle_incoming(data)
end
end
+
+ describe "general origin containment" do
+ test "contain_origin_from_id() catches obvious spoofing attempts" do
+ data = %{
+ "id" => "http://example.com/~alyssa/activities/1234.json"
+ }
+
+ :error =
+ Transmogrifier.contain_origin_from_id(
+ "http://example.org/~alyssa/activities/1234.json",
+ data
+ )
+ end
+
+ test "contain_origin_from_id() allows alternate IDs within the same origin domain" do
+ data = %{
+ "id" => "http://example.com/~alyssa/activities/1234.json"
+ }
+
+ :ok =
+ Transmogrifier.contain_origin_from_id(
+ "http://example.com/~alyssa/activities/1234",
+ data
+ )
+ end
+
+ test "contain_origin_from_id() allows matching IDs" do
+ data = %{
+ "id" => "http://example.com/~alyssa/activities/1234.json"
+ }
+
+ :ok =
+ Transmogrifier.contain_origin_from_id(
+ "http://example.com/~alyssa/activities/1234.json",
+ data
+ )
+ end
+
+ test "users cannot be collided through fake direction spoofing attempts" do
+ insert(:user, %{
+ nickname: "rye@niu.moe",
+ local: false,
+ ap_id: "https://niu.moe/users/rye",
+ follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
+ })
+
+ {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
+ end
+
+ test "all objects with fake directions are rejected by the object fetcher" do
+ {:error, _} =
+ ActivityPub.fetch_and_contain_remote_object_from_id(
+ "https://info.pleroma.site/activity4.json"
+ )
+ end
+ end
+
+ describe "reserialization" do
+ test "successfully reserializes a message with inReplyTo == nil" do
+ user = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Create",
+ "object" => %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Note",
+ "content" => "Hi",
+ "inReplyTo" => nil,
+ "attributedTo" => user.ap_id
+ },
+ "actor" => user.ap_id
+ }
+
+ {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
+ end
+
+ test "successfully reserializes a message with AS2 objects in IR" do
+ user = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Create",
+ "object" => %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Note",
+ "content" => "Hi",
+ "inReplyTo" => nil,
+ "attributedTo" => user.ap_id,
+ "tag" => [
+ %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
+ %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
+ ]
+ },
+ "actor" => user.ap_id
+ }
+
+ {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
+ end
+ end
end
diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs
new file mode 100644
index 000000000..758214e68
--- /dev/null
+++ b/test/web/activity_pub/utils_test.exs
@@ -0,0 +1,208 @@
+defmodule Pleroma.Web.ActivityPub.UtilsTest do
+ use Pleroma.DataCase
+ alias Pleroma.Activity
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "fetch the latest Follow" do
+ test "fetches the latest Follow activity" do
+ %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
+ follower = Repo.get_by(User, ap_id: activity.data["actor"])
+ followed = Repo.get_by(User, ap_id: activity.data["object"])
+
+ assert activity == Utils.fetch_latest_follow(follower, followed)
+ end
+ end
+
+ describe "fetch the latest Block" do
+ test "fetches the latest Block activity" do
+ blocker = insert(:user)
+ blocked = insert(:user)
+ {:ok, activity} = ActivityPub.block(blocker, blocked)
+
+ assert activity == Utils.fetch_latest_block(blocker, blocked)
+ end
+ end
+
+ describe "determine_explicit_mentions()" do
+ test "works with an object that has mentions" do
+ object = %{
+ "tag" => [
+ %{
+ "type" => "Mention",
+ "href" => "https://example.com/~alyssa",
+ "name" => "Alyssa P. Hacker"
+ }
+ ]
+ }
+
+ assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
+ end
+
+ test "works with an object that does not have mentions" do
+ object = %{
+ "tag" => [
+ %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
+ ]
+ }
+
+ assert Utils.determine_explicit_mentions(object) == []
+ end
+
+ test "works with an object that has mentions and other tags" do
+ object = %{
+ "tag" => [
+ %{
+ "type" => "Mention",
+ "href" => "https://example.com/~alyssa",
+ "name" => "Alyssa P. Hacker"
+ },
+ %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
+ ]
+ }
+
+ assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
+ end
+
+ test "works with an object that has no tags" do
+ object = %{}
+
+ assert Utils.determine_explicit_mentions(object) == []
+ end
+
+ test "works with an object that has only IR tags" do
+ object = %{"tag" => ["2hu"]}
+
+ assert Utils.determine_explicit_mentions(object) == []
+ end
+ end
+
+ describe "make_like_data" do
+ setup do
+ user = insert(:user)
+ other_user = insert(:user)
+ third_user = insert(:user)
+ [user: user, other_user: other_user, third_user: third_user]
+ end
+
+ test "addresses actor's follower address if the activity is public", %{
+ user: user,
+ other_user: other_user,
+ third_user: third_user
+ } do
+ expected_to = Enum.sort([user.ap_id, other_user.follower_address])
+ expected_cc = Enum.sort(["https://www.w3.org/ns/activitystreams#Public", third_user.ap_id])
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" =>
+ "hey @#{other_user.nickname}, @#{third_user.nickname} how about beering together this weekend?"
+ })
+
+ %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil)
+ assert Enum.sort(to) == expected_to
+ assert Enum.sort(cc) == expected_cc
+ end
+
+ test "does not adress actor's follower address if the activity is not public", %{
+ user: user,
+ other_user: other_user,
+ third_user: third_user
+ } do
+ expected_to = Enum.sort([user.ap_id])
+ expected_cc = [third_user.ap_id]
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "@#{other_user.nickname} @#{third_user.nickname} bought a new swimsuit!",
+ "visibility" => "private"
+ })
+
+ %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil)
+ assert Enum.sort(to) == expected_to
+ assert Enum.sort(cc) == expected_cc
+ end
+ end
+
+ describe "fetch_ordered_collection" do
+ import Tesla.Mock
+
+ test "fetches the first OrderedCollectionPage when an OrderedCollection is encountered" do
+ mock(fn
+ %{method: :get, url: "http://mastodon.com/outbox"} ->
+ json(%{"type" => "OrderedCollection", "first" => "http://mastodon.com/outbox?page=true"})
+
+ %{method: :get, url: "http://mastodon.com/outbox?page=true"} ->
+ json(%{"type" => "OrderedCollectionPage", "orderedItems" => ["ok"]})
+ end)
+
+ assert Utils.fetch_ordered_collection("http://mastodon.com/outbox", 1) == ["ok"]
+ end
+
+ test "fetches several pages in the right order one after another, but only the specified amount" do
+ mock(fn
+ %{method: :get, url: "http://example.com/outbox"} ->
+ json(%{
+ "type" => "OrderedCollectionPage",
+ "orderedItems" => [0],
+ "next" => "http://example.com/outbox?page=1"
+ })
+
+ %{method: :get, url: "http://example.com/outbox?page=1"} ->
+ json(%{
+ "type" => "OrderedCollectionPage",
+ "orderedItems" => [1],
+ "next" => "http://example.com/outbox?page=2"
+ })
+
+ %{method: :get, url: "http://example.com/outbox?page=2"} ->
+ json(%{"type" => "OrderedCollectionPage", "orderedItems" => [2]})
+ end)
+
+ assert Utils.fetch_ordered_collection("http://example.com/outbox", 0) == [0]
+ assert Utils.fetch_ordered_collection("http://example.com/outbox", 1) == [0, 1]
+ end
+
+ test "returns an error if the url doesn't have an OrderedCollection/Page" do
+ mock(fn
+ %{method: :get, url: "http://example.com/not-an-outbox"} ->
+ json(%{"type" => "NotAnOutbox"})
+ end)
+
+ assert {:error, _} = Utils.fetch_ordered_collection("http://example.com/not-an-outbox", 1)
+ end
+
+ test "returns the what was collected if there are less pages than specified" do
+ mock(fn
+ %{method: :get, url: "http://example.com/outbox"} ->
+ json(%{
+ "type" => "OrderedCollectionPage",
+ "orderedItems" => [0],
+ "next" => "http://example.com/outbox?page=1"
+ })
+
+ %{method: :get, url: "http://example.com/outbox?page=1"} ->
+ json(%{"type" => "OrderedCollectionPage", "orderedItems" => [1]})
+ end)
+
+ assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1]
+ end
+ end
+
+ test "make_json_ld_header/0" do
+ assert Utils.make_json_ld_header() == %{
+ "@context" => [
+ "https://www.w3.org/ns/activitystreams",
+ "http://localhost:4001/schemas/litepub-0.1.jsonld",
+ %{
+ "@language" => "und"
+ }
+ ]
+ }
+ end
+end
diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs
index d144a77fc..d939fc5a7 100644
--- a/test/web/activity_pub/views/object_view_test.exs
+++ b/test/web/activity_pub/views/object_view_test.exs
@@ -2,8 +2,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
use Pleroma.DataCase
import Pleroma.Factory
- alias Pleroma.Web.CommonAPI
alias Pleroma.Web.ActivityPub.ObjectView
+ alias Pleroma.Web.CommonAPI
test "renders a note object" do
note = insert(:note)
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index 7fc870e96..9fb9455d2 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -15,4 +15,66 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY")
end
+
+ test "Does not add an avatar image if the user hasn't set one" do
+ user = insert(:user)
+ {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+ result = UserView.render("user.json", %{user: user})
+ refute result["icon"]
+ refute result["image"]
+
+ user =
+ insert(:user,
+ avatar: %{"url" => [%{"href" => "https://someurl"}]},
+ info: %{
+ banner: %{"url" => [%{"href" => "https://somebanner"}]}
+ }
+ )
+
+ {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+ result = UserView.render("user.json", %{user: user})
+ assert result["icon"]["url"] == "https://someurl"
+ assert result["image"]["url"] == "https://somebanner"
+ end
+
+ describe "endpoints" do
+ test "local users have a usable endpoints structure" do
+ user = insert(:user)
+ {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+ result = UserView.render("user.json", %{user: user})
+
+ assert result["id"] == user.ap_id
+
+ %{
+ "sharedInbox" => _,
+ "oauthAuthorizationEndpoint" => _,
+ "oauthRegistrationEndpoint" => _,
+ "oauthTokenEndpoint" => _
+ } = result["endpoints"]
+ end
+
+ test "remote users have an empty endpoints structure" do
+ user = insert(:user, local: false)
+ {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+ result = UserView.render("user.json", %{user: user})
+
+ assert result["id"] == user.ap_id
+ assert result["endpoints"] == %{}
+ end
+
+ test "instance users do not expose oAuth endpoints" do
+ user = insert(:user, nickname: nil, local: true)
+ {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+ result = UserView.render("user.json", %{user: user})
+
+ refute result["endpoints"]["oauthAuthorizationEndpoint"]
+ refute result["endpoints"]["oauthRegistrationEndpoint"]
+ refute result["endpoints"]["oauthTokenEndpoint"]
+ end
+ end
end
diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs
new file mode 100644
index 000000000..24b96c4aa
--- /dev/null
+++ b/test/web/activity_pub/visibilty_test.exs
@@ -0,0 +1,98 @@
+defmodule Pleroma.Web.ActivityPub.VisibilityTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI
+ import Pleroma.Factory
+
+ setup do
+ user = insert(:user)
+ mentioned = insert(:user)
+ following = insert(:user)
+ unrelated = insert(:user)
+ {:ok, following} = Pleroma.User.follow(following, user)
+
+ {:ok, public} =
+ CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "public"})
+
+ {:ok, private} =
+ CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "private"})
+
+ {:ok, direct} =
+ CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "direct"})
+
+ {:ok, unlisted} =
+ CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "unlisted"})
+
+ %{
+ public: public,
+ private: private,
+ direct: direct,
+ unlisted: unlisted,
+ user: user,
+ mentioned: mentioned,
+ following: following,
+ unrelated: unrelated
+ }
+ end
+
+ test "is_direct?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
+ assert Visibility.is_direct?(direct)
+ refute Visibility.is_direct?(public)
+ refute Visibility.is_direct?(private)
+ refute Visibility.is_direct?(unlisted)
+ end
+
+ test "is_public?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
+ refute Visibility.is_public?(direct)
+ assert Visibility.is_public?(public)
+ refute Visibility.is_public?(private)
+ assert Visibility.is_public?(unlisted)
+ end
+
+ test "is_private?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
+ refute Visibility.is_private?(direct)
+ refute Visibility.is_private?(public)
+ assert Visibility.is_private?(private)
+ refute Visibility.is_private?(unlisted)
+ end
+
+ test "visible_for_user?", %{
+ public: public,
+ private: private,
+ direct: direct,
+ unlisted: unlisted,
+ user: user,
+ mentioned: mentioned,
+ following: following,
+ unrelated: unrelated
+ } do
+ # All visible to author
+
+ assert Visibility.visible_for_user?(public, user)
+ assert Visibility.visible_for_user?(private, user)
+ assert Visibility.visible_for_user?(unlisted, user)
+ assert Visibility.visible_for_user?(direct, user)
+
+ # All visible to a mentioned user
+
+ assert Visibility.visible_for_user?(public, mentioned)
+ assert Visibility.visible_for_user?(private, mentioned)
+ assert Visibility.visible_for_user?(unlisted, mentioned)
+ assert Visibility.visible_for_user?(direct, mentioned)
+
+ # DM not visible for just follower
+
+ assert Visibility.visible_for_user?(public, following)
+ assert Visibility.visible_for_user?(private, following)
+ assert Visibility.visible_for_user?(unlisted, following)
+ refute Visibility.visible_for_user?(direct, following)
+
+ # Public and unlisted visible for unrelated user
+
+ assert Visibility.visible_for_user?(public, unrelated)
+ assert Visibility.visible_for_user?(unlisted, unrelated)
+ refute Visibility.visible_for_user?(private, unrelated)
+ refute Visibility.visible_for_user?(direct, unrelated)
+ end
+end
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 9634ad7c5..b3167a861 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -1,10 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
use Pleroma.Web.ConnCase
- alias Pleroma.{Repo, User}
-
+ alias Pleroma.User
+ alias Pleroma.UserInviteToken
import Pleroma.Factory
- import ExUnit.CaptureLog
describe "/api/pleroma/admin/user" do
test "Delete" do
@@ -37,6 +40,157 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
end
end
+ describe "/api/pleroma/admin/users/:nickname" do
+ test "Show", %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users/#{user.nickname}")
+
+ expected = %{
+ "deactivated" => false,
+ "id" => to_string(user.id),
+ "local" => true,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "tags" => []
+ }
+
+ assert expected == json_response(conn, 200)
+ end
+
+ test "when the user doesn't exist", %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+ user = build(:user)
+
+ conn =
+ conn
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users/#{user.nickname}")
+
+ assert "Not found" == json_response(conn, 404)
+ end
+ end
+
+ describe "/api/pleroma/admin/user/follow" do
+ test "allows to force-follow another user" do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user)
+ follower = insert(:user)
+
+ build_conn()
+ |> assign(:user, admin)
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/user/follow", %{
+ "follower" => follower.nickname,
+ "followed" => user.nickname
+ })
+
+ user = User.get_by_id(user.id)
+ follower = User.get_by_id(follower.id)
+
+ assert User.following?(follower, user)
+ end
+ end
+
+ describe "/api/pleroma/admin/user/unfollow" do
+ test "allows to force-unfollow another user" do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user)
+ follower = insert(:user)
+
+ User.follow(follower, user)
+
+ build_conn()
+ |> assign(:user, admin)
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/user/unfollow", %{
+ "follower" => follower.nickname,
+ "followed" => user.nickname
+ })
+
+ user = User.get_by_id(user.id)
+ follower = User.get_by_id(follower.id)
+
+ refute User.following?(follower, user)
+ end
+ end
+
+ describe "PUT /api/pleroma/admin/users/tag" do
+ setup do
+ admin = insert(:user, info: %{is_admin: true})
+ user1 = insert(:user, %{tags: ["x"]})
+ user2 = insert(:user, %{tags: ["y"]})
+ user3 = insert(:user, %{tags: ["unchanged"]})
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> put_req_header("accept", "application/json")
+ |> put(
+ "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{
+ user2.nickname
+ }&tags[]=foo&tags[]=bar"
+ )
+
+ %{conn: conn, user1: user1, user2: user2, user3: user3}
+ end
+
+ test "it appends specified tags to users with specified nicknames", %{
+ conn: conn,
+ user1: user1,
+ user2: user2
+ } do
+ assert json_response(conn, :no_content)
+ assert User.get_by_id(user1.id).tags == ["x", "foo", "bar"]
+ assert User.get_by_id(user2.id).tags == ["y", "foo", "bar"]
+ end
+
+ test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
+ assert json_response(conn, :no_content)
+ assert User.get_by_id(user3.id).tags == ["unchanged"]
+ end
+ end
+
+ describe "DELETE /api/pleroma/admin/users/tag" do
+ setup do
+ admin = insert(:user, info: %{is_admin: true})
+ user1 = insert(:user, %{tags: ["x"]})
+ user2 = insert(:user, %{tags: ["y", "z"]})
+ user3 = insert(:user, %{tags: ["unchanged"]})
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> put_req_header("accept", "application/json")
+ |> delete(
+ "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{
+ user2.nickname
+ }&tags[]=x&tags[]=z"
+ )
+
+ %{conn: conn, user1: user1, user2: user2, user3: user3}
+ end
+
+ test "it removes specified tags from users with specified nicknames", %{
+ conn: conn,
+ user1: user1,
+ user2: user2
+ } do
+ assert json_response(conn, :no_content)
+ assert User.get_by_id(user1.id).tags == []
+ assert User.get_by_id(user2.id).tags == ["y"]
+ end
+
+ test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
+ assert json_response(conn, :no_content)
+ assert User.get_by_id(user3.id).tags == ["unchanged"]
+ end
+ end
+
describe "/api/pleroma/admin/permission_group" do
test "GET is giving user_info" do
admin = insert(:user, info: %{is_admin: true})
@@ -84,6 +238,161 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
end
end
+ describe "PUT /api/pleroma/admin/activation_status" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+
+ conn =
+ conn
+ |> assign(:user, admin)
+ |> put_req_header("accept", "application/json")
+
+ %{conn: conn}
+ end
+
+ test "deactivates the user", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false})
+
+ user = User.get_by_id(user.id)
+ assert user.info.deactivated == true
+ assert json_response(conn, :no_content)
+ end
+
+ test "activates the user", %{conn: conn} do
+ user = insert(:user, info: %{deactivated: true})
+
+ conn =
+ conn
+ |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: true})
+
+ user = User.get_by_id(user.id)
+ assert user.info.deactivated == false
+ assert json_response(conn, :no_content)
+ end
+
+ test "returns 403 when requested by a non-admin", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false})
+
+ assert json_response(conn, :forbidden)
+ end
+ end
+
+ describe "POST /api/pleroma/admin/email_invite, with valid config" do
+ setup do
+ registrations_open = Pleroma.Config.get([:instance, :registrations_open])
+ invites_enabled = Pleroma.Config.get([:instance, :invites_enabled])
+ Pleroma.Config.put([:instance, :registrations_open], false)
+ Pleroma.Config.put([:instance, :invites_enabled], true)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :registrations_open], registrations_open)
+ Pleroma.Config.put([:instance, :invites_enabled], invites_enabled)
+ :ok
+ end)
+
+ [user: insert(:user, info: %{is_admin: true})]
+ end
+
+ test "sends invitation and returns 204", %{conn: conn, user: user} do
+ recipient_email = "foo@bar.com"
+ recipient_name = "J. D."
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/pleroma/admin/email_invite?email=#{recipient_email}&name=#{recipient_name}")
+
+ assert json_response(conn, :no_content)
+
+ token_record = List.last(Pleroma.Repo.all(Pleroma.UserInviteToken))
+ assert token_record
+ refute token_record.used
+
+ notify_email = Pleroma.Config.get([:instance, :notify_email])
+ instance_name = Pleroma.Config.get([:instance, :name])
+
+ email =
+ Pleroma.Emails.UserEmail.user_invitation_email(
+ user,
+ token_record,
+ recipient_email,
+ recipient_name
+ )
+
+ Swoosh.TestAssertions.assert_email_sent(
+ from: {instance_name, notify_email},
+ to: {recipient_name, recipient_email},
+ html_body: email.html_body
+ )
+ end
+
+ test "it returns 403 if requested by a non-admin", %{conn: conn} do
+ non_admin_user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, non_admin_user)
+ |> post("/api/pleroma/admin/email_invite?email=foo@bar.com&name=JD")
+
+ assert json_response(conn, :forbidden)
+ end
+ end
+
+ describe "POST /api/pleroma/admin/email_invite, with invalid config" do
+ setup do
+ [user: insert(:user, info: %{is_admin: true})]
+ end
+
+ test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn, user: user} do
+ registrations_open = Pleroma.Config.get([:instance, :registrations_open])
+ invites_enabled = Pleroma.Config.get([:instance, :invites_enabled])
+ Pleroma.Config.put([:instance, :registrations_open], false)
+ Pleroma.Config.put([:instance, :invites_enabled], false)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :registrations_open], registrations_open)
+ Pleroma.Config.put([:instance, :invites_enabled], invites_enabled)
+ :ok
+ end)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/pleroma/admin/email_invite?email=foo@bar.com&name=JD")
+
+ assert json_response(conn, :internal_server_error)
+ end
+
+ test "it returns 500 if `registrations_open` is enabled", %{conn: conn, user: user} do
+ registrations_open = Pleroma.Config.get([:instance, :registrations_open])
+ invites_enabled = Pleroma.Config.get([:instance, :invites_enabled])
+ Pleroma.Config.put([:instance, :registrations_open], true)
+ Pleroma.Config.put([:instance, :invites_enabled], true)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :registrations_open], registrations_open)
+ Pleroma.Config.put([:instance, :invites_enabled], invites_enabled)
+ :ok
+ end)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/pleroma/admin/email_invite?email=foo@bar.com&name=JD")
+
+ assert json_response(conn, :internal_server_error)
+ end
+ end
+
test "/api/pleroma/admin/invite_token" do
admin = insert(:user, info: %{is_admin: true})
@@ -108,4 +417,368 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
assert conn.status == 200
end
+
+ describe "GET /api/pleroma/admin/users" do
+ test "renders users array for the first page" do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user, local: false, tags: ["foo", "bar"])
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?page=1")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => admin.info.deactivated,
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "roles" => %{"admin" => true, "moderator" => false},
+ "local" => true,
+ "tags" => []
+ },
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => false,
+ "tags" => ["foo", "bar"]
+ }
+ ]
+ }
+ end
+
+ test "renders empty array for the second page" do
+ admin = insert(:user, info: %{is_admin: true})
+ insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?page=2")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => []
+ }
+ end
+
+ test "regular search" do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user, nickname: "bob")
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?query=bo")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => []
+ }
+ ]
+ }
+ end
+
+ test "regular search with page size" do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user, nickname: "aalice")
+ user2 = insert(:user, nickname: "alice")
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?query=a&page_size=1&page=1")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 1,
+ "users" => [
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => []
+ }
+ ]
+ }
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?query=a&page_size=1&page=2")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 1,
+ "users" => [
+ %{
+ "deactivated" => user2.info.deactivated,
+ "id" => user2.id,
+ "nickname" => user2.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => []
+ }
+ ]
+ }
+ end
+
+ test "only local users" do
+ admin = insert(:user, info: %{is_admin: true}, nickname: "john")
+ user = insert(:user, nickname: "bob")
+
+ insert(:user, nickname: "bobb", local: false)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?query=bo&filters=local")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => []
+ }
+ ]
+ }
+ end
+
+ test "only local users with no query" do
+ admin = insert(:user, info: %{is_admin: true}, nickname: "john")
+ user = insert(:user, nickname: "bob")
+
+ insert(:user, nickname: "bobb", local: false)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?filters=local")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => []
+ },
+ %{
+ "deactivated" => admin.info.deactivated,
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "roles" => %{"admin" => true, "moderator" => false},
+ "local" => true,
+ "tags" => []
+ }
+ ]
+ }
+ end
+
+ test "it works with multiple filters" do
+ admin = insert(:user, nickname: "john", info: %{is_admin: true})
+ user = insert(:user, nickname: "bob", local: false, info: %{deactivated: true})
+
+ insert(:user, nickname: "ken", local: true, info: %{deactivated: true})
+ insert(:user, nickname: "bobb", local: false, info: %{deactivated: false})
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?filters=deactivated,external")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => user.local,
+ "tags" => []
+ }
+ ]
+ }
+ end
+ end
+
+ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
+
+ assert json_response(conn, 200) ==
+ %{
+ "deactivated" => !user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => []
+ }
+ end
+
+ describe "GET /api/pleroma/admin/invite_token" do
+ test "without options" do
+ admin = insert(:user, info: %{is_admin: true})
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/invite_token")
+
+ token = json_response(conn, 200)
+ invite = UserInviteToken.find_by_token!(token)
+ refute invite.used
+ refute invite.expires_at
+ refute invite.max_use
+ assert invite.invite_type == "one_time"
+ end
+
+ test "with expires_at" do
+ admin = insert(:user, info: %{is_admin: true})
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/invite_token", %{
+ "invite" => %{"expires_at" => Date.to_string(Date.utc_today())}
+ })
+
+ token = json_response(conn, 200)
+ invite = UserInviteToken.find_by_token!(token)
+
+ refute invite.used
+ assert invite.expires_at == Date.utc_today()
+ refute invite.max_use
+ assert invite.invite_type == "date_limited"
+ end
+
+ test "with max_use" do
+ admin = insert(:user, info: %{is_admin: true})
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/invite_token", %{
+ "invite" => %{"max_use" => 150}
+ })
+
+ token = json_response(conn, 200)
+ invite = UserInviteToken.find_by_token!(token)
+ refute invite.used
+ refute invite.expires_at
+ assert invite.max_use == 150
+ assert invite.invite_type == "reusable"
+ end
+
+ test "with max use and expires_at" do
+ admin = insert(:user, info: %{is_admin: true})
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/invite_token", %{
+ "invite" => %{"max_use" => 150, "expires_at" => Date.to_string(Date.utc_today())}
+ })
+
+ token = json_response(conn, 200)
+ invite = UserInviteToken.find_by_token!(token)
+ refute invite.used
+ assert invite.expires_at == Date.utc_today()
+ assert invite.max_use == 150
+ assert invite.invite_type == "reusable_date_limited"
+ end
+ end
+
+ describe "GET /api/pleroma/admin/invites" do
+ test "no invites" do
+ admin = insert(:user, info: %{is_admin: true})
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/invites")
+
+ assert json_response(conn, 200) == %{"invites" => []}
+ end
+
+ test "with invite" do
+ admin = insert(:user, info: %{is_admin: true})
+ {:ok, invite} = UserInviteToken.create_invite()
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/invites")
+
+ assert json_response(conn, 200) == %{
+ "invites" => [
+ %{
+ "expires_at" => nil,
+ "id" => invite.id,
+ "invite_type" => "one_time",
+ "max_use" => nil,
+ "token" => invite.token,
+ "used" => false,
+ "uses" => 0
+ }
+ ]
+ }
+ end
+ end
+
+ describe "POST /api/pleroma/admin/revoke_invite" do
+ test "with token" do
+ admin = insert(:user, info: %{is_admin: true})
+ {:ok, invite} = UserInviteToken.create_invite()
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> post("/api/pleroma/admin/revoke_invite", %{"token" => invite.token})
+
+ assert json_response(conn, 200) == %{
+ "expires_at" => nil,
+ "id" => invite.id,
+ "invite_type" => "one_time",
+ "max_use" => nil,
+ "token" => invite.token,
+ "used" => true,
+ "uses" => 0
+ }
+ end
+ end
end
diff --git a/test/web/admin_api/search_test.exs b/test/web/admin_api/search_test.exs
new file mode 100644
index 000000000..3950996ed
--- /dev/null
+++ b/test/web/admin_api/search_test.exs
@@ -0,0 +1,88 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.SearchTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.Web.AdminAPI.Search
+
+ import Pleroma.Factory
+
+ describe "search for admin" do
+ test "it ignores case" do
+ insert(:user, nickname: "papercoach")
+ insert(:user, nickname: "CanadaPaperCoach")
+
+ {:ok, _results, count} =
+ Search.user(%{
+ query: "paper",
+ local: false,
+ page: 1,
+ page_size: 50
+ })
+
+ assert count == 2
+ end
+
+ test "it returns local/external users" do
+ insert(:user, local: true)
+ insert(:user, local: false)
+ insert(:user, local: false)
+
+ {:ok, _results, local_count} =
+ Search.user(%{
+ query: "",
+ local: true
+ })
+
+ {:ok, _results, external_count} =
+ Search.user(%{
+ query: "",
+ external: true
+ })
+
+ assert local_count == 1
+ assert external_count == 2
+ end
+
+ test "it returns active/deactivated users" do
+ insert(:user, info: %{deactivated: true})
+ insert(:user, info: %{deactivated: true})
+ insert(:user, info: %{deactivated: false})
+
+ {:ok, _results, active_count} =
+ Search.user(%{
+ query: "",
+ active: true
+ })
+
+ {:ok, _results, deactivated_count} =
+ Search.user(%{
+ query: "",
+ deactivated: true
+ })
+
+ assert active_count == 1
+ assert deactivated_count == 2
+ end
+
+ test "it returns specific user" do
+ insert(:user)
+ insert(:user)
+ insert(:user, nickname: "bob", local: true, info: %{deactivated: false})
+
+ {:ok, _results, total_count} = Search.user(%{query: ""})
+
+ {:ok, _results, count} =
+ Search.user(%{
+ query: "Bo",
+ active: true,
+ local: true
+ })
+
+ assert total_count == 3
+ assert count == 1
+ end
+ end
+end
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 3dc5f6f84..b9ed088e4 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -1,10 +1,34 @@
-defmodule Pleroma.Web.CommonAPI.Test do
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.CommonAPITest do
use Pleroma.DataCase
+ alias Pleroma.Activity
+ alias Pleroma.User
+ alias Pleroma.Object
alias Pleroma.Web.CommonAPI
- alias Pleroma.{User, Object}
import Pleroma.Factory
+ test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
+ har = insert(:user)
+ jafnhar = insert(:user)
+ tridi = insert(:user)
+ option = Pleroma.Config.get([:instance, :safe_dm_mentions])
+ Pleroma.Config.put([:instance, :safe_dm_mentions], true)
+
+ {:ok, activity} =
+ CommonAPI.post(har, %{
+ "status" => "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
+ "visibility" => "direct"
+ })
+
+ refute tridi.ap_id in activity.recipients
+ assert jafnhar.ap_id in activity.recipients
+ Pleroma.Config.put([:instance, :safe_dm_mentions], option)
+ end
+
test "it de-duplicates tags" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"})
@@ -14,6 +38,13 @@ defmodule Pleroma.Web.CommonAPI.Test do
assert object.data["tag"] == ["2hu"]
end
+ test "it adds emoji in the object" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => ":moominmamma:"})
+
+ assert activity.data["object"]["emoji"]["moominmamma"]
+ end
+
test "it adds emoji when updating profiles" do
user = insert(:user, %{name: ":karjalanpiirakka:"})
@@ -57,4 +88,183 @@ defmodule Pleroma.Web.CommonAPI.Test do
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
end
end
+
+ describe "reactions" do
+ test "repeating a status" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+
+ {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
+ end
+
+ test "favoriting a status" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+
+ {:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)
+ end
+
+ test "retweeting a status twice returns an error" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+ {:ok, %Activity{}, _object} = CommonAPI.repeat(activity.id, user)
+ {:error, _} = CommonAPI.repeat(activity.id, user)
+ end
+
+ test "favoriting a status twice returns an error" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+ {:ok, %Activity{}, _object} = CommonAPI.favorite(activity.id, user)
+ {:error, _} = CommonAPI.favorite(activity.id, user)
+ end
+ end
+
+ describe "pinned statuses" do
+ setup do
+ Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+ [user: user, activity: activity]
+ end
+
+ test "pin status", %{user: user, activity: activity} do
+ assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
+
+ id = activity.id
+ user = refresh_record(user)
+
+ assert %User{info: %{pinned_activities: [^id]}} = user
+ end
+
+ test "only self-authored can be pinned", %{activity: activity} do
+ user = insert(:user)
+
+ assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
+ end
+
+ test "max pinned statuses", %{user: user, activity: activity_one} do
+ {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+ assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
+
+ user = refresh_record(user)
+
+ assert {:error, "You have already pinned the maximum number of statuses"} =
+ CommonAPI.pin(activity_two.id, user)
+ end
+
+ test "unpin status", %{user: user, activity: activity} do
+ {:ok, activity} = CommonAPI.pin(activity.id, user)
+
+ user = refresh_record(user)
+
+ assert {:ok, ^activity} = CommonAPI.unpin(activity.id, user)
+
+ user = refresh_record(user)
+
+ assert %User{info: %{pinned_activities: []}} = user
+ end
+
+ test "should unpin when deleting a status", %{user: user, activity: activity} do
+ {:ok, activity} = CommonAPI.pin(activity.id, user)
+
+ user = refresh_record(user)
+
+ assert {:ok, _} = CommonAPI.delete(activity.id, user)
+
+ user = refresh_record(user)
+
+ assert %User{info: %{pinned_activities: []}} = user
+ end
+ end
+
+ describe "mute tests" do
+ setup do
+ user = insert(:user)
+
+ activity = insert(:note_activity)
+
+ [user: user, activity: activity]
+ end
+
+ test "add mute", %{user: user, activity: activity} do
+ {:ok, _} = CommonAPI.add_mute(user, activity)
+ assert CommonAPI.thread_muted?(user, activity)
+ end
+
+ test "remove mute", %{user: user, activity: activity} do
+ CommonAPI.add_mute(user, activity)
+ {:ok, _} = CommonAPI.remove_mute(user, activity)
+ refute CommonAPI.thread_muted?(user, activity)
+ end
+
+ test "check that mutes can't be duplicate", %{user: user, activity: activity} do
+ CommonAPI.add_mute(user, activity)
+ {:error, _} = CommonAPI.add_mute(user, activity)
+ end
+ end
+
+ describe "reports" do
+ test "creates a report" do
+ reporter = insert(:user)
+ target_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
+
+ reporter_ap_id = reporter.ap_id
+ target_ap_id = target_user.ap_id
+ activity_ap_id = activity.data["id"]
+ comment = "foobar"
+
+ report_data = %{
+ "account_id" => target_user.id,
+ "comment" => comment,
+ "status_ids" => [activity.id]
+ }
+
+ assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
+
+ assert %Activity{
+ actor: ^reporter_ap_id,
+ data: %{
+ "type" => "Flag",
+ "content" => ^comment,
+ "object" => [^target_ap_id, ^activity_ap_id]
+ }
+ } = flag_activity
+ end
+ end
+
+ describe "reblog muting" do
+ setup do
+ muter = insert(:user)
+
+ muted = insert(:user)
+
+ [muter: muter, muted: muted]
+ end
+
+ test "add a reblog mute", %{muter: muter, muted: muted} do
+ {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
+
+ assert Pleroma.User.showing_reblogs?(muter, muted) == false
+ end
+
+ test "remove a reblog mute", %{muter: muter, muted: muted} do
+ {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
+ {:ok, muter} = CommonAPI.show_reblogs(muter, muted)
+
+ assert Pleroma.User.showing_reblogs?(muter, muted) == true
+ end
+ end
end
diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs
index b01ce04f8..f0c59d5c3 100644
--- a/test/web/common_api/common_api_utils_test.exs
+++ b/test/web/common_api/common_api_utils_test.exs
@@ -1,7 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.CommonAPI.UtilsTest do
+ alias Pleroma.Builders.UserBuilder
+ alias Pleroma.Object
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.Endpoint
- alias Pleroma.Builders.{UserBuilder}
use Pleroma.DataCase
test "it adds attachment links to a given text and attachment set" do
@@ -52,4 +57,136 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
assert expected == Utils.emoji_from_profile(user)
end
+
+ describe "format_input/3" do
+ test "works for bare text/plain" do
+ text = "hello world!"
+ expected = "hello world!"
+
+ {output, [], []} = Utils.format_input(text, "text/plain")
+
+ assert output == expected
+
+ text = "hello world!\n\nsecond paragraph!"
+ expected = "hello world!<br><br>second paragraph!"
+
+ {output, [], []} = Utils.format_input(text, "text/plain")
+
+ assert output == expected
+ end
+
+ test "works for bare text/html" do
+ text = "<p>hello world!</p>"
+ expected = "<p>hello world!</p>"
+
+ {output, [], []} = Utils.format_input(text, "text/html")
+
+ assert output == expected
+
+ text = "<p>hello world!</p>\n\n<p>second paragraph</p>"
+ expected = "<p>hello world!</p>\n\n<p>second paragraph</p>"
+
+ {output, [], []} = Utils.format_input(text, "text/html")
+
+ assert output == expected
+ end
+
+ test "works for bare text/markdown" do
+ text = "**hello world**"
+ expected = "<p><strong>hello world</strong></p>\n"
+
+ {output, [], []} = Utils.format_input(text, "text/markdown")
+
+ assert output == expected
+
+ text = "**hello world**\n\n*another paragraph*"
+ expected = "<p><strong>hello world</strong></p>\n<p><em>another paragraph</em></p>\n"
+
+ {output, [], []} = Utils.format_input(text, "text/markdown")
+
+ assert output == expected
+
+ text = """
+ > cool quote
+
+ by someone
+ """
+
+ expected = "<blockquote><p>cool quote</p>\n</blockquote>\n<p>by someone</p>\n"
+
+ {output, [], []} = Utils.format_input(text, "text/markdown")
+
+ assert output == expected
+ end
+
+ test "works for text/markdown with mentions" do
+ {:ok, user} =
+ UserBuilder.insert(%{nickname: "user__test", ap_id: "http://foo.com/user__test"})
+
+ text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*"
+
+ expected =
+ "<p><strong>hello world</strong></p>\n<p><em>another <span class=\"h-card\"><a data-user=\"#{
+ user.id
+ }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> and <span class=\"h-card\"><a data-user=\"#{
+ user.id
+ }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> <a href=\"http://google.com\">google.com</a> paragraph</em></p>\n"
+
+ {output, _, _} = Utils.format_input(text, "text/markdown")
+
+ assert output == expected
+ end
+ end
+
+ describe "context_to_conversation_id" do
+ test "creates a mapping object" do
+ conversation_id = Utils.context_to_conversation_id("random context")
+ object = Object.get_by_ap_id("random context")
+
+ assert conversation_id == object.id
+ end
+
+ test "returns an existing mapping for an existing object" do
+ {:ok, object} = Object.context_mapping("random context") |> Repo.insert()
+ conversation_id = Utils.context_to_conversation_id("random context")
+
+ assert conversation_id == object.id
+ end
+ end
+
+ describe "formats date to asctime" do
+ test "when date is in ISO 8601 format" do
+ date = DateTime.utc_now() |> DateTime.to_iso8601()
+
+ expected =
+ date
+ |> DateTime.from_iso8601()
+ |> elem(1)
+ |> Calendar.Strftime.strftime!("%a %b %d %H:%M:%S %z %Y")
+
+ assert Utils.date_to_asctime(date) == expected
+ end
+
+ test "when date is a binary in wrong format" do
+ date = DateTime.utc_now()
+
+ expected = ""
+
+ assert Utils.date_to_asctime(date) == expected
+ end
+
+ test "when date is a Unix timestamp" do
+ date = DateTime.utc_now() |> DateTime.to_unix()
+
+ expected = ""
+
+ assert Utils.date_to_asctime(date) == expected
+ end
+
+ test "when date is nil" do
+ expected = ""
+
+ assert Utils.date_to_asctime(nil) == expected
+ end
+ end
end
diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs
index 02e1ca76e..52729eb50 100644
--- a/test/web/federator_test.exs
+++ b/test/web/federator_test.exs
@@ -1,24 +1,18 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.FederatorTest do
- alias Pleroma.Web.Federator
+ alias Pleroma.Instances
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Federator
use Pleroma.DataCase
import Pleroma.Factory
import Mock
- test "enqueues an element according to priority" do
- queue = [%{item: 1, priority: 2}]
-
- new_queue = Federator.enqueue_sorted(queue, 2, 1)
- assert new_queue == [%{item: 2, priority: 1}, %{item: 1, priority: 2}]
-
- new_queue = Federator.enqueue_sorted(queue, 2, 3)
- assert new_queue == [%{item: 1, priority: 2}, %{item: 2, priority: 3}]
- end
-
- test "pop first item" do
- queue = [%{item: 2, priority: 1}, %{item: 1, priority: 2}]
-
- assert {2, [%{item: 1, priority: 2}]} = Federator.queue_pop(queue)
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
end
describe "Publish an activity" do
@@ -40,7 +34,7 @@ defmodule Pleroma.Web.FederatorTest do
relay_mock: relay_mock
} do
with_mocks([relay_mock]) do
- Federator.handle(:publish, activity)
+ Federator.publish(activity)
end
assert_received :relay_publish
@@ -53,7 +47,7 @@ defmodule Pleroma.Web.FederatorTest do
Pleroma.Config.put([:instance, :allow_relay], false)
with_mocks([relay_mock]) do
- Federator.handle(:publish, activity)
+ Federator.publish(activity)
end
refute_received :relay_publish
@@ -62,6 +56,122 @@ defmodule Pleroma.Web.FederatorTest do
end
end
+ describe "Targets reachability filtering in `publish`" do
+ test_with_mock "it federates only to reachable instances via AP",
+ Federator,
+ [:passthrough],
+ [] do
+ user = insert(:user)
+
+ {inbox1, inbox2} =
+ {"https://domain.com/users/nick1/inbox", "https://domain2.com/users/nick2/inbox"}
+
+ insert(:user, %{
+ local: false,
+ nickname: "nick1@domain.com",
+ ap_id: "https://domain.com/users/nick1",
+ info: %{ap_enabled: true, source_data: %{"inbox" => inbox1}}
+ })
+
+ insert(:user, %{
+ local: false,
+ nickname: "nick2@domain2.com",
+ ap_id: "https://domain2.com/users/nick2",
+ info: %{ap_enabled: true, source_data: %{"inbox" => inbox2}}
+ })
+
+ dt = NaiveDateTime.utc_now()
+ Instances.set_unreachable(inbox1, dt)
+
+ Instances.set_consistently_unreachable(URI.parse(inbox2).host)
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"})
+
+ assert called(Federator.publish_single_ap(%{inbox: inbox1, unreachable_since: dt}))
+
+ refute called(Federator.publish_single_ap(%{inbox: inbox2}))
+ end
+
+ test_with_mock "it federates only to reachable instances via Websub",
+ Federator,
+ [:passthrough],
+ [] do
+ user = insert(:user)
+ websub_topic = Pleroma.Web.OStatus.feed_path(user)
+
+ sub1 =
+ insert(:websub_subscription, %{
+ topic: websub_topic,
+ state: "active",
+ callback: "http://pleroma.soykaf.com/cb"
+ })
+
+ sub2 =
+ insert(:websub_subscription, %{
+ topic: websub_topic,
+ state: "active",
+ callback: "https://pleroma2.soykaf.com/cb"
+ })
+
+ dt = NaiveDateTime.utc_now()
+ Instances.set_unreachable(sub2.callback, dt)
+
+ Instances.set_consistently_unreachable(sub1.callback)
+
+ {:ok, _activity} = CommonAPI.post(user, %{"status" => "HI"})
+
+ assert called(
+ Federator.publish_single_websub(%{
+ callback: sub2.callback,
+ unreachable_since: dt
+ })
+ )
+
+ refute called(Federator.publish_single_websub(%{callback: sub1.callback}))
+ end
+
+ test_with_mock "it federates only to reachable instances via Salmon",
+ Federator,
+ [:passthrough],
+ [] do
+ user = insert(:user)
+
+ remote_user1 =
+ insert(:user, %{
+ local: false,
+ nickname: "nick1@domain.com",
+ ap_id: "https://domain.com/users/nick1",
+ info: %{salmon: "https://domain.com/salmon"}
+ })
+
+ remote_user2 =
+ insert(:user, %{
+ local: false,
+ nickname: "nick2@domain2.com",
+ ap_id: "https://domain2.com/users/nick2",
+ info: %{salmon: "https://domain2.com/salmon"}
+ })
+
+ dt = NaiveDateTime.utc_now()
+ Instances.set_unreachable(remote_user2.ap_id, dt)
+
+ Instances.set_consistently_unreachable("domain.com")
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"})
+
+ assert called(
+ Federator.publish_single_salmon(%{
+ recipient: remote_user2,
+ unreachable_since: dt
+ })
+ )
+
+ refute called(Federator.publish_single_websub(%{recipient: remote_user1}))
+ end
+ end
+
describe "Receive an activity" do
test "successfully processes incoming AP docs with correct origin" do
params = %{
@@ -78,7 +188,7 @@ defmodule Pleroma.Web.FederatorTest do
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
}
- {:ok, _activity} = Federator.handle(:incoming_ap_doc, params)
+ {:ok, _activity} = Federator.incoming_ap_doc(params)
end
test "rejects incoming AP docs with incorrect origin" do
@@ -96,7 +206,7 @@ defmodule Pleroma.Web.FederatorTest do
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
}
- :error = Federator.handle(:incoming_ap_doc, params)
+ :error = Federator.incoming_ap_doc(params)
end
end
end
diff --git a/test/web/http_sigs/http_sig_test.exs b/test/web/http_sigs/http_sig_test.exs
index b2bf8d61b..c4d2eaf78 100644
--- a/test/web/http_sigs/http_sig_test.exs
+++ b/test/web/http_sigs/http_sig_test.exs
@@ -1,12 +1,19 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
# http signatures
# Test data from https://tools.ietf.org/html/draft-cavage-http-signatures-08#appendix-C
defmodule Pleroma.Web.HTTPSignaturesTest do
use Pleroma.DataCase
alias Pleroma.Web.HTTPSignatures
import Pleroma.Factory
+ import Tesla.Mock
- @private_key hd(:public_key.pem_decode(File.read!("test/web/http_sigs/priv.key")))
- |> :public_key.pem_entry_decode()
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
@public_key hd(:public_key.pem_decode(File.read!("test/web/http_sigs/pub.key")))
|> :public_key.pem_entry_decode()
@@ -20,8 +27,6 @@ defmodule Pleroma.Web.HTTPSignaturesTest do
"content-length" => "18"
}
- @body "{\"hello\": \"world\"}"
-
@default_signature """
keyId="Test",algorithm="rsa-sha256",signature="jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w="
"""
diff --git a/test/web/instances/instance_test.exs b/test/web/instances/instance_test.exs
new file mode 100644
index 000000000..d28730994
--- /dev/null
+++ b/test/web/instances/instance_test.exs
@@ -0,0 +1,107 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Instances.InstanceTest do
+ alias Pleroma.Instances.Instance
+ alias Pleroma.Repo
+
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ setup_all do
+ config_path = [:instance, :federation_reachability_timeout_days]
+ initial_setting = Pleroma.Config.get(config_path)
+
+ Pleroma.Config.put(config_path, 1)
+ on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
+ :ok
+ end
+
+ describe "set_reachable/1" do
+ test "clears `unreachable_since` of existing matching Instance record having non-nil `unreachable_since`" do
+ instance = insert(:instance, unreachable_since: NaiveDateTime.utc_now())
+
+ assert {:ok, instance} = Instance.set_reachable(instance.host)
+ refute instance.unreachable_since
+ end
+
+ test "keeps nil `unreachable_since` of existing matching Instance record having nil `unreachable_since`" do
+ instance = insert(:instance, unreachable_since: nil)
+
+ assert {:ok, instance} = Instance.set_reachable(instance.host)
+ refute instance.unreachable_since
+ end
+
+ test "does NOT create an Instance record in case of no existing matching record" do
+ host = "domain.org"
+ assert nil == Instance.set_reachable(host)
+
+ assert [] = Repo.all(Ecto.Query.from(i in Instance))
+ assert Instance.reachable?(host)
+ end
+ end
+
+ describe "set_unreachable/1" do
+ test "creates new record having `unreachable_since` to current time if record does not exist" do
+ assert {:ok, instance} = Instance.set_unreachable("https://domain.com/path")
+
+ instance = Repo.get(Instance, instance.id)
+ assert instance.unreachable_since
+ assert "domain.com" == instance.host
+ end
+
+ test "sets `unreachable_since` of existing record having nil `unreachable_since`" do
+ instance = insert(:instance, unreachable_since: nil)
+ refute instance.unreachable_since
+
+ assert {:ok, _} = Instance.set_unreachable(instance.host)
+
+ instance = Repo.get(Instance, instance.id)
+ assert instance.unreachable_since
+ end
+
+ test "does NOT modify `unreachable_since` value of existing record in case it's present" do
+ instance =
+ insert(:instance, unreachable_since: NaiveDateTime.add(NaiveDateTime.utc_now(), -10))
+
+ assert instance.unreachable_since
+ initial_value = instance.unreachable_since
+
+ assert {:ok, _} = Instance.set_unreachable(instance.host)
+
+ instance = Repo.get(Instance, instance.id)
+ assert initial_value == instance.unreachable_since
+ end
+ end
+
+ describe "set_unreachable/2" do
+ test "sets `unreachable_since` value of existing record in case it's newer than supplied value" do
+ instance =
+ insert(:instance, unreachable_since: NaiveDateTime.add(NaiveDateTime.utc_now(), -10))
+
+ assert instance.unreachable_since
+
+ past_value = NaiveDateTime.add(NaiveDateTime.utc_now(), -100)
+ assert {:ok, _} = Instance.set_unreachable(instance.host, past_value)
+
+ instance = Repo.get(Instance, instance.id)
+ assert past_value == instance.unreachable_since
+ end
+
+ test "does NOT modify `unreachable_since` value of existing record in case it's equal to or older than supplied value" do
+ instance =
+ insert(:instance, unreachable_since: NaiveDateTime.add(NaiveDateTime.utc_now(), -10))
+
+ assert instance.unreachable_since
+ initial_value = instance.unreachable_since
+
+ assert {:ok, _} = Instance.set_unreachable(instance.host, NaiveDateTime.utc_now())
+
+ instance = Repo.get(Instance, instance.id)
+ assert initial_value == instance.unreachable_since
+ end
+ end
+end
diff --git a/test/web/instances/instances_test.exs b/test/web/instances/instances_test.exs
new file mode 100644
index 000000000..f0d84edea
--- /dev/null
+++ b/test/web/instances/instances_test.exs
@@ -0,0 +1,132 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.InstancesTest do
+ alias Pleroma.Instances
+
+ use Pleroma.DataCase
+
+ setup_all do
+ config_path = [:instance, :federation_reachability_timeout_days]
+ initial_setting = Pleroma.Config.get(config_path)
+
+ Pleroma.Config.put(config_path, 1)
+ on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
+ :ok
+ end
+
+ describe "reachable?/1" do
+ test "returns `true` for host / url with unknown reachability status" do
+ assert Instances.reachable?("unknown.site")
+ assert Instances.reachable?("http://unknown.site")
+ end
+
+ test "returns `false` for host / url marked unreachable for at least `reachability_datetime_threshold()`" do
+ host = "consistently-unreachable.name"
+ Instances.set_consistently_unreachable(host)
+
+ refute Instances.reachable?(host)
+ refute Instances.reachable?("http://#{host}/path")
+ end
+
+ test "returns `true` for host / url marked unreachable for less than `reachability_datetime_threshold()`" do
+ url = "http://eventually-unreachable.name/path"
+
+ Instances.set_unreachable(url)
+
+ assert Instances.reachable?(url)
+ assert Instances.reachable?(URI.parse(url).host)
+ end
+
+ test "returns true on non-binary input" do
+ assert Instances.reachable?(nil)
+ assert Instances.reachable?(1)
+ end
+ end
+
+ describe "filter_reachable/1" do
+ setup do
+ host = "consistently-unreachable.name"
+ url1 = "http://eventually-unreachable.com/path"
+ url2 = "http://domain.com/path"
+
+ Instances.set_consistently_unreachable(host)
+ Instances.set_unreachable(url1)
+
+ result = Instances.filter_reachable([host, url1, url2, nil])
+ %{result: result, url1: url1, url2: url2}
+ end
+
+ test "returns a map with keys containing 'not marked consistently unreachable' elements of supplied list",
+ %{result: result, url1: url1, url2: url2} do
+ assert is_map(result)
+ assert Enum.sort([url1, url2]) == result |> Map.keys() |> Enum.sort()
+ end
+
+ test "returns a map with `unreachable_since` values for keys",
+ %{result: result, url1: url1, url2: url2} do
+ assert is_map(result)
+ assert %NaiveDateTime{} = result[url1]
+ assert is_nil(result[url2])
+ end
+
+ test "returns an empty map for empty list or list containing no hosts / url" do
+ assert %{} == Instances.filter_reachable([])
+ assert %{} == Instances.filter_reachable([nil])
+ end
+ end
+
+ describe "set_reachable/1" do
+ test "sets unreachable url or host reachable" do
+ host = "domain.com"
+ Instances.set_consistently_unreachable(host)
+ refute Instances.reachable?(host)
+
+ Instances.set_reachable(host)
+ assert Instances.reachable?(host)
+ end
+
+ test "keeps reachable url or host reachable" do
+ url = "https://site.name?q="
+ assert Instances.reachable?(url)
+
+ Instances.set_reachable(url)
+ assert Instances.reachable?(url)
+ end
+
+ test "returns error status on non-binary input" do
+ assert {:error, _} = Instances.set_reachable(nil)
+ assert {:error, _} = Instances.set_reachable(1)
+ end
+ end
+
+ # Note: implementation-specific (e.g. Instance) details of set_unreachable/1
+ # should be tested in implementation-specific tests
+ describe "set_unreachable/1" do
+ test "returns error status on non-binary input" do
+ assert {:error, _} = Instances.set_unreachable(nil)
+ assert {:error, _} = Instances.set_unreachable(1)
+ end
+ end
+
+ describe "set_consistently_unreachable/1" do
+ test "sets reachable url or host unreachable" do
+ url = "http://domain.com?q="
+ assert Instances.reachable?(url)
+
+ Instances.set_consistently_unreachable(url)
+ refute Instances.reachable?(url)
+ end
+
+ test "keeps unreachable url or host unreachable" do
+ host = "site.name"
+ Instances.set_consistently_unreachable(host)
+ refute Instances.reachable?(host)
+
+ Instances.set_consistently_unreachable(host)
+ refute Instances.reachable?(host)
+ end
+ end
+end
diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs
index a2d3a2547..d7487bed9 100644
--- a/test/web/mastodon_api/account_view_test.exs
+++ b/test/web/mastodon_api/account_view_test.exs
@@ -1,8 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
use Pleroma.DataCase
import Pleroma.Factory
- alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.User
+ alias Pleroma.Web.MastodonAPI.AccountView
test "Represent a user account" do
source_data = %{
@@ -54,12 +58,33 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
note: "",
privacy: "public",
sensitive: false
+ },
+ pleroma: %{
+ confirmation_pending: false,
+ tags: [],
+ is_admin: false,
+ is_moderator: false,
+ relationship: %{}
}
}
assert expected == AccountView.render("account.json", %{user: user})
end
+ test "Represent the user account for the account owner" do
+ user = insert(:user)
+
+ notification_settings = %{
+ "remote" => true,
+ "local" => true,
+ "followers" => true,
+ "follows" => true
+ }
+
+ assert %{pleroma: %{notification_settings: ^notification_settings}} =
+ AccountView.render("account.json", %{user: user, for: user})
+ end
+
test "Represent a Service(bot) account" do
user =
insert(:user, %{
@@ -91,6 +116,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
note: "",
privacy: "public",
sensitive: false
+ },
+ pleroma: %{
+ confirmation_pending: false,
+ tags: [],
+ is_admin: false,
+ is_moderator: false,
+ relationship: %{}
}
}
@@ -124,12 +156,74 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
blocking: true,
muting: false,
muting_notifications: false,
+ subscribing: false,
requested: false,
domain_blocking: false,
- showing_reblogs: false,
+ showing_reblogs: true,
endorsed: false
}
assert expected == AccountView.render("relationship.json", %{user: user, target: other_user})
end
+
+ test "represent an embedded relationship" do
+ user =
+ insert(:user, %{
+ info: %{note_count: 5, follower_count: 3, source_data: %{"type" => "Service"}},
+ nickname: "shp@shitposter.club",
+ inserted_at: ~N[2017-08-15 15:47:06.597036]
+ })
+
+ other_user = insert(:user)
+
+ {:ok, other_user} = User.follow(other_user, user)
+ {:ok, other_user} = User.block(other_user, user)
+
+ expected = %{
+ id: to_string(user.id),
+ username: "shp",
+ acct: user.nickname,
+ display_name: user.name,
+ locked: false,
+ created_at: "2017-08-15T15:47:06.000Z",
+ followers_count: 3,
+ following_count: 0,
+ statuses_count: 5,
+ note: user.bio,
+ url: user.ap_id,
+ avatar: "http://localhost:4001/images/avi.png",
+ avatar_static: "http://localhost:4001/images/avi.png",
+ header: "http://localhost:4001/images/banner.png",
+ header_static: "http://localhost:4001/images/banner.png",
+ emojis: [],
+ fields: [],
+ bot: true,
+ source: %{
+ note: "",
+ privacy: "public",
+ sensitive: false
+ },
+ pleroma: %{
+ confirmation_pending: false,
+ tags: [],
+ is_admin: false,
+ is_moderator: false,
+ relationship: %{
+ id: to_string(user.id),
+ following: false,
+ followed_by: false,
+ blocking: true,
+ subscribing: false,
+ muting: false,
+ muting_notifications: false,
+ requested: false,
+ domain_blocking: false,
+ showing_reblogs: true,
+ endorsed: false
+ }
+ }
+ }
+
+ assert expected == AccountView.render("account.json", %{user: user, for: other_user})
+ end
end
diff --git a/test/web/mastodon_api/list_view_test.exs b/test/web/mastodon_api/list_view_test.exs
index 5e36872ed..73143467f 100644
--- a/test/web/mastodon_api/list_view_test.exs
+++ b/test/web/mastodon_api/list_view_test.exs
@@ -1,8 +1,11 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.MastodonAPI.ListViewTest do
use Pleroma.DataCase
import Pleroma.Factory
alias Pleroma.Web.MastodonAPI.ListView
- alias Pleroma.List
test "Represent a list" do
user = insert(:user)
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index c30f253d9..f21cf677d 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -1,13 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
use Pleroma.Web.ConnCase
- alias Pleroma.Web.TwitterAPI.TwitterAPI
- alias Pleroma.{Repo, User, Activity, Notification, Object}
- alias Pleroma.Web.{OStatus, CommonAPI}
+ alias Ecto.Changeset
+ alias Pleroma.Activity
+ alias Pleroma.Notification
+ alias Pleroma.Object
+ alias Pleroma.Repo
+ alias Pleroma.ScheduledActivity
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
-
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.FilterView
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OStatus
+ alias Pleroma.Web.Push
+ alias Pleroma.Web.TwitterAPI.TwitterAPI
import Pleroma.Factory
import ExUnit.CaptureLog
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
test "the home timeline", %{conn: conn} do
user = insert(:user)
@@ -20,7 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> assign(:user, user)
|> get("/api/v1/timelines/home")
- assert length(json_response(conn, 200)) == 0
+ assert Enum.empty?(json_response(conn, 200))
{:ok, user} = User.follow(user, following)
@@ -83,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
json_response(conn_one, 200)
- assert Repo.get(Activity, id)
+ assert Activity.get_by_id(id)
conn_two =
conn
@@ -122,7 +141,72 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
- assert Repo.get(Activity, id)
+ assert Activity.get_by_id(id)
+ end
+
+ test "posting a fake status", %{conn: conn} do
+ user = insert(:user)
+
+ real_conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" =>
+ "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
+ })
+
+ real_status = json_response(real_conn, 200)
+
+ assert real_status
+ assert Object.get_by_ap_id(real_status["uri"])
+
+ real_status =
+ real_status
+ |> Map.put("id", nil)
+ |> Map.put("url", nil)
+ |> Map.put("uri", nil)
+ |> Map.put("created_at", nil)
+ |> Kernel.put_in(["pleroma", "conversation_id"], nil)
+
+ fake_conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" =>
+ "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
+ "preview" => true
+ })
+
+ fake_status = json_response(fake_conn, 200)
+
+ assert fake_status
+ refute Object.get_by_ap_id(fake_status["uri"])
+
+ fake_status =
+ fake_status
+ |> Map.put("id", nil)
+ |> Map.put("url", nil)
+ |> Map.put("uri", nil)
+ |> Map.put("created_at", nil)
+ |> Kernel.put_in(["pleroma", "conversation_id"], nil)
+
+ assert real_status == fake_status
+ end
+
+ test "posting a status with OGP link preview", %{conn: conn} do
+ Pleroma.Config.put([:rich_media, :enabled], true)
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" => "http://example.com/ogp"
+ })
+
+ assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
+ assert Activity.get_by_id(id)
+ Pleroma.Config.put([:rich_media, :enabled], false)
end
test "posting a direct status", %{conn: conn} do
@@ -136,8 +220,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
- assert activity = Repo.get(Activity, id)
- assert activity.recipients == [user2.ap_id]
+ assert activity = Activity.get_by_id(id)
+ assert activity.recipients == [user2.ap_id, user1.ap_id]
assert activity.data["to"] == [user2.ap_id]
assert activity.data["cc"] == []
end
@@ -171,6 +255,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"visibility" => "direct"} = status
assert status["url"] != direct.data["id"]
+ # User should be able to see his own direct message
+ res_conn =
+ build_conn()
+ |> assign(:user, user_one)
+ |> get("api/v1/timelines/direct")
+
+ [status] = json_response(res_conn, 200)
+
+ assert %{"visibility" => "direct"} = status
+
# Both should be visible here
res_conn =
conn
@@ -206,6 +300,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert status["url"] != direct.data["id"]
end
+ test "doesn't include DMs from blocked users", %{conn: conn} do
+ blocker = insert(:user)
+ blocked = insert(:user)
+ user = insert(:user)
+ {:ok, blocker} = User.block(blocker, blocked)
+
+ {:ok, _blocked_direct} =
+ CommonAPI.post(blocked, %{
+ "status" => "Hi @#{blocker.nickname}!",
+ "visibility" => "direct"
+ })
+
+ {:ok, direct} =
+ CommonAPI.post(user, %{
+ "status" => "Hi @#{blocker.nickname}!",
+ "visibility" => "direct"
+ })
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> get("api/v1/timelines/direct")
+
+ [status] = json_response(res_conn, 200)
+ assert status["id"] == direct.id
+ end
+
test "replying to a status", %{conn: conn} do
user = insert(:user)
@@ -218,11 +339,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
- activity = Repo.get(Activity, id)
- object = Object.normalize(activity.data["object"])
+ activity = Activity.get_by_id(id)
assert activity.data["context"] == replied_to.data["context"]
- assert object.data["inReplyToStatusId"] == replied_to.id
+ assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
end
test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
@@ -235,7 +355,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
- activity = Repo.get(Activity, id)
+ activity = Activity.get_by_id(id)
assert activity
end
@@ -264,6 +384,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert id == to_string(user.id)
end
+ test "apps/verify_credentials", %{conn: conn} do
+ token = insert(:oauth_token)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> get("/api/v1/apps/verify_credentials")
+
+ app = Repo.preload(token, :app).app
+
+ expected = %{
+ "name" => app.client_name,
+ "website" => app.website,
+ "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
+ }
+
+ assert expected == json_response(conn, 200)
+ end
+
+ test "creates an oauth app", %{conn: conn} do
+ user = insert(:user)
+ app_attrs = build(:oauth_app)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/apps", %{
+ client_name: app_attrs.client_name,
+ redirect_uris: app_attrs.redirect_uris
+ })
+
+ [app] = Repo.all(App)
+
+ expected = %{
+ "name" => app.client_name,
+ "website" => app.website,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret,
+ "id" => app.id |> to_string(),
+ "redirect_uri" => app.redirect_uris,
+ "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
+ }
+
+ assert expected == json_response(conn, 200)
+ end
+
test "get a status", %{conn: conn} do
activity = insert(:note_activity)
@@ -287,7 +454,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{} = json_response(conn, 200)
- assert Repo.get(Activity, activity.id) == nil
+ refute Activity.get_by_id(activity.id)
end
test "when you didn't create it", %{conn: conn} do
@@ -301,7 +468,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"error" => _} = json_response(conn, 403)
- assert Repo.get(Activity, activity.id) == activity
+ assert Activity.get_by_id(activity.id) == activity
+ end
+
+ test "when you're an admin or moderator", %{conn: conn} do
+ activity1 = insert(:note_activity)
+ activity2 = insert(:note_activity)
+ admin = insert(:user, info: %{is_admin: true})
+ moderator = insert(:user, info: %{is_moderator: true})
+
+ res_conn =
+ conn
+ |> assign(:user, admin)
+ |> delete("/api/v1/statuses/#{activity1.id}")
+
+ assert %{} = json_response(res_conn, 200)
+
+ res_conn =
+ conn
+ |> assign(:user, moderator)
+ |> delete("/api/v1/statuses/#{activity2.id}")
+
+ assert %{} = json_response(res_conn, 200)
+
+ refute Activity.get_by_id(activity1.id)
+ refute Activity.get_by_id(activity2.id)
end
end
@@ -346,12 +537,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
{:ok, filter_one} = Pleroma.Filter.create(query_one)
{:ok, filter_two} = Pleroma.Filter.create(query_two)
- conn =
+ response =
conn
|> assign(:user, user)
|> get("/api/v1/filters")
-
- assert response = json_response(conn, 200)
+ |> json_response(200)
+
+ assert response ==
+ render_json(
+ FilterView,
+ "filters.json",
+ filters: [filter_two, filter_one]
+ )
end
test "get a filter", %{conn: conn} do
@@ -371,7 +568,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> assign(:user, user)
|> get("/api/v1/filters/#{filter.filter_id}")
- assert response = json_response(conn, 200)
+ assert _response = json_response(conn, 200)
end
test "update a filter", %{conn: conn} do
@@ -384,7 +581,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
context: ["home"]
}
- {:ok, filter} = Pleroma.Filter.create(query)
+ {:ok, _filter} = Pleroma.Filter.create(query)
new = %Pleroma.Filter{
phrase: "nii",
@@ -549,7 +746,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
other_user = insert(:user)
{:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
- {:ok, activity_two} =
+ {:ok, _activity_two} =
TwitterAPI.create_status(other_user, %{
"status" => "Marisa is cute.",
"visibility" => "private"
@@ -585,7 +782,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> get("/api/v1/notifications")
expected_response =
- "hi <span><a href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
+ "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
+ user.ap_id
+ }\">@<span>#{user.nickname}</span></a></span>"
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
assert response == expected_response
@@ -606,7 +805,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> get("/api/v1/notifications/#{notification.id}")
expected_response =
- "hi <span><a href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
+ "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
+ user.ap_id
+ }\">@<span>#{user.nickname}</span></a></span>"
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
assert response == expected_response
@@ -653,6 +854,148 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert all = json_response(conn, 200)
assert all == []
end
+
+ test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+ notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
+ notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
+ notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
+ notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
+
+ conn =
+ conn
+ |> assign(:user, user)
+
+ # min_id
+ conn_res =
+ conn
+ |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
+
+ result = json_response(conn_res, 200)
+ assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
+
+ # since_id
+ conn_res =
+ conn
+ |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
+
+ result = json_response(conn_res, 200)
+ assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
+
+ # max_id
+ conn_res =
+ conn
+ |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
+
+ result = json_response(conn_res, 200)
+ assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
+ end
+
+ test "filters notifications using exclude_types", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
+ {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
+ {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
+ {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
+
+ mention_notification_id =
+ Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
+
+ favorite_notification_id =
+ Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
+
+ reblog_notification_id =
+ Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
+
+ follow_notification_id =
+ Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
+
+ conn =
+ conn
+ |> assign(:user, user)
+
+ conn_res =
+ get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
+
+ assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
+
+ conn_res =
+ get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
+
+ assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
+
+ conn_res =
+ get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
+
+ assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
+
+ conn_res =
+ get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
+
+ assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
+ end
+
+ test "destroy multiple", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
+ {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
+
+ notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
+ notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
+ notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
+ notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
+
+ conn =
+ conn
+ |> assign(:user, user)
+
+ conn_res =
+ conn
+ |> get("/api/v1/notifications")
+
+ result = json_response(conn_res, 200)
+ assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
+
+ conn2 =
+ conn
+ |> assign(:user, other_user)
+
+ conn_res =
+ conn2
+ |> get("/api/v1/notifications")
+
+ result = json_response(conn_res, 200)
+ assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
+
+ conn_destroy =
+ conn
+ |> delete("/api/v1/notifications/destroy_multiple", %{
+ "ids" => [notification1_id, notification2_id]
+ })
+
+ assert json_response(conn_destroy, 200) == %{}
+
+ conn_res =
+ conn2
+ |> get("/api/v1/notifications")
+
+ result = json_response(conn_res, 200)
+ assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
+ end
end
describe "reblogging" do
@@ -665,8 +1008,41 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/reblog")
- assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
- json_response(conn, 200)
+ assert %{
+ "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
+ "reblogged" => true
+ } = json_response(conn, 200)
+
+ assert to_string(activity.id) == id
+ end
+
+ test "reblogged status for another user", %{conn: conn} do
+ activity = insert(:note_activity)
+ user1 = insert(:user)
+ user2 = insert(:user)
+ user3 = insert(:user)
+ {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
+ {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
+
+ conn_res =
+ conn
+ |> assign(:user, user3)
+ |> get("/api/v1/statuses/#{reblog_activity1.id}")
+
+ assert %{
+ "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
+ "reblogged" => false
+ } = json_response(conn_res, 200)
+
+ conn_res =
+ conn
+ |> assign(:user, user2)
+ |> get("/api/v1/statuses/#{reblog_activity1.id}")
+
+ assert %{
+ "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
+ "reblogged" => true
+ } = json_response(conn_res, 200)
assert to_string(activity.id) == id
end
@@ -805,7 +1181,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
}
media =
- TwitterAPI.upload(file, "json")
+ TwitterAPI.upload(file, user, "json")
|> Poison.decode!()
{:ok, image_post} =
@@ -825,6 +1201,26 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(image_post.id)
end
+
+ test "gets a user's statuses without reblogs", %{conn: conn} do
+ user = insert(:user)
+ {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
+ {:ok, _, _} = CommonAPI.repeat(post.id, user)
+
+ conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
+
+ assert [%{"id" => id}] = json_response(conn, 200)
+ assert id == to_string(post.id)
+
+ conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
+
+ assert [%{"id" => id}] = json_response(conn, 200)
+ assert id == to_string(post.id)
+ end
end
describe "user relationships" do
@@ -849,10 +1245,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
other_user = insert(:user)
- {:ok, activity} = ActivityPub.follow(other_user, user)
+ {:ok, _activity} = ActivityPub.follow(other_user, user)
- user = Repo.get(User, user.id)
- other_user = Repo.get(User, other_user.id)
+ user = User.get_by_id(user.id)
+ other_user = User.get_by_id(other_user.id)
assert User.following?(other_user, user) == false
@@ -866,13 +1262,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end
test "/api/v1/follow_requests/:id/authorize works" do
- user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
+ user = insert(:user, %{info: %User.Info{locked: true}})
other_user = insert(:user)
- {:ok, activity} = ActivityPub.follow(other_user, user)
+ {:ok, _activity} = ActivityPub.follow(other_user, user)
- user = Repo.get(User, user.id)
- other_user = Repo.get(User, other_user.id)
+ user = User.get_by_id(user.id)
+ other_user = User.get_by_id(other_user.id)
assert User.following?(other_user, user) == false
@@ -884,8 +1280,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
- user = Repo.get(User, user.id)
- other_user = Repo.get(User, other_user.id)
+ user = User.get_by_id(user.id)
+ other_user = User.get_by_id(other_user.id)
assert User.following?(other_user, user) == true
end
@@ -906,7 +1302,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
other_user = insert(:user)
- {:ok, activity} = ActivityPub.follow(other_user, user)
+ {:ok, _activity} = ActivityPub.follow(other_user, user)
+
+ user = User.get_by_id(user.id)
conn =
build_conn()
@@ -916,8 +1314,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
- user = Repo.get(User, user.id)
- other_user = Repo.get(User, other_user.id)
+ user = User.get_by_id(user.id)
+ other_user = User.get_by_id(other_user.id)
assert User.following?(other_user, user) == false
end
@@ -940,6 +1338,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"error" => "Can't find user"} = json_response(conn, 404)
end
+ test "account fetching also works nickname", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> get("/api/v1/accounts/#{user.nickname}")
+
+ assert %{"id" => id} = json_response(conn, 200)
+ assert id == user.id
+ end
+
test "media upload", %{conn: conn} do
file = %Plug.Upload{
content_type: "image/jpg",
@@ -960,6 +1369,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert media["type"] == "image"
assert media["description"] == desc
+ assert media["id"]
+
+ object = Repo.get(Object, media["id"])
+ assert object.data["actor"] == User.ap_id(user)
end
test "hashtag timeline", %{conn: conn} do
@@ -990,6 +1403,34 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end)
end
+ test "multi-hashtag timeline", %{conn: conn} do
+ user = insert(:user)
+
+ {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
+ {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
+ {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
+
+ any_test =
+ conn
+ |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
+
+ [status_none, status_test1, status_test] = json_response(any_test, 200)
+
+ assert to_string(activity_test.id) == status_test["id"]
+ assert to_string(activity_test1.id) == status_test1["id"]
+ assert to_string(activity_none.id) == status_none["id"]
+
+ restricted_test =
+ conn
+ |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
+
+ assert [status_test1] == json_response(restricted_test, 200)
+
+ all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
+
+ assert [status_none] == json_response(all_test, 200)
+ end
+
test "getting followers", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
@@ -1003,6 +1444,72 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert id == to_string(user.id)
end
+ test "getting followers, hide_followers", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user, %{info: %{hide_followers: true}})
+ {:ok, _user} = User.follow(user, other_user)
+
+ conn =
+ conn
+ |> get("/api/v1/accounts/#{other_user.id}/followers")
+
+ assert [] == json_response(conn, 200)
+ end
+
+ test "getting followers, hide_followers, same user requesting", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user, %{info: %{hide_followers: true}})
+ {:ok, _user} = User.follow(user, other_user)
+
+ conn =
+ conn
+ |> assign(:user, other_user)
+ |> get("/api/v1/accounts/#{other_user.id}/followers")
+
+ refute [] == json_response(conn, 200)
+ end
+
+ test "getting followers, pagination", %{conn: conn} do
+ user = insert(:user)
+ follower1 = insert(:user)
+ follower2 = insert(:user)
+ follower3 = insert(:user)
+ {:ok, _} = User.follow(follower1, user)
+ {:ok, _} = User.follow(follower2, user)
+ {:ok, _} = User.follow(follower3, user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+
+ res_conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
+
+ assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
+ assert id3 == follower3.id
+ assert id2 == follower2.id
+
+ res_conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
+
+ assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
+ assert id2 == follower2.id
+ assert id1 == follower1.id
+
+ res_conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
+
+ assert [%{"id" => id2}] = json_response(res_conn, 200)
+ assert id2 == follower2.id
+
+ assert [link_header] = get_resp_header(res_conn, "link")
+ assert link_header =~ ~r/min_id=#{follower2.id}/
+ assert link_header =~ ~r/max_id=#{follower2.id}/
+ end
+
test "getting following", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
@@ -1016,6 +1523,72 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert id == to_string(other_user.id)
end
+ test "getting following, hide_follows", %{conn: conn} do
+ user = insert(:user, %{info: %{hide_follows: true}})
+ other_user = insert(:user)
+ {:ok, user} = User.follow(user, other_user)
+
+ conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/following")
+
+ assert [] == json_response(conn, 200)
+ end
+
+ test "getting following, hide_follows, same user requesting", %{conn: conn} do
+ user = insert(:user, %{info: %{hide_follows: true}})
+ other_user = insert(:user)
+ {:ok, user} = User.follow(user, other_user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/#{user.id}/following")
+
+ refute [] == json_response(conn, 200)
+ end
+
+ test "getting following, pagination", %{conn: conn} do
+ user = insert(:user)
+ following1 = insert(:user)
+ following2 = insert(:user)
+ following3 = insert(:user)
+ {:ok, _} = User.follow(user, following1)
+ {:ok, _} = User.follow(user, following2)
+ {:ok, _} = User.follow(user, following3)
+
+ conn =
+ conn
+ |> assign(:user, user)
+
+ res_conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
+
+ assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
+ assert id3 == following3.id
+ assert id2 == following2.id
+
+ res_conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
+
+ assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
+ assert id2 == following2.id
+ assert id1 == following1.id
+
+ res_conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
+
+ assert [%{"id" => id2}] = json_response(res_conn, 200)
+ assert id2 == following2.id
+
+ assert [link_header] = get_resp_header(res_conn, "link")
+ assert link_header =~ ~r/min_id=#{following2.id}/
+ assert link_header =~ ~r/max_id=#{following2.id}/
+ end
+
test "following / unfollowing a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
@@ -1027,7 +1600,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"id" => _id, "following" => true} = json_response(conn, 200)
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
conn =
build_conn()
@@ -1036,7 +1609,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"id" => _id, "following" => false} = json_response(conn, 200)
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
conn =
build_conn()
@@ -1047,6 +1620,95 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert id == to_string(other_user.id)
end
+ test "following / unfollowing errors" do
+ user = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+
+ # self follow
+ conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
+ assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+
+ # self unfollow
+ user = User.get_cached_by_id(user.id)
+ conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
+ assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+
+ # self follow via uri
+ user = User.get_cached_by_id(user.id)
+ conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
+ assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+
+ # follow non existing user
+ conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
+ assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+
+ # follow non existing user via uri
+ conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
+ assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+
+ # unfollow non existing user
+ conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
+ assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+ end
+
+ test "muting / unmuting a user", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/accounts/#{other_user.id}/mute")
+
+ assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
+
+ user = User.get_by_id(user.id)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> post("/api/v1/accounts/#{other_user.id}/unmute")
+
+ assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
+ end
+
+ test "subscribing / unsubscribing to a user", %{conn: conn} do
+ user = insert(:user)
+ subscription_target = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
+
+ assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
+
+ assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
+ end
+
+ test "getting a list of mutes", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, user} = User.mute(user, other_user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/mutes")
+
+ other_user_id = to_string(other_user.id)
+ assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
+ end
+
test "blocking / unblocking a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
@@ -1058,7 +1720,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
- user = Repo.get(User, user.id)
+ user = User.get_by_id(user.id)
conn =
build_conn()
@@ -1123,26 +1785,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert "even.worse.site" in domain_blocks
end
- test "unimplemented mute endpoints" do
+ test "unimplemented follow_requests, blocks, domain blocks" do
user = insert(:user)
- other_user = insert(:user)
- ["mute", "unmute"]
- |> Enum.each(fn endpoint ->
- conn =
- build_conn()
- |> assign(:user, user)
- |> post("/api/v1/accounts/#{other_user.id}/#{endpoint}")
-
- assert %{"id" => id} = json_response(conn, 200)
- assert id == to_string(other_user.id)
- end)
- end
-
- test "unimplemented mutes, follow_requests, blocks, domain blocks" do
- user = insert(:user)
-
- ["blocks", "domain_blocks", "mutes", "follow_requests"]
+ ["blocks", "domain_blocks", "follow_requests"]
|> Enum.each(fn endpoint ->
conn =
build_conn()
@@ -1223,6 +1869,24 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end)
end
+ test "search doesn't show statuses that it shouldn't", %{conn: conn} do
+ {:ok, activity} =
+ CommonAPI.post(insert(:user), %{
+ "status" => "This is about 2hu, but private",
+ "visibility" => "private"
+ })
+
+ capture_log(fn ->
+ conn =
+ conn
+ |> get("/api/v1/search", %{"q" => activity.data["object"]["id"]})
+
+ assert results = json_response(conn, 200)
+
+ [] = results["statuses"]
+ end)
+ end
+
test "search fetches remote accounts", %{conn: conn} do
conn =
conn
@@ -1242,13 +1906,42 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
- conn =
+ first_conn =
conn
|> assign(:user, user)
|> get("/api/v1/favourites")
- assert [status] = json_response(conn, 200)
+ assert [status] = json_response(first_conn, 200)
assert status["id"] == to_string(activity.id)
+
+ assert [{"link", _link_header}] =
+ Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
+
+ # Honours query params
+ {:ok, second_activity} =
+ CommonAPI.post(other_user, %{
+ "status" =>
+ "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
+ })
+
+ {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
+
+ last_like = status["id"]
+
+ second_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/favourites?since_id=#{last_like}")
+
+ assert [second_status] = json_response(second_conn, 200)
+ assert second_status["id"] == to_string(second_activity.id)
+
+ third_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/favourites?limit=0")
+
+ assert [] = json_response(third_conn, 200)
end
describe "updating credentials" do
@@ -1266,9 +1959,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert user = json_response(conn, 200)
assert user["note"] ==
- "I drink <a href=\"http://localhost:4001/tag/cofe\">#cofe</a> with <span><a href=\"#{
- user2.ap_id
- }\">@<span>#{user2.nickname}</span></a></span>"
+ ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag">#cofe</a> with <span class="h-card"><a data-user=") <>
+ user2.id <>
+ ~s(" class="u-url mention" href=") <>
+ user2.ap_id <> ~s(">@<span>) <> user2.nickname <> ~s(</span></a></span>)
end
test "updates the user's locking status", %{conn: conn} do
@@ -1330,24 +2024,800 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert user_response = json_response(conn, 200)
assert user_response["header"] != User.banner_url(user)
end
+
+ test "requires 'write' permission", %{conn: conn} do
+ token1 = insert(:oauth_token, scopes: ["read"])
+ token2 = insert(:oauth_token, scopes: ["write", "follow"])
+
+ for token <- [token1, token2] do
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer #{token.token}")
+ |> patch("/api/v1/accounts/update_credentials", %{})
+
+ if token == token1 do
+ assert %{"error" => "Insufficient permissions: write."} == json_response(conn, 403)
+ else
+ assert json_response(conn, 200)
+ end
+ end
+ end
end
test "get instance information", %{conn: conn} do
- insert(:user, %{local: true})
+ conn = get(conn, "/api/v1/instance")
+ assert result = json_response(conn, 200)
+
+ email = Pleroma.Config.get([:instance, :email])
+ # Note: not checking for "max_toot_chars" since it's optional
+ assert %{
+ "uri" => _,
+ "title" => _,
+ "description" => _,
+ "version" => _,
+ "email" => from_config_email,
+ "urls" => %{
+ "streaming_api" => _
+ },
+ "stats" => _,
+ "thumbnail" => _,
+ "languages" => _,
+ "registrations" => _
+ } = result
+
+ assert email == from_config_email
+ end
+
+ test "get instance stats", %{conn: conn} do
user = insert(:user, %{local: true})
- insert(:user, %{local: false})
+
+ user2 = insert(:user, %{local: true})
+ {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
+
+ insert(:user, %{local: false, nickname: "u@peer1.com"})
+ insert(:user, %{local: false, nickname: "u@peer2.com"})
{:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
+ # Stats should count users with missing or nil `info.deactivated` value
+ user = User.get_by_id(user.id)
+ info_change = Changeset.change(user.info, %{deactivated: nil})
+
+ {:ok, _user} =
+ user
+ |> Changeset.change()
+ |> Changeset.put_embed(:info, info_change)
+ |> User.update_and_set_cache()
+
+ Pleroma.Stats.update_stats()
+
+ conn = get(conn, "/api/v1/instance")
+
+ assert result = json_response(conn, 200)
+
+ stats = result["stats"]
+
+ assert stats
+ assert stats["user_count"] == 1
+ assert stats["status_count"] == 1
+ assert stats["domain_count"] == 2
+ end
+
+ test "get peers", %{conn: conn} do
+ insert(:user, %{local: false, nickname: "u@peer1.com"})
+ insert(:user, %{local: false, nickname: "u@peer2.com"})
+
Pleroma.Stats.update_stats()
+ conn = get(conn, "/api/v1/instance/peers")
+
+ assert result = json_response(conn, 200)
+
+ assert ["peer1.com", "peer2.com"] == Enum.sort(result)
+ end
+
+ test "put settings", %{conn: conn} do
+ user = insert(:user)
+
conn =
conn
- |> get("/api/v1/instance")
+ |> assign(:user, user)
+ |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
- assert result = json_response(conn, 200)
+ assert _result = json_response(conn, 200)
+
+ user = User.get_cached_by_ap_id(user.ap_id)
+ assert user.info.settings == %{"programming" => "socks"}
+ end
+
+ describe "pinned statuses" do
+ setup do
+ Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+ [user: user, activity: activity]
+ end
+
+ test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
+ {:ok, _} = CommonAPI.pin(activity.id, user)
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
+ |> json_response(200)
+
+ id_str = to_string(activity.id)
+
+ assert [%{"id" => ^id_str, "pinned" => true}] = result
+ end
+
+ test "pin status", %{conn: conn, user: user, activity: activity} do
+ id_str = to_string(activity.id)
+
+ assert %{"id" => ^id_str, "pinned" => true} =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{activity.id}/pin")
+ |> json_response(200)
+
+ assert [%{"id" => ^id_str, "pinned" => true}] =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
+ |> json_response(200)
+ end
+
+ test "unpin status", %{conn: conn, user: user, activity: activity} do
+ {:ok, _} = CommonAPI.pin(activity.id, user)
+
+ id_str = to_string(activity.id)
+ user = refresh_record(user)
+
+ assert %{"id" => ^id_str, "pinned" => false} =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{activity.id}/unpin")
+ |> json_response(200)
+
+ assert [] =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
+ |> json_response(200)
+ end
+
+ test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
+ {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+ id_str_one = to_string(activity_one.id)
+
+ assert %{"id" => ^id_str_one, "pinned" => true} =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{id_str_one}/pin")
+ |> json_response(200)
+
+ user = refresh_record(user)
+
+ assert %{"error" => "You have already pinned the maximum number of statuses"} =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{activity_two.id}/pin")
+ |> json_response(400)
+ end
+
+ test "Status rich-media Card", %{conn: conn, user: user} do
+ Pleroma.Config.put([:rich_media, :enabled], true)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})
+
+ response =
+ conn
+ |> get("/api/v1/statuses/#{activity.id}/card")
+ |> json_response(200)
+
+ assert response == %{
+ "image" => "http://ia.media-imdb.com/images/rock.jpg",
+ "provider_name" => "www.imdb.com",
+ "provider_url" => "http://www.imdb.com",
+ "title" => "The Rock",
+ "type" => "link",
+ "url" => "http://www.imdb.com/title/tt0117500/",
+ "description" => nil,
+ "pleroma" => %{
+ "opengraph" => %{
+ "image" => "http://ia.media-imdb.com/images/rock.jpg",
+ "title" => "The Rock",
+ "type" => "video.movie",
+ "url" => "http://www.imdb.com/title/tt0117500/"
+ }
+ }
+ }
+
+ # works with private posts
+ {:ok, activity} =
+ CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"})
+
+ response_two =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/statuses/#{activity.id}/card")
+ |> json_response(200)
+
+ assert response_two == response
+
+ Pleroma.Config.put([:rich_media, :enabled], false)
+ end
+ end
+
+ test "bookmarks" do
+ user = insert(:user)
+ for_user = insert(:user)
+
+ {:ok, activity1} =
+ CommonAPI.post(user, %{
+ "status" => "heweoo?"
+ })
+
+ {:ok, activity2} =
+ CommonAPI.post(user, %{
+ "status" => "heweoo!"
+ })
+
+ response1 =
+ build_conn()
+ |> assign(:user, for_user)
+ |> post("/api/v1/statuses/#{activity1.id}/bookmark")
+
+ assert json_response(response1, 200)["bookmarked"] == true
+
+ response2 =
+ build_conn()
+ |> assign(:user, for_user)
+ |> post("/api/v1/statuses/#{activity2.id}/bookmark")
+
+ assert json_response(response2, 200)["bookmarked"] == true
+
+ bookmarks =
+ build_conn()
+ |> assign(:user, for_user)
+ |> get("/api/v1/bookmarks")
+
+ assert [json_response(response2, 200), json_response(response1, 200)] ==
+ json_response(bookmarks, 200)
+
+ response1 =
+ build_conn()
+ |> assign(:user, for_user)
+ |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
+
+ assert json_response(response1, 200)["bookmarked"] == false
+
+ bookmarks =
+ build_conn()
+ |> assign(:user, for_user)
+ |> get("/api/v1/bookmarks")
+
+ assert [json_response(response2, 200)] == json_response(bookmarks, 200)
+ end
+
+ describe "conversation muting" do
+ setup do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
+
+ [user: user, activity: activity]
+ end
+
+ test "mute conversation", %{conn: conn, user: user, activity: activity} do
+ id_str = to_string(activity.id)
+
+ assert %{"id" => ^id_str, "muted" => true} =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{activity.id}/mute")
+ |> json_response(200)
+ end
+
+ test "unmute conversation", %{conn: conn, user: user, activity: activity} do
+ {:ok, _} = CommonAPI.add_mute(user, activity)
+
+ id_str = to_string(activity.id)
+ user = refresh_record(user)
+
+ assert %{"id" => ^id_str, "muted" => false} =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{activity.id}/unmute")
+ |> json_response(200)
+ end
+ end
+
+ test "flavours switching (Pleroma Extension)", %{conn: conn} do
+ user = insert(:user)
+
+ get_old_flavour =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/pleroma/flavour")
+
+ assert "glitch" == json_response(get_old_flavour, 200)
+
+ set_flavour =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/flavour/vanilla")
+
+ assert "vanilla" == json_response(set_flavour, 200)
+
+ get_new_flavour =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/flavour/vanilla")
+
+ assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200)
+ end
+
+ describe "reports" do
+ setup do
+ reporter = insert(:user)
+ target_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
+
+ [reporter: reporter, target_user: target_user, activity: activity]
+ end
+
+ test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
+ assert %{"action_taken" => false, "id" => _} =
+ conn
+ |> assign(:user, reporter)
+ |> post("/api/v1/reports", %{"account_id" => target_user.id})
+ |> json_response(200)
+ end
+
+ test "submit a report with statuses and comment", %{
+ conn: conn,
+ reporter: reporter,
+ target_user: target_user,
+ activity: activity
+ } do
+ assert %{"action_taken" => false, "id" => _} =
+ conn
+ |> assign(:user, reporter)
+ |> post("/api/v1/reports", %{
+ "account_id" => target_user.id,
+ "status_ids" => [activity.id],
+ "comment" => "bad status!"
+ })
+ |> json_response(200)
+ end
+
+ test "account_id is required", %{
+ conn: conn,
+ reporter: reporter,
+ activity: activity
+ } do
+ assert %{"error" => "Valid `account_id` required"} =
+ conn
+ |> assign(:user, reporter)
+ |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
+ |> json_response(400)
+ end
+
+ test "comment must be up to the size specified in the config", %{
+ conn: conn,
+ reporter: reporter,
+ target_user: target_user
+ } do
+ max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
+ comment = String.pad_trailing("a", max_size + 1, "a")
+
+ error = %{"error" => "Comment must be up to #{max_size} characters"}
+
+ assert ^error =
+ conn
+ |> assign(:user, reporter)
+ |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
+ |> json_response(400)
+ end
+ end
+
+ describe "link headers" do
+ test "preserves parameters in link headers", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity1} =
+ CommonAPI.post(other_user, %{
+ "status" => "hi @#{user.nickname}",
+ "visibility" => "public"
+ })
+
+ {:ok, activity2} =
+ CommonAPI.post(other_user, %{
+ "status" => "hi @#{user.nickname}",
+ "visibility" => "public"
+ })
+
+ notification1 = Repo.get_by(Notification, activity_id: activity1.id)
+ notification2 = Repo.get_by(Notification, activity_id: activity2.id)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/notifications", %{media_only: true})
+
+ assert [link_header] = get_resp_header(conn, "link")
+ assert link_header =~ ~r/media_only=true/
+ assert link_header =~ ~r/min_id=#{notification2.id}/
+ assert link_header =~ ~r/max_id=#{notification1.id}/
+ end
+ end
+
+ test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
+ # Need to set an old-style integer ID to reproduce the problem
+ # (these are no longer assigned to new accounts but were preserved
+ # for existing accounts during the migration to flakeIDs)
+ user_one = insert(:user, %{id: 1212})
+ user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
+
+ resp_one =
+ conn
+ |> get("/api/v1/accounts/#{user_one.id}")
+
+ resp_two =
+ conn
+ |> get("/api/v1/accounts/#{user_two.nickname}")
+
+ resp_three =
+ conn
+ |> get("/api/v1/accounts/#{user_two.id}")
+
+ acc_one = json_response(resp_one, 200)
+ acc_two = json_response(resp_two, 200)
+ acc_three = json_response(resp_three, 200)
+ refute acc_one == acc_two
+ assert acc_two == acc_three
+ end
+
+ describe "custom emoji" do
+ test "with tags", %{conn: conn} do
+ [emoji | _body] =
+ conn
+ |> get("/api/v1/custom_emojis")
+ |> json_response(200)
+
+ assert Map.has_key?(emoji, "shortcode")
+ assert Map.has_key?(emoji, "static_url")
+ assert Map.has_key?(emoji, "tags")
+ assert is_list(emoji["tags"])
+ assert Map.has_key?(emoji, "url")
+ assert Map.has_key?(emoji, "visible_in_picker")
+ end
+ end
+
+ describe "index/2 redirections" do
+ setup %{conn: conn} do
+ session_opts = [
+ store: :cookie,
+ key: "_test",
+ signing_salt: "cooldude"
+ ]
+
+ conn =
+ conn
+ |> Plug.Session.call(Plug.Session.init(session_opts))
+ |> fetch_session()
+
+ test_path = "/web/statuses/test"
+ %{conn: conn, path: test_path}
+ end
+
+ test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
+ conn = get(conn, path)
+
+ assert conn.status == 302
+ assert redirected_to(conn) == "/web/login"
+ end
+
+ test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
+ token = insert(:oauth_token)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> put_session(:oauth_token, token.token)
+ |> get(path)
+
+ assert conn.status == 200
+ end
+
+ test "saves referer path to session", %{conn: conn, path: path} do
+ conn = get(conn, path)
+ return_to = Plug.Conn.get_session(conn, :return_to)
+
+ assert return_to == path
+ end
+
+ test "redirects to the saved path after log in", %{conn: conn, path: path} do
+ app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
+ auth = insert(:oauth_authorization, app: app)
+
+ conn =
+ conn
+ |> put_session(:return_to, path)
+ |> get("/web/login", %{code: auth.token})
+
+ assert conn.status == 302
+ assert redirected_to(conn) == path
+ end
+
+ test "redirects to the getting-started page when referer is not present", %{conn: conn} do
+ app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
+ auth = insert(:oauth_authorization, app: app)
+
+ conn = get(conn, "/web/login", %{code: auth.token})
+
+ assert conn.status == 302
+ assert redirected_to(conn) == "/web/getting-started"
+ end
+ end
+
+ describe "scheduled activities" do
+ test "creates a scheduled activity", %{conn: conn} do
+ user = insert(:user)
+ scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" => "scheduled",
+ "scheduled_at" => scheduled_at
+ })
+
+ assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
+ assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
+ assert [] == Repo.all(Activity)
+ end
+
+ test "creates a scheduled activity with a media attachment", %{conn: conn} do
+ user = insert(:user)
+ scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "media_ids" => [to_string(upload.id)],
+ "status" => "scheduled",
+ "scheduled_at" => scheduled_at
+ })
+
+ assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
+ assert %{"type" => "image"} = media_attachment
+ end
+
+ test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
+ %{conn: conn} do
+ user = insert(:user)
+
+ scheduled_at =
+ NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" => "not scheduled",
+ "scheduled_at" => scheduled_at
+ })
+
+ assert %{"content" => "not scheduled"} = json_response(conn, 200)
+ assert [] == Repo.all(ScheduledActivity)
+ end
+
+ test "returns error when daily user limit is exceeded", %{conn: conn} do
+ user = insert(:user)
+
+ today =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
+ |> NaiveDateTime.to_iso8601()
+
+ attrs = %{params: %{}, scheduled_at: today}
+ {:ok, _} = ScheduledActivity.create(user, attrs)
+ {:ok, _} = ScheduledActivity.create(user, attrs)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
+
+ assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
+ end
+
+ test "returns error when total user limit is exceeded", %{conn: conn} do
+ user = insert(:user)
+
+ today =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
+ |> NaiveDateTime.to_iso8601()
+
+ tomorrow =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(:timer.hours(36), :millisecond)
+ |> NaiveDateTime.to_iso8601()
+
+ attrs = %{params: %{}, scheduled_at: today}
+ {:ok, _} = ScheduledActivity.create(user, attrs)
+ {:ok, _} = ScheduledActivity.create(user, attrs)
+ {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
+
+ assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
+ end
+
+ test "shows scheduled activities", %{conn: conn} do
+ user = insert(:user)
+ scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
+ scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
+ scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
+ scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
+
+ conn =
+ conn
+ |> assign(:user, user)
+
+ # min_id
+ conn_res =
+ conn
+ |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
+
+ result = json_response(conn_res, 200)
+ assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
+
+ # since_id
+ conn_res =
+ conn
+ |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
+
+ result = json_response(conn_res, 200)
+ assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
+
+ # max_id
+ conn_res =
+ conn
+ |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
+
+ result = json_response(conn_res, 200)
+ assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
+ end
+
+ test "shows a scheduled activity", %{conn: conn} do
+ user = insert(:user)
+ scheduled_activity = insert(:scheduled_activity, user: user)
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
+
+ assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
+ assert scheduled_activity_id == scheduled_activity.id |> to_string()
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/scheduled_statuses/404")
+
+ assert %{"error" => "Record not found"} = json_response(res_conn, 404)
+ end
+
+ test "updates a scheduled activity", %{conn: conn} do
+ user = insert(:user)
+ scheduled_activity = insert(:scheduled_activity, user: user)
+
+ new_scheduled_at =
+ NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
+ scheduled_at: new_scheduled_at
+ })
+
+ assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
+ assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
+
+ assert %{"error" => "Record not found"} = json_response(res_conn, 404)
+ end
+
+ test "deletes a scheduled activity", %{conn: conn} do
+ user = insert(:user)
+ scheduled_activity = insert(:scheduled_activity, user: user)
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
+
+ assert %{} = json_response(res_conn, 200)
+ assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
+
+ assert %{"error" => "Record not found"} = json_response(res_conn, 404)
+ end
+ end
+
+ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
+ user1 = insert(:user)
+ user2 = insert(:user)
+ user3 = insert(:user)
+
+ {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
+
+ # Reply to status from another user
+ conn1 =
+ conn
+ |> assign(:user, user2)
+ |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
+
+ assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
+
+ activity = Activity.get_by_id(id)
+
+ assert activity.data["object"]["inReplyTo"] == replied_to.data["object"]["id"]
+ assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
+
+ # Reblog from the third user
+ conn2 =
+ conn
+ |> assign(:user, user3)
+ |> post("/api/v1/statuses/#{activity.id}/reblog")
+
+ assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
+ json_response(conn2, 200)
+
+ assert to_string(activity.id) == id
+
+ # Getting third user status
+ conn3 =
+ conn
+ |> assign(:user, user3)
+ |> get("api/v1/timelines/home")
+
+ [reblogged_activity] = json_response(conn3, 200)
+
+ assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
- assert result["stats"]["user_count"] == 2
- assert result["stats"]["status_count"] == 1
+ replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
+ assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
end
end
diff --git a/test/web/mastodon_api/mastodon_socket_test.exs b/test/web/mastodon_api/mastodon_socket_test.exs
deleted file mode 100644
index c7d71defc..000000000
--- a/test/web/mastodon_api/mastodon_socket_test.exs
+++ /dev/null
@@ -1,33 +0,0 @@
-defmodule Pleroma.Web.MastodonApi.MastodonSocketTest do
- use Pleroma.DataCase
-
- alias Pleroma.Web.MastodonApi.MastodonSocket
- alias Pleroma.Web.{Streamer, CommonAPI}
- alias Pleroma.User
-
- import Pleroma.Factory
-
- test "public is working when non-authenticated" do
- user = insert(:user)
-
- task =
- Task.async(fn ->
- assert_receive {:text, _}, 4_000
- end)
-
- fake_socket = %{
- transport_pid: task.pid,
- assigns: %{}
- }
-
- topics = %{
- "public" => [fake_socket]
- }
-
- {:ok, activity} = CommonAPI.post(user, %{"status" => "Test"})
-
- Streamer.push_to_socket(topics, "public", activity)
-
- Task.await(task)
- end
-end
diff --git a/test/web/mastodon_api/notification_view_test.exs b/test/web/mastodon_api/notification_view_test.exs
new file mode 100644
index 000000000..f2c1eb76c
--- /dev/null
+++ b/test/web/mastodon_api/notification_view_test.exs
@@ -0,0 +1,104 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Notification
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.NotificationView
+ alias Pleroma.Web.MastodonAPI.StatusView
+ import Pleroma.Factory
+
+ test "Mention notification" do
+ user = insert(:user)
+ mentioned_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"})
+ {:ok, [notification]} = Notification.create_notifications(activity)
+ user = User.get_by_id(user.id)
+
+ expected = %{
+ id: to_string(notification.id),
+ pleroma: %{is_seen: false},
+ type: "mention",
+ account: AccountView.render("account.json", %{user: user, for: mentioned_user}),
+ status: StatusView.render("status.json", %{activity: activity, for: mentioned_user}),
+ created_at: Utils.to_masto_date(notification.inserted_at)
+ }
+
+ result =
+ NotificationView.render("index.json", %{notifications: [notification], for: mentioned_user})
+
+ assert [expected] == result
+ end
+
+ test "Favourite notification" do
+ user = insert(:user)
+ another_user = insert(:user)
+ {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
+ {:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user)
+ {:ok, [notification]} = Notification.create_notifications(favorite_activity)
+ create_activity = Activity.get_by_id(create_activity.id)
+
+ expected = %{
+ id: to_string(notification.id),
+ pleroma: %{is_seen: false},
+ type: "favourite",
+ account: AccountView.render("account.json", %{user: another_user, for: user}),
+ status: StatusView.render("status.json", %{activity: create_activity, for: user}),
+ created_at: Utils.to_masto_date(notification.inserted_at)
+ }
+
+ result = NotificationView.render("index.json", %{notifications: [notification], for: user})
+
+ assert [expected] == result
+ end
+
+ test "Reblog notification" do
+ user = insert(:user)
+ another_user = insert(:user)
+ {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
+ {:ok, reblog_activity, _object} = CommonAPI.repeat(create_activity.id, another_user)
+ {:ok, [notification]} = Notification.create_notifications(reblog_activity)
+ reblog_activity = Activity.get_by_id(create_activity.id)
+
+ expected = %{
+ id: to_string(notification.id),
+ pleroma: %{is_seen: false},
+ type: "reblog",
+ account: AccountView.render("account.json", %{user: another_user, for: user}),
+ status: StatusView.render("status.json", %{activity: reblog_activity, for: user}),
+ created_at: Utils.to_masto_date(notification.inserted_at)
+ }
+
+ result = NotificationView.render("index.json", %{notifications: [notification], for: user})
+
+ assert [expected] == result
+ end
+
+ test "Follow notification" do
+ follower = insert(:user)
+ followed = insert(:user)
+ {:ok, follower, followed, _activity} = CommonAPI.follow(follower, followed)
+ notification = Notification |> Repo.one() |> Repo.preload(:activity)
+
+ expected = %{
+ id: to_string(notification.id),
+ pleroma: %{is_seen: false},
+ type: "follow",
+ account: AccountView.render("account.json", %{user: follower, for: followed}),
+ created_at: Utils.to_masto_date(notification.inserted_at)
+ }
+
+ result =
+ NotificationView.render("index.json", %{notifications: [notification], for: followed})
+
+ assert [expected] == result
+ end
+end
diff --git a/test/web/mastodon_api/push_subscription_view_test.exs b/test/web/mastodon_api/push_subscription_view_test.exs
new file mode 100644
index 000000000..dc935fc82
--- /dev/null
+++ b/test/web/mastodon_api/push_subscription_view_test.exs
@@ -0,0 +1,23 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.PushSubscriptionViewTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+ alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View
+ alias Pleroma.Web.Push
+
+ test "Represent a subscription" do
+ subscription = insert(:push_subscription, data: %{"alerts" => %{"mention" => true}})
+
+ expected = %{
+ alerts: %{"mention" => true},
+ endpoint: subscription.endpoint,
+ id: to_string(subscription.id),
+ server_key: Keyword.get(Push.vapid_config(), :public_key)
+ }
+
+ assert expected == View.render("push_subscription.json", %{subscription: subscription})
+ end
+end
diff --git a/test/web/mastodon_api/scheduled_activity_view_test.exs b/test/web/mastodon_api/scheduled_activity_view_test.exs
new file mode 100644
index 000000000..ecbb855d4
--- /dev/null
+++ b/test/web/mastodon_api/scheduled_activity_view_test.exs
@@ -0,0 +1,68 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do
+ use Pleroma.DataCase
+ alias Pleroma.ScheduledActivity
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.MastodonAPI.ScheduledActivityView
+ alias Pleroma.Web.MastodonAPI.StatusView
+ import Pleroma.Factory
+
+ test "A scheduled activity with a media attachment" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "hi"})
+
+ scheduled_at =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(:timer.minutes(10), :millisecond)
+ |> NaiveDateTime.to_iso8601()
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
+
+ attrs = %{
+ params: %{
+ "media_ids" => [upload.id],
+ "status" => "hi",
+ "sensitive" => true,
+ "spoiler_text" => "spoiler",
+ "visibility" => "unlisted",
+ "in_reply_to_id" => to_string(activity.id)
+ },
+ scheduled_at: scheduled_at
+ }
+
+ {:ok, scheduled_activity} = ScheduledActivity.create(user, attrs)
+ result = ScheduledActivityView.render("show.json", %{scheduled_activity: scheduled_activity})
+
+ expected = %{
+ id: to_string(scheduled_activity.id),
+ media_attachments:
+ %{"media_ids" => [upload.id]}
+ |> Utils.attachments_from_ids()
+ |> Enum.map(&StatusView.render("attachment.json", %{attachment: &1})),
+ params: %{
+ in_reply_to_id: to_string(activity.id),
+ media_ids: [upload.id],
+ poll: nil,
+ scheduled_at: nil,
+ sensitive: true,
+ spoiler_text: "spoiler",
+ text: "hi",
+ visibility: "unlisted"
+ },
+ scheduled_at: Utils.to_masto_date(scheduled_activity.scheduled_at)
+ }
+
+ assert expected == result
+ end
+end
diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs
index 4f58ce8af..4ea50c7c6 100644
--- a/test/web/mastodon_api/status_view_test.exs
+++ b/test/web/mastodon_api/status_view_test.exs
@@ -1,11 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
use Pleroma.DataCase
- alias Pleroma.Web.MastodonAPI.{StatusView, AccountView}
- alias Pleroma.{Repo, User, Object}
- alias Pleroma.Web.OStatus
+ alias Pleroma.Activity
+ alias Pleroma.User
+ alias Pleroma.Repo
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.OStatus
import Pleroma.Factory
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ test "returns a temporary ap_id based user for activities missing db users" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
+
+ Repo.delete(user)
+ Cachex.clear(:user_cache)
+
+ %{account: ms_user} = StatusView.render("status.json", activity: activity)
+
+ assert ms_user.acct == "erroruser@example.com"
+ end
+
+ test "tries to get a user by nickname if fetching by ap_id doesn't work" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
+
+ {:ok, user} =
+ user
+ |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
+ |> Repo.update()
+
+ Cachex.clear(:user_cache)
+
+ result = StatusView.render("status.json", activity: activity)
+
+ assert result[:account][:id] == to_string(user.id)
+ end
test "a note with null content" do
note = insert(:note_activity)
@@ -18,7 +64,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
Object.change(note_object, %{data: data})
|> Repo.update()
- user = User.get_cached_by_ap_id(note.data["actor"])
+ User.get_cached_by_ap_id(note.data["actor"])
status = StatusView.render("status.json", %{activity: note})
@@ -29,6 +75,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
note = insert(:note_activity)
user = User.get_cached_by_ap_id(note.data["actor"])
+ convo_id = Utils.context_to_conversation_id(note.data["object"]["context"])
+
status = StatusView.render("status.json", %{activity: note})
created_at =
@@ -38,10 +86,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
expected = %{
id: to_string(note.id),
uri: note.data["object"]["id"],
- url: note.data["object"]["id"],
+ url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
+ card: nil,
reblog: nil,
content: HtmlSanitizeEx.basic_html(note.data["object"]["content"]),
created_at: created_at,
@@ -49,14 +98,21 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
replies_count: 0,
favourites_count: 0,
reblogged: false,
+ bookmarked: false,
favourited: false,
muted: false,
+ pinned: false,
sensitive: false,
- spoiler_text: note.data["object"]["summary"],
+ spoiler_text: HtmlSanitizeEx.basic_html(note.data["object"]["summary"]),
visibility: "public",
media_attachments: [],
mentions: [],
- tags: [],
+ tags: [
+ %{
+ name: "#{note.data["object"]["tag"]}",
+ url: "/tag/#{note.data["object"]["tag"]}"
+ }
+ ],
application: %{
name: "Web",
website: nil
@@ -69,12 +125,34 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
static_url: "corndog.png",
visible_in_picker: false
}
- ]
+ ],
+ pleroma: %{
+ local: true,
+ conversation_id: convo_id,
+ content: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["content"])},
+ spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["summary"])}
+ }
}
assert status == expected
end
+ test "tells if the message is muted for some reason" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, user} = User.mute(user, other_user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
+ status = StatusView.render("status.json", %{activity: activity})
+
+ assert status.muted == false
+
+ status = StatusView.render("status.json", %{activity: activity, for: user})
+
+ assert status.muted == true
+ end
+
test "a reply" do
note = insert(:note_activity)
user = insert(:user)
@@ -101,7 +179,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
status = StatusView.render("status.json", %{activity: activity})
- assert status.mentions == [AccountView.render("mention.json", %{user: user})]
+ actor = User.get_by_ap_id(activity.actor)
+
+ assert status.mentions ==
+ Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end)
end
test "attachments" do
@@ -123,7 +204,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
remote_url: "someurl",
preview_url: "someurl",
text_url: "someurl",
- description: nil
+ description: nil,
+ pleroma: %{mime_type: "image/png"}
}
assert expected == StatusView.render("attachment.json", %{attachment: object})
@@ -145,4 +227,96 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
assert represented[:reblog][:id] == to_string(activity.id)
assert represented[:emojis] == []
end
+
+ test "a peertube video" do
+ user = insert(:user)
+
+ {:ok, object} =
+ ActivityPub.fetch_object_from_id(
+ "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
+ )
+
+ %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
+
+ represented = StatusView.render("status.json", %{for: user, activity: activity})
+
+ assert represented[:id] == to_string(activity.id)
+ assert length(represented[:media_attachments]) == 1
+ end
+
+ describe "build_tags/1" do
+ test "it returns a a dictionary tags" do
+ object_tags = [
+ "fediverse",
+ "mastodon",
+ "nextcloud",
+ %{
+ "href" => "https://kawen.space/users/lain",
+ "name" => "@lain@kawen.space",
+ "type" => "Mention"
+ }
+ ]
+
+ assert StatusView.build_tags(object_tags) == [
+ %{name: "fediverse", url: "/tag/fediverse"},
+ %{name: "mastodon", url: "/tag/mastodon"},
+ %{name: "nextcloud", url: "/tag/nextcloud"}
+ ]
+ end
+ end
+
+ describe "rich media cards" do
+ test "a rich media card without a site name renders correctly" do
+ page_url = "http://example.com"
+
+ card = %{
+ url: page_url,
+ image: page_url <> "/example.jpg",
+ title: "Example website"
+ }
+
+ %{provider_name: "example.com"} =
+ StatusView.render("card.json", %{page_url: page_url, rich_media: card})
+ end
+
+ test "a rich media card without a site name or image renders correctly" do
+ page_url = "http://example.com"
+
+ card = %{
+ url: page_url,
+ title: "Example website"
+ }
+
+ %{provider_name: "example.com"} =
+ StatusView.render("card.json", %{page_url: page_url, rich_media: card})
+ end
+
+ test "a rich media card without an image renders correctly" do
+ page_url = "http://example.com"
+
+ card = %{
+ url: page_url,
+ site_name: "Example site name",
+ title: "Example website"
+ }
+
+ %{provider_name: "Example site name"} =
+ StatusView.render("card.json", %{page_url: page_url, rich_media: card})
+ end
+
+ test "a rich media card with all relevant data renders correctly" do
+ page_url = "http://example.com"
+
+ card = %{
+ url: page_url,
+ site_name: "Example site name",
+ title: "Example website",
+ image: page_url <> "/example.jpg",
+ description: "Example description"
+ }
+
+ %{provider_name: "Example site name"} =
+ StatusView.render("card.json", %{page_url: page_url, rich_media: card})
+ end
+ end
end
diff --git a/test/web/mastodon_api/subscription_controller_test.exs b/test/web/mastodon_api/subscription_controller_test.exs
new file mode 100644
index 000000000..7dfb02f63
--- /dev/null
+++ b/test/web/mastodon_api/subscription_controller_test.exs
@@ -0,0 +1,192 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+ alias Pleroma.Web.Push
+ alias Pleroma.Web.Push.Subscription
+
+ @sub %{
+ "endpoint" => "https://example.com/example/1234",
+ "keys" => %{
+ "auth" => "8eDyX_uCN0XRhSbY5hs7Hg==",
+ "p256dh" =>
+ "BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA="
+ }
+ }
+ @server_key Keyword.get(Push.vapid_config(), :public_key)
+
+ setup do
+ user = insert(:user)
+ token = insert(:oauth_token, user: user, scopes: ["push"])
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> assign(:token, token)
+
+ %{conn: conn, user: user, token: token}
+ end
+
+ defmacro assert_error_when_disable_push(do: yield) do
+ quote do
+ vapid_details = Application.get_env(:web_push_encryption, :vapid_details, [])
+ Application.put_env(:web_push_encryption, :vapid_details, [])
+ assert "Something went wrong" == unquote(yield)
+ Application.put_env(:web_push_encryption, :vapid_details, vapid_details)
+ end
+ end
+
+ describe "creates push subscription" do
+ test "returns error when push disabled ", %{conn: conn} do
+ assert_error_when_disable_push do
+ conn
+ |> post("/api/v1/push/subscription", %{})
+ |> json_response(500)
+ end
+ end
+
+ test "successful creation", %{conn: conn} do
+ result =
+ conn
+ |> post("/api/v1/push/subscription", %{
+ "data" => %{"alerts" => %{"mention" => true, "test" => true}},
+ "subscription" => @sub
+ })
+ |> json_response(200)
+
+ [subscription] = Pleroma.Repo.all(Subscription)
+
+ assert %{
+ "alerts" => %{"mention" => true},
+ "endpoint" => subscription.endpoint,
+ "id" => to_string(subscription.id),
+ "server_key" => @server_key
+ } == result
+ end
+ end
+
+ describe "gets a user subscription" do
+ test "returns error when push disabled ", %{conn: conn} do
+ assert_error_when_disable_push do
+ conn
+ |> get("/api/v1/push/subscription", %{})
+ |> json_response(500)
+ end
+ end
+
+ test "returns error when user hasn't subscription", %{conn: conn} do
+ res =
+ conn
+ |> get("/api/v1/push/subscription", %{})
+ |> json_response(404)
+
+ assert "Not found" == res
+ end
+
+ test "returns a user subsciption", %{conn: conn, user: user, token: token} do
+ subscription =
+ insert(:push_subscription,
+ user: user,
+ token: token,
+ data: %{"alerts" => %{"mention" => true}}
+ )
+
+ res =
+ conn
+ |> get("/api/v1/push/subscription", %{})
+ |> json_response(200)
+
+ expect = %{
+ "alerts" => %{"mention" => true},
+ "endpoint" => "https://example.com/example/1234",
+ "id" => to_string(subscription.id),
+ "server_key" => @server_key
+ }
+
+ assert expect == res
+ end
+ end
+
+ describe "updates a user subsciption" do
+ setup %{conn: conn, user: user, token: token} do
+ subscription =
+ insert(:push_subscription,
+ user: user,
+ token: token,
+ data: %{"alerts" => %{"mention" => true}}
+ )
+
+ %{conn: conn, user: user, token: token, subscription: subscription}
+ end
+
+ test "returns error when push disabled ", %{conn: conn} do
+ assert_error_when_disable_push do
+ conn
+ |> put("/api/v1/push/subscription", %{data: %{"alerts" => %{"mention" => false}}})
+ |> json_response(500)
+ end
+ end
+
+ test "returns updated subsciption", %{conn: conn, subscription: subscription} do
+ res =
+ conn
+ |> put("/api/v1/push/subscription", %{
+ data: %{"alerts" => %{"mention" => false, "follow" => true}}
+ })
+ |> json_response(200)
+
+ expect = %{
+ "alerts" => %{"follow" => true, "mention" => false},
+ "endpoint" => "https://example.com/example/1234",
+ "id" => to_string(subscription.id),
+ "server_key" => @server_key
+ }
+
+ assert expect == res
+ end
+ end
+
+ describe "deletes the user subscription" do
+ test "returns error when push disabled ", %{conn: conn} do
+ assert_error_when_disable_push do
+ conn
+ |> delete("/api/v1/push/subscription", %{})
+ |> json_response(500)
+ end
+ end
+
+ test "returns error when user hasn't subscription", %{conn: conn} do
+ res =
+ conn
+ |> delete("/api/v1/push/subscription", %{})
+ |> json_response(404)
+
+ assert "Not found" == res
+ end
+
+ test "returns empty result and delete user subsciption", %{
+ conn: conn,
+ user: user,
+ token: token
+ } do
+ subscription =
+ insert(:push_subscription,
+ user: user,
+ token: token,
+ data: %{"alerts" => %{"mention" => true}}
+ )
+
+ res =
+ conn
+ |> delete("/api/v1/push/subscription", %{})
+ |> json_response(200)
+
+ assert %{} == res
+ refute Pleroma.Repo.get(Subscription, subscription.id)
+ end
+ end
+end
diff --git a/test/web/metadata/opengraph_test.exs b/test/web/metadata/opengraph_test.exs
new file mode 100644
index 000000000..4283f72cd
--- /dev/null
+++ b/test/web/metadata/opengraph_test.exs
@@ -0,0 +1,94 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Metadata.Providers.OpenGraphTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+ alias Pleroma.Web.Metadata.Providers.OpenGraph
+
+ test "it renders all supported types of attachments and skips unknown types" do
+ user = insert(:user)
+
+ note =
+ insert(:note, %{
+ data: %{
+ "actor" => user.ap_id,
+ "tag" => [],
+ "id" => "https://pleroma.gov/objects/whatever",
+ "content" => "pleroma in a nutshell",
+ "attachment" => [
+ %{
+ "url" => [
+ %{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}
+ ]
+ },
+ %{
+ "url" => [
+ %{
+ "mediaType" => "application/octet-stream",
+ "href" => "https://pleroma.gov/fqa/badapple.sfc"
+ }
+ ]
+ },
+ %{
+ "url" => [
+ %{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"}
+ ]
+ },
+ %{
+ "url" => [
+ %{
+ "mediaType" => "audio/basic",
+ "href" => "http://www.gnu.org/music/free-software-song.au"
+ }
+ ]
+ }
+ ]
+ }
+ })
+
+ result = OpenGraph.build_tags(%{object: note, url: note.data["id"], user: user})
+
+ assert Enum.all?(
+ [
+ {:meta, [property: "og:image", content: "https://pleroma.gov/tenshi.png"], []},
+ {:meta,
+ [property: "og:audio", content: "http://www.gnu.org/music/free-software-song.au"],
+ []},
+ {:meta, [property: "og:video", content: "https://pleroma.gov/about/juche.webm"],
+ []}
+ ],
+ fn element -> element in result end
+ )
+ end
+
+ test "it does not render attachments if post is nsfw" do
+ Pleroma.Config.put([Pleroma.Web.Metadata, :unfurl_nsfw], false)
+ user = insert(:user, avatar: %{"url" => [%{"href" => "https://pleroma.gov/tenshi.png"}]})
+
+ note =
+ insert(:note, %{
+ data: %{
+ "actor" => user.ap_id,
+ "id" => "https://pleroma.gov/objects/whatever",
+ "content" => "#cuteposting #nsfw #hambaga",
+ "tag" => ["cuteposting", "nsfw", "hambaga"],
+ "sensitive" => true,
+ "attachment" => [
+ %{
+ "url" => [
+ %{"mediaType" => "image/png", "href" => "https://misskey.microsoft/corndog.png"}
+ ]
+ }
+ ]
+ }
+ })
+
+ result = OpenGraph.build_tags(%{object: note, url: note.data["id"], user: user})
+
+ assert {:meta, [property: "og:image", content: "https://pleroma.gov/tenshi.png"], []} in result
+
+ refute {:meta, [property: "og:image", content: "https://misskey.microsoft/corndog.png"], []} in result
+ end
+end
diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs
index a5b0b7869..2fc42b7cc 100644
--- a/test/web/node_info_test.exs
+++ b/test/web/node_info_test.exs
@@ -1,18 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.NodeInfoTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
test "nodeinfo shows staff accounts", %{conn: conn} do
- user = insert(:user, %{local: true, info: %{is_moderator: true}})
+ moderator = insert(:user, %{local: true, info: %{is_moderator: true}})
+ admin = insert(:user, %{local: true, info: %{is_admin: true}})
+
+ conn =
+ conn
+ |> get("/nodeinfo/2.1.json")
+
+ assert result = json_response(conn, 200)
+
+ assert moderator.ap_id in result["metadata"]["staffAccounts"]
+ assert admin.ap_id in result["metadata"]["staffAccounts"]
+ end
+ test "nodeinfo shows restricted nicknames", %{conn: conn} do
conn =
conn
- |> get("/nodeinfo/2.0.json")
+ |> get("/nodeinfo/2.1.json")
assert result = json_response(conn, 200)
- assert user.ap_id in result["metadata"]["staffAccounts"]
+ assert Pleroma.Config.get([Pleroma.User, :restricted_nicknames]) ==
+ result["metadata"]["restrictedNicknames"]
end
test "returns 404 when federation is disabled", %{conn: conn} do
@@ -27,7 +44,7 @@ defmodule Pleroma.Web.NodeInfoTest do
|> json_response(404)
conn
- |> get("/nodeinfo/2.0.json")
+ |> get("/nodeinfo/2.1.json")
|> json_response(404)
instance =
@@ -43,7 +60,75 @@ defmodule Pleroma.Web.NodeInfoTest do
|> json_response(200)
conn
+ |> get("/nodeinfo/2.1.json")
+ |> json_response(200)
+ end
+
+ test "returns 404 when federation is disabled (nodeinfo 2.0)", %{conn: conn} do
+ instance =
+ Application.get_env(:pleroma, :instance)
+ |> Keyword.put(:federating, false)
+
+ Application.put_env(:pleroma, :instance, instance)
+
+ conn
+ |> get("/.well-known/nodeinfo")
+ |> json_response(404)
+
+ conn
+ |> get("/nodeinfo/2.0.json")
+ |> json_response(404)
+
+ instance =
+ Application.get_env(:pleroma, :instance)
+ |> Keyword.put(:federating, true)
+
+ Application.put_env(:pleroma, :instance, instance)
+ end
+
+ test "returns 200 when federation is enabled (nodeinfo 2.0)", %{conn: conn} do
+ conn
+ |> get("/.well-known/nodeinfo")
+ |> json_response(200)
+
+ conn
|> get("/nodeinfo/2.0.json")
|> json_response(200)
end
+
+ test "returns software.repository field in nodeinfo 2.1", %{conn: conn} do
+ conn
+ |> get("/.well-known/nodeinfo")
+ |> json_response(200)
+
+ conn =
+ conn
+ |> get("/nodeinfo/2.1.json")
+
+ assert result = json_response(conn, 200)
+ assert Pleroma.Application.repository() == result["software"]["repository"]
+ end
+
+ test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do
+ option = Pleroma.Config.get([:instance, :safe_dm_mentions])
+ Pleroma.Config.put([:instance, :safe_dm_mentions], true)
+
+ response =
+ conn
+ |> get("/nodeinfo/2.1.json")
+ |> json_response(:ok)
+
+ assert "safe_dm_mentions" in response["metadata"]["features"]
+
+ Pleroma.Config.put([:instance, :safe_dm_mentions], false)
+
+ response =
+ conn
+ |> get("/nodeinfo/2.1.json")
+ |> json_response(:ok)
+
+ refute "safe_dm_mentions" in response["metadata"]["features"]
+
+ Pleroma.Config.put([:instance, :safe_dm_mentions], option)
+ end
end
diff --git a/test/web/oauth/authorization_test.exs b/test/web/oauth/authorization_test.exs
index 98c7c4133..d8b008437 100644
--- a/test/web/oauth/authorization_test.exs
+++ b/test/web/oauth/authorization_test.exs
@@ -1,38 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OAuth.AuthorizationTest do
use Pleroma.DataCase
- alias Pleroma.Web.OAuth.{Authorization, App}
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Authorization
import Pleroma.Factory
- test "create an authorization token for a valid app" do
+ setup do
{:ok, app} =
Repo.insert(
App.register_changeset(%App{}, %{
client_name: "client",
- scopes: "scope",
+ scopes: ["read", "write"],
redirect_uris: "url"
})
)
+ %{app: app}
+ end
+
+ test "create an authorization token for a valid app", %{app: app} do
user = insert(:user)
- {:ok, auth} = Authorization.create_authorization(app, user)
+ {:ok, auth1} = Authorization.create_authorization(app, user)
+ assert auth1.scopes == app.scopes
- assert auth.user_id == user.id
- assert auth.app_id == app.id
- assert String.length(auth.token) > 10
- assert auth.used == false
- end
+ {:ok, auth2} = Authorization.create_authorization(app, user, ["read"])
+ assert auth2.scopes == ["read"]
- test "use up a token" do
- {:ok, app} =
- Repo.insert(
- App.register_changeset(%App{}, %{
- client_name: "client",
- scopes: "scope",
- redirect_uris: "url"
- })
- )
+ for auth <- [auth1, auth2] do
+ assert auth.user_id == user.id
+ assert auth.app_id == app.id
+ assert String.length(auth.token) > 10
+ assert auth.used == false
+ end
+ end
+ test "use up a token", %{app: app} do
user = insert(:user)
{:ok, auth} = Authorization.create_authorization(app, user)
@@ -56,22 +62,13 @@ defmodule Pleroma.Web.OAuth.AuthorizationTest do
assert {:error, "token expired"} == Authorization.use_token(expired_auth)
end
- test "delete authorizations" do
- {:ok, app} =
- Repo.insert(
- App.register_changeset(%App{}, %{
- client_name: "client",
- scopes: "scope",
- redirect_uris: "url"
- })
- )
-
+ test "delete authorizations", %{app: app} do
user = insert(:user)
{:ok, auth} = Authorization.create_authorization(app, user)
{:ok, auth} = Authorization.use_token(auth)
- {auths, _} = Authorization.delete_user_authorizations(user)
+ Authorization.delete_user_authorizations(user)
{_, invalid} = Authorization.use_token(auth)
diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/web/oauth/ldap_authorization_test.exs
new file mode 100644
index 000000000..0eb191c76
--- /dev/null
+++ b/test/web/oauth/ldap_authorization_test.exs
@@ -0,0 +1,195 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
+ use Pleroma.Web.ConnCase
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.Token
+ import Pleroma.Factory
+ import ExUnit.CaptureLog
+ import Mock
+
+ @skip if !Code.ensure_loaded?(:eldap), do: :skip
+
+ setup_all do
+ ldap_authenticator =
+ Pleroma.Config.get(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator)
+
+ ldap_enabled = Pleroma.Config.get([:ldap, :enabled])
+
+ on_exit(fn ->
+ Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, ldap_authenticator)
+ Pleroma.Config.put([:ldap, :enabled], ldap_enabled)
+ end)
+
+ Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator)
+ Pleroma.Config.put([:ldap, :enabled], true)
+
+ :ok
+ end
+
+ @tag @skip
+ test "authorizes the existing user using LDAP credentials" do
+ password = "testpassword"
+ user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
+ app = insert(:oauth_app, scopes: ["read", "write"])
+
+ host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
+ port = Pleroma.Config.get([:ldap, :port])
+
+ with_mocks [
+ {:eldap, [],
+ [
+ open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
+ simple_bind: fn _connection, _dn, ^password -> :ok end,
+ close: fn _connection ->
+ send(self(), :close_connection)
+ :ok
+ end
+ ]}
+ ] do
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "password",
+ "username" => user.nickname,
+ "password" => password,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert %{"access_token" => token} = json_response(conn, 200)
+
+ token = Repo.get_by(Token, token: token)
+
+ assert token.user_id == user.id
+ assert_received :close_connection
+ end
+ end
+
+ @tag @skip
+ test "creates a new user after successful LDAP authorization" do
+ password = "testpassword"
+ user = build(:user)
+ app = insert(:oauth_app, scopes: ["read", "write"])
+
+ host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
+ port = Pleroma.Config.get([:ldap, :port])
+
+ with_mocks [
+ {:eldap, [],
+ [
+ open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
+ simple_bind: fn _connection, _dn, ^password -> :ok end,
+ equalityMatch: fn _type, _value -> :ok end,
+ wholeSubtree: fn -> :ok end,
+ search: fn _connection, _options ->
+ {:ok,
+ {:eldap_search_result, [{:eldap_entry, '', [{'mail', [to_charlist(user.email)]}]}],
+ []}}
+ end,
+ close: fn _connection ->
+ send(self(), :close_connection)
+ :ok
+ end
+ ]}
+ ] do
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "password",
+ "username" => user.nickname,
+ "password" => password,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert %{"access_token" => token} = json_response(conn, 200)
+
+ token = Repo.get_by(Token, token: token) |> Repo.preload(:user)
+
+ assert token.user.nickname == user.nickname
+ assert_received :close_connection
+ end
+ end
+
+ @tag @skip
+ test "falls back to the default authorization when LDAP is unavailable" do
+ password = "testpassword"
+ user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
+ app = insert(:oauth_app, scopes: ["read", "write"])
+
+ host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
+ port = Pleroma.Config.get([:ldap, :port])
+
+ with_mocks [
+ {:eldap, [],
+ [
+ open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:error, 'connect failed'} end,
+ simple_bind: fn _connection, _dn, ^password -> :ok end,
+ close: fn _connection ->
+ send(self(), :close_connection)
+ :ok
+ end
+ ]}
+ ] do
+ log =
+ capture_log(fn ->
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "password",
+ "username" => user.nickname,
+ "password" => password,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert %{"access_token" => token} = json_response(conn, 200)
+
+ token = Repo.get_by(Token, token: token)
+
+ assert token.user_id == user.id
+ end)
+
+ assert log =~ "Could not open LDAP connection: 'connect failed'"
+ refute_received :close_connection
+ end
+ end
+
+ @tag @skip
+ test "disallow authorization for wrong LDAP credentials" do
+ password = "testpassword"
+ user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
+ app = insert(:oauth_app, scopes: ["read", "write"])
+
+ host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
+ port = Pleroma.Config.get([:ldap, :port])
+
+ with_mocks [
+ {:eldap, [],
+ [
+ open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
+ simple_bind: fn _connection, _dn, ^password -> {:error, :invalidCredentials} end,
+ close: fn _connection ->
+ send(self(), :close_connection)
+ :ok
+ end
+ ]}
+ ] do
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "password",
+ "username" => user.nickname,
+ "password" => password,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert %{"error" => "Invalid credentials"} = json_response(conn, 400)
+ assert_received :close_connection
+ end
+ end
+end
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index 3a902f128..ac7843f9b 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -1,113 +1,680 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OAuth.OAuthControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
+ import Mock
+ alias Pleroma.Registration
alias Pleroma.Repo
- alias Pleroma.Web.OAuth.{Authorization, Token}
-
- test "redirects with oauth authorization" do
- user = insert(:user)
- app = insert(:oauth_app)
-
- conn =
- build_conn()
- |> post("/oauth/authorize", %{
- "authorization" => %{
- "name" => user.nickname,
- "password" => "test",
- "client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
- "state" => "statepassed"
- }
- })
+ alias Pleroma.Web.OAuth.Authorization
+ alias Pleroma.Web.OAuth.Token
- target = redirected_to(conn)
- assert target =~ app.redirect_uris
+ @session_opts [
+ store: :cookie,
+ key: "_test",
+ signing_salt: "cooldude"
+ ]
- query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
+ describe "in OAuth consumer mode, " do
+ setup do
+ oauth_consumer_strategies_path = [:auth, :oauth_consumer_strategies]
+ oauth_consumer_strategies = Pleroma.Config.get(oauth_consumer_strategies_path)
+ Pleroma.Config.put(oauth_consumer_strategies_path, ~w(twitter facebook))
- assert %{"state" => "statepassed", "code" => code} = query
- assert Repo.get_by(Authorization, token: code)
- end
+ on_exit(fn ->
+ Pleroma.Config.put(oauth_consumer_strategies_path, oauth_consumer_strategies)
+ end)
+
+ [
+ app: insert(:oauth_app),
+ conn:
+ build_conn()
+ |> Plug.Session.call(Plug.Session.init(@session_opts))
+ |> fetch_session()
+ ]
+ end
+
+ test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{
+ app: app,
+ conn: conn
+ } do
+ conn =
+ get(
+ conn,
+ "/oauth/authorize",
+ %{
+ "response_type" => "code",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "scope" => "read"
+ }
+ )
+
+ assert response = html_response(conn, 200)
+ assert response =~ "Sign in with Twitter"
+ assert response =~ o_auth_path(conn, :prepare_request)
+ end
+
+ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{
+ app: app,
+ conn: conn
+ } do
+ conn =
+ get(
+ conn,
+ "/oauth/prepare_request",
+ %{
+ "provider" => "twitter",
+ "scope" => "read follow",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state"
+ }
+ )
+
+ assert response = html_response(conn, 302)
+
+ redirect_query = URI.parse(redirected_to(conn)).query
+ assert %{"state" => state_param} = URI.decode_query(redirect_query)
+ assert {:ok, state_components} = Poison.decode(state_param)
+
+ expected_client_id = app.client_id
+ expected_redirect_uri = app.redirect_uris
+
+ assert %{
+ "scope" => "read follow",
+ "client_id" => ^expected_client_id,
+ "redirect_uri" => ^expected_redirect_uri,
+ "state" => "a_state"
+ } = state_components
+ end
+
+ test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
+ %{app: app, conn: conn} do
+ registration = insert(:registration)
+
+ state_params = %{
+ "scope" => Enum.join(app.scopes, " "),
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => ""
+ }
+
+ with_mock Pleroma.Web.Auth.Authenticator,
+ get_registration: fn _, _ -> {:ok, registration} end do
+ conn =
+ get(
+ conn,
+ "/oauth/twitter/callback",
+ %{
+ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+ "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+ "provider" => "twitter",
+ "state" => Poison.encode!(state_params)
+ }
+ )
+
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+ end
+ end
+
+ test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",
+ %{app: app, conn: conn} do
+ registration = insert(:registration, user: nil)
+
+ state_params = %{
+ "scope" => "read write",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state"
+ }
+
+ with_mock Pleroma.Web.Auth.Authenticator,
+ get_registration: fn _, _ -> {:ok, registration} end do
+ conn =
+ get(
+ conn,
+ "/oauth/twitter/callback",
+ %{
+ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+ "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+ "provider" => "twitter",
+ "state" => Poison.encode!(state_params)
+ }
+ )
+
+ assert response = html_response(conn, 200)
+ assert response =~ ~r/name="op" type="submit" value="register"/
+ assert response =~ ~r/name="op" type="submit" value="connect"/
+ assert response =~ Registration.email(registration)
+ assert response =~ Registration.nickname(registration)
+ end
+ end
+
+ test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
+ app: app,
+ conn: conn
+ } do
+ state_params = %{
+ "scope" => Enum.join(app.scopes, " "),
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => ""
+ }
+
+ conn =
+ conn
+ |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
+ |> get(
+ "/oauth/twitter/callback",
+ %{
+ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+ "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+ "provider" => "twitter",
+ "state" => Poison.encode!(state_params)
+ }
+ )
+
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) == app.redirect_uris
+ assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
+ end
+
+ test "GET /oauth/registration_details renders registration details form", %{
+ app: app,
+ conn: conn
+ } do
+ conn =
+ get(
+ conn,
+ "/oauth/registration_details",
+ %{
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "nickname" => nil,
+ "email" => "john@doe.com"
+ }
+ )
+
+ assert response = html_response(conn, 200)
+ assert response =~ ~r/name="op" type="submit" value="register"/
+ assert response =~ ~r/name="op" type="submit" value="connect"/
+ end
+
+ test "with valid params, POST /oauth/register?op=register redirects to `redirect_uri` with `code`",
+ %{
+ app: app,
+ conn: conn
+ } do
+ registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
+
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post(
+ "/oauth/register",
+ %{
+ "op" => "register",
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "nickname" => "availablenick",
+ "email" => "available@email.com"
+ }
+ )
- test "issues a token for an all-body request" do
- user = insert(:user)
- app = insert(:oauth_app)
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+ end
- {:ok, auth} = Authorization.create_authorization(app, user)
+ test "with invalid params, POST /oauth/register?op=register renders registration_details page",
+ %{
+ app: app,
+ conn: conn
+ } do
+ another_user = insert(:user)
+ registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
- conn =
- build_conn()
- |> post("/oauth/token", %{
- "grant_type" => "authorization_code",
- "code" => auth.token,
+ params = %{
+ "op" => "register",
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
"redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "nickname" => "availablenickname",
+ "email" => "available@email.com"
+ }
+
+ for {bad_param, bad_param_value} <-
+ [{"nickname", another_user.nickname}, {"email", another_user.email}] do
+ bad_params = Map.put(params, bad_param, bad_param_value)
+
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post("/oauth/register", bad_params)
+
+ assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/
+ assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken."
+ end
+ end
+
+ test "with valid params, POST /oauth/register?op=connect redirects to `redirect_uri` with `code`",
+ %{
+ app: app,
+ conn: conn
+ } do
+ user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
+ registration = insert(:registration, user: nil)
+
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post(
+ "/oauth/register",
+ %{
+ "op" => "connect",
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "auth_name" => user.nickname,
+ "password" => "testpassword"
+ }
+ )
+
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+ end
+
+ test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
+ %{
+ app: app,
+ conn: conn
+ } do
+ user = insert(:user)
+ registration = insert(:registration, user: nil)
+
+ params = %{
+ "op" => "connect",
+ "scopes" => app.scopes,
"client_id" => app.client_id,
- "client_secret" => app.client_secret
- })
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "auth_name" => user.nickname,
+ "password" => "wrong password"
+ }
- assert %{"access_token" => token} = json_response(conn, 200)
- assert Repo.get_by(Token, token: token)
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post("/oauth/register", params)
+
+ assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/
+ assert get_flash(conn, :error) == "Invalid Username/Password"
+ end
end
- test "issues a token for request with HTTP basic auth client credentials" do
- user = insert(:user)
- app = insert(:oauth_app)
+ describe "GET /oauth/authorize" do
+ setup do
+ [
+ app: insert(:oauth_app, redirect_uris: "https://redirect.url"),
+ conn:
+ build_conn()
+ |> Plug.Session.call(Plug.Session.init(@session_opts))
+ |> fetch_session()
+ ]
+ end
+
+ test "renders authentication page", %{app: app, conn: conn} do
+ conn =
+ get(
+ conn,
+ "/oauth/authorize",
+ %{
+ "response_type" => "code",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "scope" => "read"
+ }
+ )
+
+ assert html_response(conn, 200) =~ ~s(type="submit")
+ end
- {:ok, auth} = Authorization.create_authorization(app, user)
+ test "renders authentication page if user is already authenticated but `force_login` is tru-ish",
+ %{app: app, conn: conn} do
+ token = insert(:oauth_token, app_id: app.id)
- app_encoded =
- (URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret))
- |> Base.encode64()
+ conn =
+ conn
+ |> put_session(:oauth_token, token.token)
+ |> get(
+ "/oauth/authorize",
+ %{
+ "response_type" => "code",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "scope" => "read",
+ "force_login" => "true"
+ }
+ )
- conn =
- build_conn()
- |> put_req_header("authorization", "Basic " <> app_encoded)
- |> post("/oauth/token", %{
- "grant_type" => "authorization_code",
- "code" => auth.token,
- "redirect_uri" => app.redirect_uris
- })
+ assert html_response(conn, 200) =~ ~s(type="submit")
+ end
- assert %{"access_token" => token} = json_response(conn, 200)
- assert Repo.get_by(Token, token: token)
+ test "redirects to app if user is already authenticated", %{app: app, conn: conn} do
+ token = insert(:oauth_token, app_id: app.id)
+
+ conn =
+ conn
+ |> put_session(:oauth_token, token.token)
+ |> get(
+ "/oauth/authorize",
+ %{
+ "response_type" => "code",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "scope" => "read"
+ }
+ )
+
+ assert redirected_to(conn) == "https://redirect.url"
+ end
end
- test "rejects token exchange with invalid client credentials" do
- user = insert(:user)
- app = insert(:oauth_app)
+ describe "POST /oauth/authorize" do
+ test "redirects with oauth authorization" do
+ user = insert(:user)
+ app = insert(:oauth_app, scopes: ["read", "write", "follow"])
+
+ conn =
+ build_conn()
+ |> post("/oauth/authorize", %{
+ "authorization" => %{
+ "name" => user.nickname,
+ "password" => "test",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "scope" => "read write",
+ "state" => "statepassed"
+ }
+ })
+
+ target = redirected_to(conn)
+ assert target =~ app.redirect_uris
+
+ query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
+
+ assert %{"state" => "statepassed", "code" => code} = query
+ auth = Repo.get_by(Authorization, token: code)
+ assert auth
+ assert auth.scopes == ["read", "write"]
+ end
- {:ok, auth} = Authorization.create_authorization(app, user)
+ test "returns 401 for wrong credentials", %{conn: conn} do
+ user = insert(:user)
+ app = insert(:oauth_app)
- conn =
- build_conn()
- |> put_req_header("authorization", "Basic JTIxOiVGMCU5RiVBNCVCNwo=")
- |> post("/oauth/token", %{
- "grant_type" => "authorization_code",
- "code" => auth.token,
- "redirect_uri" => app.redirect_uris
- })
+ result =
+ conn
+ |> post("/oauth/authorize", %{
+ "authorization" => %{
+ "name" => user.nickname,
+ "password" => "wrong",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "statepassed",
+ "scope" => Enum.join(app.scopes, " ")
+ }
+ })
+ |> html_response(:unauthorized)
- assert resp = json_response(conn, 400)
- assert %{"error" => _} = resp
- refute Map.has_key?(resp, "access_token")
+ # Keep the details
+ assert result =~ app.client_id
+ assert result =~ app.redirect_uris
+
+ # Error message
+ assert result =~ "Invalid Username/Password"
+ end
+
+ test "returns 401 for missing scopes", %{conn: conn} do
+ user = insert(:user)
+ app = insert(:oauth_app)
+
+ result =
+ conn
+ |> post("/oauth/authorize", %{
+ "authorization" => %{
+ "name" => user.nickname,
+ "password" => "test",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "statepassed",
+ "scope" => ""
+ }
+ })
+ |> html_response(:unauthorized)
+
+ # Keep the details
+ assert result =~ app.client_id
+ assert result =~ app.redirect_uris
+
+ # Error message
+ assert result =~ "This action is outside the authorized scopes"
+ end
+
+ test "returns 401 for scopes beyond app scopes", %{conn: conn} do
+ user = insert(:user)
+ app = insert(:oauth_app, scopes: ["read", "write"])
+
+ result =
+ conn
+ |> post("/oauth/authorize", %{
+ "authorization" => %{
+ "name" => user.nickname,
+ "password" => "test",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "statepassed",
+ "scope" => "read write follow"
+ }
+ })
+ |> html_response(:unauthorized)
+
+ # Keep the details
+ assert result =~ app.client_id
+ assert result =~ app.redirect_uris
+
+ # Error message
+ assert result =~ "This action is outside the authorized scopes"
+ end
end
- test "rejects an invalid authorization code" do
- app = insert(:oauth_app)
+ describe "POST /oauth/token" do
+ test "issues a token for an all-body request" do
+ user = insert(:user)
+ app = insert(:oauth_app, scopes: ["read", "write"])
- conn =
- build_conn()
- |> post("/oauth/token", %{
- "grant_type" => "authorization_code",
- "code" => "Imobviouslyinvalid",
- "redirect_uri" => app.redirect_uris,
- "client_id" => app.client_id,
- "client_secret" => app.client_secret
- })
+ {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "authorization_code",
+ "code" => auth.token,
+ "redirect_uri" => app.redirect_uris,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert %{"access_token" => token, "me" => ap_id} = json_response(conn, 200)
+
+ token = Repo.get_by(Token, token: token)
+ assert token
+ assert token.scopes == auth.scopes
+ assert user.ap_id == ap_id
+ end
+
+ test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
+ password = "testpassword"
+ user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
+
+ app = insert(:oauth_app, scopes: ["read", "write"])
+
+ # Note: "scope" param is intentionally omitted
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "password",
+ "username" => user.nickname,
+ "password" => password,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert %{"access_token" => token} = json_response(conn, 200)
+
+ token = Repo.get_by(Token, token: token)
+ assert token
+ assert token.scopes == app.scopes
+ end
+
+ test "issues a token for request with HTTP basic auth client credentials" do
+ user = insert(:user)
+ app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"])
+
+ {:ok, auth} = Authorization.create_authorization(app, user, ["scope1", "scope2"])
+ assert auth.scopes == ["scope1", "scope2"]
+
+ app_encoded =
+ (URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret))
+ |> Base.encode64()
+
+ conn =
+ build_conn()
+ |> put_req_header("authorization", "Basic " <> app_encoded)
+ |> post("/oauth/token", %{
+ "grant_type" => "authorization_code",
+ "code" => auth.token,
+ "redirect_uri" => app.redirect_uris
+ })
+
+ assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200)
+
+ assert scope == "scope1 scope2"
+
+ token = Repo.get_by(Token, token: token)
+ assert token
+ assert token.scopes == ["scope1", "scope2"]
+ end
+
+ test "rejects token exchange with invalid client credentials" do
+ user = insert(:user)
+ app = insert(:oauth_app)
+
+ {:ok, auth} = Authorization.create_authorization(app, user)
+
+ conn =
+ build_conn()
+ |> put_req_header("authorization", "Basic JTIxOiVGMCU5RiVBNCVCNwo=")
+ |> post("/oauth/token", %{
+ "grant_type" => "authorization_code",
+ "code" => auth.token,
+ "redirect_uri" => app.redirect_uris
+ })
+
+ assert resp = json_response(conn, 400)
+ assert %{"error" => _} = resp
+ refute Map.has_key?(resp, "access_token")
+ end
+
+ test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do
+ setting = Pleroma.Config.get([:instance, :account_activation_required])
+
+ unless setting do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+ on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
+ end
+
+ password = "testpassword"
+ user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
+ info_change = Pleroma.User.Info.confirmation_changeset(user.info, :unconfirmed)
+
+ {:ok, user} =
+ user
+ |> Ecto.Changeset.change()
+ |> Ecto.Changeset.put_embed(:info, info_change)
+ |> Repo.update()
+
+ refute Pleroma.User.auth_active?(user)
+
+ app = insert(:oauth_app)
+
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "password",
+ "username" => user.nickname,
+ "password" => password,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert resp = json_response(conn, 403)
+ assert %{"error" => _} = resp
+ refute Map.has_key?(resp, "access_token")
+ end
+
+ test "rejects token exchange for valid credentials belonging to deactivated user" do
+ password = "testpassword"
+
+ user =
+ insert(:user,
+ password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
+ info: %{deactivated: true}
+ )
+
+ app = insert(:oauth_app)
+
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "password",
+ "username" => user.nickname,
+ "password" => password,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert resp = json_response(conn, 403)
+ assert %{"error" => _} = resp
+ refute Map.has_key?(resp, "access_token")
+ end
+
+ test "rejects an invalid authorization code" do
+ app = insert(:oauth_app)
+
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "authorization_code",
+ "code" => "Imobviouslyinvalid",
+ "redirect_uri" => app.redirect_uris,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
- assert resp = json_response(conn, 400)
- assert %{"error" => _} = json_response(conn, 400)
- refute Map.has_key?(resp, "access_token")
+ assert resp = json_response(conn, 400)
+ assert %{"error" => _} = json_response(conn, 400)
+ refute Map.has_key?(resp, "access_token")
+ end
end
end
diff --git a/test/web/oauth/token_test.exs b/test/web/oauth/token_test.exs
index f926ff50b..ad2a49f09 100644
--- a/test/web/oauth/token_test.exs
+++ b/test/web/oauth/token_test.exs
@@ -1,28 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OAuth.TokenTest do
use Pleroma.DataCase
- alias Pleroma.Web.OAuth.{App, Token, Authorization}
alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Authorization
+ alias Pleroma.Web.OAuth.Token
import Pleroma.Factory
- test "exchanges a auth token for an access token" do
+ test "exchanges a auth token for an access token, preserving `scopes`" do
{:ok, app} =
Repo.insert(
App.register_changeset(%App{}, %{
client_name: "client",
- scopes: "scope",
+ scopes: ["read", "write"],
redirect_uris: "url"
})
)
user = insert(:user)
- {:ok, auth} = Authorization.create_authorization(app, user)
+ {:ok, auth} = Authorization.create_authorization(app, user, ["read"])
+ assert auth.scopes == ["read"]
{:ok, token} = Token.exchange_token(app, auth)
assert token.app_id == app.id
assert token.user_id == user.id
+ assert token.scopes == auth.scopes
assert String.length(token.token) > 10
assert String.length(token.refresh_token) > 10
@@ -35,7 +43,7 @@ defmodule Pleroma.Web.OAuth.TokenTest do
Repo.insert(
App.register_changeset(%App{}, %{
client_name: "client1",
- scopes: "scope",
+ scopes: ["scope"],
redirect_uris: "url"
})
)
@@ -44,7 +52,7 @@ defmodule Pleroma.Web.OAuth.TokenTest do
Repo.insert(
App.register_changeset(%App{}, %{
client_name: "client2",
- scopes: "scope",
+ scopes: ["scope"],
redirect_uris: "url"
})
)
@@ -54,8 +62,8 @@ defmodule Pleroma.Web.OAuth.TokenTest do
{:ok, auth1} = Authorization.create_authorization(app1, user)
{:ok, auth2} = Authorization.create_authorization(app2, user)
- {:ok, token1} = Token.exchange_token(app1, auth1)
- {:ok, token2} = Token.exchange_token(app2, auth2)
+ {:ok, _token1} = Token.exchange_token(app1, auth1)
+ {:ok, _token2} = Token.exchange_token(app2, auth2)
{tokens, _} = Token.delete_user_tokens(user)
diff --git a/test/web/ostatus/activity_representer_test.exs b/test/web/ostatus/activity_representer_test.exs
index 8bf3bc775..a4bb68c4d 100644
--- a/test/web/ostatus/activity_representer_test.exs
+++ b/test/web/ostatus/activity_representer_test.exs
@@ -1,12 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do
use Pleroma.DataCase
- alias Pleroma.Web.OStatus.ActivityRepresenter
- alias Pleroma.{User, Activity, Object}
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.OStatus
+ alias Pleroma.Web.OStatus.ActivityRepresenter
import Pleroma.Factory
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
test "an external note activity" do
incoming = File.read!("test/fixtures/mastodon-note-cw.xml")
@@ -104,10 +116,10 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do
{:ok, announce, _object} = ActivityPub.announce(user, object)
- announce = Repo.get(Activity, announce.id)
+ announce = Activity.get_by_id(announce.id)
note_user = User.get_cached_by_ap_id(note.data["actor"])
- note = Repo.get(Activity, note.id)
+ note = Activity.get_by_id(note.id)
note_xml =
ActivityRepresenter.to_simple_form(note, note_user, true)
diff --git a/test/web/ostatus/feed_representer_test.exs b/test/web/ostatus/feed_representer_test.exs
index bf3feb14e..3c7b126e7 100644
--- a/test/web/ostatus/feed_representer_test.exs
+++ b/test/web/ostatus/feed_representer_test.exs
@@ -1,9 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OStatus.FeedRepresenterTest do
use Pleroma.DataCase
import Pleroma.Factory
alias Pleroma.User
- alias Pleroma.Web.OStatus.{FeedRepresenter, UserRepresenter, ActivityRepresenter}
alias Pleroma.Web.OStatus
+ alias Pleroma.Web.OStatus.ActivityRepresenter
+ alias Pleroma.Web.OStatus.FeedRepresenter
+ alias Pleroma.Web.OStatus.UserRepresenter
test "returns a feed of the last 20 items of the user" do
note_activity = insert(:note_activity)
diff --git a/test/web/ostatus/incoming_documents/delete_handling_test.exs b/test/web/ostatus/incoming_documents/delete_handling_test.exs
index 1e041e5b0..ca6e61339 100644
--- a/test/web/ostatus/incoming_documents/delete_handling_test.exs
+++ b/test/web/ostatus/incoming_documents/delete_handling_test.exs
@@ -2,9 +2,17 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest do
use Pleroma.DataCase
import Pleroma.Factory
- alias Pleroma.{Repo, Activity, Object}
+ import Tesla.Mock
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
alias Pleroma.Web.OStatus
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
describe "deletions" do
test "it removes the mentioned activity" do
note = insert(:note_activity)
@@ -23,10 +31,10 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest do
{:ok, [delete]} = OStatus.handle_incoming(incoming)
- refute Repo.get(Activity, note.id)
- refute Repo.get(Activity, like.id)
- refute Object.get_by_ap_id(note.data["object"]["id"])
- assert Repo.get(Activity, second_note.id)
+ refute Activity.get_by_id(note.id)
+ refute Activity.get_by_id(like.id)
+ assert Object.get_by_ap_id(note.data["object"]["id"]).data["type"] == "Tombstone"
+ assert Activity.get_by_id(second_note.id)
assert Object.get_by_ap_id(second_note.data["object"]["id"])
assert delete.data["type"] == "Delete"
diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs
index e81adde68..2950f11c0 100644
--- a/test/web/ostatus/ostatus_controller_test.exs
+++ b/test/web/ostatus/ostatus_controller_test.exs
@@ -1,53 +1,66 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OStatus.OStatusControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
- alias Pleroma.{User, Repo}
+ alias Pleroma.Object
+ alias Pleroma.Repo
+ alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OStatus.ActivityRepresenter
- test "decodes a salmon", %{conn: conn} do
- user = insert(:user)
- salmon = File.read!("test/fixtures/salmon.xml")
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
- conn =
- conn
- |> put_req_header("content-type", "application/atom+xml")
- |> post("/users/#{user.nickname}/salmon", salmon)
+ describe "salmon_incoming" do
+ test "decodes a salmon", %{conn: conn} do
+ user = insert(:user)
+ salmon = File.read!("test/fixtures/salmon.xml")
- assert response(conn, 200)
- end
+ conn =
+ conn
+ |> put_req_header("content-type", "application/atom+xml")
+ |> post("/users/#{user.nickname}/salmon", salmon)
- test "decodes a salmon with a changed magic key", %{conn: conn} do
- user = insert(:user)
- salmon = File.read!("test/fixtures/salmon.xml")
+ assert response(conn, 200)
+ end
- conn =
- conn
- |> put_req_header("content-type", "application/atom+xml")
- |> post("/users/#{user.nickname}/salmon", salmon)
+ test "decodes a salmon with a changed magic key", %{conn: conn} do
+ user = insert(:user)
+ salmon = File.read!("test/fixtures/salmon.xml")
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/atom+xml")
+ |> post("/users/#{user.nickname}/salmon", salmon)
- assert response(conn, 200)
+ assert response(conn, 200)
- # Set a wrong magic-key for a user so it has to refetch
- salmon_user = User.get_by_ap_id("http://gs.example.org:4040/index.php/user/1")
- # Wrong key
- info_cng =
- User.Info.remote_user_creation(salmon_user.info, %{
- magic_key:
- "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
- })
+ # Set a wrong magic-key for a user so it has to refetch
+ salmon_user = User.get_by_ap_id("http://gs.example.org:4040/index.php/user/1")
+ # Wrong key
+ info_cng =
+ User.Info.remote_user_creation(salmon_user.info, %{
+ magic_key:
+ "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
+ })
- cng =
- Ecto.Changeset.change(salmon_user)
+ salmon_user
+ |> Ecto.Changeset.change()
|> Ecto.Changeset.put_embed(:info, info_cng)
|> Repo.update()
- conn =
- build_conn()
- |> put_req_header("content-type", "application/atom+xml")
- |> post("/users/#{user.nickname}/salmon", salmon)
+ conn =
+ build_conn()
+ |> put_req_header("content-type", "application/atom+xml")
+ |> post("/users/#{user.nickname}/salmon", salmon)
- assert response(conn, 200)
+ assert response(conn, 200)
+ end
end
test "gets a feed", %{conn: conn} do
@@ -79,6 +92,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
conn =
conn
+ |> put_req_header("accept", "application/xml")
|> get(url)
expected =
@@ -92,82 +106,77 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
test "404s on private objects", %{conn: conn} do
note_activity = insert(:direct_note_activity)
- user = User.get_by_ap_id(note_activity.data["actor"])
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
- url = "/objects/#{uuid}"
-
- conn =
- conn
- |> get(url)
- assert response(conn, 404)
+ conn
+ |> get("/objects/#{uuid}")
+ |> response(404)
end
test "404s on nonexisting objects", %{conn: conn} do
- url = "/objects/123"
+ conn
+ |> get("/objects/123")
+ |> response(404)
+ end
- conn =
- conn
- |> get(url)
+ test "gets an activity in xml format", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
- assert response(conn, 404)
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/activities/#{uuid}")
+ |> response(200)
end
- test "gets an activity", %{conn: conn} do
+ test "404s on deleted objects", %{conn: conn} do
note_activity = insert(:note_activity)
- [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
- url = "/activities/#{uuid}"
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
+ object = Object.get_by_ap_id(note_activity.data["object"]["id"])
- conn =
- conn
- |> get(url)
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/objects/#{uuid}")
+ |> response(200)
+
+ Object.delete(object)
- assert response(conn, 200)
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/objects/#{uuid}")
+ |> response(404)
end
test "404s on private activities", %{conn: conn} do
note_activity = insert(:direct_note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
- url = "/activities/#{uuid}"
- conn =
- conn
- |> get(url)
-
- assert response(conn, 404)
+ conn
+ |> get("/activities/#{uuid}")
+ |> response(404)
end
test "404s on nonexistent activities", %{conn: conn} do
- url = "/activities/123"
-
- conn =
- conn
- |> get(url)
-
- assert response(conn, 404)
+ conn
+ |> get("/activities/123")
+ |> response(404)
end
- test "gets a notice", %{conn: conn} do
+ test "gets a notice in xml format", %{conn: conn} do
note_activity = insert(:note_activity)
- url = "/notice/#{note_activity.id}"
-
- conn =
- conn
- |> get(url)
- assert response(conn, 200)
+ conn
+ |> get("/notice/#{note_activity.id}")
+ |> response(200)
end
test "gets a notice in AS2 format", %{conn: conn} do
note_activity = insert(:note_activity)
- url = "/notice/#{note_activity.id}"
- conn =
- conn
- |> put_req_header("accept", "application/activity+json")
- |> get(url)
-
- assert json_response(conn, 200)
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/notice/#{note_activity.id}")
+ |> json_response(200)
end
test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
diff --git a/test/web/ostatus/ostatus_test.exs b/test/web/ostatus/ostatus_test.exs
index b5805c668..50467c71f 100644
--- a/test/web/ostatus/ostatus_test.exs
+++ b/test/web/ostatus/ostatus_test.exs
@@ -1,11 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OStatusTest do
use Pleroma.DataCase
+ alias Pleroma.Activity
+ alias Pleroma.Instances
+ alias Pleroma.Object
+ alias Pleroma.Repo
+ alias Pleroma.User
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
- alias Pleroma.{Object, Repo, User, Activity}
import Pleroma.Factory
import ExUnit.CaptureLog
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
test "don't insert create notes twice" do
incoming = File.read!("test/fixtures/incoming_note_activity.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
@@ -150,9 +163,8 @@ defmodule Pleroma.Web.OStatusTest do
assert "https://pleroma.soykaf.com/users/lain" in activity.data["to"]
refute activity.local
- retweeted_activity = Repo.get(Activity, retweeted_activity.id)
+ retweeted_activity = Activity.get_by_id(retweeted_activity.id)
retweeted_object = Object.normalize(retweeted_activity.data["object"])
-
assert retweeted_activity.data["type"] == "Create"
assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain"
refute retweeted_activity.local
@@ -179,7 +191,7 @@ defmodule Pleroma.Web.OStatusTest do
assert user.ap_id in activity.data["to"]
refute activity.local
- retweeted_activity = Repo.get(Activity, retweeted_activity.id)
+ retweeted_activity = Activity.get_by_id(retweeted_activity.id)
assert note_activity.id == retweeted_activity.id
assert retweeted_activity.data["type"] == "Create"
assert retweeted_activity.data["actor"] == user.ap_id
@@ -314,6 +326,22 @@ defmodule Pleroma.Web.OStatusTest do
refute User.following?(follower, followed)
end
+ test "it clears `unreachable` federation status of the sender" do
+ incoming_reaction_xml = File.read!("test/fixtures/share-gs.xml")
+ doc = XML.parse_document(incoming_reaction_xml)
+ actor_uri = XML.string_from_xpath("//author/uri[1]", doc)
+ reacted_to_author_uri = XML.string_from_xpath("//author/uri[2]", doc)
+
+ Instances.set_consistently_unreachable(actor_uri)
+ Instances.set_consistently_unreachable(reacted_to_author_uri)
+ refute Instances.reachable?(actor_uri)
+ refute Instances.reachable?(reacted_to_author_uri)
+
+ {:ok, _} = OStatus.handle_incoming(incoming_reaction_xml)
+ assert Instances.reachable?(actor_uri)
+ refute Instances.reachable?(reacted_to_author_uri)
+ end
+
describe "new remote user creation" do
test "returns local users" do
local_user = insert(:user)
@@ -327,7 +355,7 @@ defmodule Pleroma.Web.OStatusTest do
{:ok, user} = OStatus.find_or_make_user(uri)
- user = Repo.get(Pleroma.User, user.id)
+ user = Pleroma.User.get_by_id(user.id)
assert user.name == "Constance Variable"
assert user.nickname == "lambadalambda@social.heldscal.la"
assert user.local == false
@@ -517,8 +545,10 @@ defmodule Pleroma.Web.OStatusTest do
note_object.data
|> Map.put("type", "Article")
+ Cachex.clear(:object_cache)
+
cs = Object.change(note_object, %{data: note_data})
- {:ok, article_object} = Repo.update(cs)
+ {:ok, _article_object} = Repo.update(cs)
# the underlying object is now an Article instead of a note, so this should fail
refute OStatus.is_representable?(note_activity)
diff --git a/test/web/ostatus/user_representer_test.exs b/test/web/ostatus/user_representer_test.exs
index 82fb8e793..e3863d2e9 100644
--- a/test/web/ostatus/user_representer_test.exs
+++ b/test/web/ostatus/user_representer_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OStatus.UserRepresenterTest do
use Pleroma.DataCase
alias Pleroma.Web.OStatus.UserRepresenter
diff --git a/test/web/plugs/federating_plug_test.exs b/test/web/plugs/federating_plug_test.exs
index 1455a1c46..612db7e32 100644
--- a/test/web/plugs/federating_plug_test.exs
+++ b/test/web/plugs/federating_plug_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.FederatingPlugTest do
use Pleroma.Web.ConnCase
diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs
new file mode 100644
index 000000000..6bac2c9f6
--- /dev/null
+++ b/test/web/push/impl_test.exs
@@ -0,0 +1,147 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Push.ImplTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.Push.Impl
+ alias Pleroma.Web.Push.Subscription
+
+ import Pleroma.Factory
+
+ setup_all do
+ Tesla.Mock.mock_global(fn
+ %{method: :post, url: "https://example.com/example/1234"} ->
+ %Tesla.Env{status: 200}
+
+ %{method: :post, url: "https://example.com/example/not_found"} ->
+ %Tesla.Env{status: 400}
+
+ %{method: :post, url: "https://example.com/example/bad"} ->
+ %Tesla.Env{status: 100}
+ end)
+
+ :ok
+ end
+
+ @sub %{
+ endpoint: "https://example.com/example/1234",
+ keys: %{
+ auth: "8eDyX_uCN0XRhSbY5hs7Hg==",
+ p256dh:
+ "BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA="
+ }
+ }
+ @api_key "BASgACIHpN1GYgzSRp"
+ @message "@Bob: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini..."
+
+ test "performs sending notifications" do
+ user = insert(:user)
+ user2 = insert(:user)
+ insert(:push_subscription, user: user, data: %{alerts: %{"mention" => true}})
+ insert(:push_subscription, user: user2, data: %{alerts: %{"mention" => true}})
+
+ insert(:push_subscription,
+ user: user,
+ data: %{alerts: %{"follow" => true, "mention" => true}}
+ )
+
+ insert(:push_subscription,
+ user: user,
+ data: %{alerts: %{"follow" => true, "mention" => false}}
+ )
+
+ notif =
+ insert(:notification,
+ user: user,
+ activity: %Pleroma.Activity{
+ data: %{
+ "type" => "Create",
+ "actor" => user.ap_id,
+ "object" => %{"content" => "<Lorem ipsum dolor sit amet."}
+ }
+ }
+ )
+
+ assert Impl.perform(notif) == [:ok, :ok]
+ end
+
+ @tag capture_log: true
+ test "returns error if notif does not match " do
+ assert Impl.perform(%{}) == :error
+ end
+
+ test "successful message sending" do
+ assert Impl.push_message(@message, @sub, @api_key, %Subscription{}) == :ok
+ end
+
+ @tag capture_log: true
+ test "fail message sending" do
+ assert Impl.push_message(
+ @message,
+ Map.merge(@sub, %{endpoint: "https://example.com/example/bad"}),
+ @api_key,
+ %Subscription{}
+ ) == :error
+ end
+
+ test "delete subsciption if restult send message between 400..500" do
+ subscription = insert(:push_subscription)
+
+ assert Impl.push_message(
+ @message,
+ Map.merge(@sub, %{endpoint: "https://example.com/example/not_found"}),
+ @api_key,
+ subscription
+ ) == :ok
+
+ refute Pleroma.Repo.get(Subscription, subscription.id)
+ end
+
+ test "renders body for create activity" do
+ assert Impl.format_body(
+ %{
+ activity: %{
+ data: %{
+ "type" => "Create",
+ "object" => %{
+ "content" =>
+ "<span>Lorem ipsum dolor sit amet</span>, consectetur :bear: adipiscing elit. Fusce sagittis finibus turpis."
+ }
+ }
+ }
+ },
+ %{nickname: "Bob"}
+ ) ==
+ "@Bob: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini..."
+ end
+
+ test "renders body for follow activity" do
+ assert Impl.format_body(%{activity: %{data: %{"type" => "Follow"}}}, %{nickname: "Bob"}) ==
+ "@Bob has followed you"
+ end
+
+ test "renders body for announce activity" do
+ user = insert(:user)
+
+ note =
+ insert(:note, %{
+ data: %{
+ "content" =>
+ "<span>Lorem ipsum dolor sit amet</span>, consectetur :bear: adipiscing elit. Fusce sagittis finibus turpis."
+ }
+ })
+
+ note_activity = insert(:note_activity, %{note: note})
+ announce_activity = insert(:announce_activity, %{user: user, note_activity: note_activity})
+
+ assert Impl.format_body(%{activity: announce_activity}, user) ==
+ "@#{user.nickname} repeated: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini..."
+ end
+
+ test "renders body for like activity" do
+ assert Impl.format_body(%{activity: %{data: %{"type" => "Like"}}}, %{nickname: "Bob"}) ==
+ "@Bob has favorited your post"
+ end
+end
diff --git a/test/web/rel_me_test.exs b/test/web/rel_me_test.exs
new file mode 100644
index 000000000..5188f4de1
--- /dev/null
+++ b/test/web/rel_me_test.exs
@@ -0,0 +1,67 @@
+defmodule Pleroma.Web.RelMeTest do
+ use ExUnit.Case, async: true
+
+ setup do
+ Tesla.Mock.mock(fn
+ %{
+ method: :get,
+ url: "http://example.com/rel_me/anchor"
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor.html")}
+
+ %{
+ method: :get,
+ url: "http://example.com/rel_me/anchor_nofollow"
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor_nofollow.html")}
+
+ %{
+ method: :get,
+ url: "http://example.com/rel_me/link"
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_link.html")}
+
+ %{
+ method: :get,
+ url: "http://example.com/rel_me/null"
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_null.html")}
+ end)
+
+ :ok
+ end
+
+ test "parse/1" do
+ hrefs = ["https://social.example.org/users/lain"]
+
+ assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/null") == {:ok, []}
+ assert {:error, _} = Pleroma.Web.RelMe.parse("http://example.com/rel_me/error")
+
+ assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/link") == {:ok, hrefs}
+ assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/anchor") == {:ok, hrefs}
+ assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/anchor_nofollow") == {:ok, hrefs}
+ end
+
+ test "maybe_put_rel_me/2" do
+ profile_urls = ["https://social.example.org/users/lain"]
+ attr = "me"
+ fallback = nil
+
+ assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/null", profile_urls) ==
+ fallback
+
+ assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/error", profile_urls) ==
+ fallback
+
+ assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/anchor", profile_urls) ==
+ attr
+
+ assert Pleroma.Web.RelMe.maybe_put_rel_me(
+ "http://example.com/rel_me/anchor_nofollow",
+ profile_urls
+ ) == attr
+
+ assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/link", profile_urls) ==
+ attr
+ end
+end
diff --git a/test/web/retry_queue_test.exs b/test/web/retry_queue_test.exs
index ce2964993..ecb3ce5d0 100644
--- a/test/web/retry_queue_test.exs
+++ b/test/web/retry_queue_test.exs
@@ -1,31 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule MockActivityPub do
- def publish_one(ret) do
+ def publish_one({ret, waiter}) do
+ send(waiter, :complete)
{ret, "success"}
end
end
-defmodule Pleroma.ActivityTest do
+defmodule Pleroma.Web.Federator.RetryQueueTest do
use Pleroma.DataCase
alias Pleroma.Web.Federator.RetryQueue
@small_retry_count 0
@hopeless_retry_count 10
+ setup do
+ RetryQueue.reset_stats()
+ end
+
+ test "RetryQueue responds to stats request" do
+ assert %{delivered: 0, dropped: 0} == RetryQueue.get_stats()
+ end
+
test "failed posts are retried" do
{:retry, _timeout} = RetryQueue.get_retry_params(@small_retry_count)
- assert {:noreply, %{delivered: 1}} ==
- RetryQueue.handle_info({:send, :ok, MockActivityPub, @small_retry_count}, %{
- delivered: 0
- })
+ wait_task =
+ Task.async(fn ->
+ receive do
+ :complete -> :ok
+ end
+ end)
+
+ RetryQueue.enqueue({:ok, wait_task.pid}, MockActivityPub, @small_retry_count)
+ Task.await(wait_task)
+ assert %{delivered: 1, dropped: 0} == RetryQueue.get_stats()
end
test "posts that have been tried too many times are dropped" do
{:drop, _timeout} = RetryQueue.get_retry_params(@hopeless_retry_count)
- assert {:noreply, %{dropped: 1}} ==
- RetryQueue.handle_cast({:maybe_enqueue, %{}, nil, @hopeless_retry_count}, %{
- dropped: 0
- })
+ RetryQueue.enqueue({:ok, nil}, MockActivityPub, @hopeless_retry_count)
+ assert %{delivered: 0, dropped: 1} == RetryQueue.get_stats()
end
end
diff --git a/test/web/rich_media/helpers_test.exs b/test/web/rich_media/helpers_test.exs
new file mode 100644
index 000000000..60d93768f
--- /dev/null
+++ b/test/web/rich_media/helpers_test.exs
@@ -0,0 +1,62 @@
+defmodule Pleroma.Web.RichMedia.HelpersTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ test "refuses to crawl incomplete URLs" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "[test](example.com/ogp)",
+ "content_type" => "text/markdown"
+ })
+
+ Pleroma.Config.put([:rich_media, :enabled], true)
+
+ assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+
+ Pleroma.Config.put([:rich_media, :enabled], false)
+ end
+
+ test "refuses to crawl malformed URLs" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "[test](example.com[]/ogp)",
+ "content_type" => "text/markdown"
+ })
+
+ Pleroma.Config.put([:rich_media, :enabled], true)
+
+ assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+
+ Pleroma.Config.put([:rich_media, :enabled], false)
+ end
+
+ test "crawls valid, complete URLs" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "[test](http://example.com/ogp)",
+ "content_type" => "text/markdown"
+ })
+
+ Pleroma.Config.put([:rich_media, :enabled], true)
+
+ assert %{page_url: "http://example.com/ogp", rich_media: _} =
+ Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+
+ Pleroma.Config.put([:rich_media, :enabled], false)
+ end
+end
diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs
new file mode 100644
index 000000000..47b127cf9
--- /dev/null
+++ b/test/web/rich_media/parser_test.exs
@@ -0,0 +1,95 @@
+defmodule Pleroma.Web.RichMedia.ParserTest do
+ use ExUnit.Case, async: true
+
+ setup do
+ Tesla.Mock.mock(fn
+ %{
+ method: :get,
+ url: "http://example.com/ogp"
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}
+
+ %{
+ method: :get,
+ url: "http://example.com/twitter-card"
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
+
+ %{
+ method: :get,
+ url: "http://example.com/oembed"
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.html")}
+
+ %{
+ method: :get,
+ url: "http://example.com/oembed.json"
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.json")}
+
+ %{method: :get, url: "http://example.com/empty"} ->
+ %Tesla.Env{status: 200, body: "hello"}
+ end)
+
+ :ok
+ end
+
+ test "returns error when no metadata present" do
+ assert {:error, _} = Pleroma.Web.RichMedia.Parser.parse("http://example.com/empty")
+ end
+
+ test "parses ogp" do
+ assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp") ==
+ {:ok,
+ %{
+ image: "http://ia.media-imdb.com/images/rock.jpg",
+ title: "The Rock",
+ type: "video.movie",
+ url: "http://www.imdb.com/title/tt0117500/"
+ }}
+ end
+
+ test "parses twitter card" do
+ assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/twitter-card") ==
+ {:ok,
+ %{
+ card: "summary",
+ site: "@flickr",
+ image: "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
+ title: "Small Island Developing States Photo Submission",
+ description: "View the album on Flickr."
+ }}
+ end
+
+ test "parses OEmbed" do
+ assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/oembed") ==
+ {:ok,
+ %{
+ author_name: "‮‭‬bees‬",
+ author_url: "https://www.flickr.com/photos/bees/",
+ cache_age: 3600,
+ flickr_type: "photo",
+ height: "768",
+ html:
+ "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by ‮‭‬bees‬, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
+ license: "All Rights Reserved",
+ license_id: 0,
+ provider_name: "Flickr",
+ provider_url: "https://www.flickr.com/",
+ thumbnail_height: 150,
+ thumbnail_url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg",
+ thumbnail_width: 150,
+ title: "Bacon Lollys",
+ type: "photo",
+ url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg",
+ version: "1.0",
+ web_page: "https://www.flickr.com/photos/bees/2362225867/",
+ web_page_short_url: "https://flic.kr/p/4AK2sc",
+ width: "1024"
+ }}
+ end
+
+ test "rejects invalid OGP data" do
+ assert {:error, _} = Pleroma.Web.RichMedia.Parser.parse("http://example.com/malformed")
+ end
+end
diff --git a/test/web/salmon/salmon_test.exs b/test/web/salmon/salmon_test.exs
index 1b39b4b2d..35503259b 100644
--- a/test/web/salmon/salmon_test.exs
+++ b/test/web/salmon/salmon_test.exs
@@ -1,7 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Salmon.SalmonTest do
use Pleroma.DataCase
+ alias Pleroma.Activity
+ alias Pleroma.Repo
+ alias Pleroma.User
alias Pleroma.Web.Salmon
- alias Pleroma.{Repo, Activity, User}
import Pleroma.Factory
@magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
@@ -10,6 +16,11 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
@magickey_friendica "RSA.AMwa8FUs2fWEjX0xN7yRQgegQffhBpuKNC6fa5VNSVorFjGZhRrlPMn7TQOeihlc9lBz2OsHlIedbYn2uJ7yCs0.AQAB"
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
test "decodes a salmon" do
{:ok, salmon} = File.read("test/fixtures/salmon.xml")
{:ok, doc} = Salmon.decode_and_validate(@magickey, salmon)
@@ -69,7 +80,7 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
test "it pushes an activity to remote accounts it's addressed to" do
user_data = %{
info: %{
- "salmon" => "http://example.org/salmon"
+ salmon: "http://test-example.org/salmon"
},
local: false
}
@@ -88,11 +99,11 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
}
{:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]})
- user = Repo.get_by(User, ap_id: activity.data["actor"])
+ user = User.get_by_ap_id(activity.data["actor"])
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
- poster = fn url, _data, _headers, _options ->
- assert url == "http://example.org/salmon"
+ poster = fn url, _data, _headers ->
+ assert url == "http://test-example.org/salmon"
end
Salmon.publish(user, activity, poster)
diff --git a/test/web/streamer_test.exs b/test/web/streamer_test.exs
index df58441f0..bfe18cb7f 100644
--- a/test/web/streamer_test.exs
+++ b/test/web/streamer_test.exs
@@ -1,9 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.StreamerTest do
use Pleroma.DataCase
- alias Pleroma.Web.Streamer
- alias Pleroma.{List, User}
+ alias Pleroma.List
+ alias Pleroma.User
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Streamer
import Pleroma.Factory
test "it sends to public" do
@@ -31,6 +36,36 @@ defmodule Pleroma.Web.StreamerTest do
Streamer.push_to_socket(topics, "public", activity)
Task.await(task)
+
+ task =
+ Task.async(fn ->
+ expected_event =
+ %{
+ "event" => "delete",
+ "payload" => activity.id
+ }
+ |> Jason.encode!()
+
+ assert_receive {:text, received_event}, 4_000
+ assert received_event == expected_event
+ end)
+
+ fake_socket = %{
+ transport_pid: task.pid,
+ assigns: %{
+ user: user
+ }
+ }
+
+ {:ok, activity} = CommonAPI.delete(activity.id, other_user)
+
+ topics = %{
+ "public" => [fake_socket]
+ }
+
+ Streamer.push_to_socket(topics, "public", activity)
+
+ Task.await(task)
end
test "it doesn't send to blocked users" do
@@ -167,4 +202,34 @@ defmodule Pleroma.Web.StreamerTest do
Task.await(task)
end
+
+ test "it doesn't send muted reblogs" do
+ user1 = insert(:user)
+ user2 = insert(:user)
+ user3 = insert(:user)
+ CommonAPI.hide_reblogs(user1, user2)
+
+ task =
+ Task.async(fn ->
+ refute_receive {:text, _}, 1_000
+ end)
+
+ fake_socket = %{
+ transport_pid: task.pid,
+ assigns: %{
+ user: user1
+ }
+ }
+
+ {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"})
+ {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2)
+
+ topics = %{
+ "public" => [fake_socket]
+ }
+
+ Streamer.push_to_socket(topics, "public", announce_activity)
+
+ Task.await(task)
+ end
end
diff --git a/test/web/twitter_api/representers/activity_representer_test.exs b/test/web/twitter_api/representers/activity_representer_test.exs
deleted file mode 100644
index 314f2b51f..000000000
--- a/test/web/twitter_api/representers/activity_representer_test.exs
+++ /dev/null
@@ -1,200 +0,0 @@
-defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenterTest do
- use Pleroma.DataCase
- alias Pleroma.{User, Activity, Object}
- alias Pleroma.Web.TwitterAPI.Representers.{ActivityRepresenter, ObjectRepresenter}
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Builders.UserBuilder
- alias Pleroma.Web.TwitterAPI.UserView
- import Pleroma.Factory
-
- test "an announce activity" do
- user = insert(:user)
- note_activity = insert(:note_activity)
- activity_actor = Repo.get_by(User, ap_id: note_activity.data["actor"])
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
-
- {:ok, announce_activity, _object} = ActivityPub.announce(user, object)
- note_activity = Activity.get_by_ap_id(note_activity.data["id"])
-
- status =
- ActivityRepresenter.to_map(announce_activity, %{
- users: [user, activity_actor],
- announced_activity: note_activity,
- for: user
- })
-
- assert status["id"] == announce_activity.id
- assert status["user"] == UserView.render("show.json", %{user: user, for: user})
-
- retweeted_status =
- ActivityRepresenter.to_map(note_activity, %{user: activity_actor, for: user})
-
- assert retweeted_status["repeated"] == true
- assert retweeted_status["id"] == note_activity.id
- assert status["statusnet_conversation_id"] == retweeted_status["statusnet_conversation_id"]
-
- assert status["retweeted_status"] == retweeted_status
- assert status["activity_type"] == "repeat"
- end
-
- test "a like activity" do
- user = insert(:user)
- note_activity = insert(:note_activity)
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
-
- {:ok, like_activity, _object} = ActivityPub.like(user, object)
-
- status =
- ActivityRepresenter.to_map(like_activity, %{user: user, liked_activity: note_activity})
-
- assert status["id"] == like_activity.id
- assert status["in_reply_to_status_id"] == note_activity.id
-
- note_activity = Activity.get_by_ap_id(note_activity.data["id"])
- activity_actor = Repo.get_by(User, ap_id: note_activity.data["actor"])
- liked_status = ActivityRepresenter.to_map(note_activity, %{user: activity_actor, for: user})
- assert liked_status["favorited"] == true
- assert status["activity_type"] == "like"
- end
-
- test "an activity" do
- user = insert(:user)
- # {:ok, mentioned_user } = UserBuilder.insert(%{nickname: "shp", ap_id: "shp"})
- mentioned_user = insert(:user, %{nickname: "shp"})
-
- # {:ok, follower} = UserBuilder.insert(%{following: [User.ap_followers(user)]})
- follower = insert(:user, %{following: [User.ap_followers(user)]})
-
- object = %Object{
- data: %{
- "type" => "Image",
- "url" => [
- %{
- "type" => "Link",
- "mediaType" => "image/jpg",
- "href" => "http://example.org/image.jpg"
- }
- ],
- "uuid" => 1
- }
- }
-
- content_html =
- "<script>alert('YAY')</script>Some :2hu: content mentioning <a href='#{mentioned_user.ap_id}'>@shp</shp>"
-
- content = HtmlSanitizeEx.strip_tags(content_html)
- date = DateTime.from_naive!(~N[2016-05-24 13:26:08.003], "Etc/UTC") |> DateTime.to_iso8601()
-
- {:ok, convo_object} = Object.context_mapping("2hu") |> Repo.insert()
-
- note_object = %{
- "id" => "https://example.com/id/1",
- "published" => date,
- "type" => "Note",
- "content" => content_html,
- "summary" => "2hu",
- "inReplyToStatusId" => 213_123,
- "attachment" => [object.data],
- "external_url" => "some url",
- "like_count" => 5,
- "announcement_count" => 3,
- "context" => "2hu",
- "tag" => ["content", "mentioning", "nsfw"],
- "emoji" => %{
- "2hu" => "corndog.png"
- }
- }
-
- Object.create(note_object)
-
- to = [
- User.ap_followers(user),
- "https://www.w3.org/ns/activitystreams#Public",
- mentioned_user.ap_id
- ]
-
- activity = %Activity{
- id: 1,
- data: %{
- "type" => "Create",
- "id" => "id",
- "to" => to,
- "actor" => User.ap_id(user),
- "object" => note_object["id"],
- "published" => date,
- "context" => "2hu"
- },
- local: false,
- recipients: to
- }
-
- expected_html =
- "<p>2hu</p>alert('YAY')Some <img height=\"32px\" width=\"32px\" alt=\"2hu\" title=\"2hu\" src=\"corndog.png\" /> content mentioning <a href=\"#{
- mentioned_user.ap_id
- }\">@shp</a>"
-
- expected_status = %{
- "id" => activity.id,
- "user" => UserView.render("show.json", %{user: user, for: follower}),
- "is_local" => false,
- "statusnet_html" => expected_html,
- "text" => "2hu" <> content,
- "is_post_verb" => true,
- "created_at" => "Tue May 24 13:26:08 +0000 2016",
- "in_reply_to_status_id" => 213_123,
- "in_reply_to_screen_name" => nil,
- "in_reply_to_user_id" => nil,
- "in_reply_to_profileurl" => nil,
- "in_reply_to_ostatus_uri" => nil,
- "statusnet_conversation_id" => convo_object.id,
- "attachments" => [
- ObjectRepresenter.to_map(object)
- ],
- "attentions" => [
- UserView.render("show.json", %{user: mentioned_user, for: follower})
- ],
- "fave_num" => 5,
- "repeat_num" => 3,
- "favorited" => false,
- "repeated" => false,
- "external_url" => "some url",
- "tags" => ["nsfw", "content", "mentioning"],
- "activity_type" => "post",
- "possibly_sensitive" => true,
- "uri" => note_object["id"],
- "visibility" => "direct",
- "summary" => "2hu"
- }
-
- assert ActivityRepresenter.to_map(activity, %{
- user: user,
- for: follower,
- mentioned: [mentioned_user]
- }) == expected_status
- end
-
- test "an undo for a follow" do
- follower = insert(:user)
- followed = insert(:user)
-
- {:ok, _follow} = ActivityPub.follow(follower, followed)
- {:ok, unfollow} = ActivityPub.unfollow(follower, followed)
-
- map = ActivityRepresenter.to_map(unfollow, %{user: follower})
- assert map["is_post_verb"] == false
- assert map["activity_type"] == "undo"
- end
-
- test "a delete activity" do
- object = insert(:note)
- user = User.get_by_ap_id(object.data["actor"])
-
- {:ok, delete} = ActivityPub.delete(object)
-
- map = ActivityRepresenter.to_map(delete, %{user: user})
-
- assert map["is_post_verb"] == false
- assert map["activity_type"] == "delete"
- assert map["uri"] == object.data["id"]
- end
-end
diff --git a/test/web/twitter_api/representers/object_representer_test.exs b/test/web/twitter_api/representers/object_representer_test.exs
index 228b2ac42..c3cf330f1 100644
--- a/test/web/twitter_api/representers/object_representer_test.exs
+++ b/test/web/twitter_api/representers/object_representer_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.TwitterAPI.Representers.ObjectReprenterTest do
use Pleroma.DataCase
diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs
index a6495ffc1..9a9630c19 100644
--- a/test/web/twitter_api/twitter_api_controller_test.exs
+++ b/test/web/twitter_api/twitter_api_controller_test.exs
@@ -1,31 +1,43 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.TwitterAPI.ControllerTest do
use Pleroma.Web.ConnCase
- alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
- alias Pleroma.Builders.{ActivityBuilder, UserBuilder}
- alias Pleroma.{Repo, Activity, User, Object, Notification}
+ alias Comeonin.Pbkdf2
+ alias Ecto.Changeset
+ alias Pleroma.Activity
+ alias Pleroma.Builders.ActivityBuilder
+ alias Pleroma.Builders.UserBuilder
+ alias Pleroma.Notification
+ alias Pleroma.Object
+ alias Pleroma.Repo
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.TwitterAPI.UserView
- alias Pleroma.Web.TwitterAPI.NotificationView
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.TwitterAPI.ActivityView
+ alias Pleroma.Web.TwitterAPI.Controller
+ alias Pleroma.Web.TwitterAPI.NotificationView
alias Pleroma.Web.TwitterAPI.TwitterAPI
- alias Comeonin.Pbkdf2
+ alias Pleroma.Web.TwitterAPI.UserView
+ import Mock
import Pleroma.Factory
+ import Swoosh.TestAssertions
+
+ @banner "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
describe "POST /api/account/update_profile_banner" do
test "it updates the banner", %{conn: conn} do
user = insert(:user)
- new_banner =
- "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
-
- response =
- conn
- |> assign(:user, user)
- |> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => new_banner})
- |> json_response(200)
+ conn
+ |> assign(:user, user)
+ |> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => @banner})
+ |> json_response(200)
- user = Repo.get(User, user.id)
+ user = refresh_record(user)
assert user.info.banner["type"] == "Image"
end
end
@@ -34,16 +46,12 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
test "it updates the background", %{conn: conn} do
user = insert(:user)
- new_bg =
- "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
-
- response =
- conn
- |> assign(:user, user)
- |> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => new_bg})
- |> json_response(200)
+ conn
+ |> assign(:user, user)
+ |> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => @banner})
+ |> json_response(200)
- user = Repo.get(User, user.id)
+ user = refresh_record(user)
assert user.info.background["type"] == "Image"
end
end
@@ -57,13 +65,14 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
end
test "with credentials", %{conn: conn, user: user} do
- conn =
+ response =
conn
|> with_credentials(user.nickname, "test")
|> post("/api/account/verify_credentials.json")
+ |> json_response(200)
- assert response = json_response(conn, 200)
- assert response == UserView.render("show.json", %{user: user, token: response["token"]})
+ assert response ==
+ UserView.render("show.json", %{user: user, token: response["token"], for: user})
end
end
@@ -84,24 +93,41 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
"error" => "Client must provide a 'status' parameter with a value."
}
- conn = conn_with_creds |> post(request_path)
+ conn =
+ conn_with_creds
+ |> post(request_path)
+
assert json_response(conn, 400) == error_response
- conn = conn_with_creds |> post(request_path, %{status: ""})
+ conn =
+ conn_with_creds
+ |> post(request_path, %{status: ""})
+
assert json_response(conn, 400) == error_response
- conn = conn_with_creds |> post(request_path, %{status: " "})
+ conn =
+ conn_with_creds
+ |> post(request_path, %{status: " "})
+
assert json_response(conn, 400) == error_response
# we post with visibility private in order to avoid triggering relay
- conn = conn_with_creds |> post(request_path, %{status: "Nice meme.", visibility: "private"})
+ conn =
+ conn_with_creds
+ |> post(request_path, %{status: "Nice meme.", visibility: "private"})
assert json_response(conn, 200) ==
- ActivityRepresenter.to_map(Repo.one(Activity), %{user: user})
+ ActivityView.render("activity.json", %{
+ activity: Repo.one(Activity),
+ user: user,
+ for: user
+ })
end
end
describe "GET /statuses/public_timeline.json" do
+ setup [:valid_user]
+
test "returns statuses", %{conn: conn} do
user = insert(:user)
activities = ActivityBuilder.insert_list(30, %{}, %{user: user})
@@ -117,7 +143,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert length(response) == 10
end
- test "returns 403 to unauthenticated request when the instance is not public" do
+ test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:public, false)
@@ -135,15 +161,59 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
Application.put_env(:pleroma, :instance, instance)
end
- test "returns 200 to unauthenticated request when the instance is public" do
+ test "returns 200 to authenticated request when the instance is not public",
+ %{conn: conn, user: user} do
+ instance =
+ Application.get_env(:pleroma, :instance)
+ |> Keyword.put(:public, false)
+
+ Application.put_env(:pleroma, :instance, instance)
+
+ conn
+ |> with_credentials(user.nickname, "test")
+ |> get("/api/statuses/public_timeline.json")
+ |> json_response(200)
+
+ instance =
+ Application.get_env(:pleroma, :instance)
+ |> Keyword.put(:public, true)
+
+ Application.put_env(:pleroma, :instance, instance)
+ end
+
+ test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do
conn
|> get("/api/statuses/public_timeline.json")
|> json_response(200)
end
+
+ test "returns 200 to authenticated request when the instance is public",
+ %{conn: conn, user: user} do
+ conn
+ |> with_credentials(user.nickname, "test")
+ |> get("/api/statuses/public_timeline.json")
+ |> json_response(200)
+ end
+
+ test_with_mock "treats user as unauthenticated if `assigns[:token]` is present but lacks `read` permission",
+ Controller,
+ [:passthrough],
+ [] do
+ token = insert(:oauth_token, scopes: ["write"])
+
+ build_conn()
+ |> put_req_header("authorization", "Bearer #{token.token}")
+ |> get("/api/statuses/public_timeline.json")
+ |> json_response(200)
+
+ assert called(Controller.public_timeline(%{assigns: %{user: nil}}, :_))
+ end
end
describe "GET /statuses/public_and_external_timeline.json" do
- test "returns 403 to unauthenticated request when the instance is not public" do
+ setup [:valid_user]
+
+ test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:public, false)
@@ -161,8 +231,36 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
Application.put_env(:pleroma, :instance, instance)
end
- test "returns 200 to unauthenticated request when the instance is public" do
+ test "returns 200 to authenticated request when the instance is not public",
+ %{conn: conn, user: user} do
+ instance =
+ Application.get_env(:pleroma, :instance)
+ |> Keyword.put(:public, false)
+
+ Application.put_env(:pleroma, :instance, instance)
+
+ conn
+ |> with_credentials(user.nickname, "test")
+ |> get("/api/statuses/public_and_external_timeline.json")
+ |> json_response(200)
+
+ instance =
+ Application.get_env(:pleroma, :instance)
+ |> Keyword.put(:public, true)
+
+ Application.put_env(:pleroma, :instance, instance)
+ end
+
+ test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do
+ conn
+ |> get("/api/statuses/public_and_external_timeline.json")
+ |> json_response(200)
+ end
+
+ test "returns 200 to authenticated request when the instance is public",
+ %{conn: conn, user: user} do
conn
+ |> with_credentials(user.nickname, "test")
|> get("/api/statuses/public_and_external_timeline.json")
|> json_response(200)
end
@@ -180,7 +278,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
response = json_response(conn, 200)
- assert response == ActivityRepresenter.to_map(activity, %{user: actor})
+ assert response == ActivityView.render("activity.json", %{activity: activity, user: actor})
end
end
@@ -265,7 +363,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
since_id = List.last(activities).id
current_user =
- Ecto.Changeset.change(current_user, following: [User.ap_followers(user)])
+ Changeset.change(current_user, following: [User.ap_followers(user)])
|> Repo.update!()
conn =
@@ -279,7 +377,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert response ==
Enum.map(returned_activities, fn activity ->
- ActivityRepresenter.to_map(activity, %{
+ ActivityView.render("activity.json", %{
+ activity: activity,
user: User.get_cached_by_ap_id(activity.data["actor"]),
for: current_user
})
@@ -322,6 +421,33 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert status["id"] == direct_two.id
assert status_two["id"] == direct.id
end
+
+ test "doesn't include DMs from blocked users", %{conn: conn} do
+ blocker = insert(:user)
+ blocked = insert(:user)
+ user = insert(:user)
+ {:ok, blocker} = User.block(blocker, blocked)
+
+ {:ok, _blocked_direct} =
+ CommonAPI.post(blocked, %{
+ "status" => "Hi @#{blocker.nickname}!",
+ "visibility" => "direct"
+ })
+
+ {:ok, direct} =
+ CommonAPI.post(user, %{
+ "status" => "Hi @#{blocker.nickname}!",
+ "visibility" => "direct"
+ })
+
+ res_conn =
+ conn
+ |> assign(:user, blocker)
+ |> get("/api/statuses/dm_timeline.json")
+
+ [status] = json_response(res_conn, 200)
+ assert status["id"] == direct.id
+ end
end
describe "GET /statuses/mentions.json" do
@@ -334,7 +460,10 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
test "with credentials", %{conn: conn, user: current_user} do
{:ok, activity} =
- ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: current_user})
+ CommonAPI.post(current_user, %{
+ "status" => "why is tenshi eating a corndog so cute?",
+ "visibility" => "public"
+ })
conn =
conn
@@ -346,11 +475,29 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert length(response) == 1
assert Enum.at(response, 0) ==
- ActivityRepresenter.to_map(activity, %{
+ ActivityView.render("activity.json", %{
user: current_user,
- mentioned: [current_user]
+ for: current_user,
+ activity: activity
})
end
+
+ test "does not show DMs in mentions timeline", %{conn: conn, user: current_user} do
+ {:ok, _activity} =
+ CommonAPI.post(current_user, %{
+ "status" => "Have you guys ever seen how cute tenshi eating a corndog is?",
+ "visibility" => "direct"
+ })
+
+ conn =
+ conn
+ |> with_credentials(current_user.nickname, "test")
+ |> get("/api/statuses/mentions.json")
+
+ response = json_response(conn, 200)
+
+ assert Enum.empty?(response)
+ end
end
describe "GET /api/qvitter/statuses/notifications.json" do
@@ -453,7 +600,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
conn = get(conn, "/api/statuses/user_timeline.json", %{"user_id" => user.id})
response = json_response(conn, 200)
assert length(response) == 1
- assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user})
+
+ assert Enum.at(response, 0) ==
+ ActivityView.render("activity.json", %{user: user, activity: activity})
end
test "with screen_name", %{conn: conn} do
@@ -463,7 +612,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
conn = get(conn, "/api/statuses/user_timeline.json", %{"screen_name" => user.nickname})
response = json_response(conn, 200)
assert length(response) == 1
- assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user})
+
+ assert Enum.at(response, 0) ==
+ ActivityView.render("activity.json", %{user: user, activity: activity})
end
test "with credentials", %{conn: conn, user: current_user} do
@@ -477,7 +628,13 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
response = json_response(conn, 200)
assert length(response) == 1
- assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: current_user})
+
+ assert Enum.at(response, 0) ==
+ ActivityView.render("activity.json", %{
+ user: current_user,
+ for: current_user,
+ activity: activity
+ })
end
test "with credentials with user_id", %{conn: conn, user: current_user} do
@@ -492,7 +649,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
response = json_response(conn, 200)
assert length(response) == 1
- assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user})
+
+ assert Enum.at(response, 0) ==
+ ActivityView.render("activity.json", %{user: user, activity: activity})
end
test "with credentials screen_name", %{conn: conn, user: current_user} do
@@ -507,7 +666,41 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
response = json_response(conn, 200)
assert length(response) == 1
- assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user})
+
+ assert Enum.at(response, 0) ==
+ ActivityView.render("activity.json", %{user: user, activity: activity})
+ end
+
+ test "with credentials with user_id, excluding RTs", %{conn: conn, user: current_user} do
+ user = insert(:user)
+ {:ok, activity} = ActivityBuilder.insert(%{"id" => 1, "type" => "Create"}, %{user: user})
+ {:ok, _} = ActivityBuilder.insert(%{"id" => 2, "type" => "Announce"}, %{user: user})
+
+ conn =
+ conn
+ |> with_credentials(current_user.nickname, "test")
+ |> get("/api/statuses/user_timeline.json", %{
+ "user_id" => user.id,
+ "include_rts" => "false"
+ })
+
+ response = json_response(conn, 200)
+
+ assert length(response) == 1
+
+ assert Enum.at(response, 0) ==
+ ActivityView.render("activity.json", %{user: user, activity: activity})
+
+ conn =
+ conn
+ |> get("/api/statuses/user_timeline.json", %{"user_id" => user.id, "include_rts" => "0"})
+
+ response = json_response(conn, 200)
+
+ assert length(response) == 1
+
+ assert Enum.at(response, 0) ==
+ ActivityView.render("activity.json", %{user: user, activity: activity})
end
end
@@ -527,12 +720,29 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|> with_credentials(current_user.nickname, "test")
|> post("/api/friendships/create.json", %{user_id: followed.id})
- current_user = Repo.get(User, current_user.id)
+ current_user = User.get_by_id(current_user.id)
assert User.ap_followers(followed) in current_user.following
assert json_response(conn, 200) ==
UserView.render("show.json", %{user: followed, for: current_user})
end
+
+ test "for restricted account", %{conn: conn, user: current_user} do
+ followed = insert(:user, info: %User.Info{locked: true})
+
+ conn =
+ conn
+ |> with_credentials(current_user.nickname, "test")
+ |> post("/api/friendships/create.json", %{user_id: followed.id})
+
+ current_user = User.get_by_id(current_user.id)
+ followed = User.get_by_id(followed.id)
+
+ refute User.ap_followers(followed) in current_user.following
+
+ assert json_response(conn, 200) ==
+ UserView.render("show.json", %{user: followed, for: current_user})
+ end
end
describe "POST /friendships/destroy.json" do
@@ -555,7 +765,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|> with_credentials(current_user.nickname, "test")
|> post("/api/friendships/destroy.json", %{user_id: followed.id})
- current_user = Repo.get(User, current_user.id)
+ current_user = User.get_by_id(current_user.id)
assert current_user.following == [current_user.ap_id]
assert json_response(conn, 200) ==
@@ -579,7 +789,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|> with_credentials(current_user.nickname, "test")
|> post("/api/blocks/create.json", %{user_id: blocked.id})
- current_user = Repo.get(User, current_user.id)
+ current_user = User.get_by_id(current_user.id)
assert User.blocks?(current_user, blocked)
assert json_response(conn, 200) ==
@@ -606,7 +816,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|> with_credentials(current_user.nickname, "test")
|> post("/api/blocks/destroy.json", %{user_id: blocked.id})
- current_user = Repo.get(User, current_user.id)
+ current_user = User.get_by_id(current_user.id)
assert current_user.info.blocks == []
assert json_response(conn, 200) ==
@@ -637,7 +847,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|> with_credentials(current_user.nickname, "test")
|> post("/api/qvitter/update_avatar.json", %{img: avatar_image})
- current_user = Repo.get(User, current_user.id)
+ current_user = User.get_by_id(current_user.id)
assert is_map(current_user.avatar)
assert json_response(conn, 200) ==
@@ -654,14 +864,13 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
end
test "unimplemented mutes with credentials", %{conn: conn, user: current_user} do
- conn =
+ response =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/qvitter/mutes.json")
+ |> json_response(200)
- current_user = Repo.get(User, current_user.id)
-
- assert [] = json_response(conn, 200)
+ assert [] = response
end
end
@@ -700,7 +909,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|> with_credentials(current_user.nickname, "test")
|> post("/api/favorites/create/1.json")
- assert json_response(conn, 500)
+ assert json_response(conn, 400)
end
end
@@ -746,11 +955,15 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|> with_credentials(current_user.nickname, "test")
|> post(request_path)
- activity = Repo.get(Activity, note_activity.id)
- activity_user = Repo.get_by(User, ap_id: note_activity.data["actor"])
+ activity = Activity.get_by_id(note_activity.id)
+ activity_user = User.get_by_ap_id(note_activity.data["actor"])
assert json_response(response, 200) ==
- ActivityRepresenter.to_map(activity, %{user: activity_user, for: current_user})
+ ActivityView.render("activity.json", %{
+ user: activity_user,
+ for: current_user,
+ activity: activity
+ })
end
end
@@ -780,11 +993,15 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|> with_credentials(current_user.nickname, "test")
|> post(request_path)
- activity = Repo.get(Activity, note_activity.id)
- activity_user = Repo.get_by(User, ap_id: note_activity.data["actor"])
+ activity = Activity.get_by_id(note_activity.id)
+ activity_user = User.get_by_ap_id(note_activity.data["actor"])
assert json_response(response, 200) ==
- ActivityRepresenter.to_map(activity, %{user: activity_user, for: current_user})
+ ActivityView.render("activity.json", %{
+ user: activity_user,
+ for: current_user,
+ activity: activity
+ })
end
end
@@ -805,7 +1022,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
user = json_response(conn, 200)
- fetched_user = Repo.get_by(User, nickname: "lain")
+ fetched_user = User.get_by_nickname("lain")
assert user == UserView.render("show.json", %{user: fetched_user})
end
@@ -828,6 +1045,143 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
end
end
+ describe "POST /api/account/password_reset, with valid parameters" do
+ setup %{conn: conn} do
+ user = insert(:user)
+ conn = post(conn, "/api/account/password_reset?email=#{user.email}")
+ %{conn: conn, user: user}
+ end
+
+ test "it returns 204", %{conn: conn} do
+ assert json_response(conn, :no_content)
+ end
+
+ test "it creates a PasswordResetToken record for user", %{user: user} do
+ token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
+ assert token_record
+ end
+
+ test "it sends an email to user", %{user: user} do
+ token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
+
+ email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
+ notify_email = Pleroma.Config.get([:instance, :notify_email])
+ instance_name = Pleroma.Config.get([:instance, :name])
+
+ assert_email_sent(
+ from: {instance_name, notify_email},
+ to: {user.name, user.email},
+ html_body: email.html_body
+ )
+ end
+ end
+
+ describe "POST /api/account/password_reset, with invalid parameters" do
+ setup [:valid_user]
+
+ test "it returns 500 when user is not found", %{conn: conn, user: user} do
+ conn = post(conn, "/api/account/password_reset?email=nonexisting_#{user.email}")
+ assert json_response(conn, :internal_server_error)
+ end
+
+ test "it returns 500 when user is not local", %{conn: conn, user: user} do
+ {:ok, user} = Repo.update(Changeset.change(user, local: false))
+ conn = post(conn, "/api/account/password_reset?email=#{user.email}")
+ assert json_response(conn, :internal_server_error)
+ end
+ end
+
+ describe "GET /api/account/confirm_email/:id/:token" do
+ setup do
+ user = insert(:user)
+ info_change = User.Info.confirmation_changeset(user.info, :unconfirmed)
+
+ {:ok, user} =
+ user
+ |> Changeset.change()
+ |> Changeset.put_embed(:info, info_change)
+ |> Repo.update()
+
+ assert user.info.confirmation_pending
+
+ [user: user]
+ end
+
+ test "it redirects to root url", %{conn: conn, user: user} do
+ conn = get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}")
+
+ assert 302 == conn.status
+ end
+
+ test "it confirms the user account", %{conn: conn, user: user} do
+ get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}")
+
+ user = User.get_by_id(user.id)
+
+ refute user.info.confirmation_pending
+ refute user.info.confirmation_token
+ end
+
+ test "it returns 500 if user cannot be found by id", %{conn: conn, user: user} do
+ conn = get(conn, "/api/account/confirm_email/0/#{user.info.confirmation_token}")
+
+ assert 500 == conn.status
+ end
+
+ test "it returns 500 if token is invalid", %{conn: conn, user: user} do
+ conn = get(conn, "/api/account/confirm_email/#{user.id}/wrong_token")
+
+ assert 500 == conn.status
+ end
+ end
+
+ describe "POST /api/account/resend_confirmation_email" do
+ setup do
+ setting = Pleroma.Config.get([:instance, :account_activation_required])
+
+ unless setting do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+ on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
+ end
+
+ user = insert(:user)
+ info_change = User.Info.confirmation_changeset(user.info, :unconfirmed)
+
+ {:ok, user} =
+ user
+ |> Changeset.change()
+ |> Changeset.put_embed(:info, info_change)
+ |> Repo.update()
+
+ assert user.info.confirmation_pending
+
+ [user: user]
+ end
+
+ test "it returns 204 No Content", %{conn: conn, user: user} do
+ conn
+ |> assign(:user, user)
+ |> post("/api/account/resend_confirmation_email?email=#{user.email}")
+ |> json_response(:no_content)
+ end
+
+ test "it sends confirmation email", %{conn: conn, user: user} do
+ conn
+ |> assign(:user, user)
+ |> post("/api/account/resend_confirmation_email?email=#{user.email}")
+
+ email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
+ notify_email = Pleroma.Config.get([:instance, :notify_email])
+ instance_name = Pleroma.Config.get([:instance, :name])
+
+ assert_email_sent(
+ from: {instance_name, notify_email},
+ to: {user.name, user.email},
+ html_body: email.html_body
+ )
+ end
+ end
+
describe "GET /api/externalprofile/show" do
test "it returns the user", %{conn: conn} do
user = insert(:user)
@@ -861,6 +1215,112 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
result = json_response(conn, 200)
assert Enum.sort(expected) == Enum.sort(result)
end
+
+ test "it returns 20 followers per page", %{conn: conn} do
+ user = insert(:user)
+ followers = insert_list(21, :user)
+
+ Enum.each(followers, fn follower ->
+ User.follow(follower, user)
+ end)
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/statuses/followers")
+
+ result = json_response(res_conn, 200)
+ assert length(result) == 20
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/statuses/followers?page=2")
+
+ result = json_response(res_conn, 200)
+ assert length(result) == 1
+ end
+
+ test "it returns a given user's followers with user_id", %{conn: conn} do
+ user = insert(:user)
+ follower_one = insert(:user)
+ follower_two = insert(:user)
+ not_follower = insert(:user)
+
+ {:ok, follower_one} = User.follow(follower_one, user)
+ {:ok, follower_two} = User.follow(follower_two, user)
+
+ conn =
+ conn
+ |> assign(:user, not_follower)
+ |> get("/api/statuses/followers", %{"user_id" => user.id})
+
+ assert MapSet.equal?(
+ MapSet.new(json_response(conn, 200)),
+ MapSet.new(
+ UserView.render("index.json", %{
+ users: [follower_one, follower_two],
+ for: not_follower
+ })
+ )
+ )
+ end
+
+ test "it returns empty when hide_followers is set to true", %{conn: conn} do
+ user = insert(:user, %{info: %{hide_followers: true}})
+ follower_one = insert(:user)
+ follower_two = insert(:user)
+ not_follower = insert(:user)
+
+ {:ok, _follower_one} = User.follow(follower_one, user)
+ {:ok, _follower_two} = User.follow(follower_two, user)
+
+ response =
+ conn
+ |> assign(:user, not_follower)
+ |> get("/api/statuses/followers", %{"user_id" => user.id})
+ |> json_response(200)
+
+ assert [] == response
+ end
+
+ test "it returns the followers when hide_followers is set to true if requested by the user themselves",
+ %{
+ conn: conn
+ } do
+ user = insert(:user, %{info: %{hide_followers: true}})
+ follower_one = insert(:user)
+ follower_two = insert(:user)
+ _not_follower = insert(:user)
+
+ {:ok, _follower_one} = User.follow(follower_one, user)
+ {:ok, _follower_two} = User.follow(follower_two, user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/statuses/followers", %{"user_id" => user.id})
+
+ refute [] == json_response(conn, 200)
+ end
+ end
+
+ describe "GET /api/statuses/blocks" do
+ test "it returns the list of users blocked by requester", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, user} = User.block(user, other_user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/statuses/blocks")
+
+ expected = UserView.render("index.json", %{users: [other_user], for: user})
+ result = json_response(conn, 200)
+ assert Enum.sort(expected) == Enum.sort(result)
+ end
end
describe "GET /api/statuses/friends" do
@@ -883,6 +1343,40 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert Enum.sort(expected) == Enum.sort(result)
end
+ test "it returns 20 friends per page, except if 'export' is set to true", %{conn: conn} do
+ user = insert(:user)
+ followeds = insert_list(21, :user)
+
+ {:ok, user} =
+ Enum.reduce(followeds, {:ok, user}, fn followed, {:ok, user} ->
+ User.follow(user, followed)
+ end)
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/statuses/friends")
+
+ result = json_response(res_conn, 200)
+ assert length(result) == 20
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/statuses/friends", %{page: 2})
+
+ result = json_response(res_conn, 200)
+ assert length(result) == 1
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/statuses/friends", %{all: true})
+
+ result = json_response(res_conn, 200)
+ assert length(result) == 21
+ end
+
test "it returns a given user's friends with user_id", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
@@ -905,6 +1399,44 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
)
end
+ test "it returns empty when hide_follows is set to true", %{conn: conn} do
+ user = insert(:user, %{info: %{hide_follows: true}})
+ followed_one = insert(:user)
+ followed_two = insert(:user)
+ not_followed = insert(:user)
+
+ {:ok, user} = User.follow(user, followed_one)
+ {:ok, user} = User.follow(user, followed_two)
+
+ conn =
+ conn
+ |> assign(:user, not_followed)
+ |> get("/api/statuses/friends", %{"user_id" => user.id})
+
+ assert [] == json_response(conn, 200)
+ end
+
+ test "it returns friends when hide_follows is set to true if the user themselves request it",
+ %{
+ conn: conn
+ } do
+ user = insert(:user, %{info: %{hide_follows: true}})
+ followed_one = insert(:user)
+ followed_two = insert(:user)
+ _not_followed = insert(:user)
+
+ {:ok, _user} = User.follow(user, followed_one)
+ {:ok, _user} = User.follow(user, followed_two)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> get("/api/statuses/friends", %{"user_id" => user.id})
+ |> json_response(200)
+
+ refute [] == response
+ end
+
test "it returns a given user's friends with screen_name", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
@@ -969,11 +1501,85 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert user.name == "new name"
assert user.bio ==
- "hi <span><a class='mention' href='#{user2.ap_id}'>@<span>#{user2.nickname}</span></a></span>"
+ "hi <span class='h-card'><a data-user='#{user2.id}' class='u-url mention' href='#{
+ user2.ap_id
+ }'>@<span>#{user2.nickname}</span></a></span>"
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
+ test "it sets and un-sets hide_follows", %{conn: conn} do
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> post("/api/account/update_profile.json", %{
+ "hide_follows" => "true"
+ })
+
+ user = Repo.get!(User, user.id)
+ assert user.info.hide_follows == true
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/account/update_profile.json", %{
+ "hide_follows" => "false"
+ })
+
+ user = Repo.get!(User, user.id)
+ assert user.info.hide_follows == false
+ assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
+ end
+
+ test "it sets and un-sets hide_followers", %{conn: conn} do
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> post("/api/account/update_profile.json", %{
+ "hide_followers" => "true"
+ })
+
+ user = Repo.get!(User, user.id)
+ assert user.info.hide_followers == true
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/account/update_profile.json", %{
+ "hide_followers" => "false"
+ })
+
+ user = Repo.get!(User, user.id)
+ assert user.info.hide_followers == false
+ assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
+ end
+
+ test "it sets and un-sets show_role", %{conn: conn} do
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> post("/api/account/update_profile.json", %{
+ "show_role" => "true"
+ })
+
+ user = Repo.get!(User, user.id)
+ assert user.info.show_role == true
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/account/update_profile.json", %{
+ "show_role" => "false"
+ })
+
+ user = Repo.get!(User, user.id)
+ assert user.info.show_role == false
+ assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
+ end
+
test "it locks an account", %{conn: conn} do
user = insert(:user)
@@ -1136,7 +1742,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
})
assert json_response(conn, 200) == %{"status" => "success"}
- fetched_user = Repo.get(User, current_user.id)
+ fetched_user = User.get_by_id(current_user.id)
assert Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true
end
end
@@ -1177,8 +1783,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
{:ok, _activity} = ActivityPub.follow(other_user, user)
- user = Repo.get(User, user.id)
- other_user = Repo.get(User, other_user.id)
+ user = User.get_by_id(user.id)
+ other_user = User.get_by_id(other_user.id)
assert User.following?(other_user, user) == false
@@ -1190,6 +1796,24 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert [relationship] = json_response(conn, 200)
assert other_user.id == relationship["id"]
end
+
+ test "requires 'read' permission", %{conn: conn} do
+ token1 = insert(:oauth_token, scopes: ["write"])
+ token2 = insert(:oauth_token, scopes: ["read"])
+
+ for token <- [token1, token2] do
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer #{token.token}")
+ |> get("/api/pleroma/friend_requests")
+
+ if token == token1 do
+ assert %{"error" => "Insufficient permissions: read."} == json_response(conn, 403)
+ else
+ assert json_response(conn, 200)
+ end
+ end
+ end
end
describe "POST /api/pleroma/friendships/approve" do
@@ -1199,15 +1823,15 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
{:ok, _activity} = ActivityPub.follow(other_user, user)
- user = Repo.get(User, user.id)
- other_user = Repo.get(User, other_user.id)
+ user = User.get_by_id(user.id)
+ other_user = User.get_by_id(other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
- |> post("/api/pleroma/friendships/approve", %{"user_id" => to_string(other_user.id)})
+ |> post("/api/pleroma/friendships/approve", %{"user_id" => other_user.id})
assert relationship = json_response(conn, 200)
assert other_user.id == relationship["id"]
@@ -1222,15 +1846,15 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
{:ok, _activity} = ActivityPub.follow(other_user, user)
- user = Repo.get(User, user.id)
- other_user = Repo.get(User, other_user.id)
+ user = User.get_by_id(user.id)
+ other_user = User.get_by_id(other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
- |> post("/api/pleroma/friendships/deny", %{"user_id" => to_string(other_user.id)})
+ |> post("/api/pleroma/friendships/deny", %{"user_id" => other_user.id})
assert relationship = json_response(conn, 200)
assert other_user.id == relationship["id"]
@@ -1241,16 +1865,205 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
describe "GET /api/pleroma/search_user" do
test "it returns users, ordered by similarity", %{conn: conn} do
user = insert(:user, %{name: "eal"})
- user_two = insert(:user, %{name: "ean"})
- user_three = insert(:user, %{name: "ebn"})
+ user_two = insert(:user, %{name: "eal me"})
+ _user_three = insert(:user, %{name: "zzz"})
+
+ resp =
+ conn
+ |> get(twitter_api_search__path(conn, :search_user), query: "eal me")
+ |> json_response(200)
+
+ assert length(resp) == 2
+ assert [user_two.id, user.id] == Enum.map(resp, fn %{"id" => id} -> id end)
+ end
+ end
+
+ describe "POST /api/media/upload" do
+ setup context do
+ Pleroma.DataCase.ensure_local_uploader(context)
+ end
+
+ test "it performs the upload and sets `data[actor]` with AP id of uploader user", %{
+ conn: conn
+ } do
+ user = insert(:user)
+
+ upload_filename = "test/fixtures/image_tmp.jpg"
+ File.cp!("test/fixtures/image.jpg", upload_filename)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname(upload_filename),
+ filename: "image.jpg"
+ }
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> put_req_header("content-type", "application/octet-stream")
+ |> post("/api/media/upload", %{
+ "media" => file
+ })
+ |> json_response(:ok)
+
+ assert response["media_id"]
+ object = Repo.get(Object, response["media_id"])
+ assert object
+ assert object.data["actor"] == User.ap_id(user)
+ end
+ end
+
+ describe "POST /api/media/metadata/create" do
+ setup do
+ object = insert(:note)
+ user = User.get_by_ap_id(object.data["actor"])
+ %{object: object, user: user}
+ end
+
+ test "it returns :forbidden status on attempt to modify someone else's upload", %{
+ conn: conn,
+ object: object
+ } do
+ initial_description = object.data["name"]
+ another_user = insert(:user)
+
+ conn
+ |> assign(:user, another_user)
+ |> post("/api/media/metadata/create", %{"media_id" => object.id})
+ |> json_response(:forbidden)
+
+ object = Repo.get(Object, object.id)
+ assert object.data["name"] == initial_description
+ end
+
+ test "it updates `data[name]` of referenced Object with provided value", %{
+ conn: conn,
+ object: object,
+ user: user
+ } do
+ description = "Informative description of the image. Initial value: #{object.data["name"]}}"
+
+ conn
+ |> assign(:user, user)
+ |> post("/api/media/metadata/create", %{
+ "media_id" => object.id,
+ "alt_text" => %{"text" => description}
+ })
+ |> json_response(:no_content)
+
+ object = Repo.get(Object, object.id)
+ assert object.data["name"] == description
+ end
+ end
+
+ describe "POST /api/statuses/user_timeline.json?user_id=:user_id&pinned=true" do
+ test "it returns a list of pinned statuses", %{conn: conn} do
+ Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+
+ user = insert(:user, %{name: "egor"})
+ {:ok, %{id: activity_id}} = CommonAPI.post(user, %{"status" => "HI!!!"})
+ {:ok, _} = CommonAPI.pin(activity_id, user)
resp =
conn
- |> get(twitter_api_search__path(conn, :search_user), query: "eal")
+ |> get("/api/statuses/user_timeline.json", %{user_id: user.id, pinned: true})
|> json_response(200)
- assert length(resp) == 3
- assert [user.id, user_two.id, user_three.id] == Enum.map(resp, fn %{"id" => id} -> id end)
+ assert length(resp) == 1
+ assert [%{"id" => ^activity_id, "pinned" => true}] = resp
+ end
+ end
+
+ describe "POST /api/statuses/pin/:id" do
+ setup do
+ Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+ [user: insert(:user)]
+ end
+
+ test "without valid credentials", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ conn = post(conn, "/api/statuses/pin/#{note_activity.id}.json")
+ assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
+ end
+
+ test "with credentials", %{conn: conn, user: user} do
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test!"})
+
+ request_path = "/api/statuses/pin/#{activity.id}.json"
+
+ response =
+ conn
+ |> with_credentials(user.nickname, "test")
+ |> post(request_path)
+
+ user = refresh_record(user)
+
+ assert json_response(response, 200) ==
+ ActivityView.render("activity.json", %{user: user, for: user, activity: activity})
+ end
+ end
+
+ describe "POST /api/statuses/unpin/:id" do
+ setup do
+ Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+ [user: insert(:user)]
+ end
+
+ test "without valid credentials", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ conn = post(conn, "/api/statuses/unpin/#{note_activity.id}.json")
+ assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
+ end
+
+ test "with credentials", %{conn: conn, user: user} do
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test!"})
+ {:ok, activity} = CommonAPI.pin(activity.id, user)
+
+ request_path = "/api/statuses/unpin/#{activity.id}.json"
+
+ response =
+ conn
+ |> with_credentials(user.nickname, "test")
+ |> post(request_path)
+
+ user = refresh_record(user)
+
+ assert json_response(response, 200) ==
+ ActivityView.render("activity.json", %{user: user, for: user, activity: activity})
+ end
+ end
+
+ describe "GET /api/oauth_tokens" do
+ setup do
+ token = insert(:oauth_token) |> Repo.preload(:user)
+
+ %{token: token}
+ end
+
+ test "renders list", %{token: token} do
+ response =
+ build_conn()
+ |> assign(:user, token.user)
+ |> get("/api/oauth_tokens")
+
+ keys =
+ json_response(response, 200)
+ |> hd()
+ |> Map.keys()
+
+ assert keys -- ["id", "app_name", "valid_until"] == []
+ end
+
+ test "revoke token", %{token: token} do
+ response =
+ build_conn()
+ |> assign(:user, token.user)
+ |> delete("/api/oauth_tokens/#{token.id}")
+
+ tokens = Token.get_user_tokens(token.user)
+
+ assert tokens == []
+ assert response.status == 201
end
end
end
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index bc53fe68a..5bea1037a 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -1,16 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
use Pleroma.DataCase
- alias Pleroma.Builders.UserBuilder
- alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
- alias Pleroma.{Activity, User, Object, Repo, UserInviteToken}
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.UserInviteToken
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.ActivityView
+ alias Pleroma.Web.TwitterAPI.TwitterAPI
+ alias Pleroma.Web.TwitterAPI.UserView
import Pleroma.Factory
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
test "create a status" do
user = insert(:user)
- _mentioned_user = insert(:user, %{nickname: "shp", ap_id: "shp"})
+ mentioned_user = insert(:user, %{nickname: "shp", ap_id: "shp"})
object_data = %{
"type" => "Image",
@@ -36,7 +49,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
object = Object.normalize(activity.data["object"])
expected_text =
- "Hello again, <span><a class='mention' href='shp'>@<span>shp</span></a></span>.&lt;script&gt;&lt;/script&gt;<br>This is on another :moominmamma: line. <a href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a> <a href='http://localhost:4001/tag/epic' rel='tag'>#epic</a> <a href='http://localhost:4001/tag/phantasmagoric' rel='tag'>#phantasmagoric</a><br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>"
+ "Hello again, <span class='h-card'><a data-user='#{mentioned_user.id}' class='u-url mention' href='shp'>@<span>shp</span></a></span>.&lt;script&gt;&lt;/script&gt;<br>This is on another :moominmamma: line. <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a> <a class='hashtag' data-tag='epic' href='http://localhost:4001/tag/epic' rel='tag'>#epic</a> <a class='hashtag' data-tag='phantasmagoric' href='http://localhost:4001/tag/phantasmagoric' rel='tag'>#phantasmagoric</a><br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>"
assert get_in(object.data, ["content"]) == expected_text
assert get_in(object.data, ["type"]) == "Note"
@@ -94,7 +107,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
assert get_in(reply_object.data, ["context"]) == get_in(object.data, ["context"])
assert get_in(reply_object.data, ["inReplyTo"]) == get_in(activity.data, ["object"])
- assert get_in(reply_object.data, ["inReplyToStatusId"]) == activity.id
+ assert Activity.get_in_reply_to_activity(reply).id == activity.id
end
test "Follow another user using user_id" do
@@ -184,25 +197,42 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
end
test "upload a file" do
+ user = insert(:user)
+
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
- response = TwitterAPI.upload(file)
+ response = TwitterAPI.upload(file, user)
assert is_binary(response)
end
test "it favorites a status, returns the updated activity" do
user = insert(:user)
+ other_user = insert(:user)
note_activity = insert(:note_activity)
{:ok, status} = TwitterAPI.fav(user, note_activity.id)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
+ assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 1
+
+ object = Object.normalize(note_activity.data["object"])
+
+ assert object.data["like_count"] == 1
assert status == updated_activity
+
+ {:ok, _status} = TwitterAPI.fav(other_user, note_activity.id)
+
+ object = Object.normalize(note_activity.data["object"])
+
+ assert object.data["like_count"] == 2
+
+ updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
+ assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 2
end
test "it unfavorites a status, returns the updated activity" do
@@ -246,19 +276,69 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
"nickname" => "lain",
"email" => "lain@wired.jp",
"fullname" => "lain iwakura",
- "bio" => "close the world.",
"password" => "bear",
"confirm" => "bear"
}
{:ok, user} = TwitterAPI.register_user(data)
- fetched_user = Repo.get_by(User, nickname: "lain")
+ fetched_user = User.get_by_nickname("lain")
assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
end
+ test "it registers a new user with empty string in bio and returns the user." do
+ data = %{
+ "nickname" => "lain",
+ "email" => "lain@wired.jp",
+ "fullname" => "lain iwakura",
+ "bio" => "",
+ "password" => "bear",
+ "confirm" => "bear"
+ }
+
+ {:ok, user} = TwitterAPI.register_user(data)
+
+ fetched_user = User.get_by_nickname("lain")
+
+ assert UserView.render("show.json", %{user: user}) ==
+ UserView.render("show.json", %{user: fetched_user})
+ end
+
+ test "it sends confirmation email if :account_activation_required is specified in instance config" do
+ setting = Pleroma.Config.get([:instance, :account_activation_required])
+
+ unless setting do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+ on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
+ end
+
+ data = %{
+ "nickname" => "lain",
+ "email" => "lain@wired.jp",
+ "fullname" => "lain iwakura",
+ "bio" => "",
+ "password" => "bear",
+ "confirm" => "bear"
+ }
+
+ {:ok, user} = TwitterAPI.register_user(data)
+
+ assert user.info.confirmation_pending
+
+ email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
+
+ notify_email = Pleroma.Config.get([:instance, :notify_email])
+ instance_name = Pleroma.Config.get([:instance, :name])
+
+ Swoosh.TestAssertions.assert_email_sent(
+ from: {instance_name, notify_email},
+ to: {user.name, user.email},
+ html_body: email.html_body
+ )
+ end
+
test "it registers a new user and parses mentions in the bio" do
data1 = %{
"nickname" => "john",
@@ -283,73 +363,318 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:ok, user2} = TwitterAPI.register_user(data2)
expected_text =
- "<span><a class='mention' href='#{user1.ap_id}'>@<span>john</span></a></span> test"
+ "<span class='h-card'><a data-user='#{user1.id}' class='u-url mention' href='#{user1.ap_id}'>@<span>john</span></a></span> test"
assert user2.bio == expected_text
end
- @moduletag skip: "needs 'registrations_open: false' in config"
- test "it registers a new user via invite token and returns the user." do
- {:ok, token} = UserInviteToken.create_token()
+ describe "register with one time token" do
+ setup do
+ setting = Pleroma.Config.get([:instance, :registrations_open])
- data = %{
- "nickname" => "vinny",
- "email" => "pasta@pizza.vs",
- "fullname" => "Vinny Vinesauce",
- "bio" => "streamer",
- "password" => "hiptofbees",
- "confirm" => "hiptofbees",
- "token" => token.token
- }
+ if setting do
+ Pleroma.Config.put([:instance, :registrations_open], false)
+ on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end)
+ end
- {:ok, user} = TwitterAPI.register_user(data)
+ :ok
+ end
- fetched_user = Repo.get_by(User, nickname: "vinny")
- token = Repo.get_by(UserInviteToken, token: token.token)
+ test "returns user on success" do
+ {:ok, invite} = UserInviteToken.create_invite()
- assert token.used == true
+ data = %{
+ "nickname" => "vinny",
+ "email" => "pasta@pizza.vs",
+ "fullname" => "Vinny Vinesauce",
+ "bio" => "streamer",
+ "password" => "hiptofbees",
+ "confirm" => "hiptofbees",
+ "token" => invite.token
+ }
- assert UserView.render("show.json", %{user: user}) ==
- UserView.render("show.json", %{user: fetched_user})
+ {:ok, user} = TwitterAPI.register_user(data)
+
+ fetched_user = User.get_by_nickname("vinny")
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
+
+ assert invite.used == true
+
+ assert UserView.render("show.json", %{user: user}) ==
+ UserView.render("show.json", %{user: fetched_user})
+ end
+
+ test "returns error on invalid token" do
+ data = %{
+ "nickname" => "GrimReaper",
+ "email" => "death@reapers.afterlife",
+ "fullname" => "Reaper Grim",
+ "bio" => "Your time has come",
+ "password" => "scythe",
+ "confirm" => "scythe",
+ "token" => "DudeLetMeInImAFairy"
+ }
+
+ {:error, msg} = TwitterAPI.register_user(data)
+
+ assert msg == "Invalid token"
+ refute User.get_by_nickname("GrimReaper")
+ end
+
+ test "returns error on expired token" do
+ {:ok, invite} = UserInviteToken.create_invite()
+ UserInviteToken.update_invite!(invite, used: true)
+
+ data = %{
+ "nickname" => "GrimReaper",
+ "email" => "death@reapers.afterlife",
+ "fullname" => "Reaper Grim",
+ "bio" => "Your time has come",
+ "password" => "scythe",
+ "confirm" => "scythe",
+ "token" => invite.token
+ }
+
+ {:error, msg} = TwitterAPI.register_user(data)
+
+ assert msg == "Expired token"
+ refute User.get_by_nickname("GrimReaper")
+ end
end
- @moduletag skip: "needs 'registrations_open: false' in config"
- test "it returns an error if invalid token submitted" do
- data = %{
- "nickname" => "GrimReaper",
- "email" => "death@reapers.afterlife",
- "fullname" => "Reaper Grim",
- "bio" => "Your time has come",
- "password" => "scythe",
- "confirm" => "scythe",
- "token" => "DudeLetMeInImAFairy"
- }
+ describe "registers with date limited token" do
+ setup do
+ setting = Pleroma.Config.get([:instance, :registrations_open])
+
+ if setting do
+ Pleroma.Config.put([:instance, :registrations_open], false)
+ on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end)
+ end
+
+ data = %{
+ "nickname" => "vinny",
+ "email" => "pasta@pizza.vs",
+ "fullname" => "Vinny Vinesauce",
+ "bio" => "streamer",
+ "password" => "hiptofbees",
+ "confirm" => "hiptofbees"
+ }
+
+ check_fn = fn invite ->
+ data = Map.put(data, "token", invite.token)
+ {:ok, user} = TwitterAPI.register_user(data)
+ fetched_user = User.get_by_nickname("vinny")
+
+ assert UserView.render("show.json", %{user: user}) ==
+ UserView.render("show.json", %{user: fetched_user})
+ end
+
+ {:ok, data: data, check_fn: check_fn}
+ end
+
+ test "returns user on success", %{check_fn: check_fn} do
+ {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today()})
+
+ check_fn.(invite)
+
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
+
+ refute invite.used
+ end
+
+ test "returns user on token which expired tomorrow", %{check_fn: check_fn} do
+ {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), 1)})
+
+ check_fn.(invite)
+
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
+
+ refute invite.used
+ end
+
+ test "returns an error on overdue date", %{data: data} do
+ {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1)})
- {:error, msg} = TwitterAPI.register_user(data)
+ data = Map.put(data, "token", invite.token)
- assert msg == "Invalid token"
- refute Repo.get_by(User, nickname: "GrimReaper")
+ {:error, msg} = TwitterAPI.register_user(data)
+
+ assert msg == "Expired token"
+ refute User.get_by_nickname("vinny")
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
+
+ refute invite.used
+ end
end
- @moduletag skip: "needs 'registrations_open: false' in config"
- test "it returns an error if expired token submitted" do
- {:ok, token} = UserInviteToken.create_token()
- UserInviteToken.mark_as_used(token.token)
+ describe "registers with reusable token" do
+ setup do
+ setting = Pleroma.Config.get([:instance, :registrations_open])
- data = %{
- "nickname" => "GrimReaper",
- "email" => "death@reapers.afterlife",
- "fullname" => "Reaper Grim",
- "bio" => "Your time has come",
- "password" => "scythe",
- "confirm" => "scythe",
- "token" => token.token
- }
+ if setting do
+ Pleroma.Config.put([:instance, :registrations_open], false)
+ on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end)
+ end
+
+ :ok
+ end
+
+ test "returns user on success, after him registration fails" do
+ {:ok, invite} = UserInviteToken.create_invite(%{max_use: 100})
+
+ UserInviteToken.update_invite!(invite, uses: 99)
+
+ data = %{
+ "nickname" => "vinny",
+ "email" => "pasta@pizza.vs",
+ "fullname" => "Vinny Vinesauce",
+ "bio" => "streamer",
+ "password" => "hiptofbees",
+ "confirm" => "hiptofbees",
+ "token" => invite.token
+ }
+
+ {:ok, user} = TwitterAPI.register_user(data)
+ fetched_user = User.get_by_nickname("vinny")
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
+
+ assert invite.used == true
+
+ assert UserView.render("show.json", %{user: user}) ==
+ UserView.render("show.json", %{user: fetched_user})
+
+ data = %{
+ "nickname" => "GrimReaper",
+ "email" => "death@reapers.afterlife",
+ "fullname" => "Reaper Grim",
+ "bio" => "Your time has come",
+ "password" => "scythe",
+ "confirm" => "scythe",
+ "token" => invite.token
+ }
+
+ {:error, msg} = TwitterAPI.register_user(data)
- {:error, msg} = TwitterAPI.register_user(data)
+ assert msg == "Expired token"
+ refute User.get_by_nickname("GrimReaper")
+ end
+ end
+
+ describe "registers with reusable date limited token" do
+ setup do
+ setting = Pleroma.Config.get([:instance, :registrations_open])
+
+ if setting do
+ Pleroma.Config.put([:instance, :registrations_open], false)
+ on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end)
+ end
+
+ :ok
+ end
+
+ test "returns user on success" do
+ {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100})
+
+ data = %{
+ "nickname" => "vinny",
+ "email" => "pasta@pizza.vs",
+ "fullname" => "Vinny Vinesauce",
+ "bio" => "streamer",
+ "password" => "hiptofbees",
+ "confirm" => "hiptofbees",
+ "token" => invite.token
+ }
+
+ {:ok, user} = TwitterAPI.register_user(data)
+ fetched_user = User.get_by_nickname("vinny")
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
+
+ refute invite.used
+
+ assert UserView.render("show.json", %{user: user}) ==
+ UserView.render("show.json", %{user: fetched_user})
+ end
+
+ test "error after max uses" do
+ {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100})
+
+ UserInviteToken.update_invite!(invite, uses: 99)
+
+ data = %{
+ "nickname" => "vinny",
+ "email" => "pasta@pizza.vs",
+ "fullname" => "Vinny Vinesauce",
+ "bio" => "streamer",
+ "password" => "hiptofbees",
+ "confirm" => "hiptofbees",
+ "token" => invite.token
+ }
+
+ {:ok, user} = TwitterAPI.register_user(data)
+ fetched_user = User.get_by_nickname("vinny")
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
+ assert invite.used == true
+
+ assert UserView.render("show.json", %{user: user}) ==
+ UserView.render("show.json", %{user: fetched_user})
+
+ data = %{
+ "nickname" => "GrimReaper",
+ "email" => "death@reapers.afterlife",
+ "fullname" => "Reaper Grim",
+ "bio" => "Your time has come",
+ "password" => "scythe",
+ "confirm" => "scythe",
+ "token" => invite.token
+ }
+
+ {:error, msg} = TwitterAPI.register_user(data)
+
+ assert msg == "Expired token"
+ refute User.get_by_nickname("GrimReaper")
+ end
+
+ test "returns error on overdue date" do
+ {:ok, invite} =
+ UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100})
+
+ data = %{
+ "nickname" => "GrimReaper",
+ "email" => "death@reapers.afterlife",
+ "fullname" => "Reaper Grim",
+ "bio" => "Your time has come",
+ "password" => "scythe",
+ "confirm" => "scythe",
+ "token" => invite.token
+ }
- assert msg == "Expired token"
- refute Repo.get_by(User, nickname: "GrimReaper")
+ {:error, msg} = TwitterAPI.register_user(data)
+
+ assert msg == "Expired token"
+ refute User.get_by_nickname("GrimReaper")
+ end
+
+ test "returns error on with overdue date and after max" do
+ {:ok, invite} =
+ UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100})
+
+ UserInviteToken.update_invite!(invite, uses: 100)
+
+ data = %{
+ "nickname" => "GrimReaper",
+ "email" => "death@reapers.afterlife",
+ "fullname" => "Reaper Grim",
+ "bio" => "Your time has come",
+ "password" => "scythe",
+ "confirm" => "scythe",
+ "token" => invite.token
+ }
+
+ {:error, msg} = TwitterAPI.register_user(data)
+
+ assert msg == "Expired token"
+ refute User.get_by_nickname("GrimReaper")
+ end
end
test "it returns the error on registration problems" do
@@ -364,7 +689,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, error_object} = TwitterAPI.register_user(data)
assert is_binary(error_object[:error])
- refute Repo.get_by(User, nickname: "lain")
+ refute User.get_by_nickname("lain")
end
test "it assigns an integer conversation_id" do
@@ -380,22 +705,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
:ok
end
- describe "context_to_conversation_id" do
- test "creates a mapping object" do
- conversation_id = TwitterAPI.context_to_conversation_id("random context")
- object = Object.get_by_ap_id("random context")
-
- assert conversation_id == object.id
- end
-
- test "returns an existing mapping for an existing object" do
- {:ok, object} = Object.context_mapping("random context") |> Repo.insert()
- conversation_id = TwitterAPI.context_to_conversation_id("random context")
-
- assert conversation_id == object.id
- end
- end
-
describe "fetching a user by uri" do
test "fetches a user by uri" do
id = "https://mastodon.social/users/lambadalambda"
@@ -406,7 +715,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
assert represented["id"] == UserView.render("show.json", %{user: remote, for: user})["id"]
# Also fetches the feed.
- # assert Activity.get_create_activity_by_object_ap_id("tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status")
+ # assert Activity.get_create_by_object_ap_id("tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status")
+ # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
end
end
end
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
new file mode 100644
index 000000000..c58b49ea4
--- /dev/null
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -0,0 +1,248 @@
+defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.Notification
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+ import Pleroma.Factory
+
+ setup do
+ Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ describe "POST /api/pleroma/follow_import" do
+ test "it returns HTTP 200", %{conn: conn} do
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user1)
+ |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"})
+ |> json_response(:ok)
+
+ assert response == "job started"
+ end
+
+ test "it imports new-style mastodon follow lists", %{conn: conn} do
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user1)
+ |> post("/api/pleroma/follow_import", %{
+ "list" => "Account address,Show boosts\n#{user2.ap_id},true"
+ })
+ |> json_response(:ok)
+
+ assert response == "job started"
+ end
+
+ test "requires 'follow' permission", %{conn: conn} do
+ token1 = insert(:oauth_token, scopes: ["read", "write"])
+ token2 = insert(:oauth_token, scopes: ["follow"])
+ another_user = insert(:user)
+
+ for token <- [token1, token2] do
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer #{token.token}")
+ |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"})
+
+ if token == token1 do
+ assert %{"error" => "Insufficient permissions: follow."} == json_response(conn, 403)
+ else
+ assert json_response(conn, 200)
+ end
+ end
+ end
+ end
+
+ describe "POST /api/pleroma/blocks_import" do
+ test "it returns HTTP 200", %{conn: conn} do
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user1)
+ |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"})
+ |> json_response(:ok)
+
+ assert response == "job started"
+ end
+ end
+
+ describe "POST /api/pleroma/notifications/read" do
+ test "it marks a single notification as read", %{conn: conn} do
+ user1 = insert(:user)
+ user2 = insert(:user)
+ {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
+ {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
+ {:ok, [notification1]} = Notification.create_notifications(activity1)
+ {:ok, [notification2]} = Notification.create_notifications(activity2)
+
+ conn
+ |> assign(:user, user1)
+ |> post("/api/pleroma/notifications/read", %{"id" => "#{notification1.id}"})
+ |> json_response(:ok)
+
+ assert Repo.get(Notification, notification1.id).seen
+ refute Repo.get(Notification, notification2.id).seen
+ end
+ end
+
+ describe "PUT /api/pleroma/notification_settings" do
+ test "it updates notification settings", %{conn: conn} do
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> put("/api/pleroma/notification_settings", %{
+ "remote" => false,
+ "followers" => false,
+ "bar" => 1
+ })
+ |> json_response(:ok)
+
+ user = Repo.get(User, user.id)
+
+ assert %{"remote" => false, "local" => true, "followers" => false, "follows" => true} ==
+ user.info.notification_settings
+ end
+ end
+
+ describe "GET /api/statusnet/config.json" do
+ test "returns the state of safe_dm_mentions flag", %{conn: conn} do
+ option = Pleroma.Config.get([:instance, :safe_dm_mentions])
+ Pleroma.Config.put([:instance, :safe_dm_mentions], true)
+
+ response =
+ conn
+ |> get("/api/statusnet/config.json")
+ |> json_response(:ok)
+
+ assert response["site"]["safeDMMentionsEnabled"] == "1"
+
+ Pleroma.Config.put([:instance, :safe_dm_mentions], false)
+
+ response =
+ conn
+ |> get("/api/statusnet/config.json")
+ |> json_response(:ok)
+
+ assert response["site"]["safeDMMentionsEnabled"] == "0"
+
+ Pleroma.Config.put([:instance, :safe_dm_mentions], option)
+ end
+
+ test "it returns the managed config", %{conn: conn} do
+ Pleroma.Config.put([:instance, :managed_config], false)
+ Pleroma.Config.put([:fe], theme: "rei-ayanami-towel")
+
+ response =
+ conn
+ |> get("/api/statusnet/config.json")
+ |> json_response(:ok)
+
+ refute response["site"]["pleromafe"]
+
+ Pleroma.Config.put([:instance, :managed_config], true)
+
+ response =
+ conn
+ |> get("/api/statusnet/config.json")
+ |> json_response(:ok)
+
+ assert response["site"]["pleromafe"]
+ end
+
+ test "if :pleroma, :fe is false, it returns the new style config settings", %{conn: conn} do
+ Pleroma.Config.put([:instance, :managed_config], true)
+ Pleroma.Config.put([:fe, :theme], "rei-ayanami-towel")
+ Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
+
+ response =
+ conn
+ |> get("/api/statusnet/config.json")
+ |> json_response(:ok)
+
+ assert response["site"]["pleromafe"]["theme"] == "rei-ayanami-towel"
+
+ Pleroma.Config.put([:fe], false)
+
+ response =
+ conn
+ |> get("/api/statusnet/config.json")
+ |> json_response(:ok)
+
+ assert response["site"]["pleromafe"]["theme"] == "asuka-hospital"
+ end
+ end
+
+ describe "GET /api/pleroma/frontend_configurations" do
+ test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} do
+ config = [
+ frontend_a: %{
+ x: 1,
+ y: 2
+ },
+ frontend_b: %{
+ z: 3
+ }
+ ]
+
+ Pleroma.Config.put(:frontend_configurations, config)
+
+ response =
+ conn
+ |> get("/api/pleroma/frontend_configurations")
+ |> json_response(:ok)
+
+ assert response == Jason.encode!(config |> Enum.into(%{})) |> Jason.decode!()
+ end
+ end
+
+ describe "/api/pleroma/emoji" do
+ test "returns json with custom emoji with tags", %{conn: conn} do
+ emoji =
+ conn
+ |> get("/api/pleroma/emoji")
+ |> json_response(200)
+
+ assert Enum.all?(emoji, fn
+ {_key,
+ %{
+ "image_url" => url,
+ "tags" => tags
+ }} ->
+ is_binary(url) and is_list(tags)
+ end)
+ end
+ end
+
+ describe "GET /ostatus_subscribe?acct=...." do
+ test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do
+ conn =
+ get(
+ conn,
+ "/ostatus_subscribe?acct=https://mastodon.social/users/emelie/statuses/101849165031453009"
+ )
+
+ assert redirected_to(conn) =~ "/notice/"
+ end
+
+ test "show follow account page if the `acct` is a account link", %{conn: conn} do
+ response =
+ get(
+ conn,
+ "/ostatus_subscribe?acct=https://mastodon.social/users/emelie"
+ )
+
+ assert html_response(response, 200) =~ "Log in to follow"
+ end
+ end
+end
diff --git a/test/web/twitter_api/views/activity_view_test.exs b/test/web/twitter_api/views/activity_view_test.exs
index f4741cf24..7ef0270cc 100644
--- a/test/web/twitter_api/views/activity_view_test.exs
+++ b/test/web/twitter_api/views/activity_view_test.exs
@@ -1,19 +1,126 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
use Pleroma.DataCase
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.UserView
- alias Pleroma.Web.TwitterAPI.TwitterAPI
- alias Pleroma.Repo
- alias Pleroma.{Activity, Object}
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
import Pleroma.Factory
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
import Mock
+ test "returns a temporary ap_id based user for activities missing db users" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
+
+ Repo.delete(user)
+ Cachex.clear(:user_cache)
+
+ %{"user" => tw_user} = ActivityView.render("activity.json", activity: activity)
+
+ assert tw_user["screen_name"] == "erroruser@example.com"
+ assert tw_user["name"] == user.ap_id
+ assert tw_user["statusnet_profile_url"] == user.ap_id
+ end
+
+ test "tries to get a user by nickname if fetching by ap_id doesn't work" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
+
+ {:ok, user} =
+ user
+ |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
+ |> Repo.update()
+
+ Cachex.clear(:user_cache)
+
+ result = ActivityView.render("activity.json", activity: activity)
+ assert result["user"]["id"] == user.id
+ end
+
+ test "tells if the message is muted for some reason" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, user} = User.mute(user, other_user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
+ status = ActivityView.render("activity.json", %{activity: activity})
+
+ assert status["muted"] == false
+
+ status = ActivityView.render("activity.json", %{activity: activity, for: user})
+
+ assert status["muted"] == true
+ end
+
+ test "a create activity with a html status" do
+ text = """
+ #Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg
+ """
+
+ {:ok, activity} = CommonAPI.post(insert(:user), %{"status" => text})
+
+ result = ActivityView.render("activity.json", activity: activity)
+
+ assert result["statusnet_html"] ==
+ "<a class=\"hashtag\" data-tag=\"bike\" href=\"http://localhost:4001/tag/bike\" rel=\"tag\">#Bike</a> log - Commute Tuesday<br /><a href=\"https://pla.bike/posts/20181211/\">https://pla.bike/posts/20181211/</a><br /><a class=\"hashtag\" data-tag=\"cycling\" href=\"http://localhost:4001/tag/cycling\" rel=\"tag\">#cycling</a> <a class=\"hashtag\" data-tag=\"chscycling\" href=\"http://localhost:4001/tag/chscycling\" rel=\"tag\">#CHScycling</a> <a class=\"hashtag\" data-tag=\"commute\" href=\"http://localhost:4001/tag/commute\" rel=\"tag\">#commute</a><br />MVIMG_20181211_054020.jpg"
+
+ assert result["text"] ==
+ "#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg"
+ end
+
+ test "a create activity with a summary containing emoji" do
+ {:ok, activity} =
+ CommonAPI.post(insert(:user), %{
+ "spoiler_text" => ":woollysocks: meow",
+ "status" => "."
+ })
+
+ result = ActivityView.render("activity.json", activity: activity)
+
+ expected = ":woollysocks: meow"
+
+ expected_html =
+ "<img height=\"32px\" width=\"32px\" alt=\"woollysocks\" title=\"woollysocks\" src=\"http://localhost:4001/finmoji/128px/woollysocks-128.png\" /> meow"
+
+ assert result["summary"] == expected
+ assert result["summary_html"] == expected_html
+ end
+
+ test "a create activity with a summary containing invalid HTML" do
+ {:ok, activity} =
+ CommonAPI.post(insert(:user), %{
+ "spoiler_text" => "<span style=\"color: magenta; font-size: 32px;\">meow</span>",
+ "status" => "."
+ })
+
+ result = ActivityView.render("activity.json", activity: activity)
+
+ expected = "meow"
+
+ assert result["summary"] == expected
+ assert result["summary_html"] == expected
+ end
+
test "a create activity with a note" do
user = insert(:user)
other_user = insert(:user, %{nickname: "shp"})
@@ -23,7 +130,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
result = ActivityView.render("activity.json", activity: activity)
- convo_id = TwitterAPI.context_to_conversation_id(object.data["context"])
+ convo_id = Utils.context_to_conversation_id(object.data["context"])
expected = %{
"activity_type" => "post",
@@ -46,15 +153,21 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
"possibly_sensitive" => false,
"repeat_num" => 0,
"repeated" => false,
+ "pinned" => false,
"statusnet_conversation_id" => convo_id,
+ "summary" => "",
+ "summary_html" => "",
"statusnet_html" =>
- "Hey <span><a href=\"#{other_user.ap_id}\">@<span>shp</span></a></span>!",
+ "Hey <span class=\"h-card\"><a data-user=\"#{other_user.id}\" class=\"u-url mention\" href=\"#{
+ other_user.ap_id
+ }\">@<span>shp</span></a></span>!",
"tags" => [],
"text" => "Hey @shp!",
"uri" => object.data["id"],
"user" => UserView.render("show.json", %{user: user}),
"visibility" => "direct",
- "summary" => nil
+ "card" => nil,
+ "muted" => false
}
assert result == expected
@@ -66,12 +179,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
object = Object.normalize(activity.data["object"])
- convo_id = TwitterAPI.context_to_conversation_id(object.data["context"])
+ convo_id = Utils.context_to_conversation_id(object.data["context"])
mocks = [
{
- TwitterAPI,
- [],
+ Utils,
+ [:passthrough],
[context_to_conversation_id: fn _ -> false end]
},
{
@@ -86,7 +199,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
assert result["statusnet_conversation_id"] == convo_id
assert result["user"]
- refute called(TwitterAPI.context_to_conversation_id(:_))
+ refute called(Utils.context_to_conversation_id(:_))
refute called(User.get_cached_by_ap_id(user.ap_id))
refute called(User.get_cached_by_ap_id(other_user.ap_id))
end
@@ -114,6 +227,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
{:ok, like, _object} = CommonAPI.favorite(activity.id, other_user)
result = ActivityView.render("activity.json", activity: like)
+ activity = Pleroma.Activity.get_by_ap_id(activity.data["id"])
expected = %{
"activity_type" => "like",
@@ -123,6 +237,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
"in_reply_to_status_id" => activity.id,
"is_local" => true,
"is_post_verb" => false,
+ "favorited_status" => ActivityView.render("activity.json", activity: activity),
"statusnet_html" => "shp favorited a status.",
"text" => "shp favorited a status.",
"uri" => "tag:#{like.data["id"]}:objectType=Favourite",
@@ -150,6 +265,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
"in_reply_to_status_id" => nil,
"is_local" => true,
"is_post_verb" => false,
+ "favorited_status" => nil,
"statusnet_html" => "shp favorited a status.",
"text" => "shp favorited a status.",
"uri" => "tag:#{like.data["id"]}:objectType=Favourite",
@@ -166,9 +282,9 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
{:ok, announce, object} = CommonAPI.repeat(activity.id, other_user)
- convo_id = TwitterAPI.context_to_conversation_id(object.data["context"])
+ convo_id = Utils.context_to_conversation_id(object.data["context"])
- activity = Repo.get(Activity, activity.id)
+ activity = Activity.get_by_id(activity.id)
result = ActivityView.render("activity.json", activity: announce)
@@ -241,4 +357,18 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
assert result == expected
end
+
+ test "a peertube video" do
+ {:ok, object} =
+ ActivityPub.fetch_object_from_id(
+ "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
+ )
+
+ %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
+
+ result = ActivityView.render("activity.json", activity: activity)
+
+ assert length(result["attachments"]) == 1
+ assert result["summary"] == "Friday Night"
+ end
end
diff --git a/test/web/twitter_api/views/notification_view_test.exs b/test/web/twitter_api/views/notification_view_test.exs
index 79eafda7d..6baeeaf63 100644
--- a/test/web/twitter_api/views/notification_view_test.exs
+++ b/test/web/twitter_api/views/notification_view_test.exs
@@ -1,14 +1,18 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.TwitterAPI.NotificationViewTest do
use Pleroma.DataCase
- alias Pleroma.{User, Notification}
- alias Pleroma.Web.TwitterAPI.TwitterAPI
+ alias Pleroma.Notification
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.NotificationView
+ alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView
- alias Pleroma.Web.TwitterAPI.ActivityView
- alias Pleroma.Web.CommonAPI.Utils
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Builders.UserBuilder
import Pleroma.Factory
@@ -67,7 +71,7 @@ defmodule Pleroma.Web.TwitterAPI.NotificationViewTest do
user = User.get_cached_by_ap_id(note_activity.data["actor"])
repeater = insert(:user)
- {:ok, activity} = TwitterAPI.repeat(repeater, note_activity.id)
+ {:ok, _activity} = TwitterAPI.repeat(repeater, note_activity.id)
[notification] = Notification.for_user(user)
represented = %{
@@ -89,7 +93,7 @@ defmodule Pleroma.Web.TwitterAPI.NotificationViewTest do
user = User.get_cached_by_ap_id(note_activity.data["actor"])
liker = insert(:user)
- {:ok, activity} = TwitterAPI.fav(liker, note_activity.id)
+ {:ok, _activity} = TwitterAPI.fav(liker, note_activity.id)
[notification] = Notification.for_user(user)
represented = %{
diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs
index e69ca24a9..0feaf4b64 100644
--- a/test/web/twitter_api/views/user_view_test.exs
+++ b/test/web/twitter_api/views/user_view_test.exs
@@ -1,10 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.TwitterAPI.UserViewTest do
use Pleroma.DataCase
alias Pleroma.User
- alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.CommonAPI.Utils
- alias Pleroma.Builders.UserBuilder
+ alias Pleroma.Web.TwitterAPI.UserView
import Pleroma.Factory
@@ -27,7 +30,7 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
assert represented["profile_image_url"] == image
end
- test "A user with emoji in username", %{user: user} do
+ test "A user with emoji in username" do
expected =
"<img height=\"32px\" width=\"32px\" alt=\"karjalanpiirakka\" title=\"karjalanpiirakka\" src=\"/file.png\" /> man"
@@ -87,7 +90,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"follows_you" => false,
"statusnet_blocking" => false,
"rights" => %{
- "delete_others_notice" => false
+ "delete_others_notice" => false,
+ "admin" => false
},
"statusnet_profile_url" => user.ap_id,
"cover_photo" => banner,
@@ -96,7 +100,13 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"locked" => false,
"default_scope" => "public",
"no_rich_text" => false,
- "fields" => []
+ "hide_follows" => false,
+ "hide_followers" => false,
+ "fields" => [],
+ "pleroma" => %{
+ "confirmation_pending" => false,
+ "tags" => []
+ }
}
assert represented == UserView.render("show.json", %{user: user})
@@ -128,7 +138,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"follows_you" => false,
"statusnet_blocking" => false,
"rights" => %{
- "delete_others_notice" => false
+ "delete_others_notice" => false,
+ "admin" => false
},
"statusnet_profile_url" => user.ap_id,
"cover_photo" => banner,
@@ -137,7 +148,13 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"locked" => false,
"default_scope" => "public",
"no_rich_text" => false,
- "fields" => []
+ "hide_follows" => false,
+ "hide_followers" => false,
+ "fields" => [],
+ "pleroma" => %{
+ "confirmation_pending" => false,
+ "tags" => []
+ }
}
assert represented == UserView.render("show.json", %{user: user, for: follower})
@@ -170,7 +187,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"follows_you" => true,
"statusnet_blocking" => false,
"rights" => %{
- "delete_others_notice" => false
+ "delete_others_notice" => false,
+ "admin" => false
},
"statusnet_profile_url" => follower.ap_id,
"cover_photo" => banner,
@@ -179,7 +197,13 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"locked" => false,
"default_scope" => "public",
"no_rich_text" => false,
- "fields" => []
+ "hide_follows" => false,
+ "hide_followers" => false,
+ "fields" => [],
+ "pleroma" => %{
+ "confirmation_pending" => false,
+ "tags" => []
+ }
}
assert represented == UserView.render("show.json", %{user: follower, for: user})
@@ -190,6 +214,36 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
represented = UserView.render("show.json", %{user: user, for: user})
assert represented["rights"]["delete_others_notice"]
+ assert represented["role"] == "moderator"
+ end
+
+ test "a user that is a admin" do
+ user = insert(:user, %{info: %{is_admin: true}})
+ represented = UserView.render("show.json", %{user: user, for: user})
+
+ assert represented["rights"]["admin"]
+ assert represented["role"] == "admin"
+ end
+
+ test "A moderator with hidden role for another user", %{user: user} do
+ admin = insert(:user, %{info: %{is_moderator: true, show_role: false}})
+ represented = UserView.render("show.json", %{user: admin, for: user})
+
+ assert represented["role"] == nil
+ end
+
+ test "An admin with hidden role for another user", %{user: user} do
+ admin = insert(:user, %{info: %{is_admin: true, show_role: false}})
+ represented = UserView.render("show.json", %{user: admin, for: user})
+
+ assert represented["role"] == nil
+ end
+
+ test "A regular user for the admin", %{user: user} do
+ admin = insert(:user, %{info: %{is_admin: true}})
+ represented = UserView.render("show.json", %{user: user, for: admin})
+
+ assert represented["pleroma"]["deactivated"] == false
end
test "A blocked user for the blocker" do
@@ -219,7 +273,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"follows_you" => false,
"statusnet_blocking" => true,
"rights" => %{
- "delete_others_notice" => false
+ "delete_others_notice" => false,
+ "admin" => false
},
"statusnet_profile_url" => user.ap_id,
"cover_photo" => banner,
@@ -228,10 +283,16 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"locked" => false,
"default_scope" => "public",
"no_rich_text" => false,
- "fields" => []
+ "hide_follows" => false,
+ "hide_followers" => false,
+ "fields" => [],
+ "pleroma" => %{
+ "confirmation_pending" => false,
+ "tags" => []
+ }
}
- blocker = Repo.get(User, blocker.id)
+ blocker = User.get_by_id(blocker.id)
assert represented == UserView.render("show.json", %{user: user, for: blocker})
end
diff --git a/test/web/views/error_view_test.exs b/test/web/views/error_view_test.exs
index 1d443b187..d529fd2c3 100644
--- a/test/web/views/error_view_test.exs
+++ b/test/web/views/error_view_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ErrorViewTest do
use Pleroma.Web.ConnCase, async: true
@@ -10,11 +14,16 @@ defmodule Pleroma.Web.ErrorViewTest do
test "render 500.json" do
assert render(Pleroma.Web.ErrorView, "500.json", []) ==
- %{errors: %{detail: "Internal server error"}}
+ %{errors: %{detail: "Internal server error", reason: "nil"}}
end
test "render any other" do
assert render(Pleroma.Web.ErrorView, "505.json", []) ==
- %{errors: %{detail: "Internal server error"}}
+ %{errors: %{detail: "Internal server error", reason: "nil"}}
+ end
+
+ test "render 500.json with reason" do
+ assert render(Pleroma.Web.ErrorView, "500.json", reason: "test reason") ==
+ %{errors: %{detail: "Internal server error", reason: "\"test reason\""}}
end
end
diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs
new file mode 100644
index 000000000..43fccfc7a
--- /dev/null
+++ b/test/web/web_finger/web_finger_controller_test.exs
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ test "Webfinger JRD" do
+ user = insert(:user)
+
+ response =
+ build_conn()
+ |> put_req_header("accept", "application/jrd+json")
+ |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
+
+ assert json_response(response, 200)["subject"] == "acct:#{user.nickname}@localhost"
+ end
+
+ test "Webfinger XML" do
+ user = insert(:user)
+
+ response =
+ build_conn()
+ |> put_req_header("accept", "application/xrd+xml")
+ |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
+
+ assert response(response, 200)
+ end
+
+ test "Sends a 400 when resource param is missing" do
+ response =
+ build_conn()
+ |> put_req_header("accept", "application/xrd+xml,application/jrd+json")
+ |> get("/.well-known/webfinger")
+
+ assert response(response, 400)
+ end
+end
diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs
index 28d429565..6b20d8d56 100644
--- a/test/web/web_finger/web_finger_test.exs
+++ b/test/web/web_finger/web_finger_test.exs
@@ -1,7 +1,17 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.WebFingerTest do
use Pleroma.DataCase
alias Pleroma.Web.WebFinger
import Pleroma.Factory
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
describe "host meta" do
test "returns a link to the xml lrdd" do
diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs
index d861c241f..1e69ed01a 100644
--- a/test/web/websub/websub_controller_test.exs
+++ b/test/web/websub/websub_controller_test.exs
@@ -1,9 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Websub.WebsubControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
- alias Pleroma.Web.Websub.WebsubClientSubscription
- alias Pleroma.{Repo, Activity}
+ alias Pleroma.Activity
+ alias Pleroma.Repo
alias Pleroma.Web.Websub
+ alias Pleroma.Web.Websub.WebsubClientSubscription
test "websub subscription request", %{conn: conn} do
user = insert(:user)
@@ -46,35 +51,37 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do
assert_in_delta NaiveDateTime.diff(websub.valid_until, NaiveDateTime.utc_now()), 100, 5
end
- test "handles incoming feed updates", %{conn: conn} do
- websub = insert(:websub_client_subscription)
- doc = "some stuff"
- signature = Websub.sign(websub.secret, doc)
+ describe "websub_incoming" do
+ test "handles incoming feed updates", %{conn: conn} do
+ websub = insert(:websub_client_subscription)
+ doc = "some stuff"
+ signature = Websub.sign(websub.secret, doc)
- conn =
- conn
- |> put_req_header("x-hub-signature", "sha1=" <> signature)
- |> put_req_header("content-type", "application/atom+xml")
- |> post("/push/subscriptions/#{websub.id}", doc)
+ conn =
+ conn
+ |> put_req_header("x-hub-signature", "sha1=" <> signature)
+ |> put_req_header("content-type", "application/atom+xml")
+ |> post("/push/subscriptions/#{websub.id}", doc)
- assert response(conn, 200) == "OK"
+ assert response(conn, 200) == "OK"
- assert length(Repo.all(Activity)) == 1
- end
+ assert length(Repo.all(Activity)) == 1
+ end
- test "rejects incoming feed updates with the wrong signature", %{conn: conn} do
- websub = insert(:websub_client_subscription)
- doc = "some stuff"
- signature = Websub.sign("wrong secret", doc)
+ test "rejects incoming feed updates with the wrong signature", %{conn: conn} do
+ websub = insert(:websub_client_subscription)
+ doc = "some stuff"
+ signature = Websub.sign("wrong secret", doc)
- conn =
- conn
- |> put_req_header("x-hub-signature", "sha1=" <> signature)
- |> put_req_header("content-type", "application/atom+xml")
- |> post("/push/subscriptions/#{websub.id}", doc)
+ conn =
+ conn
+ |> put_req_header("x-hub-signature", "sha1=" <> signature)
+ |> put_req_header("content-type", "application/atom+xml")
+ |> post("/push/subscriptions/#{websub.id}", doc)
- assert response(conn, 500) == "Error"
+ assert response(conn, 500) == "Error"
- assert length(Repo.all(Activity)) == 0
+ assert Enum.empty?(Repo.all(Activity))
+ end
end
end
diff --git a/test/web/websub/websub_test.exs b/test/web/websub/websub_test.exs
index da7bc9112..74386d7db 100644
--- a/test/web/websub/websub_test.exs
+++ b/test/web/websub/websub_test.exs
@@ -1,15 +1,22 @@
-defmodule Pleroma.Web.WebsubMock do
- def verify(sub) do
- {:ok, sub}
- end
-end
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.WebsubTest do
use Pleroma.DataCase
+
+ alias Pleroma.Web.Router.Helpers
alias Pleroma.Web.Websub
- alias Pleroma.Web.Websub.{WebsubServerSubscription, WebsubClientSubscription}
+ alias Pleroma.Web.Websub.WebsubClientSubscription
+ alias Pleroma.Web.Websub.WebsubServerSubscription
+
import Pleroma.Factory
- alias Pleroma.Web.Router.Helpers
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
test "a verification of a request that is accepted" do
sub = insert(:websub_subscription)
@@ -26,8 +33,8 @@ defmodule Pleroma.Web.WebsubTest do
assert String.to_integer(seconds) > 0
{:ok,
- %HTTPoison.Response{
- status_code: 200,
+ %Tesla.Env{
+ status: 200,
body: challenge
}}
end
@@ -41,8 +48,8 @@ defmodule Pleroma.Web.WebsubTest do
getter = fn _path, _headers, _options ->
{:ok,
- %HTTPoison.Response{
- status_code: 500,
+ %Tesla.Env{
+ status: 500,
body: ""
}}
end
@@ -113,12 +120,7 @@ defmodule Pleroma.Web.WebsubTest do
test "discovers the hub and canonical url" do
topic = "https://mastodon.social/users/lambadalambda.atom"
- getter = fn ^topic ->
- doc = File.read!("test/fixtures/lambadalambda.atom")
- {:ok, %{status_code: 200, body: doc}}
- end
-
- {:ok, discovered} = Websub.gather_feed_data(topic, getter)
+ {:ok, discovered} = Websub.gather_feed_data(topic)
expected = %{
"hub" => "https://mastodon.social/api/push",
@@ -158,7 +160,7 @@ defmodule Pleroma.Web.WebsubTest do
websub.id
)
- {:ok, %{status_code: 202}}
+ {:ok, %{status: 202}}
end
task = Task.async(fn -> Websub.request_subscription(websub, poster) end)
@@ -177,7 +179,7 @@ defmodule Pleroma.Web.WebsubTest do
websub = insert(:websub_client_subscription, %{hub: hub, topic: topic})
poster = fn ^hub, {:form, _data}, _headers ->
- {:ok, %{status_code: 202}}
+ {:ok, %{status: 202}}
end
{:error, websub} = Websub.request_subscription(websub, poster, 1000)
@@ -186,7 +188,7 @@ defmodule Pleroma.Web.WebsubTest do
websub = insert(:websub_client_subscription, %{hub: hub, topic: topic})
poster = fn ^hub, {:form, _data}, _headers ->
- {:ok, %{status_code: 400}}
+ {:ok, %{status: 400}}
end
{:error, websub} = Websub.request_subscription(websub, poster, 1000)
@@ -209,6 +211,7 @@ defmodule Pleroma.Web.WebsubTest do
insert(:websub_client_subscription, %{
valid_until: NaiveDateTime.add(now, 2 * day),
topic: "http://example.org/still_good",
+ hub: "http://example.org/still_good",
state: "accepted"
})
@@ -216,6 +219,7 @@ defmodule Pleroma.Web.WebsubTest do
insert(:websub_client_subscription, %{
valid_until: NaiveDateTime.add(now, day - 100),
topic: "http://example.org/needs_refresh",
+ hub: "http://example.org/needs_refresh",
state: "accepted"
})
diff --git a/test/xml_builder_test.exs b/test/xml_builder_test.exs
index 4be7bbd01..a7742f339 100644
--- a/test/xml_builder_test.exs
+++ b/test/xml_builder_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.XmlBuilderTest do
use Pleroma.DataCase
alias Pleroma.XmlBuilder