diff --git a/app/helpers/submissions_helper.rb b/app/helpers/submissions_helper.rb index 7e12703c8e..49bcc6e0b8 100644 --- a/app/helpers/submissions_helper.rb +++ b/app/helpers/submissions_helper.rb @@ -139,7 +139,8 @@ def submission_link(submission, options) def field_selection_tag(request_options, field_info, name_format, enforce_required) select_tag( name_format % field_info.key, - options_for_select(field_info.selection.map(&:to_s), request_options[field_info.key]), + options_for_select(field_info.selection.map(&:to_s), + request_options[field_info.key].presence || field_info.default_value), class: 'custom-select', required: enforce_required && field_info.required, read_only: field_info.selection.size == 1 diff --git a/app/models/bulk_submission.rb b/app/models/bulk_submission.rb index 170ca24bc5..0f29773072 100644 --- a/app/models/bulk_submission.rb +++ b/app/models/bulk_submission.rb @@ -253,7 +253,8 @@ def process # rubocop:todo Metrics/CyclomaticComplexity 'scrna core cells per chip well', '% phix requested', 'low diversity', - 'ot recipe' + 'ot recipe', + 'wafer size' ].freeze ALIAS_FIELDS = { 'plate barcode' => 'barcode', 'tube barcode' => 'barcode' }.freeze @@ -360,7 +361,8 @@ def extract_request_options(details) ['scrna core cells per chip well', 'cells_per_chip_well'], ['% phix requested', 'percent_phix_requested'], ['low diversity', 'low_diversity'], - ['ot recipe', 'ot_recipe'] + ['ot recipe', 'ot_recipe'], + ['wafer size', 'wafer_size'] ].each do |source_key, target_key| assign_value_if_source_present(details, source_key, request_options, target_key) end diff --git a/app/models/ultima_sequencing_pipeline.rb b/app/models/ultima_sequencing_pipeline.rb index 3a28998d3b..639c7ee349 100644 --- a/app/models/ultima_sequencing_pipeline.rb +++ b/app/models/ultima_sequencing_pipeline.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Specialized sequencing pipeline for Ultima +# Specialized sequencing pipeline for Ultima UG100 class UltimaSequencingPipeline < SequencingPipeline def ot_recipe_consistent_for_batch?(batch) ot_recipe_list = batch.requests.filter_map { |request| request.request_metadata.ot_recipe } diff --git a/app/models/ultima_ug200_sequencing_pipeline.rb b/app/models/ultima_ug200_sequencing_pipeline.rb new file mode 100644 index 0000000000..896f4e7696 --- /dev/null +++ b/app/models/ultima_ug200_sequencing_pipeline.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Specialized sequencing pipeline for Ultima UG200 +class UltimaUG200SequencingPipeline < UltimaSequencingPipeline + def wafer_size_consistent_for_batch?(batch) + wafer_size_list = batch.requests.filter_map { |request| request.request_metadata.wafer_size } + + # There are some requests that don't have the wafer_size attribute + return false if wafer_size_list.size != batch.requests.size + + (wafer_size_list.uniq.size == 1) + end +end diff --git a/app/models/ultima_ug200_sequencing_request.rb b/app/models/ultima_ug200_sequencing_request.rb new file mode 100644 index 0000000000..0bed36c602 --- /dev/null +++ b/app/models/ultima_ug200_sequencing_request.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +# Request class specific to the Ultima UG200 sequencing platform. +# Includes wafer size and OT recipe. +class UltimaUG200SequencingRequest < SequencingRequest + include Api::Messages::UseqWaferIo::LaneExtensions + + FREE = 'Free' + FLEX = 'Flex' + OT_RECIPE_OPTIONS = [FREE, FLEX].freeze + + has_metadata as: Request do + # Defining the sequencing request metadata here again, as 'has_metadata' + # does not automatically append these custom attributes to the request. + # + # The has_metadata call dynamically defines an inner Metadata class and + # takes the attributes from the block and adds them to the Metadata class. + # There is an assumption that the inner Metadata class is available in a + # sequencing request class defintion. Calling has_metadata again does not + # inherit the attributes given in the block supplied in the superclass. + # They need to be supplied again for this class for a proper inner Metadata + # class definition. In a future refactoring these attributes can be moved a + # class attribute and subclasses can merge its own attibutes to that. A + # common method can set up the inner Metadata class in the subclasses. + custom_attribute(:fragment_size_required_from, integer: true, minimum: 1) + custom_attribute(:fragment_size_required_to, integer: true, minimum: 1) + + custom_attribute(:ot_recipe, default: FREE, in: OT_RECIPE_OPTIONS, required: true) + enum :ot_recipe, { Free: 0, Flex: 1 } + custom_attribute(:wafer_size, default: '10TB', validator: true, required: true, selection: true) + end + + # Delegate to request_metadata so the attributes are visible to the validator in the RSpec tests. + # This delegation has no real effect outside of the tests. + delegate :wafer_size, :ot_recipe, to: :request_metadata + + # Generates unique wafer ID, concatenation of batch_for_opentrons, + # id_pool_lims, and request_order. + # @return [String] unique wafer ID for LIMS + def id_wafer_lims + "#{batch.id}_#{source_labware.human_barcode}_#{position}" + end +end diff --git a/app/validators/ultima_ug200_validator.rb b/app/validators/ultima_ug200_validator.rb new file mode 100644 index 0000000000..ae66f898ad --- /dev/null +++ b/app/validators/ultima_ug200_validator.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +class UltimaUG200Validator < UltimaValidator + WAFER_SIZE_CONSISTENT_MSG = 'Wafer size must be the same for both requests.' + + # Used in _pipeline_limit.html to display custom validation warnings + def self.validation_info + 'Wafer Size and OT Recipe must be the same for both requests.' + end + + # Validates that a batch contains the two requests. + def validate(record) + validate_exactly_two_requests(record) + requests_have_same_ot_recipe(record) + requests_have_same_wafer_size(record) + end + + private + + def requests_have_same_wafer_size(record) + return if record.pipeline.wafer_size_consistent_for_batch?(record) + + record.errors.add(:base, WAFER_SIZE_CONSISTENT_MSG) + end +end diff --git a/app/views/bulk_submissions/_guidance.html.erb b/app/views/bulk_submissions/_guidance.html.erb index 6532b2d383..4706dfc832 100644 --- a/app/views/bulk_submissions/_guidance.html.erb +++ b/app/views/bulk_submissions/_guidance.html.erb @@ -47,4 +47,5 @@ used to split a submission up into orders, and as a result must have the same re
% PhiX requested
Required percentage of PhiX needed.
Low Diversity
Low Diversity value being "Yes" or "No"
OT Recipe
OT (OpenTron liquid handler) Recipe value being "Free" or "Flex"
+
Wafer Size
Wafer size value being "5TB", "10TB" or "20TB"
diff --git a/config/bulk_submission_excel/columns.yml b/config/bulk_submission_excel/columns.yml index 02de62cb55..8e265391cb 100644 --- a/config/bulk_submission_excel/columns.yml +++ b/config/bulk_submission_excel/columns.yml @@ -367,3 +367,22 @@ ot_recipe: range_name: :ot_recipe conditional_formattings: empty_cell: +wafer_size: + heading: "Wafer Size" + unlocked: true + attribute: :wafer_size + validation: + options: + type: :list + formula1: "$A$1:$A$2" + allowBlank: false + showInputMessage: true + showErrorMessage: true + errorStyle: :stop + errorTitle: "Wafer Size" + error: "You must enter a value from the list provided." + promptTitle: "Wafer Size" + prompt: "Select a value type from the approved list" + range_name: :wafer_size + conditional_formattings: + empty_cell: diff --git a/config/bulk_submission_excel/ranges.yml b/config/bulk_submission_excel/ranges.yml index a1c091015a..b211a116cb 100644 --- a/config/bulk_submission_excel/ranges.yml +++ b/config/bulk_submission_excel/ranges.yml @@ -27,3 +27,8 @@ ot_recipe: options: - "Free" - "Flex" +wafer_size: + options: + - "5TB" + - "10TB" + - "20TB" diff --git a/config/default_records/descriptors/005_ultima_ug200_descriptors.wip.yml b/config/default_records/descriptors/005_ultima_ug200_descriptors.wip.yml new file mode 100644 index 0000000000..df4f31fa60 --- /dev/null +++ b/config/default_records/descriptors/005_ultima_ug200_descriptors.wip.yml @@ -0,0 +1,86 @@ +--- +# Section names must be unique inside the record loader folder. +"OTR carrier Lot # UG200": + name: "OTR carrier Lot #" + task: Opentrons + workflow: Ultima UG200 + kind: Text + required: false + sorter: 0 +OTR carrier expiry UG200: + name: OTR carrier expiry + task: Opentrons + workflow: Ultima UG200 + kind: Date + required: false + sorter: 1 +"Reaction Mix 7 Lot # UG200": + name: "Reaction Mix 7 Lot #" + task: Opentrons + workflow: Ultima UG200 + kind: Text + required: false + sorter: 2 +Reaction Mix 7 expiry UG200: + name: Reaction Mix 7 expiry + task: Opentrons + workflow: Ultima UG200 + kind: Date + required: false + sorter: 3 +"NFW Lot # UG200": + name: "NFW Lot #" + task: Opentrons + workflow: Ultima UG200 + kind: Text + required: false + sorter: 4 +NFW expiry UG200: + name: NFW expiry + task: Opentrons + workflow: Ultima UG200 + kind: Date + required: false + sorter: 5 +"Oil Lot # UG200": + name: "Oil Lot #" + task: Opentrons + workflow: Ultima UG200 + kind: Text + required: false + sorter: 6 +Oil expiry UG200: + name: Oil expiry + task: Opentrons + workflow: Ultima UG200 + kind: Date + required: false + sorter: 7 +Pipette carousel UG200: + name: Pipette carousel + task: Opentrons + workflow: Ultima UG200 + kind: Text + required: false + sorter: 8 +Opentrons Inst. Name UG200: + name: Opentrons Inst. Name + task: Opentrons + workflow: Ultima UG200 + kind: Text + required: true + sorter: 9 +Assign Control Bead Tube UG200: + name: Assign Control Bead Tube + task: Amp + workflow: Ultima UG200 + kind: Text + required: false + sorter: 0 +UG AMP Inst. Name UG200: + name: UG AMP Inst. Name + task: Amp + workflow: Ultima UG200 + kind: Text + required: true + sorter: 1 diff --git a/config/default_records/pipeline_request_information_types/004_ultima_ug200_pipeline_request_information_types.wip.yml b/config/default_records/pipeline_request_information_types/004_ultima_ug200_pipeline_request_information_types.wip.yml new file mode 100644 index 0000000000..76a81c2576 --- /dev/null +++ b/config/default_records/pipeline_request_information_types/004_ultima_ug200_pipeline_request_information_types.wip.yml @@ -0,0 +1,16 @@ +--- +FragmentSizeRequiredFromInformationTypeForUltimaUG200: + pipeline_name: Ultima UG200 + request_information_type_key: fragment_size_required_from + +FragmentSizeRequiredToInformationTypeForUltimaUG200: + pipeline_name: Ultima UG200 + request_information_type_key: fragment_size_required_to + +WaferSizeInformationTypeForUltimaUG200: + pipeline_name: Ultima UG200 + request_information_type_key: wafer_size + +OTRecipeInformationTypeForUltimaUG200: + pipeline_name: Ultima UG200 + request_information_type_key: ot_recipe diff --git a/config/default_records/pipelines/004_ultima_ug200_pipelines.wip.yml b/config/default_records/pipelines/004_ultima_ug200_pipelines.wip.yml new file mode 100644 index 0000000000..21b128df05 --- /dev/null +++ b/config/default_records/pipelines/004_ultima_ug200_pipelines.wip.yml @@ -0,0 +1,17 @@ +--- +Ultima UG200: + name: Ultima UG200 Sequencing + sti_type: UltimaUG200SequencingPipeline + validator_class_name: UltimaUG200Validator + sorter: 10 + max_size: 2 + summary: 1 + externally_managed: 0 + group_name: Sequencing + control_request_type_id: 0 + min_size: 1 + request_type_keys: + - ultima_ug200_sequencing + workflow: + name: Ultima UG200 + item_limit: 2 diff --git a/config/default_records/plate_purposes/017_ultima_ug200_purposes.wip.yml b/config/default_records/plate_purposes/017_ultima_ug200_purposes.wip.yml new file mode 100644 index 0000000000..682ce88e23 --- /dev/null +++ b/config/default_records/plate_purposes/017_ultima_ug200_purposes.wip.yml @@ -0,0 +1,9 @@ +--- +# 96 well input plates +UPF2 Cherrypicked: + type: PlatePurpose::Input + stock_plate: true + default_state: passed + cherrypickable_target: true + target_type: Plate + size: 96 diff --git a/config/default_records/request_information_types/004_ultima_ug200_request_information_types.wip.yml b/config/default_records/request_information_types/004_ultima_ug200_request_information_types.wip.yml new file mode 100644 index 0000000000..bde23fdb31 --- /dev/null +++ b/config/default_records/request_information_types/004_ultima_ug200_request_information_types.wip.yml @@ -0,0 +1,7 @@ +--- +wafer_size: + name: Wafer size + key: wafer_size + label: Wafer size + width: 5 + hide_in_inbox: 0 diff --git a/config/default_records/request_type_validators/003_ultima_ug200_request_type_validators.wip.yml b/config/default_records/request_type_validators/003_ultima_ug200_request_type_validators.wip.yml new file mode 100644 index 0000000000..028e593253 --- /dev/null +++ b/config/default_records/request_type_validators/003_ultima_ug200_request_type_validators.wip.yml @@ -0,0 +1,8 @@ +--- +WaferSizeRequestedUltimaUG200Sequencing: + request_type_key: ultima_ug200_sequencing + request_option: wafer_size + valid_options: + - 5TB + - 10TB + - 20TB diff --git a/config/default_records/request_types/026_ultima_ug200_request_types.wip.yml b/config/default_records/request_types/026_ultima_ug200_request_types.wip.yml new file mode 100644 index 0000000000..bc5977f439 --- /dev/null +++ b/config/default_records/request_types/026_ultima_ug200_request_types.wip.yml @@ -0,0 +1,31 @@ +# Request types for Ultima UG200 sequencing platform. +--- +limber_ultima_ug200_htp_pcr_free: + name: Limber Ultima UG200 HTP PCR Free + order: 1 + request_class_name: IlluminaHtp::Requests::StdLibraryRequest + asset_type: Well + for_multiplexing: false + billable: true + product_line_name: Ultima # same as UG100 + acceptable_purposes: + - UPF2 Cherrypicked + library_types: + - Ultima High Throughput PCR Free 96 # same as UG100 +limber_multiplexing_ultima_ug200: + name: Limber Multiplexing Ultima UG200 + asset_type: Well + order: 2 + request_class_name: Request::Multiplexing + for_multiplexing: true + product_line_name: Ultima # same as UG100 + target_purpose_name: UPF2 EqVol Norm +ultima_ug200_sequencing: + name: Ultima UG200 sequencing + asset_type: LibraryTube + order: 2 + initial_state: pending + billable: true + product_line_name: Ultima # same as UG100 + request_class_name: UltimaUG200SequencingRequest + request_purpose: standard diff --git a/config/default_records/submission_templates/021_ultima_ug200_submission_templates.wip.yml b/config/default_records/submission_templates/021_ultima_ug200_submission_templates.wip.yml new file mode 100644 index 0000000000..5c534184d0 --- /dev/null +++ b/config/default_records/submission_templates/021_ultima_ug200_submission_templates.wip.yml @@ -0,0 +1,18 @@ +# This submission template is associated with the Ultima PCR Free Library preparation pipeline +# and the Ultima UG200 sequencing platform. +--- +Limber-Htp - Ultima UG200 PCR Free - Ultima UG200 sequencing: + submission_class_name: "LinearSubmission" + related_records: + request_type_keys: + ["limber_ultima_ug200_htp_pcr_free", "limber_multiplexing_ultima_ug200", "ultima_ug200_sequencing"] + order_role: PCR Free + product_line_name: Ultima # same as UG100 + product_catalogue_name: GenericNoPCR +Limber-Htp - Ultima UG200 PCR Free - Ultima UG200 sequencing Automated: + submission_class_name: "LinearSubmission" + related_records: + request_type_keys: ["ultima_ug200_sequencing"] + order_role: PCR Free + product_line_name: Ultima # same as UG100 + product_catalogue_name: GenericNoPCR diff --git a/config/default_records/tasks/005_ultima_ug200_tasks.wip.yml b/config/default_records/tasks/005_ultima_ug200_tasks.wip.yml new file mode 100644 index 0000000000..a72c05029d --- /dev/null +++ b/config/default_records/tasks/005_ultima_ug200_tasks.wip.yml @@ -0,0 +1,14 @@ +--- +# Section names must be unique inside the record loader folder. +Opentrons UG200: + name: Opentrons + workflow: Ultima UG200 + sorted: 0 + lab_activity: true + sti_type: SetDescriptorsTask +Amp UG200: + name: Amp + workflow: Ultima UG200 + sorted: 1 + lab_activity: true + sti_type: SetDescriptorsTask diff --git a/config/default_records/tube_purposes/011_ultima_ug200_tube_purposes.wip.yml b/config/default_records/tube_purposes/011_ultima_ug200_tube_purposes.wip.yml new file mode 100644 index 0000000000..3cb41b6b30 --- /dev/null +++ b/config/default_records/tube_purposes/011_ultima_ug200_tube_purposes.wip.yml @@ -0,0 +1,6 @@ +--- +UPF2 EqVol Norm: + # Even though this is Ultima, not Illumina, the MX tube has the same behaviour + type: IlluminaHtp::MxTubePurpose + target_type: MultiplexedLibraryTube + stock_plate: false diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 1b0cac6328..d3ad112180 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -16,6 +16,7 @@ inflect.acronym 'ENA' # European Nucleotide Archive inflect.acronym 'HTTP' # HyperText Transfer Protocol inflect.acronym 'EBI' # European Bioinformatics Institute + inflect.acronym 'UG200' # Ultima UG200 end # These inflection rules are supported but not enabled by default: diff --git a/config/locales/metadata/en.yml b/config/locales/metadata/en.yml index 08f1fbd67e..da8fa0683b 100644 --- a/config/locales/metadata/en.yml +++ b/config/locales/metadata/en.yml @@ -79,6 +79,9 @@ en: ot_recipe: label: OT Recipe + wafer_size: + label: Wafer Size + library_creation_request: <<: *REQUEST sequencing_request: @@ -107,6 +110,9 @@ en: ultima_sequencing_request: <<: *REQUEST + ultima_ug200_sequencing_request: + <<: *REQUEST + pulldown: requests: wgs_library_request: diff --git a/db/migrate/20260324112536_add_wafer_size_to_request_metadata.rb b/db/migrate/20260324112536_add_wafer_size_to_request_metadata.rb new file mode 100644 index 0000000000..f1e811d8f0 --- /dev/null +++ b/db/migrate/20260324112536_add_wafer_size_to_request_metadata.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# This migration adds a wafer_size column to the request_metadata table, which is used to store +# the wafer size for Ultima sequencing requests. This is stored as a string, with possible +# values of 5TB, 10TB, and 20TB at time of writing. +class AddWaferSizeToRequestMetadata < ActiveRecord::Migration[7.1] + def change + add_column :request_metadata, :wafer_size, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 34cce1cefe..4f5e30ff8a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_03_17_142326) do +ActiveRecord::Schema[7.2].define(version: 2026_03_24_112536) do create_table "accession_sample_statuses", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.integer "sample_id", null: false t.string "status", null: false @@ -1191,6 +1191,7 @@ t.boolean "low_diversity" t.integer "percent_phix_requested" t.integer "ot_recipe" + t.string "wafer_size" t.index ["request_id"], name: "index_request_metadata_on_request_id" end diff --git a/spec/data/bulk_submission_excel/columns.yml b/spec/data/bulk_submission_excel/columns.yml index 03280783ee..ab5bd8739e 100644 --- a/spec/data/bulk_submission_excel/columns.yml +++ b/spec/data/bulk_submission_excel/columns.yml @@ -315,3 +315,22 @@ ot_recipe: range_name: :ot_recipe conditional_formattings: empty_cell: +wafer_size: + heading: "Wafer Size" + unlocked: true + attribute: :wafer_size + validation: + options: + type: :list + formula1: "$A$1:$A$2" + allowBlank: false + showInputMessage: true + showErrorMessage: true + errorStyle: :stop + errorTitle: "Wafer Size" + error: "You must enter a value from the list provided." + promptTitle: "Wafer Size" + prompt: "Select a value type from the approved list" + range_name: :wafer_size + conditional_formattings: + empty_cell: diff --git a/spec/data/bulk_submission_excel/ranges.yml b/spec/data/bulk_submission_excel/ranges.yml index a1c091015a..b211a116cb 100644 --- a/spec/data/bulk_submission_excel/ranges.yml +++ b/spec/data/bulk_submission_excel/ranges.yml @@ -27,3 +27,8 @@ ot_recipe: options: - "Free" - "Flex" +wafer_size: + options: + - "5TB" + - "10TB" + - "20TB" diff --git a/spec/factories/request_factories.rb b/spec/factories/request_factories.rb index 5a012985fc..ad180e5d76 100644 --- a/spec/factories/request_factories.rb +++ b/spec/factories/request_factories.rb @@ -124,6 +124,20 @@ end end + factory(:ultima_ug200_sequencing_request) do + request_type factory: %i[ultima_ug200_sequencing] + request_purpose { :standard } + sti_type { 'UltimaUG200SequencingRequest' } + request_metadata_attributes do + { + fragment_size_required_from: 150, + fragment_size_required_to: 400, + ot_recipe: 'Free', + wafer_size: '10TB' + } + end + end + factory(:library_creation_request, parent: :request, class: 'LibraryCreationRequest') do asset factory: %i[sample_tube] request_type factory: %i[library_creation_request_type] diff --git a/spec/factories/request_type_factories.rb b/spec/factories/request_type_factories.rb index 4d18e1eb3d..87b2fecfd4 100644 --- a/spec/factories/request_type_factories.rb +++ b/spec/factories/request_type_factories.rb @@ -139,6 +139,11 @@ request_class { UltimaSequencingRequest } end + factory :ultima_ug200_sequencing do + asset_type { 'LibraryTube' } + request_class { UltimaUG200SequencingRequest } + end + factory :miseq_sequencing_request_type do request_class { MiSeqSequencingRequest } asset_type { 'LibraryTube' } diff --git a/spec/helpers/submissions_helper_spec.rb b/spec/helpers/submissions_helper_spec.rb new file mode 100644 index 0000000000..5e5101c42c --- /dev/null +++ b/spec/helpers/submissions_helper_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe SubmissionsHelper do + describe '#field_input_tag' do + let(:field_info) do + double( + kind: 'Selection', + key: :ot_recipe, + selection: %w[Free Flex], + default_value: 'Free', + required: true + ) + end + + let(:wafer_size_field_info) do + double( + kind: 'Selection', + key: :wafer_size, + selection: %w[5TB 10TB], + default_value: '10TB', + required: true + ) + end + + it 'uses the default value for selection fields when no request option is set' do + html = helper.field_input_tag(field_info, values: {}, name_format: 'submission[order_params][%s]') + + expect(html).to include('') + end + + it 'uses the default value for selection fields when request option is blank' do + html = helper.field_input_tag(field_info, values: { ot_recipe: '' }, name_format: 'submission[order_params][%s]') + + expect(html).to include('') + end + + it 'uses the default wafer_size when request option is blank' do + html = helper.field_input_tag(wafer_size_field_info, + values: { wafer_size: '' }, + name_format: 'submission[order_params][%s]') + + expect(html).to include('') + end + end +end diff --git a/spec/models/ultima_sequencing_request_spec.rb b/spec/models/ultima_sequencing_request_spec.rb index d152679b4e..3cbaa4864f 100644 --- a/spec/models/ultima_sequencing_request_spec.rb +++ b/spec/models/ultima_sequencing_request_spec.rb @@ -33,7 +33,7 @@ end context 'when ot_recipe value is not assigned' do - it 'is invalid and displays required percent phix requested error message' do + it 'is invalid and displays required OT recipe error message' do request.request_metadata.ot_recipe = nil request.validate expect(request.errors[:'request_metadata.ot_recipe']).to include("can't be blank") diff --git a/spec/models/ultima_ug200_sequencing_pipeline_spec.rb b/spec/models/ultima_ug200_sequencing_pipeline_spec.rb new file mode 100644 index 0000000000..361bce75a5 --- /dev/null +++ b/spec/models/ultima_ug200_sequencing_pipeline_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe UltimaUG200SequencingPipeline do + let(:pipeline) do + described_class.new( + workflow: Workflow.new, + request_types: [create(:ultima_ug200_sequencing)] + ) + end + + describe '#ot_recipe_consistent_for_batch?' do + it 'returns true when all requests have the same ot_recipe' do + batch = pipeline.batches.build + r1 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { ot_recipe: 'Free' }) + r2 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { ot_recipe: 'Free' }) + batch.requests << [r1, r2] + + expect(pipeline.ot_recipe_consistent_for_batch?(batch)).to be true + end + + it 'returns false when requests have different ot_recipes' do + batch = pipeline.batches.build + req1 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { ot_recipe: 'Free' }) + req2 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { ot_recipe: 'Flex' }) + batch.requests << [req1, req2] + + expect(pipeline.ot_recipe_consistent_for_batch?(batch)).to be false + end + + it 'returns false when some requests are missing ot_recipe' do + batch = pipeline.batches.build + r1 = create(:sequencing_request, request_metadata_attributes: { ot_recipe: 'Free' }) + r2 = create(:sequencing_request, request_metadata_attributes: {}) # no ot_recipe + batch.requests << [r1, r2] + + expect(pipeline.ot_recipe_consistent_for_batch?(batch)).to be false + end + end + + describe '#wafer_size_consistent_for_batch?' do + it 'returns true when all requests have the same wafer_size' do + batch = pipeline.batches.build + r1 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { wafer_size: '10TB' }) + r2 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { wafer_size: '10TB' }) + batch.requests << [r1, r2] + + expect(pipeline.wafer_size_consistent_for_batch?(batch)).to be true + end + + it 'returns false when requests have different wafer_sizes' do + batch = pipeline.batches.build + req1 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { wafer_size: '5TB' }) + req2 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { wafer_size: '10TB' }) + batch.requests << [req1, req2] + + expect(pipeline.wafer_size_consistent_for_batch?(batch)).to be false + end + + # NB. Wafer size is a required field, so cannot be missing. Tested in the request spec. + end + + describe '#post_release_batch' do + let(:batch) { create(:batch) } + + it 'calls Messenger with UseqWaferIo template and useq_wafer root' do + allow(Messenger).to receive(:create!) + pipeline.post_release_batch(batch, create(:user)) + + expect(Messenger).to have_received(:create!).with( + hash_including(target: batch, template: 'UseqWaferIo', root: 'useq_wafer') + ) + end + end +end diff --git a/spec/models/ultima_ug200_sequencing_request_spec.rb b/spec/models/ultima_ug200_sequencing_request_spec.rb new file mode 100644 index 0000000000..a87bc38fd2 --- /dev/null +++ b/spec/models/ultima_ug200_sequencing_request_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe UltimaUG200SequencingRequest do + let(:request) { create(:ultima_ug200_sequencing_request) } + + describe 'Validations' do + context 'when all attributes are valid' do + it 'is valid' do + expect(request).to be_valid + end + end + + context 'when fragment_size_required_from is less than 1' do + it 'is invalid and displays fragment size from error message' do + request.request_metadata.fragment_size_required_from = 0 + request.validate + expect(request.errors[:'request_metadata.fragment_size_required_from']).to include( + 'must be greater than or equal to 1' + ) + end + end + + context 'when fragment_size_required_to is less than 1' do + it 'is invalid and displays fragment size to error message' do + request.request_metadata.fragment_size_required_to = 0 + request.validate + expect(request.errors[:'request_metadata.fragment_size_required_to']).to include( + 'must be greater than or equal to 1' + ) + end + end + + context 'when wafer_size value is not assigned' do + it 'is invalid and displays required wafer size error message' do + request.request_metadata.wafer_size = nil + request.validate + expect(request.errors[:'request_metadata.wafer_size']).to include("can't be blank") + end + end + + context 'when ot_recipe value is not assigned' do + it 'is invalid and displays required OT recipe error message' do + request.request_metadata.ot_recipe = nil + request.validate + expect(request.errors[:'request_metadata.ot_recipe']).to include("can't be blank") + end + end + end +end diff --git a/spec/validators/ultima_ug200_validator_spec.rb b/spec/validators/ultima_ug200_validator_spec.rb new file mode 100644 index 0000000000..34082981cb --- /dev/null +++ b/spec/validators/ultima_ug200_validator_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true +require 'rails_helper' + +describe UltimaUG200Validator do + describe '#validate' do + subject(:validator) { described_class.new } + + let(:pipeline) { UltimaUG200SequencingPipeline.new } + let(:batch) { create(:batch, pipeline:) } + let(:requests) { [request1, request2] } + let(:request1_metadata) { { ot_recipe: 'Free', wafer_size: '10TB' } } + let(:request2_metadata) { { ot_recipe: 'Free', wafer_size: '10TB' } } + let(:request1) { create(:ultima_sequencing_request, request_metadata_attributes: request1_metadata) } + let(:request2) { create(:ultima_sequencing_request, request_metadata_attributes: request2_metadata) } + + before do + batch.requests << requests + end + + context 'when batch contains two requests with the same OT Recipe' do + it 'is valid' do + validator.validate(batch) + expect(batch.errors[:base]).to be_empty + end + end + + context 'when batch contains two requests with different ot_recipe' do + let(:request2_metadata) { { ot_recipe: 'Flex', wafer_size: '10TB' } } + + it 'is invalid due to ot_recipe mismatch' do + validator.validate(batch) + expect(batch.errors[:base]).to include(described_class::OT_RECIPE_CONSISTENT_MSG) + end + end + + context 'when batch contains two requests with the same Wafer Size' do + it 'is valid' do + validator.validate(batch) + expect(batch.errors[:base]).not_to include(described_class::WAFER_SIZE_CONSISTENT_MSG) + end + end + + context 'when batch contains two requests with different wafer_size' do + let(:request1_metadata) { { ot_recipe: 'Free', wafer_size: '5TB' } } + + it 'is invalid due to wafer_size mismatch' do + validator.validate(batch) + expect(batch.errors[:base]).to include(described_class::WAFER_SIZE_CONSISTENT_MSG) + end + end + + context 'when batch contains a single request' do + let(:requests) { [request1] } + + it 'is invalid' do + validator.validate(batch) + expect(batch.errors[:base]).to include(described_class::TWO_REQUESTS_MSG) + end + end + end +end diff --git a/test/controllers/batches_controller_test.rb b/test/controllers/batches_controller_test.rb index a109e47d21..a7810bbefd 100644 --- a/test/controllers/batches_controller_test.rb +++ b/test/controllers/batches_controller_test.rb @@ -257,6 +257,9 @@ class BatchesControllerTest < ActionController::TestCase "request_group_#{@plate.id}_#{@submission.id}_size": '1', commit: 'Submit' } + + assert_redirected_to batch_path(assigns(:batch)) + assert_predicate assigns(:batch), :persisted? end end diff --git a/test/lib/label_printer/plate_to_tube_test.rb b/test/lib/label_printer/plate_to_tube_test.rb index 49bb24fbb4..6a289237cc 100644 --- a/test/lib/label_printer/plate_to_tube_test.rb +++ b/test/lib/label_printer/plate_to_tube_test.rb @@ -13,7 +13,12 @@ def setup # rubocop:todo Metrics/AbcSize @barcode1 = '1111' @asset_name = 'tube name' @tube1 = create(:sample_tube, :tube_barcode, barcode: barcode1, prefix: prefix, name: asset_name) - @sample_tubes = create_list(:sample_tube, 4, :tube_barcode) + @sample_tubes = [ + create(:sample_tube, :tube_barcode, barcode: '2222', prefix: prefix), + create(:sample_tube, :tube_barcode, barcode: '3333', prefix: prefix), + create(:sample_tube, :tube_barcode, barcode: '4444', prefix: prefix), + create(:sample_tube, :tube_barcode, barcode: '5555', prefix: prefix) + ] sample_tubes.unshift(tube1) options = { sample_tubes: } @tube_label = LabelPrinter::Label::PlateToTubes.new(options) diff --git a/test/unit/batch_test.rb b/test/unit/batch_test.rb index abfc223aef..e9d8ae10a5 100644 --- a/test/unit/batch_test.rb +++ b/test/unit/batch_test.rb @@ -647,7 +647,7 @@ class BatchTest < ActiveSupport::TestCase end should 'check that with the pipeline that the batch is valid' do - @batch.complete!(@user) + assert_nothing_raised { @batch.complete!(@user) } end end diff --git a/test/unit/event_test.rb b/test/unit/event_test.rb index 67e6a550e7..68fe62686c 100644 --- a/test/unit/event_test.rb +++ b/test/unit/event_test.rb @@ -18,7 +18,7 @@ class EventTest < ActiveSupport::TestCase setup { @event = Event.create(descriptor_key: '') } should 'be valid' do - @event.valid? + assert_predicate @event, :valid? end end end @@ -28,7 +28,7 @@ class EventTest < ActiveSupport::TestCase setup { @event = Event.create } should 'be valid' do - @event.valid? + assert_predicate @event, :valid? end end end diff --git a/test/unit/request_factory_test.rb b/test/unit/request_factory_test.rb index 5339e13433..538b6eaacc 100644 --- a/test/unit/request_factory_test.rb +++ b/test/unit/request_factory_test.rb @@ -42,7 +42,7 @@ class RequestcreateTest < ActiveSupport::TestCase setup { @project.update!(enforce_quotas: true) } should 'not fail' do - RequestFactory.copy_request(@request) + assert_nothing_raised { RequestFactory.copy_request(@request) } end end end diff --git a/test/unit/sequencing_qc_batch_test.rb b/test/unit/sequencing_qc_batch_test.rb index 89a123a9bb..eda3f8a081 100644 --- a/test/unit/sequencing_qc_batch_test.rb +++ b/test/unit/sequencing_qc_batch_test.rb @@ -38,6 +38,7 @@ class SequencingQcBatchTest < ActiveSupport::TestCase should 'do nothing if the next state is nil' do @batch.stubs(:qc_next_state).returns(nil) + @batch.expects(:update_attribute).never # no state update when there is no next state @batch.qc_submitted end end @@ -51,6 +52,7 @@ class SequencingQcBatchTest < ActiveSupport::TestCase should 'do nothing if the next state is nil' do @batch.stubs(:qc_next_state).returns(nil) + @batch.expects(:update_attribute).never # no state update when there is no next state @batch.qc_criteria_received end end @@ -64,6 +66,7 @@ class SequencingQcBatchTest < ActiveSupport::TestCase should 'do nothing if the next state is nil' do @batch.stubs(:qc_next_state).returns(nil) + @batch.expects(:update_attribute).never # no state update when there is no next state @batch.qc_complete end end diff --git a/test/unit/tasks/plate_transfer_task_test.rb b/test/unit/tasks/plate_transfer_task_test.rb index 9ed5ba55f8..669a0df93d 100644 --- a/test/unit/tasks/plate_transfer_task_test.rb +++ b/test/unit/tasks/plate_transfer_task_test.rb @@ -100,6 +100,7 @@ class PlateTransferTaskTest < ActiveSupport::TestCase end should 'find the existing plate' do + assert_equal 1, Plate.count - @plate_count end end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index 126e8414d0..081be0ac56 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -123,7 +123,7 @@ class UserTest < ActiveSupport::TestCase should 'be able to have one assigned' do code = 'code' - @user.swipecard_code = code + assert_nothing_raised { @user.swipecard_code = code } end end