Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a8928d9
added unit test code to cover Questionnaire model to 100%
darrinj22 Apr 16, 2026
874cdf9
fixed small foreign key bug in question advice model
darrinj22 Apr 16, 2026
2a9d4ae
Merge pull request #1 from MATTMINWIN/djhanse2/questionnaire_unit_tests
darrinj22 Apr 17, 2026
3f69294
course model to 100% coverage; fixed small bug
darrinj22 Apr 18, 2026
c6e02f9
Merge pull request #10 from MATTMINWIN/djhanse2/course_unit_tests
darrinj22 Apr 18, 2026
1271bff
added unit testing for question advice
Apr 19, 2026
cd6c0ec
Update spec/models/question_advice_spec.rb
MATTMINWIN Apr 21, 2026
c96bd3d
Update spec/models/question_advice_spec.rb
MATTMINWIN Apr 21, 2026
a036e98
Update spec/models/question_advice_spec.rb
MATTMINWIN Apr 21, 2026
35dceee
Update spec/models/question_advice_spec.rb
MATTMINWIN Apr 21, 2026
391384f
Update spec/models/question_advice_spec.rb
MATTMINWIN Apr 21, 2026
3ea38bf
Apply suggestion from @darrinj22
MATTMINWIN Apr 21, 2026
ddf45bd
Apply suggestion from @darrinj22
MATTMINWIN Apr 21, 2026
439d4cf
Merge pull request #11 from MATTMINWIN/mmnguye5/fix_question_advice
MATTMINWIN Apr 21, 2026
8257c01
fixed bug in course model and added testing for it
darrinj22 Apr 21, 2026
16ee937
Merge pull request #13 from MATTMINWIN/djhanse2/fix_course_participan…
darrinj22 Apr 21, 2026
d88fee8
Merge branch 'expertiza:main' into main
z0brooks Apr 24, 2026
48b56f6
Few line changes regarding to a few minor bugs
Apr 27, 2026
5ef501f
Merge pull request #15 from MATTMINWIN/mmnguye5/fix_minor_errors
MATTMINWIN Apr 27, 2026
e98ee7b
Update README.md with testing instructions
z0brooks Apr 28, 2026
3415e6d
Update README.md
z0brooks Apr 28, 2026
5eb0a94
Revert README.md
z0brooks May 4, 2026
8b14c34
Just a small test description with QuestionAdvice fix
May 4, 2026
e394ab3
Merge pull request #16 from MATTMINWIN/mmnguye5/fix_minor_description
MATTMINWIN May 4, 2026
b543318
Fixed an error message in Course.rb
May 4, 2026
0c694d9
Merge pull request #17 from MATTMINWIN/mmnguye5/fix_minor_description
MATTMINWIN May 4, 2026
d3f9bf1
Removed duplicate Instructor factory
darrinj22 May 4, 2026
f7a8c9e
Merge pull request #18 from MATTMINWIN/djhanse2/cleanup_instructor_fa…
darrinj22 May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/models/course.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Course < ApplicationRecord
validates :name, presence: true
validates :directory_path, presence: true
has_many :participants, class_name: 'CourseParticipant', foreign_key: 'parent_id', dependent: :destroy, inverse_of: :course
has_many :users, through: :course_participants, inverse_of: :course
has_many :users, through: :participants
has_many :ta_mappings, dependent: :destroy
has_many :tas, through: :ta_mappings, source: :ta
has_many :teams, class_name: 'CourseTeam', foreign_key: 'parent_id', dependent: :destroy, inverse_of: :course
Expand All @@ -21,7 +21,7 @@ def path
# Add a Teaching Assistant to the course
def add_ta(user)
if user.nil?
return { success: false, message: "The user with id #{user.id} does not exist" }
return { success: false, message: "User passed does not exist" }
elsif TaMapping.exists?(user_id: user.id, course_id: id)
return { success: false, message: "The user with id #{user.id} is already a TA for this course." }
else
Expand All @@ -38,7 +38,7 @@ def add_ta(user)

# Removes Teaching Assistant from the course
def remove_ta(user_id)
ta_mapping = ta_mappings.find_by(user_id: user_id, course_id: :id)
ta_mapping = ta_mappings.find_by(user_id: user_id, course_id: id)
return { success: false, message: "No TA mapping found for the specified course and TA" } if ta_mapping.nil?
ta = User.find(ta_mapping.user_id)
ta_count = TaMapping.where(user_id: user_id).size - 1
Expand Down
2 changes: 1 addition & 1 deletion app/models/question_advice.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

class QuestionAdvice < ApplicationRecord
belongs_to :item
belongs_to :item, foreign_key: :question_id
def self.export_fields(_options)
QuestionAdvice.columns.map(&:name)
end
Expand Down
2 changes: 1 addition & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions spec/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
FactoryBot.define do
factory :student_task do
assignment { nil }
current_stage { "MyString" }
current_stage { 'MyString' }
participant { nil }
stage_deadline { "2024-04-15 15:55:54" }
topic { "MyString" }
stage_deadline { '2024-04-15 15:55:54' }
topic { 'MyString' }
end

factory :join_team_request do
end

factory :bookmark do
url { "MyText" }
title { "MyText" }
description { "MyText" }
url { 'MyText' }
title { 'MyText' }
description { 'MyText' }
user_id { 1 }
topic_id { 1 }
end
Expand Down
2 changes: 1 addition & 1 deletion spec/factories/courses.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
private { false }
directory_path { Faker::File.dir }
association :institution, factory: :institution
association :instructor, factory: :user
association :instructor, factory: :instructor
end
end
11 changes: 11 additions & 0 deletions spec/factories/question_advice.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

# spec/factories/question_advice.rb
FactoryBot.define do
factory :question_advice do
score {5}
association :item
advice {'default advice'}
end

end
3 changes: 1 addition & 2 deletions spec/factories/questionnaires.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
private { false }
min_question_score { 0 }
max_question_score { 10 }
association :instructor
association :assignment
association :instructor, factory: :instructor

# Trait for questionnaire with questions
trait :with_questions do
Expand Down
109 changes: 104 additions & 5 deletions spec/models/course_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,47 @@
require 'rails_helper'

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_id: role.id) }
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', role: role) }
before do
allow_any_instance_of(User).to receive(:set_defaults)
end

let(:instructor) { create(:instructor) }
let(:institution) { create(:institution) }
let(:course) { create(:course, name: 'ECE517', instructor: instructor, institution: institution) }
let(:user1) { create(:user, full_name: 'abc bbc', role: create(:role, :instructor)) }

describe 'validations' do
# Ensures the course requires a name to be valid.
it 'validates presence of name' do
course.name = ''
expect(course).not_to be_valid
end
# Ensures the course requires a directory path to be valid.
it 'validates presence of directory_path' do
course.directory_path = ' '
expect(course).not_to be_valid
end
end

describe 'associations' do
# Ensures users are reachable through course participants.
it 'returns users through participants' do
student = create(:user, :student)
create(:course_participant, course: course, user: student)

expect(course.users).to include(student)
end
end
describe '#path' do
context 'when there is no associated instructor' do
# Raises an error if path is requested without an instructor.
it 'an error is raised' do
allow(course).to receive(:instructor_id).and_return(nil)
expect { course.path }.to raise_error('Path can not be created as the course must be associated with an instructor.')
end
end
context 'when there is an associated instructor' do
# Returns a directory path when instructor and institution are present.
it 'returns a directory' do
allow(course).to receive(:instructor_id).and_return(6)
allow(User).to receive(:find).with(6).and_return(user1)
Expand All @@ -37,4 +54,86 @@
end
end

describe '#add_ta' do
let(:ta_role) { create(:role, name: 'Teaching Assistant') }
let(:ta_user) { create(:user, :ta) }

# Adds a TA mapping and updates the user role to Teaching Assistant.
it 'adds a TA and updates their role' do
ta_role
result = course.add_ta(ta_user)

expect(result[:success]).to be(true)
expect(result[:data]).to include('course_id' => course.id, 'user_id' => ta_user.id)
expect(ta_user.reload.role.name).to eq('Teaching Assistant')
end

# Prevents adding a user who is already a TA for the course.
it 'returns an error when the user is already a TA for the course' do
TaMapping.create!(user_id: ta_user.id, course_id: course.id)
result = course.add_ta(ta_user)

expect(result[:success]).to be(false)
expect(result[:message]).to eq("The user with id #{ta_user.id} is already a TA for this course.")
end

# Returns a failure response when the user does not exist.
it 'returns an error when the user is nil' do
result = course.add_ta(nil)

expect(result[:success]).to be(false)
expect(result[:message]).to eq('The user with id does not exist')
end

# Returns validation errors when the TA mapping cannot be saved.
it 'returns mapping errors when save fails' do
ta_role
ta_mapping = instance_double(TaMapping, save: false, errors: 'invalid mapping')
allow(TaMapping).to receive(:create).and_return(ta_mapping)

result = course.add_ta(ta_user)

expect(result[:success]).to be(false)
expect(result[:message]).to eq('invalid mapping')
end
end

describe '#remove_ta' do
let(:student_role) { create(:role, name: 'Student') }
let(:ta_user) { create(:user, :ta) }

# Returns an error when no TA mapping exists for the user.
it 'returns an error when no mapping exists' do
result = course.remove_ta(ta_user.id)
expect(result[:success]).to be(false)
expect(result[:message]).to eq('No TA mapping found for the specified course and TA')
end

# Removes the TA mapping and downgrades the role when it is the last assignment.
it 'removes the mapping and downgrades role when it is the last mapping' do
ta_mapping = TaMapping.create!(user_id: ta_user.id, course_id: course.id)
allow(course.ta_mappings).to receive(:find_by).and_return(ta_mapping)
allow(TaMapping).to receive(:where).with(user_id: ta_user.id).and_return([ta_mapping])
stub_const('Role::STUDENT', student_role)

result = course.remove_ta(ta_user.id)

expect(result[:success]).to be(true)
expect(result[:ta_name]).to eq(ta_user.name)
expect(ta_user.reload.role).to eq(student_role)
end
Comment thread
coderabbitai[bot] marked this conversation as resolved.
end

describe '#copy_course' do
# Creates a duplicate course with updated name and directory path.
it 'creates a copied course with updated name and directory_path' do
result = course.copy_course

expect(result).to be(true)
copied_course = Course.find_by(name: "#{course.name}_copy")
expect(copied_course).not_to be_nil
expect(copied_course.directory_path).to eq("#{course.directory_path}_copy")
end
end

end
118 changes: 118 additions & 0 deletions spec/models/question_advice_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@

require 'rails_helper'
describe QuestionAdvice, type: :model do

before do
allow_any_instance_of(User).to receive(:set_defaults)
end
let(:instructor) { create(:instructor) }
let(:questionnaire) do
create(:questionnaire,
name: 'abc',
private: false,
min_question_score: 0,
max_question_score: 10,
instructor: instructor)
end

let(:question1) do
create(:item, questionnaire: questionnaire, weight: 1, seq: 1, txt: 'que 1', question_type: 'scale')
end
let(:question2) do
create(:item, questionnaire: questionnaire, weight: 10, seq: 2, txt: 'que 2', question_type: 'multiple_choice')
end

let(:question_advice1) do
create(:question_advice, question_id: question1.id)
end

let(:question_advice2) do
create(:question_advice, question_id: question2.id, advice: 'advice for question 2', score: 6)
end

let(:question_advice3) do
create(:question_advice, question_id: question1.id, advice: 'advice for question 3', score: 1)
end

describe '#question association' do
it 'returns the associated question of QuestionAdvice' do
expect(question_advice1.question_id).to eq(question1.id)
expect(question_advice2.question_id).to eq(question2.id)
expect(question_advice3.question_id).to eq(question1.id)
end

end

describe '#score' do
it 'returns the score of QuestionAdvice' do
expect(question_advice1.score).to eq(5)
expect(question_advice2.score).to eq(6)
expect(question_advice3.score).to eq(1)
end

end

describe '#advice' do
it 'returns the advice of QuestionAdvice' do
expect(question_advice1.advice).to eq('default advice')
expect(question_advice2.advice).to eq('advice for question 2')
expect(question_advice3.advice).to eq('advice for question 3')
end

end

describe '#test export_fields' do
it 'make sure that the columns of QuestionAdvice are properly being mapped' do
output = QuestionAdvice.export_fields(options = {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove useless assignment in export_fields test call.

options = {} is assigned but never used after the call; pass the hash directly.

🛠️ Proposed fix
-  output = QuestionAdvice.export_fields(options = {})
+  output = QuestionAdvice.export_fields({})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
output = QuestionAdvice.export_fields(options = {})
output = QuestionAdvice.export_fields({})
🧰 Tools
🪛 RuboCop (1.86.1)

[convention] 66-66: Use 2 (not 4) spaces for indentation.

(Layout/IndentationWidth)


[warning] 66-66: Useless assignment to variable - options.

(Lint/UselessAssignment)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/models/question_advice_spec.rb` at line 66, The test unnecessarily
assigns options = {} when calling QuestionAdvice.export_fields; remove the
useless assignment and call the method with a literal hash instead by replacing
"output = QuestionAdvice.export_fields(options = {})" with "output =
QuestionAdvice.export_fields({})" (target the usage of export_fields in the spec
to make this change).

expect(output).to eq(["id", "question_id", "score", "advice", "created_at", "updated_at"])
end
end

describe '#test export' do
it 'test the export method to see if values are being properly stored ' do
csv = []

expect(question1.questionnaire).to eq(questionnaire)
expect(question2.questionnaire).to eq(questionnaire)

expect(question_advice1.advice).to eq('default advice')
expect(question_advice2.advice).to eq('advice for question 2')
expect(question_advice3.advice).to eq('advice for question 3')


QuestionAdvice.export(csv, questionnaire.id, nil)

expect(csv.length).to eq(3)
end
end

describe '#test to_json_by_question_id' do
it 'verify json output with QuestionAdvice entries associated with question 1' do

#instantiate the question_advice table
expect(question_advice1.question_id).to eq(question1.id)
expect(question_advice3.question_id).to eq(question1.id)

output = QuestionAdvice.where(question_id: question1.id).first


expect(output.question_id).to eq(question1.id)

output1 = QuestionAdvice.to_json_by_question_id(question1.id)

expect(output1).to eq([
{ score: 5, advice: 'default advice' },
{ score: 1, advice: 'advice for question 3' }
])
end

it 'verify json output with QuestionAdvice entries associated with question 2' do
expect(question_advice2.question_id).to eq(question2.id)
output = QuestionAdvice.to_json_by_question_id(question2.id)
expect(output).to eq([
{ score: 6, advice: 'advice for question 2'}
])
end

end
end
Loading
Loading