Skip to content

Commit 0e41670

Browse files
committed
test: enhance outcome testing and resolve related issues
1 parent d22653b commit 0e41670

11 files changed

+195
-283
lines changed

app/models/learning_outcome.rb

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,28 +110,49 @@ def add_csv_row(row)
110110
row << [unit_code, task_abbreviation, abbreviation, short_description, full_outcome_description, linked_learning_outcomes]
111111
end
112112

113-
def self.create_from_csv(row, result)
113+
def self.create_from_csv(unit, task_definition, row, result)
114114
unit_code = row['unit_code']
115115
task_abbreviation = row['task_abbreviation']
116116

117-
if unit_code.present?
118-
unit = Unit.find_by(code: unit_code)
119-
unless unit
120-
result[:errors] << { row: row, message: "Unit #{unit_code} not found" }
117+
# In a unit context, but no unit code
118+
if unit.present?
119+
if unit_code.nil?
120+
result[:errors] << { row: row, message: 'Missing unit_code' }
121+
return
122+
elsif unit_code != unit.code
123+
result[:errors] << { row: row, message: "Unit #{unit_code} does not match unit #{unit.code}" }
121124
return
122125
end
123126
end
124127

128+
# In a task context, but no task abbreviation
129+
if task_definition.present?
130+
if task_abbreviation.nil?
131+
result[:errors] << { row: row, message: 'Missing task_abbreviation' }
132+
return
133+
elsif task_abbreviation != task_definition.abbreviation
134+
result[:errors] << { row: row, message: "Task #{task_abbreviation} does not match task #{task_definition.abbreviation}" }
135+
return
136+
end
137+
end
138+
139+
if unit_code.present? && unit.nil?
140+
result[:errors] << { row: row, message: 'Unit outcomes must be uploaded to a unit' }
141+
return
142+
end
143+
144+
if task_abbreviation.present? && unit.nil?
145+
result[:errors] << { row: row, message: 'Task outcomes must be uploaded to a unit' }
146+
return
147+
end
148+
149+
# Get the context for the outcome
125150
if task_abbreviation.present?
126-
task_definition = TaskDefinition.find_by(abbreviation: task_abbreviation)
151+
task_definition = unit.task_definitions.find_by(abbreviation: task_abbreviation)
127152
unless task_definition
128153
result[:errors] << { row: row, message: "Task #{task_abbreviation} not found" }
129154
return
130155
end
131-
unless unit.task_definitions.include?(task_definition)
132-
result[:errors] << { row: row, message: "Task #{task_abbreviation} is not linked to unit #{unit_code}" }
133-
return
134-
end
135156
context_type = 'TaskDefinition'
136157
context_id = task_definition.id
137158
elsif unit_code.present?
@@ -165,12 +186,26 @@ def self.create_from_csv(row, result)
165186
end
166187

167188
linked_outcomes = row['linked_outcomes'].to_s.split(',').map(&:strip)
168-
linked_outcome_ids = LearningOutcome.where(abbreviation: linked_outcomes).pluck(:id)
169-
missing_links = linked_outcomes - LearningOutcome.where(abbreviation: linked_outcomes).pluck(:abbreviation)
170189

171-
if missing_links.any?
172-
result[:errors] << { row: row, message: "Linked outcomes #{missing_links.join(', ')} not found" }
173-
return
190+
linked_outcome_ids = []
191+
192+
linked_outcomes.each do |linked_outcome|
193+
# Search in the unit if present
194+
found_outcome = if unit.present?
195+
unit.learning_outcomes.find_by(abbreviation: linked_outcome) || LearningOutcome.global_outcomes.find_by(abbreviation: linked_outcome)
196+
else
197+
# Search in the global outcomes
198+
LearningOutcome.global_outcomes.find_by(abbreviation: linked_outcome)
199+
end
200+
201+
if found_outcome.present?
202+
linked_outcome_ids << found_outcome.id
203+
else
204+
result[:errors] << { row: row, message: "Linked outcome #{linked_outcome} not found" }
205+
return
206+
end
207+
208+
found_outcome.id
174209
end
175210

176211
outcome = LearningOutcome.find_or_create_by(context_id: context_id, context_type: context_type, abbreviation: abbreviation)

app/models/learning_outcome_link.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class LearningOutcomeLink < ApplicationRecord
1818
validates :source_id, uniqueness: { scope: :target_id, message: 'Link already exists' }
1919

2020
validate :validate_link_type
21+
validate :validate_context
2122

2223
def validate_link_type
2324
return if source.nil? || target.nil?
@@ -30,4 +31,12 @@ def validate_link_type
3031
errors.add(:link_type, 'Learning outcomes must only link to higher level outcomes')
3132
end
3233
end
34+
35+
def validate_context
36+
return if source.nil? || target.nil?
37+
38+
if source.context_type == 'TaskDefinition' && target.context_type == 'Unit' && source.context.unit != target.context
39+
errors.add(:link_type, 'Task learning outcomes must be linked to the same unit')
40+
end
41+
end
3342
end

app/models/task_definition.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@ def self.to_csv(task_definitions)
282282
end
283283
end
284284

285-
def export_learning_outcome_to_csv(include_tlos = false)
285+
# Export the learning outcomes for this task definition to a CSV file
286+
# @param _include_tlos [Boolean] ignored as at the task definition level already
287+
def export_learning_outcome_to_csv(*)
286288
CSV.generate do |row|
287289
row << LearningOutcome.csv_header
288290
learning_outcomes.each do |outcome|
@@ -291,7 +293,7 @@ def export_learning_outcome_to_csv(include_tlos = false)
291293
end
292294
end
293295

294-
def export_feedback_chips_to_csv(include_tlos = false)
296+
def export_feedback_chips_to_csv(*)
295297
CSV.generate do |row|
296298
row << Feedback::FeedbackChip.csv_header
297299
learning_outcomes.each do |outcome|
@@ -323,7 +325,7 @@ def import_outcomes_from_csv(file)
323325
next if row[0] =~ /abbreviation/
324326

325327
begin
326-
LearningOutcome.create_from_csv(row, result)
328+
LearningOutcome.create_from_csv(unit, self, row, result)
327329
rescue StandardError => e
328330
result[:errors] << { row: row, message: e.message.to_s }
329331
end

app/models/unit.rb

Lines changed: 33 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,17 @@ def rollover(teaching_period, start_date, end_date, new_code)
319319
new_unit.group_sets << group_set.dup
320320
end
321321

322-
task_definition_outcome_mapping = {}
323-
unit_learning_outcome_mapping = {}
324-
chip_mapping = {}
322+
# Old outcome to new outcome mapping
323+
outcome_mapping = {}
324+
325+
# Duplicate unit learning outcomes - before task definitions as they are linked to them
326+
learning_outcomes.each do |learning_outcome|
327+
new_outcome = learning_outcome.dup
328+
new_outcome.context = new_unit
329+
new_outcome.save!
330+
new_unit.learning_outcomes << new_outcome
331+
outcome_mapping[learning_outcome] = new_outcome
332+
end
325333

326334
# Duplicate task definitions
327335
task_definitions.each do |td|
@@ -330,31 +338,7 @@ def rollover(teaching_period, start_date, end_date, new_code)
330338
td.learning_outcomes.each do |learning_outcome| # for each old task definition, duplicate the learning outcomes associated with it aswell
331339
new_outcome = learning_outcome.dup
332340
new_td.learning_outcomes << new_outcome
333-
task_definition_outcome_mapping[learning_outcome.id] = new_outcome.id
334-
335-
learning_outcome.feedback_chips.each do |chip|
336-
new_chip = chip.dup
337-
new_outcome.feedback_chips << new_chip
338-
new_chip.learning_outcome_id = new_outcome.id if chip.respond_to?(:learning_outcome_id)
339-
new_chip.parent_chip_id = nil
340-
new_chip.save!
341-
chip_mapping[chip.id] = new_chip.id
342-
end
343-
344-
learning_outcome.feedback_chips.where.not(parent_chip_id: nil).find_each do |chip|
345-
child_chip = new_outcome.feedback_chips.find(chip_mapping[chip.id])
346-
parent_chip = new_outcome.feedback_chips.find(chip_mapping[chip.parent_chip_id])
347-
child_chip.update(parent_chip_id: parent_chip.id)
348-
end
349-
end
350-
351-
LearningOutcomeLink.where(source_id: task_definition_outcome_mapping.keys).find_each do |link|
352-
new_source_id = task_definition_outcome_mapping[link.source_id]
353-
new_target_id = task_definition_outcome_mapping[link.target_id]
354-
355-
if new_source_id && new_target_id
356-
LearningOutcomeLink.create!(source_id: new_source_id, target_id: new_target_id)
357-
end
341+
outcome_mapping[learning_outcome] = new_outcome
358342
end
359343

360344
# Update default task definition if necessary
@@ -363,52 +347,37 @@ def rollover(teaching_period, start_date, end_date, new_code)
363347
end
364348
end
365349

366-
# Duplicate unit learning outcomes
367-
learning_outcomes.each do |learning_outcome|
368-
new_outcome = learning_outcome.dup
369-
new_outcome.context = new_unit
370-
new_outcome.save!
371-
new_unit.learning_outcomes << new_outcome
372-
unit_learning_outcome_mapping[learning_outcome.id] = new_outcome.id
350+
# Link outcomes
351+
outcome_mapping.each do |old_outcome, new_outcome|
352+
old_outcome.linked_outcomes.each do |old_linked_outcome|
353+
new_target = outcome_mapping[old_linked_outcome] || old_linked_outcome
373354

374-
learning_outcome.feedback_chips.each do |chip|
355+
if new_outcome.present? && new_target.present?
356+
LearningOutcomeLink.create!(source_id: new_outcome.id, target_id: new_target.id)
357+
end
358+
end
359+
end
360+
361+
# Now duplicate all feedback chips
362+
chip_mapping = {}
363+
364+
outcome_mapping.each do |source_outcome, new_outcome|
365+
source_outcome.feedback_chips.each do |chip|
375366
new_chip = chip.dup
376367
new_outcome.feedback_chips << new_chip
377-
new_chip.learning_outcome_id = new_outcome.id if chip.respond_to?(:learning_outcome_id)
368+
new_chip.learning_outcome_id = new_outcome.id
378369
new_chip.parent_chip_id = nil
379370
new_chip.save!
380-
chip_mapping[chip.id] = new_chip.id
371+
chip_mapping[chip] = new_chip
381372
end
382373

383-
learning_outcome.feedback_chips.where.not(parent_chip_id: nil).find_each do |chip|
384-
child_chip = new_outcome.feedback_chips.find(chip_mapping[chip.id])
385-
parent_chip = new_outcome.feedback_chips.find(chip_mapping[chip.parent_chip_id])
374+
source_outcome.feedback_chips.where.not(parent_chip_id: nil).find_each do |old_chip|
375+
child_chip = chip_mapping[old_chip]
376+
parent_chip = chip_mapping[old_chip.parent_chip]
386377
child_chip.update(parent_chip_id: parent_chip.id)
387378
end
388379
end
389380

390-
LearningOutcomeLink.where(source_id: unit_learning_outcome_mapping.keys).find_each do |link|
391-
new_source_id = unit_learning_outcome_mapping[link.source_id] || link.source_id
392-
new_target_id = unit_learning_outcome_mapping[link.target_id] || link.target_id
393-
394-
if new_source_id && new_target_id
395-
LearningOutcomeLink.create!(source_id: new_source_id, target_id: new_target_id)
396-
end
397-
end
398-
399-
chip_mapping.each do |old_id, new_id|
400-
old_chip = Feedback::FeedbackChip.find(old_id)
401-
if old_chip.parent_chip_id && chip_mapping[old_chip.parent_chip_id]
402-
new_chip = Feedback::FeedbackChip.find(new_id)
403-
new_chip.update(parent_chip_id: chip_mapping[old_chip.parent_chip_id])
404-
end
405-
end
406-
407-
# Duplicate alignments
408-
task_outcome_alignments.each do |align|
409-
align.duplicate_to(new_unit)
410-
end
411-
412381
new_unit
413382
end
414383

@@ -1154,7 +1123,7 @@ def import_outcomes_from_csv(file)
11541123
next if row[0] =~ /unit_code/
11551124

11561125
begin
1157-
LearningOutcome.create_from_csv(row, result)
1126+
LearningOutcome.create_from_csv(self, nil, row, result)
11581127
rescue Exception => e
11591128
result[:errors] << { row: row, message: e.message.to_s }
11601129
end

test/api/feedback/feedback_chip_api_consolidated_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def test_auth_for_create_edit_unit_feedback_chips
9191
def test_auth_for_create_edit_global_feedback_chips
9292
admin = FactoryBot.create(:user, :admin)
9393

94-
learning_outcome = LearningOutcome.global_outcomes.first
94+
learning_outcome = FactoryBot.create(:learning_outcome, context_id: nil, context_type: nil)
9595

9696
users_can = [
9797
admin

test/api/feedback/learning_outcome_api_test.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ def test_delete_task_definition_learning_outcome
242242
end
243243

244244
def test_create_learning_outcome_links
245-
task_definition = FactoryBot.create(:task_definition, outcome_count: 0)
246245
unit = FactoryBot.create(:unit, name: 'i like units', code: 'abcde', description: 'test unit')
246+
task_definition = FactoryBot.create(:task_definition, outcome_count: 0, unit: unit)
247247
target_learning_outcome = FactoryBot.create(:learning_outcome, context_id: unit.id, context_type: 'Unit', abbreviation: 'TGT1', short_description: 'target learning outcome', full_outcome_description: 'this outcome will be linked to source')
248248
target_learning_outcome2 = FactoryBot.create(:learning_outcome, context_id: unit.id, context_type: 'Unit', abbreviation: 'TGT2', short_description: 'target learning outcome 2', full_outcome_description: 'this outcome will be linked to source')
249249
data_to_post = {
@@ -268,8 +268,8 @@ def test_create_learning_outcome_links
268268
end
269269

270270
def test_overwrite_linked_learning_outcomes
271-
task_definition = FactoryBot.create(:task_definition, outcome_count: 0)
272271
unit = FactoryBot.create(:unit, name: 'i like units', code: 'abcde', description: 'test unit')
272+
task_definition = FactoryBot.create(:task_definition, outcome_count: 0, unit: unit)
273273
target_learning_outcome = FactoryBot.create(:learning_outcome, context_id: unit.id, context_type: 'Unit', abbreviation: 'TGT1', short_description: 'target learning outcome', full_outcome_description: 'this outcome will be linked to source')
274274
target_learning_outcome2 = FactoryBot.create(:learning_outcome, context_id: unit.id, context_type: 'Unit', abbreviation: 'TGT2', short_description: 'target learning outcome 2', full_outcome_description: 'this outcome will be linked to source')
275275
data_to_post = {
@@ -329,7 +329,7 @@ def test_unit_rollover
329329
# Create a unit with learning outcomes
330330
global_learning_outcome = FactoryBot.create(:learning_outcome, context_id: nil, context_type: nil, abbreviation: 'nglo')
331331
original_unit = FactoryBot.create(:unit, name: 'rollver unit', code: 'abcde', description: 'test unit to be rolled over') # When creating unit, it generates 2 random outcomes in addition to below
332-
original_task_definition = FactoryBot.create(:task_definition, unit_id: original_unit.id, outcome_count: 0)
332+
original_task_definition = FactoryBot.create(:task_definition, unit: original_unit, outcome_count: 0)
333333
original_lo1 = FactoryBot.create(:learning_outcome, context_id: original_unit.id, context_type: 'Unit', abbreviation: 'olo1', short_description: 'sd', full_outcome_description: 'fod')
334334
original_lo2 = FactoryBot.create(:learning_outcome, context_id: original_unit.id, context_type: 'Unit', abbreviation: 'olo2', short_description: 'sd', full_outcome_description: 'fod')
335335
original_lo3 = FactoryBot.create(:learning_outcome, context_id: original_unit.id, context_type: 'Unit', abbreviation: 'olo3', short_description: 'sd', full_outcome_description: 'fod')
@@ -365,6 +365,7 @@ def test_unit_rollover
365365

366366
new_unit = original_unit.rollover(TeachingPeriod.find(2), nil, nil, nil)
367367
new_td = new_unit.task_definitions.find_by(name: original_task_definition.name)
368+
new_td_lo = new_td.learning_outcomes.find_by(abbreviation: td_lo.abbreviation)
368369

369370
assert_equal 5, new_unit.learning_outcomes.count, 'New unit should have 5 learning outcomes'
370371
auto1, auto2, new_lo1, new_lo2, new_lo3 = new_unit.learning_outcomes.order(:id)
@@ -406,7 +407,7 @@ def test_unit_rollover
406407
new_link = LearningOutcomeLink.find_by(source_id: new_lo1.id, target_id: global_learning_outcome.id)
407408
assert_not_nil new_link, 'New learning outcome 1 should be linked to the global learning outcome'
408409

409-
new_link = LearningOutcomeLink.find_by(source_id: new_lo2.id, target_id: td_lo.id)
410+
new_link = LearningOutcomeLink.find_by(source_id: new_td_lo.id, target_id: new_lo2.id)
410411
assert_not_nil new_link, 'New learning outcome 2 should be linked to the task definition learning outcome'
411412
end
412413

0 commit comments

Comments
 (0)