about summary refs log tree commit diff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/activitypub/inboxes_controller_spec.rb4
-rw-r--r--spec/controllers/api/proofs_controller_spec.rb96
-rw-r--r--spec/controllers/relationships_controller_spec.rb (renamed from spec/controllers/settings/follower_domains_controller_spec.rb)26
-rw-r--r--spec/controllers/settings/identity_proofs_controller_spec.rb112
-rw-r--r--spec/controllers/well_known/keybase_proof_config_controller_spec.rb15
-rw-r--r--spec/fabricators/account_identity_proof_fabricator.rb8
-rw-r--r--spec/helpers/admin/action_log_helper_spec.rb272
-rw-r--r--spec/lib/activitypub/activity/announce_spec.rb1
-rw-r--r--spec/lib/activitypub/activity/flag_spec.rb23
-rw-r--r--spec/lib/language_detector_spec.rb4
-rw-r--r--spec/lib/proof_provider/keybase/verifier_spec.rb82
-rw-r--r--spec/services/reblog_service_spec.rb21
-rw-r--r--spec/services/report_service_spec.rb5
13 files changed, 646 insertions, 23 deletions
diff --git a/spec/controllers/activitypub/inboxes_controller_spec.rb b/spec/controllers/activitypub/inboxes_controller_spec.rb
index 4055d9342..eab4b8c3e 100644
--- a/spec/controllers/activitypub/inboxes_controller_spec.rb
+++ b/spec/controllers/activitypub/inboxes_controller_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe ActivityPub::InboxesController, type: :controller do
           Fabricate(:account)
         end
 
-        post :create
+        post :create, body: '{}'
         expect(response).to have_http_status(202)
       end
     end
@@ -21,7 +21,7 @@ RSpec.describe ActivityPub::InboxesController, type: :controller do
           false
         end
 
-        post :create
+        post :create, body: '{}'
         expect(response).to have_http_status(401)
       end
     end
diff --git a/spec/controllers/api/proofs_controller_spec.rb b/spec/controllers/api/proofs_controller_spec.rb
new file mode 100644
index 000000000..dbde4927f
--- /dev/null
+++ b/spec/controllers/api/proofs_controller_spec.rb
@@ -0,0 +1,96 @@
+require 'rails_helper'
+
+describe Api::ProofsController do
+  let(:alice) { Fabricate(:account, username: 'alice') }
+
+  before do
+    stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_valid.json?domain=cb6e6126.ngrok.io&kb_username=crypto_alice&sig_hash=111111111111111111111111111111111111111111111111111111111111111111&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":false}')
+    stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_live.json?domain=cb6e6126.ngrok.io&kb_username=crypto_alice&sig_hash=111111111111111111111111111111111111111111111111111111111111111111&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":true}')
+    stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_valid.json?domain=cb6e6126.ngrok.io&kb_username=hidden_alice&sig_hash=222222222222222222222222222222222222222222222222222222222222222222&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":true}')
+    stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_live.json?domain=cb6e6126.ngrok.io&kb_username=hidden_alice&sig_hash=222222222222222222222222222222222222222222222222222222222222222222&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":true}')
+  end
+
+  describe 'GET #index' do
+    describe 'with a non-existent username' do
+      it '404s' do
+        get :index, params: { username: 'nonexistent', provider: 'keybase' }
+
+        expect(response).to have_http_status(:not_found)
+      end
+    end
+
+    describe 'with a user that has no proofs' do
+      it 'is an empty list of signatures' do
+        get :index, params: { username: alice.username, provider: 'keybase' }
+
+        expect(body_as_json[:signatures]).to eq []
+      end
+    end
+
+    describe 'with a user that has a live, valid proof' do
+      let(:token1) { '111111111111111111111111111111111111111111111111111111111111111111' }
+      let(:kb_name1) { 'crypto_alice' }
+
+      before do
+        Fabricate(:account_identity_proof, account: alice, verified: true, live: true, token: token1, provider_username: kb_name1)
+      end
+
+      it 'is a list with that proof in it' do
+        get :index, params: { username: alice.username, provider: 'keybase' }
+
+        expect(body_as_json[:signatures]).to eq [
+          { kb_username: kb_name1, sig_hash: token1 },
+        ]
+      end
+
+      describe 'add one that is neither live nor valid' do
+        let(:token2) { '222222222222222222222222222222222222222222222222222222222222222222' }
+        let(:kb_name2) { 'hidden_alice' }
+
+        before do
+          Fabricate(:account_identity_proof, account: alice, verified: false, live: false, token: token2, provider_username: kb_name2)
+        end
+
+        it 'is a list with both proofs' do
+          get :index, params: { username: alice.username, provider: 'keybase' }
+
+          expect(body_as_json[:signatures]).to eq [
+            { kb_username: kb_name1, sig_hash: token1 },
+            { kb_username: kb_name2, sig_hash: token2 },
+          ]
+        end
+      end
+    end
+
+    describe 'a user that has an avatar' do
+      let(:alice) { Fabricate(:account, username: 'alice', avatar: attachment_fixture('avatar.gif')) }
+
+      context 'and a proof' do
+        let(:token1) { '111111111111111111111111111111111111111111111111111111111111111111' }
+        let(:kb_name1) { 'crypto_alice' }
+
+        before do
+          Fabricate(:account_identity_proof, account: alice, verified: true, live: true, token: token1, provider_username: kb_name1)
+          get :index, params: { username: alice.username, provider: 'keybase' }
+        end
+
+        it 'has two keys: signatures and avatar' do
+          expect(body_as_json.keys).to match_array [:signatures, :avatar]
+        end
+
+        it 'has the correct signatures' do
+          expect(body_as_json[:signatures]).to eq [
+            { kb_username: kb_name1, sig_hash: token1 },
+          ]
+        end
+
+        it 'has the correct avatar url' do
+          first_part = 'https://cb6e6126.ngrok.io/system/accounts/avatars/'
+          last_part  = 'original/avatar.gif'
+
+          expect(body_as_json[:avatar]).to match /#{Regexp.quote(first_part)}(?:\d{3,5}\/){3}#{Regexp.quote(last_part)}/
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/settings/follower_domains_controller_spec.rb b/spec/controllers/relationships_controller_spec.rb
index 6d415a654..16e255afe 100644
--- a/spec/controllers/settings/follower_domains_controller_spec.rb
+++ b/spec/controllers/relationships_controller_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-describe Settings::FollowerDomainsController do
+describe RelationshipsController do
   render_views
 
   let(:user) { Fabricate(:user) }
@@ -12,24 +12,17 @@ describe Settings::FollowerDomainsController do
   end
 
   describe 'GET #show' do
-    subject { get :show, params: { page: 2 } }
+    subject { get :show, params: { page: 2, relationship: 'followed_by' } }
 
-    it 'assigns @account' do
-      sign_in user, scope: :user
-      subject
-      expect(assigns(:account)).to eq user.account
-    end
-
-    it 'assigns @domains' do
+    it 'assigns @accounts' do
       Fabricate(:account, domain: 'old').follow!(user.account)
       Fabricate(:account, domain: 'recent').follow!(user.account)
 
       sign_in user, scope: :user
       subject
 
-      assigned = assigns(:domains).per(1).to_a
+      assigned = assigns(:accounts).per(1).to_a
       expect(assigned.size).to eq 1
-      expect(assigned[0].accounts_from_domain).to eq 1
       expect(assigned[0].domain).to eq 'old'
     end
 
@@ -49,25 +42,24 @@ describe Settings::FollowerDomainsController do
       stub_request(:post, 'http://example.com/salmon').to_return(status: 200)
     end
 
-    shared_examples 'redirects back to followers page' do |notice|
+    shared_examples 'redirects back to followers page' do
       it 'redirects back to followers page' do
         poopfeast.follow!(user.account)
 
         sign_in user, scope: :user
         subject
 
-        expect(flash[:notice]).to eq notice
-        expect(response).to redirect_to(settings_follower_domains_path)
+        expect(response).to redirect_to(relationships_path)
       end
     end
 
     context 'when select parameter is not provided' do
       subject { patch :update }
-      include_examples 'redirects back to followers page', 'In the process of soft-blocking followers from 0 domains...'
+      include_examples 'redirects back to followers page'
     end
 
     context 'when select parameter is provided' do
-      subject { patch :update, params: { select: ['example.com'] } }
+      subject { patch :update, params: { form_account_batch: { account_ids: [poopfeast.id] }, block_domains: '' } }
 
       it 'soft-blocks followers from selected domains' do
         poopfeast.follow!(user.account)
@@ -79,7 +71,7 @@ describe Settings::FollowerDomainsController do
       end
 
       include_examples 'authenticate user'
-      include_examples 'redirects back to followers page', 'In the process of soft-blocking followers from one domain...'
+      include_examples 'redirects back to followers page'
     end
   end
 end
diff --git a/spec/controllers/settings/identity_proofs_controller_spec.rb b/spec/controllers/settings/identity_proofs_controller_spec.rb
new file mode 100644
index 000000000..46af3ccf4
--- /dev/null
+++ b/spec/controllers/settings/identity_proofs_controller_spec.rb
@@ -0,0 +1,112 @@
+require 'rails_helper'
+
+describe Settings::IdentityProofsController do
+  render_views
+
+  let(:user) { Fabricate(:user) }
+  let(:valid_token) { '1'*66 }
+  let(:kbname) { 'kbuser' }
+  let(:provider) { 'keybase' }
+  let(:findable_id) { Faker::Number.number(5) }
+  let(:unfindable_id) { Faker::Number.number(5) }
+  let(:postable_params) do
+    { account_identity_proof: { provider: provider, provider_username: kbname, token: valid_token } }
+  end
+
+  before do
+    allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:status) { { 'proof_valid' => true, 'proof_live' => true } }
+    sign_in user, scope: :user
+  end
+
+  describe 'new proof creation' do
+    context 'GET #new with no existing proofs' do
+      it 'redirects to :index' do
+        get :new
+        expect(response).to redirect_to settings_identity_proofs_path
+      end
+    end
+
+    context 'POST #create' do
+      context 'when saving works' do
+        before do
+          allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
+          allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
+          allow_any_instance_of(AccountIdentityProof).to receive(:on_success_path) { root_url }
+        end
+
+        it 'serializes a ProofProvider::Keybase::Worker' do
+          expect(ProofProvider::Keybase::Worker).to receive(:perform_async)
+          post :create, params: postable_params
+        end
+
+        it 'delegates redirection to the proof provider' do
+          expect_any_instance_of(AccountIdentityProof).to receive(:on_success_path)
+          post :create, params: postable_params
+          expect(response).to redirect_to root_url
+        end
+      end
+
+      context 'when saving fails' do
+        before do
+          allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { false }
+        end
+
+        it 'redirects to :index' do
+          post :create, params: postable_params
+          expect(response).to redirect_to settings_identity_proofs_path
+        end
+
+        it 'flashes a helpful message' do
+          post :create, params: postable_params
+          expect(flash[:alert]).to eq I18n.t('identity_proofs.errors.failed', provider: 'Keybase')
+        end
+      end
+
+      context 'it can also do an update if the provider and username match an existing proof' do
+        before do
+          allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
+          allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
+          Fabricate(:account_identity_proof, account: user.account, provider: provider, provider_username: kbname)
+          allow_any_instance_of(AccountIdentityProof).to receive(:on_success_path) { root_url }
+        end
+
+        it 'calls update with the new token' do
+          expect_any_instance_of(AccountIdentityProof).to receive(:save) do |proof|
+            expect(proof.token).to eq valid_token
+          end
+
+          post :create, params: postable_params
+        end
+      end
+    end
+  end
+
+  describe 'GET #index' do
+    context 'with no existing proofs' do
+      it 'shows the helpful explanation' do
+        get :index
+        expect(response.body).to match I18n.t('identity_proofs.explanation_html')
+      end
+    end
+
+    context 'with two proofs' do
+      before do
+        allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
+        @proof1 = Fabricate(:account_identity_proof, account: user.account)
+        @proof2 = Fabricate(:account_identity_proof, account: user.account)
+        allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
+        allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) { }
+      end
+
+      it 'has the first proof username on the page' do
+        get :index
+        expect(response.body).to match /#{Regexp.quote(@proof1.provider_username)}/
+      end
+
+      it 'has the second proof username on the page' do
+        get :index
+        expect(response.body).to match /#{Regexp.quote(@proof2.provider_username)}/
+      end
+    end
+  end
+end
diff --git a/spec/controllers/well_known/keybase_proof_config_controller_spec.rb b/spec/controllers/well_known/keybase_proof_config_controller_spec.rb
new file mode 100644
index 000000000..9067e676d
--- /dev/null
+++ b/spec/controllers/well_known/keybase_proof_config_controller_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+describe WellKnown::KeybaseProofConfigController, type: :controller do
+  render_views
+
+  describe 'GET #show' do
+    it 'renders json' do
+      get :show
+
+      expect(response).to have_http_status(200)
+      expect(response.content_type).to eq 'application/json'
+      expect { JSON.parse(response.body) }.not_to raise_exception
+    end
+  end
+end
diff --git a/spec/fabricators/account_identity_proof_fabricator.rb b/spec/fabricators/account_identity_proof_fabricator.rb
new file mode 100644
index 000000000..94f40dfd6
--- /dev/null
+++ b/spec/fabricators/account_identity_proof_fabricator.rb
@@ -0,0 +1,8 @@
+Fabricator(:account_identity_proof) do
+  account
+  provider 'keybase'
+  provider_username { sequence(:provider_username) { |i| "#{Faker::Lorem.characters(15)}" } }
+  token { sequence(:token) { |i| "#{i}#{Faker::Crypto.sha1()*2}"[0..65] } }
+  verified false
+  live false
+end
diff --git a/spec/helpers/admin/action_log_helper_spec.rb b/spec/helpers/admin/action_log_helper_spec.rb
new file mode 100644
index 000000000..d7af6b939
--- /dev/null
+++ b/spec/helpers/admin/action_log_helper_spec.rb
@@ -0,0 +1,272 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::ActionLogsHelper, type: :helper do
+  klass = Class.new do
+    include ActionView::Helpers
+    include Admin::ActionLogsHelper
+  end
+
+  let(:hoge) { klass.new }
+
+  describe '#log_target' do
+    after do
+      hoge.log_target(log)
+    end
+
+    context 'log.target' do
+      let(:log) { double(target: true) }
+
+      it 'calls linkable_log_target' do
+        expect(hoge).to receive(:linkable_log_target).with(log.target)
+      end
+    end
+
+    context '!log.target' do
+      let(:log) { double(target: false, target_type: :type, recorded_changes: :change) }
+
+      it 'calls log_target_from_history' do
+        expect(hoge).to receive(:log_target_from_history).with(log.target_type, log.recorded_changes)
+      end
+    end
+  end
+
+  describe '#relevant_log_changes' do
+    let(:log) { double(target_type: target_type, action: log_action, recorded_changes: recorded_changes) }
+    let(:recorded_changes) { double }
+
+    after do
+      hoge.relevant_log_changes(log)
+    end
+
+    context "log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action)" do
+      let(:target_type) { 'CustomEmoji' }
+      let(:log_action)  { :enable }
+
+      it "calls log.recorded_changes.slice('domain')" do
+        expect(recorded_changes).to receive(:slice).with('domain')
+      end
+    end
+
+    context "log.target_type == 'CustomEmoji' && log.action == :update" do
+      let(:target_type) { 'CustomEmoji' }
+      let(:log_action)  { :update }
+
+      it "calls log.recorded_changes.slice('domain', 'visible_in_picker')" do
+        expect(recorded_changes).to receive(:slice).with('domain', 'visible_in_picker')
+      end
+    end
+
+    context "log.target_type == 'User' && [:promote, :demote].include?(log.action)" do
+      let(:target_type) { 'User' }
+      let(:log_action)  { :promote }
+
+      it "calls log.recorded_changes.slice('moderator', 'admin')" do
+        expect(recorded_changes).to receive(:slice).with('moderator', 'admin')
+      end
+    end
+
+    context "log.target_type == 'User' && [:change_email].include?(log.action)" do
+      let(:target_type) { 'User' }
+      let(:log_action)  { :change_email }
+
+      it "calls log.recorded_changes.slice('email', 'unconfirmed_email')" do
+        expect(recorded_changes).to receive(:slice).with('email', 'unconfirmed_email')
+      end
+    end
+
+    context "log.target_type == 'DomainBlock'" do
+      let(:target_type) { 'DomainBlock' }
+      let(:log_action)  { nil }
+
+      it "calls log.recorded_changes.slice('severity', 'reject_media')" do
+        expect(recorded_changes).to receive(:slice).with('severity', 'reject_media')
+      end
+    end
+
+    context "log.target_type == 'Status' && log.action == :update" do
+      let(:target_type) { 'Status' }
+      let(:log_action)  { :update }
+
+      it "log.recorded_changes.slice('sensitive')" do
+        expect(recorded_changes).to receive(:slice).with('sensitive')
+      end
+    end
+  end
+
+  describe '#log_extra_attributes' do
+    after do
+      hoge.log_extra_attributes(hoge: 'hoge')
+    end
+
+    it "calls content_tag(:span, key, class: 'diff-key')" do
+      allow(hoge).to receive(:log_change).with(anything)
+      expect(hoge).to receive(:content_tag).with(:span, :hoge, class: 'diff-key')
+    end
+
+    it 'calls safe_join twice' do
+      expect(hoge).to receive(:safe_join).with(
+        ['<span class="diff-key">hoge</span>',
+         '=',
+         '<span class="diff-neutral">hoge</span>']
+      )
+
+      expect(hoge).to receive(:safe_join).with([nil], ' ')
+    end
+  end
+
+  describe '#log_change' do
+    after do
+      hoge.log_change(val)
+    end
+
+    context '!val.is_a?(Array)' do
+      let(:val) { 'hoge' }
+
+      it "calls content_tag(:span, val, class: 'diff-neutral')" do
+        expect(hoge).to receive(:content_tag).with(:span, val, class: 'diff-neutral')
+      end
+    end
+
+    context 'val.is_a?(Array)' do
+      let(:val) { %w(foo bar) }
+
+      it 'calls #content_tag twice and #safe_join' do
+        expect(hoge).to receive(:content_tag).with(:span, 'foo', class: 'diff-old')
+        expect(hoge).to receive(:content_tag).with(:span, 'bar', class: 'diff-new')
+        expect(hoge).to receive(:safe_join).with([nil, nil], '→')
+      end
+    end
+  end
+
+  describe '#icon_for_log' do
+    subject   { hoge.icon_for_log(log) }
+
+    context "log.target_type == 'Account'" do
+      let(:log) { double(target_type: 'Account') }
+
+      it 'returns "user"' do
+        expect(subject).to be 'user'
+      end
+    end
+
+    context "log.target_type == 'User'" do
+      let(:log) { double(target_type: 'User') }
+
+      it 'returns "user"' do
+        expect(subject).to be 'user'
+      end
+    end
+
+    context "log.target_type == 'CustomEmoji'" do
+      let(:log) { double(target_type: 'CustomEmoji') }
+
+      it 'returns "file"' do
+        expect(subject).to be 'file'
+      end
+    end
+
+    context "log.target_type == 'Report'" do
+      let(:log) { double(target_type: 'Report') }
+
+      it 'returns "flag"' do
+        expect(subject).to be 'flag'
+      end
+    end
+
+    context "log.target_type == 'DomainBlock'" do
+      let(:log) { double(target_type: 'DomainBlock') }
+
+      it 'returns "lock"' do
+        expect(subject).to be 'lock'
+      end
+    end
+
+    context "log.target_type == 'EmailDomainBlock'" do
+      let(:log) { double(target_type: 'EmailDomainBlock') }
+
+      it 'returns "envelope"' do
+        expect(subject).to be 'envelope'
+      end
+    end
+
+    context "log.target_type == 'Status'" do
+      let(:log) { double(target_type: 'Status') }
+
+      it 'returns "pencil"' do
+        expect(subject).to be 'pencil'
+      end
+    end
+  end
+
+  describe '#class_for_log_icon' do
+    subject   { hoge.class_for_log_icon(log) }
+
+    %i(enable unsuspend unsilence confirm promote resolve).each do |action|
+      context "log.action == #{action}" do
+        let(:log) { double(action: action) }
+
+        it 'returns "positive"' do
+          expect(subject).to be 'positive'
+        end
+      end
+    end
+
+    context 'log.action == :create' do
+      context 'opposite_verbs?(log)' do
+        let(:log) { double(action: :create, target_type: 'DomainBlock') }
+
+        it 'returns "negative"' do
+          expect(subject).to be 'negative'
+        end
+      end
+
+      context '!opposite_verbs?(log)' do
+        let(:log) { double(action: :create, target_type: '') }
+
+        it 'returns "positive"' do
+          expect(subject).to be 'positive'
+        end
+      end
+    end
+
+    %i(update reset_password disable_2fa memorialize change_email).each do |action|
+      context "log.action == #{action}" do
+        let(:log) { double(action: action) }
+
+        it 'returns "neutral"' do
+          expect(subject).to be 'neutral'
+        end
+      end
+    end
+
+    %i(demote silence disable suspend remove_avatar remove_header reopen).each do |action|
+      context "log.action == #{action}" do
+        let(:log) { double(action: action) }
+
+        it 'returns "negative"' do
+          expect(subject).to be 'negative'
+        end
+      end
+    end
+
+    context 'log.action == :destroy' do
+      context 'opposite_verbs?(log)' do
+        let(:log) { double(action: :destroy, target_type: 'DomainBlock') }
+
+        it 'returns "positive"' do
+          expect(subject).to be 'positive'
+        end
+      end
+
+      context '!opposite_verbs?(log)' do
+        let(:log) { double(action: :destroy, target_type: '') }
+
+        it 'returns "negative"' do
+          expect(subject).to be 'negative'
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb
index aa58d9e23..926083a4f 100644
--- a/spec/lib/activitypub/activity/announce_spec.rb
+++ b/spec/lib/activitypub/activity/announce_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe ActivityPub::Activity::Announce do
       type: 'Announce',
       actor: 'https://example.com/actor',
       object: object_json,
+      to: 'http://example.com/followers',
     }.with_indifferent_access
   end
 
diff --git a/spec/lib/activitypub/activity/flag_spec.rb b/spec/lib/activitypub/activity/flag_spec.rb
index 3f082a813..ec7359f2f 100644
--- a/spec/lib/activitypub/activity/flag_spec.rb
+++ b/spec/lib/activitypub/activity/flag_spec.rb
@@ -1,14 +1,15 @@
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Flag do
-  let(:sender)  { Fabricate(:account, domain: 'example.com') }
+  let(:sender)  { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
   let(:flagged) { Fabricate(:account) }
   let(:status)  { Fabricate(:status, account: flagged, uri: 'foobar') }
+  let(:flag_id) { nil }
 
   let(:json) do
     {
       '@context': 'https://www.w3.org/ns/activitystreams',
-      id: nil,
+      id: flag_id,
       type: 'Flag',
       content: 'Boo!!',
       actor: ActivityPub::TagManager.instance.uri_for(sender),
@@ -34,4 +35,22 @@ RSpec.describe ActivityPub::Activity::Flag do
       expect(report.status_ids).to eq [status.id]
     end
   end
+
+  describe '#perform with a defined uri' do
+    subject { described_class.new(json, sender) }
+    let (:flag_id) { 'http://example.com/reports/1' }
+
+    before do
+      subject.perform
+    end
+
+    it 'creates a report' do
+      report = Report.find_by(account: sender, target_account: flagged)
+
+      expect(report).to_not be_nil
+      expect(report.comment).to eq 'Boo!!'
+      expect(report.status_ids).to eq [status.id]
+      expect(report.uri).to eq flag_id
+    end
+  end
 end
diff --git a/spec/lib/language_detector_spec.rb b/spec/lib/language_detector_spec.rb
index 0fa2a59ef..0cb70605a 100644
--- a/spec/lib/language_detector_spec.rb
+++ b/spec/lib/language_detector_spec.rb
@@ -106,11 +106,11 @@ describe LanguageDetector do
       end
 
       describe 'remote user' do
-        it 'nil for foreign user when language is not present' do
+        it 'detects Korean language' do
           string = '안녕하세요'
           result = described_class.instance.detect(string, account_remote)
 
-          expect(result).to eq nil
+          expect(result).to eq :ko
         end
       end
 
diff --git a/spec/lib/proof_provider/keybase/verifier_spec.rb b/spec/lib/proof_provider/keybase/verifier_spec.rb
new file mode 100644
index 000000000..4ce67da9c
--- /dev/null
+++ b/spec/lib/proof_provider/keybase/verifier_spec.rb
@@ -0,0 +1,82 @@
+require 'rails_helper'
+
+describe ProofProvider::Keybase::Verifier do
+  let(:my_domain) { Rails.configuration.x.local_domain }
+
+  let(:keybase_proof) do
+    local_proof = AccountIdentityProof.new(
+      provider: 'Keybase',
+      provider_username: 'cryptoalice',
+      token: '11111111111111111111111111'
+    )
+
+    described_class.new('alice', 'cryptoalice', '11111111111111111111111111')
+  end
+
+  let(:query_params) do
+    "domain=#{my_domain}&kb_username=cryptoalice&sig_hash=11111111111111111111111111&username=alice"
+  end
+
+  describe '#valid?' do
+    let(:base_url) { 'https://keybase.io/_/api/1.0/sig/proof_valid.json' }
+
+    context 'when valid' do
+      before do
+        json_response_body = '{"status":{"code":0,"name":"OK"},"proof_valid":true}'
+        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
+      end
+
+      it 'calls out to keybase and returns true' do
+        expect(keybase_proof.valid?).to eq true
+      end
+    end
+
+    context 'when invalid' do
+      before do
+        json_response_body = '{"status":{"code":0,"name":"OK"},"proof_valid":false}'
+        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
+      end
+
+      it 'calls out to keybase and returns false' do
+        expect(keybase_proof.valid?).to eq false
+      end
+    end
+
+    context 'with an unexpected api response' do
+      before do
+        json_response_body = '{"status":{"code":100,"desc":"wrong size hex_id","fields":{"sig_hash":"wrong size hex_id"},"name":"INPUT_ERROR"}}'
+        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
+      end
+
+      it 'swallows the error and returns false' do
+        expect(keybase_proof.valid?).to eq false
+      end
+    end
+  end
+
+  describe '#status' do
+    let(:base_url) { 'https://keybase.io/_/api/1.0/sig/proof_live.json' }
+
+    context 'with a normal response' do
+      before do
+        json_response_body = '{"status":{"code":0,"name":"OK"},"proof_live":false,"proof_valid":true}'
+        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
+      end
+
+      it 'calls out to keybase and returns the status fields as proof_valid and proof_live' do
+        expect(keybase_proof.status).to include({ 'proof_valid' => true, 'proof_live' => false })
+      end
+    end
+
+    context 'with an unexpected keybase response' do
+      before do
+        json_response_body = '{"status":{"code":100,"desc":"missing non-optional field sig_hash","fields":{"sig_hash":"missing non-optional field sig_hash"},"name":"INPUT_ERROR"}}'
+        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
+      end
+
+      it 'raises a ProofProvider::Keybase::UnexpectedResponseError' do
+        expect { keybase_proof.status }.to raise_error ProofProvider::Keybase::UnexpectedResponseError
+      end
+    end
+  end
+end
diff --git a/spec/services/reblog_service_spec.rb b/spec/services/reblog_service_spec.rb
index 2755da772..9e66c6643 100644
--- a/spec/services/reblog_service_spec.rb
+++ b/spec/services/reblog_service_spec.rb
@@ -3,6 +3,27 @@ require 'rails_helper'
 RSpec.describe ReblogService, type: :service do
   let(:alice)  { Fabricate(:account, username: 'alice') }
 
+  context 'creates a reblog with appropriate visibility' do
+    let(:bob)               { Fabricate(:account, username: 'bob') }
+    let(:visibility)        { :public }
+    let(:reblog_visibility) { :public }
+    let(:status)            { Fabricate(:status, account: bob, visibility: visibility) }
+
+    subject { ReblogService.new }
+
+    before do
+      subject.call(alice, status, visibility: reblog_visibility)
+    end
+
+    describe 'boosting privately' do
+      let(:reblog_visibility) { :private }
+
+      it 'reblogs privately' do
+        expect(status.reblogs.first.visibility).to eq 'private'
+      end
+    end
+  end
+
   context 'OStatus' do
     let(:bob)    { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com') }
     let(:status) { Fabricate(:status, account: bob, uri: 'tag:example.com;something:something') }
diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb
index e8b094c89..454e4d896 100644
--- a/spec/services/report_service_spec.rb
+++ b/spec/services/report_service_spec.rb
@@ -21,6 +21,11 @@ RSpec.describe ReportService, type: :service do
       subject.call(source_account, remote_account, forward: false)
       expect(a_request(:post, 'http://example.com/inbox')).to_not have_been_made
     end
+
+    it 'has an uri' do
+      report = subject.call(source_account, remote_account, forward: true)
+      expect(report.uri).to_not be_nil
+    end
   end
 
   context 'when other reports already exist for the same target' do