多言語化の基盤作り (#55) (#421)

Reviewed-on: #421
Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #421 でマージされました。
このコミットが含まれているのは:
2026-10-03 02:00:09 +09:00
committed by みてるぞ
コミット e6ab941774
91個のファイルの変更、3370行の追加、1223行の削除
+102 -84
ファイルの表示
@@ -113,8 +113,8 @@ RSpec.describe 'Posts API', type: :request do
'sections' => [])
end
let!(:tag_name) { TagName.create!(name: 'spec_tag') }
let!(:tag) { Tag.create!(tag_name: tag_name, category: :general) }
let!(:tag_name) { create(:tag_name, name: 'spec_tag') }
let!(:tag) { create(:tag, primary_tag_name: tag_name, category: :general) }
let!(:post_record) do
Post.create!(title: 'spec post', url: 'https://example.com/spec').tap do |p|
@@ -125,11 +125,11 @@ RSpec.describe 'Posts API', type: :request do
describe 'GET /posts' do
let!(:user) { create_member_user! }
let!(:tag_name) { TagName.create!(name: 'spec_tag') }
let!(:tag) { Tag.create!(tag_name:, category: :general) }
let!(:tag_name2) { TagName.create!(name: 'unko') }
let!(:tag2) { Tag.create!(tag_name: tag_name2, category: :deerjikist) }
let!(:alias_tag_name) { TagName.create!(name: 'manko', canonical: tag_name) }
let!(:tag_name) { create(:tag_name, name: 'spec_tag') }
let!(:tag) { create(:tag, primary_tag_name: tag_name, category: :general) }
let!(:tag_name2) { create(:tag_name, name: 'unko') }
let!(:tag2) { create(:tag, primary_tag_name: tag_name2, category: :deerjikist) }
let!(:alias_tag_name) { create(:tag_name, :alias, name: 'manko', tag:) }
let!(:hit_post) do
Post.create!(uploaded_user: user, title: 'hello spec world',
@@ -267,7 +267,7 @@ RSpec.describe 'Posts API', type: :request do
end
it 'keeps internal name searches independent of colliding external ids' do
get '/posts', params: { tags: tag.name }
get '/posts', params: { tags: primary_tag_name_for(tag, 'ja').name }
expect(response).to have_http_status(:ok)
expect(json.fetch('count')).to eq(3)
@@ -286,7 +286,7 @@ RSpec.describe 'Posts API', type: :request do
[nil, 'all'].each do |match|
it "intersects internal and external matches with match=#{ match || 'omitted' }" do
params = { tags: "#{ tag.name } nico:search_external" }
params = { tags: "#{ primary_tag_name_for(tag, 'ja').name } nico:search_external" }
params[:match] = match if match
get '/posts', params: params
@@ -307,7 +307,8 @@ RSpec.describe 'Posts API', type: :request do
end
it 'excludes external matches from an internal tag search' do
get '/posts', params: { tags: "#{ tag.name } not:nico:search_external" }
get '/posts', params: {
tags: "#{ primary_tag_name_for(tag, 'ja').name } not:nico:search_external" }
expect(response).to have_http_status(:ok)
expect(json.fetch('count')).to eq(2)
@@ -316,7 +317,9 @@ RSpec.describe 'Posts API', type: :request do
end
it 'unions an external match with a negated internal match' do
get '/posts', params: { tags: "nico:search_external not:#{ tag.name }", match: 'any' }
get '/posts', params: {
tags: "nico:search_external not:#{ primary_tag_name_for(tag, 'ja').name }",
match: 'any' }
expect(response).to have_http_status(:ok)
expect(json.fetch('count')).to eq(2)
@@ -333,7 +336,9 @@ RSpec.describe 'Posts API', type: :request do
end
it 'applies the same mixed name search to the existing random endpoint' do
get '/posts/random', params: { tags: "#{ tag.name } nico:search_external", match: 'all' }
get '/posts/random', params: {
tags: "#{ primary_tag_name_for(tag, 'ja').name } nico:search_external",
match: 'all' }
expect(response).to have_http_status(:ok)
expect(json.fetch('id')).to eq(both_post.id)
@@ -341,17 +346,17 @@ RSpec.describe 'Posts API', type: :request do
end
context 'when tags contain not:' do
let!(:foo_tag_name) { TagName.create!(name: 'not_spec_foo') }
let!(:foo_tag) { Tag.create!(tag_name: foo_tag_name, category: :general) }
let!(:foo_tag_name) { create(:tag_name, name: 'not_spec_foo') }
let!(:foo_tag) { create(:tag, primary_tag_name: foo_tag_name, category: :general) }
let!(:bar_tag_name) { TagName.create!(name: 'not_spec_bar') }
let!(:bar_tag) { Tag.create!(tag_name: bar_tag_name, category: :general) }
let!(:bar_tag_name) { create(:tag_name, name: 'not_spec_bar') }
let!(:bar_tag) { create(:tag, primary_tag_name: bar_tag_name, category: :general) }
let!(:baz_tag_name) { TagName.create!(name: 'not_spec_baz') }
let!(:baz_tag) { Tag.create!(tag_name: baz_tag_name, category: :general) }
let!(:baz_tag_name) { create(:tag_name, name: 'not_spec_baz') }
let!(:baz_tag) { create(:tag, primary_tag_name: baz_tag_name, category: :general) }
let!(:foo_alias_tag_name) do
TagName.create!(name: 'not_spec_foo_alias', canonical: foo_tag_name)
create(:tag_name, :alias, name: 'not_spec_foo_alias', tag: foo_tag)
end
let!(:foo_only_post) do
@@ -739,7 +744,9 @@ RSpec.describe 'Posts API', type: :request do
.find { _1['name'] == 'nico:post_index_external' }
expect(post_json.fetch('tags')).to include(
a_hash_including('id' => tag.id, 'name' => tag.name, 'category' => 'general'))
a_hash_including('id' => tag.id,
'name' => primary_tag_name_for(tag, 'ja').name,
'category' => 'general'))
expect(external_json).not_to be_nil
expect_external_tag_json(external_json, external_tag)
end
@@ -772,11 +779,10 @@ RSpec.describe 'Posts API', type: :request do
end
it 'omits deprecated tags' do
deprecated_tag = Tag.create!(
name: 'deprecated_post_tag',
category: :general,
deprecated_at: Time.current
)
deprecated_tag = create(:tag,
primary_name: 'deprecated_post_tag',
category: :general,
deprecated_at: Time.current)
PostTag.create!(post: post_record, tag: deprecated_tag)
request
@@ -853,9 +859,9 @@ RSpec.describe 'Posts API', type: :request do
tags =
15.times.map do |i|
tag_name = TagName.create!(name: "show_query_tag_#{ i }")
tag = Tag.create!(tag_name:, category: :general)
TagName.create!(name: "show_query_alias_#{ i }", canonical: tag_name)
tag_name = create(:tag_name, name: "show_query_tag_#{ i }")
tag = create(:tag, primary_tag_name: tag_name, category: :general)
create(:tag_name, :alias, name: "show_query_alias_#{ i }", tag:)
PostTag.create!(post: post_record, tag:)
tag
end
@@ -910,7 +916,9 @@ RSpec.describe 'Posts API', type: :request do
expect(response).to have_http_status(:ok)
expect(json.fetch('tags')).to include(
a_hash_including('id' => tag.id, 'name' => tag.name, 'category' => 'general'))
a_hash_including('id' => tag.id,
'name' => primary_tag_name_for(tag, 'ja').name,
'category' => 'general'))
external_json =
json
.fetch('tags')
@@ -1000,7 +1008,7 @@ RSpec.describe 'Posts API', type: :request do
describe 'POST /posts' do
let(:member) { create(:user, :member) }
let!(:alias_tag_name) { TagName.create!(name: 'manko', canonical: tag_name) }
let!(:alias_tag_name) { create(:tag_name, :alias, name: 'manko', tag:) }
it '401 when not logged in' do
sign_out
@@ -1157,11 +1165,10 @@ RSpec.describe 'Posts API', type: :request do
end
it 'rejects a deprecated tag specified directly' do
Tag.create!(
name: 'deprecated_direct_tag',
category: :general,
deprecated_at: Time.current
)
create(:tag,
primary_name: 'deprecated_direct_tag',
category: :general,
deprecated_at: Time.current)
sign_in_as(member)
post '/posts', params: post_write_params(
@@ -1178,18 +1185,16 @@ RSpec.describe 'Posts API', type: :request do
end
it 'expands through multiple deprecated parent tags and saves active ancestors' do
child = Tag.create!(name: 'active_child', category: :general)
deprecated_parent = Tag.create!(
name: 'deprecated_parent',
category: :general,
deprecated_at: Time.current
)
deprecated_grandparent = Tag.create!(
name: 'deprecated_grandparent',
category: :general,
deprecated_at: Time.current
)
active_grandparent = Tag.create!(name: 'active_grandparent', category: :general)
child = create(:tag, primary_name: 'active_child', category: :general)
deprecated_parent = create(:tag,
primary_name: 'deprecated_parent',
category: :general,
deprecated_at: Time.current)
deprecated_grandparent = create(:tag,
primary_name: 'deprecated_grandparent',
category: :general,
deprecated_at: Time.current)
active_grandparent = create(:tag, primary_name: 'active_grandparent', category: :general)
TagImplication.create!(tag: child, parent_tag: deprecated_parent)
TagImplication.create!(tag: deprecated_parent, parent_tag: deprecated_grandparent)
TagImplication.create!(tag: deprecated_grandparent, parent_tag: active_grandparent)
@@ -1203,7 +1208,8 @@ RSpec.describe 'Posts API', type: :request do
)
expect(response).to have_http_status(:created)
saved_names = Post.find(json.fetch('id')).tags.map(&:name)
saved_names = TagName.where(tag_id: Post.find(json.fetch('id')).tags.select(:id),
language_code: 'ja', primary_flg: true).pluck(:name)
expect(saved_names).to include('active_child', 'active_grandparent')
expect(saved_names).not_to include('deprecated_parent', 'deprecated_grandparent')
end
@@ -1306,7 +1312,8 @@ RSpec.describe 'Posts API', type: :request do
expect(response).to have_http_status(:created)
created_post = Post.find(json.fetch('id'))
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: '伊地知ニジカ' })
tag = TagName.find_by!(name: '伊地知ニジカ',
language_code: 'ja', primary_flg: true).tag
section = PostTagSection.find_by!(post: created_post, tag:)
expect(section.begin_ms).to eq(60_000)
@@ -1326,7 +1333,8 @@ RSpec.describe 'Posts API', type: :request do
expect(response).to have_http_status(:created)
created_post = Post.find(json.fetch('id'))
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: '伊地知ニジカ' })
tag = TagName.find_by!(name: '伊地知ニジカ',
language_code: 'ja', primary_flg: true).tag
expect(PostTagSection.find_by(post: created_post, tag:)).to be_nil
end
@@ -1343,7 +1351,8 @@ RSpec.describe 'Posts API', type: :request do
expect(response).to have_http_status(:created)
created_post = Post.find(json.fetch('id'))
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: '伊地知ニジカ' })
tag = TagName.find_by!(name: '伊地知ニジカ',
language_code: 'ja', primary_flg: true).tag
expect(PostTagSection.find_by(post: created_post, tag:)).to be_nil
end
@@ -1622,6 +1631,7 @@ RSpec.describe 'Posts API', type: :request do
expect(arguments[:thumbnails].keys).to eq([0])
expect(arguments[:host]).to eq('http://www.example.com')
end
expect(creator).to have_received(:run).with(Locale.nipponese)
end
it 'rejects malformed manifests as a request-level error' do
@@ -1658,8 +1668,8 @@ RSpec.describe 'Posts API', type: :request do
create(:post_tag_section, post: post_record, tag:,
begin_ms: 1000, end_ms: 2000)
tn2 = TagName.create!(name: 'spec_tag_2')
replacement_tag = Tag.create!(tag_name: tn2, category: :general)
tn2 = create(:tag_name, name: 'spec_tag_2')
replacement_tag = create(:tag, primary_tag_name: tn2, category: :general)
put "/posts/#{post_record.id}", params: post_update_params(
post_record,
@@ -1707,11 +1717,10 @@ RSpec.describe 'Posts API', type: :request do
end
it 'rejects a deprecated tag specified directly' do
Tag.create!(
name: 'deprecated_update_tag',
category: :general,
deprecated_at: Time.current
)
create(:tag,
primary_name: 'deprecated_update_tag',
category: :general,
deprecated_at: Time.current)
sign_in_as(member)
put "/posts/#{ post_record.id }", params: post_update_params(
@@ -1966,7 +1975,7 @@ RSpec.describe 'Posts API', type: :request do
put "/posts/#{post_record.id}", params: post_write_params(
base_version_no: base_version.version_no,
title: 'updated by me',
tags: "spec_tag #{Tag.no_deerjikist.name}")
tags: "spec_tag #{primary_tag_name_for(Tag.no_deerjikist, 'ja').name}")
expect(response).to have_http_status(:conflict)
@@ -1986,7 +1995,8 @@ RSpec.describe 'Posts API', type: :request do
base_version = create_post_version_for!(post_record.reload)
current_tag = Tag.find_or_create_by_tag_name!('current_added_tag', category: :general)
current_tag = Tag.find_or_create_by_tag_name!(
Locale.nipponese, 'current_added_tag', category: :general)
PostTag.create!(post: post_record, tag: current_tag, created_user: member)
PostVersionRecorder.record!(
@@ -1997,7 +2007,8 @@ RSpec.describe 'Posts API', type: :request do
put "/posts/#{post_record.id}", params: post_write_params(
base_version_no: base_version.version_no,
title: post_record.title,
tags: "spec_tag #{Tag.no_deerjikist.name} incoming_added_tag")
tags: ['spec_tag', primary_tag_name_for(Tag.no_deerjikist, 'ja').name,
'incoming_added_tag'].join(' '))
expect(response).to have_http_status(:conflict)
@@ -2015,7 +2026,8 @@ RSpec.describe 'Posts API', type: :request do
base_version = create_post_version_for!(post_record.reload)
current_tag = Tag.find_or_create_by_tag_name!('current_merge_tag', category: :general)
current_tag = Tag.find_or_create_by_tag_name!(
Locale.nipponese, 'current_merge_tag', category: :general)
PostTag.create!(post: post_record, tag: current_tag, created_user: member)
PostVersionRecorder.record!(
@@ -2026,15 +2038,17 @@ RSpec.describe 'Posts API', type: :request do
put "/posts/#{post_record.id}", params: post_write_params(
base_version_no: base_version.version_no,
title: post_record.title,
tags: "spec_tag #{Tag.no_deerjikist.name} incoming_merge_tag",
tags: ['spec_tag', primary_tag_name_for(Tag.no_deerjikist, 'ja').name,
'incoming_merge_tag'].join(' '),
merge: '1')
expect(response).to have_http_status(:ok)
names = post_record.reload.tags.map(&:name)
names = TagName.where(tag_id: post_record.reload.tags.select(:id),
language_code: 'ja', primary_flg: true).pluck(:name)
expect(names).to include('spec_tag')
expect(names).to include(Tag.no_deerjikist.name)
expect(names).to include(primary_tag_name_for(Tag.no_deerjikist, 'ja').name)
expect(names).to include('current_merge_tag')
expect(names).to include('incoming_merge_tag')
end
@@ -2057,14 +2071,15 @@ RSpec.describe 'Posts API', type: :request do
put "/posts/#{post_record.id}", params: post_write_params(
base_version_no: base_version.version_no,
title: post_record.title,
tags: "spec_tag #{ Tag.no_deerjikist.name }")
tags: "spec_tag #{ primary_tag_name_for(Tag.no_deerjikist, 'ja').name }")
expect(response).to have_http_status(:ok)
names = post_record.reload.tags.map(&:name)
names = TagName.where(tag_id: post_record.reload.tags.select(:id),
language_code: 'ja', primary_flg: true).pluck(:name)
expect(names).to include('spec_tag')
expect(names).to include(Tag.no_deerjikist.name)
expect(names).to include(primary_tag_name_for(Tag.no_deerjikist, 'ja').name)
expect(post_record.external_tags).to contain_exactly(nico_tag)
end
@@ -2079,14 +2094,15 @@ RSpec.describe 'Posts API', type: :request do
put "/posts/#{post_record.id}", params: post_write_params(
base_version_no: base_version.version_no,
title: 'updated title',
tags: "spec_tag #{ Tag.no_deerjikist.name }")
tags: "spec_tag #{ primary_tag_name_for(Tag.no_deerjikist, 'ja').name }")
expect(response).to have_http_status(:ok)
names = post_record.reload.tags.map(&:name)
names = TagName.where(tag_id: post_record.reload.tags.select(:id),
language_code: 'ja', primary_flg: true).pluck(:name)
expect(names).to include('spec_tag')
expect(names).to include(Tag.no_deerjikist.name)
expect(names).to include(primary_tag_name_for(Tag.no_deerjikist, 'ja').name)
expect(post_record.external_tags).to contain_exactly(nico_tag)
external_json =
@@ -2102,7 +2118,8 @@ RSpec.describe 'Posts API', type: :request do
sign_in_as(member)
nico_tag = create_nico_tag!('nico:relation_source')
linked_tag = Tag.find_or_create_by_tag_name!('relation_linked_tag', category: :general)
linked_tag = Tag.find_or_create_by_tag_name!(
Locale.nipponese, 'relation_linked_tag', category: :general)
NicoTagRelation.create!(nico_tag:, tag: linked_tag)
PostExternalTag.create!(post: post_record, external_tag: nico_tag)
@@ -2113,16 +2130,17 @@ RSpec.describe 'Posts API', type: :request do
put "/posts/#{post_record.id}", params: post_write_params(
base_version_no: base_version.version_no,
title: post_record.title,
tags: "spec_tag #{ Tag.no_deerjikist.name }")
tags: "spec_tag #{ primary_tag_name_for(Tag.no_deerjikist, 'ja').name }")
expect(response).to have_http_status(:ok)
names = post_record.reload.tags.map(&:name)
names = TagName.where(tag_id: post_record.reload.tags.select(:id),
language_code: 'ja', primary_flg: true).pluck(:name)
expect(post_record.external_tags).to contain_exactly(nico_tag)
expect(names).to include('spec_tag')
expect(names).to include(Tag.no_deerjikist.name)
expect(names).not_to include(linked_tag.name)
expect(names).to include(primary_tag_name_for(Tag.no_deerjikist, 'ja').name)
expect(names).not_to include(primary_tag_name_for(linked_tag, 'ja').name)
end
it 'force-updates stale posts without base_version_no' do
@@ -2138,7 +2156,7 @@ RSpec.describe 'Posts API', type: :request do
put "/posts/#{post_record.id}", params: post_write_params(
title: 'forced title',
tags: "spec_tag #{Tag.no_deerjikist.name}",
tags: "spec_tag #{primary_tag_name_for(Tag.no_deerjikist, 'ja').name}",
force: '1')
expect(response).to have_http_status(:ok)
@@ -2191,8 +2209,8 @@ RSpec.describe 'Posts API', type: :request do
let(:oc_from) { Time.zone.local(2019, 12, 31, 0, 0, 0) }
let(:oc_before) { Time.zone.local(2020, 1, 1, 0, 0, 0) }
let!(:tag_name2) { TagName.create!(name: 'spec_tag_2') }
let!(:tag2) { Tag.create!(tag_name: tag_name2, category: :general) }
let!(:tag_name2) { create(:tag_name, name: 'spec_tag_2') }
let!(:tag2) { create(:tag, primary_tag_name: tag_name2, category: :general) }
def snapshot_tags(post)
post.snapshot_tag_names.join(' ')
@@ -2417,7 +2435,7 @@ RSpec.describe 'Posts API', type: :request do
expect(response).to have_http_status(:ok)
expect(json.fetch('count')).to eq(3)
expect(json.fetch('versions').first.fetch('tags')).to include(
'name' => tag2.name, 'type' => 'context')
'name' => primary_tag_name_for(tag2, 'ja').name, 'type' => 'context')
end
it 'filters versions by tag when the current snapshot includes the tag' do
@@ -2550,8 +2568,8 @@ RSpec.describe 'Posts API', type: :request do
sign_in_as(member)
base_version = create_post_version_for!(post_record)
tag_name2 = TagName.create!(name: 'spec_tag_2')
Tag.create!(tag_name: tag_name2, category: :general)
tag_name2 = create(:tag_name, name: 'spec_tag_2')
create(:tag, primary_tag_name: tag_name2, category: :general)
expect do
put "/posts/#{post_record.id}", params: post_write_params(
@@ -2715,8 +2733,8 @@ RSpec.describe 'Posts API', type: :request do
base_version = create_post_version_for!(post_record.reload)
tag_name2 = TagName.create!(name: 'spec_tag_2')
tag2 = Tag.create!(tag_name: tag_name2, category: :general)
tag_name2 = create(:tag_name, name: 'spec_tag_2')
tag2 = create(:tag, primary_tag_name: tag_name2, category: :general)
expect {
put "/posts/#{post_record.id}", params: post_write_params(