From b399dc3191552d9bb50f5435b43cd7bc39332111 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 25 Mar 2026 06:23:45 -0400 Subject: [PATCH] Add coverage for "fields" feature of Account (#38369) --- spec/models/account/fields_spec.rb | 198 +++++++++++++++++++++++++++++ spec/models/account_spec.rb | 15 --- 2 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 spec/models/account/fields_spec.rb diff --git a/spec/models/account/fields_spec.rb b/spec/models/account/fields_spec.rb new file mode 100644 index 0000000000..cca2d59e8b --- /dev/null +++ b/spec/models/account/fields_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Account, '#fields' do + subject { Fabricate.build :account } + + describe 'Validations' do + context 'when account is local' do + subject { Fabricate.build :account, domain: nil } + + it { is_expected.to allow_value(fields_empty_name_value).for(:fields) } + it { is_expected.to_not allow_values(fields_over_limit, fields_empty_name).for(:fields) } + + def fields_empty_name_value + Array.new(4) { { 'name' => '', 'value' => '' } } + end + + def fields_over_limit + Array.new(described_class::DEFAULT_FIELDS_SIZE + 1) { { 'name' => 'Name', 'value' => 'Value', 'verified_at' => '01/01/1970' } } + end + + def fields_empty_name + [{ 'name' => '', 'value' => 'Value', 'verified_at' => '01/01/1970' }] + end + end + end + + describe '#fields' do + subject { account.fields } + + let(:account) { Fabricate.build :account } + + context 'when attribute is nil' do + before { account.fields = nil } + + it { is_expected.to be_empty } + end + + context 'when attribute has valid data' do + before { account.fields = [{ 'name' => 'Personal Web Site', 'value' => 'https://host.example' }] } + + it 'returns array of account field objects' do + expect(subject) + .to be_an(Array) + .and contain_exactly( + be_a(Account::Field).and(have_attributes(name: /Personal/, value: /host.example/)) + ) + end + end + + context 'when attribute has invalid data' do + before { account.fields = [{ 'blurp' => 'zorp', '@@@@' => '###' }] } + + it { is_expected.to be_empty } + end + end + + describe '#fields_attributes=' do + let(:account) { Fabricate.build :account } + + context 'when sent empty hash' do + it 'assigns empty array to fields' do + account.fields_attributes = {} + + expect(account.fields) + .to be_empty + end + end + + context 'when sent indexed hash' do + it 'assigns fields array' do + account.fields_attributes = { + '0' => { name: 'Color', value: 'Red' }, + '1' => { name: 'Size', value: 'Medium' }, + } + + expect(account.fields) + .to be_an(Array) + .and contain_exactly( + be_a(Account::Field).and(have_attributes(name: /Color/, value: /Red/)), + be_a(Account::Field).and(have_attributes(name: /Size/, value: /Medium/)) + ) + end + end + + context 'when sent indexed hash with missing values' do + it 'rejects blanks and assigns fields array' do + account.fields_attributes = { + '0' => { name: 'Color', value: 'Red' }, + '1' => { name: '', value: '' }, + } + + expect(account.fields) + .to be_an(Array) + .and contain_exactly( + be_a(Account::Field).and(have_attributes(name: /Color/, value: /Red/)) + ) + end + end + + context 'when sent array of field hashes' do + it 'assigns fields array' do + account.fields_attributes = [ + { name: 'Color', value: 'Red' }, + { name: 'Size', value: 'Medium' }, + ] + + expect(account.fields) + .to be_an(Array) + .and contain_exactly( + be_a(Account::Field).and(have_attributes(name: /Color/, value: /Red/)), + be_a(Account::Field).and(have_attributes(name: /Size/, value: /Medium/)) + ) + end + end + + context 'when fields were previously a hash' do + before { account.fields = {} } + + it 'assigns fields array' do + account.fields_attributes = { + '0' => { name: 'Color', value: 'Red' }, + } + + expect(account.fields) + .to be_an(Array) + .and contain_exactly( + be_a(Account::Field).and(have_attributes(name: /Color/, value: /Red/)) + ) + end + end + + context 'when fields were previously verified' do + before { account.fields = [{ name: 'Color', value: 'Red', verified_at: 2.weeks.ago.to_datetime }] } + + it 'assigns fields array with preserved verification' do + account.fields_attributes = { + '0' => { name: 'Color', value: 'Red' }, + } + + expect(account.fields) + .to be_an(Array) + .and contain_exactly( + be_a(Account::Field).and(have_attributes(name: /Color/, value: /Red/, verified_at: 2.weeks.ago.to_datetime).and(be_verified)) + ) + end + end + end + + describe '#build_fields' do + let(:account) { Fabricate.build :account } + + context 'when fields already full' do + before { account.fields = Array.new(Account::DEFAULT_FIELDS_SIZE) { |i| { name: "Name#{i}", value: 'Test' } } } + + it 'returns nil without updating fields' do + expect(account.build_fields) + .to be_nil + + expect(account.fields) + .to be_an(Array) + .and have_attributes(size: Account::DEFAULT_FIELDS_SIZE) + end + end + + context 'when fields partially full' do + before { account.fields = Array.new(2) { |i| { name: "Name#{i}", value: 'Test' } } } + + it 'returns nil without updating fields' do + expect(account.build_fields) + .to be_an(Array) + + expect(account.attributes['fields']) + .to be_an(Array) + .and contain_exactly( + include('name' => /Name/), + include('name' => /Name/), + include('name' => ''), + include('name' => '') + ) + end + end + + context 'when fields were previously a hash' do + before { account.fields = {} } + + it 'assigns fields array with empty values' do + expect(account.build_fields) + .to be_an(Array) + + expect(account.attributes['fields']) + .to be_an(Array) + .and all(include('name' => '', 'value' => '')) + end + end + end +end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index bb521e234a..8786760125 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -546,9 +546,6 @@ RSpec.describe Account do it { is_expected.to_not allow_values(account_note_over_limit).for(:note) } - it { is_expected.to allow_value(fields_empty_name_value).for(:fields) } - it { is_expected.to_not allow_values(fields_over_limit, fields_empty_name).for(:fields) } - it { is_expected.to validate_absence_of(:followers_url).on(:create) } it { is_expected.to validate_absence_of(:inbox_url).on(:create) } it { is_expected.to validate_absence_of(:shared_inbox_url).on(:create) } @@ -590,18 +587,6 @@ RSpec.describe Account do def account_note_over_limit 'a' * described_class::NOTE_LENGTH_LIMIT * 2 end - - def fields_empty_name_value - Array.new(4) { { 'name' => '', 'value' => '' } } - end - - def fields_over_limit - Array.new(described_class::DEFAULT_FIELDS_SIZE + 1) { { 'name' => 'Name', 'value' => 'Value', 'verified_at' => '01/01/1970' } } - end - - def fields_empty_name - [{ 'name' => '', 'value' => 'Value', 'verified_at' => '01/01/1970' }] - end end describe 'scopes' do