From 48ab85826d909bae42ec1d9cbfed3f2b375a9604 Mon Sep 17 00:00:00 2001 From: Pablo Monfort Date: Wed, 26 Aug 2026 22:25:51 -0300 Subject: [PATCH 1/9] AO3-4122 Add tests for duplicate translation languages --- features/admins/admin_post_news.feature | 9 ++++ features/step_definitions/admin_steps.rb | 10 +++++ spec/models/admin_post_spec.rb | 55 ++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/features/admins/admin_post_news.feature b/features/admins/admin_post_news.feature index 5e5d4c94f07..aa859617d88 100755 --- a/features/admins/admin_post_news.feature +++ b/features/admins/admin_post_news.feature @@ -66,6 +66,15 @@ Feature: Admin Actions to Post News And I am logged in as "ordinaryuser" Then I should see a translated admin post + Scenario: Make a second translation of an admin post in the same language + Given I have posted an admin post + And basic languages + And the admin post "Erste Ankuendigung" translating "Default Admin Post" to "Deutsch" + And I am logged in as a "translation" admin + When I make a translation of an admin post + Then I should see "Sorry! We couldn't save this admin post because:" + And I should see "Translated post already has a translation in this language" + Scenario: Make a translation of an admin post that doesn't exist Given basic languages And I am logged in as a "translation" admin diff --git a/features/step_definitions/admin_steps.rb b/features/step_definitions/admin_steps.rb index c5b90c408ba..df1b5e9f6f7 100644 --- a/features/step_definitions/admin_steps.rb +++ b/features/step_definitions/admin_steps.rb @@ -160,6 +160,16 @@ FactoryBot.create(:admin_post, :draft, title: title, comment_permissions: :enable_all, tag_list: tags) end +Given "the admin post {string} translating {string} to {string}" do |title, translated_title, lang| + admin_post = AdminPost.find_by!(title: translated_title) + language = Language.find_by!(name: lang) + FactoryBot.create(:admin_post, + title: title, + comment_permissions: :enable_all, + translated_post_id: admin_post.id, + language: language) +end + Given "the draft admin post {string} translating {string} to {string}" do |title, translated_title, lang| admin_post = AdminPost.find_by!(title: translated_title) language = Language.find_by!(name: lang) diff --git a/spec/models/admin_post_spec.rb b/spec/models/admin_post_spec.rb index a9f360320f0..e0754c451a7 100644 --- a/spec/models/admin_post_spec.rb +++ b/spec/models/admin_post_spec.rb @@ -78,6 +78,61 @@ end end + describe "translation language uniqueness" do + let(:admin_post) { create(:admin_post) } + let(:language) { create(:language) } + let(:translation) do + build(:admin_post, translated_post: admin_post, language: language) + end + let(:message) { "already has a translation in this language" } + + context "when the original post has a posted translation in the language" do + before do + create(:admin_post, translated_post: admin_post, language: language) + end + + it "adds an error to the translated post" do + expect(translation).to be_invalid + expect(translation.errors[:translated_post_id]).to include(message) + end + end + + context "when the original post has a draft translation in the language" do + before do + create(:admin_post, :draft, + translated_post: admin_post, language: language) + end + + it "adds an error to the translated post" do + expect(translation).to be_invalid + expect(translation.errors[:translated_post_id]).to include(message) + end + end + + context "when another post has a translation in that language" do + before do + other_post = create(:admin_post) + create(:admin_post, translated_post: other_post, language: language) + end + + it "does not add an error" do + expect(translation).to be_valid + end + end + + context "when updating the existing translation" do + let!(:existing) do + create(:admin_post, translated_post: admin_post, language: language) + end + + it "does not add an error" do + existing.title = "Updated translation title" + + expect(existing).to be_valid + end + end + end + describe "#set_published_at" do before { freeze_time } From a901e233df090de5fc36be8097f5725eadfc7856 Mon Sep 17 00:00:00 2001 From: Pablo Monfort Date: Wed, 26 Aug 2026 22:25:51 -0300 Subject: [PATCH 2/9] AO3-4122 Prevent duplicate translation languages for admin posts --- app/models/admin_post.rb | 7 +++++++ config/locales/models/en.yml | 1 + 2 files changed, 8 insertions(+) diff --git a/app/models/admin_post.rb b/app/models/admin_post.rb index 416efc4e585..3dc5ded02df 100644 --- a/app/models/admin_post.rb +++ b/app/models/admin_post.rb @@ -34,6 +34,13 @@ class AdminPost < ApplicationRecord validates_length_of :content, maximum: ArchiveConfig.CONTENT_MAX, too_long: ts("cannot be more than %{max} characters long.", max: ArchiveConfig.CONTENT_MAX) + # rubocop:disable Rails/UniqueValidationWithoutIndex + validates :translated_post_id, + uniqueness: { scope: :language_id, + message: :translation_already_exists }, + allow_nil: true + # rubocop:enable Rails/UniqueValidationWithoutIndex + validate :translated_post_must_exist validate :translated_post_language_must_differ diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml index a5d48d4358e..af8c78851d8 100644 --- a/config/locales/models/en.yml +++ b/config/locales/models/en.yml @@ -102,6 +102,7 @@ en: invalid_permissions: Comment permissions are invalid. translated_post_id: must_be_posted_first: cannot be posted before the original post + translation_already_exists: already has a translation in this language block: attributes: blocked: From e0ef5018536dcc7481967b5c05f71b29251ee11b Mon Sep 17 00:00:00 2001 From: Pablo Monfort Date: Fri, 9 Oct 2026 00:12:10 -0300 Subject: [PATCH 3/9] AO3-4122 Rename validation describe to attribute name --- spec/models/admin_post_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/admin_post_spec.rb b/spec/models/admin_post_spec.rb index e0754c451a7..b87b2ded8df 100644 --- a/spec/models/admin_post_spec.rb +++ b/spec/models/admin_post_spec.rb @@ -78,7 +78,7 @@ end end - describe "translation language uniqueness" do + describe "#translated_post_id" do let(:admin_post) { create(:admin_post) } let(:language) { create(:language) } let(:translation) do From b59cfbd1a070b9b6bc9b67b0bad1f4cd0fec8a40 Mon Sep 17 00:00:00 2001 From: Pablo Monfort Date: Wed, 28 Oct 2026 20:58:57 -0300 Subject: [PATCH 4/9] AO3-4122 Update translation exists error message --- config/locales/models/en.yml | 5 +++-- spec/models/admin_post_spec.rb | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml index af8c78851d8..d90747cb98c 100644 --- a/config/locales/models/en.yml +++ b/config/locales/models/en.yml @@ -101,8 +101,9 @@ en: format: "%{message}" invalid_permissions: Comment permissions are invalid. translated_post_id: - must_be_posted_first: cannot be posted before the original post - translation_already_exists: already has a translation in this language + format: "%{message}" + must_be_posted_first: Translated post cannot be posted before the original post + translation_already_exists: Admin post already has a translation in this language. block: attributes: blocked: diff --git a/spec/models/admin_post_spec.rb b/spec/models/admin_post_spec.rb index b87b2ded8df..d1440eba831 100644 --- a/spec/models/admin_post_spec.rb +++ b/spec/models/admin_post_spec.rb @@ -59,7 +59,7 @@ drafted_post = travel_to(ArchiveConfig.ADMIN_POST_COMMENTING_EXPIRATION_DAYS.days.ago) do create(:admin_post, :draft) end - drafted_post.update(posted: true) + drafted_post.update!(posted: true) AdminPost.disable_old_post_comments expect(drafted_post.reload.disable_all_comments?).to be(false) @@ -84,7 +84,7 @@ let(:translation) do build(:admin_post, translated_post: admin_post, language: language) end - let(:message) { "already has a translation in this language" } + let(:message) { "Admin post already has a translation in this language." } context "when the original post has a posted translation in the language" do before do From 5fe99e8eedc7938813411338a6fadd89d6ed5aa7 Mon Sep 17 00:00:00 2001 From: Pablo Monfort Date: Thu, 29 Oct 2026 00:45:58 -0300 Subject: [PATCH 5/9] AO3-4122 Move content length message to locale file --- app/models/admin_post.rb | 3 +-- config/locales/models/en.yml | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/admin_post.rb b/app/models/admin_post.rb index 3dc5ded02df..e10a9c91c8f 100644 --- a/app/models/admin_post.rb +++ b/app/models/admin_post.rb @@ -31,8 +31,7 @@ class AdminPost < ApplicationRecord validates_length_of :content, minimum: ArchiveConfig.CONTENT_MIN, too_short: ts("must be at least %{min} characters long.", min: ArchiveConfig.CONTENT_MIN) - validates_length_of :content, maximum: ArchiveConfig.CONTENT_MAX, - too_long: ts("cannot be more than %{max} characters long.", max: ArchiveConfig.CONTENT_MAX) + validates_length_of :content, maximum: ArchiveConfig.CONTENT_MAX # rubocop:disable Rails/UniqueValidationWithoutIndex validates :translated_post_id, diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml index d90747cb98c..aaf345a0e32 100644 --- a/config/locales/models/en.yml +++ b/config/locales/models/en.yml @@ -100,6 +100,8 @@ en: comment_permissions: format: "%{message}" invalid_permissions: Comment permissions are invalid. + content: + too_long: cannot be more than %{count} characters long. translated_post_id: format: "%{message}" must_be_posted_first: Translated post cannot be posted before the original post From 65c321cf937189a480970ea4737dda36faccabe7 Mon Sep 17 00:00:00 2001 From: Pablo Monfort Date: Thu, 29 Oct 2026 01:31:28 -0300 Subject: [PATCH 6/9] AO3-4122 Move translated post errors to locale file --- app/models/admin_post.rb | 4 ++-- config/locales/models/en.yml | 2 ++ features/admins/admin_post_news.feature | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/admin_post.rb b/app/models/admin_post.rb index e10a9c91c8f..9ba79172da8 100644 --- a/app/models/admin_post.rb +++ b/app/models/admin_post.rb @@ -99,7 +99,7 @@ def tags def translated_post_must_exist if translated_post_id.present? && AdminPost.find_by(id: translated_post_id).nil? - errors.add(:translated_post_id, "does not exist") + errors.add(:translated_post_id, :does_not_exist) end end @@ -107,7 +107,7 @@ def translated_post_language_must_differ return if translated_post.blank? return unless translated_post.language == language - errors.add(:translated_post_id, "cannot be same language as original post") + errors.add(:translated_post_id, :same_language_as_original) end def translated_post_must_be_posted_first diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml index aaf345a0e32..3c343f491bb 100644 --- a/config/locales/models/en.yml +++ b/config/locales/models/en.yml @@ -103,8 +103,10 @@ en: content: too_long: cannot be more than %{count} characters long. translated_post_id: + does_not_exist: Translated post does not exist format: "%{message}" must_be_posted_first: Translated post cannot be posted before the original post + same_language_as_original: Translated post cannot be same language as original post translation_already_exists: Admin post already has a translation in this language. block: attributes: diff --git a/features/admins/admin_post_news.feature b/features/admins/admin_post_news.feature index aa859617d88..92da4f5dff9 100755 --- a/features/admins/admin_post_news.feature +++ b/features/admins/admin_post_news.feature @@ -73,7 +73,7 @@ Feature: Admin Actions to Post News And I am logged in as a "translation" admin When I make a translation of an admin post Then I should see "Sorry! We couldn't save this admin post because:" - And I should see "Translated post already has a translation in this language" + And I should see "Admin post already has a translation in this language." Scenario: Make a translation of an admin post that doesn't exist Given basic languages From 11e9f7885eef981efd65a165787eeeadda26200f Mon Sep 17 00:00:00 2001 From: Pablo Monfort Date: Thu, 29 Oct 2026 02:14:11 -0300 Subject: [PATCH 7/9] AO3-4122 Convert content length validation to new style --- app/models/admin_post.rb | 6 ++---- config/locales/models/en.yml | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/admin_post.rb b/app/models/admin_post.rb index 9ba79172da8..7cfe03b88d9 100644 --- a/app/models/admin_post.rb +++ b/app/models/admin_post.rb @@ -28,10 +28,8 @@ class AdminPost < ApplicationRecord too_long: ts("must be less than %{max} characters long.", max: ArchiveConfig.TITLE_MAX) validates_presence_of :content - validates_length_of :content, minimum: ArchiveConfig.CONTENT_MIN, - too_short: ts("must be at least %{min} characters long.", min: ArchiveConfig.CONTENT_MIN) - - validates_length_of :content, maximum: ArchiveConfig.CONTENT_MAX + validates :content, length: { minimum: ArchiveConfig.CONTENT_MIN, + maximum: ArchiveConfig.CONTENT_MAX } # rubocop:disable Rails/UniqueValidationWithoutIndex validates :translated_post_id, diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml index 3c343f491bb..0d815a24cc3 100644 --- a/config/locales/models/en.yml +++ b/config/locales/models/en.yml @@ -102,6 +102,7 @@ en: invalid_permissions: Comment permissions are invalid. content: too_long: cannot be more than %{count} characters long. + too_short: must be at least %{count} characters long. translated_post_id: does_not_exist: Translated post does not exist format: "%{message}" From 85e91536d57a473a26d3a5d534c786de18d0f873 Mon Sep 17 00:00:00 2001 From: Pablo Monfort Date: Thu, 29 Oct 2026 02:17:00 -0300 Subject: [PATCH 8/9] AO3-4122 Convert content presence validation to new style --- app/models/admin_post.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/admin_post.rb b/app/models/admin_post.rb index 7cfe03b88d9..1cd7fb5711d 100644 --- a/app/models/admin_post.rb +++ b/app/models/admin_post.rb @@ -27,8 +27,8 @@ class AdminPost < ApplicationRecord maximum: ArchiveConfig.TITLE_MAX, too_long: ts("must be less than %{max} characters long.", max: ArchiveConfig.TITLE_MAX) - validates_presence_of :content - validates :content, length: { minimum: ArchiveConfig.CONTENT_MIN, + validates :content, presence: true, + length: { minimum: ArchiveConfig.CONTENT_MIN, maximum: ArchiveConfig.CONTENT_MAX } # rubocop:disable Rails/UniqueValidationWithoutIndex From a6eb37ed38f4d6b174b1bea5fa123a199fb6be8f Mon Sep 17 00:00:00 2001 From: Pablo Monfort Date: Thu, 29 Oct 2026 02:28:21 -0300 Subject: [PATCH 9/9] AO3-4122 Convert title validations to new style --- app/models/admin_post.rb | 11 +++-------- config/locales/models/en.yml | 3 +++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/models/admin_post.rb b/app/models/admin_post.rb index 1cd7fb5711d..99aed3d42a6 100644 --- a/app/models/admin_post.rb +++ b/app/models/admin_post.rb @@ -18,14 +18,9 @@ class AdminPost < ApplicationRecord has_many :admin_post_taggings has_many :tags, through: :admin_post_taggings, source: :admin_post_tag - validates_presence_of :title - validates_length_of :title, - minimum: ArchiveConfig.TITLE_MIN, - too_short: ts("must be at least %{min} characters long.", min: ArchiveConfig.TITLE_MIN) - - validates_length_of :title, - maximum: ArchiveConfig.TITLE_MAX, - too_long: ts("must be less than %{max} characters long.", max: ArchiveConfig.TITLE_MAX) + validates :title, presence: true, + length: { minimum: ArchiveConfig.TITLE_MIN, + maximum: ArchiveConfig.TITLE_MAX } validates :content, presence: true, length: { minimum: ArchiveConfig.CONTENT_MIN, diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml index 0d815a24cc3..2d5cd5cc52e 100644 --- a/config/locales/models/en.yml +++ b/config/locales/models/en.yml @@ -103,6 +103,9 @@ en: content: too_long: cannot be more than %{count} characters long. too_short: must be at least %{count} characters long. + title: + too_long: must be less than %{count} characters long. + too_short: must be at least %{count} characters long. translated_post_id: does_not_exist: Translated post does not exist format: "%{message}"