From b714d7975d32914b2454ffbac8f2def5cbcafe8d Mon Sep 17 00:00:00 2001 From: Sarav Shah Date: Mon, 10 Sep 2018 12:47:27 -0700 Subject: [PATCH] bundle_context class to bundle_context AR Model When method by method and moved over methods in BundleContextTemp to BundleContext AR model. Included validations as well has methods that are used throughou the application. Wrote RSpect tests for the methods moved over, and left ticket numbers on outstanding work. --- app/models/bundle_context.rb | 83 ++++++++++++++++++++ config/projects/TEMPLATE.yaml | 1 - spec/models/bundle_context_spec.rb | 119 ++++++++++++++++++++++++++--- 3 files changed, 192 insertions(+), 11 deletions(-) diff --git a/app/models/bundle_context.rb b/app/models/bundle_context.rb index 647ba625..d7015fff 100644 --- a/app/models/bundle_context.rb +++ b/app/models/bundle_context.rb @@ -7,6 +7,11 @@ class BundleContext < ApplicationRecord validates :staging_style_symlink, inclusion: { in: [ true, false ] } validates :content_metadata_creation, presence: true, null: false + validate :verify_bundle_directory + validate :verify_content_metadata_creation + + after_initialize :normalize_bundle_dir + enum content_structure: { "simple_image_structure" => 0, "simple_book_structure" => 1, @@ -21,4 +26,82 @@ class BundleContext < ApplicationRecord "smpl_style" => 2 } + def staging_dir + '/dor/assembly' + end + + def normalize_bundle_dir + self[:bundle_dir].chomp("/") if bundle_dir + end + + def progress_log_file + Tempfile.new.path(id) #FIXME: (#78) + end + + def content_exclusion #FIXME: Delete everywhere in code (#227) + nil + end + + def file_attr + nil # FIXME can get rid of this (#228) + end + + def validate_files? + false #FIXME delete everwhere in code (#230) + end + + def content_tag_override? #TODO: find where this is used as a conditional and delete code that won't be executed (#231) + true + end + + def smpl_manifest + 'smpl_manifest.csv' + end + + def manifest + 'manifest.csv' + end + + def path_in_bundle(rel_path) + File.join(bundle_dir, rel_path) + end + + # TODO: BundleContext is not really a logical home for this util method + # load CSV allowing UTF-8 to pass through, deleting blank columns + # @param [String] filename + # @return [Array] + # @raise if file missing/unreadable + def self.import_csv(filename) + raise BundleUsageError, "CSV filename required" unless filename.present? + raise BundleUsageError, "Required file not found: #{filename}." unless File.readable?(filename) + file_contents = IO.read(filename).encode("utf-8", replace: nil) + csv = CSV.parse(file_contents, :headers => true) + csv.map { |row| row.to_hash.with_indifferent_access } + end + + # On first call, loads the manifest data, caches results + # @return [Array] + def manifest_rows + @manifest_rows ||= self.class.import_csv(path_in_bundle(manifest)) + end + + def manifest_cols + { + label: 'label', + source_id: 'sourceid', + object_container: 'object', #object referring to filename or foldername + druid: 'druid' + } + end + + private + + def verify_bundle_directory + return if errors.key?(:bundle_dir) + errors.add(:bundle_dir, "Bundle directory: #{bundle_dir} not found.") unless File.directory?(bundle_dir) + end + + def verify_content_metadata_creation + errors.add(:content_metadata_creation, "The SMPL manifest #{smpl_manifest} was not found in #{bundle_dir}.") if smpl_style? && !File.exist?(File.join(bundle_dir, smpl_manifest)) + end end diff --git a/config/projects/TEMPLATE.yaml b/config/projects/TEMPLATE.yaml index 5fe598aa..88a4412a 100644 --- a/config/projects/TEMPLATE.yaml +++ b/config/projects/TEMPLATE.yaml @@ -93,7 +93,6 @@ stageable_discovery: # 00/ # 1.tif # 2.tif - # 01/ # 1.jp2 # 2.jp2 # diff --git a/spec/models/bundle_context_spec.rb b/spec/models/bundle_context_spec.rb index 89849212..f2a1bb9c 100644 --- a/spec/models/bundle_context_spec.rb +++ b/spec/models/bundle_context_spec.rb @@ -1,15 +1,18 @@ -require 'rails_helper' - RSpec.describe BundleContext, type: :model do - context "validation" do - let(:user) { User.new(sunet_id: "Jdoe")} - subject(:bc) { BundleContext.new(project_name: "SmokeTest", - content_structure: 1, - bundle_dir: "spec/test_data/bundle_input_g", - staging_style_symlink: false, - content_metadata_creation: 1, - user: user) } + subject(:bc) do + BundleContext.new( + project_name: "SmokeTest", + content_structure: 1, + bundle_dir: "spec/test_data/bundle_input_g/", + staging_style_symlink: false, + content_metadata_creation: 1, + user: user + ) + end + let(:user) { User.new(sunet_id: "Jdoe") } + + context "validation" do it "is not valid unless it has all required attributes" do expect(BundleContext.new).not_to be_valid expect(bc).to be_valid @@ -61,10 +64,106 @@ end end + context "bundle_dir path does not exist" do + it "object does not pass validation" do + expect { bc.bundle_dir = 'does/not/exist' }.to change { bc.valid? }.to(false) + end + end + it { is_expected.to validate_presence_of(:project_name) } it { is_expected.to validate_presence_of(:content_structure) } it { is_expected.to validate_presence_of(:bundle_dir) } it { is_expected.to validate_presence_of(:content_metadata_creation) } it { is_expected.to belong_to(:user) } end + + describe "#staging_dir" do + it 'is hardcoded to the correct path' do + expect(described_class.new.staging_dir).to eq '/dor/assembly' + end + end + + describe "#normalize_bundle_dir" do + it "removes the trailing forward slash" do + expect(bc.normalize_bundle_dir).to eq "spec/test_data/bundle_input_g" + end + end + + describe "#content_tag_override?" do + it "is set to true" do + expect(described_class.new.content_tag_override?).to be true + end + end + + describe "#smpl_manifest" do + it "returns the file name" do + expect(described_class.new.smpl_manifest).to eq 'smpl_manifest.csv' + end + end + + describe "#manifest" do + it "returns the file name" do + expect(described_class.new.manifest).to eq 'manifest.csv' + end + end + + describe "#path_in_bundle" do + it "creates a relative path" do + expect(bc.path_in_bundle("manifest.csv")).to eq "spec/test_data/bundle_input_g/manifest.csv" + end + end + + describe "#progress_log_file" do + skip("Need to figure out where to set this path via planning meeting 9/10/18") + end + + describe '#import_csv' do + let(:manifest) do + described_class.import_csv("#{Rails.root}/spec/test_data/bundle_input_a/manifest.csv") + end + + it "loads a CSV as a hash with indifferent access" do + expect(manifest).to be_an(Array) + expect(manifest.size).to eq(3) + headers = %w{format sourceid filename label year inst_notes prod_notes has_more_metadata description} + expect(manifest).to all(be_an(ActiveSupport::HashWithIndifferentAccess)) # accessible w/ string and symbols + expect(manifest).to all(include(*headers)) + expect(manifest[0][:description]).to be_nil + expect(manifest[1][:description]).to eq('') + expect(manifest[2][:description]).to eq('yo, this is a description') + end + end + + describe "manifest_rows" do + it "loads the manifest CSV" do + expect(described_class).to receive(:import_csv).with("spec/test_data/bundle_input_g/manifest.csv") + bc.manifest_rows + end + + it "memoizes the manifest rows" do + expect(described_class).to receive(:import_csv).once.with("spec/test_data/bundle_input_g/manifest.csv").and_call_original + 2.times { bc.manifest_rows } + end + + it "expect the content of manifest rows" do + expect(bc.manifest_rows).to eq( + [ + {"druid"=>"druid:jy812bp9403", "sourceid"=>"bar-1.0", "folder"=>"jy812bp9403", "label"=>"Label 1", "description"=>"This is a description for label 1"}, + {"druid"=>"druid:tz250tk7584", "sourceid"=>"bar-2.1", "folder"=>"tz250tk7584", "label"=>"Label 2", "description"=>"This is a description for label 2"}, + {"druid"=>"druid:gn330dv6119", "sourceid"=>"bar-3.1", "folder"=>"gn330dv6119", "label"=>"Label 3", "description"=>"This is a description for label 3"} + ] + ) + end + end + + describe "manifest_cols" do + it "sets the column names" do + expect(bc.manifest_cols).to eq( + label: 'label', + source_id: 'sourceid', + object_container: 'object', # object referring to filename or foldername + druid: 'druid' + ) + end + end end