Newer
Older
dmpopidor / test / unit / template_test.rb
@briley briley on 23 May 2018 21 KB Template Versioning
require 'test_helper'

class TemplateTest < ActiveSupport::TestCase

  setup do
    # Need to clear the tables until we get seed.rb out of test_helper.rb
    Template.destroy_all
    
    @funder = init_funder
    @org = init_organisation
    @institution = init_institution
    @funder_org = init_funder_organisation
    
    @basic_template = init_template(@funder, published: true)
  end

  def init_full_template(template)
    phase = init_phase(template)
    section = init_section(phase)
    init_question(section)
    return template
  end

  def settings(extras = {})
    {margin:    (@margin || { top: 10, bottom: 10, left: 10, right: 10 }),
     font_face: (@font_face || Settings::Template::VALID_FONT_FACES.first),
     font_size: (@font_size || 11)
    }.merge(extras)
  end

  def default_formatting
    Settings::Template::DEFAULT_SETTINGS[:formatting]
  end
  
  test "default values are properly set on template creation" do
    template = init_template(@funder)
    assert_equal false, template.published, 'expected a new template to not be published'
    assert_equal false, template.archived, 'expected a new template to not be archived'
    assert_not_nil template.family_id, 'expected a new template to have a family_id'
    assert_equal false, template.is_default, 'expected a new template to not be the default template'
    assert template.publicly_visible?, 'expected a new funder template to be publicly visible'

    tmplt = init_template(@org)
    tmplt2 = init_template(@funder_org)
    assert tmplt.organisationally_visible?, 'expected a new non-funder template to be organisationally visible'
    assert tmplt2.organisationally_visible?, 'expected a new non-funder template to be organisationally visible'
  end

  test "required fields are required" do
    assert_not Template.new.valid?
    assert_not Template.new(version: 1, title: 'Tester').valid?, "expected the 'org' field to be required"
    assert_not Template.new(org: @funder, version: 1).valid?, "expected the 'title' field to be required"
  end

  test "unarchived returns only unarchived templates" do
    # Create an unarchived and an archived template (set archived after init because it will default to false on creation)
    archived = init_template(@funder, { title: 'Archived Template' })
    archived.update_attributes(archived: true)
    results = Template.unarchived
    assert_equal 1, results.length, 'expected there to be only 1 unarchived template'
    assert_equal @basic_template, results.first, 'expected the correct template to have been returned'
  end
  
  test "archived returns only archived templates" do
    # Create an unarchived and an archived template (set archived after init because it will default to false on creation)
    archived = init_template(@funder, { title: 'Archived Template' })
    archived.update_attributes(archived: true)
    results = Template.archived
    assert_equal 1, results.length, 'expected there to be only 1 archived template'
    assert_equal archived, results.first, 'expected the correct template to have been returned'
  end
  
  test "able to determine the latest version number" do
    version2 = @basic_template.generate_version!
    version2.save!
    results = Template.latest_version_per_family(@basic_template.family_id)
    assert_equal 1, results.length, 'expected only one version to be returned for the specific family'
    assert_equal version2.version, results.first.version, 'expected the new version'
  end
  
  test "able to retrieve the latest version" do
    version2 = @basic_template.generate_version!
    version2.save!
    result = Template.latest_version(@basic_template.family_id)
    assert_equal 1, result.length, 'expected only one version to be returned'
    assert_equal version2, result.first, 'expected the new version'
  end

  test "able to version a template" do
    template = init_full_template(@basic_template)
    assert_equal 0, template.version, 'expected the initial template version to be zero'
    version2 = template.generate_version!
    assert_equal 1, version2.version, 'expected the version number to be one more than the original template\'s'
    assert_equal template.family_id, version2.family_id, 'expected the new version to have the same family_id'
    assert_equal template.visibility, version2.visibility, 'expected the new version to have the same visibility'
    assert_equal template.is_default, version2.is_default, 'expected the new version to have the same default flag'
    assert_equal false, version2.archived, 'expected the new version to no be archived'
    # All components were transferred over to the new version
    assert_equal template.phases.length, version2.phases.length, 'expected the new version to have the same number of phases'
    template.phases.each_with_index do |phase, idx|
      assert_phases_equal(phase, version2.phases[idx])
    end
  end

  test "#generate_copy! raises RuntimeError when a non Org object is passed" do
    init_full_template(@basic_template)
    exception = assert_raises(RuntimeError) do
      @basic_template.generate_copy!(nil)
    end
    assert_equal(_('generate_copy! requires an organisation target'), exception.message)
  end

  test "#generate_copy! creates a new copy of a template" do
    template = init_full_template(@basic_template)
    template.update_attributes(is_default: true, published: true) # Update these flags to verify that the copy sets them properly
    copy = template.generate_copy!(@institution)
    assert_not_equal template.id, copy.id, 'expecetd the copy to have a different id'
    assert_not_equal template.family_id, copy.family_id, 'expected the copy to have a different family id'
    assert_equal @institution, copy.org, 'expected the copy to have the correct Org'
    assert_equal 0, copy.version, 'expected the copy\'s version number to be zero'
    assert_not copy.published?, 'expected the copy to not be published'
    assert_not copy.is_default?, 'expected the copy to not be the default template'
    assert_equal 'organisationally_visible', copy.visibility, 'expected the visibility to be organisational'
    assert_equal 'Copy of %{template}' % { template: template.title }, copy.title, 'expected the template title to be "Copy of %{template}"'
    assert_equal template.description, copy.description, 'expected the template descriptions to match'
    assert_equal template.phases.length, copy.phases.length, 'expected the copy to have the same number of phases'
    template.phases.each_with_index do |phase, idx|
      assert_phases_equal(phase, copy.phases[idx])
    end
  end

  test "can properly determine if current template is the latest version" do
    assert @basic_template.latest?, 'expected the initial template to be the latest version'
    version2 = @basic_template.generate_version!
    version2.save!
    assert_not @basic_template.latest?, 'expected the initial template to no longer be the latest version'
    assert version2.latest?, 'expected the new version to be the latest version'
  end

  test "#customize! raises RuntimeError when a non Org object is passed" do
    init_full_template(@basic_template)
    exception = assert_raises(RuntimeError) do
      @basic_template.customize!(nil)
    end
    assert_equal(_('customize! requires an organisation target'), exception.message)
  end

  test "#customize! raises RuntimeError when the template belongs to a non funder" do
    template = init_template(@org, published: true)
    exception = assert_raises(StandardError) do
      template.customize!(@institution)
    end
  end
  
  test "#customize! allows user to customize the default template" do
    template = init_template(@org, published: true, is_default: true)
    customization = template.customize!(@institution)
    assert_not_nil(customization)
  end
  
  test "#customize! generates a new template" do
    template = init_full_template(@basic_template)
    customization = template.customize!(@institution)
    assert(customization.family_id.present?, 'expected a newly family_id value')
    assert_equal(template.family_id, customization.customization_of, 'expected the customization_of id to match the base template\'s family_id')
    assert_equal(0, customization.version, 'expected the initial customization version to be zero')
    assert_equal(@institution, customization.org, 'expected the customizatio\'s org to match the one specified')
    assert_not(customization.published, 'expected the customization to not be published')
    assert_equal('organisationally_visible', customization.visibility, 'expected the customization\'s visibility to be organisationally visible')
    assert_not(customization.is_default, 'expected the customization to not be the default template')

    # Following statements go further than checking that the instance method behaves adequately
    assert_equal template.phases.length, customization.phases.length, 'expected the customization to have the same number of phases as the base template'
    template.phases.each_with_index do |phase, idx|
      assert_phases_equal(phase, customization.phases[idx])
    end
  end
  test "#customize! is thread-safe and therefore only one customization_of/version/org_id record exists in the db" do
    template = init_template(@funder, published: true)
    await = true
    should_assert = true
    threads = 3.times.map do |i|
      Thread.new do
        while await do ; end
        begin
          template.customize!(@org)
        rescue ActiveRecord::StatementInvalid => e
          # SQLite only supports one writer at a time. (e.g. https://www.sqlite.org/rescode.html#busy)
          should_assert = false if e.message.include?("SQLite3::BusyException")
        end
      end
    end
    await = false
    threads.map(&:join)
    # ActiveRecord::Base.connection.adapter_name != 'SQLite'
    assert_equal(1, Template.where(customization_of: template.family_id, version: 0, org_id: @org.id).count) if should_assert
  end

  test "template customizations can be transferred after base template changes" do
    init_full_template(@basic_template)
    customization = @basic_template.customize!(@institution)
    first_question = customization.phases.first.sections.first.questions.first
    init_annotation(customization.org, first_question)
    customization.save!
  end

  test "base_org returns the current template org if the template is not customized" do
    assert_equal @basic_template.org, @basic_template.base_org, 'expected an uncustomized template to consider its own org the base_org'
  end
  test "base_org returns the parent template org if the template is customized" do
    customization = @basic_template.customize!(@institution)
    assert_equal @basic_template.org, customization.base_org, 'expected a customized template to consider the parent template\'s org the base_org'
  end

  test "#generate_version! raises RuntimeError when the template is not published" do
    template = init_template(@org, published: false)
    exception = assert_raises(RuntimeError) do
      template.generate_version!
    end
    assert_equal(_('generate_version! requires a published template'), exception.message)
  end

  test "#generate_version! creates a new version for a published and non-customised template" do
    template = init_template(@org, published: true)
    new_template = template.generate_version!
    assert_equal(@basic_template.version + 1, new_template.version)
    assert_not(new_template.published)
  end

  test "#generate_version! is thread-safe and therefore only one family_id/version record exists in the db" do
    template = init_template(@org, published: true)
    await = true
    should_assert = true
    threads = 3.times.map do |i|
      Thread.new do
        while await do ; end
        begin
          template.generate_version!
        rescue ActiveRecord::StatementInvalid => e
          # SQLite only supports one writer at a time. (e.g. https://www.sqlite.org/rescode.html#busy)
          should_assert = false if e.message.include?("SQLite3::BusyException")
        end
      end
    end
    await = false
    threads.map(&:join)
    assert_equal(1, Template.where(family_id: template.family_id, version: 1).count) if should_assert
  end

  test "#upgrade_customization! raises RuntimeError when the template is not a customisation of another template" do
    template = init_template(@org, published: true)
    exception = assert_raises(RuntimeError) do
      template.upgrade_customization!
    end
    assert_equal(_('upgrade_customization! requires a customised template'), exception.message)
  end

  test "#upgrade_customization! creates a new version" do
    customization = @basic_template.customize!(@institution)
    customization.published = true
    transferred = customization.upgrade_customization!
    assert_equal(customization.version + 1, transferred.version, 'expected the version number to have been incremented when the current cusomization was published')
    assert_equal(customization.family_id, transferred.family_id, 'expected the family_id to be retained when upgrade_customization! is called')
  end

  test "#upgrade_customization! appends modifiable phases to the new customisation" do
    init_full_template(@basic_template)
    customization = @basic_template.customize!(@institution)
    customization.phases << Phase.new(title: 'New customised phase', number: 2, modifiable: true)
    customization.phases << Phase.new(title: 'New customised phase 2', number: 3, modifiable: true)

    transferred = customization.upgrade_customization!
    assert_not_equal(customization.object_id, transferred.object_id, 'customization and transferred are distinct objects')
    assert_equal(3, transferred.phases.length, 'expected 3 phases after upgrading a customised template')
  end

  test "#upgrade_customization! appends modifiable sections into an unmodifiable phase" do
    init_full_template(@basic_template)
    customization = @basic_template.customize!(@institution)
    customization.phases.first.sections << Section.new(title: 'New customised section', number: 2, modifiable: true)
    customization.phases.first.sections << Section.new(title: 'New customised section 2', number: 3, modifiable: true)

    transferred = customization.upgrade_customization!
    assert_not_equal(customization.object_id, transferred.object_id, 'customization and transferred are distinct objects')
    assert_equal(3, transferred.phases.first.sections.length, 'expected 3 sections after upgrading a customised template')
  end

  test "#upgrade_customization! appends modifiable questions into an unmodifiable section" do
    init_full_template(@basic_template)
    customization = @basic_template.customize!(@institution)
    customization.phases.first.sections.first.questions << Question.new(text: 'New customised question', number: 2, modifiable: true)
    customization.phases.first.sections.first.questions << Question.new(text: 'New customised question 2', number: 3, modifiable: true)

    transferred = customization.upgrade_customization!
    assert_not_equal(customization.object_id, transferred.object_id, 'customization and transferred are distinct objects')
    assert_equal(3, transferred.phases.first.sections.first.questions.length, 'expected 3 questions after upgrading a customised template')
  end

  test "#upgrade_customization! appends annotations added to an unmodifiable question" do
    init_full_template(@basic_template)
    customization = @basic_template.customize!(@institution)
    customization.phases.first.sections.first.questions.first.annotations << 
      Annotation.new(text: 'New customised guidance', type: Annotation.types[:guidance], org: customization.org)
    customization.phases.first.sections.first.questions.first.annotations << 
      Annotation.new(text: 'New customised example_answer', type: Annotation.types[:example_answer], org: customization.org)

    @basic_template.phases.first.sections.first.questions.first.annotations <<
      Annotation.new(text: 'New funder guidance', type: Annotation.types[:guidance], org: @basic_template.org)
    @basic_template.phases.first.sections.first.questions.first.annotations <<
      Annotation.new(text: 'New funder example_answer', type: Annotation.types[:example_answer], org: @basic_template.org)

    transferred = customization.upgrade_customization!
    assert_not_equal(customization.object_id, transferred.object_id, 'customization and transferred are distinct objects')
    assert_equal(4, transferred.phases.first.sections.first.questions.first.annotations.length, 'expected 4 annotations after upgrading a customised template')
  end

  test "#generate_version? returns true when the template is published" do
    @basic_template.published = true
    assert(@basic_template.generate_version?)
  end

  test "#generate_version? returns false when the template is not published" do
    @basic_template.published = false
    assert_not(@basic_template.generate_version?)
  end

  test "#customize? returns false when no org is passed" do
    assert_not(@basic_template.customize?(nil))
  end

  test "#customize? returns false when the template is not owned by a funder or is not the default template" do
    template = init_template(@institution, { title: 'institutional template', is_default: false })
    assert_not template.customize?(@funder)
  end

  test "#customize? returns true when the org does not have a customization of the template" do
    assert(@basic_template.customize?(@institution))
  end

  test "#customize? returns false when the org has already a customization of the template" do
    @basic_template.customize!(@institution)
    assert_not(@basic_template.customize?(@institution))
  end

  test "#upgrade_customization? returns false when the template is not a customization of another template" do
    assert_not(@basic_template.upgrade_customization?)
  end

  test "#upgrade_customization? returns false when the template is already according to the latest published funder template" do
    @basic_template.published = true
    customization = @basic_template.customize!(@institution)
    assert_not(customization.upgrade_customization?)
  end

  test "#upgrade_customization? returns true when the template is stale, i.e a new version from funder has been published" do
    @basic_template.published = true
    customization = @basic_template.customize!(@institution)
    customization.created_at = customization.created_at.yesterday
    new_version = @basic_template.generate_version!
    new_version.published = true
    new_version.save!
    assert(customization.upgrade_customization?)
  end

  test "default template retains the correct flags when versioned" do
    @basic_template.update!({ org: @org, published: true, is_default: true, visibility: Template.visibilities[:publicly_visible] })
    new_version = @basic_template.generate_version!
    assert_not new_version.published?, 'expected the new version to not be published'
    assert new_version.is_default?, 'expected the new version to be flagged as the default template'
    assert new_version.publicly_visible?, 'expected the new version to be publicly visible'
  end
  
  test " a customization of the default template is not marked as the default" do
    @basic_template.update!({ org: @org, published: true, is_default: true, visibility: Template.visibilities[:publicly_visible] })
    customization = @basic_template.customize!(@institution)
    assert_not customization.published?, 'expected the customization to not be published'
    assert_not customization.is_default?, 'expected the customization to not be flagged as the default template'
    assert_not customization.publicly_visible?, 'expected the customization to not be publicly visible'
  end

  test "::find_or_generate_version! raises RuntimeError when attempting to retrieve a historical version for being modified" do
    @basic_template.generate_version!
    exception = assert_raises(RuntimeError) do
      Template.find_or_generate_version!(@basic_template)
    end
    assert_equal(_('A historical template cannot be retrieved for being modified'), exception.message)
  end

  test "::find_or_generate_version! does not generate a new version" do
    new_template = @basic_template.generate_version!
    template = Template.find_or_generate_version!(new_template)
    assert_equal(new_template, template)
  end

  test "::find_or_generate_version! generates a new version" do
    template = Template.find_or_generate_version!(@basic_template)
    assert_not_equal(@basic_template, template)
  end

  test "publishing a template automatically unpublishes other versions" do
    @basic_template.published = true
    @basic_template.save!
    v2 = @basic_template.generate_version!
    v2.published = true
    v2.save!
    assert v2.reload.published?, 'expected the new version to be published'
    assert_not @basic_template.reload.published?, 'expected the old version to be unpublished'
  end

  test "draft? returns false for a published template" do
    @basic_template.published = true
    assert_not @basic_template.draft?
  end
  
  test "draft? returns true for an unpublished version of a template that has a published version" do
    @basic_template.published = true
    version = @basic_template.generate_version!
    assert version.draft?
  end
  
  test "draft? returns false for a template that has no published versions" do
    @basic_template.published = true
    version = @basic_template.generate_version!
    @basic_template.update(published: false)
    assert_not version.draft?
  end
end