@@ -329,5 +329,173 @@ class StudentsControllerTest < ActionDispatch::IntegrationTest
329329 assert_redirected_to root_path
330330 assert_equal "Access denied. Admin privileges required." , flash [ :alert ]
331331 end
332+
333+ # Import/Template tests
334+ test "template should download CSV template" do
335+ get template_admin_v2_students_path
336+
337+ assert_response :success
338+ assert_equal "text/csv" , response . media_type
339+ assert_equal "attachment; filename=\" student_import_template.csv\" " , response . headers [ "Content-Disposition" ]
340+ assert_match ( /classroom_id,username/ , response . body )
341+ end
342+
343+ test "import should create students from valid CSV" do
344+ csv_content = "classroom_id,username\n #{ @classroom1 . id } ,import_student1\n #{ @classroom2 . id } ,import_student2"
345+ csv_file = Tempfile . new ( [ "test_import" , ".csv" ] )
346+ csv_file . write ( csv_content )
347+ csv_file . rewind
348+
349+ assert_difference ( "Student.count" , 2 ) do
350+ post import_admin_v2_students_path , params : {
351+ csv_file : fixture_file_upload ( csv_file . path , "text/csv" )
352+ }
353+ end
354+
355+ csv_file . close
356+ csv_file . unlink
357+
358+ assert_redirected_to admin_v2_students_path
359+ assert_match ( /Successfully created 2 students/ , flash [ :notice ] )
360+ assert_match ( /import_student1/ , flash [ :notice ] )
361+ assert_match ( /import_student2/ , flash [ :notice ] )
362+ end
363+
364+ test "import should skip existing students" do
365+ csv_content = "classroom_id,username\n #{ @classroom1 . id } ,student1\n #{ @classroom2 . id } ,new_student"
366+ csv_file = Tempfile . new ( [ "test_import" , ".csv" ] )
367+ csv_file . write ( csv_content )
368+ csv_file . rewind
369+
370+ assert_difference ( "Student.count" , 1 ) do
371+ post import_admin_v2_students_path , params : {
372+ csv_file : fixture_file_upload ( csv_file . path , "text/csv" )
373+ }
374+ end
375+
376+ csv_file . close
377+ csv_file . unlink
378+
379+ assert_redirected_to admin_v2_students_path
380+ assert_match ( /Successfully created 1 students/ , flash [ :notice ] )
381+ assert_match ( /Skipped 1 existing usernames/ , flash [ :notice ] )
382+ end
383+
384+ test "import should handle errors and show line numbers" do
385+ csv_content = "classroom_id,username\n 999,invalid_student\n #{ @classroom1 . id } ,valid_student"
386+ csv_file = Tempfile . new ( [ "test_import" , ".csv" ] )
387+ csv_file . write ( csv_content )
388+ csv_file . rewind
389+
390+ post import_admin_v2_students_path , params : {
391+ csv_file : fixture_file_upload ( csv_file . path , "text/csv" )
392+ }
393+
394+ csv_file . close
395+ csv_file . unlink
396+
397+ assert_redirected_to admin_v2_students_path
398+ assert_match ( /errors occurred/ , flash [ :alert ] )
399+ assert_match ( /Row 2:/ , flash [ :alert ] )
400+ end
401+
402+ test "import should reject missing file" do
403+ assert_no_difference ( "Student.count" ) do
404+ post import_admin_v2_students_path
405+ end
406+
407+ assert_redirected_to admin_v2_students_path
408+ assert_equal "Please select a CSV file" , flash [ :alert ]
409+ end
410+
411+ test "import should handle malformed CSV" do
412+ csv_content = "classroom_id,username\n 1,\" unclosed quote\n 2,another_row"
413+ csv_file = Tempfile . new ( [ "test_import" , ".csv" ] )
414+ csv_file . write ( csv_content )
415+ csv_file . rewind
416+
417+ assert_no_difference ( "Student.count" ) do
418+ post import_admin_v2_students_path , params : {
419+ csv_file : fixture_file_upload ( csv_file . path , "text/csv" )
420+ }
421+ end
422+
423+ csv_file . close
424+ csv_file . unlink
425+
426+ assert_redirected_to admin_v2_students_path
427+ assert_match ( /Invalid CSV format/ , flash [ :alert ] )
428+ end
429+
430+ test "import should handle empty CSV" do
431+ csv_content = "classroom_id,username\n "
432+ csv_file = Tempfile . new ( [ "test_import" , ".csv" ] )
433+ csv_file . write ( csv_content )
434+ csv_file . rewind
435+
436+ assert_no_difference ( "Student.count" ) do
437+ post import_admin_v2_students_path , params : {
438+ csv_file : fixture_file_upload ( csv_file . path , "text/csv" )
439+ }
440+ end
441+
442+ csv_file . close
443+ csv_file . unlink
444+
445+ assert_redirected_to admin_v2_students_path
446+ assert_equal "No students found in CSV file" , flash [ :alert ]
447+ end
448+
449+ test "import should show both success and error messages" do
450+ csv_content = "classroom_id,username\n #{ @classroom1 . id } ,import_success1\n 999,import_fail\n #{ @classroom2 . id } ,import_success2"
451+ csv_file = Tempfile . new ( [ "test_import" , ".csv" ] )
452+ csv_file . write ( csv_content )
453+ csv_file . rewind
454+
455+ post import_admin_v2_students_path , params : {
456+ csv_file : fixture_file_upload ( csv_file . path , "text/csv" )
457+ }
458+
459+ csv_file . close
460+ csv_file . unlink
461+
462+ assert_redirected_to admin_v2_students_path
463+ assert_match ( /Successfully created 2 students/ , flash [ :notice ] )
464+ assert_match ( /1 errors occurred/ , flash [ :alert ] )
465+ end
466+
467+ test "non-admin cannot access template" do
468+ sign_out ( @admin )
469+ teacher = create ( :teacher )
470+ sign_in ( teacher )
471+
472+ get template_admin_v2_students_path
473+
474+ assert_redirected_to root_path
475+ assert_equal "Access denied. Admin privileges required." , flash [ :alert ]
476+ end
477+
478+ test "non-admin cannot import students" do
479+ sign_out ( @admin )
480+ teacher = create ( :teacher )
481+ sign_in ( teacher )
482+
483+ csv_content = "classroom_id,username\n #{ @classroom1 . id } ,import_student"
484+ csv_file = Tempfile . new ( [ "test_import" , ".csv" ] )
485+ csv_file . write ( csv_content )
486+ csv_file . rewind
487+
488+ assert_no_difference ( "Student.count" ) do
489+ post import_admin_v2_students_path , params : {
490+ csv_file : fixture_file_upload ( csv_file . path , "text/csv" )
491+ }
492+ end
493+
494+ csv_file . close
495+ csv_file . unlink
496+
497+ assert_redirected_to root_path
498+ assert_equal "Access denied. Admin privileges required." , flash [ :alert ]
499+ end
332500 end
333501end
0 commit comments