From 31a3849111ab95122415785b0102b1b326c1a578 Mon Sep 17 00:00:00 2001 From: Dante Soares Date: Wed, 3 Aug 2022 17:09:10 -0500 Subject: [PATCH 1/3] Fix search returning 0 results when multiple tags, authors, copyright_holders or collaborators are present --- app/routines/search_exercises.rb | 66 ++++++++++----- app/routines/search_vocab_terms.rb | 66 ++++++++++----- spec/routines/search_exercises_spec.rb | 98 +++++++++++++++++++++- spec/routines/search_vocab_terms_spec.rb | 100 ++++++++++++++++++++++- 4 files changed, 279 insertions(+), 51 deletions(-) diff --git a/app/routines/search_exercises.rb b/app/routines/search_exercises.rb index 7f854211..74f08c7a 100644 --- a/app/routines/search_exercises.rb +++ b/app/routines/search_exercises.rb @@ -156,7 +156,11 @@ def exec(params = {}, options = {}) sanitized_tags = to_string_array(tag).map(&:downcase) next @items = @items.none if sanitized_tags.empty? - @items = @items.joins(:tags).where(tags: { name: sanitized_tags }) + @items = @items.where( + ExerciseTag.joins(:tag).where( + '"exercise_tags"."exercise_id" = "exercises"."id"' + ).where(tag: { name: sanitized_tags }).arel.exists + ) end end @@ -189,11 +193,16 @@ def exec(params = {}, options = {}) sn = to_string_array(name, append_wildcard: true) next @items = @items.none if sn.empty? - @items = @items.joins(publication: { authors: { user: :account } }).where( - acct[:username].matches_any(sn) - .or(acct[:first_name].matches_any(sn)) - .or(acct[:last_name].matches_any(sn)) - .or(acct[:full_name].matches_any(sn))) + @items = @items.joins(:publication).where( + Author.joins(user: :account).where( + '"authors"."publication_id" = "publication"."id"' + ).where( + acct[:username].matches_any(sn) + .or(acct[:first_name].matches_any(sn)) + .or(acct[:last_name].matches_any(sn)) + .or(acct[:full_name].matches_any(sn)) + ).arel.exists + ) end end @@ -202,11 +211,16 @@ def exec(params = {}, options = {}) sn = to_string_array(name, append_wildcard: true) next @items = @items.none if sn.empty? - @items = @items.joins(publication: { copyright_holders: { user: :account } }).where( - acct[:username].matches_any(sn) - .or(acct[:first_name].matches_any(sn)) - .or(acct[:last_name].matches_any(sn)) - .or(acct[:full_name].matches_any(sn))) + @items = @items.joins(:publication).where( + CopyrightHolder.joins(user: :account).where( + '"copyright_holders"."publication_id" = "publication"."id"' + ).where( + acct[:username].matches_any(sn) + .or(acct[:first_name].matches_any(sn)) + .or(acct[:last_name].matches_any(sn)) + .or(acct[:full_name].matches_any(sn)) + ).arel.exists + ) end end @@ -215,17 +229,25 @@ def exec(params = {}, options = {}) sn = to_string_array(name, append_wildcard: true) next @items = @items.none if sn.empty? - @items = @items.joins( - publication: { authors: { user: :account }, copyright_holders: { user: :account } } - ).where( - acct_author[:username].matches_any(sn) - .or(acct_author[:first_name].matches_any(sn)) - .or(acct_author[:last_name].matches_any(sn)) - .or(acct_author[:full_name].matches_any(sn)) - .or(acct_copyright[:username].matches_any(sn)) - .or(acct_copyright[:first_name].matches_any(sn)) - .or(acct_copyright[:last_name].matches_any(sn)) - .or(acct_copyright[:full_name].matches_any(sn))) + @items = @items.joins(:publication).where( + Author.joins(user: :account).where( + '"authors"."publication_id" = "publication"."id"' + ).where( + acct[:username].matches_any(sn) + .or(acct[:first_name].matches_any(sn)) + .or(acct[:last_name].matches_any(sn)) + .or(acct[:full_name].matches_any(sn)) + ).arel.exists.or( + CopyrightHolder.joins(user: :account).where( + '"copyright_holders"."publication_id" = "publication"."id"' + ).where( + acct[:username].matches_any(sn) + .or(acct[:first_name].matches_any(sn)) + .or(acct[:last_name].matches_any(sn)) + .or(acct[:full_name].matches_any(sn)) + ).arel.exists + ) + ) end end end diff --git a/app/routines/search_vocab_terms.rb b/app/routines/search_vocab_terms.rb index 036e882d..bd900f1e 100644 --- a/app/routines/search_vocab_terms.rb +++ b/app/routines/search_vocab_terms.rb @@ -165,7 +165,11 @@ def exec(params = {}, options = {}) sanitized_tags = to_string_array(tag).map(&:downcase) next @items = @items.none if sanitized_tags.empty? - @items = @items.joins(:tags).where(tags: { name: sanitized_tags }) + @items = @items.where( + VocabTermTag.joins(:tag).where( + '"vocab_term_tags"."vocab_term_id" = "vocab_terms"."id"' + ).where(tag: { name: sanitized_tags }).arel.exists + ) end end @@ -198,11 +202,16 @@ def exec(params = {}, options = {}) sn = to_string_array(name, append_wildcard: true) next @items = @items.none if sn.empty? - @items = @items.joins(publication: { authors: { user: :account } }).where( - acct[:username].matches_any(sn) - .or(acct[:first_name].matches_any(sn)) - .or(acct[:last_name].matches_any(sn)) - .or(acct[:full_name].matches_any(sn))) + @items = @items.joins(:publication).where( + Author.joins(user: :account).where( + '"authors"."publication_id" = "publication"."id"' + ).where( + acct[:username].matches_any(sn) + .or(acct[:first_name].matches_any(sn)) + .or(acct[:last_name].matches_any(sn)) + .or(acct[:full_name].matches_any(sn)) + ).arel.exists + ) end end @@ -211,11 +220,16 @@ def exec(params = {}, options = {}) sn = to_string_array(name, append_wildcard: true) next @items = @items.none if sn.empty? - @items = @items.joins(publication: { copyright_holders: { user: :account } }).where( - acct[:username].matches_any(sn) - .or(acct[:first_name].matches_any(sn)) - .or(acct[:last_name].matches_any(sn)) - .or(acct[:full_name].matches_any(sn))) + @items = @items.joins(:publication).where( + CopyrightHolder.joins(user: :account).where( + '"copyright_holders"."publication_id" = "publication"."id"' + ).where( + acct[:username].matches_any(sn) + .or(acct[:first_name].matches_any(sn)) + .or(acct[:last_name].matches_any(sn)) + .or(acct[:full_name].matches_any(sn)) + ).arel.exists + ) end end @@ -224,17 +238,25 @@ def exec(params = {}, options = {}) sn = to_string_array(name, append_wildcard: true) next @items = @items.none if sn.empty? - @items = @items.joins( - publication: { authors: { user: :account }, copyright_holders: { user: :account } } - ).where( - acct_author[:username].matches_any(sn) - .or(acct_author[:first_name].matches_any(sn)) - .or(acct_author[:last_name].matches_any(sn)) - .or(acct_author[:full_name].matches_any(sn)) - .or(acct_copyright[:username].matches_any(sn)) - .or(acct_copyright[:first_name].matches_any(sn)) - .or(acct_copyright[:last_name].matches_any(sn)) - .or(acct_copyright[:full_name].matches_any(sn))) + @items = @items.joins(:publication).where( + Author.joins(user: :account).where( + '"authors"."publication_id" = "publication"."id"' + ).where( + acct[:username].matches_any(sn) + .or(acct[:first_name].matches_any(sn)) + .or(acct[:last_name].matches_any(sn)) + .or(acct[:full_name].matches_any(sn)) + ).arel.exists.or( + CopyrightHolder.joins(user: :account).where( + '"copyright_holders"."publication_id" = "publication"."id"' + ).where( + acct[:username].matches_any(sn) + .or(acct[:first_name].matches_any(sn)) + .or(acct[:last_name].matches_any(sn)) + .or(acct[:full_name].matches_any(sn)) + ).arel.exists + ) + ) end end end diff --git a/spec/routines/search_exercises_spec.rb b/spec/routines/search_exercises_spec.rb index cfa45842..2036fe44 100644 --- a/spec/routines/search_exercises_spec.rb +++ b/spec/routines/search_exercises_spec.rb @@ -41,6 +41,9 @@ @exercise_1.publication.publish.save! FactoryBot.create :author, publication: @exercise_1.publication FactoryBot.create :copyright_holder, publication: @exercise_1.publication + @exercise_1.publication.reload + FactoryBot.create :author, publication: @exercise_1.publication + FactoryBot.create :copyright_holder, publication: @exercise_1.publication @exercise_2 = Exercise.new Api::V1::Exercises::Representer.new(@exercise_2).from_hash( @@ -119,7 +122,7 @@ end end - it "returns an Exercise matching some tags" do + it "returns an Exercise matching some tag" do result = described_class.call(q: 'tag:tAg1') expect(result.errors).to be_empty @@ -128,7 +131,7 @@ expect(outputs.items).to eq [@exercise_1] end - it "does not return old versions of published Exercises matching the tags" do + it "does not return old versions of published Exercises matching the tag" do new_exercise = @exercise_1.new_version new_exercise.tags = ['tag2', 'tag3'] new_exercise.save! @@ -214,7 +217,7 @@ end context "multiple matches" do - it "returns Exercises matching some tags" do + it "returns Exercises matching some tag" do result = described_class.call(q: 'tag:TaG2') expect(result.errors).to be_empty @@ -223,6 +226,24 @@ expect(outputs.items).to eq [@exercise_1, @exercise_2] end + it "returns a Exercises matching any of a list of tags" do + result = described_class.call(q: 'tag:tAg1,TaG3') + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 2 + expect(outputs.items).to eq [@exercise_1, @exercise_2] + end + + it "returns an Exercise matching all of a list of tags" do + result = described_class.call(q: 'tag:tAg1 tag:TaG2') + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@exercise_1] + end + it "returns Exercises matching some content" do result = described_class.call(q: 'content:AdIpIsCi') expect(result.errors).to be_empty @@ -232,6 +253,77 @@ expect(outputs.items).to eq [@exercise_1, @exercise_2] end + it "returns Exercises matching any of a list of authors" do + result = described_class.call( + q: "author:\"#{@exercise_1.authors.first.name},#{@exercise_2.authors.first.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 2 + expect(outputs.items).to eq [@exercise_1, @exercise_2] + end + + it "returns an Exercise matching all of a list of authors" do + result = described_class.call( + q: "author:\"#{@exercise_1.authors.first.name + }\" author:\"#{@exercise_1.authors.last.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@exercise_1] + end + + it "returns Exercises matching any of a list of copyright holders" do + result = described_class.call( + q: "copyright_holder:\"#{@exercise_1.copyright_holders.first.name + },#{@exercise_2.copyright_holders.first.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 2 + expect(outputs.items).to eq [@exercise_1, @exercise_2] + end + + it "returns an Exercise matching all of a list of copyright holders" do + result = described_class.call( + q: "copyright_holder:\"#{@exercise_1.copyright_holders.first.name + }\" copyright_holder:\"#{@exercise_1.copyright_holders.last.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@exercise_1] + end + + it "returns Exercises matching any of a list of collaborators" do + result = described_class.call( + q: "collaborator:\"#{@exercise_1.authors.first.name + },#{@exercise_2.copyright_holders.first.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 2 + expect(outputs.items).to eq [@exercise_1, @exercise_2] + end + + it "returns an Exercise matching all of a list of collaborators" do + result = described_class.call( + q: "collaborator:\"#{@exercise_1.authors.first.name + }\" collaborator:\"#{@exercise_1.copyright_holders.first.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@exercise_1] + end + it "sorts by multiple fields in different directions" do result = described_class.call(q: 'content:aDiPiScI', order_by: "number DESC, version ASC") expect(result.errors).to be_empty diff --git a/spec/routines/search_vocab_terms_spec.rb b/spec/routines/search_vocab_terms_spec.rb index e53dc84e..3b294200 100644 --- a/spec/routines/search_vocab_terms_spec.rb +++ b/spec/routines/search_vocab_terms_spec.rb @@ -24,6 +24,9 @@ @vocab_term_1.save! FactoryBot.create :author, publication: @vocab_term_1.publication FactoryBot.create :copyright_holder, publication: @vocab_term_1.publication + @vocab_term_1.publication.reload + FactoryBot.create :author, publication: @vocab_term_1.publication + FactoryBot.create :copyright_holder, publication: @vocab_term_1.publication @vocab_term_1.publication.authors.reset @vocab_term_1.publication.copyright_holders.reset @@ -110,7 +113,7 @@ expect(outputs.items).to eq [@vocab_term_2] end - it "returns a VocabTerm matching some tags" do + it "returns a VocabTerm matching some tag" do result = described_class.call(q: 'tag:tAg1') expect(result.errors).to be_empty @@ -234,7 +237,34 @@ end context "multiple matches" do - it "returns VocabTerms matching the content" do + it "returns VocabTerms matching some tag" do + result = described_class.call(q: 'tag:TaG2') + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 2 + expect(outputs.items).to eq [@vocab_term_1, @vocab_term_2] + end + + it "returns a VocabTerms matching any of a list of tags" do + result = described_class.call(q: 'tag:tAg1,TaG3') + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 2 + expect(outputs.items).to eq [@vocab_term_1, @vocab_term_2] + end + + it "returns a VocabTerm matching all of a list of tags" do + result = described_class.call(q: 'tag:tAg1 tag:TaG2') + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@vocab_term_1] + end + + it "returns VocabTerms matching some content" do result = described_class.call(q: 'content:"lOrEm IpSuM"') expect(result.errors).to be_empty @@ -243,8 +273,58 @@ expect(outputs.items).to eq [@vocab_term_1, @vocab_term_2] end - it "returns VocabTerms matching the tags" do - result = described_class.call(q: 'tag:TaG2') + it "returns VocabTerms matching any of a list of authors" do + result = described_class.call( + q: "author:\"#{@vocab_term_1.authors.first.name},#{@vocab_term_2.authors.first.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 2 + expect(outputs.items).to eq [@vocab_term_1, @vocab_term_2] + end + + it "returns a VocabTerm matching all of a list of authors" do + result = described_class.call( + q: "author:\"#{@vocab_term_1.authors.first.name + }\" author:\"#{@vocab_term_1.authors.last.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@vocab_term_1] + end + + it "returns VocabTerms matching any of a list of copyright holders" do + result = described_class.call( + q: "copyright_holder:\"#{@vocab_term_1.copyright_holders.first.name + },#{@vocab_term_2.copyright_holders.first.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 2 + expect(outputs.items).to eq [@vocab_term_1, @vocab_term_2] + end + + it "returns a VocabTerm matching all of a list of copyright holders" do + result = described_class.call( + q: "copyright_holder:\"#{@vocab_term_1.copyright_holders.first.name + }\" copyright_holder:\"#{@vocab_term_1.copyright_holders.last.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@vocab_term_1] + end + + it "returns VocabTerms matching any of a list of collaborators" do + result = described_class.call( + q: "collaborator:\"#{@vocab_term_1.authors.first.name + },#{@vocab_term_2.copyright_holders.first.name}\"" + ) expect(result.errors).to be_empty outputs = result.outputs @@ -252,6 +332,18 @@ expect(outputs.items).to eq [@vocab_term_1, @vocab_term_2] end + it "returns a VocabTerm matching all of a list of collaborators" do + result = described_class.call( + q: "collaborator:\"#{@vocab_term_1.authors.first.name + }\" collaborator:\"#{@vocab_term_1.copyright_holders.first.name}\"" + ) + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@vocab_term_1] + end + it "sorts by multiple fields in different directions" do result = described_class.call(q: 'content:"lOrEm IpSuM"', order_by: "number DESC, version ASC") From 6e1c2d714e53959c63edeb933d11eac95f450c37 Mon Sep 17 00:00:00 2001 From: Dante Soares Date: Thu, 4 Aug 2022 13:28:12 -0500 Subject: [PATCH 2/3] Search exercises and vocab terms by solutions_are_public --- app/routines/search_exercises.rb | 13 +++++++++++++ app/routines/search_vocab_terms.rb | 13 +++++++++++++ spec/routines/search_exercises_spec.rb | 15 +++++++++++++-- spec/routines/search_vocab_terms_spec.rb | 15 +++++++++++++-- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/app/routines/search_exercises.rb b/app/routines/search_exercises.rb index 74f08c7a..b76afbaa 100644 --- a/app/routines/search_exercises.rb +++ b/app/routines/search_exercises.rb @@ -250,6 +250,19 @@ def exec(params = {}, options = {}) ) end end + + with.keyword :solutions_are_public do |saps| + saps.each do |sap| + sanitized_saps = to_string_array(sap).map do |str| + ActiveModel::Type::Boolean.new.cast(str) + end + next @items = @items.none if sanitized_saps.empty? + + @items = @items.joins(publication: :publication_group).where( + publication: { publication_group: { solutions_are_public: sanitized_saps } } + ) + end + end end outputs.items = outputs.items.select( diff --git a/app/routines/search_vocab_terms.rb b/app/routines/search_vocab_terms.rb index bd900f1e..4de48b55 100644 --- a/app/routines/search_vocab_terms.rb +++ b/app/routines/search_vocab_terms.rb @@ -259,6 +259,19 @@ def exec(params = {}, options = {}) ) end end + + with.keyword :solutions_are_public do |saps| + saps.each do |sap| + sanitized_saps = to_string_array(sap).map do |str| + ActiveModel::Type::Boolean.new.cast(str) + end + next @items = @items.none if sanitized_saps.empty? + + @items = @items.joins(publication: :publication_group).where( + publication: { publication_group: { solutions_are_public: sanitized_saps } } + ) + end + end end outputs.items = outputs.items.select( diff --git a/spec/routines/search_exercises_spec.rb b/spec/routines/search_exercises_spec.rb index 2036fe44..01378ec4 100644 --- a/spec/routines/search_exercises_spec.rb +++ b/spec/routines/search_exercises_spec.rb @@ -35,7 +35,8 @@ 'content_html' => "Sed do eiusmod tempor" }], 'formats' => [ 'multiple-choice', 'free-response' ] - }] + }], + 'solutions_are_public' => true ) @exercise_1.save! @exercise_1.publication.publish.save! @@ -58,7 +59,8 @@ 'content_html' => "Sed quia non numquam" }], 'formats' => [ 'multiple-choice', 'free-response' ] - }] + }], + 'solutions_are_public' => false ) @exercise_2.save! @exercise_2.publication.version = 42 @@ -214,6 +216,15 @@ expect(outputs.total_count).to eq 1 expect(outputs.items).to eq [@exercise_1] end + + it "returns an Exercise matching solutions_are_public" do + result = described_class.call q: "solutions_are_public:true" + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@exercise_1] + end end context "multiple matches" do diff --git a/spec/routines/search_vocab_terms_spec.rb b/spec/routines/search_vocab_terms_spec.rb index 3b294200..d962e47f 100644 --- a/spec/routines/search_vocab_terms_spec.rb +++ b/spec/routines/search_vocab_terms_spec.rb @@ -19,7 +19,8 @@ 'tags' => ['tag1', 'tag2'], 'term' => "Lorem ipsum", 'definition' => "Dolor sit amet", - 'distractor_literals' => ["Consectetur adipiscing elit", "Sed do eiusmod tempor"] + 'distractor_literals' => ["Consectetur adipiscing elit", "Sed do eiusmod tempor"], + 'solutions_are_public' => true ) @vocab_term_1.save! FactoryBot.create :author, publication: @vocab_term_1.publication @@ -36,7 +37,8 @@ 'tags' => ['tag2', 'tag3'], 'term' => "Dolorem ipsum", 'definition' => "Quia dolor sit amet", - 'distractor_literals' => ["Consectetur adipisci velit", "Sed quia non numquam"] + 'distractor_literals' => ["Consectetur adipisci velit", "Sed quia non numquam"], + 'solutions_are_public' => false ) @vocab_term_2.save! @vocab_term_2.publication.update_attribute :version, 42 @@ -234,6 +236,15 @@ expect(outputs.total_count).to eq 1 expect(outputs.items).to eq [@vocab_term_1] end + + it "returns a VocabTerm matching solutions_are_public" do + result = described_class.call q: "solutions_are_public:true" + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@vocab_term_1] + end end context "multiple matches" do From 0448ac8db3bd19387d16f2c015ffccf95e4bb8f3 Mon Sep 17 00:00:00 2001 From: Dante Soares Date: Thu, 4 Aug 2022 14:04:20 -0500 Subject: [PATCH 3/3] Added a format filter to Exercises search --- app/routines/search_exercises.rb | 13 ++++++++++++ spec/routines/search_exercises_spec.rb | 29 +++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/app/routines/search_exercises.rb b/app/routines/search_exercises.rb index b76afbaa..f0fe86ee 100644 --- a/app/routines/search_exercises.rb +++ b/app/routines/search_exercises.rb @@ -251,6 +251,19 @@ def exec(params = {}, options = {}) end end + with.keyword :format do |formats| + formats.each do |format| + sanitized_formats = to_string_array(format).map(&:downcase) + next @items = @items.none if sanitized_formats.empty? + + @items = @items.where( + Question.joins(stems: :stylings).where( + '"questions"."exercise_id" = "exercises"."id"' + ).where(stems: { stylings: { style: sanitized_formats } }).arel.exists + ) + end + end + with.keyword :solutions_are_public do |saps| saps.each do |sap| sanitized_saps = to_string_array(sap).map do |str| diff --git a/spec/routines/search_exercises_spec.rb b/spec/routines/search_exercises_spec.rb index 01378ec4..d12006e7 100644 --- a/spec/routines/search_exercises_spec.rb +++ b/spec/routines/search_exercises_spec.rb @@ -58,7 +58,7 @@ 'answers' => [{ 'content_html' => "Sed quia non numquam" }], - 'formats' => [ 'multiple-choice', 'free-response' ] + 'formats' => [ 'true-false' ] }], 'solutions_are_public' => false ) @@ -217,6 +217,15 @@ expect(outputs.items).to eq [@exercise_1] end + it "returns an Exercise matching a format" do + result = described_class.call q: "format:multiple-choice" + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@exercise_1] + end + it "returns an Exercise matching solutions_are_public" do result = described_class.call q: "solutions_are_public:true" expect(result.errors).to be_empty @@ -335,6 +344,24 @@ expect(outputs.items).to eq [@exercise_1] end + it "returns Exercises matching any of a list of formats" do + result = described_class.call q: "format:multiple-choice,true-false" + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 2 + expect(outputs.items).to eq [@exercise_1, @exercise_2] + end + + it "returns an Exercise matching all of a list of formats" do + result = described_class.call q: "format:multiple-choice format:free-response" + expect(result.errors).to be_empty + + outputs = result.outputs + expect(outputs.total_count).to eq 1 + expect(outputs.items).to eq [@exercise_1] + end + it "sorts by multiple fields in different directions" do result = described_class.call(q: 'content:aDiPiScI', order_by: "number DESC, version ASC") expect(result.errors).to be_empty