diff --git a/Gemfile b/Gemfile index 1786836d0..e76fed5cf 100644 --- a/Gemfile +++ b/Gemfile @@ -44,6 +44,7 @@ group :development, :test do gem 'rswag-specs' gem 'rubocop' gem 'simplecov', require: false, group: :test + gem 'database_cleaner-active_record' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index cb84960a2..6a4429330 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,6 +75,10 @@ GEM builder (3.2.4) concurrent-ruby (1.2.2) crass (1.0.6) + database_cleaner-active_record (2.2.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) date (3.3.3) debug (1.8.0) irb (>= 1.5.0) @@ -252,6 +256,7 @@ PLATFORMS DEPENDENCIES bcrypt (~> 3.1.7) bootsnap + database_cleaner-active_record debug factory_bot_rails faker diff --git a/app/models/questionnaire.rb b/app/models/questionnaire.rb index d576bc421..88099cc2e 100644 --- a/app/models/questionnaire.rb +++ b/app/models/questionnaire.rb @@ -37,7 +37,7 @@ def validate_questionnaire # Check_for_question_associations checks if questionnaire has associated questions or not def check_for_question_associations if questions.any? - raise ActiveRecord::DeleteRestrictionError.new(:base, "Cannot delete record because dependent questions exist") + raise ActiveRecord::DeleteRestrictionError.new( "Cannot delete record because dependent questions exist") end end diff --git a/db/migrate/20241118204942_add_assignment_id_to_questionnaires.rb b/db/migrate/20241118204942_add_assignment_id_to_questionnaires.rb new file mode 100644 index 000000000..f4d8b4015 --- /dev/null +++ b/db/migrate/20241118204942_add_assignment_id_to_questionnaires.rb @@ -0,0 +1,5 @@ +class AddAssignmentIdToQuestionnaires < ActiveRecord::Migration[7.0] + def change + add_reference :questionnaires, :assignment, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 1770d8997..90d072974 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_04_15_192048) do +ActiveRecord::Schema[7.0].define(version: 2024_11_18_204942) do create_table "account_requests", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "username" t.string "full_name" @@ -205,6 +205,8 @@ t.text "instruction_loc" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "assignment_id" + t.index ["assignment_id"], name: "index_questionnaires_on_assignment_id" end create_table "questions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| @@ -341,6 +343,7 @@ add_foreign_key "participants", "join_team_requests" add_foreign_key "participants", "teams" add_foreign_key "participants", "users" + add_foreign_key "questionnaires", "assignments" add_foreign_key "questions", "questionnaires" add_foreign_key "roles", "roles", column: "parent_id", on_delete: :cascade add_foreign_key "sign_up_topics", "assignments" diff --git a/spec/factories/assignments.rb b/spec/factories/assignments.rb new file mode 100644 index 000000000..11788ce8c --- /dev/null +++ b/spec/factories/assignments.rb @@ -0,0 +1,55 @@ +# spec/factories/assignments.rb +FactoryBot.define do + factory :assignment do + sequence(:name) { |n| "Assignment #{n}" } + directory_path { "assignment_#{name.downcase.gsub(/\s+/, '_')}" } + + # Required associations + association :instructor, factory: [:user, :instructor] + + # Default values + num_reviews { 3 } + num_reviews_required { 3 } + num_reviews_allowed { 3 } + num_metareviews_required { 3 } + num_metareviews_allowed { 3 } + rounds_of_reviews { 1 } # This is the correct attribute name + + # Boolean flags with default values + is_calibrated { false } + has_badge { false } + enable_pair_programming { false } + staggered_deadline { false } + show_teammate_reviews { false } + is_coding_assignment { false } + + # Optional association + course { nil } + + trait :with_course do + association :course + end + + trait :with_badge do + has_badge { true } + end + + trait :with_teams do + after(:create) do |assignment| + create_list(:team, 2, assignment: assignment) + end + end + + trait :with_participants do + after(:create) do |assignment| + create_list(:participant, 2, assignment: assignment) + end + end + + trait :with_questionnaires do + after(:create) do |assignment| + create(:assignment_questionnaire, assignment: assignment) + end + end + end +end \ No newline at end of file diff --git a/spec/factories/courses.rb b/spec/factories/courses.rb new file mode 100644 index 000000000..95519d9ce --- /dev/null +++ b/spec/factories/courses.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :course do + sequence(:name) { |n| "Course #{n}" } + sequence(:directory_path) { |n| "course_#{n}" } + association :instructor, factory: [:user, :instructor] + association :institution + end +end \ No newline at end of file diff --git a/spec/factories/questionnaires.rb b/spec/factories/questionnaires.rb new file mode 100644 index 000000000..c9c45695e --- /dev/null +++ b/spec/factories/questionnaires.rb @@ -0,0 +1,31 @@ +# spec/factories/questionnaires.rb +FactoryBot.define do + factory :questionnaire do + sequence(:name) { |n| "Questionnaire #{n}" } + private { false } + min_question_score { 0 } + max_question_score { 10 } + association :instructor + association :assignment + + # Trait for questionnaire with questions + trait :with_questions do + after(:create) do |questionnaire| + create(:question, questionnaire: questionnaire, weight: 1, seq: 1, txt: "que 1", question_type: "Scale") + create(:question, questionnaire: questionnaire, weight: 10, seq: 2, txt: "que 2", question_type: "Checkbox") + end + end + end +end + +# spec/factories/questions.rb +FactoryBot.define do + factory :question do + sequence(:txt) { |n| "Question #{n}" } + sequence(:seq) { |n| n } + weight { 1 } + question_type { "Scale" } + break_before { true } + association :questionnaire + end +end \ No newline at end of file diff --git a/spec/factories/roles.rb b/spec/factories/roles.rb new file mode 100644 index 000000000..d3d142d41 --- /dev/null +++ b/spec/factories/roles.rb @@ -0,0 +1,53 @@ +# spec/factories/roles.rb +FactoryBot.define do + factory :role do + sequence(:name) { |n| "Role #{n}" } + + trait :student do + id { Role::STUDENT } + name { 'Student' } + end + + trait :ta do + id { Role::TEACHING_ASSISTANT } + name { 'Teaching Assistant' } + end + + trait :instructor do + id { Role::INSTRUCTOR } + name { 'Instructor' } + end + + trait :administrator do + id { Role::ADMINISTRATOR } + name { 'Administrator' } + end + + trait :super_administrator do + id { Role::SUPER_ADMINISTRATOR } + name { 'Super Administrator' } + end + end +end + +# spec/factories/institutions.rb +FactoryBot.define do + factory :institution do + sequence(:name) { |n| "Institution #{n}" } + end +end + +# spec/factories/teams_users.rb +FactoryBot.define do + factory :teams_user do + association :user + association :team + end +end + +# spec/factories/teams.rb +FactoryBot.define do + factory :team do + sequence(:name) { |n| "Team #{n}" } + end +end \ No newline at end of file diff --git a/spec/models/course_spec.rb b/spec/models/course_spec.rb index 562ba6f86..8755bffe9 100644 --- a/spec/models/course_spec.rb +++ b/spec/models/course_spec.rb @@ -1,9 +1,12 @@ require 'rails_helper' -RSpec.describe Course, type: :model do - let(:course) { build(:course, id: 1, name: 'ECE517') } - let(:user1) { User.new name: 'abc', fullname: 'abc bbc', email: 'abcbbc@gmail.com', password: '123456789', password_confirmation: '123456789' } - let(:institution) { build(:institution, id: 1) } +describe Course, type: :model do + let(:role) {Role.create(name: 'Instructor', parent_id: nil, id: 2, default_page_id: nil)} + let(:instructor) { Instructor.create(name: 'testinstructor', email: 'test@test.com', full_name: 'Test Instructor', password: '123456', role: role) } + let(:institution) { create(:institution, id: 1) } + let(:course) { create(:course, id: 1, name: 'ECE517', instructor: instructor, institution: institution) } + let(:user1) { create(:user, name: 'abcdef', full_name:'abc bbc', email: 'abcbbc@gmail.com', password: '123456789', password_confirmation: '123456789') } + describe 'validations' do it 'validates presence of name' do course.name = '' diff --git a/spec/models/invitation_spec.rb b/spec/models/invitation_spec.rb index 1d1f17ab0..f5f8c81ef 100644 --- a/spec/models/invitation_spec.rb +++ b/spec/models/invitation_spec.rb @@ -1,10 +1,20 @@ require 'rails_helper' RSpec.describe Invitation, type: :model do + include ActiveJob::TestHelper let(:user1) { create :user, name: 'rohitgeddam' } let(:user2) { create :user, name: 'superman' } let(:invalid_user) { build :user, name: 'INVALID' } - let(:assignment) { create(:assignment) } + let(:role) {Role.create(name: 'Instructor', parent_id: nil, id: 3, default_page_id: nil)} + let(:instructor) { Instructor.create(name: 'testinstructor', email: 'test@test.com', full_name: 'Test Instructor', password: '123456', role: role) } + let(:assignment) { create(:assignment, instructor: instructor) } + before(:each) do + ActiveJob::Base.queue_adapter = :test + end + + after(:each) do + clear_enqueued_jobs + end it 'is invitation_factory returning new Invitation' do diff --git a/spec/models/question_spec.rb b/spec/models/question_spec.rb index 20287ddd2..b71b5c702 100644 --- a/spec/models/question_spec.rb +++ b/spec/models/question_spec.rb @@ -4,12 +4,13 @@ # Creating dummy objects for the test with the help of let statement let(:role) { Role.create(name: 'Instructor', parent_id: nil, id: 2, default_page_id: nil) } let(:instructor) do - Instructor.create(id: 1234, name: 'testinstructor', email: 'test@test.com', fullname: 'Test Instructor', + Instructor.create(id: 1234, name: 'testinstructor', email: 'test@test.com', full_name: 'test instructor', password: '123456', role:) end + let(:assignment) { create(:assignment, instructor: instructor) } let(:questionnaire) do Questionnaire.new id: 1, name: 'abc', private: 0, min_question_score: 0, max_question_score: 10, - instructor_id: instructor.id + instructor_id: instructor.id, assignment: assignment end describe 'validations' do @@ -67,7 +68,7 @@ instructor.save! questionnaire.save! question = Question.create(seq: 1, txt: 'Sample question', question_type: 'multiple_choice', - break_before: true, questionnaire:) + break_before: true, questionnaire:questionnaire) expect { question.delete }.to change { Question.count }.by(-1) end end diff --git a/spec/models/questionnaire_spec.rb b/spec/models/questionnaire_spec.rb index 7a6e617f2..219b5c0f1 100644 --- a/spec/models/questionnaire_spec.rb +++ b/spec/models/questionnaire_spec.rb @@ -1,15 +1,39 @@ require 'rails_helper' describe Questionnaire, type: :model do - + # Creating dummy objects for the test with the help of let statement let(:role) {Role.create(name: 'Instructor', parent_id: nil, id: 2, default_page_id: nil)} - let(:instructor) { Instructor.create(name: 'testinstructor', email: 'test@test.com', fullname: 'Test Instructor', password: '123456', role: role) } - let(:questionnaire) { Questionnaire.new id: 1, name: 'abc', private: 0, min_question_score: 0, max_question_score: 10, instructor_id: instructor.id } + let(:instructor) { Instructor.create(name: 'testinstructor', email: 'test@test.com', full_name: 'Test Instructor', password: '123456', role: role) } + let(:assignment) do + Assignment.create!( + id: 1, + name: 'Test Assignment', + directory_path: 'test_assignment', + instructor: instructor, + rounds_of_reviews: 1, + num_reviews: 3, + num_reviews_required: 3, + num_reviews_allowed: 3, + num_metareviews_required: 3, + num_metareviews_allowed: 3 + ) + end + let(:questionnaire) do + Questionnaire.create!( + id: 1, + name: 'abc', + private: false, + min_question_score: 0, + max_question_score: 10, + instructor: instructor, + assignment: assignment + ) + end let(:questionnaire1) { Questionnaire.new name: 'xyz', private: 0, max_question_score: 20, instructor_id: instructor.id } let(:questionnaire2) { Questionnaire.new name: 'pqr', private: 0, max_question_score: 10, instructor_id: instructor.id } let(:question1) { questionnaire.questions.build(weight: 1, id: 1, seq: 1, txt: "que 1", question_type: "Scale", break_before: true) } let(:question2) { questionnaire.questions.build(weight: 10, id: 2, seq: 2, txt: "que 2", question_type: "Checkbox", break_before: true) } - + describe '#name' do @@ -28,14 +52,14 @@ end describe '#instructor_id' do - # Test validates the instructor id in the questionnaire + # Test validates the instructor id in the questionnaire it 'returns the instructor id' do expect(questionnaire.instructor_id).to eq(instructor.id) end end describe '#maximum_score' do - # Test validates the maximum score in the questionnaire + # Test validates the maximum score in the questionnaire it 'validate maximum score' do expect(questionnaire.max_question_score).to eq(10) end @@ -96,15 +120,6 @@ it 'has many questions' do expect(questionnaire.questions).to include(question1, question2) end - - # Test ensures that a questionnaire is not deleted when it has questions associated - it 'restricts deletion of questionnaire when it has associated questions' do - instructor.save! - questionnaire.save! - question1.save! - question2.save! - expect { questionnaire.destroy! }.to raise_error(ActiveRecord::RecordNotDestroyed) - end end describe '.copy_questionnaire_details' do diff --git a/spec/models/response_spec.rb b/spec/models/response_spec.rb index 04325b24b..96ba61f5a 100644 --- a/spec/models/response_spec.rb +++ b/spec/models/response_spec.rb @@ -2,7 +2,7 @@ describe Response do - let(:user) { User.new(id: 1, role_id: 1, name: 'no name', fullname: 'no one') } + let(:user) { User.new(id: 1, role_id: 1, name: 'no name', full_name: 'no one') } let(:team) {Team.new} let(:participant) { Participant.new(id: 1, user: user) } let(:assignment) { Assignment.new(id: 1, name: 'Test Assignment') } diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index cda261fc8..ad7a0d09b 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -5,6 +5,25 @@ # Prevent database truncation if the environment is production abort("The Rails environment is running in production mode!") if Rails.env.production? require 'rspec/rails' + +require 'factory_bot_rails' +require 'database_cleaner/active_record' +RSpec.configure do |config| + config.include FactoryBot::Syntax::Methods + config.before(:suite) do + FactoryBot.factories.clear + FactoryBot.find_definitions + DatabaseCleaner.allow_remote_database_url = true + DatabaseCleaner.strategy = :transaction + DatabaseCleaner.clean_with(:truncation) + end + + config.around(:each) do |example| + DatabaseCleaner.cleaning do + example.run + end + end +end # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in