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

Conflicts:
- `app/controllers/settings/preferences_controller.rb`:
  Conflicts due to us having more user settings and upstream dropping
  `hide_network` (to replace it with an account attribute, properly migrated).
  Dropped `hide_network` like upstream.
- `app/lib/user_settings_decorator.rb`:
  Conflicts due to us having more user settings and upstream dropping
  `hide_network` (to replace it with an account attribute, properly migrated).
  Dropped `hide_network` like upstream.
- `app/models/status.rb`:
  Conflict because of slight change in how glitch-soc handles the scope to
  filter out local-only posts for anonymous viewers.
  Took upstream's changes and re-applied glitch-soc's change.
- `app/models/user.rb`:
  Conflicts due to us having more user settings and upstream dropping
  `hide_network` (to replace it with an account attribute, properly migrated).
  Dropped `hide_network` like upstream.
- `app/views/directories/index.html.haml`:
  Conflict because upstream redesigned that page while glitch-soc had a minor
  change to support hiding the number of followers.
  Ported glitch-soc's change on top of upstream's redesign.

Additional changes:
- `app/models/account_statuses_filter.rb`:
  See change to `app/models/status.rb`.
This commit is contained in:
Claire
2022-03-08 20:22:54 +01:00
75 changed files with 906 additions and 597 deletions

View File

@@ -5,7 +5,7 @@ RSpec.describe AccountsController, type: :controller do
let(:account) { Fabricate(:account) }
shared_examples 'cachable response' do
shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -374,7 +374,7 @@ RSpec.describe AccountsController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
it 'renders account' do
json = body_as_json
@@ -432,7 +432,7 @@ RSpec.describe AccountsController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
it 'renders account' do
json = body_as_json
@@ -499,7 +499,7 @@ RSpec.describe AccountsController, type: :controller do
expect(response).to have_http_status(200)
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
end
context do

View File

@@ -7,7 +7,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
let!(:private_pinned) { Fabricate(:status, account: account, text: 'secret private stuff', visibility: :private) }
let(:remote_account) { nil }
shared_examples 'cachable response' do
shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -48,7 +48,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
it 'returns orderedItems with pinned statuses' do
expect(body[:orderedItems]).to be_an Array
@@ -101,7 +101,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
it 'returns orderedItems with pinned statuses' do
json = body_as_json

View File

@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe ActivityPub::OutboxesController, type: :controller do
let!(:account) { Fabricate(:account) }
shared_examples 'cachable response' do
shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -53,7 +53,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
expect(body[:totalItems]).to eq 4
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
it 'does not have a Vary header' do
expect(response.headers['Vary']).to be_nil
@@ -98,7 +98,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
expect(body[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
it 'returns Vary header with Signature' do
expect(response.headers['Vary']).to include 'Signature'

View File

@@ -8,7 +8,7 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
let(:remote_reply_id) { 'https://foobar.com/statuses/1234' }
let(:remote_querier) { nil }
shared_examples 'cachable response' do
shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -93,7 +93,7 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
context 'without only_other_accounts' do
it "returns items with thread author's replies" do

View File

@@ -31,7 +31,7 @@ describe Api::V1::Accounts::NotesController do
end
end
context 'when account note exceends allowed length' do
context 'when account note exceeds allowed length' do
let(:comment) { 'a' * 2_001 }
it 'returns 422' do

View File

@@ -140,7 +140,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
expect(response).to have_http_status(200)
end
it 'unsensitives account' do
it 'unsensitizes account' do
expect(account.reload.sensitized?).to be false
end
end

View File

@@ -56,7 +56,7 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control
Fabricate(:favourite, status: status)
end
it 'returns http unautharized' do
it 'returns http unauthorized' do
get :index, params: { status_id: status.id }
expect(response).to have_http_status(404)
end

View File

@@ -56,7 +56,7 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll
Fabricate(:status, reblog_of_id: status.id)
end
it 'returns http unautharized' do
it 'returns http unauthorized' do
get :index, params: { status_id: status.id }
expect(response).to have_http_status(404)
end

View File

@@ -130,7 +130,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
let(:status) { Fabricate(:status, account: user.account, visibility: :private) }
describe 'GET #show' do
it 'returns http unautharized' do
it 'returns http unauthorized' do
get :show, params: { id: status.id }
expect(response).to have_http_status(404)
end
@@ -141,7 +141,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
Fabricate(:status, account: user.account, thread: status)
end
it 'returns http unautharized' do
it 'returns http unauthorized' do
get :context, params: { id: status.id }
expect(response).to have_http_status(404)
end

View File

@@ -191,30 +191,30 @@ describe ApplicationController, type: :controller do
controller do
before_action :require_admin!
def sucesss
def success
head 200
end
end
before do
routes.draw { get 'sucesss' => 'anonymous#sucesss' }
routes.draw { get 'success' => 'anonymous#success' }
end
it 'returns a 403 if current user is not admin' do
sign_in(Fabricate(:user, admin: false))
get 'sucesss'
get 'success'
expect(response).to have_http_status(403)
end
it 'returns a 403 if current user is only a moderator' do
sign_in(Fabricate(:user, moderator: true))
get 'sucesss'
get 'success'
expect(response).to have_http_status(403)
end
it 'does nothing if current user is admin' do
sign_in(Fabricate(:user, admin: true))
get 'sucesss'
get 'success'
expect(response).to have_http_status(200)
end
end
@@ -223,30 +223,30 @@ describe ApplicationController, type: :controller do
controller do
before_action :require_staff!
def sucesss
def success
head 200
end
end
before do
routes.draw { get 'sucesss' => 'anonymous#sucesss' }
routes.draw { get 'success' => 'anonymous#success' }
end
it 'returns a 403 if current user is not admin or moderator' do
sign_in(Fabricate(:user, admin: false, moderator: false))
get 'sucesss'
get 'success'
expect(response).to have_http_status(403)
end
it 'does nothing if current user is moderator' do
sign_in(Fabricate(:user, moderator: true))
get 'sucesss'
get 'success'
expect(response).to have_http_status(200)
end
it 'does nothing if current user is admin' do
sign_in(Fabricate(:user, admin: true))
get 'sucesss'
get 'success'
expect(response).to have_http_status(200)
end
end

View File

@@ -103,7 +103,7 @@ describe FollowerAccountsController do
context 'when account hides their network' do
before do
alice.user.settings.hide_network = true
alice.update(hide_collections: true)
end
it 'returns followers count' do

View File

@@ -103,7 +103,7 @@ describe FollowingAccountsController do
context 'when account hides their network' do
before do
alice.user.settings.hide_network = true
alice.update(hide_collections: true)
end
it 'returns followers count' do

View File

@@ -5,7 +5,7 @@ require 'rails_helper'
describe StatusesController do
render_views
shared_examples 'cachable response' do
shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -108,7 +108,7 @@ describe StatusesController do
expect(response.headers['Vary']).to eq 'Accept'
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
@@ -496,7 +496,7 @@ describe StatusesController do
expect(response.headers['Vary']).to eq 'Accept'
end
it_behaves_like 'cachable response'
it_behaves_like 'cacheable response'
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'

View File

@@ -60,7 +60,7 @@ describe ApplicationHelper do
end
describe 'favicon_path' do
it 'returns /favicon.ico on production enviromnent' do
it 'returns /favicon.ico on production environment' do
expect(Rails.env).to receive(:production?).and_return(true)
expect(helper.favicon_path).to eq '/favicon.ico'
end

View File

@@ -6,7 +6,7 @@ RSpec.describe TagManager do
around do |example|
original_local_domain = Rails.configuration.x.local_domain
Rails.configuration.x.local_domain = 'domain.test'
Rails.configuration.x.local_domain = 'domain.example.com'
example.run
@@ -18,11 +18,11 @@ RSpec.describe TagManager do
end
it 'returns true if the slash-stripped string equals to local domain' do
expect(TagManager.instance.local_domain?('DoMaIn.Test/')).to eq true
expect(TagManager.instance.local_domain?('DoMaIn.Example.com/')).to eq true
end
it 'returns false for irrelevant string' do
expect(TagManager.instance.local_domain?('DoMaIn.Test!')).to eq false
expect(TagManager.instance.local_domain?('DoMaIn.Example.com!')).to eq false
end
end
@@ -31,7 +31,7 @@ RSpec.describe TagManager do
around do |example|
original_web_domain = Rails.configuration.x.web_domain
Rails.configuration.x.web_domain = 'domain.test'
Rails.configuration.x.web_domain = 'domain.example.com'
example.run
@@ -43,11 +43,11 @@ RSpec.describe TagManager do
end
it 'returns true if the slash-stripped string equals to web domain' do
expect(TagManager.instance.web_domain?('DoMaIn.Test/')).to eq true
expect(TagManager.instance.web_domain?('DoMaIn.Example.com/')).to eq true
end
it 'returns false for string with irrelevant characters' do
expect(TagManager.instance.web_domain?('DoMaIn.Test!')).to eq false
expect(TagManager.instance.web_domain?('DoMaIn.Example.com!')).to eq false
end
end
@@ -57,7 +57,7 @@ RSpec.describe TagManager do
end
it 'returns normalized domain' do
expect(TagManager.instance.normalize_domain('DoMaIn.Test/')).to eq 'domain.test'
expect(TagManager.instance.normalize_domain('DoMaIn.Example.com/')).to eq 'domain.example.com'
end
end
@@ -69,18 +69,18 @@ RSpec.describe TagManager do
end
it 'returns true if the normalized string with port is local URL' do
Rails.configuration.x.web_domain = 'domain.test:42'
expect(TagManager.instance.local_url?('https://DoMaIn.Test:42/')).to eq true
Rails.configuration.x.web_domain = 'domain.example.com:42'
expect(TagManager.instance.local_url?('https://DoMaIn.Example.com:42/')).to eq true
end
it 'returns true if the normalized string without port is local URL' do
Rails.configuration.x.web_domain = 'domain.test'
expect(TagManager.instance.local_url?('https://DoMaIn.Test/')).to eq true
Rails.configuration.x.web_domain = 'domain.example.com'
expect(TagManager.instance.local_url?('https://DoMaIn.Example.com/')).to eq true
end
it 'returns false for string with irrelevant characters' do
Rails.configuration.x.web_domain = 'domain.test'
expect(TagManager.instance.local_url?('https://domainn.test/')).to eq false
Rails.configuration.x.web_domain = 'domain.example.com'
expect(TagManager.instance.local_url?('https://domain.example.net/')).to eq false
end
end
end

View File

@@ -0,0 +1,229 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AccountStatusesFilter do
let(:account) { Fabricate(:account) }
let(:current_account) { nil }
let(:params) { {} }
subject { described_class.new(account, current_account, params) }
def status!(visibility)
Fabricate(:status, account: account, visibility: visibility)
end
def status_with_tag!(visibility, tag)
Fabricate(:status, account: account, visibility: visibility, tags: [tag])
end
def status_with_parent!(visibility)
Fabricate(:status, account: account, visibility: visibility, thread: Fabricate(:status))
end
def status_with_reblog!(visibility)
Fabricate(:status, account: account, visibility: visibility, reblog: Fabricate(:status))
end
def status_with_mention!(visibility, mentioned_account = nil)
Fabricate(:status, account: account, visibility: visibility).tap do |status|
Fabricate(:mention, status: status, account: mentioned_account || Fabricate(:account))
end
end
def status_with_media_attachment!(visibility)
Fabricate(:status, account: account, visibility: visibility).tap do |status|
Fabricate(:media_attachment, account: account, status: status)
end
end
describe '#results' do
let(:tag) { Fabricate(:tag) }
before do
status!(:public)
status!(:unlisted)
status!(:private)
status_with_parent!(:public)
status_with_reblog!(:public)
status_with_tag!(:public, tag)
status_with_mention!(:direct)
status_with_media_attachment!(:public)
end
shared_examples 'filter params' do
context 'with only_media param' do
let(:params) { { only_media: true } }
it 'returns only statuses with media' do
expect(subject.results.all?(&:with_media?)).to be true
end
end
context 'with tagged param' do
let(:params) { { tagged: tag.name } }
it 'returns only statuses with tag' do
expect(subject.results.all? { |s| s.tags.include?(tag) }).to be true
end
end
context 'with exclude_replies param' do
let(:params) { { exclude_replies: true } }
it 'returns only statuses that are not replies' do
expect(subject.results.none?(&:reply?)).to be true
end
end
context 'with exclude_reblogs param' do
let(:params) { { exclude_reblogs: true } }
it 'returns only statuses that are not reblogs' do
expect(subject.results.none?(&:reblog?)).to be true
end
end
end
context 'when accessed anonymously' do
let(:current_account) { nil }
let(:direct_status) { nil }
it 'returns only public statuses' do
expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public)
end
it 'returns public replies' do
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
end
it 'returns public reblogs' do
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
end
it_behaves_like 'filter params'
end
context 'when accessed with a blocked account' do
let(:current_account) { Fabricate(:account) }
before do
account.block!(current_account)
end
it 'returns nothing' do
expect(subject.results.to_a).to be_empty
end
end
context 'when accessed by self' do
let(:current_account) { account }
it 'returns everything' do
expect(subject.results.pluck(:visibility).uniq).to match_array %w(direct private unlisted public)
end
it 'returns replies' do
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
end
it 'returns reblogs' do
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
end
it_behaves_like 'filter params'
end
context 'when accessed by a follower' do
let(:current_account) { Fabricate(:account) }
before do
current_account.follow!(account)
end
it 'returns private statuses' do
expect(subject.results.pluck(:visibility).uniq).to match_array %w(private unlisted public)
end
it 'returns replies' do
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
end
it 'returns reblogs' do
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
end
context 'when there is a direct status mentioning the non-follower' do
let!(:direct_status) { status_with_mention!(:direct, current_account) }
it 'returns the direct status' do
expect(subject.results.pluck(:id)).to include(direct_status.id)
end
end
it_behaves_like 'filter params'
end
context 'when accessed by a non-follower' do
let(:current_account) { Fabricate(:account) }
it 'returns only public statuses' do
expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public)
end
it 'returns public replies' do
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
end
it 'returns public reblogs' do
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
end
context 'when there is a private status mentioning the non-follower' do
let!(:private_status) { status_with_mention!(:private, current_account) }
it 'returns the private status' do
expect(subject.results.pluck(:id)).to include(private_status.id)
end
end
context 'when blocking a reblogged account' do
let(:reblog) { status_with_reblog!('public') }
before do
current_account.block!(reblog.reblog.account)
end
it 'does not return reblog of blocked account' do
expect(subject.results.pluck(:id)).to_not include(reblog.id)
end
end
context 'when muting a reblogged account' do
let(:reblog) { status_with_reblog!('public') }
before do
current_account.mute!(reblog.reblog.account)
end
it 'does not return reblog of muted account' do
expect(subject.results.pluck(:id)).to_not include(reblog.id)
end
end
context 'when blocked by a reblogged account' do
let(:reblog) { status_with_reblog!('public') }
before do
reblog.reblog.account.block!(current_account)
end
it 'does not return reblog of blocked-by account' do
expect(subject.results.pluck(:id)).to_not include(reblog.id)
end
end
it_behaves_like 'filter params'
end
end
end

View File

@@ -119,7 +119,7 @@ describe Report do
end
end
describe 'validatiions' do
describe 'validations' do
it 'has a valid fabricator' do
report = Fabricate(:report)
report.valid?

View File

@@ -435,59 +435,6 @@ RSpec.describe Status, type: :model do
end
end
describe '.permitted_for' do
subject { described_class.permitted_for(target_account, account).pluck(:visibility) }
let(:target_account) { alice }
let(:account) { bob }
let!(:public_status) { Fabricate(:status, account: target_account, visibility: 'public') }
let!(:unlisted_status) { Fabricate(:status, account: target_account, visibility: 'unlisted') }
let!(:private_status) { Fabricate(:status, account: target_account, visibility: 'private') }
let!(:direct_status) do
Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
Fabricate(:mention, status: status, account: account)
end
end
let!(:other_direct_status) do
Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
Fabricate(:mention, status: status)
end
end
context 'given nil' do
let(:account) { nil }
let(:direct_status) { nil }
it { is_expected.to eq(%w(unlisted public)) }
end
context 'given blocked account' do
before do
target_account.block!(account)
end
it { is_expected.to be_empty }
end
context 'given same account' do
let(:account) { target_account }
it { is_expected.to eq(%w(direct direct private unlisted public)) }
end
context 'given followed account' do
before do
account.follow!(target_account)
end
it { is_expected.to eq(%w(direct private unlisted public)) }
end
context 'given unfollowed account' do
it { is_expected.to eq(%w(direct unlisted public)) }
end
end
describe 'before_validation' do
it 'sets account being replied to correctly over intermediary nodes' do
first_status = Fabricate(:status, account: bob)

View File

@@ -114,13 +114,13 @@ RSpec.describe UserPolicy do
permissions :promote? do
context 'admin?' do
context 'promoteable?' do
context 'promotable?' do
it 'permits' do
expect(subject).to permit(admin, john.user)
end
end
context '!promoteable?' do
context '!promotable?' do
it 'denies' do
expect(subject).to_not permit(admin, admin.user)
end

View File

@@ -0,0 +1,58 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe FamiliarFollowersPresenter do
describe '#accounts' do
let(:account) { Fabricate(:account) }
let(:familiar_follower) { Fabricate(:account) }
let(:requested_accounts) { Fabricate.times(2, :account) }
subject { described_class.new(requested_accounts, account.id) }
before do
familiar_follower.follow!(requested_accounts.first)
account.follow!(familiar_follower)
end
it 'returns a result for each requested account' do
expect(subject.accounts.map(&:id)).to eq requested_accounts.map(&:id)
end
it 'returns followers you follow' do
result = subject.accounts.first
expect(result).to_not be_nil
expect(result.id).to eq requested_accounts.first.id
expect(result.accounts).to match_array([familiar_follower])
end
context 'when requested account hides followers' do
before do
requested_accounts.first.update(hide_collections: true)
end
it 'does not return followers you follow' do
result = subject.accounts.first
expect(result).to_not be_nil
expect(result.id).to eq requested_accounts.first.id
expect(result.accounts).to be_empty
end
end
context 'when familiar follower hides follows' do
before do
familiar_follower.update(hide_collections: true)
end
it 'does not return followers you follow' do
result = subject.accounts.first
expect(result).to_not be_nil
expect(result.id).to eq requested_accounts.first.id
expect(result.accounts).to be_empty
end
end
end
end

View File

@@ -63,20 +63,20 @@ RSpec.describe UnsuspendAccountService, type: :service do
describe 'unsuspending a remote account' do
include_examples 'common behavior' do
let!(:account) { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
let!(:reslove_account_service) { double }
let!(:resolve_account_service) { double }
before do
allow(ResolveAccountService).to receive(:new).and_return(reslove_account_service)
allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service)
end
context 'when the account is not remotely suspended' do
before do
allow(reslove_account_service).to receive(:call).with(account).and_return(account)
allow(resolve_account_service).to receive(:call).with(account).and_return(account)
end
it 're-fetches the account' do
subject.call
expect(reslove_account_service).to have_received(:call).with(account)
expect(resolve_account_service).to have_received(:call).with(account)
end
it "merges back into local followers' feeds" do
@@ -92,7 +92,7 @@ RSpec.describe UnsuspendAccountService, type: :service do
context 'when the account is remotely suspended' do
before do
allow(reslove_account_service).to receive(:call).with(account) do |account|
allow(resolve_account_service).to receive(:call).with(account) do |account|
account.suspend!(origin: :remote)
account
end
@@ -100,7 +100,7 @@ RSpec.describe UnsuspendAccountService, type: :service do
it 're-fetches the account' do
subject.call
expect(reslove_account_service).to have_received(:call).with(account)
expect(resolve_account_service).to have_received(:call).with(account)
end
it "does not merge back into local followers' feeds" do
@@ -116,12 +116,12 @@ RSpec.describe UnsuspendAccountService, type: :service do
context 'when the account is remotely deleted' do
before do
allow(reslove_account_service).to receive(:call).with(account).and_return(nil)
allow(resolve_account_service).to receive(:call).with(account).and_return(nil)
end
it 're-fetches the account' do
subject.call
expect(reslove_account_service).to have_received(:call).with(account)
expect(resolve_account_service).to have_received(:call).with(account)
end
it "does not merge back into local followers' feeds" do

View File

@@ -22,7 +22,7 @@ module ProfileStories
def with_alice_as_local_user
@alice_bio = '@alice and @bob are fictional characters commonly used as'\
'placeholder names in #cryptology, as well as #science and'\
'engineering 📖 literature. Not affilated with @pepe.'
'engineering 📖 literature. Not affiliated with @pepe.'
@alice = Fabricate(
:user,