diff --git a/app/graph/mutations/graphql_crud_operations.rb b/app/graph/mutations/graphql_crud_operations.rb index 1520987d4..3d47bb624 100644 --- a/app/graph/mutations/graphql_crud_operations.rb +++ b/app/graph/mutations/graphql_crud_operations.rb @@ -7,7 +7,20 @@ def self.safe_save(obj, attrs, parent_names = []) obj.send(method, value) if obj.respond_to?(method) end obj.disable_es_callbacks = Rails.env.to_s == "test" - obj.save_with_version! + + begin + obj.save_with_version! + rescue RuntimeError => e + if e.message.include?("\"code\":#{LapisConstants::ErrorCodes::const_get('DUPLICATED')}") && + obj.is_a?(ProjectMedia) && + obj.set_fact_check.present? && + obj.set_original_claim.present? + existing_pm = ProjectMedia.find(JSON.parse(e.message)['data']['id']) + obj = ProjectMedia.handle_fact_check_for_existing_claim(existing_pm,obj) + else + raise e + end + end name = obj.class_name.underscore { name.to_sym => obj }.merge( diff --git a/app/models/concerns/project_media_creators.rb b/app/models/concerns/project_media_creators.rb index 032bd86d5..5f54989c5 100644 --- a/app/models/concerns/project_media_creators.rb +++ b/app/models/concerns/project_media_creators.rb @@ -11,6 +11,28 @@ def create_metrics_annotation end end + def create_claim_description_and_fact_check + cd = ClaimDescription.create!(description: self.set_claim_description, context: self.set_claim_context, project_media: self, skip_check_ability: true) unless self.set_claim_description.blank? + fc = nil + unless self.set_fact_check.blank? + fact_check = self.set_fact_check.with_indifferent_access + fc = FactCheck.create!({ + title: fact_check['title'], + summary: fact_check['summary'], + language: fact_check['language'], + url: fact_check['url'], + publish_report: !!fact_check['publish_report'], + signature: Digest::MD5.hexdigest([self.set_fact_check.to_json, self.team_id].join(':')), + claim_description: cd, + report_status: (fact_check['publish_report'] ? 'published' : 'unpublished'), + rating: self.set_status, + tags: self.set_tags.to_a.map(&:strip), + skip_check_ability: true + }) + end + fc + end + private def create_team_tasks @@ -236,28 +258,6 @@ def create_relationship(type = Relationship.confirmed_type) end end - def create_claim_description_and_fact_check - cd = ClaimDescription.create!(description: self.set_claim_description, context: self.set_claim_context, project_media: self, skip_check_ability: true) unless self.set_claim_description.blank? - fc = nil - unless self.set_fact_check.blank? - fact_check = self.set_fact_check.with_indifferent_access - fc = FactCheck.create!({ - title: fact_check['title'], - summary: fact_check['summary'], - language: fact_check['language'], - url: fact_check['url'], - publish_report: !!fact_check['publish_report'], - signature: Digest::MD5.hexdigest([self.set_fact_check.to_json, self.team_id].join(':')), - claim_description: cd, - report_status: (fact_check['publish_report'] ? 'published' : 'unpublished'), - rating: self.set_status, - tags: self.set_tags.to_a.map(&:strip), - skip_check_ability: true - }) - end - fc - end - def create_tags_in_background if self.set_tags.is_a?(Array) project_media_id = self.id diff --git a/app/models/project_media.rb b/app/models/project_media.rb index 223403cc9..ab169298d 100644 --- a/app/models/project_media.rb +++ b/app/models/project_media.rb @@ -430,6 +430,31 @@ def apply_rules_and_actions_on_update self.team.apply_rules_and_actions(self, rule_ids) end + def self.handle_fact_check_for_existing_claim(existing_pm,new_pm) + if existing_pm.fact_check.blank? + existing_pm.append_fact_check_from(new_pm) + return existing_pm + elsif existing_pm.fact_check.present? + if existing_pm.fact_check.language != new_pm.set_fact_check['language'] + new_pm.replace_with_blank_media + return new_pm + end + end + end + + def append_fact_check_from(new_pm) + self.set_claim_description = new_pm.set_claim_description + self.set_fact_check = new_pm.set_fact_check + self.create_claim_description_and_fact_check + end + + def replace_with_blank_media + m = Blank.create! + self.set_original_claim = nil + self.media_id = m.id + self.save! + end + protected def add_extra_elasticsearch_data(ms) diff --git a/test/controllers/graphql_controller_12_test.rb b/test/controllers/graphql_controller_12_test.rb index bf666da1e..4b0b16a3a 100644 --- a/test/controllers/graphql_controller_12_test.rb +++ b/test/controllers/graphql_controller_12_test.rb @@ -731,4 +731,128 @@ def teardown assert_equal 1, pm_tags.count assert_equal 1, fc_tags.count end + + test "should append FactCheck to ProjectMedia, if ProjectMedia already exists and does not have a FactCheck" do + Sidekiq::Testing.fake! + url = 'http://example.com' + pender_url = CheckConfig.get('pender_url_private') + '/api/medias' + response_body = '{"type":"media","data":{"url":"' + url + '","type":"item"}}' + WebMock.stub_request(:get, pender_url).with({ query: { url: url } }).to_return(body: response_body) + + t = create_team + p = create_project team: t + pm = create_project_media team: t, set_original_claim: url + + assert_not_nil pm + + a = ApiKey.create! + b = create_bot_user api_key_id: a.id + create_team_user team: t, user: b + authenticate_with_token(a) + + query = <<~GRAPHQL + mutation { + createProjectMedia(input: { + project_id: #{p.id}, + media_type: "Blank", + channel: { main: 1 }, + set_tags: ["tag"], + set_status: "verified", + set_claim_description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + set_original_claim: "#{url}", + set_fact_check: { + title: "Title #1", + language: "en", + } + }) { + project_media { + dbid + full_url + claim_description { + fact_check { + dbid + } + } + } + } + } + GRAPHQL + + assert_no_difference 'ProjectMedia.count' do + assert_difference 'FactCheck.count' do + post :create, params: { query: query, team: t.slug } + assert_response :success + end + end + + response_pm = JSON.parse(@response.body)['data']['createProjectMedia']['project_media'] + fact_check = response_pm['claim_description']['fact_check'] + + assert_not_nil fact_check + assert_equal response_pm['dbid'], pm.id + end + + test "should create a FactCheck with a Blank ProjectMedia, if ProjectMedia already exists and has a FactCheck in a different language" do + Sidekiq::Testing.fake! + url = 'http://example.com' + pender_url = CheckConfig.get('pender_url_private') + '/api/medias' + response_body = '{"type":"media","data":{"url":"' + url + '","type":"item"}}' + WebMock.stub_request(:get, pender_url).with({ query: { url: url } }).to_return(body: response_body) + + t = create_team + t.settings[:languages] << 'pt' + t.save! + p = create_project team: t + pm = create_project_media team: t, set_original_claim: url + c = create_claim_description project_media: pm + fc_1 = create_fact_check claim_description: c + + assert_not_nil fc_1 + + a = ApiKey.create! + b = create_bot_user api_key_id: a.id + create_team_user team: t, user: b + authenticate_with_token(a) + + query = <<~GRAPHQL + mutation { + createProjectMedia(input: { + project_id: #{p.id}, + media_type: "Blank", + channel: { main: 1 }, + set_tags: ["tag"], + set_status: "verified", + set_claim_description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + set_original_claim: "#{url}", + set_fact_check: { + title: "Title #1", + language: "pt", + } + }) { + project_media { + dbid + full_url + claim_description { + fact_check { + dbid + } + } + } + } + } + GRAPHQL + + assert_difference 'ProjectMedia.count' do + assert_difference 'FactCheck.count' do + post :create, params: { query: query, team: t.slug } + assert_response :success + end + end + + response_pm = JSON.parse(@response.body)['data']['createProjectMedia']['project_media'] + fc_2 = response_pm['claim_description']['fact_check'] + + assert_not_nil fc_2 + assert_not_equal response_pm['dbid'], pm.id + end end diff --git a/test/models/project_media_7_test.rb b/test/models/project_media_7_test.rb index c96ca0a7c..73553e8b8 100644 --- a/test/models/project_media_7_test.rb +++ b/test/models/project_media_7_test.rb @@ -67,4 +67,52 @@ def setup assert_equal 'Claim', pm_claim.media.type assert_equal 'This is a claim.', pm_claim.media.quote end + + test "should not create duplicate media from original claim URL as Link" do + setup_elasticsearch + + # Mock Pender response for Link + link_url = 'https://example.com' + pender_url = CheckConfig.get('pender_url_private') + '/api/medias' + link_response = { + type: 'media', + data: { + url: link_url, + type: 'item' + } + }.to_json + WebMock.stub_request(:get, pender_url).with(query: { url: link_url }).to_return(body: link_response) + + t = create_team + create_project team: t + + assert_raise RuntimeError do + 2.times { create_project_media(team: t, set_original_claim: link_url) } + end + end + + test "should create duplicate media from original claim URL as UploadedImage" do + Tempfile.create(['test_image', '.jpg']) do |file| + file.write(File.read(File.join(Rails.root, 'test', 'data', 'rails.png'))) + file.rewind + image_url = "http://example.com/#{file.path.split('/').last}" + WebMock.stub_request(:get, image_url).to_return(body: file.read, headers: { 'Content-Type' => 'image/jpeg' }) + + t = create_team + create_project team: t + + assert_difference 'ProjectMedia.count', 2 do + 2.times { create_project_media(team: t, set_original_claim: image_url) } + end + end + end + + test "should create duplicate media from original claim URL as Claim" do + t = create_team + create_project team: t + + assert_difference 'ProjectMedia.count', 2 do + 2.times { create_project_media(team: t, set_original_claim: 'This is a claim.') } + end + end end