{"record":{"id":"de79f7410b1404a9","repo":"instructure/canvas-lms","slug":"cannot-merge-a-test-student","errorCode":null,"errorMessage":"cannot merge a test student","messagePattern":"cannot merge a test student","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/user_merge.rb","lineNumber":44,"sourceCode":"  attr_accessor :target_user, :data, :merge_data\n\n  def initialize(from_user)\n    @from_user = from_user\n    @target_user = nil\n    @merge_data = nil\n    @data = []\n  end\n\n  def pseudonyms_to_move_in_this_shard\n    Pseudonym.where(user_id: from_user)\n  end\n\n  def handle_instructure_identity(pseudonyms_to_move) end\n\n  def into(target_user, merger: nil, source: nil)\n    return unless target_user\n    return if target_user == from_user\n    raise \"cannot merge a test student\" if from_user.preferences[:fake_student] || target_user.preferences[:fake_student]\n\n    @target_user = target_user\n    @merger = merger\n    target_user.associate_with_shard(from_user.shard, :shadow)\n    # we also store records for the from_user on the target shard for a split\n    from_user.associate_with_shard(target_user.shard, :shadow)\n    target_user.shard.activate do\n      @merge_data = UserMergeData.create!(user: target_user, from_user:, workflow_state: \"merging\")\n      @merge_data.items.create!(user: target_user, item_type: \"logs\", item: { merger_id: merger.id, source: }.to_s) if merger || source\n\n      items = []\n      if target_user.avatar_state == :none && from_user.avatar_state != :none\n        %i[avatar_image_source avatar_image_url avatar_image_updated_at avatar_state].each do |attr|\n          items << merge_data.items.new(user: from_user, item_type: attr.to_s, item: from_user[attr]) if from_user[attr]\n          target_user[attr] = from_user[attr]\n        end\n      end\n","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/user_merge.rb#L26-L62","documentation":"UserMerge#into blocks merging when either the source (from_user) or target_user is a test student (fake_student preference). Test students are per-course synthetic users, so merging them into or out of a real account would corrupt enrollment history, so the merge aborts with this error.","triggerScenarios":"Calling user.merge_into(target) or UserMerge.from(user).into(target) where either user.preferences[:fake_student] is true — i.e. one of the users is the course test student created via 'Test Student' in course settings.","commonSituations":"Admin console merge tool used on a test student; scripts consolidating users that sweep up fake students; account merges that don't filter test students beforehand.","solutions":["Exclude fake students from the merge candidate list before calling into()","Check from_user.preferences[:fake_student] || target_user.preferences[:fake_student] and abort gracefully with a message instead of raising","Remove/recreate the test student data separately; test students can be deleted from a course instead of merged","If a test student legitimately represents a real person, have that person enroll as themselves and merge those accounts"],"exampleFix":"// before\nUserMerge.from(source_user).into(target_user)\n// after\nunless source_user.preferences[:fake_student] || target_user.preferences[:fake_student]\n  UserMerge.from(source_user).into(target_user)\nelse\n  Rails.logger.warn('skipping merge: test student involved')\nend","handlingStrategy":"validation","validationCode":"raise 'test student involved' if source_user.preferences[:fake_student] || target_user.preferences[:fake_student]","typeGuard":"def mergeable?(a, b) = !a.preferences[:fake_student] && !b.preferences[:fake_student]","tryCatchPattern":"begin\n  UserMerge.from(source).into(target)\nrescue RuntimeError => e\n  raise unless e.message == 'cannot merge a test student'\n  flash_error('test students cannot be merged')\nend","preventionTips":["Exclude fake students from merge UI and admin tooling","Filter preferences[:fake_student] in bulk user-consolidation scripts","Delete test students from courses rather than merging them"],"tags":["user-merge","test-student","invalid-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}