diff --git a/app/models/labware_creators/plate_split_to_tube_racks.rb b/app/models/labware_creators/plate_split_to_tube_racks.rb index 8c40b9f6b..15a355555 100644 --- a/app/models/labware_creators/plate_split_to_tube_racks.rb +++ b/app/models/labware_creators/plate_split_to_tube_racks.rb @@ -161,14 +161,12 @@ def perform_transfers ) end - # We will create multiple child tube racks, redirect back to the parent plate + # We redirect to the contingency tube rack that we have just created. def redirection_target - # NB. if we want to change this to the first tube rack use: child_tube_racks[SEQ_TUBE_RACK_NAME] - parent + child_tube_racks[SPR_TUBE_RACK_NAME] end - # We will want to see the list of tubes in the rack - # TODO: as these are racked_tubes and not child tubes, does the tube rack presenter have a relatives tab? + # Display the relatives tab on the child tube rack page. def anchor 'relatives_tab' end diff --git a/app/models/robots/bed/base.rb b/app/models/robots/bed/base.rb index 8776f8387..0883422e2 100644 --- a/app/models/robots/bed/base.rb +++ b/app/models/robots/bed/base.rb @@ -100,9 +100,9 @@ def formatted_message private def correct_labware_purpose - return true if Array(purpose).include?(labware.purpose_name) + return true if Array(purpose).include?(labware.purpose.name) - error("Labware #{labware.human_barcode} is a #{labware.purpose_name} not a #{purpose_labels} labware.") + error("Labware #{labware.human_barcode} is a #{labware.purpose.name} not a #{purpose_labels} labware.") end def correct_labware_state diff --git a/app/models/robots/bed/plate_to_tube_racks_bed.rb b/app/models/robots/bed/plate_to_tube_racks_bed.rb index 02e360c84..a07c1d646 100644 --- a/app/models/robots/bed/plate_to_tube_racks_bed.rb +++ b/app/models/robots/bed/plate_to_tube_racks_bed.rb @@ -4,19 +4,19 @@ module Robots::Bed # This bed hosts the parent plate or a tube-rack. It uses the robot to find # its labware and child labware. When it is used as a source bed, it should # host the Plate. When it is used as a destination bed, it should host a - # tube-rack wrapper. + # Tube Rack. # class PlateToTubeRacksBed < Robots::Bed::Base # Updates the metadata of the labware with the robot barcode. # This method is called inside the robot controller's start action for - # tube-rack wrappers and it sets the created_with_robot metadata field. + # Tube Racks and it sets the created_with_robot metadata field. # # @param robot_barcode [String] the robot barcode # @return [void] # def labware_created_with_robot(robot_barcode) # RobotController uses machine barcode for initialising LabwareMetadata - labware.tubes.each do |tube| + labware.racked_tubes.tubes.each do |tube| LabwareMetadata.new(user_uuid: user_uuid, barcode: tube.barcode.machine).update!( created_with_robot: robot_barcode ) @@ -25,7 +25,7 @@ def labware_created_with_robot(robot_barcode) # Returns an array of labware from the robot's labware store for barcodes. # - # @return [Array] child tube-rack wrappers + # @return [Array] child tube racks # def child_labware return [] if labware.blank? @@ -51,7 +51,7 @@ def load(barcodes) def transition return if target_state.nil? || labware.nil? # We have nothing to do - labware.tubes.each { |tube| change_tube_state(tube) } + labware.racked_tubes.each { |racked_tube| change_tube_state(racked_tube.tube) } end # Changes the state of one tube to the target state. This method is called diff --git a/app/models/robots/plate_to_tube_racks_robot.rb b/app/models/robots/plate_to_tube_racks_robot.rb index 9f00bc6a8..386faf141 100644 --- a/app/models/robots/plate_to_tube_racks_robot.rb +++ b/app/models/robots/plate_to_tube_racks_robot.rb @@ -27,16 +27,7 @@ module Robots class PlateToTubeRacksRobot < Robots::SplittingRobot attr_writer :relationships # Hash from robot config into @relationships - # Option for including downstream tubes and metadata in Plate API response. - PLATE_INCLUDES = 'purpose,wells,wells.downstream_tubes,wells.downstream_tubes.custom_metadatum_collection' - - # Returns the well order for getting wells from the plate. - # - # @return [Symbol] the well order - # - def well_order - :coordinate - end + PLATE_INCLUDES = 'purpose' # Returns the bed class for this robot. # @@ -74,20 +65,22 @@ def verify(params) # wrapper objects for tube racks. # # @param barcodes [Array] array of barcodes - # @return [Array] + # @return [Array] # def find_bed_labware(barcodes) barcodes.filter_map { |barcode| labware_store[barcode] } end - # Returns an array of child labware from the robot's labware store for + # Returns an array of child tube racks from the robot's labware store for # the given Plate. # # @param plate [Plate] the parent plate - # @return [Array] array of tube rack wrapper objects + # @return [Array] array of tube rack wrapper objects # def child_labware(plate) - labware_store.values.select { |labware| labware.respond_to?(:parent) && labware.parent.uuid == plate.uuid } + labware_store.values.select do |labware| + labware.respond_to?(:parents) && labware.parents&.first&.uuid == plate.uuid + end end private @@ -103,16 +96,25 @@ def prepare_robot(bed_labwares) # Prepares the labware store before handling robot actions. This method is # called before the robot's bed verification and perform transfer actions. + # NB. This is what labwares should be scanned, given the plate barcode scanned. + # i.e. the plate barcode is scanned, and the expected tube rack children are determined. # # @param bed_labwares [Hash] the hash from request parameters # @return [void] # def prepare_labware_store(bed_labwares) return if labware_store.present? + stripped_barcodes(bed_labwares).each do |barcode| plate = find_plate(barcode) + + # skip non plates next if plate.blank? + + # add the parent plate to the labware store add_plate_to_labware_store(plate) + + # determine the expected tube racks for this parent plate and add to the labware store add_tube_racks_to_labware_store(plate) end end @@ -134,16 +136,12 @@ def prepare_labware_store(bed_labwares) # that were already recorded by the prepare_labware_store method. We # override the bed configuration based on availability of labware here. # - # NB. The child labware are tube-rack wrapper objects, not actual labware. - # The information about tube-racks are found using the metadata of the - # downstream tubes, included in the Sequencescape API response. - # # @ return [void] # def prepare_beds @relationships.each do |relationship| relationship_children = relationship.dig('options', 'children') - labware_store_purposes = labware_store.values.map(&:purpose_name) + labware_store_purposes = labware_store.values.map { |labware| labware.purpose.name } bed_barcodes_to_remove = relationship_children.select { |barcode| labware_store_purposes.exclude?(beds[barcode].purpose) } @@ -192,10 +190,28 @@ def add_plate_to_labware_store(plate) # @param plate [Plate] the parent plate # @return [void] # + # rubocop:disable Metrics/AbcSize def add_tube_racks_to_labware_store(plate) - find_tube_racks(plate).each { |rack| labware_store[rack.barcode.human] = rack } + plate.children.each do |asset| + # NB. children of plate are currently Assets, whereas we need TubeRack objects + + # skip when child is anything other than a tube rack + next unless asset.type == 'tube_racks' + + # fetch tube rack from API + tube_rack = find_tube_rack(asset.uuid) + + # cycle beds, if tube rack matches purpose and state from config, add it + beds.each_value do |bed| + if bed.purpose == tube_rack.purpose.name && bed.states.include?(tube_rack.state) + labware_store[tube_rack.barcode.human] = tube_rack + end + end + end end + # rubocop:enable Metrics/AbcSize + # Returns the labware store. The hash is indexed by the labware barcode. # The values are either Plate objects or labware-like wrapper objects for # tube racks. @@ -216,37 +232,11 @@ def find_plate(barcode) Sequencescape::Api::V2::Plate.find_all({ barcode: [barcode] }, includes: PLATE_INCLUDES).first end - # Returns an array of tube rack wrapper objects that from the downstream tubes - # of the given plate. - # - # @param plate [Plate] the parent plate - # @return [Array] array of tube rack wrapper objects - # - def find_tube_racks(plate) - plate - .wells - .sort_by(&well_order) - .each_with_object([]) do |well, racks| - next if well.downstream_tubes.blank? - well.downstream_tubes.each do |tube| - barcode = tube.custom_metadatum_collection.metadata[:tube_rack_barcode] - find_or_create_tube_rack_wrapper(racks, barcode, plate).push_tube(tube) - end - end - end - - # Returns an existing or new tube rack wrapper object. - # - # @param racks [Array] the tube racks found so far - # @param barcode[String] the barcode of the tube rack - # @param plate [Plate] the parent plate - # @return [TubeRackWrapper] the tube rack wrapper object - # - def find_or_create_tube_rack_wrapper(racks, barcode, plate) - rack = racks.detect { |tube_rack| tube_rack.barcode.human == barcode } - return rack if rack.present? - labware_barcode = LabwareBarcode.new(human: barcode, machine: barcode) - racks.push(TubeRackWrapper.new(labware_barcode, plate)).last + def find_tube_rack(uuid) + Sequencescape::Api::V2::TubeRack.find_all( + { uuid: [uuid] }, + includes: Sequencescape::Api::V2::TubeRack::DEFAULT_TUBE_RACK_INCLUDES + ).first end end end diff --git a/app/models/robots/tube_rack_wrapper.rb b/app/models/robots/tube_rack_wrapper.rb deleted file mode 100644 index a63445220..000000000 --- a/app/models/robots/tube_rack_wrapper.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true -module Robots - # This wrapper class is for tube racks that are not actual recorded labware. - # The instance acts as a labware wrapper and provides access to the tubes. - # Labware methods are delegated to the last tube on the tube rack. - # - class TubeRackWrapper - attr_accessor :barcode, :parent, :tubes - delegate :purpose_name, :purpose, :state, :uuid, to: :last_tube, allow_nil: true - - # Initializes a new instance of the class. - # - # @param barcode [LabwareBarcode] the barcode object of the tube rack - # @param parent [Plate] the parent plate - # @param tubes [Array] the tubes on the tube rack - # - def initialize(barcode, parent, tubes: []) - @barcode = barcode - @parent = parent - @tubes = [] - - # Keep track of tube positions, tube coordinate on rack => index in - # @tubes array, e.g. 'C1' => 0 . - @tube_positions = {} - tubes.each { |tube| push_tube(tube) } # Eliminate duplicate tubes by position - end - - # Returns the last tube on the tube rack. This method is used for delegating - # certain methods to make it behave like a labware object. - # - # @return [Tube] the last tube - def last_tube - @tubes.last - end - - # Appends a tube to the tube rack or replaces an existing tube. If there - # is an existing tube with the same position, the tube with the latest - # creation date is kept. - # - # @param tube [Tube] the tube to be added - # @return [Void] - # - def push_tube(tube) - index = @tube_positions[tube_rack_position(tube)] - if index.present? - @tubes[index] = tube if tube.created_at >= @tubes[index].created_at - else - @tubes.push(tube) - @tube_positions[tube_rack_position(tube)] = @tubes.length - 1 - end - end - - # Returns the human readable barcode of the tube rack. - # - # @return [String] the human readable barcode - # - def human_barcode - barcode.human - end - - private - - # Returns the tube rack position of a tube from its metadata - # - # @param tube [Tube] the tube - # @return [String] the tube rack position - # - def tube_rack_position(tube) - tube.custom_metadatum_collection.metadata[:tube_rack_position] - end - end -end diff --git a/app/sequencescape/sequencescape/api/v2/tube_rack.rb b/app/sequencescape/sequencescape/api/v2/tube_rack.rb index 849b97853..46b434f3d 100644 --- a/app/sequencescape/sequencescape/api/v2/tube_rack.rb +++ b/app/sequencescape/sequencescape/api/v2/tube_rack.rb @@ -12,6 +12,7 @@ class Sequencescape::Api::V2::TubeRack < Sequencescape::Api::V2::Base self.tube_rack = true + DEFAULT_TUBE_RACK_INCLUDES = [:purpose, 'racked_tubes', 'racked_tubes.tube'].freeze STATES_TO_FILTER_OUT = %w[cancelled failed].freeze STATE_EMPTY = 'empty' STATE_MIXED = 'mixed' @@ -54,12 +55,14 @@ def stock_plate nil end - def self.find_by(params) - options = params.dup - includes = options.delete(:includes) || DEFAULT_INCLUDES + def self.find_by(options, includes: DEFAULT_TUBE_RACK_INCLUDES) Sequencescape::Api::V2::TubeRack.includes(*includes).find(**options).first end + def self.find_all(options, includes: DEFAULT_TUBE_RACK_INCLUDES) + Sequencescape::Api::V2::TubeRack.includes(*includes).where(**options).all + end + # This method sorts the racked tubes by their coordinate, taking into account both row and column parts. # Sorts by column first and then by row. # diff --git a/app/views/labware/_basic_relative.html.erb b/app/views/labware/_basic_relative.html.erb index 3f5a9e4f2..ad5b08204 100644 --- a/app/views/labware/_basic_relative.html.erb +++ b/app/views/labware/_basic_relative.html.erb @@ -1,8 +1,6 @@ <%# A simple representation of any labware type not currently handled otherwise -%> <%# locals: (labware:, open_in_new_window: false) -%> -<% - labware_path = limber_plate_path(labware.uuid) + "#relatives" -%> +<% labware_path = limber_plate_path(labware.uuid) + "#relatives" %>
<%= labware.purpose.name %> - <%= link_to labware_path, class: 'card-link stretched-link', target: (open_in_new_window ? '_blank' : '_self') do %> <%= labware.barcode.human.present? ? labware.barcode.human : labware.barcode.machine %> diff --git a/app/views/labware/_child_labware_item.html.erb b/app/views/labware/_child_labware_item.html.erb index 2ef6b82e9..99d914c32 100644 --- a/app/views/labware/_child_labware_item.html.erb +++ b/app/views/labware/_child_labware_item.html.erb @@ -10,6 +10,8 @@ <% else %> <%= render partial: 'labware/simple_tube', locals: { tube: labware, open_in_new_window: open_in_new_window } %> <% end %> +<% elsif labware.tube_rack? %> + <%= render partial: 'labware/tube_rack', locals: { tube_rack: labware, open_in_new_window: open_in_new_window } %> <% else %> <%= render partial: 'labware/basic_relative', locals: { labware: labware, open_in_new_window: open_in_new_window } %> <% end %> diff --git a/app/views/labware/_parent_labware_item.html.erb b/app/views/labware/_parent_labware_item.html.erb index ecb83a9a9..004df3dcc 100644 --- a/app/views/labware/_parent_labware_item.html.erb +++ b/app/views/labware/_parent_labware_item.html.erb @@ -4,6 +4,8 @@ <% open_in_new_window = false %> <% if labware.tube? %> <%= render partial: 'labware/simple_tube', locals: { tube: labware, open_in_new_window: open_in_new_window } %> +<% elsif labware.tube_rack? %> + <%= render partial: 'labware/tube_rack', locals: { tube_rack: labware, open_in_new_window: open_in_new_window } %> <% else %> <%= render partial: 'labware/basic_relative', locals: { labware: labware, open_in_new_window: open_in_new_window } %> <% end %> diff --git a/app/views/labware/_tube_rack.html.erb b/app/views/labware/_tube_rack.html.erb new file mode 100644 index 000000000..431d71fa0 --- /dev/null +++ b/app/views/labware/_tube_rack.html.erb @@ -0,0 +1,8 @@ +<%# locals: (tube_rack:, open_in_new_window: false) -%> +<% tube_rack_path = limber_tube_rack_path(tube_rack.uuid) + "#relatives" %> +
+ <%= tube_rack.purpose.name %> - <%= link_to tube_rack_path, class: 'card-link stretched-link', target: (open_in_new_window ? '_blank' : '_self') do %> + <%= tube_rack.barcode.human.present? ? tube_rack.barcode.human : tube_rack.barcode.machine %> + <% end %> + <%= content_tag(:span, state_badge(tube_rack.state), class: 'float-right') %> +
diff --git a/config/robots.rb b/config/robots.rb index 0b71b5728..c497dc7bb 100644 --- a/config/robots.rb +++ b/config/robots.rb @@ -3256,8 +3256,8 @@ # LRC PBMC Bank to LRC TR Bank Seq and LRC TR Bank Spare tube racks # Transfers 1:2 (2nd rack optional) custom_robot( - 'hamilton-lrc-pbmc-bank-to-lrc-bank-seq-and-lrc-bank-spare-tube-racks', - name: 'Hamilton LRC PBMC Bank => LRC TR Bank Seq and LRC Bank Spare Tube Racks', + 'hamilton-lrc-pbmc-bank-to-lrc-tr-bank-seq-and-lrc-tr-bank-spare-tube-racks', + name: 'Hamilton LRC PBMC Bank => LRC TR Bank Seq and LRC TR Bank Spare Tube Racks', beds: { bed(12).barcode => { purpose: 'LRC PBMC Bank', diff --git a/spec/factories/plate_purpose_factories.rb b/spec/factories/plate_purpose_factories.rb index 567aab279..9d10ff6a3 100644 --- a/spec/factories/plate_purpose_factories.rb +++ b/spec/factories/plate_purpose_factories.rb @@ -8,6 +8,13 @@ uuid { 'example-purpose-uuid' } end + # Basic v2 Tube Rack Purpose + factory :v2_tube_rack_purpose, class: Sequencescape::Api::V2::TubeRackPurpose, traits: [:barcoded_v2] do + skip_create + sequence(:name) { |n| "Limber Example TubeRackPurpose #{n}" } + uuid { 'example-tr-purpose-uuid' } + end + # Basic V1 Plate Purpose factory :plate_purpose, class: Sequencescape::PlatePurpose, traits: [:api_object] do name { 'Limber Example Purpose' } diff --git a/spec/factories/tube_rack_factories.rb b/spec/factories/tube_rack_factories.rb index e7642a340..897481af6 100644 --- a/spec/factories/tube_rack_factories.rb +++ b/spec/factories/tube_rack_factories.rb @@ -13,13 +13,16 @@ # Overide the purpose name purpose_name { 'example-purpose' } - # Overive the purpose uuid + # Overide the purpose uuid purpose_uuid { 'example-purpose-uuid' } - # The plate purpose - purpose { create :v2_purpose, name: purpose_name, uuid: purpose_uuid } + # The tube rack purpose + purpose { create :v2_tube_rack_purpose, name: purpose_name, uuid: purpose_uuid } tubes { {} } + # The parent assets + parents { [] } + racked_tubes do tubes.map { |coordinate, tube| create :racked_tube, coordinate: coordinate, tube: tube, tube_rack: instance } end @@ -39,6 +42,8 @@ Sequencescape::Api::V2::TubeRack.associations.each do |association| tube_rack._cached_relationship(association.attr_name) { evaluator.send(association.attr_name) } end + + tube_rack._cached_relationship(:parents) { evaluator.parents } end end diff --git a/spec/models/labware_creators/plate_split_to_tube_racks_spec.rb b/spec/models/labware_creators/plate_split_to_tube_racks_spec.rb index 5410fe862..35f86f042 100644 --- a/spec/models/labware_creators/plate_split_to_tube_racks_spec.rb +++ b/spec/models/labware_creators/plate_split_to_tube_racks_spec.rb @@ -378,10 +378,12 @@ def expect_specific_tube_rack_creation(child_tube_racks, child_tube_rack_attribu end describe '#redirection_target' do - before { stub_v2_plate(parent_plate, stub_search: false, custom_includes: plate_includes) } + let(:child_tube_racks) { { described_class::SPR_TUBE_RACK_NAME => contingency_tube_rack } } + + before { allow(subject).to receive(:child_tube_racks).and_return(child_tube_racks) } it 'returns the parent object' do - expect(subject.redirection_target).to eq(parent_plate) + expect(subject.redirection_target).to eq(contingency_tube_rack) end end diff --git a/spec/models/robots/plate_to_tube_racks_robot_spec.rb b/spec/models/robots/plate_to_tube_racks_robot_spec.rb index c264a5240..57fd39203 100644 --- a/spec/models/robots/plate_to_tube_racks_robot_spec.rb +++ b/spec/models/robots/plate_to_tube_racks_robot_spec.rb @@ -11,24 +11,8 @@ let(:user_uuid) { 'user_uuid' } # tube rack barcodes - let(:tube_rack1_barcode) { 'TR00000001' } - let(:tube_rack2_barcode) { 'TR00000002' } - - # tube metadata - let(:tube1_metadata) { { 'tube_rack_barcode' => tube_rack1_barcode, 'tube_rack_position' => 'A1' } } - let(:tube2_metadata) { { 'tube_rack_barcode' => tube_rack1_barcode, 'tube_rack_position' => 'B1' } } - let(:tube3_metadata) { { 'tube_rack_barcode' => tube_rack1_barcode, 'tube_rack_position' => 'C1' } } - let(:tube4_metadata) { { 'tube_rack_barcode' => tube_rack2_barcode, 'tube_rack_position' => 'A1' } } - let(:tube5_metadata) { { 'tube_rack_barcode' => tube_rack2_barcode, 'tube_rack_position' => 'B1' } } - let(:tube6_metadata) { { 'tube_rack_barcode' => tube_rack2_barcode, 'tube_rack_position' => 'C1' } } - - # tube custom metadata collections - let(:tube1_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube1_metadata) } - let(:tube2_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube2_metadata) } - let(:tube3_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube3_metadata) } - let(:tube4_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube4_metadata) } - let(:tube5_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube5_metadata) } - let(:tube6_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube6_metadata) } + let(:tube_rack1_barcode) { 'TR1F' } + let(:tube_rack2_barcode) { 'TR2G' } # tube uuids let(:tube1_uuid) { 'tube1_uuid' } @@ -128,6 +112,74 @@ ) end + # tube rack purpose uuids + let(:tube_rack1_purpose_uuid) { 'tube_rack1_purpose_uuid' } + let(:tube_rack2_purpose_uuid) { 'tube_rack2_purpose_uuid' } + + # tube rack purpose names + let(:tube_rack1_purpose_name) { 'tube_rack1_purpose_name' } + let(:tube_rack2_purpose_name) { 'tube_rack2_purpose_name' } + + # tube rack purposes + let(:tube_rack1_purpose) do + create(:v2_tube_rack_purpose, name: tube_rack1_purpose_name, uuid: tube_rack1_purpose_uuid) + end + let(:tube_rack2_purpose) do + create(:v2_tube_rack_purpose, name: tube_rack2_purpose_name, uuid: tube_rack2_purpose_uuid) + end + + # tube rack uuids + let(:tube_rack1_uuid) { 'tube_rack1_uuid' } + let(:tube_rack2_uuid) { 'tube_rack2_uuid' } + + # tube racks + let!(:tube_rack1) do + create( + :tube_rack, + purpose: tube_rack1_purpose, + barcode_number: 1, + barcode_prefix: 'TR', + uuid: tube_rack1_uuid, + tubes: { + A1: tube1, + B1: tube2, + C1: tube3 + }, + parents: [plate] + ) + end + let!(:tube_rack2) do + create( + :tube_rack, + purpose: tube_rack2_purpose, + barcode_number: 2, + barcode_prefix: 'TR', + uuid: tube_rack2_uuid, + tubes: { + A1: tube4, + B1: tube5, + C1: tube6 + }, + parents: [plate] + ) + end + + # tube metadata + let(:tube1_metadata) { { 'tube_rack_barcode' => tube_rack1_barcode, 'tube_rack_position' => 'A1' } } + let(:tube2_metadata) { { 'tube_rack_barcode' => tube_rack1_barcode, 'tube_rack_position' => 'B1' } } + let(:tube3_metadata) { { 'tube_rack_barcode' => tube_rack1_barcode, 'tube_rack_position' => 'C1' } } + let(:tube4_metadata) { { 'tube_rack_barcode' => tube_rack2_barcode, 'tube_rack_position' => 'A1' } } + let(:tube5_metadata) { { 'tube_rack_barcode' => tube_rack2_barcode, 'tube_rack_position' => 'B1' } } + let(:tube6_metadata) { { 'tube_rack_barcode' => tube_rack2_barcode, 'tube_rack_position' => 'C1' } } + + # tube custom metadata collections + let(:tube1_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube1_metadata) } + let(:tube2_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube2_metadata) } + let(:tube3_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube3_metadata) } + let(:tube4_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube4_metadata) } + let(:tube5_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube5_metadata) } + let(:tube6_custom_metadatum_collection) { create(:custom_metadatum_collection, metadata: tube6_metadata) } + # wells let(:well1) { create(:v2_well, location: 'A1', downstream_tubes: [tube1, tube4]) } let(:well2) { create(:v2_well, location: 'B1', downstream_tubes: [tube2, tube5]) } @@ -159,15 +211,16 @@ let(:bed3_barcode) { 'bed3_barcode' } # bed purposes: same as plate and tube-rack purposes by default - let(:config_plate_purpose) { 'plate_purpose_name' } - let(:config_tube_purpose1) { 'tube_purpose1_name' } - let(:config_tube_purpose2) { 'tube_purpose2_name' } + let(:config_plate_purpose) { plate_purpose_name } + let(:config_tube_rack1_purpose) { tube_rack1_purpose_name } + let(:config_tube_rack2_purpose) { tube_rack2_purpose_name } let(:robot_name) { 'robot_name' } let(:robot_config) do { :name => robot_name, + :verify_robot => false, :beds => { bed1_barcode => { purpose: config_plate_purpose, @@ -175,13 +228,13 @@ label: 'Bed 1' }, bed2_barcode => { - purpose: config_tube_purpose1, + purpose: config_tube_rack1_purpose, states: ['pending'], label: 'Bed 2', target_state: 'passed' }, bed3_barcode => { - purpose: config_tube_purpose2, + purpose: config_tube_rack2_purpose, states: ['pending'], label: 'Bed 3', target_state: 'passed' @@ -211,12 +264,21 @@ end before do - # Stub robot requests to the Sequencescape API to look up plate by - # barcode. It returns the plate with its wells and downstream tubes. - includes = 'purpose,wells,wells.downstream_tubes,wells.downstream_tubes.custom_metadatum_collection' - bed_plate_lookup_with_barcode(plate.barcode.human, [plate], includes) - bed_plate_lookup_with_barcode(tube_rack1_barcode, [], includes) - bed_plate_lookup_with_barcode(tube_rack2_barcode, [], includes) + # Stub robot request to the Sequencescape API to look up the parent plate + plate_includes = described_class::PLATE_INCLUDES + bed_plate_lookup_with_barcode(plate.barcode.human, [plate], plate_includes) + + # Stub robot requests to the Sequencescape API to look up tube-racks as if plates, should return empty + bed_plate_lookup_with_barcode(tube_rack1_barcode, [], plate_includes) + bed_plate_lookup_with_barcode(tube_rack2_barcode, [], plate_includes) + + # Stub robot requests to the Sequencescape API to look up tube-racks + tube_rack_includes = Sequencescape::Api::V2::TubeRack::DEFAULT_TUBE_RACK_INCLUDES + bed_tube_rack_lookup_with_uuid(tube_rack1.uuid, [tube_rack1], tube_rack_includes) if tube_rack1 + bed_tube_rack_lookup_with_uuid(tube_rack2.uuid, [tube_rack2], tube_rack_includes) + + # Set up children of plate + allow(plate).to receive(:children).and_return([tube_rack1, tube_rack2]) end describe '#verify' do @@ -260,16 +322,9 @@ it { is_expected.not_to be_valid } it 'has correct error messages' do - # The following errors are expected because we could not find the - # plate, we do not know anything about the tube-racks, and we had to - # remove the beds from the robot. - errors = [ - 'Bed 1: should not be empty.', - 'Bed 1: should have children.', - "#{bed2_barcode} does not appear to be a valid bed barcode.", - "#{bed3_barcode} does not appear to be a valid bed barcode." - ] - errors.each { |error| expect(subject.message).to include(error) } + # The following errors are expected because we could not find the plate. + expected_errors = ['Bed 1: should not be empty.', 'Bed 1: should have children.'] + expected_errors.each { |error| expect(subject.message).to include(error) } end end @@ -291,12 +346,13 @@ let(:scanned_layout) { { bed1_barcode => [plate.human_barcode] } } it { is_expected.not_to be_valid } + # code knows by comparing to the labware store which specific tube racks are missing it 'has correct error messages' do - errors = [ + expected_errors = [ "Bed 2: Was expected to contain labware barcode #{tube_rack1_barcode} but nothing was scanned (empty).", "Bed 3: Was expected to contain labware barcode #{tube_rack2_barcode} but nothing was scanned (empty)." ] - errors.each { |error| expect(subject.message).to include(error) } + expected_errors.each { |error| expect(subject.message).to include(error) } end end @@ -323,8 +379,9 @@ end context 'with a plate that has an incorrect purpose' do - # The plate has a purpose that does not match the bed purpose. + # The plate has a purpose that does not match the expected bed purpose. let(:plate_purpose_name) { 'incorrect_purpose' } + let(:config_plate_purpose) { 'plate_purpose_name' } it { is_expected.not_to be_valid } @@ -346,6 +403,10 @@ let(:well2) { create(:v2_well, location: 'B1', downstream_tubes: [tube5]) } let(:well3) { create(:v2_well, location: 'C1', downstream_tubes: [tube6]) } + let(:tube_rack1) { nil } + + before { allow(plate).to receive(:children).and_return([tube_rack2]) } + context 'with a valid scanned layout' do let(:scanned_layout) { { bed1_barcode => [plate.human_barcode], bed3_barcode => [tube_rack2_barcode] } } diff --git a/spec/support/api_url_helper.rb b/spec/support/api_url_helper.rb index 8a5031fed..7da65f2f4 100644 --- a/spec/support/api_url_helper.rb +++ b/spec/support/api_url_helper.rb @@ -300,8 +300,8 @@ def stub_v2_tube_rack(tube_rack, stub_search: true, custom_query: nil, custom_in ) end - arguments = [{ uuid: labware.uuid }] - allow(Sequencescape::Api::V2::TubeRack).to receive(:find).with(*arguments).and_return([labware]) + arguments = [{ uuid: tube_rack.uuid }] + allow(Sequencescape::Api::V2::TubeRack).to receive(:find).with(*arguments).and_return([tube_rack]) end # rubocop:enable Metrics/AbcSize diff --git a/spec/support/robot_helpers.rb b/spec/support/robot_helpers.rb index 1b86fd797..11ddc536f 100644 --- a/spec/support/robot_helpers.rb +++ b/spec/support/robot_helpers.rb @@ -21,4 +21,14 @@ def bed_plate_lookup_with_barcode(barcode, result, includes = %i[purpose parents result ) end + + def bed_tube_rack_lookup_with_uuid( + uuid, + result, + includes = Sequencescape::Api::V2::TubeRack::DEFAULT_TUBE_RACK_INCLUDES + ) + allow(Sequencescape::Api::V2::TubeRack).to receive(:find_all).with({ uuid: Array(uuid) }, includes:).and_return( + result + ) + end end