aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/activity_test.exs13
-rw-r--r--test/config/transfer_task_test.exs53
-rw-r--r--test/conversation/participation_test.exs3
-rw-r--r--test/fixtures/rich_media/non_ogp_embed.html1479
-rw-r--r--test/fixtures/rich_media/ogp-missing-title.html12
-rw-r--r--test/html_test.exs69
-rw-r--r--test/integration/mastodon_websocket_test.exs10
-rw-r--r--test/media_proxy_test.exs15
-rw-r--r--test/notification_test.exs52
-rw-r--r--test/object/containment_test.exs6
-rw-r--r--test/object/fetcher_test.exs34
-rw-r--r--test/plugs/idempotency_plug_test.exs110
-rw-r--r--test/plugs/rate_limit_plug_test.exs50
-rw-r--r--test/plugs/rate_limiter_test.exs108
-rw-r--r--test/support/factory.ex14
-rw-r--r--test/support/http_request_mock.ex40
-rw-r--r--test/tasks/config_test.exs63
-rw-r--r--test/tasks/ecto/migrate_test.exs20
-rw-r--r--test/tasks/ecto/rollback_test.exs16
-rw-r--r--test/tasks/instance.exs3
-rw-r--r--test/tasks/user_test.exs3
-rw-r--r--test/user_test.exs84
-rw-r--r--test/web/activity_pub/mrf/anti_link_spam_policy_test.exs145
-rw-r--r--test/web/activity_pub/transmogrifier_test.exs24
-rw-r--r--test/web/admin_api/admin_api_controller_test.exs341
-rw-r--r--test/web/admin_api/config_test.exs258
-rw-r--r--test/web/admin_api/views/report_view_test.exs98
-rw-r--r--test/web/common_api/common_api_test.exs2
-rw-r--r--test/web/mastodon_api/account_view_test.exs20
-rw-r--r--test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs304
-rw-r--r--test/web/mastodon_api/mastodon_api_controller_test.exs851
-rw-r--r--test/web/mastodon_api/search_controller_test.exs128
-rw-r--r--test/web/oauth/oauth_controller_test.exs183
-rw-r--r--test/web/rich_media/helpers_test.exs47
-rw-r--r--test/web/rich_media/parser_test.exs33
-rw-r--r--test/web/streamer_test.exs152
-rw-r--r--test/web/twitter_api/password_controller_test.exs56
37 files changed, 4160 insertions, 739 deletions
diff --git a/test/activity_test.exs b/test/activity_test.exs
index e56e39096..7ba4363c8 100644
--- a/test/activity_test.exs
+++ b/test/activity_test.exs
@@ -139,18 +139,25 @@ defmodule Pleroma.ActivityTest do
assert [^local_activity] = Activity.search(nil, "find me")
end
- test "find all statuses for unauthenticated users when `limit_unauthenticated_to_local_content` is `false`",
+ test "find only local statuses for unauthenticated users when `limit_to_local_content` is `:all`",
+ %{local_activity: local_activity} do
+ Pleroma.Config.put([:instance, :limit_to_local_content], :all)
+ assert [^local_activity] = Activity.search(nil, "find me")
+ Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
+ end
+
+ test "find all statuses for unauthenticated users when `limit_to_local_content` is `false`",
%{
local_activity: local_activity,
remote_activity: remote_activity
} do
- Pleroma.Config.put([:instance, :limit_unauthenticated_to_local_content], false)
+ Pleroma.Config.put([:instance, :limit_to_local_content], false)
activities = Enum.sort_by(Activity.search(nil, "find me"), & &1.id)
assert [^local_activity, ^remote_activity] = activities
- Pleroma.Config.put([:instance, :limit_unauthenticated_to_local_content], true)
+ Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
end
end
end
diff --git a/test/config/transfer_task_test.exs b/test/config/transfer_task_test.exs
new file mode 100644
index 000000000..c0e433263
--- /dev/null
+++ b/test/config/transfer_task_test.exs
@@ -0,0 +1,53 @@
+defmodule Pleroma.Config.TransferTaskTest do
+ use Pleroma.DataCase
+
+ setup do
+ dynamic = Pleroma.Config.get([:instance, :dynamic_configuration])
+
+ Pleroma.Config.put([:instance, :dynamic_configuration], true)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :dynamic_configuration], dynamic)
+ end)
+ end
+
+ test "transfer config values from db to env" do
+ refute Application.get_env(:pleroma, :test_key)
+ refute Application.get_env(:idna, :test_key)
+
+ Pleroma.Web.AdminAPI.Config.create(%{
+ group: "pleroma",
+ key: "test_key",
+ value: [live: 2, com: 3]
+ })
+
+ Pleroma.Web.AdminAPI.Config.create(%{
+ group: "idna",
+ key: "test_key",
+ value: [live: 15, com: 35]
+ })
+
+ Pleroma.Config.TransferTask.start_link()
+
+ assert Application.get_env(:pleroma, :test_key) == [live: 2, com: 3]
+ assert Application.get_env(:idna, :test_key) == [live: 15, com: 35]
+
+ on_exit(fn ->
+ Application.delete_env(:pleroma, :test_key)
+ Application.delete_env(:idna, :test_key)
+ end)
+ end
+
+ test "non existing atom" do
+ Pleroma.Web.AdminAPI.Config.create(%{
+ group: "pleroma",
+ key: "undefined_atom_key",
+ value: [live: 2, com: 3]
+ })
+
+ assert ExUnit.CaptureLog.capture_log(fn ->
+ Pleroma.Config.TransferTask.start_link()
+ end) =~
+ "updating env causes error, key: \"undefined_atom_key\", error: %ArgumentError{message: \"argument error\"}"
+ end
+end
diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs
index 0e60bfca5..2a03e5d67 100644
--- a/test/conversation/participation_test.exs
+++ b/test/conversation/participation_test.exs
@@ -72,8 +72,11 @@ defmodule Pleroma.Conversation.ParticipationTest do
object2 = Pleroma.Object.normalize(activity_two)
object3 = Pleroma.Object.normalize(activity_three)
+ user = Repo.get(Pleroma.User, user.id)
+
assert participation_one.conversation.ap_id == object3.data["context"]
assert participation_two.conversation.ap_id == object2.data["context"]
+ assert participation_one.conversation.users == [user]
# Pagination
assert [participation_one] = Participation.for_user(user, %{"limit" => 1})
diff --git a/test/fixtures/rich_media/non_ogp_embed.html b/test/fixtures/rich_media/non_ogp_embed.html
new file mode 100644
index 000000000..62a1d677a
--- /dev/null
+++ b/test/fixtures/rich_media/non_ogp_embed.html
@@ -0,0 +1,1479 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="CACHE-CONTROL" content="NO-CACHE">
+ <meta charset="UTF-8">
+ <link rel="apple-touch-icon-precomposed" sizes="57x57" href="https://img.mfcimg.com/images/favicons/apple-touch-icon-57x57.png?nc=1" />
+<link rel="apple-touch-icon-precomposed" sizes="114x114" href="https://img.mfcimg.com/images/favicons/apple-touch-icon-114x114.png?nc=1" />
+<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://img.mfcimg.com/images/favicons/apple-touch-icon-72x72.png?nc=1" />
+<link rel="apple-touch-icon-precomposed" sizes="144x144" href="https://img.mfcimg.com/images/favicons/apple-touch-icon-144x144.png?nc=1" />
+<link rel="apple-touch-icon-precomposed" sizes="60x60" href="https://img.mfcimg.com/images/favicons/apple-touch-icon-60x60.png?nc=1" />
+<link rel="apple-touch-icon-precomposed" sizes="120x120" href="https://img.mfcimg.com/images/favicons/apple-touch-icon-120x120.png?nc=1" />
+<link rel="apple-touch-icon-precomposed" sizes="76x76" href="https://img.mfcimg.com/images/favicons/apple-touch-icon-76x76.png?nc=1" />
+<link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://img.mfcimg.com/images/favicons/apple-touch-icon-152x152.png?nc=1" />
+<link rel="icon" type="image/png" href="https://img.mfcimg.com/images/favicons/favicon-196x196.png?nc=1" sizes="196x196" />
+<link rel="icon" type="image/png" href="https://img.mfcimg.com/images/favicons/favicon-96x96.png?nc=1" sizes="96x96" />
+<link rel="icon" type="image/png" href="https://img.mfcimg.com/images/favicons/favicon-32x32.png?nc=1" sizes="32x32" />
+<link rel="icon" type="image/png" href="https://img.mfcimg.com/images/favicons/favicon-16x16.png?nc=1" sizes="16x16" />
+<link rel="icon" type="image/png" href="https://img.mfcimg.com/images/favicons/favicon-128.png?nc=1" sizes="128x128" />
+<meta name="application-name" content="MyFreeCams.com Profiles" />
+<meta name="msapplication-TileColor" content="#008000" />
+<meta name="msapplication-TileImage" content="https://img.mfcimg.com/images/favicons/mstile-144x144.png?nc=1" />
+<meta name="msapplication-square70x70logo" content="https://img.mfcimg.com/images/favicons/mstile-70x70.png?nc=1" />
+<meta name="msapplication-square150x150logo" content="https://img.mfcimg.com/images/favicons/mstile-150x150.png?nc=1" />
+<meta name="msapplication-wide310x150logo" content="https://img.mfcimg.com/images/favicons/mstile-310x150.png?nc=1" />
+<meta name="msapplication-square310x310logo" content="https://img.mfcimg.com/images/favicons/mstile-310x310.png?nc=1" />
+
+ <script src="https://img.mfcimg.com/profiles/jquery/jquery-1.9.1.min.js"></script>
+<script src="https://img.mfcimg.com/profiles/jquery/jquery-ui-1.9.2.min.js"></script>
+<script src="https://img.mfcimg.com/profiles/jquery/jquery.ui.touch-punch.min.js"></script> <script>
+ var g_hPlatform = { "id": 1, "domain": "myfreecams.com", "name": "MyFreeCams", "code": "mfc", "image_url": "https://img.mfcimg.com/" };
+
+ try { document.domain = 'myfreecams.com'; } catch (e) {}
+
+ var MfcAssets = {
+ images: "/bundles/mfcprofile/vendor/img/",
+ urls: {
+ www: "https://www.myfreecams.com/",
+ new_comments: "/BlueAngelLove/comments/since/0"
+ }
+ };
+
+ var MfcPageVars = {
+ userId: 0,
+ accessLevel: 0,
+ token: "xIqyjzUBSrt6Rbl_su7UOrDxNZJlZNc4nsWh6eXxDkg",
+ profileState: {"number":127,"string":"Offline"},
+ serverTime: {"unixTime":1561209909,"time":"6:25am PDT","dst":1},
+ profileUsername: "BlueAngelLove",
+ admirers: 4719,
+ username: "",
+ userPhotoUrl: "",
+ vToken: "4c4ea23b221f89b73c964b7f99a50f78",
+ avatarRev: 0,
+ avgRating: {"rating_count":7060,"rating_average":"4.8681"},
+ rating: 0
+};
+
+
+ function MfcProfilePage( jQuery )
+ {
+ var _self = this;
+
+ _self.$ = jQuery;
+
+ _self.token = ( typeof(MfcPageVars) !== 'undefined' && MfcPageVars.token ) ? MfcPageVars.token : _self.$('meta[name=token]').attr('content');
+
+ _self.beforeDomReady();
+
+
+ _self.$(function(){ _self.afterDomReady(); });
+ };
+
+ MfcProfilePage.prototype.beforeDomReady = function()
+ {
+ var _self = this;
+ var $ = _self.$;
+
+ if ( _self.token )
+ {
+ $.ajaxSetup({
+ beforeSend: function(xhr, settings) {
+ if ( settings.type === 'GET' || settings.crossDomain )
+ return;
+
+ if ( $.type(settings.data) === 'object' && $.type(settings.data.append) === 'function' )
+ {
+ settings.data.append('_token', _self.token);
+ }
+ else if ( $.type(settings.data) === 'string' && settings.data.indexOf('_token=') === -1 )
+ {
+ if ( settings.data.length === 0 )
+ {
+ xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
+ } else {
+ settings.data += '&';
+ }
+
+ settings.data += encodeURIComponent('_token') + '=' + encodeURIComponent(_self.token);
+ }
+ }
+ });
+
+ $(document).on('submit', 'form', function(e)
+ {
+ if ( ! $(this).find('#_token').length && ! $(this).data('mfc-no-token') )
+ $(this).append($('<input type="hidden" name="_token" id="_token" value="' + _self.token + '">'));
+ });
+ }
+ };
+
+ MfcProfilePage.prototype.afterDomReady = function()
+ {
+ var _self = this;
+ var $ = this.$;
+
+ var page = $('body').data('mfc-page');
+
+ if ( $.isFunction(_self[page]) )
+ _self[page]();
+ };
+
+ new MfcProfilePage(jQuery);
+</script>
+
+ <link href="https://img.mfcimg.com/profiles/prod/22793316144741120/css/profiles.css?nc=22793316144741120" type="text/css" rel="stylesheet">
+
+ <title>BlueAngelLove's Homepage on MyFreeCams.com</title>
+ <meta name="description" content="BlueAngelLove's webcam homepage on MyFreeCams.com - your #1 adult webcam community">
+ <meta name="keywords" content="webcams,models,adult,community,nude,chat,video">
+
+ <style type="text/css">
+ body.mfc_display_inline_mode #header_bar, body.mfc_display_inline_mode #footer_bar {
+ visibility: hidden;
+ }
+ body.mfc_profile_standard.mfc_display_inline_mode {
+ margin-left: 0;
+ margin-right: 0;
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+ body.mfc_profile_standard.mfc_display_inline_mode #profile_about_me {
+ display: flex;
+ flex-flow: wrap;
+ }
+ body.mfc_profile_standard.mfc_display_inline_mode #profile_about_me .heading {
+ flex: 0 0 100%;
+ }
+ body.mfc_profile_standard.mfc_display_inline_mode #profile_about_me .container {
+ flex: 0 1 50%;
+ margin: 0;
+ padding: 0;
+ }
+ body.mfc_profile_standard.mfc_display_inline_mode #profile_about_me #about_me_container, body.mfc_profile_standard.mfc_display_inline_mode #profile_about_me #tags_container {
+ flex: 0 0 100%;
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+ @media (max-width: 850px) {
+ body.mfc_profile_standard.mfc_display_inline_mode #profile_about_me .container {
+ flex: 0 0 100%;
+ }
+ }
+ @media (min-width: 1500px) {
+ body.mfc_profile_standard.mfc_display_inline_mode #profile_about_me .container {
+ flex: 0 0 33%;
+ }
+ }
+ </style>
+
+ <link href="/BlueAngelLove/css?nc=204272526" rel="stylesheet" type="text/css">
+
+
+ <script type="text/javascript">
+ g_bInIframe = (function(w) {
+ try {
+ return w.self !== w.top;
+ } catch (e) {
+ return true;
+ }
+ return false;
+ })(window);
+
+ (function(w,d) {
+ 'use strict';
+
+ var hrefClickFn = function (e) {
+ e = e || w.event;
+
+ var target = findHrefElFn(e.target || e.srcElement);
+
+ if ( target != undefined && ((target.hostname + target.pathname.replace(/(^\/?)/,'/')).toLowerCase() !== (location.hostname + location.pathname).toLowerCase()) ) {
+ target.setAttribute('target', '_blank');
+ target.setAttribute('rel', 'noopener noreferrer');
+ }
+
+ return true;
+ };
+
+ var isHrefElFn = function(el) {
+ var elName = (el.nodeName || el.tagName).toLowerCase();
+ if ( (elName === 'a' || elName === 'area') && el.href != undefined ) { return true; }
+ return false;
+ };
+
+ var findHrefElFn = function(el) {
+ if ( isHrefElFn(el) ) { return el; }
+ while (el = el.parentNode) {
+ if ( isHrefElFn(el) ) { return el; }
+ }
+ return undefined;
+ };
+
+ if ( g_bInIframe ) {
+ if ( d.addEventListener ) {
+ d.addEventListener('click', hrefClickFn);
+ } else {
+ d.attachEvent('onclick', hrefClickFn);
+ }
+ }
+ })(window, document);
+</script>
+
+ </head>
+ <body class="mfc_profile_customized" data-mfc-page="userShow">
+ <script type="text/javascript">
+ (function(w,d,v) {
+ 'use strict';
+
+ var classes = [];
+ var search = w.location.search || '';
+ var vs = (typeof v === 'object' && v.profileState) ? v.profileState.number : 127;
+
+ if ( search.match(/[?&]inline_mode=1/) ) {
+ classes.push('mfc_display_inline_mode');
+ }
+ if ( search.match(/[?&]online=1/) || vs != 127 ) {
+ classes.push('mfc_online');
+ }
+ if ( 'Model' === 'Model' && ( search.match(/[?&]broadcasting=1/) || vs < 90 ) ) {
+ classes.push('mfc_broadcasting');
+ }
+
+ if ( classes.length ) {
+ d.getElementsByTagName('body')[0].className += ' ' + classes.join(' ');
+ }
+
+ })(window, document, MfcPageVars);
+</script>
+ <div id="fixed_background"></div>
+
+ <div id="header_bar">
+ <div class="header_links">
+ <a href="/">Profiles.MyFreeCams.com</a> |
+ <a href='https://www.myfreecams.com/'>MyFreeCams.com</a> |
+ <a href="/_/my_profile">My Profile</a> |
+ <a href="/_/login">Profile Settings</a>
+ </div>
+
+ <div class="clearfix header_time">
+
+ <div id="server_time">
+ <table>
+ <tbody>
+ <tr>
+ <td>Your Time:</td>
+ <td id="your_time"></td>
+ </tr>
+ <tr>
+ <td>MyFreeCams Time:</td>
+ <td id="mfc_time"></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ </div>
+ </div>
+
+ <div id="profile">
+ <div class="profile_row">
+ <div class="profile_section" id="profile_header">
+ <div class="profile_section_content">
+ <div class="profile_section_background"></div>
+
+ <div id="avatar_holder">
+ <img id="profile_avatar" class="img_radius_shadow" src="https://img.mfcimg.com/photos2/320/3204009/avatar.90x90.jpg?nc=1557647675" onError="this.onerror=null; this.src='https://img.mfcimg.com/images/nophoto-f.gif';">
+ </div>
+
+ <div id="profile_header_container">
+ <div class="heading">
+ BlueAngelLove
+ </div>
+
+ <div class="container" id="status_container">
+ <div class="label" id="status_label">
+ Status:
+ </div>
+ <div class="value" id="status_value">
+ <span id="member_status_value" class="hidden"></span>
+ <span id="member_type_value">&nbsp;- Model -</span>
+ <span id="member_message_value" class="hidden" data-mfc-member-type="Model"><a href="#" id="show_message_dialog">Send a MyFreeCams Mail</a></span>
+ </div>
+ </div>
+
+
+
+ <div class="container" id="blurb_container">
+ <span class="label" id="blurb_label">
+ Profile Headline:
+ </span>
+
+ <span class="value" id="blurb_value">
+ Enjoy and Love
+ </span>
+ </div>
+
+
+
+
+
+
+
+
+ <div class="container" id="unix_last_broadcast_container">
+ <span class="label" id="unix_last_broadcast_label">
+ Last Broadcast:
+ </span>
+
+ <span class="value convert-time" id="unix_last_broadcast_value" data-mfc-unix-time="1561100400" data-mfc-time-format="ddd, MMM D YYYY"></span>
+ </div>
+
+
+
+
+
+ <div class="container" id="unix_last_updated_container">
+ <span class="label" id="unix_last_updated_label">
+ Last Updated:
+ </span>
+
+ <span class="value convert-time" id="unix_last_updated_value" data-mfc-unix-time="1561193088" data-mfc-time-format="llll"></span>
+ </div>
+
+
+
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="profile_row" id="profile_main_about_holder">
+
+ <div id="profile_main_photo">
+ <div class="profile_section">
+ <div class="profile_section_content">
+ <div class="profile_section_background"></div>
+
+
+ <div class="heading">
+ My Most Recent Pictures
+ </div>
+ <div class="recent_photos">
+ <img src="https://img.mfcimg.com/photos2/320/3204009/986-665-202-679-12065535.80x80.jpg" class="img_radius_shadow show_preview" onError="this.onerror=null; this.src='https://img.mfcimg.com/images/nophoto-f.gif';" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/986-665-202-679-12065535.250.jpg">
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="profile_section" id="profile_about_me_friends">
+ <div class="profile_section_content">
+ <div class="profile_section_background"></div>
+
+ <div class="profile_subsection" id="profile_about_me">
+
+ <div class="heading">
+ About Me
+ </div>
+
+
+
+ <div class="container" id="username_container">
+ <span class="label" id="username_label">
+ Username:
+ </span>
+
+ <span class="value" id="username_value">
+ BlueAngelLove </span>
+ </div>
+
+
+
+
+
+
+
+
+ <div class="container" id="gender_container">
+ <span class="label" id="gender_label">
+ Gender:
+ </span>
+
+ <span class="value" id="gender_value">
+ Female </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="body_type_container">
+ <span class="label" id="body_type_label">
+ Body Type:
+ </span>
+
+ <span class="value" id="body_type_value">
+ Athletic </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="ethnicity_container">
+ <span class="label" id="ethnicity_label">
+ Ethnicity:
+ </span>
+
+ <span class="value" id="ethnicity_value">
+ Other </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="hair_container">
+ <span class="label" id="hair_label">
+ Hair:
+ </span>
+
+ <span class="value" id="hair_value">
+ Brown </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="eyes_container">
+ <span class="label" id="eyes_label">
+ Eyes:
+ </span>
+
+ <span class="value" id="eyes_value">
+ Blue </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="weight_container">
+ <span class="label" id="weight_label">
+ Weight:
+ </span>
+
+ <span class="value" id="weight_value">
+ 45 kilos </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="height_container">
+ <span class="label" id="height_label">
+ Height:
+ </span>
+
+ <span class="value" id="height_value">
+ 165 centimeters </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="age_container">
+ <span class="label" id="age_label">
+ Age:
+ </span>
+
+ <span class="value" id="age_value">
+ 34 </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="city_container">
+ <span class="label" id="city_label">
+ City:
+ </span>
+
+ <span class="value" id="city_value">
+ Mountains </span>
+ </div>
+
+
+
+
+
+
+
+
+
+
+
+ <div class="container" id="sexual_preference_container">
+ <span class="label" id="sexual_preference_label">
+ Sexual Preference:
+ </span>
+
+ <span class="value" id="sexual_preference_value">
+ Bisexual </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="smoke_container">
+ <span class="label" id="smoke_label">
+ Smoke:
+ </span>
+
+ <span class="value" id="smoke_value">
+ Non Smoker </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="drink_container">
+ <span class="label" id="drink_label">
+ Drink:
+ </span>
+
+ <span class="value" id="drink_value">
+ Non Drinker </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="drugs_container">
+ <span class="label" id="drugs_label">
+ Drugs:
+ </span>
+
+ <span class="value" id="drugs_value">
+ Never </span>
+ </div>
+
+
+
+
+
+
+
+
+ <div class="container" id="occupation_container">
+ <span class="label" id="occupation_label">
+ Occupation/Major:
+ </span>
+
+ <span class="value" id="occupation_value">
+ Guide </span>
+ </div>
+
+
+
+
+
+
+
+
+ <div class="container" id="favorite_food_container">
+ <span class="label" id="favorite_food_label">
+ Favorite Food:
+ </span>
+
+ <span class="value" id="favorite_food_value">
+ Chocolate </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="pets_container">
+ <span class="label" id="pets_label">
+ Pets:
+ </span>
+
+ <span class="value" id="pets_value">
+ I dont like pets </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="automobile_container">
+ <span class="label" id="automobile_label">
+ Automobile:
+ </span>
+
+ <span class="value" id="automobile_value">
+ Ford </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="about_me_container">
+ <span class="label" id="about_me_label">
+ About Me:
+ </span>
+
+ <span class="value" id="about_me_value">
+ <a href="//www.dmca.com/Protection/Status.aspx?ID=96b05ddf-1265-4f81-9d84-7dcfeb87cbb6" title="DMCA.com Protection Status" class="dmca-badge"> <img src="https://images.dmca.com/Badges/dmca_protected_16_120.png?ID=96b05ddf-1265-4f81-9d84-7dcfeb87cbb6" alt="DMCA.com Protection Status"></a><a href="http://www.cutercounter.com/" target="_blank" rel="noopener noreferrer"><img src="http://www.cutercounter.com/hits.php?id=grmpackf&amp;nd=7&amp;style=102" border="0" alt="website counter"></a>
+<div id="myCv" class="gen">
+<div class="defaultbg"></div>
+<div class="maintitle">BlueAngelLove</div>
+ <div id="buttons">
+ <a href="https://wa.me/40747018024" class="btn blue"> CONTACT ME </a>
+ <div class="corp">
+
+ <div id="ModelCard">
+ <img src="https://img.mfcimg.com/photos2/320/3204009/314-736-287-236-10552594.jpg" alt="Model's image"><hr><div class="bum"></div>
+ <div class="bum"><a href="http://www.myfreecams.com/mfc2/php/tip.php?request=tip&amp;username=blueangellove" title="Tip Me Offline">Tip Me Offline</a></div>
+ <div class="bum"><a href="http://www.amazon.co.uk/wishlist/3D0MOTP0S0SE5" target="_blank" title="My Amazon Wishlist" rel="noopener noreferrer">My Amazon Wishlist</a></div>
+ <div class="bum"><a href="https://twitter.com/BlueAngelLove33" target="_blank" title="Follow me on Twitter" rel="noopener noreferrer">Follow Me on Twitter</a></div>
+ <div class="bum"><a href="https://wa.me/40747018024" target="_blank" title="Follow me on WhatsApp" rel="noopener noreferrer">Follow Me on Whatsapp</a></div>
+ <div class="bum"><a href="https://www.instagram.com/blueangellove3?r=nametag" title="Follow me on Instagram">Follow Me on Instagram</a></div>
+ <div class="bum"><a href="https://www.snapchat.com/add/cjullyana" title="Follow me on Snapchat">Follow Me on SnapChat</a></div>
+ <div class="bum"><a href="http://hatscripts.com/addskype?BlueAngelLove33" title="Follow me on Skype">Follow Me on Skype</a></div>
+ <div class="bum"><a href="https://www.youtube.com/playlist?list=PLGqo-7BiklVM37HIBud981EpiXxV3yM4m" title="Follow me on Youtube">Follow Me on Youtube </a></div>
+ <div class="bum"><a href="#roomrules" target="_blank" title="Join my Chat Room" rel="noopener noreferrer">Join My Room</a></div>
+ <div class="bum"><a href="https://MFCsha.re/BlueAngelLove" title="Follow me on MFC Share">Follow Me on MFC Share</a></div>
+ <div class="bum"><a href="https://social.myfreecams.com/BlueAngelLove" title="Follow me on Social MFC">Follow Me on Social MFC </a></div>
+ <div class="bum"><a href="https://www.rabb.it/s/d556sr" title="Follow me on Rabbit TV">Follow me on Rabbit TV</a></div>
+ <div class="bum"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=Y8KLFSLZJAKP2&amp;source=url" title="Spoil Me and Offer Your Gift">Spoil Me and Offer Your Gift</a></div>
+ <div class="bum"><a href="https://player.vimeo.com/video/326274838" title="Follow Me">Follow Me</a></div>
+ <div class="bum"><a href="https://www.timeanddate.com/worldclock/personal.html?cities=179,136,248,250,263,152,2462,716,195,69&amp;wch=2" title="TIME CONVERTOR">TIME CONVERTOR</a></div>
+ <hr></div>
+ </div>
+ <div id="AboutMe">
+ <div class="skilltitle">Angel</div>
+ <div class="corp corpus xcr">
+ <div class="unscor">
+ I Want To Be Seduced
+ <div class="skills_model1"><div class="metru_experience skill bxhdw"></div></div>
+ </div>
+ <div class="unscor">
+ I Love Flirt
+ <div class="skills_model1"><div class="metru_coding skill bxhdw"></div></div>
+ </div>
+ <div class="unscor">
+ I Want Be Part Of Your life
+ <div class="skills_model1"><div class="metru_concept skill bxhdw"></div></div>
+ </div>
+ <div class="unscor">
+ I Love Dancing
+ <div class="skills_model1"><div class="metru_concept skill bxhdw"></div></div>
+ </div>
+ <div class="unscor">
+ I Love Erotic Chats
+ <div class="skills_model1"><div class="metru_graphic skill bxhdw"></div></div>
+ </div>
+ <div class="unscor">
+ I am Funny
+ <div class="skills_model1"><div class="metru_coding skill bxhdw"></div></div>
+ </div>
+ <div class="unscor">
+ I Enjoy C2C
+ <div class="skills_model1"><div class="metru_experience skill bxhdw"></div></div>
+ </div>
+ <div class="unscor">
+ I Love Sex and Feel u
+ <div class="skills_model1"><div class="metru_concept skill bxhdw"></div></div>
+ </div>
+ </div>
+ <hr></div>
+ </div>
+ <hr><div class="maintitle">June Month Contestst-Top 3 tippers Get A gift mailed,videos,pictures and will my right hand full month and room helpers as well (be my men for a month or who knows...maybe forever) ***
+Love Ya Angels*** We are currently ranked #2200 overall </div>
+ <p class="dasinfo">Get Listed on My Wall of Fame</p>
+ <div class="corp zreq">
+ <div class="ttippers xcr">
+
+ <p> ElmosEgo 6570 Tks </p>
+ <p> Rw2lite 4800 Tks </p>
+ <p> Toastboi 2093 Tks </p>
+ <p> Acoolahole 1850 Tks </p>
+ <p> Gonodog 1299 Tks </p>
+ <p> Pumpy_G 800 Tks </p>
+ <p> Fowser 690 Tks </p>
+ <p> Aquanautic 600 Tks </p>
+ <p> Daveonthelake 535 Tks </p>
+ <p> Wildpervert2 500 Tks </p>
+ <p> Cloud10101 350 Tks </p>
+ <p> Branson102 337 Tks </p>
+ <p> TheCopperhead 329 Tks </p>
+ <p> Mouche99 250 Tks </p>
+ <p> The88drummer 233 Tks </p>
+ <p> Stringtrees86 199 Tks </p>
+ <p> Blazegordon 183 Tks </p>
+ <p> Waiting_4 183 Tks </p>
+ <p> Sam_mie 170 Tks </p>
+ <p> UtterTripe 150 Tks </p>
+ <p> Darth_penguin 150 Tks </p>
+ <p> Playfullpurv 120 Tks </p>
+ <p> Jordnsprings 103 Tks </p>
+ <p> Travelinlover 100 Tks </p>
+ <p> Da884 100 Tks </p>
+
+ </div>
+ <a href="https://imgbb.com/"><img src="https://i.ibb.co/mybZhYn/cory1.jpg" alt="cory1" border="0"></a>
+</div>
+ <hr><div class="maintitle">May Contest winners - Each month Top 3 tippers Get A gift mailed, videos and pictures - Love Ya Angels </div>
+ <p class="dasinfo">Get Listed on My Wall of Fame</p>
+ <div class="corp zreq">
+ <div class="ttippers xcr">
+ <p> Rw2lite </p>
+ <p> ElmosEgo </p>
+ <p> TJuonesWoah </p>
+
+</div>
+ <a href="https://imgbb.com/"><img src="https://i.ibb.co/mybZhYn/cory1.jpg" alt="cory1" border="0"></a>
+</div>
+ <hr><div class="maintitle"> Menu Per Day </div>
+ <p class="dasinfo">LOVE YA ANGELS</p>
+ <div class="corp zreq">
+ <div class="ttippers xcr">
+ <p> Monday - Outfits Strip </p>
+ <p> Tusday - Raffle </p>
+ <p> Wensday - Gamblers Night </p>
+ <p> Thusday - Orgasmic Vibra or Dildos </p>
+ <p> Friday - Wheel/Treat or Trick </p>
+ <p> Saturday - Phrase </p>
+ <p> Sunday - Keno and Boyfriend choice </p>
+
+
+
+ </div>
+ <div class="dasinfo">You have to tip to get listed above so do your best to get on my exclusive Top Tippers List</div>
+ <div class="bum"></div>
+ </div>
+ <a href="https://ibb.co/vcFmK7x"><img src="https://i.ibb.co/j8NG1cv/Whats-App-Image-2019-01-09-at-10-35-17.jpg" alt="Whats-App-Image-2019-01-09-at-10-35-17" border="0"></a>
+ <a href="https://ibb.co/RcMF0T5"><img src="https://i.ibb.co/kXr782d/45280406-1564895203655742-4887638015087738880-n.jpg" alt="45280406-1564895203655742-4887638015087738880-n" border="0"></a>
+ <a href="https://ibb.co/1s0Tp2r"><img src="https://i.ibb.co/gvrJXgS/best.jpg" alt="best" border="0"></a>
+ <a href="https://ibb.co/cJmJHHv"><img src="https://i.ibb.co/F6P6MMW/Whats-App-Image-2019-01-09-at-10-35-16.jpg" alt="Whats-App-Image-2019-01-09-at-10-35-16" border="0"></a>
+
+ <div class="bum"><a href="http://www.myfreecams.com/mfc2/php/tip.php?request=tip&amp;username=blueangellove" title="Tip Me Offline">Tip Me Offline</a></div>
+ <div class="bum"><a href="http://www.amazon.co.uk/wishlist/3D0MOTP0S0SE5" target="_blank" title="My Amazon Wishlist" rel="noopener noreferrer">My Amazon Wishlist</a></div>
+ <div class="bum"><a href="https://twitter.com/BlueAngelLove33" target="_blank" title="Follow me on Twitter" rel="noopener noreferrer">Follow Me on Twitter</a></div>
+ <div class="bum"><a href="https://wa.me/40747018024" target="_blank" title="Follow me on WhatsApp" rel="noopener noreferrer">Follow Me on Whatsapp</a></div>
+ <div class="bum"><a href="https://www.instagram.com/blueangellove3?r=nametag" title="Follow me on Instagram">Follow Me on Instagram</a></div>
+ <div class="bum"><a href="https://www.snapchat.com/add/cjullyana" title="Follow me on Snapchat">Follow Me on SnapChat</a></div>
+ <div class="bum"><a href="http://hatscripts.com/addskype?BlueAngelLove33" title="Follow me on Skype">Follow Me on Skype</a></div>
+ <div class="bum"><a href="https://www.youtube.com/playlist?list=PLGqo-7BiklVM37HIBud981EpiXxV3yM4m" title="Follow me on Youtube">Follow Me on Youtube </a></div>
+ <div class="bum"><a href="#roomrules" target="_blank" title="Join my Chat Room" rel="noopener noreferrer">Join My Room</a></div>
+ <div class="bum"><a href="https://MFCsha.re/BlueAngelLove" title="Follow me on MFC Share">Follow Me on MFC Share</a></div>
+ <div class="bum"><a href="https://social.myfreecams.com/BlueAngelLove" title="Follow me on Social MFC">Follow Me on Social MFC </a></div>
+ <div class="bum"><a href="https://www.rabb.it/s/d556sr" title="Follow me on Rabbit TV">Follow me on Rabbit TV</a></div>
+ <div class="bum"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=Y8KLFSLZJAKP2&amp;source=url" title="Spoil Me and Offer Your Gift">Spoil Me and Offer Your Gift</a></div>
+ <div class="bum"><a href="https://player.vimeo.com/video/326274838" title="Follow Me">Follow Me</a></div>
+ <div class="bum"><a href="https://www.timeanddate.com/worldclock/personal.html?cities=179,136,248,250,263,152,2462,716,195,69&amp;wch=2" title="TIME CONVERTOR">TIME CONVERTOR</a></div>
+ <hr><div id="GNav">
+ <div id="GNwrapper">
+ <a class="abMe" title="About Me" href="#aboutmesection"></a></div>
+</div>
+<div class="bum"><a href="https://MFCsha.re/BlueAngelLove" title="Follow me on MFC Share">Follow Me on MFC Share</a></div>
+<img src="https://i.ibb.co/Pmt2PsP/Whats-App-Image-2019-05-12-at-05-55-35-1.jpg" alt="Whats-App-Image-2019-05-12-at-05-55-35-1" border="0"><img src="https://image.ibb.co/bt1tzq/lovense-level.png" alt="lovense-level" border="0"><div>
+<span class="neontexte"></span></div>
+<div id="OneSection">
+<div></div>
+<div>
+<img src="http://1.bp.blogspot.com/-qTHLNVFggQU/VFdFIOFPDqI/AAAAAAAAGdU/6cWnDLVp0d8/s1600/findme.png" class="findme" alt="camgirl xxx amateur sex sexy"></div>
+</div>
+<div>
+<div id="TwoSection">
+<div id="aboutmesection">
+<a href="https://ibb.co/ZS2D4vh"><img src="https://i.ibb.co/421rvCj/39741863-284302529029606-7659956026455621632-n.jpg" alt="39741863-284302529029606-7659956026455621632-n" border="0"></a>
+<div id="abtmesec" class="frame">
+<span class="neontext">Let's Fun Laugh Live</span><br><i>I am Jullia from Transylvania and is a pleasure to have u around Enjoy me and my room</i><br><br><span class="neontext">BlueAngelLove</span><br><i>I am good, but not an angel. I do sin, but I am not the devil. I am just a girl in a big world trying to find someone to love and be loved</i><br><br></div>
+<a href="https://ibb.co/gvVKkkf"><img src="https://i.ibb.co/6vBCjjT/20171114-113848.jpg" alt="20171114-113848" border="0"></a><a>
+</a></div>~~~Live~Laugh~Love~~~<i><a href="https://info.flagcounter.com/1Ea8"><img src="https://s04.flagcounter.com/countxl/1Ea8/bg_85C2FF/txt_242424/border_CCCCCC/columns_3/maxflags_33/viewers_0/labels_1/pageviews_0/flags_0/percent_0/" alt="Flag Counter" border="0"></a>
+</i></div><div id="vimlft" class="frame"></div></div></div> </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="tags_container">
+ <span class="label" id="tags_label">
+ Tags:
+ </span>
+
+ <span class="value" id="tags_value">
+ natural, blue eyes, toys, funny, oil, shower, fetish, costume, sex, natural, masturbation, finger, dp, anal, girl next door, romantic, naughty, pervert, open mind, play roles, horny, playful, smiley, lover, sweet, sexy, beautiful, hot, shaved, friendly, pussy, skype </span>
+ </div>
+
+
+
+ </div>
+
+ <div class="profile_subsection" id="profile_friends">
+
+ <div class="heading">
+ Friends
+ </div>
+
+ <div class="container" id="average_rating_container">
+ <span class="label" id="average_rating_label">
+ Average Rating:
+ </span>
+ <span class="value" id="average_rating_value">
+ <span id="average_rating"></span>
+ <span id="average_rating_count"></span>
+ </span>
+ </div>
+
+ <div class="container" id="rate_container">
+ <span class="label" id="rate_label">
+ Rate BlueAngelLove:
+ </span>
+ <span class="value" id="rate_value">
+ <form id="new_rating" class="hidden" action="/BlueAngelLove/ratings" method="post">
+ <span id="rating_value_bar"></span>
+ <span id="rating_confirm" class="hidden emphasis notice"></span>
+</form>
+<div id="new_rating_login_message" class="hidden">
+ You must <a href="/_/login">login</a> to rate.
+</div> </span>
+ </div>
+
+ <div class="container" id="admirers_container">
+ <span class="label" id="admirers_label">
+ Admirers:
+ <br>
+ <form id="new_admirer" action="/BlueAngelLove/admirers" method="post">
+
+ (<a href='#' id="admire">admire</a><span id="admire_confirm" class="hidden notice">admired!</span>)
+</form> </span>
+ <span class="value" id="admirers_value"></span>
+ </div>
+
+ <div class="container" id="friends_container">
+ <span class="label" id="friends_label">
+ Profile Friends:
+ <br>
+ <form id="new_homepage_friend" action="/BlueAngelLove/homepage_friends" method="post">
+
+ (<a href='#' id='make_friend'>make friend</a><span id="make_friend_confirm" class="hidden notice">added!</span>)
+</form> </span>
+ <span class="value" id="friends_value">
+ <a href="/Schnitzngrubn">Schnitzngrubn</a>
+ <a href="/UtterTripe">UtterTripe</a>
+ <a href="/MisterPopular">MisterPopular</a>
+ <a href="/neoviewer">neoviewer</a>
+ <a href="/lasse1991">lasse1991</a>
+ <a href="/toastboi">toastboi</a>
+ <a href="/obiwan1965">obiwan1965</a>
+ <a href="/Eastie_Beasty">Eastie_Beasty</a>
+ <a href="/Robby1890">Robby1890</a>
+ <a href="/rw2lite">rw2lite</a>
+ <a href="/zoomie2178">zoomie2178</a>
+ <a href="/AS_rayman41">AS_rayman41</a>
+ <a href="/CJamz87">CJamz87</a>
+ <a href="/Dunky4Jullia">Dunky4Jullia</a>
+ <a href="/Zdasher">Zdasher</a>
+ <a href="/Fowser">Fowser</a>
+ <a href="/buffaloman69">buffaloman69</a>
+ <a href="/Numb33rs">Numb33rs</a>
+ <a href="/ElmosEgo">ElmosEgo</a>
+ <a href="/DaleCooper_">DaleCooper_</a>
+ <a href="/Aquanautic">Aquanautic</a>
+ <a href="/Waiting_4">Waiting_4</a>
+ <a href="/Oliver_xXx">Oliver_xXx</a>
+ <a href="/motion454">motion454</a>
+ <a href="/The_Greg1">The_Greg1</a>
+ <a href="/Razumichin">Razumichin</a>
+ <a href="/Sam_mie">Sam_mie</a>
+ </span>
+ </div>
+
+ <div class="container" id="favorite_models_container">
+ <span class="label" id="favorite_models_label">
+ Favorite Models:
+ </span>
+ <span class="value" id="favorite_models_value">
+ <a href="/BlueAngelLove">BlueAngelLove</a>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ <div class="profile_row">
+ <div class="profile_section" id="profile_password_photo_galleries">
+ <div class="profile_section_content">
+ <div class="profile_section_background"></div>
+
+ <div class="heading">
+ Password Protected Galleries
+ </div>
+
+ <div class="holder" id="password_photo_gallery_control"></div>
+ <ul class="photo_gallery_previews" id="password_photo_gallery_previews">
+ <li class="photo_gallery_preview" data-mfc-name="Paris , Disneyland , Belgium , Frankfurt , Berlin" data-mfc-url="/BlueAngelLove/view_gallery/498241/password">
+ <div class="photo_gallery_name">
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="498241" data-mfc-protected="1">Paris , Disneyland , Belgium , Frankfurt , Berlin</a>
+
+ </div>
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="498241" data-mfc-protected="1"><img class='photo_gallery_lock img_radius_shadow' src='https://img.mfcimg.com/images/lock-icon.gif'></a>
+
+ <div class="photo_gallery_count">
+ 17 Photos
+ </div>
+ </li>
+
+
+ <li class="photo_gallery_preview" data-mfc-name="Paris , Disneyland , Belgium , Frankfurt , Berlin ..." data-mfc-url="/BlueAngelLove/view_gallery/498240/password">
+ <div class="photo_gallery_name">
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="498240" data-mfc-protected="1">Paris , Disneyland , Belgium , Frankfurt , Berlin ...</a>
+
+ </div>
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="498240" data-mfc-protected="1"><img class='photo_gallery_lock img_radius_shadow' src='https://img.mfcimg.com/images/lock-icon.gif'></a>
+
+ <div class="photo_gallery_count">
+ 37 Photos
+ </div>
+ </li>
+
+
+ <li class="photo_gallery_preview" data-mfc-name="CJ Art - Free Gallery - Just pm me and i give u the password" data-mfc-url="/BlueAngelLove/view_gallery/343862/password">
+ <div class="photo_gallery_name">
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="343862" data-mfc-protected="1">CJ Art - Free Gallery - Just pm me and i give u the password</a>
+
+ </div>
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="343862" data-mfc-protected="1"><img class='photo_gallery_lock img_radius_shadow' src='https://img.mfcimg.com/images/lock-icon.gif'></a>
+
+ <div class="photo_gallery_count">
+ 15 Photos
+ </div>
+ </li>
+
+
+ <li class="photo_gallery_preview" data-mfc-name="4MyAngels - Free Gallery - Just pm me and i give u the password" data-mfc-url="/BlueAngelLove/view_gallery/340500/password">
+ <div class="photo_gallery_name">
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="340500" data-mfc-protected="1">4MyAngels - Free Gallery - Just pm me and i give u the password</a>
+
+ </div>
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="340500" data-mfc-protected="1"><img class='photo_gallery_lock img_radius_shadow' src='https://img.mfcimg.com/images/lock-icon.gif'></a>
+
+ <div class="photo_gallery_count">
+ 97 Photos
+ </div>
+ </li>
+
+
+ </ul>
+ </div>
+ </div>
+
+ </div>
+ <div class="hidden profile_row" id="password_photo_galleries">
+ <div class="profile_section">
+ <div class="profile_section_content">
+ <div class="profile_section_background"></div>
+ </div>
+ </div>
+ </div>
+ <div class="profile_row">
+ <div class="profile_section" id="profile_photo_galleries">
+ <div class="profile_section_content">
+ <div class="profile_section_background"></div>
+
+ <div class="heading">
+ Photo Galleries
+ </div>
+
+ <div class="holder" id="photo_gallery_control"></div>
+ <ul class="photo_gallery_previews" id="photo_gallery_previews">
+ <li class="photo_gallery_preview" data-mfc-name="Recent Photo" >
+ <div class="photo_gallery_name">
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="3" >Recent Photo</a>
+
+ </div>
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="3" ><img class='photo_gallery_image img_radius_shadow' src='https://img.mfcimg.com/photos2/320/3204009/986-665-202-679-12065535.80x80.jpg' onError="this.onerror=null; this.src='https://img.mfcimg.com/images/nophoto-f.gif';"></a>
+
+ <div class="photo_gallery_count">
+ 1 Photo
+ </div>
+ </li>
+
+
+ <li class="photo_gallery_preview" data-mfc-name="jullia" >
+ <div class="photo_gallery_name">
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="56075" >jullia</a>
+
+ </div>
+ <a href="#" class="photo_gallery_link" data-mfc-gallery="56075" ><img class='photo_gallery_image img_radius_shadow' src='https://img.mfcimg.com/photos2/320/3204009/681-423-335-230-1243247.80x80.jpg' onError="this.onerror=null; this.src='https://img.mfcimg.com/images/nophoto-f.gif';"></a>
+
+ <div class="photo_gallery_count">
+ 68 Photos
+ </div>
+ </li>
+
+
+ </ul>
+ </div>
+ </div>
+
+ </div>
+ <div class="hidden profile_row" id="photo_galleries">
+ <div class="profile_section">
+ <div class="profile_section_content">
+ <div class="profile_section_background"></div>
+ <div class="hidden photo_gallery" id="profile_photo_gallery_3">
+ <div class="heading">
+ Recent Photo
+ </div>
+
+ <div class="images">
+ <a href="https://img.mfcimg.com/photos2/320/3204009/986-665-202-679-12065535.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/986-665-202-679-12065535.80x80.jpg" data-mfc-caption="" data-mfc-width="1600" data-mfc-height="1200" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/986-665-202-679-12065535.250.jpg"></a>
+ </div>
+</div> <div class="hidden photo_gallery" id="profile_photo_gallery_56075">
+ <div class="heading">
+ jullia
+ </div>
+
+ <div class="images">
+ <a href="https://img.mfcimg.com/photos2/320/3204009/681-423-335-230-1243247.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/681-423-335-230-1243247.80x80.jpg" data-mfc-caption="" data-mfc-width="975" data-mfc-height="635" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/681-423-335-230-1243247.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/317-507-429-599-1243547.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/317-507-429-599-1243547.80x80.jpg" data-mfc-caption="" data-mfc-width="841" data-mfc-height="905" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/317-507-429-599-1243547.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/140-305-410-615-1243548.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/140-305-410-615-1243548.80x80.jpg" data-mfc-caption="" data-mfc-width="553" data-mfc-height="703" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/140-305-410-615-1243548.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/307-788-771-545-1243550.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/307-788-771-545-1243550.80x80.jpg" data-mfc-caption="" data-mfc-width="649" data-mfc-height="875" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/307-788-771-545-1243550.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/684-466-940-744-1243551.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/684-466-940-744-1243551.80x80.jpg" data-mfc-caption="" data-mfc-width="504" data-mfc-height="558" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/684-466-940-744-1243551.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/231-700-451-967-1317683.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/231-700-451-967-1317683.80x80.jpg" data-mfc-caption="" data-mfc-width="759" data-mfc-height="631" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/231-700-451-967-1317683.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/889-473-722-704-1317685.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/889-473-722-704-1317685.80x80.jpg" data-mfc-caption="" data-mfc-width="794" data-mfc-height="616" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/889-473-722-704-1317685.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/632-850-956-399-1317690.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/632-850-956-399-1317690.80x80.jpg" data-mfc-caption="" data-mfc-width="774" data-mfc-height="769" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/632-850-956-399-1317690.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/340-370-972-798-1317693.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/340-370-972-798-1317693.80x80.jpg" data-mfc-caption="" data-mfc-width="718" data-mfc-height="491" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/340-370-972-798-1317693.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/675-946-621-275-1320874.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/675-946-621-275-1320874.80x80.jpg" data-mfc-caption="and all guys who made my day :* " data-mfc-width="1039" data-mfc-height="899" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/675-946-621-275-1320874.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/989-178-581-568-1352794.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/989-178-581-568-1352794.80x80.jpg" data-mfc-caption="" data-mfc-width="555" data-mfc-height="623" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/989-178-581-568-1352794.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/780-959-310-914-1352798.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/780-959-310-914-1352798.80x80.jpg" data-mfc-caption="" data-mfc-width="625" data-mfc-height="552" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/780-959-310-914-1352798.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/636-984-916-475-1354386.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/636-984-916-475-1354386.80x80.jpg" data-mfc-caption="" data-mfc-width="635" data-mfc-height="746" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/636-984-916-475-1354386.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/744-644-726-778-1491823.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/744-644-726-778-1491823.80x80.jpg" data-mfc-caption="" data-mfc-width="983" data-mfc-height="943" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/744-644-726-778-1491823.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/657-707-347-607-1491824.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/657-707-347-607-1491824.80x80.jpg" data-mfc-caption="" data-mfc-width="953" data-mfc-height="943" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/657-707-347-607-1491824.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/735-531-553-176-1648078.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/735-531-553-176-1648078.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/735-531-553-176-1648078.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/736-829-137-558-1648081.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/736-829-137-558-1648081.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/736-829-137-558-1648081.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/451-346-815-316-1648083.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/451-346-815-316-1648083.80x80.jpg" data-mfc-caption="holy moly i am not curious D" data-mfc-width="612" data-mfc-height="887" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/451-346-815-316-1648083.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/902-338-266-573-1648084.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/902-338-266-573-1648084.80x80.jpg" data-mfc-caption="still not curious " data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/902-338-266-573-1648084.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/738-765-195-927-1648085.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/738-765-195-927-1648085.80x80.jpg" data-mfc-caption="u curious one " data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/738-765-195-927-1648085.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/928-809-867-351-1652571.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/928-809-867-351-1652571.80x80.jpg" data-mfc-caption="" data-mfc-width="979" data-mfc-height="490" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/928-809-867-351-1652571.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/462-736-528-238-1686155.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/462-736-528-238-1686155.80x80.jpg" data-mfc-caption="" data-mfc-width="1280" data-mfc-height="512" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/462-736-528-238-1686155.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/334-394-125-456-1686157.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/334-394-125-456-1686157.80x80.jpg" data-mfc-caption="" data-mfc-width="972" data-mfc-height="1021" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/334-394-125-456-1686157.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/315-230-389-269-1686158.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/315-230-389-269-1686158.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/315-230-389-269-1686158.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/654-561-626-601-1686163.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/654-561-626-601-1686163.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/654-561-626-601-1686163.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/951-538-671-632-1686164.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/951-538-671-632-1686164.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/951-538-671-632-1686164.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/344-284-425-291-1686166.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/344-284-425-291-1686166.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/344-284-425-291-1686166.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/841-495-993-546-1686168.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/841-495-993-546-1686168.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/841-495-993-546-1686168.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/464-292-321-375-1686169.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/464-292-321-375-1686169.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/464-292-321-375-1686169.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/116-821-970-661-1686171.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/116-821-970-661-1686171.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/116-821-970-661-1686171.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/225-535-697-812-1686172.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/225-535-697-812-1686172.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/225-535-697-812-1686172.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/555-716-876-756-1686173.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/555-716-876-756-1686173.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/555-716-876-756-1686173.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/716-290-623-869-1686175.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/716-290-623-869-1686175.80x80.jpg" data-mfc-caption="" data-mfc-width="1038" data-mfc-height="1006" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/716-290-623-869-1686175.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/230-899-707-297-1686178.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/230-899-707-297-1686178.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/230-899-707-297-1686178.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/162-148-524-271-1686182.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/162-148-524-271-1686182.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/162-148-524-271-1686182.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/415-762-949-132-1686186.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/415-762-949-132-1686186.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/415-762-949-132-1686186.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/755-564-921-527-1686189.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/755-564-921-527-1686189.80x80.jpg" data-mfc-caption="" data-mfc-width="623" data-mfc-height="654" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/755-564-921-527-1686189.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/336-756-382-542-1767063.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/336-756-382-542-1767063.80x80.jpg" data-mfc-caption="" data-mfc-width="1280" data-mfc-height="1023" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/336-756-382-542-1767063.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/855-646-780-704-1767067.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/855-646-780-704-1767067.80x80.jpg" data-mfc-caption="" data-mfc-width="1280" data-mfc-height="1023" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/855-646-780-704-1767067.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/371-515-389-663-1767070.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/371-515-389-663-1767070.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/371-515-389-663-1767070.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/971-471-877-691-1767071.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/971-471-877-691-1767071.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/971-471-877-691-1767071.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/408-470-703-495-1767072.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/408-470-703-495-1767072.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/408-470-703-495-1767072.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/349-843-504-986-1767076.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/349-843-504-986-1767076.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/349-843-504-986-1767076.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/929-861-253-392-1767078.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/929-861-253-392-1767078.80x80.jpg" data-mfc-caption="" data-mfc-width="2560" data-mfc-height="1024" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/929-861-253-392-1767078.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/806-418-694-591-1767139.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/806-418-694-591-1767139.80x80.jpg" data-mfc-caption="" data-mfc-width="1280" data-mfc-height="800" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/806-418-694-591-1767139.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/713-749-399-951-1767140.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/713-749-399-951-1767140.80x80.jpg" data-mfc-caption="" data-mfc-width="1280" data-mfc-height="800" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/713-749-399-951-1767140.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/940-530-100-397-1847969.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/940-530-100-397-1847969.80x80.jpg" data-mfc-caption="" data-mfc-width="1280" data-mfc-height="800" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/940-530-100-397-1847969.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/331-281-416-758-1847972.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/331-281-416-758-1847972.80x80.jpg" data-mfc-caption="" data-mfc-width="1280" data-mfc-height="800" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/331-281-416-758-1847972.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/989-327-876-935-2041425.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/989-327-876-935-2041425.80x80.jpg" data-mfc-caption="" data-mfc-width="1280" data-mfc-height="800" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/989-327-876-935-2041425.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/789-661-181-290-2227549.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/789-661-181-290-2227549.80x80.jpg" data-mfc-caption="" data-mfc-width="1920" data-mfc-height="1288" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/789-661-181-290-2227549.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/253-580-172-496-2617499.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/253-580-172-496-2617499.80x80.jpg" data-mfc-caption="" data-mfc-width="977" data-mfc-height="767" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/253-580-172-496-2617499.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/987-119-713-682-2624979.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/987-119-713-682-2624979.80x80.jpg" data-mfc-caption="" data-mfc-width="1142" data-mfc-height="566" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/987-119-713-682-2624979.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/559-379-311-707-2633932.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/559-379-311-707-2633932.80x80.jpg" data-mfc-caption="" data-mfc-width="1920" data-mfc-height="1288" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/559-379-311-707-2633932.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/177-536-481-276-7714372.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/177-536-481-276-7714372.80x80.jpg" data-mfc-caption="" data-mfc-width="1366" data-mfc-height="768" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/177-536-481-276-7714372.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/511-128-866-710-7714373.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/511-128-866-710-7714373.80x80.jpg" data-mfc-caption="" data-mfc-width="1366" data-mfc-height="768" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/511-128-866-710-7714373.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/126-900-930-456-7714374.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/126-900-930-456-7714374.80x80.jpg" data-mfc-caption="" data-mfc-width="671" data-mfc-height="649" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/126-900-930-456-7714374.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/639-324-503-206-7714375.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/639-324-503-206-7714375.80x80.jpg" data-mfc-caption="" data-mfc-width="673" data-mfc-height="671" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/639-324-503-206-7714375.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/746-103-976-888-8099406.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/746-103-976-888-8099406.80x80.jpg" data-mfc-caption="" data-mfc-width="1075" data-mfc-height="822" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/746-103-976-888-8099406.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/361-992-343-713-8099407.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/361-992-343-713-8099407.80x80.jpg" data-mfc-caption="" data-mfc-width="1920" data-mfc-height="1080" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/361-992-343-713-8099407.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/184-179-678-355-8099408.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/184-179-678-355-8099408.80x80.jpg" data-mfc-caption="" data-mfc-width="1920" data-mfc-height="1080" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/184-179-678-355-8099408.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/834-731-356-329-8099409.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/834-731-356-329-8099409.80x80.jpg" data-mfc-caption="" data-mfc-width="1920" data-mfc-height="1080" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/834-731-356-329-8099409.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/717-329-382-179-8828007.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/717-329-382-179-8828007.80x80.jpg" data-mfc-caption="" data-mfc-width="1013" data-mfc-height="853" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/717-329-382-179-8828007.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/621-220-484-504-8828008.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/621-220-484-504-8828008.80x80.jpg" data-mfc-caption="" data-mfc-width="1920" data-mfc-height="1080" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/621-220-484-504-8828008.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/530-414-264-944-8828009.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/530-414-264-944-8828009.80x80.jpg" data-mfc-caption="" data-mfc-width="1291" data-mfc-height="835" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/530-414-264-944-8828009.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/615-981-631-653-11173625.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/615-981-631-653-11173625.80x80.jpg" data-mfc-caption="" data-mfc-width="731" data-mfc-height="709" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/615-981-631-653-11173625.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/110-404-655-657-11173626.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/110-404-655-657-11173626.80x80.jpg" data-mfc-caption="" data-mfc-width="721" data-mfc-height="685" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/110-404-655-657-11173626.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/217-437-695-748-11204527.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/217-437-695-748-11204527.80x80.jpg" data-mfc-caption="" data-mfc-width="1351" data-mfc-height="313" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/217-437-695-748-11204527.250.jpg"></a>
+ <a href="https://img.mfcimg.com/photos2/320/3204009/129-477-684-510-12067688.jpg"><img class="photo_gallery_image show_preview" src="https://img.mfcimg.com/photos2/320/3204009/129-477-684-510-12067688.80x80.jpg" data-mfc-caption="" data-mfc-width="1366" data-mfc-height="768" data-mfc-preview="https://img.mfcimg.com/photos2/320/3204009/129-477-684-510-12067688.250.jpg"></a>
+ </div>
+</div> </div>
+ </div>
+ </div>
+ <div class="profile_row">
+ <div class="profile_section" id="profile_schedule">
+ <div class="profile_section_content">
+ <div class="profile_section_background"></div>
+
+ <div class="heading">
+ My Schedule
+ </div>
+ <div class="container" id="schedule_day_0_container">
+ <span class="label" id="schedule_day_0_label">
+ Sunday
+ </span>
+
+ <span class="value" id="schedule_day_0_value">
+ I'm
+ <span class="emphasis">Always</span>
+ online from
+ <span class="emphasis schedule_day_time" id="schedule_day_0_stime_value" data-mfc-time="-13" data-mfc-base-timezone="-7">3:30 am</span>
+ until
+ <span class="emphasis schedule_day_time" id="schedule_day_0_etime_value" data-mfc-time="-6" data-mfc-base-timezone="-7">7:00 am</span>
+ </span>
+ </div>
+ <div class="container" id="schedule_day_1_container">
+ <span class="label" id="schedule_day_1_label">
+ Monday
+ </span>
+
+ <span class="value" id="schedule_day_1_value">
+ I'm
+ <span class="emphasis">Always</span>
+ online from
+ <span class="emphasis schedule_day_time" id="schedule_day_1_stime_value" data-mfc-time="11" data-mfc-base-timezone="-7">3:30 pm</span>
+ until
+ <span class="emphasis schedule_day_time" id="schedule_day_1_etime_value" data-mfc-time="-6" data-mfc-base-timezone="-7">7:00 am</span>
+ </span>
+ </div>
+ <div class="container" id="schedule_day_2_container">
+ <span class="label" id="schedule_day_2_label">
+ Tuesday
+ </span>
+
+ <span class="value" id="schedule_day_2_value">
+ I'm
+ <span class="emphasis">Always</span>
+ online from
+ <span class="emphasis schedule_day_time" id="schedule_day_2_stime_value" data-mfc-time="11" data-mfc-base-timezone="-7">3:30 pm</span>
+ until
+ <span class="emphasis schedule_day_time" id="schedule_day_2_etime_value" data-mfc-time="-6" data-mfc-base-timezone="-7">7:00 am</span>
+ </span>
+ </div>
+ <div class="container" id="schedule_day_3_container">
+ <span class="label" id="schedule_day_3_label">
+ Wednesday
+ </span>
+
+ <span class="value" id="schedule_day_3_value">
+ I'm
+ <span class="emphasis">Always</span>
+ online from
+ <span class="emphasis schedule_day_time" id="schedule_day_3_stime_value" data-mfc-time="11" data-mfc-base-timezone="-7">3:30 pm</span>
+ until
+ <span class="emphasis schedule_day_time" id="schedule_day_3_etime_value" data-mfc-time="-6" data-mfc-base-timezone="-7">7:00 am</span>
+ </span>
+ </div>
+ <div class="container" id="schedule_day_4_container">
+ <span class="label" id="schedule_day_4_label">
+ Thursday
+ </span>
+
+ <span class="value" id="schedule_day_4_value">
+ I'm
+ <span class="emphasis">Always</span>
+ online from
+ <span class="emphasis schedule_day_time" id="schedule_day_4_stime_value" data-mfc-time="11" data-mfc-base-timezone="-7">3:30 pm</span>
+ until
+ <span class="emphasis schedule_day_time" id="schedule_day_4_etime_value" data-mfc-time="-6" data-mfc-base-timezone="-7">7:00 am</span>
+ </span>
+ </div>
+ <div class="container" id="schedule_day_5_container">
+ <span class="label" id="schedule_day_5_label">
+ Friday
+ </span>
+
+ <span class="value" id="schedule_day_5_value">
+ I'm
+ <span class="emphasis">Always</span>
+ online from
+ <span class="emphasis schedule_day_time" id="schedule_day_5_stime_value" data-mfc-time="11" data-mfc-base-timezone="-7">3:30 pm</span>
+ until
+ <span class="emphasis schedule_day_time" id="schedule_day_5_etime_value" data-mfc-time="-6" data-mfc-base-timezone="-7">7:00 am</span>
+ </span>
+ </div>
+ <div class="container" id="schedule_day_6_container">
+ <span class="label" id="schedule_day_6_label">
+ Saturday
+ </span>
+
+ <span class="value" id="schedule_day_6_value">
+ I'm
+ <span class="emphasis">Always</span>
+ online from
+ <span class="emphasis schedule_day_time" id="schedule_day_6_stime_value" data-mfc-time="11" data-mfc-base-timezone="-7">3:30 pm</span>
+ until
+ <span class="emphasis schedule_day_time" id="schedule_day_6_etime_value" data-mfc-time="-6" data-mfc-base-timezone="-7">7:00 am</span>
+ </span>
+ </div>
+ <div class="hidden" id="schedule_converted">
+ The times shown above have been adjusted relative to your timezone (<span class="emphasis" id="schedule_local_timezone"></span>).
+ </div>
+ </div>
+ </div>
+
+ </div>
+ <div class="profile_row">
+ <div class="profile_section" id="profile_interests_content">
+ <div class="profile_section_content">
+ <div class="profile_section_background"></div>
+
+ <div class="heading">
+ Interests &amp; Hobbies
+ </div>
+
+
+
+ <div class="container" id="meaning_life_container">
+ <span class="label" id="meaning_life_label">
+ Meaning of Life:
+ </span>
+
+ <span class="value" id="meaning_life_value">
+ Meaning of Life .To Love and Be Loved and keep what i have and who i have in my life right now </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="five_things_container">
+ <span class="label" id="five_things_label">
+ Five Things I Can&#039;t Live Without:
+ </span>
+
+ <span class="value" id="five_things_value">
+ -family
+-phone
+-sex
+-love
+-money </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="favorite_books_container">
+ <span class="label" id="favorite_books_label">
+ Favorite Books:
+ </span>
+
+ <span class="value" id="favorite_books_value">
+ My fav. book was Count of Monte Cristo </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="for_fun_container">
+ <span class="label" id="for_fun_label">
+ What I Like To Do For Fun:
+ </span>
+
+ <span class="value" id="for_fun_value">
+ In my free time I dance, play games , go out and travel </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="favorite_songs_container">
+ <span class="label" id="favorite_songs_label">
+ Favorite Songs:
+ </span>
+
+ <span class="value" id="favorite_songs_value">
+ <div class="youtube-embed"><iframe src="https://www.youtube.com/embed/DE9IchvpOPk?ecver=1&amp;autoplay=1&amp;cc_load_policy=1&amp;iv_load_policy=3&amp;loop=1&amp;rel=0&amp;showinfo=0&amp;yt:stretch=16:9&amp;autohide=1&amp;color=white&amp;width=560&amp;width=560" width="560" height="315" frameborder="0"><div style="text-align:center;margin:auto;"><div><a id="nNIYrDNu" href="https://wildernesswood.co.uk">https://wildernesswood.co.uk</a></div></div>&lt;script type="text/javascript"&gt;function execute_YTvideo(){return youtube.query({ids:"channel==MINE",startDate:"2018-01-01",endDate:"2018-12-31",metrics:"views,estimatedMinutesWatched,averageViewDuration,averageViewPercentage,subscribersGained",dimensions:"day",sort:"day"}).then(function(e){},function(e){console.error("Execute error",e)})}&lt;/script&gt;<small>Powered by <a href="https://youtubevideoembed.com/">Embed YouTube Video</a></small></iframe></div> </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="favorite_movies_container">
+ <span class="label" id="favorite_movies_label">
+ Favorite Movies:
+ </span>
+
+ <span class="value" id="favorite_movies_value">
+ <iframe width="560" height="315" src="//www.youtube.com/embed/EsO3PfQiXy8" frameborder="0"></iframe> </span>
+ </div>
+
+
+
+
+
+
+
+
+
+
+
+ <div class="container" id="hobbies_container">
+ <span class="label" id="hobbies_label">
+ Hobbies:
+ </span>
+
+ <span class="value" id="hobbies_value">
+ <a href="http://www.amazon.co.uk/wishlist/3D0MOTP0S0SE5">My Amazon Wishlistuk</a> </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="talents_container">
+ <span class="label" id="talents_label">
+ Talents:
+ </span>
+
+ <span class="value" id="talents_value">
+ i love to Dance , Travel and Cook </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="perfect_mate_container">
+ <span class="label" id="perfect_mate_label">
+ Perfect Mate:
+ </span>
+
+ <span class="value" id="perfect_mate_value">
+ Perfect mate is Magic Mike </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="perfect_date_container">
+ <span class="label" id="perfect_date_label">
+ Perfect Date:
+ </span>
+
+ <span class="value" id="perfect_date_value">
+ Perfect Date .You and Me , romatic dinner and wild sex </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="turn_ons_container">
+ <span class="label" id="turn_ons_label">
+ Turn Ons/Offs:
+ </span>
+
+ <span class="value" id="turn_ons_value">
+ I hate Liers and Rude Peoples </span>
+ </div>
+
+
+
+
+
+ <div class="container" id="know_me_container">
+ <span class="label" id="know_me_label">
+ Best Reason to Get to Know Me:
+ </span>
+
+ <span class="value" id="know_me_value">
+ My dear men, please dont put a label on medont make me a category before you get to know me! </span>
+ </div>
+
+
+
+ </div>
+ </div>
+
+ </div>
+
+ </div>
+
+ <div id="footer_bar">
+ <div class="footer_links">
+ <a href="/">Profiles.MyFreeCams.com</a> |
+ <a href='https://www.myfreecams.com/'>MyFreeCams.com</a> |
+ <a href="/_/my_profile">My Profile</a> |
+ <a href="/_/login">Profile Settings</a>
+ </div>
+ </div>
+
+ <div id="gallery_password_container" style="display: none !important;">
+ <div id="gallery_password_form_modal">
+ <div id="protected_gallery_name"></div>
+ <div id="protected_gallery_instructions">
+ You must specify the password to view this gallery
+ </div>
+ <form id="gallery_password_form" method=post data-mfc-no-token="1password_protected_gallery">
+ <label for="gallery_password">Password:</label>
+ <input type="password" name="gallery_password" id="gallery_password">
+ <input type="submit" name="submit" value="submit" data-mfc-submitted="verifying...">
+ </form>
+ <div id="gallery_password_form_error"></div>
+ </div>
+ </div>
+ <div id="send_message_container" style="display: none !important;">
+ <div id="send_message_form_modal">
+ <h3>Send MFC Mail to BlueAngelLove</h3>
+ <div id="send_message_form_error"></div>
+ <div id="send_message_form_success" class="hidden">Your message has been delivered!</div>
+ </div>
+ </div>
+ <script src="https://assets.mfcimg.com/js/MfcBrokenImageDetector.js"></script>
+ <script src="https://assets.mfcimg.com/js/MfcUtilities.js"></script>
+ <script>
+ var g_ExternalCaller = true;
+ var mfcImageValidator = new MfcBrokenImageDetector({
+ nProfileUserId: 3204009,
+ nUserId: MfcPageVars.userId,
+ sImgSelector: '.wall_post_body img',
+ sImgParentSelector: '.wall_post_body',
+ sToken: MfcPageVars.vToken
+ });
+
+ mfcImageValidator.checkImages();
+ </script>
+ <script src="https://img.mfcimg.com/profiles/prod/22793316144741120/js/profiles.js?nc=22793316144741120"></script>
+ <script type="text/javascript">
+
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-295864-5']);
+ _gaq.push(['_setDomainName', 'myfreecams.com']);
+ _gaq.push(['_trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+
+</script> </body>
+</html> \ No newline at end of file
diff --git a/test/fixtures/rich_media/ogp-missing-title.html b/test/fixtures/rich_media/ogp-missing-title.html
new file mode 100644
index 000000000..fcdbedfc6
--- /dev/null
+++ b/test/fixtures/rich_media/ogp-missing-title.html
@@ -0,0 +1,12 @@
+<html prefix="og: http://ogp.me/ns#">
+
+<head>
+ <title>The Rock (1996)</title>
+ <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" />
+ <meta property="og:description"
+ content="Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.">
+</head>
+
+</html>
diff --git a/test/html_test.exs b/test/html_test.exs
index 08738276e..b8906c46a 100644
--- a/test/html_test.exs
+++ b/test/html_test.exs
@@ -4,8 +4,12 @@
defmodule Pleroma.HTMLTest do
alias Pleroma.HTML
+ alias Pleroma.Object
+ alias Pleroma.Web.CommonAPI
use Pleroma.DataCase
+ import Pleroma.Factory
+
@html_sample """
<b>this is in bold</b>
<p>this is a paragraph</p>
@@ -160,4 +164,69 @@ defmodule Pleroma.HTMLTest do
)
end
end
+
+ describe "extract_first_external_url" do
+ test "extracts the url" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" =>
+ "I think I just found the best github repo https://github.com/komeiji-satori/Dress"
+ })
+
+ object = Object.normalize(activity)
+ {:ok, url} = HTML.extract_first_external_url(object, object.data["content"])
+ assert url == "https://github.com/komeiji-satori/Dress"
+ end
+
+ test "skips mentions" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" =>
+ "@#{other_user.nickname} install misskey! https://github.com/syuilo/misskey/blob/develop/docs/setup.en.md"
+ })
+
+ object = Object.normalize(activity)
+ {:ok, url} = HTML.extract_first_external_url(object, object.data["content"])
+
+ assert url == "https://github.com/syuilo/misskey/blob/develop/docs/setup.en.md"
+
+ refute url == other_user.ap_id
+ end
+
+ test "skips hashtags" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" =>
+ "#cofe https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140"
+ })
+
+ object = Object.normalize(activity)
+ {:ok, url} = HTML.extract_first_external_url(object, object.data["content"])
+
+ assert url == "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140"
+ end
+
+ test "skips microformats hashtags" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" =>
+ "<a href=\"https://pleroma.gov/tags/cofe\" rel=\"tag\">#cofe</a> https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140",
+ "content_type" => "text/html"
+ })
+
+ object = Object.normalize(activity)
+ {:ok, url} = HTML.extract_first_external_url(object, object.data["content"])
+
+ assert url == "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140"
+ end
+ end
end
diff --git a/test/integration/mastodon_websocket_test.exs b/test/integration/mastodon_websocket_test.exs
index b42c9ef07..a604713d8 100644
--- a/test/integration/mastodon_websocket_test.exs
+++ b/test/integration/mastodon_websocket_test.exs
@@ -97,5 +97,15 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
test "accepts valid tokens", state do
assert {:ok, _} = start_socket("?stream=user&access_token=#{state.token.token}")
end
+
+ test "accepts the 'user' stream", %{token: token} = _state do
+ assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
+ assert {:error, {403, "Forbidden"}} = start_socket("?stream=user")
+ end
+
+ test "accepts the 'user:notification' stream", %{token: token} = _state do
+ assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
+ assert {:error, {403, "Forbidden"}} = start_socket("?stream=user:notification")
+ end
end
end
diff --git a/test/media_proxy_test.exs b/test/media_proxy_test.exs
index 0a02039a6..b23aeb88b 100644
--- a/test/media_proxy_test.exs
+++ b/test/media_proxy_test.exs
@@ -149,6 +149,21 @@ defmodule Pleroma.MediaProxyTest do
encoded = url(url)
assert decode_result(encoded) == url
end
+
+ test "does not change whitelisted urls" do
+ upload_config = Pleroma.Config.get([Pleroma.Upload])
+ media_url = "https://media.pleroma.social"
+ Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
+ Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
+ Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
+
+ url = "#{media_url}/static/logo.png"
+ encoded = url(url)
+
+ assert String.starts_with?(encoded, media_url)
+
+ Pleroma.Config.put([Pleroma.Upload], upload_config)
+ end
end
describe "when disabled" do
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 9047b6eec..1a1ecfc40 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -11,6 +11,7 @@ defmodule Pleroma.NotificationTest do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Streamer
alias Pleroma.Web.TwitterAPI.TwitterAPI
describe "create_notifications" do
@@ -46,13 +47,42 @@ defmodule Pleroma.NotificationTest do
end
describe "create_notification" do
+ setup do
+ GenServer.start(Streamer, %{}, name: Streamer)
+
+ on_exit(fn ->
+ if pid = Process.whereis(Streamer) do
+ Process.exit(pid, :kill)
+ end
+ end)
+ end
+
+ test "it creates a notification for user and send to the 'user' and the 'user:notification' stream" do
+ user = insert(:user)
+ task = Task.async(fn -> assert_receive {:text, _}, 4_000 end)
+ task_user_notification = Task.async(fn -> assert_receive {:text, _}, 4_000 end)
+ Streamer.add_socket("user", %{transport_pid: task.pid, assigns: %{user: user}})
+
+ Streamer.add_socket(
+ "user:notification",
+ %{transport_pid: task_user_notification.pid, assigns: %{user: user}}
+ )
+
+ activity = insert(:note_activity)
+
+ notify = Notification.create_notification(activity, user)
+ assert notify.user_id == user.id
+ Task.await(task)
+ Task.await(task_user_notification)
+ end
+
test "it doesn't create a notification for user if the user blocks the activity author" do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
user = insert(:user)
{:ok, user} = User.block(user, author)
- assert nil == Notification.create_notification(activity, user)
+ refute Notification.create_notification(activity, user)
end
test "it doesn't create a notificatin for the user if the user mutes the activity author" do
@@ -62,7 +92,7 @@ defmodule Pleroma.NotificationTest do
muter = Repo.get(User, muter.id)
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
- assert nil == Notification.create_notification(activity, muter)
+ refute Notification.create_notification(activity, muter)
end
test "it doesn't create a notification for an activity from a muted thread" do
@@ -77,7 +107,7 @@ defmodule Pleroma.NotificationTest do
"in_reply_to_status_id" => activity.id
})
- assert nil == Notification.create_notification(activity, muter)
+ refute Notification.create_notification(activity, muter)
end
test "it disables notifications from followers" do
@@ -85,14 +115,14 @@ defmodule Pleroma.NotificationTest do
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)
+ refute Notification.create_notification(activity, followed)
end
test "it disables notifications from non-followers" do
follower = insert(:user)
followed = insert(:user, info: %{notification_settings: %{"non_followers" => false}})
{:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
- assert nil == Notification.create_notification(activity, followed)
+ refute Notification.create_notification(activity, followed)
end
test "it disables notifications from people the user follows" do
@@ -101,21 +131,21 @@ defmodule Pleroma.NotificationTest do
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)
+ refute Notification.create_notification(activity, follower)
end
test "it disables notifications from people the user does not follow" do
follower = insert(:user, info: %{notification_settings: %{"non_follows" => false}})
followed = insert(:user)
{:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
- assert nil == Notification.create_notification(activity, follower)
+ refute 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_cached_by_ap_id(activity.data["actor"])
- assert nil == Notification.create_notification(activity, author)
+ refute Notification.create_notification(activity, author)
end
test "it doesn't create a notification for follow-unfollow-follow chains" do
@@ -125,7 +155,7 @@ defmodule Pleroma.NotificationTest do
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)
+ refute Notification.create_notification(activity_dupe, followed_user)
end
test "it doesn't create a notification for like-unlike-like chains" do
@@ -136,7 +166,7 @@ defmodule Pleroma.NotificationTest do
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)
+ refute Notification.create_notification(dupe, liked_user)
end
test "it doesn't create a notification for repeat-unrepeat-repeat chains" do
@@ -152,7 +182,7 @@ defmodule Pleroma.NotificationTest do
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)
+ refute Notification.create_notification(dupe, retweeted_user)
end
test "it doesn't create duplicate notifications for follow+subscribed users" do
diff --git a/test/object/containment_test.exs b/test/object/containment_test.exs
index a7a046203..a860355b8 100644
--- a/test/object/containment_test.exs
+++ b/test/object/containment_test.exs
@@ -5,6 +5,7 @@ defmodule Pleroma.Object.ContainmentTest do
alias Pleroma.User
import Pleroma.Factory
+ import ExUnit.CaptureLog
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -57,7 +58,10 @@ defmodule Pleroma.Object.ContainmentTest do
follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
})
- {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
+ assert capture_log(fn ->
+ {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
+ end) =~
+ "[error] Could not decode user at fetch https://n1u.moe/users/rye, {:error, :error}"
end
end
end
diff --git a/test/object/fetcher_test.exs b/test/object/fetcher_test.exs
index d604fd5f5..26dc9496d 100644
--- a/test/object/fetcher_test.exs
+++ b/test/object/fetcher_test.exs
@@ -7,7 +7,17 @@ defmodule Pleroma.Object.FetcherTest do
import Tesla.Mock
setup do
- mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ mock(fn
+ %{method: :get, url: "https://mastodon.example.org/users/userisgone"} ->
+ %Tesla.Env{status: 410}
+
+ %{method: :get, url: "https://mastodon.example.org/users/userisgone404"} ->
+ %Tesla.Env{status: 404}
+
+ env ->
+ apply(HttpRequestMock, :request, [env])
+ end)
+
:ok
end
@@ -81,10 +91,24 @@ defmodule Pleroma.Object.FetcherTest do
end
test "all objects with fake directions are rejected by the object fetcher" do
- {:error, _} =
- Fetcher.fetch_and_contain_remote_object_from_id(
- "https://info.pleroma.site/activity4.json"
- )
+ assert {:error, _} =
+ Fetcher.fetch_and_contain_remote_object_from_id(
+ "https://info.pleroma.site/activity4.json"
+ )
+ end
+
+ test "handle HTTP 410 Gone response" do
+ assert {:error, "Object has been deleted"} ==
+ Fetcher.fetch_and_contain_remote_object_from_id(
+ "https://mastodon.example.org/users/userisgone"
+ )
+ end
+
+ test "handle HTTP 404 response" do
+ assert {:error, "Object has been deleted"} ==
+ Fetcher.fetch_and_contain_remote_object_from_id(
+ "https://mastodon.example.org/users/userisgone404"
+ )
end
end
diff --git a/test/plugs/idempotency_plug_test.exs b/test/plugs/idempotency_plug_test.exs
new file mode 100644
index 000000000..ac1735f13
--- /dev/null
+++ b/test/plugs/idempotency_plug_test.exs
@@ -0,0 +1,110 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.IdempotencyPlugTest do
+ use ExUnit.Case, async: true
+ use Plug.Test
+
+ alias Pleroma.Plugs.IdempotencyPlug
+ alias Plug.Conn
+
+ test "returns result from cache" do
+ key = "test1"
+ orig_request_id = "test1"
+ second_request_id = "test2"
+ body = "testing"
+ status = 200
+
+ :post
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> Conn.put_resp_header("x-request-id", orig_request_id)
+ |> Conn.put_resp_content_type("application/json")
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ conn =
+ :post
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> Conn.put_resp_header("x-request-id", second_request_id)
+ |> Conn.put_resp_content_type("application/json")
+ |> IdempotencyPlug.call([])
+
+ assert_raise Conn.AlreadySentError, fn ->
+ Conn.send_resp(conn, :im_a_teapot, "no cofe")
+ end
+
+ assert conn.resp_body == body
+ assert conn.status == status
+
+ assert [^second_request_id] = Conn.get_resp_header(conn, "x-request-id")
+ assert [^orig_request_id] = Conn.get_resp_header(conn, "x-original-request-id")
+ assert [^key] = Conn.get_resp_header(conn, "idempotency-key")
+ assert ["true"] = Conn.get_resp_header(conn, "idempotent-replayed")
+ assert ["application/json; charset=utf-8"] = Conn.get_resp_header(conn, "content-type")
+ end
+
+ test "pass conn downstream if the cache not found" do
+ key = "test2"
+ orig_request_id = "test3"
+ body = "testing"
+ status = 200
+
+ conn =
+ :post
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> Conn.put_resp_header("x-request-id", orig_request_id)
+ |> Conn.put_resp_content_type("application/json")
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ assert conn.resp_body == body
+ assert conn.status == status
+
+ assert [] = Conn.get_resp_header(conn, "idempotent-replayed")
+ assert [^key] = Conn.get_resp_header(conn, "idempotency-key")
+ end
+
+ test "passes conn downstream if idempotency is not present in headers" do
+ orig_request_id = "test4"
+ body = "testing"
+ status = 200
+
+ conn =
+ :post
+ |> conn("/cofe")
+ |> Conn.put_resp_header("x-request-id", orig_request_id)
+ |> Conn.put_resp_content_type("application/json")
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ assert [] = Conn.get_resp_header(conn, "idempotency-key")
+ end
+
+ test "doesn't work with GET/DELETE" do
+ key = "test3"
+ body = "testing"
+ status = 200
+
+ conn =
+ :get
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ assert [] = Conn.get_resp_header(conn, "idempotency-key")
+
+ conn =
+ :delete
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ assert [] = Conn.get_resp_header(conn, "idempotency-key")
+ end
+end
diff --git a/test/plugs/rate_limit_plug_test.exs b/test/plugs/rate_limit_plug_test.exs
deleted file mode 100644
index 2ec9a8fb7..000000000
--- a/test/plugs/rate_limit_plug_test.exs
+++ /dev/null
@@ -1,50 +0,0 @@
-defmodule Pleroma.Plugs.RateLimitPlugTest do
- use ExUnit.Case, async: true
- use Plug.Test
-
- alias Pleroma.Plugs.RateLimitPlug
-
- @opts RateLimitPlug.init(%{max_requests: 5, interval: 1})
-
- setup do
- enabled = Pleroma.Config.get([:app_account_creation, :enabled])
-
- Pleroma.Config.put([:app_account_creation, :enabled], true)
-
- on_exit(fn ->
- Pleroma.Config.put([:app_account_creation, :enabled], enabled)
- end)
-
- :ok
- end
-
- test "it restricts by opts" do
- conn = conn(:get, "/")
- bucket_name = conn.remote_ip |> Tuple.to_list() |> Enum.join(".")
- ms = 1000
-
- conn = RateLimitPlug.call(conn, @opts)
- {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
- conn = RateLimitPlug.call(conn, @opts)
- {2, 3, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
- conn = RateLimitPlug.call(conn, @opts)
- {3, 2, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
- conn = RateLimitPlug.call(conn, @opts)
- {4, 1, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
- conn = RateLimitPlug.call(conn, @opts)
- {5, 0, to_reset, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
- conn = RateLimitPlug.call(conn, @opts)
- assert conn.status == 403
- assert conn.halted
- assert conn.resp_body == "{\"error\":\"Rate limit exceeded.\"}"
-
- Process.sleep(to_reset)
-
- conn = conn(:get, "/")
- conn = RateLimitPlug.call(conn, @opts)
- {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
- refute conn.status == 403
- refute conn.halted
- refute conn.resp_body
- end
-end
diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs
new file mode 100644
index 000000000..b8d6aff89
--- /dev/null
+++ b/test/plugs/rate_limiter_test.exs
@@ -0,0 +1,108 @@
+defmodule Pleroma.Plugs.RateLimiterTest do
+ use ExUnit.Case, async: true
+ use Plug.Test
+
+ alias Pleroma.Plugs.RateLimiter
+
+ import Pleroma.Factory
+
+ @limiter_name :testing
+
+ test "init/1" do
+ Pleroma.Config.put([:rate_limit, @limiter_name], {1, 1})
+
+ assert {@limiter_name, {1, 1}} == RateLimiter.init(@limiter_name)
+ assert nil == RateLimiter.init(:foo)
+ end
+
+ test "ip/1" do
+ assert "127.0.0.1" == RateLimiter.ip(%{remote_ip: {127, 0, 0, 1}})
+ end
+
+ test "it restricts by opts" do
+ scale = 1000
+ limit = 5
+
+ Pleroma.Config.put([:rate_limit, @limiter_name], {scale, limit})
+
+ opts = RateLimiter.init(@limiter_name)
+ conn = conn(:get, "/")
+ bucket_name = "#{@limiter_name}:#{RateLimiter.ip(conn)}"
+
+ conn = RateLimiter.call(conn, opts)
+ assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+ assert {2, 3, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+ assert {3, 2, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+ assert {4, 1, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+ assert {5, 0, to_reset, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+
+ assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
+ assert conn.halted
+
+ Process.sleep(to_reset)
+
+ conn = conn(:get, "/")
+
+ conn = RateLimiter.call(conn, opts)
+ assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ refute conn.status == Plug.Conn.Status.code(:too_many_requests)
+ refute conn.resp_body
+ refute conn.halted
+ end
+
+ test "optional limits for authenticated users" do
+ Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
+
+ scale = 1000
+ limit = 5
+ Pleroma.Config.put([:rate_limit, @limiter_name], [{1, 10}, {scale, limit}])
+
+ opts = RateLimiter.init(@limiter_name)
+
+ user = insert(:user)
+ conn = conn(:get, "/") |> assign(:user, user)
+ bucket_name = "#{@limiter_name}:#{user.id}"
+
+ conn = RateLimiter.call(conn, opts)
+ assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+ assert {2, 3, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+ assert {3, 2, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+ assert {4, 1, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+ assert {5, 0, to_reset, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ conn = RateLimiter.call(conn, opts)
+
+ assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
+ assert conn.halted
+
+ Process.sleep(to_reset)
+
+ conn = conn(:get, "/") |> assign(:user, user)
+
+ conn = RateLimiter.call(conn, opts)
+ assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
+
+ refute conn.status == Plug.Conn.Status.code(:too_many_requests)
+ refute conn.resp_body
+ refute conn.halted
+ end
+end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index faa5132ae..dd57a0bed 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -311,4 +311,18 @@ defmodule Pleroma.Factory do
}
}
end
+
+ def config_factory do
+ %Pleroma.Web.AdminAPI.Config{
+ key: sequence(:key, &"some_key_#{&1}"),
+ group: "pleroma",
+ value:
+ sequence(
+ :value,
+ fn key ->
+ :erlang.term_to_binary(%{another_key: "#{key}somevalue", another: "#{key}somevalue"})
+ end
+ )
+ }
+ end
end
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 67ef0928a..30169edb0 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -757,6 +757,14 @@ defmodule HttpRequestMock do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
end
+ def get("https://example.com/ogp", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
+ end
+
+ def get("https://pleroma.local/notice/9kCP7V", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
+ end
+
def get("http://example.com/ogp-missing-data", _, _, _) do
{:ok,
%Tesla.Env{
@@ -765,6 +773,14 @@ defmodule HttpRequestMock do
}}
end
+ def get("https://example.com/ogp-missing-data", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/rich_media/ogp-missing-data.html")
+ }}
+ end
+
def get("http://example.com/malformed", _, _, _) do
{:ok,
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}}
@@ -802,6 +818,30 @@ defmodule HttpRequestMock do
}}
end
+ def post("http://mastodon.example.org/inbox", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("https://hubzilla.example.org/inbox", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("http://gs.example.org/index.php/main/salmon/user/1", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
def post("http://200.site" <> _, _, _, _) do
{:ok,
%Tesla.Env{
diff --git a/test/tasks/config_test.exs b/test/tasks/config_test.exs
new file mode 100644
index 000000000..83a363356
--- /dev/null
+++ b/test/tasks/config_test.exs
@@ -0,0 +1,63 @@
+defmodule Mix.Tasks.Pleroma.ConfigTest do
+ use Pleroma.DataCase
+ alias Pleroma.Repo
+ alias Pleroma.Web.AdminAPI.Config
+
+ setup_all do
+ Mix.shell(Mix.Shell.Process)
+ temp_file = "config/temp.exported_from_db.secret.exs"
+
+ dynamic = Pleroma.Config.get([:instance, :dynamic_configuration])
+
+ Pleroma.Config.put([:instance, :dynamic_configuration], true)
+
+ on_exit(fn ->
+ Mix.shell(Mix.Shell.IO)
+ Application.delete_env(:pleroma, :first_setting)
+ Application.delete_env(:pleroma, :second_setting)
+ Pleroma.Config.put([:instance, :dynamic_configuration], dynamic)
+ :ok = File.rm(temp_file)
+ end)
+
+ {:ok, temp_file: temp_file}
+ end
+
+ test "settings are migrated to db" do
+ assert Repo.all(Config) == []
+
+ Application.put_env(:pleroma, :first_setting, key: "value", key2: [Pleroma.Repo])
+ Application.put_env(:pleroma, :second_setting, key: "value2", key2: [Pleroma.Activity])
+
+ Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
+
+ first_db = Config.get_by_params(%{group: "pleroma", key: "first_setting"})
+ second_db = Config.get_by_params(%{group: "pleroma", key: "second_setting"})
+ refute Config.get_by_params(%{group: "pleroma", key: "Pleroma.Repo"})
+
+ assert Config.from_binary(first_db.value) == [key: "value", key2: [Pleroma.Repo]]
+ assert Config.from_binary(second_db.value) == [key: "value2", key2: [Pleroma.Activity]]
+ end
+
+ test "settings are migrated to file and deleted from db", %{temp_file: temp_file} do
+ Config.create(%{
+ group: "pleroma",
+ key: "setting_first",
+ value: [key: "value", key2: [Pleroma.Activity]]
+ })
+
+ Config.create(%{
+ group: "pleroma",
+ key: "setting_second",
+ value: [key: "valu2", key2: [Pleroma.Repo]]
+ })
+
+ Mix.Tasks.Pleroma.Config.run(["migrate_from_db", "temp", "true"])
+
+ assert Repo.all(Config) == []
+ assert File.exists?(temp_file)
+ {:ok, file} = File.read(temp_file)
+
+ assert file =~ "config :pleroma, setting_first:"
+ assert file =~ "config :pleroma, setting_second:"
+ end
+end
diff --git a/test/tasks/ecto/migrate_test.exs b/test/tasks/ecto/migrate_test.exs
new file mode 100644
index 000000000..0538a7b40
--- /dev/null
+++ b/test/tasks/ecto/migrate_test.exs
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-onl
+
+defmodule Mix.Tasks.Pleroma.Ecto.MigrateTest do
+ use Pleroma.DataCase, async: true
+ import ExUnit.CaptureLog
+ require Logger
+
+ test "ecto.migrate info message" do
+ level = Logger.level()
+ Logger.configure(level: :warn)
+
+ assert capture_log(fn ->
+ Mix.Tasks.Pleroma.Ecto.Migrate.run()
+ end) =~ "[info] Already up"
+
+ Logger.configure(level: level)
+ end
+end
diff --git a/test/tasks/ecto/rollback_test.exs b/test/tasks/ecto/rollback_test.exs
new file mode 100644
index 000000000..33d093fca
--- /dev/null
+++ b/test/tasks/ecto/rollback_test.exs
@@ -0,0 +1,16 @@
+defmodule Mix.Tasks.Pleroma.Ecto.RollbackTest do
+ use Pleroma.DataCase
+ import ExUnit.CaptureLog
+ require Logger
+
+ test "ecto.rollback info message" do
+ level = Logger.level()
+ Logger.configure(level: :warn)
+
+ assert capture_log(fn ->
+ Mix.Tasks.Pleroma.Ecto.Rollback.run()
+ end) =~ "[info] Rollback succesfully"
+
+ Logger.configure(level: level)
+ end
+end
diff --git a/test/tasks/instance.exs b/test/tasks/instance.exs
index 6917a2376..1875f52a3 100644
--- a/test/tasks/instance.exs
+++ b/test/tasks/instance.exs
@@ -36,6 +36,8 @@ defmodule Pleroma.InstanceTest do
"--dbpass",
"dbpass",
"--indexable",
+ "y",
+ "--db-configurable",
"y"
])
end
@@ -53,6 +55,7 @@ defmodule Pleroma.InstanceTest do
assert generated_config =~ "database: \"dbname\""
assert generated_config =~ "username: \"dbuser\""
assert generated_config =~ "password: \"dbpass\""
+ assert generated_config =~ "dynamic_configuration: true"
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
end
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index 6fd7c7113..3d4b08fba 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -89,8 +89,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do
assert_received {:mix_shell, :info, [message]}
assert message =~ " deleted"
- user = User.get_cached_by_nickname(user.nickname)
- assert user.info.deactivated
+ refute User.get_by_nickname(user.nickname)
end
test "no user to delete" do
diff --git a/test/user_test.exs b/test/user_test.exs
index 157fdfbd7..90d2db2eb 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -920,42 +920,44 @@ defmodule Pleroma.UserTest do
{:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
- Ecto.Adapters.SQL.Sandbox.unboxed_run(Repo, fn ->
- {:ok, _} = User.delete_user_activities(user)
- # TODO: Remove favorites, repeats, delete activities.
- refute Activity.get_by_id(activity.id)
- end)
+ {: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
+ test ".delete deactivates a user, all follow relationships and all activities" do
user = insert(:user)
- followed = insert(:user)
follower = insert(:user)
- {:ok, user} = User.follow(user, followed)
{:ok, follower} = User.follow(follower, user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
{:ok, activity_two} = CommonAPI.post(follower, %{"status" => "3hu"})
- {:ok, _, _} = CommonAPI.favorite(activity_two.id, user)
- {:ok, _, _} = CommonAPI.favorite(activity.id, follower)
- {:ok, _, _} = CommonAPI.repeat(activity.id, follower)
+ {:ok, like, _} = CommonAPI.favorite(activity_two.id, user)
+ {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower)
+ {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user)
{:ok, _} = User.delete(user)
- followed = User.get_cached_by_id(followed.id)
follower = User.get_cached_by_id(follower.id)
- user = User.get_cached_by_id(user.id)
- assert user.info.deactivated
+ refute User.following?(follower, user)
+ refute User.get_by_id(user.id)
- refute User.following?(user, followed)
- refute User.following?(followed, follower)
+ user_activities =
+ user.ap_id
+ |> Activity.query_by_actor()
+ |> Repo.all()
+ |> Enum.map(fn act -> act.data["type"] end)
- # TODO: Remove favorites, repeats, delete activities.
+ assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end)
refute Activity.get_by_id(activity.id)
+ refute Activity.get_by_id(like.id)
+ refute Activity.get_by_id(like_two.id)
+ refute Activity.get_by_id(repeat.id)
end
test "get_public_key_for_ap_id fetches a user that's not in the db" do
@@ -1011,6 +1013,18 @@ defmodule Pleroma.UserTest do
end
describe "User.search" do
+ test "accepts limit parameter" do
+ Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
+ assert length(User.search("john", limit: 3)) == 3
+ assert length(User.search("john")) == 5
+ end
+
+ test "accepts offset parameter" do
+ Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
+ assert length(User.search("john", limit: 3)) == 3
+ assert length(User.search("john", limit: 3, offset: 3)) == 2
+ end
+
test "finds a user by full or partial nickname" do
user = insert(:user, %{nickname: "john"})
@@ -1077,6 +1091,24 @@ defmodule Pleroma.UserTest do
Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
end
+ test "finds followers of user by partial name" do
+ u1 = insert(:user)
+ u2 = insert(:user, %{name: "Jimi"})
+ follower_jimi = insert(:user, %{name: "Jimi Hendrix"})
+ follower_lizz = insert(:user, %{name: "Lizz Wright"})
+ friend = insert(:user, %{name: "Jimi"})
+
+ {:ok, follower_jimi} = User.follow(follower_jimi, u1)
+ {:ok, _follower_lizz} = User.follow(follower_lizz, u2)
+ {:ok, u1} = User.follow(u1, friend)
+
+ assert Enum.map(User.search("jimi", following: true, for_user: u1), & &1.id) == [
+ follower_jimi.id
+ ]
+
+ assert User.search("lizz", following: true, for_user: u1) == []
+ end
+
test "find local and remote users for authenticated users" do
u1 = insert(:user, %{name: "lain"})
u2 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
@@ -1099,8 +1131,20 @@ defmodule Pleroma.UserTest do
assert [%{id: ^id}] = User.search("lain")
end
- test "find all users for unauthenticated users when `limit_unauthenticated_to_local_content` is `false`" do
- Pleroma.Config.put([:instance, :limit_unauthenticated_to_local_content], false)
+ test "find only local users for authenticated users when `limit_to_local_content` is `:all`" do
+ Pleroma.Config.put([:instance, :limit_to_local_content], :all)
+
+ %{id: id} = insert(:user, %{name: "lain"})
+ insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
+ insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
+
+ assert [%{id: ^id}] = User.search("lain")
+
+ Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
+ end
+
+ test "find all users for unauthenticated users when `limit_to_local_content` is `false`" do
+ Pleroma.Config.put([:instance, :limit_to_local_content], false)
u1 = insert(:user, %{name: "lain"})
u2 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
@@ -1114,7 +1158,7 @@ defmodule Pleroma.UserTest do
assert [u1.id, u2.id, u3.id] == results
- Pleroma.Config.put([:instance, :limit_unauthenticated_to_local_content], true)
+ Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
end
test "finds a user whose name is nil" do
diff --git a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs
new file mode 100644
index 000000000..03dc299ec
--- /dev/null
+++ b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs
@@ -0,0 +1,145 @@
+# 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.AntiLinkSpamPolicyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+ import ExUnit.CaptureLog
+
+ alias Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy
+
+ @linkless_message %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "hi world!"
+ }
+ }
+
+ @linkful_message %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "<a href='https://example.com'>hi world!</a>"
+ }
+ }
+
+ @response_message %{
+ "type" => "Create",
+ "object" => %{
+ "name" => "yes",
+ "type" => "Answer"
+ }
+ }
+
+ describe "with new user" do
+ test "it allows posts without links" do
+ user = insert(:user)
+
+ assert user.info.note_count == 0
+
+ message =
+ @linkless_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+
+ test "it disallows posts with links" do
+ user = insert(:user)
+
+ assert user.info.note_count == 0
+
+ message =
+ @linkful_message
+ |> Map.put("actor", user.ap_id)
+
+ {:reject, _} = AntiLinkSpamPolicy.filter(message)
+ end
+ end
+
+ describe "with old user" do
+ test "it allows posts without links" do
+ user = insert(:user, info: %{note_count: 1})
+
+ assert user.info.note_count == 1
+
+ message =
+ @linkless_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+
+ test "it allows posts with links" do
+ user = insert(:user, info: %{note_count: 1})
+
+ assert user.info.note_count == 1
+
+ message =
+ @linkful_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+ end
+
+ describe "with followed new user" do
+ test "it allows posts without links" do
+ user = insert(:user, info: %{follower_count: 1})
+
+ assert user.info.follower_count == 1
+
+ message =
+ @linkless_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+
+ test "it allows posts with links" do
+ user = insert(:user, info: %{follower_count: 1})
+
+ assert user.info.follower_count == 1
+
+ message =
+ @linkful_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+ end
+
+ describe "with unknown actors" do
+ test "it rejects posts without links" do
+ message =
+ @linkless_message
+ |> Map.put("actor", "http://invalid.actor")
+
+ assert capture_log(fn ->
+ {:reject, _} = AntiLinkSpamPolicy.filter(message)
+ end) =~ "[error] Could not decode user at fetch http://invalid.actor"
+ end
+
+ test "it rejects posts with links" do
+ message =
+ @linkful_message
+ |> Map.put("actor", "http://invalid.actor")
+
+ assert capture_log(fn ->
+ {:reject, _} = AntiLinkSpamPolicy.filter(message)
+ end) =~ "[error] Could not decode user at fetch http://invalid.actor"
+ end
+ end
+
+ describe "with contentless-objects" do
+ test "it does not reject them or error out" do
+ user = insert(:user, info: %{note_count: 1})
+
+ message =
+ @response_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+ end
+end
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index 28971ae45..68ec03c33 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
alias Pleroma.Web.Websub.WebsubClientSubscription
import Pleroma.Factory
+ import ExUnit.CaptureLog
alias Pleroma.Web.CommonAPI
setup_all do
@@ -60,6 +61,24 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
end
+ test "it does not crash if the object in inReplyTo can't be fetched" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+
+ object =
+ data["object"]
+ |> Map.put("inReplyTo", "https://404.site/whatever")
+
+ data =
+ data
+ |> Map.put("object", object)
+
+ assert capture_log(fn ->
+ {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
+ end) =~ "[error] Couldn't fetch \"\"https://404.site/whatever\"\", error: nil"
+ end
+
test "it works for incoming notices" do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
@@ -500,7 +519,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
data
|> Map.put("object", object)
- :error = Transmogrifier.handle_incoming(data)
+ assert capture_log(fn ->
+ :error = Transmogrifier.handle_incoming(data)
+ end) =~
+ "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, {:error, :nxdomain}}"
assert Activity.get_by_id(activity.id)
end
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 43dcf945a..4278ac59d 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -1292,4 +1292,345 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
assert json_response(conn, :bad_request) == "Could not delete"
end
end
+
+ describe "GET /api/pleroma/admin/config" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+
+ %{conn: assign(conn, :user, admin)}
+ end
+
+ test "without any settings in db", %{conn: conn} do
+ conn = get(conn, "/api/pleroma/admin/config")
+
+ assert json_response(conn, 200) == %{"configs" => []}
+ end
+
+ test "with settings in db", %{conn: conn} do
+ config1 = insert(:config)
+ config2 = insert(:config)
+
+ conn = get(conn, "/api/pleroma/admin/config")
+
+ %{
+ "configs" => [
+ %{
+ "key" => key1,
+ "value" => _
+ },
+ %{
+ "key" => key2,
+ "value" => _
+ }
+ ]
+ } = json_response(conn, 200)
+
+ assert key1 == config1.key
+ assert key2 == config2.key
+ end
+ end
+
+ describe "POST /api/pleroma/admin/config" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+
+ temp_file = "config/test.exported_from_db.secret.exs"
+
+ on_exit(fn ->
+ Application.delete_env(:pleroma, :key1)
+ Application.delete_env(:pleroma, :key2)
+ Application.delete_env(:pleroma, :key3)
+ Application.delete_env(:pleroma, :key4)
+ Application.delete_env(:pleroma, :keyaa1)
+ Application.delete_env(:pleroma, :keyaa2)
+ Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
+ Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
+ :ok = File.rm(temp_file)
+ end)
+
+ dynamic = Pleroma.Config.get([:instance, :dynamic_configuration])
+
+ Pleroma.Config.put([:instance, :dynamic_configuration], true)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :dynamic_configuration], dynamic)
+ end)
+
+ %{conn: assign(conn, :user, admin)}
+ end
+
+ test "create new config setting in db", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{group: "pleroma", key: "key1", value: "value1"},
+ %{
+ group: "pleroma",
+ key: "key2",
+ value: %{
+ "nested_1" => "nested_value1",
+ "nested_2" => [
+ %{"nested_22" => "nested_value222"},
+ %{"nested_33" => %{"nested_44" => "nested_444"}}
+ ]
+ }
+ },
+ %{
+ group: "pleroma",
+ key: "key3",
+ value: [
+ %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
+ %{"nested_4" => ":true"}
+ ]
+ },
+ %{
+ group: "pleroma",
+ key: "key4",
+ value: %{"nested_5" => ":upload", "endpoint" => "https://example.com"}
+ },
+ %{
+ group: "idna",
+ key: "key5",
+ value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => "pleroma",
+ "key" => "key1",
+ "value" => "value1"
+ },
+ %{
+ "group" => "pleroma",
+ "key" => "key2",
+ "value" => [
+ %{"nested_1" => "nested_value1"},
+ %{
+ "nested_2" => [
+ %{"nested_22" => "nested_value222"},
+ %{"nested_33" => %{"nested_44" => "nested_444"}}
+ ]
+ }
+ ]
+ },
+ %{
+ "group" => "pleroma",
+ "key" => "key3",
+ "value" => [
+ [%{"nested_3" => "nested_3"}, %{"nested_33" => "nested_33"}],
+ %{"nested_4" => true}
+ ]
+ },
+ %{
+ "group" => "pleroma",
+ "key" => "key4",
+ "value" => [%{"endpoint" => "https://example.com"}, %{"nested_5" => "upload"}]
+ },
+ %{
+ "group" => "idna",
+ "key" => "key5",
+ "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
+ }
+ ]
+ }
+
+ assert Application.get_env(:pleroma, :key1) == "value1"
+
+ assert Application.get_env(:pleroma, :key2) == [
+ nested_1: "nested_value1",
+ nested_2: [
+ [nested_22: "nested_value222"],
+ [nested_33: [nested_44: "nested_444"]]
+ ]
+ ]
+
+ assert Application.get_env(:pleroma, :key3) == [
+ [nested_3: :nested_3, nested_33: "nested_33"],
+ [nested_4: true]
+ ]
+
+ assert Application.get_env(:pleroma, :key4) == [
+ endpoint: "https://example.com",
+ nested_5: :upload
+ ]
+
+ assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
+ end
+
+ test "update config setting & delete", %{conn: conn} do
+ config1 = insert(:config, key: "keyaa1")
+ config2 = insert(:config, key: "keyaa2")
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{group: config1.group, key: config1.key, value: "another_value"},
+ %{group: config2.group, key: config2.key, delete: "true"}
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => "pleroma",
+ "key" => config1.key,
+ "value" => "another_value"
+ }
+ ]
+ }
+
+ assert Application.get_env(:pleroma, :keyaa1) == "another_value"
+ refute Application.get_env(:pleroma, :keyaa2)
+ end
+
+ test "common config example", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => "pleroma",
+ "key" => "Pleroma.Captcha.NotReal",
+ "value" => %{
+ "enabled" => ":false",
+ "method" => "Pleroma.Captcha.Kocaptcha",
+ "seconds_valid" => "i:60"
+ }
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => "pleroma",
+ "key" => "Pleroma.Captcha.NotReal",
+ "value" => [
+ %{"enabled" => false},
+ %{"method" => "Pleroma.Captcha.Kocaptcha"},
+ %{"seconds_valid" => 60}
+ ]
+ }
+ ]
+ }
+ end
+
+ test "tuples with more than two values", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => "pleroma",
+ "key" => "Pleroma.Web.Endpoint.NotReal",
+ "value" => [
+ %{
+ "http" => %{
+ "dispatch" => [
+ %{
+ "tuple" => [
+ ":_",
+ [
+ %{
+ "tuple" => [
+ "/api/v1/streaming",
+ "Pleroma.Web.MastodonAPI.WebsocketHandler",
+ []
+ ]
+ },
+ %{
+ "tuple" => [
+ "/websocket",
+ "Phoenix.Endpoint.CowboyWebSocket",
+ %{
+ "tuple" => [
+ "Phoenix.Transports.WebSocket",
+ %{
+ "tuple" => [
+ "Pleroma.Web.Endpoint",
+ "Pleroma.Web.UserSocket",
+ []
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ %{
+ "tuple" => [
+ ":_",
+ "Phoenix.Endpoint.Cowboy2Handler",
+ %{
+ "tuple" => ["Pleroma.Web.Endpoint", []]
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => "pleroma",
+ "key" => "Pleroma.Web.Endpoint.NotReal",
+ "value" => [
+ %{
+ "http" => %{
+ "dispatch" => %{
+ "_" => [
+ %{
+ "tuple" => [
+ "/api/v1/streaming",
+ "Pleroma.Web.MastodonAPI.WebsocketHandler",
+ []
+ ]
+ },
+ %{
+ "tuple" => [
+ "/websocket",
+ "Phoenix.Endpoint.CowboyWebSocket",
+ %{
+ "Elixir.Phoenix.Transports.WebSocket" => %{
+ "tuple" => [
+ "Pleroma.Web.Endpoint",
+ "Pleroma.Web.UserSocket",
+ []
+ ]
+ }
+ }
+ ]
+ },
+ %{
+ "tuple" => [
+ "_",
+ "Phoenix.Endpoint.Cowboy2Handler",
+ %{"Elixir.Pleroma.Web.Endpoint" => []}
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ end
+ end
+end
+
+# Needed for testing
+defmodule Pleroma.Web.Endpoint.NotReal do
+end
+
+defmodule Pleroma.Captcha.NotReal do
end
diff --git a/test/web/admin_api/config_test.exs b/test/web/admin_api/config_test.exs
new file mode 100644
index 000000000..10cb3b68a
--- /dev/null
+++ b/test/web/admin_api/config_test.exs
@@ -0,0 +1,258 @@
+defmodule Pleroma.Web.AdminAPI.ConfigTest do
+ use Pleroma.DataCase, async: true
+ import Pleroma.Factory
+ alias Pleroma.Web.AdminAPI.Config
+
+ test "get_by_key/1" do
+ config = insert(:config)
+ insert(:config)
+
+ assert config == Config.get_by_params(%{group: config.group, key: config.key})
+ end
+
+ test "create/1" do
+ {:ok, config} = Config.create(%{group: "pleroma", key: "some_key", value: "some_value"})
+ assert config == Config.get_by_params(%{group: "pleroma", key: "some_key"})
+ end
+
+ test "update/1" do
+ config = insert(:config)
+ {:ok, updated} = Config.update(config, %{value: "some_value"})
+ loaded = Config.get_by_params(%{group: config.group, key: config.key})
+ assert loaded == updated
+ end
+
+ test "update_or_create/1" do
+ config = insert(:config)
+ key2 = "another_key"
+
+ params = [
+ %{group: "pleroma", key: key2, value: "another_value"},
+ %{group: config.group, key: config.key, value: "new_value"}
+ ]
+
+ assert Repo.all(Config) |> length() == 1
+
+ Enum.each(params, &Config.update_or_create(&1))
+
+ assert Repo.all(Config) |> length() == 2
+
+ config1 = Config.get_by_params(%{group: config.group, key: config.key})
+ config2 = Config.get_by_params(%{group: "pleroma", key: key2})
+
+ assert config1.value == Config.transform("new_value")
+ assert config2.value == Config.transform("another_value")
+ end
+
+ test "delete/1" do
+ config = insert(:config)
+ {:ok, _} = Config.delete(%{key: config.key, group: config.group})
+ refute Config.get_by_params(%{key: config.key, group: config.group})
+ end
+
+ describe "transform/1" do
+ test "string" do
+ binary = Config.transform("value as string")
+ assert binary == :erlang.term_to_binary("value as string")
+ assert Config.from_binary(binary) == "value as string"
+ end
+
+ test "list of modules" do
+ binary = Config.transform(["Pleroma.Repo", "Pleroma.Activity"])
+ assert binary == :erlang.term_to_binary([Pleroma.Repo, Pleroma.Activity])
+ assert Config.from_binary(binary) == [Pleroma.Repo, Pleroma.Activity]
+ end
+
+ test "list of strings" do
+ binary = Config.transform(["string1", "string2"])
+ assert binary == :erlang.term_to_binary(["string1", "string2"])
+ assert Config.from_binary(binary) == ["string1", "string2"]
+ end
+
+ test "map" do
+ binary =
+ Config.transform(%{
+ "types" => "Pleroma.PostgresTypes",
+ "telemetry_event" => ["Pleroma.Repo.Instrumenter"],
+ "migration_lock" => ""
+ })
+
+ assert binary ==
+ :erlang.term_to_binary(
+ telemetry_event: [Pleroma.Repo.Instrumenter],
+ types: Pleroma.PostgresTypes
+ )
+
+ assert Config.from_binary(binary) == [
+ telemetry_event: [Pleroma.Repo.Instrumenter],
+ types: Pleroma.PostgresTypes
+ ]
+ end
+
+ test "complex map with nested integers, lists and atoms" do
+ binary =
+ Config.transform(%{
+ "uploader" => "Pleroma.Uploaders.Local",
+ "filters" => ["Pleroma.Upload.Filter.Dedupe"],
+ "link_name" => ":true",
+ "proxy_remote" => ":false",
+ "proxy_opts" => %{
+ "redirect_on_failure" => ":false",
+ "max_body_length" => "i:1048576",
+ "http" => %{
+ "follow_redirect" => ":true",
+ "pool" => ":upload"
+ }
+ }
+ })
+
+ assert binary ==
+ :erlang.term_to_binary(
+ filters: [Pleroma.Upload.Filter.Dedupe],
+ link_name: true,
+ proxy_opts: [
+ http: [
+ follow_redirect: true,
+ pool: :upload
+ ],
+ max_body_length: 1_048_576,
+ redirect_on_failure: false
+ ],
+ proxy_remote: false,
+ uploader: Pleroma.Uploaders.Local
+ )
+
+ assert Config.from_binary(binary) ==
+ [
+ filters: [Pleroma.Upload.Filter.Dedupe],
+ link_name: true,
+ proxy_opts: [
+ http: [
+ follow_redirect: true,
+ pool: :upload
+ ],
+ max_body_length: 1_048_576,
+ redirect_on_failure: false
+ ],
+ proxy_remote: false,
+ uploader: Pleroma.Uploaders.Local
+ ]
+ end
+
+ test "keyword" do
+ binary =
+ Config.transform(%{
+ "level" => ":warn",
+ "meta" => [":all"],
+ "webhook_url" => "https://hooks.slack.com/services/YOUR-KEY-HERE"
+ })
+
+ assert binary ==
+ :erlang.term_to_binary(
+ level: :warn,
+ meta: [:all],
+ webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
+ )
+
+ assert Config.from_binary(binary) == [
+ level: :warn,
+ meta: [:all],
+ webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
+ ]
+ end
+
+ test "complex map with sigil" do
+ binary =
+ Config.transform(%{
+ federated_timeline_removal: [],
+ reject: [~r/comp[lL][aA][iI][nN]er/],
+ replace: []
+ })
+
+ assert binary ==
+ :erlang.term_to_binary(
+ federated_timeline_removal: [],
+ reject: [~r/comp[lL][aA][iI][nN]er/],
+ replace: []
+ )
+
+ assert Config.from_binary(binary) ==
+ [federated_timeline_removal: [], reject: [~r/comp[lL][aA][iI][nN]er/], replace: []]
+ end
+
+ test "complex map with tuples with more than 2 values" do
+ binary =
+ Config.transform(%{
+ "http" => %{
+ "dispatch" => [
+ %{
+ "tuple" => [
+ ":_",
+ [
+ %{
+ "tuple" => [
+ "/api/v1/streaming",
+ "Pleroma.Web.MastodonAPI.WebsocketHandler",
+ []
+ ]
+ },
+ %{
+ "tuple" => [
+ "/websocket",
+ "Phoenix.Endpoint.CowboyWebSocket",
+ %{
+ "tuple" => [
+ "Phoenix.Transports.WebSocket",
+ %{"tuple" => ["Pleroma.Web.Endpoint", "Pleroma.Web.UserSocket", []]}
+ ]
+ }
+ ]
+ },
+ %{
+ "tuple" => [
+ ":_",
+ "Phoenix.Endpoint.Cowboy2Handler",
+ %{
+ "tuple" => ["Pleroma.Web.Endpoint", []]
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ ]
+ }
+ })
+
+ assert binary ==
+ :erlang.term_to_binary(
+ http: [
+ dispatch: [
+ _: [
+ {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
+ {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
+ {Phoenix.Transports.WebSocket,
+ {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}},
+ {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
+ ]
+ ]
+ ]
+ )
+
+ assert Config.from_binary(binary) == [
+ http: [
+ dispatch: [
+ {:_,
+ [
+ {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
+ {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
+ {Phoenix.Transports.WebSocket,
+ {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}},
+ {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
+ ]}
+ ]
+ ]
+ ]
+ end
+ end
+end
diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs
new file mode 100644
index 000000000..f35f36cac
--- /dev/null
+++ b/test/web/admin_api/views/report_view_test.exs
@@ -0,0 +1,98 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ReportViewTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+ alias Pleroma.Web.AdminAPI.ReportView
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ test "renders a report" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id})
+
+ expected = %{
+ content: nil,
+ actor: AccountView.render("account.json", %{user: user}),
+ account: AccountView.render("account.json", %{user: other_user}),
+ statuses: [],
+ state: "open",
+ id: activity.id
+ }
+
+ result =
+ ReportView.render("show.json", %{report: activity})
+ |> Map.delete(:created_at)
+
+ assert result == expected
+ end
+
+ test "includes reported statuses" do
+ user = insert(:user)
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "toot"})
+
+ {:ok, report_activity} =
+ CommonAPI.report(user, %{"account_id" => other_user.id, "status_ids" => [activity.id]})
+
+ expected = %{
+ content: nil,
+ actor: AccountView.render("account.json", %{user: user}),
+ account: AccountView.render("account.json", %{user: other_user}),
+ statuses: [StatusView.render("status.json", %{activity: activity})],
+ state: "open",
+ id: report_activity.id
+ }
+
+ result =
+ ReportView.render("show.json", %{report: report_activity})
+ |> Map.delete(:created_at)
+
+ assert result == expected
+ end
+
+ test "renders report's state" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id})
+ {:ok, activity} = CommonAPI.update_report_state(activity.id, "closed")
+ assert %{state: "closed"} = ReportView.render("show.json", %{report: activity})
+ end
+
+ test "renders report description" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.report(user, %{
+ "account_id" => other_user.id,
+ "comment" => "posts are too good for this instance"
+ })
+
+ assert %{content: "posts are too good for this instance"} =
+ ReportView.render("show.json", %{report: activity})
+ end
+
+ test "sanitizes report description" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.report(user, %{
+ "account_id" => other_user.id,
+ "comment" => ""
+ })
+
+ data = Map.put(activity.data, "content", "<script> alert('hecked :D:D:D:D:D:D:D') </script>")
+ activity = Map.put(activity, :data, data)
+
+ refute "<script> alert('hecked :D:D:D:D:D:D:D') </script>" ==
+ ReportView.render("show.json", %{report: activity})[:content]
+ end
+end
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 7ff23b63d..e96106f11 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -121,7 +121,7 @@ defmodule Pleroma.Web.CommonAPITest do
})
Enum.each(["public", "private", "unlisted"], fn visibility ->
- assert {:error, {:private_to_public, _}} =
+ assert {:error, "The message visibility must be direct"} =
CommonAPI.post(user, %{
"status" => "suya..",
"visibility" => visibility,
diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs
index e2244dcb7..de6aeec72 100644
--- a/test/web/mastodon_api/account_view_test.exs
+++ b/test/web/mastodon_api/account_view_test.exs
@@ -19,9 +19,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
]
}
+ background_image = %{
+ "url" => [%{"href" => "https://example.com/images/asuka_hospital.png"}]
+ }
+
user =
insert(:user, %{
- info: %{note_count: 5, follower_count: 3, source_data: source_data},
+ info: %{
+ note_count: 5,
+ follower_count: 3,
+ source_data: source_data,
+ background: background_image
+ },
nickname: "shp@shitposter.club",
name: ":karjalanpiirakka: shp",
bio: "<script src=\"invalid-html\"></script><span>valid html</span>",
@@ -60,6 +69,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
pleroma: %{}
},
pleroma: %{
+ background_image: "https://example.com/images/asuka_hospital.png",
confirmation_pending: false,
tags: [],
is_admin: false,
@@ -126,6 +136,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
pleroma: %{}
},
pleroma: %{
+ background_image: nil,
confirmation_pending: false,
tags: [],
is_admin: false,
@@ -216,6 +227,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
pleroma: %{}
},
pleroma: %{
+ background_image: nil,
confirmation_pending: false,
tags: [],
is_admin: false,
@@ -257,4 +269,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
result = AccountView.render("account.json", %{user: user, for: user})
assert result.pleroma[:settings_store] == nil
end
+
+ test "sanitizes display names" do
+ user = insert(:user, name: "<marquee> username </marquee>")
+ result = AccountView.render("account.json", %{user: user})
+ refute result.display_name == "<marquee> username </marquee>"
+ end
end
diff --git a/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs b/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs
new file mode 100644
index 000000000..71d0c8af8
--- /dev/null
+++ b/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs
@@ -0,0 +1,304 @@
+# 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.MastodonAPIController.UpdateCredentialsTest do
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+
+ describe "updating credentials" do
+ test "sets user settings in a generic way", %{conn: conn} do
+ user = insert(:user)
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "pleroma_settings_store" => %{
+ pleroma_fe: %{
+ theme: "bla"
+ }
+ }
+ })
+
+ assert user = json_response(res_conn, 200)
+ assert user["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}}
+
+ user = Repo.get(User, user["id"])
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "pleroma_settings_store" => %{
+ masto_fe: %{
+ theme: "bla"
+ }
+ }
+ })
+
+ assert user = json_response(res_conn, 200)
+
+ assert user["pleroma"]["settings_store"] ==
+ %{
+ "pleroma_fe" => %{"theme" => "bla"},
+ "masto_fe" => %{"theme" => "bla"}
+ }
+
+ user = Repo.get(User, user["id"])
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "pleroma_settings_store" => %{
+ masto_fe: %{
+ theme: "blub"
+ }
+ }
+ })
+
+ assert user = json_response(res_conn, 200)
+
+ assert user["pleroma"]["settings_store"] ==
+ %{
+ "pleroma_fe" => %{"theme" => "bla"},
+ "masto_fe" => %{"theme" => "blub"}
+ }
+ end
+
+ test "updates the user's bio", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "note" => "I drink #cofe with @#{user2.nickname}"
+ })
+
+ assert user = json_response(conn, 200)
+
+ assert user["note"] ==
+ ~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
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{locked: "true"})
+
+ assert user = json_response(conn, 200)
+ assert user["locked"] == true
+ end
+
+ test "updates the user's default scope", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"})
+
+ assert user = json_response(conn, 200)
+ assert user["source"]["privacy"] == "cofe"
+ end
+
+ test "updates the user's hide_followers status", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"})
+
+ assert user = json_response(conn, 200)
+ assert user["pleroma"]["hide_followers"] == true
+ end
+
+ test "updates the user's skip_thread_containment option", %{conn: conn} do
+ user = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"})
+ |> json_response(200)
+
+ assert response["pleroma"]["skip_thread_containment"] == true
+ assert refresh_record(user).info.skip_thread_containment
+ end
+
+ test "updates the user's hide_follows status", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"})
+
+ assert user = json_response(conn, 200)
+ assert user["pleroma"]["hide_follows"] == true
+ end
+
+ test "updates the user's hide_favorites status", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
+
+ assert user = json_response(conn, 200)
+ assert user["pleroma"]["hide_favorites"] == true
+ end
+
+ test "updates the user's show_role status", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"})
+
+ assert user = json_response(conn, 200)
+ assert user["source"]["pleroma"]["show_role"] == false
+ end
+
+ test "updates the user's no_rich_text status", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
+
+ assert user = json_response(conn, 200)
+ assert user["source"]["pleroma"]["no_rich_text"] == true
+ end
+
+ test "updates the user's name", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
+
+ assert user = json_response(conn, 200)
+ assert user["display_name"] == "markorepairs"
+ end
+
+ test "updates the user's avatar", %{conn: conn} do
+ user = insert(:user)
+
+ new_avatar = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
+
+ assert user_response = json_response(conn, 200)
+ assert user_response["avatar"] != User.avatar_url(user)
+ end
+
+ test "updates the user's banner", %{conn: conn} do
+ user = insert(:user)
+
+ new_header = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
+
+ assert user_response = json_response(conn, 200)
+ assert user_response["header"] != User.banner_url(user)
+ end
+
+ test "updates the user's background", %{conn: conn} do
+ user = insert(:user)
+
+ new_header = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "pleroma_background_image" => new_header
+ })
+
+ assert user_response = json_response(conn, 200)
+ assert user_response["pleroma"]["background_image"]
+ 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
+
+ test "updates profile emojos", %{conn: conn} do
+ user = insert(:user)
+
+ note = "*sips :blank:*"
+ name = "I am :firefox:"
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "note" => note,
+ "display_name" => name
+ })
+
+ assert json_response(conn, 200)
+
+ conn =
+ conn
+ |> get("/api/v1/accounts/#{user.id}")
+
+ assert user = json_response(conn, 200)
+
+ assert user["note"] == note
+ assert user["display_name"] == name
+ assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
+ end
+ end
+end
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index 33c8e209a..03f57dbfa 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -94,56 +94,186 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> json_response(403) == %{"error" => "This resource requires authentication."}
end
- test "posting a status", %{conn: conn} do
- user = insert(:user)
+ describe "posting statuses" do
+ setup do
+ user = insert(:user)
- idempotency_key = "Pikachu rocks!"
+ conn =
+ build_conn()
+ |> assign(:user, user)
- conn_one =
- conn
- |> assign(:user, user)
- |> put_req_header("idempotency-key", idempotency_key)
- |> post("/api/v1/statuses", %{
- "status" => "cofe",
- "spoiler_text" => "2hu",
- "sensitive" => "false"
- })
+ [conn: conn]
+ end
- {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
- # Six hours
- assert ttl > :timer.seconds(6 * 60 * 60 - 1)
+ test "posting a status", %{conn: conn} do
+ idempotency_key = "Pikachu rocks!"
- assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
- json_response(conn_one, 200)
+ conn_one =
+ conn
+ |> put_req_header("idempotency-key", idempotency_key)
+ |> post("/api/v1/statuses", %{
+ "status" => "cofe",
+ "spoiler_text" => "2hu",
+ "sensitive" => "false"
+ })
- assert Activity.get_by_id(id)
+ {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
+ # Six hours
+ assert ttl > :timer.seconds(6 * 60 * 60 - 1)
- conn_two =
- conn
- |> assign(:user, user)
- |> put_req_header("idempotency-key", idempotency_key)
- |> post("/api/v1/statuses", %{
- "status" => "cofe",
- "spoiler_text" => "2hu",
- "sensitive" => "false"
- })
+ assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
+ json_response(conn_one, 200)
- assert %{"id" => second_id} = json_response(conn_two, 200)
+ assert Activity.get_by_id(id)
- assert id == second_id
+ conn_two =
+ conn
+ |> put_req_header("idempotency-key", idempotency_key)
+ |> post("/api/v1/statuses", %{
+ "status" => "cofe",
+ "spoiler_text" => "2hu",
+ "sensitive" => "false"
+ })
- conn_three =
- conn
- |> assign(:user, user)
- |> post("/api/v1/statuses", %{
- "status" => "cofe",
- "spoiler_text" => "2hu",
- "sensitive" => "false"
- })
+ assert %{"id" => second_id} = json_response(conn_two, 200)
+ assert id == second_id
- assert %{"id" => third_id} = json_response(conn_three, 200)
+ conn_three =
+ conn
+ |> post("/api/v1/statuses", %{
+ "status" => "cofe",
+ "spoiler_text" => "2hu",
+ "sensitive" => "false"
+ })
+
+ assert %{"id" => third_id} = json_response(conn_three, 200)
+ refute id == third_id
+ end
+
+ test "replying to a status", %{conn: conn} do
+ user = insert(:user)
+ {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
- refute id == third_id
+ conn =
+ conn
+ |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
+
+ assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
+
+ activity = Activity.get_by_id(id)
+
+ assert activity.data["context"] == replied_to.data["context"]
+ assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
+ end
+
+ test "replying to a direct message with visibility other than direct", %{conn: conn} do
+ user = insert(:user)
+ {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
+
+ Enum.each(["public", "private", "unlisted"], fn visibility ->
+ conn =
+ conn
+ |> post("/api/v1/statuses", %{
+ "status" => "@#{user.nickname} hey",
+ "in_reply_to_id" => replied_to.id,
+ "visibility" => visibility
+ })
+
+ assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
+ end)
+ end
+
+ test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
+ conn =
+ conn
+ |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
+
+ assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
+ assert Activity.get_by_id(id)
+ end
+
+ test "posting a sensitive status", %{conn: conn} do
+ conn =
+ conn
+ |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
+
+ assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
+ assert Activity.get_by_id(id)
+ end
+
+ test "posting a fake status", %{conn: conn} do
+ real_conn =
+ conn
+ |> 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
+ |> 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)
+
+ conn =
+ conn
+ |> post("/api/v1/statuses", %{
+ "status" => "https://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
+ user2 = insert(:user)
+ content = "direct cofe @#{user2.nickname}"
+
+ conn =
+ conn
+ |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
+
+ assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
+ assert activity = Activity.get_by_id(id)
+ assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
+ assert activity.data["to"] == [user2.ap_id]
+ assert activity.data["cc"] == []
+ end
end
describe "posting polls" do
@@ -243,100 +373,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end
end
- test "posting a sensitive status", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
-
- assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
- 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
- user1 = insert(:user)
- user2 = insert(:user)
- content = "direct cofe @#{user2.nickname}"
-
- conn =
- conn
- |> assign(:user, user1)
- |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
-
- assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
- 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
-
test "direct timeline", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
@@ -501,39 +537,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert status["id"] == direct.id
end
- test "replying to a status", %{conn: conn} do
- user = insert(:user)
-
- {:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"})
-
- conn =
- conn
- |> assign(:user, user)
- |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
-
- assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
-
- activity = Activity.get_by_id(id)
-
- assert activity.data["context"] == replied_to.data["context"]
- 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
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
-
- assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
-
- activity = Activity.get_by_id(id)
-
- assert activity
- end
-
test "verify_credentials", %{conn: conn} do
user = insert(:user)
@@ -542,7 +545,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> assign(:user, user)
|> get("/api/v1/accounts/verify_credentials")
- assert %{"id" => id, "source" => %{"privacy" => "public"}} = json_response(conn, 200)
+ response = json_response(conn, 200)
+
+ assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
+ assert response["pleroma"]["chat_token"]
assert id == to_string(user.id)
end
@@ -1421,6 +1427,82 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end
end
+ describe "media upload" do
+ setup do
+ upload_config = Pleroma.Config.get([Pleroma.Upload])
+ proxy_config = Pleroma.Config.get([:media_proxy])
+
+ on_exit(fn ->
+ Pleroma.Config.put([Pleroma.Upload], upload_config)
+ Pleroma.Config.put([:media_proxy], proxy_config)
+ end)
+
+ user = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+
+ image = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ [conn: conn, image: image]
+ end
+
+ test "returns uploaded image", %{conn: conn, image: image} do
+ desc = "Description of the image"
+
+ media =
+ conn
+ |> post("/api/v1/media", %{"file" => image, "description" => desc})
+ |> json_response(:ok)
+
+ assert media["type"] == "image"
+ assert media["description"] == desc
+ assert media["id"]
+
+ object = Repo.get(Object, media["id"])
+ assert object.data["actor"] == User.ap_id(conn.assigns[:user])
+ end
+
+ test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
+ Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
+
+ proxy_url = "https://cache.pleroma.social"
+ Pleroma.Config.put([:media_proxy, :enabled], true)
+ Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
+
+ media =
+ conn
+ |> post("/api/v1/media", %{"file" => image})
+ |> json_response(:ok)
+
+ assert String.starts_with?(media["url"], proxy_url)
+ end
+
+ test "returns media url when proxy is enabled but media url is whitelisted", %{
+ conn: conn,
+ image: image
+ } do
+ media_url = "https://media.pleroma.social"
+ Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
+
+ Pleroma.Config.put([:media_proxy, :enabled], true)
+ Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
+ Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
+
+ media =
+ conn
+ |> post("/api/v1/media", %{"file" => image})
+ |> json_response(:ok)
+
+ assert String.starts_with?(media["url"], media_url)
+ end
+ end
+
describe "locked accounts" do
test "/api/v1/follow_requests works" do
user = insert(:user, %{info: %User.Info{locked: true}})
@@ -1530,32 +1612,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert id == user.id
end
- test "media upload", %{conn: conn} do
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- desc = "Description of the image"
-
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> post("/api/v1/media", %{"file" => file, "description" => desc})
-
- assert media = json_response(conn, 200)
-
- 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 "mascot upload", %{conn: conn} do
user = insert(:user)
@@ -2084,116 +2140,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end)
end
- test "account search", %{conn: conn} do
- user = insert(:user)
- user_two = insert(:user, %{nickname: "shp@shitposter.club"})
- user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
-
- results =
- conn
- |> assign(:user, user)
- |> get("/api/v1/accounts/search", %{"q" => "shp"})
- |> json_response(200)
-
- result_ids = for result <- results, do: result["acct"]
-
- assert user_two.nickname in result_ids
- assert user_three.nickname in result_ids
-
- results =
- conn
- |> assign(:user, user)
- |> get("/api/v1/accounts/search", %{"q" => "2hu"})
- |> json_response(200)
-
- result_ids = for result <- results, do: result["acct"]
-
- assert user_three.nickname in result_ids
- end
-
- test "search", %{conn: conn} do
- user = insert(:user)
- user_two = insert(:user, %{nickname: "shp@shitposter.club"})
- user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
-
- {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
-
- {:ok, _activity} =
- CommonAPI.post(user, %{
- "status" => "This is about 2hu, but private",
- "visibility" => "private"
- })
-
- {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
-
- conn =
- conn
- |> get("/api/v1/search", %{"q" => "2hu"})
-
- assert results = json_response(conn, 200)
-
- [account | _] = results["accounts"]
- assert account["id"] == to_string(user_three.id)
-
- assert results["hashtags"] == []
-
- [status] = results["statuses"]
- assert status["id"] == to_string(activity.id)
- end
-
- test "search fetches remote statuses", %{conn: conn} do
- capture_log(fn ->
- conn =
- conn
- |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
-
- assert results = json_response(conn, 200)
-
- [status] = results["statuses"]
- assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
- 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" => Object.normalize(activity).data["id"]})
-
- assert results = json_response(conn, 200)
-
- [] = results["statuses"]
- end)
- end
-
- test "search fetches remote accounts", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
-
- assert results = json_response(conn, 200)
- [account] = results["accounts"]
- assert account["acct"] == "shp@social.heldscal.la"
- end
-
- test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
- conn =
- conn
- |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "false"})
-
- assert results = json_response(conn, 200)
- assert [] == results["accounts"]
- end
-
test "returns the favorites of a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
@@ -2434,278 +2380,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end
end
- describe "updating credentials" do
- test "sets user settings in a generic way", %{conn: conn} do
- user = insert(:user)
-
- res_conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{
- "pleroma_settings_store" => %{
- pleroma_fe: %{
- theme: "bla"
- }
- }
- })
-
- assert user = json_response(res_conn, 200)
- assert user["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}}
-
- user = Repo.get(User, user["id"])
-
- res_conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{
- "pleroma_settings_store" => %{
- masto_fe: %{
- theme: "bla"
- }
- }
- })
-
- assert user = json_response(res_conn, 200)
-
- assert user["pleroma"]["settings_store"] ==
- %{
- "pleroma_fe" => %{"theme" => "bla"},
- "masto_fe" => %{"theme" => "bla"}
- }
-
- user = Repo.get(User, user["id"])
-
- res_conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{
- "pleroma_settings_store" => %{
- masto_fe: %{
- theme: "blub"
- }
- }
- })
-
- assert user = json_response(res_conn, 200)
-
- assert user["pleroma"]["settings_store"] ==
- %{
- "pleroma_fe" => %{"theme" => "bla"},
- "masto_fe" => %{"theme" => "blub"}
- }
- end
-
- test "updates the user's bio", %{conn: conn} do
- user = insert(:user)
- user2 = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{
- "note" => "I drink #cofe with @#{user2.nickname}"
- })
-
- assert user = json_response(conn, 200)
-
- assert user["note"] ==
- ~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
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{locked: "true"})
-
- assert user = json_response(conn, 200)
- assert user["locked"] == true
- end
-
- test "updates the user's default scope", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"})
-
- assert user = json_response(conn, 200)
- assert user["source"]["privacy"] == "cofe"
- end
-
- test "updates the user's hide_followers status", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"})
-
- assert user = json_response(conn, 200)
- assert user["pleroma"]["hide_followers"] == true
- end
-
- test "updates the user's skip_thread_containment option", %{conn: conn} do
- user = insert(:user)
-
- response =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"})
- |> json_response(200)
-
- assert response["pleroma"]["skip_thread_containment"] == true
- assert refresh_record(user).info.skip_thread_containment
- end
-
- test "updates the user's hide_follows status", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"})
-
- assert user = json_response(conn, 200)
- assert user["pleroma"]["hide_follows"] == true
- end
-
- test "updates the user's hide_favorites status", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
-
- assert user = json_response(conn, 200)
- assert user["pleroma"]["hide_favorites"] == true
- end
-
- test "updates the user's show_role status", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"})
-
- assert user = json_response(conn, 200)
- assert user["source"]["pleroma"]["show_role"] == false
- end
-
- test "updates the user's no_rich_text status", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
-
- assert user = json_response(conn, 200)
- assert user["source"]["pleroma"]["no_rich_text"] == true
- end
-
- test "updates the user's name", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
-
- assert user = json_response(conn, 200)
- assert user["display_name"] == "markorepairs"
- end
-
- test "updates the user's avatar", %{conn: conn} do
- user = insert(:user)
-
- new_avatar = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
-
- assert user_response = json_response(conn, 200)
- assert user_response["avatar"] != User.avatar_url(user)
- end
-
- test "updates the user's banner", %{conn: conn} do
- user = insert(:user)
-
- new_header = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
-
- 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
-
- test "updates profile emojos", %{conn: conn} do
- user = insert(:user)
-
- note = "*sips :blank:*"
- name = "I am :firefox:"
-
- conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{
- "note" => note,
- "display_name" => name
- })
-
- assert json_response(conn, 200)
-
- conn =
- conn
- |> get("/api/v1/accounts/#{user.id}")
-
- assert user = json_response(conn, 200)
-
- assert user["note"] == note
- assert user["display_name"] == name
- assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
- end
- end
-
test "get instance information", %{conn: conn} do
conn = get(conn, "/api/v1/instance")
assert result = json_response(conn, 200)
@@ -2886,7 +2560,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end
test "returns rich-media card", %{conn: conn, user: user} do
- {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
card_data = %{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
@@ -2918,7 +2592,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
# works with private posts
{:ok, activity} =
- CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"})
+ CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
response_two =
conn
@@ -2930,7 +2604,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end
test "replaces missing description with an empty string", %{conn: conn, user: user} do
- {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp-missing-data"})
+ {:ok, activity} =
+ CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
response =
conn
@@ -3501,24 +3176,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end
describe "create account by app" do
- setup do
- enabled = Pleroma.Config.get([:app_account_creation, :enabled])
- max_requests = Pleroma.Config.get([:app_account_creation, :max_requests])
- interval = Pleroma.Config.get([:app_account_creation, :interval])
-
- Pleroma.Config.put([:app_account_creation, :enabled], true)
- Pleroma.Config.put([:app_account_creation, :max_requests], 5)
- Pleroma.Config.put([:app_account_creation, :interval], 1)
-
- on_exit(fn ->
- Pleroma.Config.put([:app_account_creation, :enabled], enabled)
- Pleroma.Config.put([:app_account_creation, :max_requests], max_requests)
- Pleroma.Config.put([:app_account_creation, :interval], interval)
- end)
-
- :ok
- end
-
test "Account registration via Application", %{conn: conn} do
conn =
conn
@@ -3621,7 +3278,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
agreement: true
})
- assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."}
+ assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
end
end
diff --git a/test/web/mastodon_api/search_controller_test.exs b/test/web/mastodon_api/search_controller_test.exs
new file mode 100644
index 000000000..c3f531590
--- /dev/null
+++ b/test/web/mastodon_api/search_controller_test.exs
@@ -0,0 +1,128 @@
+# 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.SearchControllerTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.Object
+ alias Pleroma.Web.CommonAPI
+ import Pleroma.Factory
+ import ExUnit.CaptureLog
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ test "account search", %{conn: conn} do
+ user = insert(:user)
+ user_two = insert(:user, %{nickname: "shp@shitposter.club"})
+ user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
+
+ results =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/search", %{"q" => "shp"})
+ |> json_response(200)
+
+ result_ids = for result <- results, do: result["acct"]
+
+ assert user_two.nickname in result_ids
+ assert user_three.nickname in result_ids
+
+ results =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/search", %{"q" => "2hu"})
+ |> json_response(200)
+
+ result_ids = for result <- results, do: result["acct"]
+
+ assert user_three.nickname in result_ids
+ end
+
+ test "search", %{conn: conn} do
+ user = insert(:user)
+ user_two = insert(:user, %{nickname: "shp@shitposter.club"})
+ user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{
+ "status" => "This is about 2hu, but private",
+ "visibility" => "private"
+ })
+
+ {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
+
+ conn =
+ conn
+ |> get("/api/v1/search", %{"q" => "2hu"})
+
+ assert results = json_response(conn, 200)
+
+ [account | _] = results["accounts"]
+ assert account["id"] == to_string(user_three.id)
+
+ assert results["hashtags"] == []
+
+ [status] = results["statuses"]
+ assert status["id"] == to_string(activity.id)
+ end
+
+ test "search fetches remote statuses", %{conn: conn} do
+ capture_log(fn ->
+ conn =
+ conn
+ |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
+
+ assert results = json_response(conn, 200)
+
+ [status] = results["statuses"]
+ assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
+ 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" => Object.normalize(activity).data["id"]})
+
+ assert results = json_response(conn, 200)
+
+ [] = results["statuses"]
+ end)
+ end
+
+ test "search fetches remote accounts", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
+
+ assert results = json_response(conn, 200)
+ [account] = results["accounts"]
+ assert account["acct"] == "shp@social.heldscal.la"
+ end
+
+ test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
+ conn =
+ conn
+ |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "false"})
+
+ assert results = json_response(conn, 200)
+ assert [] == results["accounts"]
+ end
+end
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index 1c04ac9ad..aae34804d 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.Web.OAuth.Authorization
+ alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.Token
@oauth_config_path [:oauth2, :issue_new_refresh_token]
@@ -49,7 +50,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
%{
"response_type" => "code",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"scope" => "read"
}
)
@@ -72,7 +73,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"authorization" => %{
"scope" => "read follow",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"state" => "a_state"
}
}
@@ -98,11 +99,12 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
%{app: app, conn: conn} do
registration = insert(:registration)
+ redirect_uri = OAuthController.default_redirect_uri(app)
state_params = %{
"scope" => Enum.join(app.scopes, " "),
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => redirect_uri,
"state" => ""
}
@@ -121,7 +123,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
)
assert response = html_response(conn, 302)
- assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+ assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
end
end
@@ -132,7 +134,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
state_params = %{
"scope" => "read write",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"state" => "a_state"
}
@@ -165,7 +167,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
state_params = %{
"scope" => Enum.join(app.scopes, " "),
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"state" => ""
}
@@ -199,7 +201,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"authorization" => %{
"scopes" => app.scopes,
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"state" => "a_state",
"nickname" => nil,
"email" => "john@doe.com"
@@ -218,6 +220,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
conn: conn
} do
registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
+ redirect_uri = OAuthController.default_redirect_uri(app)
conn =
conn
@@ -229,7 +232,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"authorization" => %{
"scopes" => app.scopes,
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => redirect_uri,
"state" => "a_state",
"nickname" => "availablenick",
"email" => "available@email.com"
@@ -238,7 +241,36 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
)
assert response = html_response(conn, 302)
- assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+ assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
+ end
+
+ test "with unlisted `redirect_uri`, POST /oauth/register?op=register results in HTTP 401",
+ %{
+ app: app,
+ conn: conn
+ } do
+ registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
+ unlisted_redirect_uri = "http://cross-site-request.com"
+
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post(
+ "/oauth/register",
+ %{
+ "op" => "register",
+ "authorization" => %{
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => unlisted_redirect_uri,
+ "state" => "a_state",
+ "nickname" => "availablenick",
+ "email" => "available@email.com"
+ }
+ }
+ )
+
+ assert response = html_response(conn, 401)
end
test "with invalid params, POST /oauth/register?op=register renders registration_details page",
@@ -254,7 +286,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"authorization" => %{
"scopes" => app.scopes,
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"state" => "a_state",
"nickname" => "availablenickname",
"email" => "available@email.com"
@@ -286,6 +318,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
} do
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
registration = insert(:registration, user: nil)
+ redirect_uri = OAuthController.default_redirect_uri(app)
conn =
conn
@@ -297,7 +330,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"authorization" => %{
"scopes" => app.scopes,
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => redirect_uri,
"state" => "a_state",
"name" => user.nickname,
"password" => "testpassword"
@@ -306,7 +339,37 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
)
assert response = html_response(conn, 302)
- assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+ assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
+ end
+
+ test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in HTTP 401`",
+ %{
+ app: app,
+ conn: conn
+ } do
+ user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
+ registration = insert(:registration, user: nil)
+ unlisted_redirect_uri = "http://cross-site-request.com"
+
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post(
+ "/oauth/register",
+ %{
+ "op" => "connect",
+ "authorization" => %{
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => unlisted_redirect_uri,
+ "state" => "a_state",
+ "name" => user.nickname,
+ "password" => "testpassword"
+ }
+ }
+ )
+
+ assert response = html_response(conn, 401)
end
test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
@@ -322,7 +385,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"authorization" => %{
"scopes" => app.scopes,
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"state" => "a_state",
"name" => user.nickname,
"password" => "wrong password"
@@ -358,7 +421,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
%{
"response_type" => "code",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"scope" => "read"
}
)
@@ -378,7 +441,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"authorization" => %{
"response_type" => "code",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"scope" => "read"
}
}
@@ -399,7 +462,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
%{
"response_type" => "code",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"scope" => "read",
"force_login" => "true"
}
@@ -408,7 +471,61 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
assert html_response(conn, 200) =~ ~s(type="submit")
end
- test "redirects to app if user is already authenticated", %{app: app, conn: conn} do
+ test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params",
+ %{
+ 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" => OAuthController.default_redirect_uri(app),
+ "state" => "specific_client_state",
+ "scope" => "read"
+ }
+ )
+
+ assert URI.decode(redirected_to(conn)) ==
+ "https://redirect.url?access_token=#{token.token}&state=specific_client_state"
+ end
+
+ test "with existing authentication and unlisted non-OOB `redirect_uri`, redirects without credentials",
+ %{
+ app: app,
+ conn: conn
+ } do
+ unlisted_redirect_uri = "http://cross-site-request.com"
+ 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" => unlisted_redirect_uri,
+ "state" => "specific_client_state",
+ "scope" => "read"
+ }
+ )
+
+ assert redirected_to(conn) == unlisted_redirect_uri
+ end
+
+ test "with existing authentication and OOB `redirect_uri`, redirects to app with `token` and `state` params",
+ %{
+ app: app,
+ conn: conn
+ } do
token = insert(:oauth_token, app_id: app.id)
conn =
@@ -419,12 +536,12 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
%{
"response_type" => "code",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
"scope" => "read"
}
)
- assert redirected_to(conn) == "https://redirect.url"
+ assert html_response(conn, 200) =~ "Authorization exists"
end
end
@@ -432,6 +549,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
test "redirects with oauth authorization" do
user = insert(:user)
app = insert(:oauth_app, scopes: ["read", "write", "follow"])
+ redirect_uri = OAuthController.default_redirect_uri(app)
conn =
build_conn()
@@ -440,14 +558,14 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"name" => user.nickname,
"password" => "test",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => redirect_uri,
"scope" => "read write",
"state" => "statepassed"
}
})
target = redirected_to(conn)
- assert target =~ app.redirect_uris
+ assert target =~ redirect_uri
query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
@@ -460,6 +578,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
test "returns 401 for wrong credentials", %{conn: conn} do
user = insert(:user)
app = insert(:oauth_app)
+ redirect_uri = OAuthController.default_redirect_uri(app)
result =
conn
@@ -468,7 +587,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"name" => user.nickname,
"password" => "wrong",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => redirect_uri,
"state" => "statepassed",
"scope" => Enum.join(app.scopes, " ")
}
@@ -477,7 +596,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
# Keep the details
assert result =~ app.client_id
- assert result =~ app.redirect_uris
+ assert result =~ redirect_uri
# Error message
assert result =~ "Invalid Username/Password"
@@ -486,6 +605,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
test "returns 401 for missing scopes", %{conn: conn} do
user = insert(:user)
app = insert(:oauth_app)
+ redirect_uri = OAuthController.default_redirect_uri(app)
result =
conn
@@ -494,7 +614,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"name" => user.nickname,
"password" => "test",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => redirect_uri,
"state" => "statepassed",
"scope" => ""
}
@@ -503,7 +623,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
# Keep the details
assert result =~ app.client_id
- assert result =~ app.redirect_uris
+ assert result =~ redirect_uri
# Error message
assert result =~ "This action is outside the authorized scopes"
@@ -512,6 +632,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
test "returns 401 for scopes beyond app scopes", %{conn: conn} do
user = insert(:user)
app = insert(:oauth_app, scopes: ["read", "write"])
+ redirect_uri = OAuthController.default_redirect_uri(app)
result =
conn
@@ -520,7 +641,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"name" => user.nickname,
"password" => "test",
"client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => redirect_uri,
"state" => "statepassed",
"scope" => "read write follow"
}
@@ -529,7 +650,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
# Keep the details
assert result =~ app.client_id
- assert result =~ app.redirect_uris
+ assert result =~ redirect_uri
# Error message
assert result =~ "This action is outside the authorized scopes"
@@ -548,7 +669,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|> post("/oauth/token", %{
"grant_type" => "authorization_code",
"code" => auth.token,
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
@@ -602,7 +723,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|> post("/oauth/token", %{
"grant_type" => "authorization_code",
"code" => auth.token,
- "redirect_uri" => app.redirect_uris
+ "redirect_uri" => OAuthController.default_redirect_uri(app)
})
assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200)
@@ -647,7 +768,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|> post("/oauth/token", %{
"grant_type" => "authorization_code",
"code" => auth.token,
- "redirect_uri" => app.redirect_uris
+ "redirect_uri" => OAuthController.default_redirect_uri(app)
})
assert resp = json_response(conn, 400)
@@ -726,7 +847,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|> post("/oauth/token", %{
"grant_type" => "authorization_code",
"code" => "Imobviouslyinvalid",
- "redirect_uri" => app.redirect_uris,
+ "redirect_uri" => OAuthController.default_redirect_uri(app),
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
diff --git a/test/web/rich_media/helpers_test.exs b/test/web/rich_media/helpers_test.exs
index 53b0596f5..c8f442b05 100644
--- a/test/web/rich_media/helpers_test.exs
+++ b/test/web/rich_media/helpers_test.exs
@@ -1,14 +1,19 @@
defmodule Pleroma.Web.RichMedia.HelpersTest do
use Pleroma.DataCase
+ alias Pleroma.Config
alias Pleroma.Object
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.RichMedia.Helpers
import Pleroma.Factory
import Tesla.Mock
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ rich_media = Config.get([:rich_media, :enabled])
+ on_exit(fn -> Config.put([:rich_media, :enabled], rich_media) end)
+
:ok
end
@@ -21,11 +26,9 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
"content_type" => "text/markdown"
})
- Pleroma.Config.put([:rich_media, :enabled], true)
+ 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
@@ -37,11 +40,9 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
"content_type" => "text/markdown"
})
- Pleroma.Config.put([:rich_media, :enabled], true)
+ 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
@@ -49,16 +50,14 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
{:ok, activity} =
CommonAPI.post(user, %{
- "status" => "[test](http://example.com/ogp)",
+ "status" => "[test](https://example.com/ogp)",
"content_type" => "text/markdown"
})
- Pleroma.Config.put([:rich_media, :enabled], true)
+ Config.put([:rich_media, :enabled], true)
- assert %{page_url: "http://example.com/ogp", rich_media: _} =
+ assert %{page_url: "https://example.com/ogp", rich_media: _} =
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
-
- Pleroma.Config.put([:rich_media, :enabled], false)
end
test "refuses to crawl URLs from posts marked sensitive" do
@@ -74,11 +73,9 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
assert object.data["sensitive"]
- Pleroma.Config.put([:rich_media, :enabled], true)
+ 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 URLs from posts tagged NSFW" do
@@ -93,10 +90,28 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
assert object.data["sensitive"]
- Pleroma.Config.put([:rich_media, :enabled], true)
+ Config.put([:rich_media, :enabled], true)
assert %{} = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+ end
+
+ test "refuses to crawl URLs of private network from posts" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{"status" => "http://127.0.0.1:4000/notice/9kCP7VNyPJXFOXDrgO"})
+
+ {:ok, activity2} = CommonAPI.post(user, %{"status" => "https://10.111.10.1/notice/9kCP7V"})
+ {:ok, activity3} = CommonAPI.post(user, %{"status" => "https://172.16.32.40/notice/9kCP7V"})
+ {:ok, activity4} = CommonAPI.post(user, %{"status" => "https://192.168.10.40/notice/9kCP7V"})
+ {:ok, activity5} = CommonAPI.post(user, %{"status" => "https://pleroma.local/notice/9kCP7V"})
+
+ Config.put([:rich_media, :enabled], true)
- Pleroma.Config.put([:rich_media, :enabled], false)
+ assert %{} = Helpers.fetch_data_for_activity(activity)
+ assert %{} = Helpers.fetch_data_for_activity(activity2)
+ assert %{} = Helpers.fetch_data_for_activity(activity3)
+ assert %{} = Helpers.fetch_data_for_activity(activity4)
+ assert %{} = Helpers.fetch_data_for_activity(activity5)
end
end
diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs
index 3a9cc1854..bc48341ca 100644
--- a/test/web/rich_media/parser_test.exs
+++ b/test/web/rich_media/parser_test.exs
@@ -11,6 +11,21 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
%{
method: :get,
+ url: "http://example.com/non-ogp"
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")}
+
+ %{
+ method: :get,
+ url: "http://example.com/ogp-missing-title"
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/rich_media/ogp-missing-title.html")
+ }
+
+ %{
+ method: :get,
url: "http://example.com/twitter-card"
} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
@@ -38,6 +53,11 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
assert {:error, _} = Pleroma.Web.RichMedia.Parser.parse("http://example.com/empty")
end
+ test "doesn't just add a title" do
+ assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") ==
+ {:error, "Found metadata was invalid or incomplete: %{}"}
+ end
+
test "parses ogp" do
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp") ==
{:ok,
@@ -51,6 +71,19 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
}}
end
+ test "falls back to <title> when ogp:title is missing" do
+ assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp-missing-title") ==
+ {:ok,
+ %{
+ image: "http://ia.media-imdb.com/images/rock.jpg",
+ title: "The Rock (1996)",
+ description:
+ "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
+ 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,
diff --git a/test/web/streamer_test.exs b/test/web/streamer_test.exs
index c18b9f9fe..4633d7765 100644
--- a/test/web/streamer_test.exs
+++ b/test/web/streamer_test.exs
@@ -21,6 +21,52 @@ defmodule Pleroma.Web.StreamerTest do
:ok
end
+ describe "user streams" do
+ setup do
+ GenServer.start(Streamer, %{}, name: Streamer)
+
+ on_exit(fn ->
+ if pid = Process.whereis(Streamer) do
+ Process.exit(pid, :kill)
+ end
+ end)
+
+ user = insert(:user)
+ notify = insert(:notification, user: user, activity: build(:note_activity))
+ {:ok, %{user: user, notify: notify}}
+ end
+
+ test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do
+ task =
+ Task.async(fn ->
+ assert_receive {:text, _}, 4_000
+ end)
+
+ Streamer.add_socket(
+ "user",
+ %{transport_pid: task.pid, assigns: %{user: user}}
+ )
+
+ Streamer.stream("user", notify)
+ Task.await(task)
+ end
+
+ test "it sends notify to in the 'user:notification' stream", %{user: user, notify: notify} do
+ task =
+ Task.async(fn ->
+ assert_receive {:text, _}, 4_000
+ end)
+
+ Streamer.add_socket(
+ "user:notification",
+ %{transport_pid: task.pid, assigns: %{user: user}}
+ )
+
+ Streamer.stream("user:notification", notify)
+ Task.await(task)
+ end
+ end
+
test "it sends to public" do
user = insert(:user)
other_user = insert(:user)
@@ -310,4 +356,110 @@ defmodule Pleroma.Web.StreamerTest do
Task.await(task)
end
+
+ describe "direct streams" do
+ setup do
+ GenServer.start(Streamer, %{}, name: Streamer)
+
+ on_exit(fn ->
+ if pid = Process.whereis(Streamer) do
+ Process.exit(pid, :kill)
+ end
+ end)
+
+ :ok
+ end
+
+ test "it sends conversation update to the 'direct' stream", %{} do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ task =
+ Task.async(fn ->
+ assert_receive {:text, _received_event}, 4_000
+ end)
+
+ Streamer.add_socket(
+ "direct",
+ %{transport_pid: task.pid, assigns: %{user: user}}
+ )
+
+ {:ok, _create_activity} =
+ CommonAPI.post(another_user, %{
+ "status" => "hey @#{user.nickname}",
+ "visibility" => "direct"
+ })
+
+ Task.await(task)
+ end
+
+ test "it doesn't send conversation update to the 'direct' streamj when the last message in the conversation is deleted" do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ {:ok, create_activity} =
+ CommonAPI.post(another_user, %{
+ "status" => "hi @#{user.nickname}",
+ "visibility" => "direct"
+ })
+
+ task =
+ Task.async(fn ->
+ assert_receive {:text, received_event}, 4_000
+ assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event)
+
+ refute_receive {:text, _}, 4_000
+ end)
+
+ Streamer.add_socket(
+ "direct",
+ %{transport_pid: task.pid, assigns: %{user: user}}
+ )
+
+ {:ok, _} = CommonAPI.delete(create_activity.id, another_user)
+
+ Task.await(task)
+ end
+
+ test "it sends conversation update to the 'direct' stream when a message is deleted" do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ {:ok, create_activity} =
+ CommonAPI.post(another_user, %{
+ "status" => "hi @#{user.nickname}",
+ "visibility" => "direct"
+ })
+
+ {:ok, create_activity2} =
+ CommonAPI.post(another_user, %{
+ "status" => "hi @#{user.nickname}",
+ "in_reply_to_status_id" => create_activity.id,
+ "visibility" => "direct"
+ })
+
+ task =
+ Task.async(fn ->
+ assert_receive {:text, received_event}, 4_000
+ assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event)
+
+ assert_receive {:text, received_event}, 4_000
+
+ assert %{"event" => "conversation", "payload" => received_payload} =
+ Jason.decode!(received_event)
+
+ assert %{"last_status" => last_status} = Jason.decode!(received_payload)
+ assert last_status["id"] == to_string(create_activity.id)
+ end)
+
+ Streamer.add_socket(
+ "direct",
+ %{transport_pid: task.pid, assigns: %{user: user}}
+ )
+
+ {:ok, _} = CommonAPI.delete(create_activity2.id, another_user)
+
+ Task.await(task)
+ end
+ end
end
diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs
new file mode 100644
index 000000000..6b9da8204
--- /dev/null
+++ b/test/web/twitter_api/password_controller_test.exs
@@ -0,0 +1,56 @@
+defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.PasswordResetToken
+ alias Pleroma.Web.OAuth.Token
+ import Pleroma.Factory
+
+ describe "GET /api/pleroma/password_reset/token" do
+ test "it returns error when token invalid", %{conn: conn} do
+ response =
+ conn
+ |> get("/api/pleroma/password_reset/token")
+ |> html_response(:ok)
+
+ assert response =~ "<h2>Invalid Token</h2>"
+ end
+
+ test "it shows password reset form", %{conn: conn} do
+ user = insert(:user)
+ {:ok, token} = PasswordResetToken.create_token(user)
+
+ response =
+ conn
+ |> get("/api/pleroma/password_reset/#{token.token}")
+ |> html_response(:ok)
+
+ assert response =~ "<h2>Password Reset for #{user.nickname}</h2>"
+ end
+ end
+
+ describe "POST /api/pleroma/password_reset" do
+ test "it returns HTTP 200", %{conn: conn} do
+ user = insert(:user)
+ {:ok, token} = PasswordResetToken.create_token(user)
+ {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{})
+
+ params = %{
+ "password" => "test",
+ password_confirmation: "test",
+ token: token.token
+ }
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> post("/api/pleroma/password_reset", %{data: params})
+ |> html_response(:ok)
+
+ assert response =~ "<h2>Password changed!</h2>"
+
+ user = refresh_record(user)
+ assert Comeonin.Pbkdf2.checkpw("test", user.password_hash)
+ assert length(Token.get_user_tokens(user)) == 0
+ end
+ end
+end