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