require 'rails_helper' RSpec.describe 'Wiki and native API locale contracts', type: :request do include WikiRecords include TagGroupRecords include_context 'Ainu locale contract' let!(:user) { create_member_user! } let!(:tag) { create(:tag, primary_name: 'japanese_title') } let!(:ainu_name) { add_ainu_name(tag, 'ainu_title') } let(:headers) { { 'X-Transfer-Code' => user.inheritance_code } } let!(:japanese) do TagDescriptionOperation.create!( Locale.nipponese, tag:, body: 'Japanese body', created_by_user: user) end let!(:ainu) do TagDescriptionOperation.create!( ainu_locale, tag:, body: 'Ainu body', created_by_user: user) end ['/wiki', '/wiki/search'].each do |path| it "filters #{ path } by the selected language" do get path, params: { locale: 'ain' } expect(response).to have_http_status(:ok) expect(json.map { _1.fetch('id') }).to eq([ainu.id]) expect(json.sole).to include('title' => 'ainu_title', 'body' => 'Ainu body') get path, params: { locale: 'ain', title: 'japanese' } expect(response).to have_http_status(:ok) expect(json).to eq([]) end end it 'uses the existing default locale when no locale is specified' do get '/wiki' expect(response).to have_http_status(:ok) expect(json.map { _1.fetch('id') }).to eq([japanese.id]) end it 'resolves titles and aliases only in the selected language in both APIs' do ['/wiki/title/japanese_title', '/tags/name/japanese_title/description'].each do |path| get path, params: { locale: 'ain' } expect(response).to have_http_status(:not_found) end ['/wiki/title/ainu_title', '/tags/name/ainu_title/description'].each do |path| get path, params: { locale: 'ain' } expect(response).to have_http_status(:ok) expect(json.fetch('body')).to eq('Ainu body') end get '/wiki/title/ainu_title/exists', params: { locale: 'ain' } expect(response).to have_http_status(:no_content) get '/wiki/title/japanese_title/exists', params: { locale: 'ain' } expect(response).to have_http_status(:not_found) end it 'does not disclose another language through ID, existence, diff or history' do revision = current_description_version(japanese) ["/wiki/#{ japanese.id }", "/wiki/#{ japanese.id }/exists", "/wiki/#{ japanese.id }/diff"].each do |path| get path, params: { locale: 'ain', from: revision.id } expect(response).to have_http_status(:not_found) expect(response.body).not_to include('Japanese body') end get '/wiki/changes', params: { locale: 'ain', id: japanese.id } expect(response).to have_http_status(:ok) expect(json).to eq([]) get '/wiki/changes', params: { locale: 'ain' } expect(json.map { _1.fetch('revision_id') }) .to eq([current_description_version(ainu).id]) end it 'does not update a description addressed through another language ID' do snapshot = [japanese.attributes, ainu.attributes, ainu_name.attributes] expect do put "/wiki/#{ japanese.id }", params: { locale: 'ain', title: 'renamed', body: 'wrong target' }, headers: headers end.not_to change(TagDescriptionVersion, :count) expect(response).to have_http_status(:not_found) expect([japanese.reload.attributes, ainu.reload.attributes, ainu_name.reload.attributes]) .to eq(snapshot) end [:wiki, :native].each do |api| it "creates the selected language through #{ api } without changing Japanese history" do other = create(:tag, primary_name: 'other_japanese') add_ainu_name(other, 'other_ainu') original = TagDescriptionOperation.create!( Locale.nipponese, tag: other, body: 'original', created_by_user: user) snapshot = original.attributes expect do if api == :wiki post '/wiki', params: { locale: 'ain', title: 'other_ainu', body: 'created' }, headers: headers else put "/tags/#{ other.id }/description", params: { locale: 'ain', body: 'created' }, headers: headers end end.to change(TagDescription, :count).by(1) .and change(TagDescriptionVersion, :count).by(1) expect(response).to have_http_status(:created) created = TagDescription.find_by!(tag: other, language_code: 'ain') expect(created).to have_attributes(body: 'created', version_no: 1) expect(current_description_version(created)).to have_attributes(language_code: 'ain') expect(original.reload.attributes).to eq(snapshot) end end it 'shares selected-language edits and revision identity across both APIs' do snapshot = japanese.attributes first = current_description_version(ainu) put "/tags/#{ tag.id }/description", params: { locale: 'ain', body: 'native edit' }, headers: headers expect(response).to have_http_status(:ok) second = current_description_version(ainu) expect(second).to have_attributes(language_code: 'ain', version_no: 2) expect(second.id).not_to eq(first.id) get "/wiki/#{ ainu.id }", params: { locale: 'ain' } expect(json).to include('body' => 'native edit', 'revision_id' => second.id) put "/wiki/#{ ainu.id }", params: { locale: 'ain', title: 'ainu_title', body: 'facade edit', base_revision_id: second.id }, headers: headers expect(response).to have_http_status(:ok) get "/tags/#{ tag.id }/description", params: { locale: 'ain' } expect(json).to include('body' => 'facade edit', 'language_code' => 'ain', 'version_no' => 3) expect(japanese.reload.attributes).to eq(snapshot) end it 'rejects a stale selected-language revision without a partial title or body edit' do stale = current_description_version(ainu) TagDescriptionOperation.update!( ainu_locale, tag:, body: 'current', updated_by_user: user) snapshot = [ainu.reload.attributes, ainu_name.attributes, japanese.attributes] expect do put "/wiki/#{ ainu.id }", params: { locale: 'ain', title: 'conflicting_name', body: 'current', base_revision_id: stale.id }, headers: headers end.to change(TagDescriptionVersion, :count).by(0).and change(TagVersion, :count).by(0) expect(response).to have_http_status(:conflict) expect([ainu.reload.attributes, ainu_name.reload.attributes, japanese.reload.attributes]) .to eq(snapshot) end it 'scopes historical show and diff to the selected description' do first = current_description_version(ainu) TagDescriptionOperation.update!( ainu_locale, tag:, body: 'later', updated_by_user: user) get "/wiki/#{ ainu.id }", params: { locale: 'ain', version: first.id } expect(response).to have_http_status(:ok) expect(json).to include('body' => 'Ainu body', 'revision_id' => first.id) get "/wiki/#{ ainu.id }/diff", params: { locale: 'ain', from: first.id } expect(response).to have_http_status(:ok) expect(json.fetch('newer_revision_id')).to eq(current_description_version(ainu).id) get "/wiki/#{ ainu.id }/diff", params: { locale: 'ain', from: current_description_version(japanese).id } expect(response).to have_http_status(:not_found) end end