Merge upstream (#81)

This commit is contained in:
kibigo!
2017-07-15 14:33:15 -07:00
213 changed files with 2714 additions and 1364 deletions

View File

@@ -38,7 +38,7 @@ RSpec.describe AccountsController, type: :controller do
context 'activitystreams2' do
before do
get :show, params: { username: alice.username }, format: 'activitystreams2'
get :show, params: { username: alice.username }, format: 'json'
end
it 'assigns @account' do

View File

@@ -1,69 +0,0 @@
require 'rails_helper'
RSpec.describe Api::ActivityPub::ActivitiesController, type: :controller do
render_views
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
describe 'GET #show' do
describe 'normal status' do
public_status = nil
before do
public_status = Fabricate(:status, account: user.account, text: 'Hello world', visibility: :public)
@request.env['HTTP_ACCEPT'] = 'application/activity+json'
get :show_status, params: { id: public_status.id }
end
it 'returns http success' do
expect(response).to have_http_status(:success)
end
it 'sets Content-Type header to AS2' do
expect(response.header['Content-Type']).to include 'application/activity+json'
end
it 'returns http success' do
json_data = JSON.parse(response.body)
expect(json_data).to include('@context' => 'https://www.w3.org/ns/activitystreams')
expect(json_data).to include('type' => 'Create')
expect(json_data).to include('id' => @request.url)
expect(json_data).to include('type' => 'Create')
expect(json_data).to include('object' => api_activitypub_note_url(public_status))
expect(json_data).to include('url' => TagManager.instance.url_for(public_status))
end
end
describe 'reblog' do
original = nil
reblog = nil
before do
original = Fabricate(:status, account: user.account, text: 'Hello world', visibility: :public)
reblog = Fabricate(:status, account: user.account, reblog_of_id: original.id, visibility: :public)
@request.env['HTTP_ACCEPT'] = 'application/activity+json'
get :show_status, params: { id: reblog.id }
end
it 'returns http success' do
expect(response).to have_http_status(:success)
end
it 'sets Content-Type header to AS2' do
expect(response.header['Content-Type']).to include 'application/activity+json'
end
it 'returns http success' do
json_data = JSON.parse(response.body)
expect(json_data).to include('@context' => 'https://www.w3.org/ns/activitystreams')
expect(json_data).to include('type' => 'Announce')
expect(json_data).to include('id' => @request.url)
expect(json_data).to include('type' => 'Announce')
expect(json_data).to include('object' => api_activitypub_status_url(original))
expect(json_data).to include('url' => TagManager.instance.url_for(reblog))
end
end
end
end

View File

@@ -1,73 +0,0 @@
require 'rails_helper'
RSpec.describe Api::ActivityPub::NotesController, type: :controller do
render_views
let(:user_alice) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:user_bob) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
describe 'GET #show' do
describe 'normal status' do
public_status = nil
before do
public_status = Fabricate(:status, account: user_alice.account, text: 'Hello world', visibility: :public)
@request.env['HTTP_ACCEPT'] = 'application/activity+json'
get :show, params: { id: public_status.id }
end
it 'returns http success' do
expect(response).to have_http_status(:success)
end
it 'sets Content-Type header to AS2' do
expect(response.header['Content-Type']).to include 'application/activity+json'
end
it 'returns http success' do
json_data = JSON.parse(response.body)
expect(json_data).to include('@context' => 'https://www.w3.org/ns/activitystreams')
expect(json_data).to include('type' => 'Note')
expect(json_data).to include('id' => @request.url)
expect(json_data).to include('name' => 'Hello world')
expect(json_data).to include('content' => 'Hello world')
expect(json_data).to include('published')
expect(json_data).to include('url' => TagManager.instance.url_for(public_status))
end
end
describe 'reply' do
original = nil
reply = nil
before do
original = Fabricate(:status, account: user_alice.account, text: 'Hello world', visibility: :public)
reply = Fabricate(:status, account: user_bob.account, text: 'Hello world', in_reply_to_id: original.id, visibility: :public)
@request.env['HTTP_ACCEPT'] = 'application/activity+json'
get :show, params: { id: reply.id }
end
it 'returns http success' do
expect(response).to have_http_status(:success)
end
it 'sets Content-Type header to AS2' do
expect(response.header['Content-Type']).to include 'application/activity+json'
end
it 'returns http success' do
json_data = JSON.parse(response.body)
expect(json_data).to include('@context' => 'https://www.w3.org/ns/activitystreams')
expect(json_data).to include('type' => 'Note')
expect(json_data).to include('id' => @request.url)
expect(json_data).to include('name' => 'Hello world')
expect(json_data).to include('content' => 'Hello world')
expect(json_data).to include('published')
expect(json_data).to include('url' => TagManager.instance.url_for(reply))
expect(json_data).to include('inReplyTo' => api_activitypub_note_url(original))
end
end
end
end

View File

@@ -1,156 +0,0 @@
require 'rails_helper'
RSpec.describe Api::ActivityPub::OutboxController, type: :controller do
render_views
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
describe 'GET #show' do
before do
@request.headers['ACCEPT'] = 'application/activity+json'
end
describe 'collection with small number of statuses' do
public_status = nil
before do
public_status = Fabricate(:status, account: user.account, text: 'Hello world', visibility: :public)
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :private)
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :unlisted)
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :direct)
get :show, params: { id: user.account.id }
end
it 'returns http success' do
expect(response).to have_http_status(:success)
end
it 'sets Content-Type header to AS2' do
expect(response.header['Content-Type']).to include 'application/activity+json'
end
it 'returns AS2 JSON body' do
json_data = JSON.parse(response.body)
expect(json_data).to include('@context' => 'https://www.w3.org/ns/activitystreams')
expect(json_data).to include('id' => @request.url)
expect(json_data).to include('type' => 'OrderedCollection')
expect(json_data).to include('totalItems' => 1)
expect(json_data).to include('current')
expect(json_data).to include('first')
expect(json_data).to include('last')
end
end
describe 'collection with large number of statuses' do
before do
30.times do
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :public)
end
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :private)
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :unlisted)
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :direct)
get :show, params: { id: user.account.id }
end
it 'returns http success' do
expect(response).to have_http_status(:success)
end
it 'sets Content-Type header to AS2' do
expect(response.header['Content-Type']).to include 'application/activity+json'
end
it 'returns AS2 JSON body' do
json_data = JSON.parse(response.body)
expect(json_data).to include('@context' => 'https://www.w3.org/ns/activitystreams')
expect(json_data).to include('id' => @request.url)
expect(json_data).to include('type' => 'OrderedCollection')
expect(json_data).to include('totalItems' => 30)
expect(json_data).to include('current')
expect(json_data).to include('first')
expect(json_data).to include('last')
end
end
describe 'page with small number of statuses' do
statuses = []
before do
5.times do
statuses << Fabricate(:status, account: user.account, text: 'Hello world', visibility: :public)
end
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :private)
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :unlisted)
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :direct)
get :show, params: { id: user.account.id, max_id: statuses.last.id + 1 }
end
it 'returns http success' do
expect(response).to have_http_status(:success)
end
it 'sets Content-Type header to AS2' do
expect(response.header['Content-Type']).to include 'application/activity+json'
end
it 'returns AS2 JSON body' do
json_data = JSON.parse(response.body)
expect(json_data).to include('@context' => 'https://www.w3.org/ns/activitystreams')
expect(json_data).to include('id' => @request.url)
expect(json_data).to include('type' => 'OrderedCollectionPage')
expect(json_data).to include('partOf')
expect(json_data).to include('items')
expect(json_data['items'].length).to eq(5)
expect(json_data).to include('prev')
expect(json_data).to include('next')
expect(json_data).to include('current')
expect(json_data).to include('first')
expect(json_data).to include('last')
end
end
describe 'page with large number of statuses' do
statuses = []
before do
30.times do
statuses << Fabricate(:status, account: user.account, text: 'Hello world', visibility: :public)
end
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :private)
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :unlisted)
Fabricate(:status, account: user.account, text: 'Hello world', visibility: :direct)
get :show, params: { id: user.account.id, max_id: statuses.last.id + 1 }
end
it 'returns http success' do
expect(response).to have_http_status(:success)
end
it 'sets Content-Type header to AS2' do
expect(response.header['Content-Type']).to include 'application/activity+json'
end
it 'returns AS2 JSON body' do
json_data = JSON.parse(response.body)
expect(json_data).to include('@context' => 'https://www.w3.org/ns/activitystreams')
expect(json_data).to include('id' => @request.url)
expect(json_data).to include('type' => 'OrderedCollectionPage')
expect(json_data).to include('partOf')
expect(json_data).to include('items')
expect(json_data['items'].length).to eq(20)
expect(json_data).to include('prev')
expect(json_data).to include('next')
expect(json_data).to include('current')
expect(json_data).to include('first')
expect(json_data).to include('last')
end
end
end
end

View File

@@ -21,6 +21,7 @@ RSpec.describe Api::PushController, type: :controller do
'https://callback.host/api',
'as1234df',
'3600',
nil
)
expect(response).to have_http_status(:success)
end

View File

@@ -0,0 +1,81 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::Web::PushSubscriptionsController do
render_views
let(:user) { Fabricate(:user) }
let(:create_payload) do
{
data: {
endpoint: 'https://fcm.googleapis.com/fcm/send/fiuH06a27qE:APA91bHnSiGcLwdaxdyqVXNDR9w1NlztsHb6lyt5WDKOC_Z_Q8BlFxQoR8tWFSXUIDdkyw0EdvxTu63iqamSaqVSevW5LfoFwojws8XYDXv_NRRLH6vo2CdgiN4jgHv5VLt2A8ah6lUX',
keys: {
p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=',
auth: 'eH_C8rq2raXqlcBVDa1gLg==',
},
}
}
end
let(:alerts_payload) do
{
data: {
alerts: {
follow: true,
favourite: false,
reblog: true,
mention: false,
}
}
}
end
describe 'POST #create' do
it 'saves push subscriptions' do
sign_in(user)
stub_request(:post, create_payload[:data][:endpoint]).to_return(status: 200)
post :create, format: :json, params: create_payload
user.reload
push_subscription = Web::PushSubscription.find_by(endpoint: create_payload[:data][:endpoint])
expect(push_subscription['endpoint']).to eq(create_payload[:data][:endpoint])
expect(push_subscription['key_p256dh']).to eq(create_payload[:data][:keys][:p256dh])
expect(push_subscription['key_auth']).to eq(create_payload[:data][:keys][:auth])
end
it 'sends welcome notification' do
sign_in(user)
stub_request(:post, create_payload[:data][:endpoint]).to_return(status: 200)
post :create, format: :json, params: create_payload
end
end
describe 'PUT #update' do
it 'changes alert settings' do
sign_in(user)
stub_request(:post, create_payload[:data][:endpoint]).to_return(status: 200)
post :create, format: :json, params: create_payload
alerts_payload[:id] = Web::PushSubscription.find_by(endpoint: create_payload[:data][:endpoint]).id
put :update, format: :json, params: alerts_payload
push_subscription = Web::PushSubscription.find_by(endpoint: create_payload[:data][:endpoint])
expect(push_subscription.data['follow']).to eq(alerts_payload[:data][:follow])
expect(push_subscription.data['favourite']).to eq(alerts_payload[:data][:favourite])
expect(push_subscription.data['reblog']).to eq(alerts_payload[:data][:reblog])
expect(push_subscription.data['mention']).to eq(alerts_payload[:data][:mention])
end
end
end

View File

@@ -0,0 +1,74 @@
# frozen_string_literal: true
require 'rails_helper'
describe ApplicationController, type: :controller do
controller do
include SignatureVerification
def success
head 200
end
def alternative_success
head 200
end
end
before do
routes.draw { get 'success' => 'anonymous#success' }
end
context 'without signature header' do
before do
get :success
end
describe '#signed_request?' do
it 'returns false' do
expect(controller.signed_request?).to be false
end
end
describe '#signed_request_account' do
it 'returns nil' do
expect(controller.signed_request_account).to be_nil
end
end
end
context 'with signature header' do
let!(:author) { Fabricate(:account) }
before do
get :success
fake_request = Request.new(:get, request.url)
fake_request.on_behalf_of(author)
request.headers.merge!(fake_request.headers)
end
describe '#signed_request?' do
it 'returns true' do
expect(controller.signed_request?).to be true
end
end
describe '#signed_request_account' do
it 'returns an account' do
expect(controller.signed_request_account).to eq author
end
it 'returns nil when path does not match' do
request.path = '/alternative-path'
expect(controller.signed_request_account).to be_nil
end
it 'returns nil when method does not match' do
post :success
expect(controller.signed_request_account).to be_nil
end
end
end
end

View File

@@ -9,7 +9,7 @@ describe WellKnown::WebfingerController, type: :controller do
end
before do
alice.private_key = <<PEM
alice.private_key = <<-PEM
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDHgPoPJlrfMZrVcuF39UbVssa8r4ObLP3dYl9Y17Mgp5K4mSYD
R/Y2ag58tSi6ar2zM3Ze3QYsNfTq0NqN1g89eAu0MbSjWqpOsgntRPJiFuj3hai2
@@ -27,7 +27,7 @@ FTX8IvYBNTbpEttc1VCf/0ccnNpfb0CrFNSPWxRj7t7D
-----END RSA PRIVATE KEY-----
PEM
alice.public_key = <<PEM
alice.public_key = <<-PEM
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHgPoPJlrfMZrVcuF39UbVssa8
r4ObLP3dYl9Y17Mgp5K4mSYDR/Y2ag58tSi6ar2zM3Ze3QYsNfTq0NqN1g89eAu0
@@ -48,29 +48,23 @@ PEM
it 'returns JSON when account can be found' do
get :show, params: { resource: alice.to_webfinger_s }, format: :json
json = body_as_json
expect(response).to have_http_status(:success)
expect(response.content_type).to eq 'application/jrd+json'
expect(response.body).to eq "{\"subject\":\"acct:alice@cb6e6126.ngrok.io\",\"aliases\":[\"https://cb6e6126.ngrok.io/@alice\",\"https://cb6e6126.ngrok.io/users/alice\"],\"links\":[{\"rel\":\"http://webfinger.net/rel/profile-page\",\"type\":\"text/html\",\"href\":\"https://cb6e6126.ngrok.io/@alice\"},{\"rel\":\"http://schemas.google.com/g/2010#updates-from\",\"type\":\"application/atom+xml\",\"href\":\"https://cb6e6126.ngrok.io/users/alice.atom\"},{\"rel\":\"self\",\"type\":\"application/activity+json\",\"href\":\"https://cb6e6126.ngrok.io/@alice\"},{\"rel\":\"salmon\",\"href\":\"#{api_salmon_url(alice.id)}\"},{\"rel\":\"magic-public-key\",\"href\":\"data:application/magic-public-key,RSA.x4D6DyZa3zGa1XLhd_VG1bLGvK-Dmyz93WJfWNezIKeSuJkmA0f2NmoOfLUoumq9szN2Xt0GLDX06tDajdYPPXgLtDG0o1qqTrIJ7UTyYhbo94Wotl9iJvEwa5IjP1Mn00YJ_KvFrzKCm15PC7up6r-NtHsqoYS8X1KAqcbnptU=.AQAB\"},{\"rel\":\"http://ostatus.org/schema/1.0/subscribe\",\"template\":\"https://cb6e6126.ngrok.io/authorize_follow?acct={uri}\"}]}"
expect(json[:subject]).to eq 'acct:alice@cb6e6126.ngrok.io'
expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice')
end
it 'returns JSON when account can be found' do
get :show, params: { resource: alice.to_webfinger_s }, format: :xml
xml = Nokogiri::XML(response.body)
expect(response).to have_http_status(:success)
expect(response.content_type).to eq 'application/xrd+xml'
expect(response.body).to eq <<"XML"
<?xml version="1.0"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Subject>acct:alice@cb6e6126.ngrok.io</Subject>
<Alias>https://cb6e6126.ngrok.io/@alice</Alias>
<Alias>https://cb6e6126.ngrok.io/users/alice</Alias>
<Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="https://cb6e6126.ngrok.io/@alice"/>
<Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="https://cb6e6126.ngrok.io/users/alice.atom"/>
<Link rel="salmon" href="#{api_salmon_url(alice.id)}"/>
<Link rel="magic-public-key" href="data:application/magic-public-key,RSA.x4D6DyZa3zGa1XLhd_VG1bLGvK-Dmyz93WJfWNezIKeSuJkmA0f2NmoOfLUoumq9szN2Xt0GLDX06tDajdYPPXgLtDG0o1qqTrIJ7UTyYhbo94Wotl9iJvEwa5IjP1Mn00YJ_KvFrzKCm15PC7up6r-NtHsqoYS8X1KAqcbnptU=.AQAB"/>
<Link rel="http://ostatus.org/schema/1.0/subscribe" template="https://cb6e6126.ngrok.io/authorize_follow?acct={uri}"/>
</XRD>
XML
expect(xml.at_xpath('//xmlns:Subject').content).to eq 'acct:alice@cb6e6126.ngrok.io'
expect(xml.xpath('//xmlns:Alias').map(&:content)).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice')
end
it 'returns http not found when account cannot be found' do
@@ -80,19 +74,22 @@ XML
end
it 'returns JSON when account can be found with alternate domains' do
Rails.configuration.x.alternate_domains = ["foo.org"]
username, domain = alice.to_webfinger_s.split("@")
Rails.configuration.x.alternate_domains = ['foo.org']
username, = alice.to_webfinger_s.split('@')
get :show, params: { resource: "#{username}@foo.org" }, format: :json
json = body_as_json
expect(response).to have_http_status(:success)
expect(response.content_type).to eq 'application/jrd+json'
expect(response.body).to eq "{\"subject\":\"acct:alice@cb6e6126.ngrok.io\",\"aliases\":[\"https://cb6e6126.ngrok.io/@alice\",\"https://cb6e6126.ngrok.io/users/alice\"],\"links\":[{\"rel\":\"http://webfinger.net/rel/profile-page\",\"type\":\"text/html\",\"href\":\"https://cb6e6126.ngrok.io/@alice\"},{\"rel\":\"http://schemas.google.com/g/2010#updates-from\",\"type\":\"application/atom+xml\",\"href\":\"https://cb6e6126.ngrok.io/users/alice.atom\"},{\"rel\":\"self\",\"type\":\"application/activity+json\",\"href\":\"https://cb6e6126.ngrok.io/@alice\"},{\"rel\":\"salmon\",\"href\":\"#{api_salmon_url(alice.id)}\"},{\"rel\":\"magic-public-key\",\"href\":\"data:application/magic-public-key,RSA.x4D6DyZa3zGa1XLhd_VG1bLGvK-Dmyz93WJfWNezIKeSuJkmA0f2NmoOfLUoumq9szN2Xt0GLDX06tDajdYPPXgLtDG0o1qqTrIJ7UTyYhbo94Wotl9iJvEwa5IjP1Mn00YJ_KvFrzKCm15PC7up6r-NtHsqoYS8X1KAqcbnptU=.AQAB\"},{\"rel\":\"http://ostatus.org/schema/1.0/subscribe\",\"template\":\"https://cb6e6126.ngrok.io/authorize_follow?acct={uri}\"}]}"
expect(json[:subject]).to eq 'acct:alice@cb6e6126.ngrok.io'
expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice')
end
it 'returns http not found when account can not be found with alternate domains' do
Rails.configuration.x.alternate_domains = ["foo.org"]
username, domain = alice.to_webfinger_s.split("@")
Rails.configuration.x.alternate_domains = ['foo.org']
username, = alice.to_webfinger_s.split('@')
get :show, params: { resource: "#{username}@bar.org" }, format: :json

View File

@@ -0,0 +1,5 @@
Fabricator(:web_push_subscription) do
endpoint Faker::Internet.url
key_p256dh Faker::Internet.password
key_auth Faker::Internet.password
end

View File

@@ -1,15 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Activitystreams2BuilderHelper, type: :helper do
it 'returns display name if present' do
account = Fabricate(:account, display_name: 'display name', username: 'username')
expect(account_name(account)).to eq 'display name'
end
it 'returns username if display name is not present' do
account = Fabricate(:account, display_name: '', username: 'username')
expect(account_name(account)).to eq 'username'
end
end

View File

@@ -0,0 +1,15 @@
require 'rails_helper'
RSpec.describe EmojiHelper, type: :helper do
describe '#emojify' do
it 'converts shortcodes to unicode' do
text = ':book: Book'
expect(emojify(text)).to eq '📖 Book'
end
it 'does not convert shortcodes that are part of a string into unicode' do
text = ':see_no_evil::hear_no_evil::speak_no_evil:'
expect(emojify(text)).to eq text
end
end
end

View File

@@ -1,13 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe HttpHelper do
describe 'http_client' do
it 'returns HTTP::Client with default options' do
options = helper.http_client.default_options
expect(options.headers['User-Agent']).to match /.+ \(Mastodon\/.+;\ \+http:\/\/cb6e6126\.ngrok\.io\/\)/
expect(options.timeout_options).to eq read_timeout: 10, write_timeout: 10, connect_timeout: 10
end
end
end

View File

@@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe RoutingHelper, type: :helper do
end

View File

@@ -2,32 +2,6 @@ import { expect } from 'chai';
import emojify from '../../../app/javascript/mastodon/emoji';
describe('emojify', () => {
it('does a basic emojify', () => {
expect(emojify(':smile:')).to.equal(
'<img draggable="false" class="emojione" alt="😄" title=":smile:" src="/emoji/1f604.svg" />');
});
it('does a double emojify', () => {
expect(emojify(':smile: and :wink:')).to.equal(
'<img draggable="false" class="emojione" alt="😄" title=":smile:" src="/emoji/1f604.svg" /> and <img draggable="false" class="emojione" alt="😉" title=":wink:" src="/emoji/1f609.svg" />');
});
it('works with random colons', () => {
expect(emojify(':smile: : :wink:')).to.equal(
'<img draggable="false" class="emojione" alt="😄" title=":smile:" src="/emoji/1f604.svg" /> : <img draggable="false" class="emojione" alt="😉" title=":wink:" src="/emoji/1f609.svg" />');
expect(emojify(':smile::::wink:')).to.equal(
'<img draggable="false" class="emojione" alt="😄" title=":smile:" src="/emoji/1f604.svg" />::<img draggable="false" class="emojione" alt="😉" title=":wink:" src="/emoji/1f609.svg" />');
expect(emojify(':smile:::::wink:')).to.equal(
'<img draggable="false" class="emojione" alt="😄" title=":smile:" src="/emoji/1f604.svg" />:::<img draggable="false" class="emojione" alt="😉" title=":wink:" src="/emoji/1f609.svg" />');
});
it('works with tags', () => {
expect(emojify('<p>:smile:</p>')).to.equal(
'<p><img draggable="false" class="emojione" alt="😄" title=":smile:" src="/emoji/1f604.svg" /></p>');
expect(emojify('<p>:smile:</p> and <p>:wink:</p>')).to.equal(
'<p><img draggable="false" class="emojione" alt="😄" title=":smile:" src="/emoji/1f604.svg" /></p> and <p><img draggable="false" class="emojione" alt="😉" title=":wink:" src="/emoji/1f609.svg" /></p>');
});
it('ignores unknown shortcodes', () => {
expect(emojify(':foobarbazfake:')).to.equal(':foobarbazfake:');
});
@@ -46,38 +20,28 @@ describe('emojify', () => {
expect(emojify(':smile')).to.equal(':smile');
});
it('does two emoji next to each other', () => {
expect(emojify(':smile::wink:')).to.equal(
'<img draggable="false" class="emojione" alt="😄" title=":smile:" src="/emoji/1f604.svg" /><img draggable="false" class="emojione" alt="😉" title=":wink:" src="/emoji/1f609.svg" />');
});
it('does unicode', () => {
expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).to.equal(
'<img draggable="false" class="emojione" alt="👩‍👩‍👦‍👦" title=":family_wwbb:" src="/emoji/1f469-1f469-1f466-1f466.svg" />');
'<img draggable="false" class="emojione" alt="👩‍👩‍👦‍👦" src="/emoji/1f469-1f469-1f466-1f466.svg" />');
expect(emojify('\uD83D\uDC68\uD83D\uDC69\uD83D\uDC67\uD83D\uDC67')).to.equal(
'<img draggable="false" class="emojione" alt="👨👩👧👧" title=":family_mwgg:" src="/emoji/1f468-1f469-1f467-1f467.svg" />');
expect(emojify('\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66')).to.equal('<img draggable="false" class="emojione" alt="👩👩👦" title=":family_wwb:" src="/emoji/1f469-1f469-1f466.svg" />');
'<img draggable="false" class="emojione" alt="👨👩👧👧" src="/emoji/1f468-1f469-1f467-1f467.svg" />');
expect(emojify('\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66')).to.equal('<img draggable="false" class="emojione" alt="👩👩👦" src="/emoji/1f469-1f469-1f466.svg" />');
expect(emojify('\u2757')).to.equal(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />');
'<img draggable="false" class="emojione" alt="❗" src="/emoji/2757.svg" />');
});
it('does multiple unicode', () => {
expect(emojify('\u2757 #\uFE0F\u20E3')).to.equal(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/0023-20e3.svg" />');
'<img draggable="false" class="emojione" alt="❗" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" src="/emoji/0023-20e3.svg" />');
expect(emojify('\u2757#\uFE0F\u20E3')).to.equal(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/0023-20e3.svg" />');
'<img draggable="false" class="emojione" alt="❗" src="/emoji/2757.svg" /><img draggable="false" class="emojione" alt="#️⃣" src="/emoji/0023-20e3.svg" />');
expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).to.equal(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/0023-20e3.svg" /> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />');
'<img draggable="false" class="emojione" alt="❗" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" src="/emoji/0023-20e3.svg" /> <img draggable="false" class="emojione" alt="❗" src="/emoji/2757.svg" />');
expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).to.equal(
'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/0023-20e3.svg" /> bar');
});
it('does mixed unicode and shortnames', () => {
expect(emojify(':smile:#\uFE0F\u20E3:wink:\u2757')).to.equal('<img draggable="false" class="emojione" alt="😄" title=":smile:" src="/emoji/1f604.svg" /><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/0023-20e3.svg" /><img draggable="false" class="emojione" alt="😉" title=":wink:" src="/emoji/1f609.svg" /><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />');
'foo <img draggable="false" class="emojione" alt="❗" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" src="/emoji/0023-20e3.svg" /> bar');
});
it('ignores unicode inside of tags', () => {
expect(emojify('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>')).to.equal('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>');
});
});

View File

@@ -81,6 +81,13 @@ RSpec.describe FeedManager do
expect(FeedManager.instance.filter?(:home, reply, bob.id)).to be true
end
it 'returns true for the second reply by followee to a non-federated status' do
reply = Fabricate(:status, text: 'Reply 1', reply: true, account: alice)
second_reply = Fabricate(:status, text: 'Reply 2', thread: reply, account: alice)
bob.follow!(alice)
expect(FeedManager.instance.filter?(:home, second_reply, bob.id)).to be true
end
it 'returns false for status by followee mentioning another account' do
bob.follow!(alice)
status = PostStatusService.new.call(alice, 'Hey @jeff')

54
spec/lib/request_spec.rb Normal file
View File

@@ -0,0 +1,54 @@
# frozen_string_literal: true
require 'rails_helper'
describe Request do
subject { Request.new(:get, 'http://example.com') }
describe '#headers' do
it 'returns user agent' do
expect(subject.headers['User-Agent']).to be_present
end
it 'returns the date header' do
expect(subject.headers['Date']).to be_present
end
it 'returns the host header' do
expect(subject.headers['Host']).to be_present
end
it 'does not return virtual request-target header' do
expect(subject.headers['(request-target)']).to be_nil
end
end
describe '#on_behalf_of' do
it 'when used, adds signature header' do
subject.on_behalf_of(Fabricate(:account))
expect(subject.headers['Signature']).to be_present
end
end
describe '#add_headers' do
it 'adds headers to the request' do
subject.add_headers('Test' => 'Foo')
expect(subject.headers['Test']).to eq 'Foo'
end
end
describe '#perform' do
before do
stub_request(:get, 'http://example.com')
subject.perform
end
it 'executes a HTTP request' do
expect(a_request(:get, 'http://example.com')).to have_been_made.once
end
it 'sets headers' do
expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
end
end
end

View File

@@ -1,6 +1,24 @@
require 'rails_helper'
RSpec.describe Tag, type: :model do
describe 'validations' do
it 'invalid with #' do
expect(Tag.new(name: '#hello_world')).to_not be_valid
end
it 'invalid with .' do
expect(Tag.new(name: '.abcdef123')).to_not be_valid
end
it 'invalid with spaces' do
expect(Tag.new(name: 'hello world')).to_not be_valid
end
it 'valid with ' do
expect(Tag.new(name: '')).to be_valid
end
end
describe 'HASHTAG_RE' do
subject { Tag::HASHTAG_RE }
@@ -27,6 +45,15 @@ RSpec.describe Tag, type: :model do
expect(results).to eq [tag]
end
it 'finds tag records in case insensitive' do
tag = Fabricate(:tag, name: "MATCH")
_miss_tag = Fabricate(:tag, name: "miss")
results = Tag.search_for("match")
expect(results).to eq [tag]
end
it 'finds the exact matching tag as the first item' do
similar_tag = Fabricate(:tag, name: "matchlater")
tag = Fabricate(:tag, name: "match")

View File

@@ -0,0 +1,28 @@
require 'rails_helper'
RSpec.describe Web::PushSubscription, type: :model do
let(:alerts) { { mention: true, reblog: false, follow: true, follow_request: false, favourite: true } }
let(:payload_no_alerts) { Web::PushSubscription.new(id: 1, endpoint: 'a', key_p256dh: 'c', key_auth: 'd').as_payload }
let(:payload_alerts) { Web::PushSubscription.new(id: 1, endpoint: 'a', key_p256dh: 'c', key_auth: 'd', data: { alerts: alerts }).as_payload }
let(:push_subscription) { Web::PushSubscription.new(data: { alerts: alerts }) }
describe '#as_payload' do
it 'only returns id and endpoint' do
expect(payload_no_alerts.keys).to eq [:id, :endpoint]
end
it 'returns alerts if set' do
expect(payload_alerts.keys).to eq [:id, :endpoint, :alerts]
end
end
describe '#pushable?' do
it 'obeys alert settings' do
expect(push_subscription.send(:pushable?, Notification.new(activity_type: 'Mention'))).to eq true
expect(push_subscription.send(:pushable?, Notification.new(activity_type: 'Status'))).to eq false
expect(push_subscription.send(:pushable?, Notification.new(activity_type: 'Follow'))).to eq true
expect(push_subscription.send(:pushable?, Notification.new(activity_type: 'FollowRequest'))).to eq false
expect(push_subscription.send(:pushable?, Notification.new(activity_type: 'Favourite'))).to eq true
end
end
end

View File

@@ -27,7 +27,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d
render
mf2 = Microformats2.parse(rendered)
mf2 = Microformats.parse(rendered)
expect(mf2.entry.name.to_s).to eq status.text
expect(mf2.entry.url.to_s).not_to be_empty
@@ -53,7 +53,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d
render
mf2 = Microformats2.parse(rendered)
mf2 = Microformats.parse(rendered)
expect(mf2.entry.name.to_s).to eq reply.text
expect(mf2.entry.url.to_s).not_to be_empty

View File

@@ -83,6 +83,6 @@ describe Pubsubhubbub::ConfirmationWorker do
end
def http_headers
{ 'Connection' => 'close', 'Host' => 'example.com', 'User-Agent' => 'Mastodon/PubSubHubbub' }
{ 'Connection' => 'close', 'Host' => 'example.com', 'User-Agent' => 'http.rb/2.2.2 (Mastodon/1.4.7; +https://cb6e6126.ngrok.io/)' }
end
end

View File

@@ -59,7 +59,7 @@ describe Pubsubhubbub::DeliveryWorker do
'Content-Type' => 'application/atom+xml',
'Host' => 'example.com',
'Link' => "<https://#{Rails.configuration.x.local_domain}/api/push>; rel=\"hub\", <https://#{Rails.configuration.x.local_domain}/users/#{subscription.account.username}.atom>; rel=\"self\"",
'User-Agent' => 'Mastodon/PubSubHubbub',
'User-Agent' => 'http.rb/2.2.2 (Mastodon/1.4.7; +https://cb6e6126.ngrok.io/)',
}.tap do |basic|
known_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), subscription.secret.to_s, payload)
basic.merge('X-Hub-Signature' => "sha1=#{known_digest}") if subscription.secret?