Merge branch 'master' into glitch-soc/merge-upstream

This commit is contained in:
Thibaut Girka 2019-03-28 18:35:25 +01:00
commit ce7d055d3c
26 changed files with 299 additions and 31 deletions

View file

@ -80,7 +80,7 @@ Rails/HttpStatus:
Rails/Exit: Rails/Exit:
Exclude: Exclude:
- 'lib/mastodon/*' - 'lib/mastodon/*'
- 'lib/cli' - 'lib/cli.rb'
Style/ClassAndModuleChildren: Style/ClassAndModuleChildren:
Enabled: false Enabled: false

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class Api::V1::Accounts::IdentityProofsController < Api::BaseController
before_action :require_user!
before_action :set_account
respond_to :json
def index
@proofs = @account.identity_proofs.active
render json: @proofs, each_serializer: REST::IdentityProofSerializer
end
private
def set_account
@account = Account.find(params[:account_id])
end
end

View file

@ -18,7 +18,12 @@ class Settings::IdentityProofsController < Settings::BaseController
provider_username: params[:provider_username] provider_username: params[:provider_username]
) )
render layout: 'auth' if current_account.username == params[:username]
render layout: 'auth'
else
flash[:alert] = I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username)
redirect_to settings_identity_proofs_path
end
end end
def create def create
@ -26,6 +31,7 @@ class Settings::IdentityProofsController < Settings::BaseController
@proof.token = resource_params[:token] @proof.token = resource_params[:token]
if @proof.save if @proof.save
PostStatusService.new.call(current_user.account, text: post_params[:status_text]) if publish_proof?
redirect_to @proof.on_success_path(params[:user_agent]) redirect_to @proof.on_success_path(params[:user_agent])
else else
flash[:alert] = I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize) flash[:alert] = I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
@ -36,10 +42,22 @@ class Settings::IdentityProofsController < Settings::BaseController
private private
def check_required_params def check_required_params
redirect_to settings_identity_proofs_path unless [:provider, :provider_username, :token].all? { |k| params[k].present? } redirect_to settings_identity_proofs_path unless [:provider, :provider_username, :username, :token].all? { |k| params[k].present? }
end end
def resource_params def resource_params
params.require(:account_identity_proof).permit(:provider, :provider_username, :token) params.require(:account_identity_proof).permit(:provider, :provider_username, :token)
end end
def publish_proof?
ActiveModel::Type::Boolean.new.cast(post_params[:post_status])
end
def post_params
params.require(:account_identity_proof).permit(:post_status, :status_text)
end
def set_body_classes
@body_classes = ''
end
end end

View file

@ -0,0 +1,30 @@
import api from '../api';
export const IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST = 'IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST';
export const IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS = 'IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS';
export const IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL = 'IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL';
export const fetchAccountIdentityProofs = accountId => (dispatch, getState) => {
dispatch(fetchAccountIdentityProofsRequest(accountId));
api(getState).get(`/api/v1/accounts/${accountId}/identity_proofs`)
.then(({ data }) => dispatch(fetchAccountIdentityProofsSuccess(accountId, data)))
.catch(err => dispatch(fetchAccountIdentityProofsFail(accountId, err)));
};
export const fetchAccountIdentityProofsRequest = id => ({
type: IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST,
id,
});
export const fetchAccountIdentityProofsSuccess = (accountId, identity_proofs) => ({
type: IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS,
accountId,
identity_proofs,
});
export const fetchAccountIdentityProofsFail = (accountId, err) => ({
type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL,
accountId,
err,
});

View file

@ -62,6 +62,7 @@ class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -81,7 +82,7 @@ class Header extends ImmutablePureComponent {
} }
render () { render () {
const { account, intl, domain } = this.props; const { account, intl, domain, identity_proofs } = this.props;
if (!account) { if (!account) {
return null; return null;
@ -234,8 +235,20 @@ class Header extends ImmutablePureComponent {
<div className='account__header__extra'> <div className='account__header__extra'>
<div className='account__header__bio'> <div className='account__header__bio'>
{fields.size > 0 && ( { (fields.size > 0 || identity_proofs.size > 0) && (
<div className='account__header__fields'> <div className='account__header__fields'>
{identity_proofs.map((proof, i) => (
<dl key={i}>
<dt dangerouslySetInnerHTML={{ __html: proof.get('provider') }} />
<dd className='verified'>
<a href={proof.get('proof_url')} target='_blank' rel='noopener'><span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
<Icon id='check' className='verified__mark' />
</span></a>
<a href={proof.get('profile_url')} target='_blank' rel='noopener'><span dangerouslySetInnerHTML={{ __html: ' '+proof.get('provider_username') }} /></a>
</dd>
</dl>
))}
{fields.map((pair, i) => ( {fields.map((pair, i) => (
<dl key={i}> <dl key={i}>
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} /> <dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />

View file

@ -12,6 +12,7 @@ export default class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
identity_proofs: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired,
@ -84,7 +85,7 @@ export default class Header extends ImmutablePureComponent {
} }
render () { render () {
const { account, hideTabs } = this.props; const { account, hideTabs, identity_proofs } = this.props;
if (account === null) { if (account === null) {
return <MissingIndicator />; return <MissingIndicator />;
@ -96,6 +97,7 @@ export default class Header extends ImmutablePureComponent {
<InnerHeader <InnerHeader
account={account} account={account}
identity_proofs={identity_proofs}
onFollow={this.handleFollow} onFollow={this.handleFollow}
onBlock={this.handleBlock} onBlock={this.handleBlock}
onMention={this.handleMention} onMention={this.handleMention}

View file

@ -21,6 +21,7 @@ import { openModal } from '../../../actions/modal';
import { blockDomain, unblockDomain } from '../../../actions/domain_blocks'; import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { unfollowModal } from '../../../initial_state'; import { unfollowModal } from '../../../initial_state';
import { List as ImmutableList } from 'immutable';
const messages = defineMessages({ const messages = defineMessages({
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
@ -35,6 +36,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, { accountId }) => ({ const mapStateToProps = (state, { accountId }) => ({
account: getAccount(state, accountId), account: getAccount(state, accountId),
domain: state.getIn(['meta', 'domain']), domain: state.getIn(['meta', 'domain']),
identity_proofs: state.getIn(['identity_proofs', accountId], ImmutableList()),
}); });
return mapStateToProps; return mapStateToProps;

View file

@ -12,6 +12,7 @@ import ColumnBackButton from '../../components/column_back_button';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
const path = withReplies ? `${accountId}:with_replies` : accountId; const path = withReplies ? `${accountId}:with_replies` : accountId;
@ -42,6 +43,7 @@ class AccountTimeline extends ImmutablePureComponent {
const { params: { accountId }, withReplies } = this.props; const { params: { accountId }, withReplies } = this.props;
this.props.dispatch(fetchAccount(accountId)); this.props.dispatch(fetchAccount(accountId));
this.props.dispatch(fetchAccountIdentityProofs(accountId));
if (!withReplies) { if (!withReplies) {
this.props.dispatch(expandAccountFeaturedTimeline(accountId)); this.props.dispatch(expandAccountFeaturedTimeline(accountId));
} }
@ -51,6 +53,7 @@ class AccountTimeline extends ImmutablePureComponent {
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
this.props.dispatch(fetchAccount(nextProps.params.accountId)); this.props.dispatch(fetchAccount(nextProps.params.accountId));
this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
if (!nextProps.withReplies) { if (!nextProps.withReplies) {
this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId)); this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
} }

View file

@ -83,7 +83,7 @@
"compose_form.spoiler.unmarked": "Text není skrytý", "compose_form.spoiler.unmarked": "Text není skrytý",
"compose_form.spoiler_placeholder": "Sem napište vaše varování", "compose_form.spoiler_placeholder": "Sem napište vaše varování",
"confirmation_modal.cancel": "Zrušit", "confirmation_modal.cancel": "Zrušit",
"confirmations.block.block_and_report": "Block & Report", "confirmations.block.block_and_report": "Blokovat a nahlásit",
"confirmations.block.confirm": "Blokovat", "confirmations.block.confirm": "Blokovat",
"confirmations.block.message": "Jste si jistý/á, že chcete zablokovat uživatele {name}?", "confirmations.block.message": "Jste si jistý/á, že chcete zablokovat uživatele {name}?",
"confirmations.delete.confirm": "Smazat", "confirmations.delete.confirm": "Smazat",

View file

@ -0,0 +1,25 @@
import { Map as ImmutableMap, fromJS } from 'immutable';
import {
IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST,
IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS,
IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL,
} from '../actions/identity_proofs';
const initialState = ImmutableMap();
export default function identityProofsReducer(state = initialState, action) {
switch(action.type) {
case IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST:
return state.set('isLoading', true);
case IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL:
return state.set('isLoading', false);
case IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS:
return state.update(identity_proofs => identity_proofs.withMutations(map => {
map.set('isLoading', false);
map.set('loaded', true);
map.set(action.accountId, fromJS(action.identity_proofs));
}));
default:
return state;
}
};

View file

@ -30,6 +30,7 @@ import filters from './filters';
import conversations from './conversations'; import conversations from './conversations';
import suggestions from './suggestions'; import suggestions from './suggestions';
import polls from './polls'; import polls from './polls';
import identity_proofs from './identity_proofs';
const reducers = { const reducers = {
dropdown_menu, dropdown_menu,
@ -56,6 +57,7 @@ const reducers = {
notifications, notifications,
height_cache, height_cache,
custom_emojis, custom_emojis,
identity_proofs,
lists, lists,
listEditor, listEditor,
listAdder, listAdder,

View file

@ -3064,15 +3064,19 @@ a.status-card.compact:hover {
.relationship-tag { .relationship-tag {
color: $primary-text-color; color: $primary-text-color;
margin-bottom: 4px; margin-bottom: 4px;
opacity: 0.7;
display: block; display: block;
vertical-align: top; vertical-align: top;
background-color: rgba($base-overlay-background, 0.4); background-color: $base-overlay-background;
text-transform: uppercase; text-transform: uppercase;
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
padding: 4px; padding: 4px;
border-radius: 4px; border-radius: 4px;
opacity: 0.7;
&:hover {
opacity: 1;
}
} }
.setting-toggle { .setting-toggle {

View file

@ -10,12 +10,10 @@
} }
.logo-container { .logo-container {
margin: 100px auto; margin: 100px auto 50px;
margin-bottom: 50px;
@media screen and (max-width: 400px) { @media screen and (max-width: 500px) {
margin: 30px auto; margin: 40px auto 0;
margin-bottom: 20px;
} }
h1 { h1 {

View file

@ -854,13 +854,19 @@ code {
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
flex-shrink: 1; flex-shrink: 1;
max-width: 50%;
&-sep { &-sep {
align-self: center;
flex-grow: 0; flex-grow: 0;
overflow: visible; overflow: visible;
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
p {
word-break: break-word;
}
} }
.account__avatar { .account__avatar {
@ -882,12 +888,13 @@ code {
height: 100%; height: 100%;
left: 50%; left: 50%;
position: absolute; position: absolute;
top: 0;
width: 1px; width: 1px;
} }
} }
&__row { &__row {
align-items: center; align-items: flex-start;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }

View file

@ -1,7 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class ProofProvider::Keybase class ProofProvider::Keybase
BASE_URL = 'https://keybase.io' BASE_URL = ENV.fetch('KEYBASE_BASE_URL', 'https://keybase.io')
DOMAIN = ENV.fetch('KEYBASE_DOMAIN', Rails.configuration.x.local_domain)
class Error < StandardError; end class Error < StandardError; end

View file

@ -14,7 +14,7 @@ class ProofProvider::Keybase::ConfigSerializer < ActiveModel::Serializer
end end
def domain def domain
Rails.configuration.x.local_domain ProofProvider::Keybase::DOMAIN
end end
def display_name def display_name
@ -66,6 +66,6 @@ class ProofProvider::Keybase::ConfigSerializer < ActiveModel::Serializer
end end
def contact def contact
[Setting.site_contact_email.presence].compact [Setting.site_contact_email.presence || 'unknown'].compact
end end
end end

View file

@ -49,14 +49,10 @@ class ProofProvider::Keybase::Verifier
def query_params def query_params
{ {
domain: domain, domain: ProofProvider::Keybase::DOMAIN,
kb_username: @provider_username, kb_username: @provider_username,
username: @local_username, username: @local_username,
sig_hash: @token, sig_hash: @token,
} }
end end
def domain
Rails.configuration.x.local_domain
end
end end

View file

@ -26,7 +26,7 @@ class AccountIdentityProof < ApplicationRecord
scope :active, -> { where(verified: true, live: true) } scope :active, -> { where(verified: true, live: true) }
after_create_commit :queue_worker after_commit :queue_worker, if: :saved_change_to_token?
delegate :refresh!, :on_success_path, :badge, to: :provider_instance delegate :refresh!, :on_success_path, :badge, to: :provider_instance

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
class REST::IdentityProofSerializer < ActiveModel::Serializer
attributes :provider, :provider_username, :updated_at, :proof_url, :profile_url
def proof_url
object.badge.proof_url
end
def profile_url
object.badge.profile_url
end
def provider
object.provider.capitalize
end
end

View file

@ -27,5 +27,10 @@
%p= t('identity_proofs.i_am_html', username: content_tag(:strong, @proof.provider_username), service: @proof.provider.capitalize) %p= t('identity_proofs.i_am_html', username: content_tag(:strong, @proof.provider_username), service: @proof.provider.capitalize)
.connection-prompt__post
= f.input :post_status, label: t('identity_proofs.publicize_checkbox'), as: :boolean, wrapper: :with_label, :input_html => { checked: true }
= f.input :status_text, as: :text, input_html: { value: t('identity_proofs.publicize_toot', username: @proof.provider_username, service: @proof.provider.capitalize, url: @proof.badge.proof_url), rows: 4 }
= f.button :button, t('identity_proofs.authorize'), type: :submit = f.button :button, t('identity_proofs.authorize'), type: :submit
= link_to t('simple_form.no'), settings_identity_proofs_url, class: 'button negative' = link_to t('simple_form.no'), settings_identity_proofs_url, class: 'button negative'

View file

@ -2,8 +2,9 @@
cs: cs:
activerecord: activerecord:
attributes: attributes:
status: poll:
owned_poll: Anketa expires_at: Uzávěrka
options: Volby
errors: errors:
models: models:
account: account:

View file

@ -249,6 +249,7 @@ cs:
feature_profile_directory: Adresář profilů feature_profile_directory: Adresář profilů
feature_registrations: Registrace feature_registrations: Registrace
feature_relay: Federovací most feature_relay: Federovací most
feature_timeline_preview: Náhled časové osy
features: Vlastnosti features: Vlastnosti
hidden_service: Federace se skrytými službami hidden_service: Federace se skrytými službami
open_reports: otevřená hlášení open_reports: otevřená hlášení

View file

@ -652,10 +652,13 @@ en:
keybase: keybase:
invalid_token: Keybase tokens are hashes of signatures and must be 66 hex characters invalid_token: Keybase tokens are hashes of signatures and must be 66 hex characters
verification_failed: Keybase does not recognize this token as a signature of Keybase user %{kb_username}. Please retry from Keybase. verification_failed: Keybase does not recognize this token as a signature of Keybase user %{kb_username}. Please retry from Keybase.
wrong_user: Cannot create a proof for %{proving} while logged in as %{current}. Log in as %{proving} and try again.
explanation_html: Here you can cryptographically connect your other identities, such as a Keybase profile. This lets other people send you encrypted messages and trust content you send them. explanation_html: Here you can cryptographically connect your other identities, such as a Keybase profile. This lets other people send you encrypted messages and trust content you send them.
i_am_html: I am %{username} on %{service}. i_am_html: I am %{username} on %{service}.
identity: Identity identity: Identity
inactive: Inactive inactive: Inactive
publicize_checkbox: 'And toot this:'
publicize_toot: 'It is proven! I am %{username} on %{service}: %{url}'
status: Verification status status: Verification status
view_proof: View proof view_proof: View proof
imports: imports:

View file

@ -366,6 +366,7 @@ Rails.application.routes.draw do
resources :followers, only: :index, controller: 'accounts/follower_accounts' resources :followers, only: :index, controller: 'accounts/follower_accounts'
resources :following, only: :index, controller: 'accounts/following_accounts' resources :following, only: :index, controller: 'accounts/following_accounts'
resources :lists, only: :index, controller: 'accounts/lists' resources :lists, only: :index, controller: 'accounts/lists'
resources :identity_proofs, only: :index, controller: 'accounts/identity_proofs'
member do member do
post :follow post :follow

View file

@ -41,6 +41,79 @@ module Mastodon
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains' desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
subcommand 'domains', Mastodon::DomainsCLI subcommand 'domains', Mastodon::DomainsCLI
option :dry_run, type: :boolean
desc 'self-destruct', 'Erase the server from the federation'
long_desc <<~LONG_DESC
Erase the server from the federation by broadcasting account delete
activities to all known other servers. This allows a "clean exit" from
running a Mastodon server, as it leaves next to no cache behind on
other servers.
This command is always interactive and requires confirmation twice.
No local data is actually deleted, because emptying the
database or removing files is much faster through other, external
means, such as e.g. deleting the entire VPS. However, because other
servers will delete data about local users, but no local data will be
updated (such as e.g. followers), there will be a state mismatch
that will lead to glitches and issues if you then continue to run and use
the server.
So either you know exactly what you are doing, or you are starting
from a blank slate afterwards by manually clearing out all the local
data!
LONG_DESC
def self_destruct
require 'tty-prompt'
prompt = TTY::Prompt.new
exit(1) unless prompt.ask('Type in the domain of the server to confirm:', required: true) == Rails.configuration.x.local_domain
prompt.warn('This operation WILL NOT be reversible. It can also take a long time.')
prompt.warn('While the data won\'t be erased locally, the server will be in a BROKEN STATE afterwards.')
prompt.warn('A running Sidekiq process is required. Do not shut it down until queues clear.')
exit(1) if prompt.no?('Are you sure you want to proceed?')
inboxes = Account.inboxes
processed = 0
dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
if inboxes.empty?
prompt.ok('It seems like your server has not federated with anything')
prompt.ok('You can shut it down and delete it any time')
return
end
prompt.warn('Do NOT interrupt this process...')
Account.local.without_suspended.find_each do |account|
payload = ActiveModelSerializers::SerializableResource.new(
account,
serializer: ActivityPub::DeleteActorSerializer,
adapter: ActivityPub::Adapter
).as_json
json = Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(account))
unless options[:dry_run]
ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
[json, account.id, inbox_url]
end
account.update_column(:suspended, true)
end
processed += 1
end
prompt.ok("Queued #{inboxes.size * processed} items into Sidekiq for #{processed} accounts#{dry_run}")
prompt.ok('Wait until Sidekiq processes all items, then you can shut everything down and delete the data')
rescue TTY::Reader::InputInterrupt
exit(1)
end
map %w(--version -v) => :version map %w(--version -v) => :version
desc 'version', 'Show version' desc 'version', 'Show version'

View file

@ -1,6 +1,7 @@
require 'rails_helper' require 'rails_helper'
describe Settings::IdentityProofsController do describe Settings::IdentityProofsController do
include RoutingHelper
render_views render_views
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
@ -9,8 +10,15 @@ describe Settings::IdentityProofsController do
let(:provider) { 'keybase' } let(:provider) { 'keybase' }
let(:findable_id) { Faker::Number.number(5) } let(:findable_id) { Faker::Number.number(5) }
let(:unfindable_id) { Faker::Number.number(5) } let(:unfindable_id) { Faker::Number.number(5) }
let(:new_proof_params) do
{ provider: provider, provider_username: kbname, token: valid_token, username: user.account.username }
end
let(:status_text) { "i just proved that i am also #{kbname} on #{provider}." }
let(:status_posting_params) do
{ post_status: '0', status_text: status_text }
end
let(:postable_params) do let(:postable_params) do
{ account_identity_proof: { provider: provider, provider_username: kbname, token: valid_token } } { account_identity_proof: new_proof_params.merge(status_posting_params) }
end end
before do before do
@ -19,10 +27,32 @@ describe Settings::IdentityProofsController do
end end
describe 'new proof creation' do describe 'new proof creation' do
context 'GET #new with no existing proofs' do context 'GET #new' do
it 'redirects to :index' do context 'with all of the correct params' do
get :new before do
expect(response).to redirect_to settings_identity_proofs_path allow_any_instance_of(ProofProvider::Keybase::Badge).to receive(:avatar_url) { full_pack_url('media/images/void.png') }
end
it 'renders the template' do
get :new, params: new_proof_params
expect(response).to render_template(:new)
end
end
context 'without any params' do
it 'redirects to :index' do
get :new, params: {}
expect(response).to redirect_to settings_identity_proofs_path
end
end
context 'with params to prove a different, not logged-in user' do
let(:wrong_user_params) { new_proof_params.merge(username: 'someone_else') }
it 'shows a helpful alert' do
get :new, params: wrong_user_params
expect(flash[:alert]).to eq I18n.t('identity_proofs.errors.wrong_user', proving: 'someone_else', current: user.account.username)
end
end end
end end
@ -44,6 +74,23 @@ describe Settings::IdentityProofsController do
post :create, params: postable_params post :create, params: postable_params
expect(response).to redirect_to root_url expect(response).to redirect_to root_url
end end
it 'does not post a status' do
expect(PostStatusService).not_to receive(:new)
post :create, params: postable_params
end
context 'and the user has requested to post a status' do
let(:postable_params_with_status) do
postable_params.tap { |p| p[:account_identity_proof][:post_status] = '1' }
end
it 'posts a status' do
expect_any_instance_of(PostStatusService).to receive(:call).with(user.account, text: status_text)
post :create, params: postable_params_with_status
end
end
end end
context 'when saving fails' do context 'when saving fails' do