CSC/ECE 517 Fall 2023 - E2366. Reimplement assignment model and assignment controller (Phase 2): Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
 
(11 intermediate revisions by 2 users not shown)
Line 26: Line 26:
Here are the methods that we reimplemented in the Assignment Model:
Here are the methods that we reimplemented in the Assignment Model:


==valid_num_review==
 
===Model===
====valid_num_review====
The method checks if the number of reviews or metareviews required exceeds the allowed limit. This method raises an error if more reviews are required than are allowed. But it is a single method for both reviews and meta reviews. In the reimplemented method,  we have taken a new parameter - review_type which is the type of review and called the method only for the corresponding review type.
The method checks if the number of reviews or metareviews required exceeds the allowed limit. This method raises an error if more reviews are required than are allowed. But it is a single method for both reviews and meta reviews. In the reimplemented method,  we have taken a new parameter - review_type which is the type of review and called the method only for the corresponding review type.
We also removed the incorrect assignment of num_reviews_allowed to num_reviews which actually represents number of reviews done by a student for an assignment. This did not make any sense and hence was removed.
We also removed the incorrect assignment of num_reviews_allowed to num_reviews which actually represents number of reviews done by a student for an assignment. This did not make any sense and hence was removed.
Line 34: Line 36:
[[File:Valid_num_review.png ‎| 700px]]
[[File:Valid_num_review.png ‎| 700px]]


==varying_rubrics_by_round?==  
====varying_rubrics_by_round?====  
This method checks whether different rubrics are being used in different rounds of review.  It used to do it, however, by checking whether more than one rubric is listed as being used in Round 2 (used_in_round = 2). The reimplemented method checks if we have more than one rubric of a given type for an assignment. We also created migrations for adding the field used_in_round to the assignment questionnaire model to ensure the fulfilment of SOLID principles.   
This method checks whether different rubrics are being used in different rounds of review.  It used to do it, however, by checking whether more than one rubric is listed as being used in Round 2 (used_in_round = 2). The reimplemented method checks if we have more than one rubric of a given type for an assignment. We also created migrations for adding the field used_in_round to the assignment questionnaire model to ensure the fulfilment of SOLID principles.   


[[File:Varying_rubrics_by_round.png ‎| 700px]]
[[File:Varying_rubrics_by_round.png ‎| 700px]]


==has_badge?==
====has_badge?====
This method returns the value of the has_badge instance variable and this was initially known as badge?. The code is easy to read and understand. It explicitly checks if has_badge is nil and returns false in that case, otherwise returning the value of has_badge. This contributes to code that is easy to reason about. The method encapsulates the logic for checking whether an object has a badge or not. This is a good practice as it hides the implementation details from the outside world and provides a clear and readable interface.
This method returns the value of the has_badge instance variable and this was initially known as badge?. The code is easy to read and understand. It explicitly checks if has_badge is nil and returns false in that case, otherwise returning the value of has_badge. This contributes to code that is easy to reason about. This method was reimplemented as an attr_accessor as the method only returned the value of the instance variable.
 
[[File:Has_badge_new.jpeg ‎| 700px]]
 
==is_calibrated?==
This method returns the value of the is_calibrated instance variable and was initially known as calibrated?. This is a concise, more appropriately named method and it encapsulates the logic for checking if an object is calibrated. We did this to ensure that the new method it is more dry and follows SOLID principles.  


[[File:Is_calibrated.png ‎| 700px]]
====is_calibrated?====
This method returns the value of the is_calibrated instance variable and was initially known as calibrated?. We reimplemented the method as an attr_accessor as it returns the value of an instance variable. This ensured reduced redundancy in the code.


 
====current_stage(topic_id = nil)====  
==current_stage(topic_id = nil)==  
This method returns the current stage (Submission, Review, Finished, etc.) potentially based on which topic the logged-in user holds.  It returns a string, which is useful for printing. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic object oriented and SOLID principles.
This method returns the current stage (Submission, Review, Finished, etc.) potentially based on which topic the logged-in user holds.  It returns a string, which is useful for printing. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic object oriented and SOLID principles.






==current_stage_name(topic_id = nil)== This is a convoluted method that returns the same thing as current_stage, except in a few specialized cases, such as when the assignment has topics and the user hasn’t chosen a topic. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic object oriented and SOLID principles.
====current_stage_name(topic_id = nil)====
This is a convoluted method that returns the same thing as current_stage, except in a few specialized cases, such as when the assignment has topics and the user hasn’t chosen a topic. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic object oriented and SOLID principles.


==stage_deadline(topic_id = nil)==  
====stage_deadline(topic_id = nil)====  
This method is similar to current_stage, but returns the time of the next deadline unless the assignment is finished. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic SOLID principles.  
This method is similar to current_stage, but returns the time of the next deadline unless the assignment is finished. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic SOLID principles.  




==num_reviews_greater?(reviews_required, reviews_allowed)==  
====num_reviews_greater?(reviews_required, reviews_allowed)====  
This method checks whether the number of reviews required is greater than the number of reviews allowed.  Basically, it just compares two instance variables of Assignment,but handles the special cases of reviews_allowed being null or negative. In the reimplemented method, we checked if reviews_allowed is valid or not in a separate function. We used a separate function (valid_reviews_allowed?) allows for potential reusability of the validation logic in other parts of the codebase. The code snippet separates the concern of checking if reviews_allowed is valid into a dedicated method (valid_reviews_allowed?). This is done so that the new code adheres to the Single Responsibility Principle by having each method focused on a single task and hence this aligns with the SOLID principles.
This method checks whether the number of reviews required is greater than the number of reviews allowed.  Basically, it just compares two instance variables of Assignment,but handles the special cases of reviews_allowed being null or negative. In the reimplemented method, we checked if reviews_allowed is valid or not in a separate function. We used a separate function (valid_reviews_allowed?) allows for potential reusability of the validation logic in other parts of the codebase. The code snippet separates the concern of checking if reviews_allowed is valid into a dedicated method (valid_reviews_allowed?). This is done so that the new code adheres to the Single Responsibility Principle by having each method focused on a single task and hence this aligns with the SOLID principles.


Line 67: Line 65:




==quiz_allowed(topic_id = nil)==  
====quiz_allowed(topic_id = nil)====  
This method returns the quiz_allowed_id field of the next due date, i.e., whether a participant is currently allowed to take a quiz. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic SOLID principles.
This method returns the quiz_allowed_id field of the next due date, i.e., whether a participant is currently allowed to take a quiz. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic SOLID principles.




==response_map_to_metareview(metareviewer)==  
====response_map_to_metareview(metareviewer)====  
This method finds the “most appropriate” ResponseMap to use as the reviewed_object in a new MetareviewResponseMap.  However, this method had to be removed from the reimplementation as it was better suited to be a part of MetareviewResponseMap model where it can be combined with the reviewer_metareviews_map method that is basically a step of this method in order to follow basic SOLID principles.
This method finds the “most appropriate” ResponseMap to use as the reviewed_object in a new MetareviewResponseMap.  However, this method had to be removed from the reimplementation as it was better suited to be a part of MetareviewResponseMap model where it can be combined with the reviewer_metareviews_map method that is basically a step of this method in order to follow basic SOLID principles.




==review_questionnaire_id(round_number = nil, topic_id = nil)==  
====review_questionnaire_id(round_number = nil, topic_id = nil)====  
This method finds the questionnaire in use for reviews of work submitted for the assignment. This is a complicated method because it calculates to find the round number, instead of using number_of_current_round. In the reimplementation, instead of creating a new form, we can use an already existing form that is associated with the assignment. The logic behind determining a round number based on the topic can also be improved. However, this method had to be removed from the reimplementation as it was better suited to be a part of the Questionnaire model in order to follow basic SOLID principles.
This method finds the questionnaire in use for reviews of work submitted for the assignment. This is a complicated method because it calculates to find the round number, instead of using number_of_current_round. In the reimplementation, instead of creating a new form, we can use an already existing form that is associated with the assignment. The logic behind determining a round number based on the topic can also be improved. However, this method had to be removed from the reimplementation as it was better suited to be a part of the Questionnaire model in order to follow basic SOLID principles.


==reviewer_metareviews_map(response_map_set)==  
====reviewer_metareviews_map(response_map_set)====  
This is a step in finding the response_map_to_metareview. There is no need for a separate method for finding reviewer_metareviews_map. This method can be combined with response_map_to_metareview during reimplementation and moved to the MetareviewResponseMap Model.
This is a step in finding the response_map_to_metareview. There is no need for a separate method for finding reviewer_metareviews_map. This method can be combined with response_map_to_metareview during reimplementation and moved to the MetareviewResponseMap Model.


==pair_programming_enabled?==  
====pair_programming_enabled?====  
This method returns the value of the enable_pair_programming instance variable. This method is concise and straightforward. It directly calls the enable_pair_programming method, avoiding unnecessary duplication of code. There's no redundant logic or unnecessary complexity. It provides a clean interface for querying the state without exposing the internal details because we had to follow the principles of SOLID programming. We added the enabled_pair_programming field to the Assignment model through migrations.
This method returns the value of the enable_pair_programming instance variable. We reimplemented the method as an attr_accessor to reduce redundancy in the code as a method for just accessing the instance variable was not required. We added the enabled_pair_programming field to the Assignment model through migrations.


[[File:Pair_programming.png ‎| 700px]]
====staggered_and_no_topic?(topic_id)====
This method checks if the assignment is a staggered-deadline assignment and the logged-in user has not chosen a topic then it returns true otherwise it returns false. We reimplemented this method as an an attr_accessor as it was returning the value of an instance variable staggered_deadline. To ensure the code follows Single Responsibility principle, checking if the team, with which the logged-in user is associated, has chosen a topic can be implemented as a separate method in other models such as Teams.


==staggered_and_no_topic?(topic_id)==
====team_assignment?====
This method checks if the assignment is a staggered-deadline assignment and the logged-in user has not chosen a topic then it returns true otherwise it returns false. This method is concise and straightforward and ensures that SOLID principles are followed by avoiding unnecessary duplication of logical constructs and also adheres to Single responsibility principle. To verify the functionality of this method we created teams_users model and created foreign key associations between teams_users and users and teams_users and team. We also created a signed_up_teams model and the corresponding foreign key associations with sign_up_topics and teams model.
 
[[File:Staggered_and_no_topic.png ‎| 700px]]
 
==team_assignment?==
This method checks whether max_team_size > 1. It was initially checking is the max_team_size >0. It focuses on a single responsibility and follows the right naming convention. It is made more concise and straightforward to make it easy to understand and follow the SOLID principles.
This method checks whether max_team_size > 1. It was initially checking is the max_team_size >0. It focuses on a single responsibility and follows the right naming convention. It is made more concise and straightforward to make it easy to understand and follow the SOLID principles.


[[File:Team_assignment.png ‎| 700px]]
[[File:Team_assignment.png ‎| 700px]]


==teams?==
====teams?====
This method is used to check whether any team is associated with this assignment. For this, we added a has_teams field to the assignment model and a foreign key association between assignment and teams model with the help of migrations. It is made more concise and straightforward to make it easy to understand and follow the SOLID principles.
This method is used to check whether any team is associated with this assignment. For this, we added a has_teams field to the assignment model and a foreign key association between assignment and teams model with the help of migrations. It is made more concise and straightforward to make it easy to understand and follow the SOLID principles.


[[File:Teams1.png ‎| 700px]]
[[File:Teams1.png ‎| 700px]]


==topics?==
====topics?====
This method is used to check whether any topics exist for this assignment. It delegates the responsibility of determining the presence of topics to sign_up_topics. For this, we created a sign_up_topics model and has_topics field for assignment model. It is made more concise and straightforward to make it easy to understand and follow the SOLID principles.
This method is used to check whether any topics exist for this assignment. It delegates the responsibility of determining the presence of topics to sign_up_topics. For this, we created a sign_up_topics model and has_topics field for assignment model. It is made more concise and straightforward to make it easy to understand and follow the SOLID principles.


[[File:Topics.png ‎| 700px]]
[[File:Topics.png ‎| 700px]]


==create_node==  
====create_node====
This method creates an AssignmentNode for this assignment. The method thas a single responsibility: creating an AssignmentNode and associating it with a parent (CourseNode). We added a nodes model and the subclasses CourseNode and Assignment Node. This aligns with the Single responsibility principle.It is readable and clear and the use of a conditional statement enhances readability by ensuring that the assignment node is associated with a parent only when a valid parent is found. It is well-designed to ensure that the code adheres to DRY principles by reusing logic and following SOLID principles, particularly the Single Responsibility Principle and encapsulation.  
This method creates an AssignmentNode for this assignment. The method has a single responsibility: creating an AssignmentNode and associating it with a parent (CourseNode). We added a nodes model and the subclasses CourseNode and Assignment Node. This aligns with the Single responsibility principle.It is readable and clear and the use of a conditional statement enhances readability by ensuring that the assignment node is associated with a parent only when a valid parent is found. It is well-designed to ensure that the code adheres to DRY principles by reusing logic and following SOLID principles, particularly the Single Responsibility Principle and encapsulation.  


[[File:Create_node.png ‎| 700px]]
[[File:Create_node.png ‎| 700px]]


===Controller===


==Controller==
==Check if Assignment Has Badge==
Endpoint: GET /api/v1/assignments/{assignment_id}/has_badge
Description: Check if the assignment has a badge.
Input: ID of the assignment (Path Parameter)
Output: Boolean value of the has_badge field in the assignment table.
Response Body: true if the assignment has a badge, false otherwise.
[[File:Has_badge_controller.png ‎| 700px]]
==Check if Pair Programming is Enabled==
Endpoint: GET api/v1/assignments/{assignment_id}/pair_programming_enabled
Description: Check if pair programming is enabled for the assignment.
Input: ID of the assignment (Path Parameter)
Output: Boolean value of the enable_pair_programming field in the assignment table.
Response Body: true if pair programming is enabled, false otherwise.




[[File:Pair_programming_controller.png ‎| 700px]]


==Check if Assignment Has Topics==
====Check if Assignment Has Topics====
Endpoint: GET api/v1/assignments/{assignment_id}/has_topics
Endpoint: GET api/v1/assignments/{assignment_id}/has_topics
Description: Check if the assignment has topics.
Description: Check if the assignment has topics.
Line 143: Line 120:




==Check if Assignment is a Team Assignment==
====Check if Assignment is a Team Assignment====
Endpoint: GET api/v1/assignments/{assignment_id}/team_assignment
Endpoint: GET api/v1/assignments/{assignment_id}/team_assignment
Description: Check if the assignment is a team assignment.
Description: Check if the assignment is a team assignment.
Line 153: Line 130:




==Check if Assignment Has Valid Number of Reviews==
====Check if Assignment Has Valid Number of Reviews====
Endpoint: GET api/v1/assignments/{assignment_id}/valid_num_review/{{review_type}}
Endpoint: GET api/v1/assignments/{assignment_id}/valid_num_review/{{review_type}}
Description: Check if the assignment has a valid number of reviews for a specific review type.
Description: Check if the assignment has a valid number of reviews for a specific review type.
Line 162: Line 139:
[[File:Valid_num_review_controller.png ‎| 700px]]
[[File:Valid_num_review_controller.png ‎| 700px]]


==Check if Assignment is Calibrated==
Endpoint: GET, api/v1/assignments/{assignment_id}/is_calibrated
Description: Check if the assignment is calibrated.
Input: ID of the assignment (Path Parameter)
Output: Boolean value of the is_calibrated field in the assignment table.
Response Body: true if the assignment is calibrated, false otherwise.
[[File:Is_calibrated_controller.png ‎| 700px]]




==Check if Assignment Has Teams==
====Check if Assignment Has Teams====
EndpointGET api/v1/assignments/{assignment_id}/has_teams
EndpointGET api/v1/assignments/{assignment_id}/has_teams
Description: Check if the assignment has teams.
Description: Check if the assignment has teams.
Line 183: Line 150:
[[File:Has_teams_controller.png ‎| 700px]]
[[File:Has_teams_controller.png ‎| 700px]]


==Check if Assignment is Staggered and Has No Assigned Topic for Current User==
Endpoint: GET api/v1/assignments/{assignment_id}/staggered_and_no_topic
Description: Check if the assignment is staggered and has no assigned topic for the current user.
Input: ID of the assignment (Path Parameter)
Output: Boolean value indicating if the assignment is staggered and has no assigned topic.
Response Body: true if staggered and no topic assigned, false otherwise.
[[File:Staggered_controller.png ‎| 700px]]


==Create Nodes for Assignment==
====Create Nodes for Assignment====
Endpoint: POST api/v1/assignments/{assignment_id}/create_node
Endpoint: POST api/v1/assignments/{assignment_id}/create_node
Description: Create node for the assignment.
Description: Create node for the assignment.
Line 201: Line 160:
[[File:Create_node_controller.png ‎| 700px]]
[[File:Create_node_controller.png ‎| 700px]]


==Check if Assignment Has Varying Rubrics Across Rounds==
====Check if Assignment Has Varying Rubrics Across Rounds====
Endpoint: GET api/v1/assignments/{assignment_id}/varying_rubrics_by_round
Endpoint: GET api/v1/assignments/{assignment_id}/varying_rubrics_by_round
Description: Check if the assignment has varying rubrics across rounds.
Description: Check if the assignment has varying rubrics across rounds.
Line 231: Line 190:
==Test Plan==
==Test Plan==


==Testing model methods==
===Testing model methods===


We used the given test skeletons for assignment model and implement the respective Rspec test cases. For all the new methods reimplemented in the Assignment model, there are automated test cases implemented using Rspec Framework and FactoryBot. The automated testcases implemented are as follows :
We used the given test skeletons for assignment model and implement the respective Rspec test cases. For all the new methods reimplemented in the Assignment model, there are automated test cases implemented using Rspec Framework and FactoryBot. The automated testcases implemented are as follows :


==test_valid_num_review==
====test_valid_num_review====


The testcases are designed to validate the relationship between the required and allowed numbers of reviews or metareviews, depending on the specified review_type. The tests cover scenarios where review_type is either "review" or "metareview" and evaluate the method's behavior in two cases:  
The testcases are designed to validate the relationship between the required and allowed numbers of reviews or metareviews, depending on the specified review_type. The tests cover scenarios where review_type is either "review" or "metareview" and evaluate the method's behavior in two cases:  
Line 290: Line 249:
   end
   end


==test_is_calibrated?==
The test covers two scenarios:
*Context: 'when is_calibrated is true':
The test creates an Assignment object using the create(:assignment) factory. It sets is_calibrated to true. The expectation is that calling is_calibrated? on the assignment returns true.
*Context: 'when is_calibrated is false':
The test creates an Assignment object using the create(:assignment) factory. It sets is_calibrated to false. The expectation is that calling is_calibrated? on the assignment returns false.
'''
  describe '#is_calibrated?' do
    let(:assignment) { create(:assignment) }
    context 'when is_calibrated is true' do
      it 'returns true' do
        assignment.is_calibrated = true
        expect(assignment.is_calibrated?).to be true
      end
    end


    context 'when is_calibrated is false' do
====test_teams?====
      it 'returns false' do
        assignment.is_calibrated = false
        expect(assignment.is_calibrated?).to be false
      end
    end
  end
 
==test_has_badge?==
The test covers two scenarios:
*Context: 'when has_badge is true':
The test creates an Assignment object. It sets has_badge to true. The expectation is that calling has_badge? on the assignment returns true.
*Context: 'when has_badge is false':
The test creates an Assignment object. It sets has_badge to false. The expectation is that calling has_badge? on the assignment returns false.
 
'''
  describe '#has_badge?' do
    let(:assignment) { Assignment.new }
    context 'when has_badge is true' do
      it 'returns true' do
        assignment.has_badge = true
        expect(assignment.has_badge?).to be true
      end
    end
 
    context 'when has_badge is false' do
      it 'returns false' do
        assignment.has_badge = false
        expect(assignment.has_badge?).to be false
      end
    end
  end
 
==test_pair_programming_enabled?==
The test is organized into two contexts:
*Context: "when pair programming is enabled":
The test sets up an Assignment object using the create(:assignment) factory. It uses a before block to enable pair programming (assignment.enable_pair_programming = true) before each test in this context. The test asserts that when pair_programming_enabled? is called on the assignment, it should return true.
*Context: "when pair programming is disabled":
The test sets up an Assignment object using the create(:assignment) factory. It uses a before block to disable pair programming (assignment.enable_pair_programming = false) before each test in this context. The test asserts that when pair_programming_enabled? is called on the assignment, it should return false.
 
 
'''
  describe "pair_programming_enabled?" do
    let(:assignment) {create(:assignment)}
    context "when pair programming is enabled" do
      before do
        # Enable pair programming before each test in this context
        assignment.enable_pair_programming = true
      end
      it "returns true" do
        expect(assignment.pair_programming_enabled?).to eq(true)
      end
    end
 
    context "when pair programming is disabled" do
      before do
        # Disable pair programming before each test in this context
        assignment.enable_pair_programming=false
        # You may need a method to disable pair programming if it's not the inverse of enable_pair_programming
      end
      it "returns false" do
        expect(assignment.pair_programming_enabled?).to eq(false)
      end
    end
  end
 
==test_staggered_and_no_topic?(topic_id)==
The test covers various scenarios based on the combination of staggered deadlines and the presence of a topic_id. Here's a summary:
*Context: "when staggered deadline is enabled and topic_id is not provided":
The test creates an Assignment object using the create(:assignment) factory. It sets up a scenario where staggered deadlines are enabled and no topic_id is provided. The test expects the staggered_and_no_topic? method to return true.
*Context: "when staggered deadline is enabled and topic_id is provided":
Similar to the previous context, but with a scenario where a topic_id is provided. The test expects the staggered_and_no_topic? method to return false.
*Context: "when staggered deadline is disabled and topic_id is not provided":
The test sets up a scenario where staggered deadlines are disabled and no topic_id is provided. The test expects the staggered_and_no_topic? method to return false.
*Context: "when staggered deadline is disabled and topic_id is provided":
Similar to the previous context, but with staggered deadlines disabled and a topic_id provided. The test expects the staggered_and_no_topic? method to return false.
 
'''
  describe "staggered_and_no_topic?" do
    let(:assignment) {create(:assignment)}
    context "when staggered deadline is enabled and topic_id is not provided" do
      it "returns true" do
        allow(assignment).to receive(:staggered_deadline?).and_return(true)
        expect(assignment.staggered_and_no_topic?(nil)).to eq(true)
      end
    end
 
    context "when staggered deadline is enabled and topic_id is provided" do
      it "returns false" do
        allow(assignment).to receive(:staggered_deadline?).and_return(true)
        expect(assignment.staggered_and_no_topic?("some_topic_id")).to eq(false)
      end
    end
 
    context "when staggered deadline is disabled and topic_id is not provided" do
      it "returns false" do
        allow(subject).to receive(:staggered_deadline?).and_return(false)
        expect(assignment.staggered_and_no_topic?(nil)).to eq(false)
      end
    end
 
    context "when staggered deadline is disabled and topic_id is provided" do
      it "returns false" do
        allow(assignment).to receive(:staggered_deadline?).and_return(false)
        expect(assignment.staggered_and_no_topic?("some_topic_id")).to eq(false)
      end
    end
  end
 
==test_teams?==
The test covers two scenarios:
The test covers two scenarios:
*Context: 'when teams are associated with the assignment':
*Context: 'when teams are associated with the assignment':
Line 442: Line 275:
   end
   end


==test_topics?==
====test_topics?====
The test covers two scenarios:
The test covers two scenarios:
*Context: 'when no topic is associated with the assignment':
*Context: 'when no topic is associated with the assignment':
Line 469: Line 302:
   end
   end


==test_create_node==
====test_create_node====
The test covers two scenarios:
The test covers two scenarios:
*Context: "when the parent node exists":
*Context: "when the parent node exists":
Line 513: Line 346:
   end
   end


==test_varying_rubrics_by_round?==
====test_varying_rubrics_by_round?====
The test is divided into two contexts:
The test is divided into two contexts:
*Context 'when rubrics with specified rounds are present':
*Context 'when rubrics with specified rounds are present':
Line 540: Line 373:
   end
   end


==test_ team_assignment?==
====test_ team_assignment?====
The test covers three scenarios:
The test covers three scenarios:
*Context: 'when max_team_size is greater than 0':
*Context: 'when max_team_size is greater than 0':
Line 575: Line 408:
   end
   end


===Controller test plan===


==Controller test plan==
====Add Participant to Assignment====
 
==Add Participant to Assignment==
Endpoint: POST /api/v1/assignments/{assignment_id}/add_participant/{user_id}
Endpoint: POST /api/v1/assignments/{assignment_id}/add_participant/{user_id}


Line 624: Line 456:




==Remove Participant from Assignment==
====Remove Participant from Assignment====
Endpoint: DELETE /api/v1/assignments/{assignment_id}/remove_participant/{user_id}
Endpoint: DELETE /api/v1/assignments/{assignment_id}/remove_participant/{user_id}


Line 673: Line 505:
     end
     end


==Assign Course to Assignment==
====Assign Course to Assignment====
Endpoint: PATCH /api/v1/assignments/{assignment_id}/assign_courses_to_assignment/{course_id}
Endpoint: PATCH /api/v1/assignments/{assignment_id}/assign_courses_to_assignment/{course_id}


Line 721: Line 553:
     end
     end


==Remove Assignment from Course==
====Remove Assignment from Course====
Endpoint: PATCH /api/v1/assignments/{assignment_id}/remove_assignment_from_course
Endpoint: PATCH /api/v1/assignments/{assignment_id}/remove_assignment_from_course


Line 770: Line 602:
     end
     end


==Copy Assignment==
====Copy Assignment====
Endpoint: POST /api/v1/assignments/{assignment_id}/copy_assignment
Endpoint: POST /api/v1/assignments/{assignment_id}/copy_assignment


Line 814: Line 646:
     end
     end


==Delete Assignment==
====Delete Assignment====
Endpoint: DELETE /api/v1/assignments/{id}
Endpoint: DELETE /api/v1/assignments/{id}


Line 858: Line 690:
     end
     end


==Check if Assignment has a Badge==
Endpoint: GET /api/v1/assignments/{assignment_id}/has_badge
Test Scenario:
Check if an assignment has a badge.
Attempt to check a non-existent assignment for a badge.
Expected Outcome:
HTTP status 200, assignment has a badge.
HTTP status 404, assignment not found.
'''
get('Check if an assignment has a badge') do
      tags 'Assignments'
      produces 'application/json'
      parameter name: 'Authorization', in: :header, type: :string
      parameter name: 'Content-Type', in: :header, type: :string
      let('Authorization') { "Bearer #{auth_token}" }
      let('Content-Type') { 'application/json' }
      response(200, 'successful') do
        let(:assignment) { create(:assignment) }
        let(:assignment_id) { assignment.id }


        run_test! do |response|
          expect(response).to have_http_status(:ok)
        end
      end
      response(404, 'Assignment not found') do
        let(:assignment_id) { 999 } # Non-existent ID


        run_test! do |response|
====Check if Assignment has Topics====
          data = JSON.parse(response.body)
          expect(data['error']).to eq('Assignment not found')
        end
      end
    end
 
==Check if Pair Programming is Enabled==
Endpoint: GET /api/v1/assignments/{assignment_id}/pair_programming_enabled
 
Test Scenario:
 
Check if pair programming is enabled for an assignment.
 
Attempt to check a non-existent assignment for pair programming.
 
Expected Outcome:
 
HTTP status 200, pair programming is enabled.
 
HTTP status 404, assignment not found.
'''
get('Check if pair programming is enabled for an assignment') do
      tags 'Assignments'
      produces 'application/json'
      parameter name: 'Authorization', in: :header, type: :string
      parameter name: 'Content-Type', in: :header, type: :string
      let('Authorization') { "Bearer #{auth_token}" }
      let('Content-Type') { 'application/json' }
 
      response(200, 'successful') do
        let(:assignment) { create(:assignment) }
        let(:assignment_id) { assignment.id }
 
        run_test! do |response|
          expect(response).to have_http_status(:ok)
        end
      end
 
      response(404, 'Assignment not found') do
        let(:assignment_id) { 999 } # Non-existent ID
 
        run_test! do |response|
          expect(response).to have_http_status(:not_found)
        end
      end
    end
 
==Check if Assignment has Topics==
Endpoint: GET /api/v1/assignments/{assignment_id}/has_topics
Endpoint: GET /api/v1/assignments/{assignment_id}/has_topics


Line 983: Line 734:
     end
     end


==Check if Assignment is a Team Assignment==
====Check if Assignment is a Team Assignment====
Endpoint: GET /api/v1/assignments/{assignment_id}/team_assignment
Endpoint: GET /api/v1/assignments/{assignment_id}/team_assignment


Line 1,025: Line 776:
     end
     end


==Check if Assignment has a Valid Number of Reviews==
====Check if Assignment has a Valid Number of Reviews====
Endpoint: GET /api/v1/assignments/{assignment_id}/valid_num_review/{review_type}
Endpoint: GET /api/v1/assignments/{assignment_id}/valid_num_review/{review_type}


Line 1,068: Line 819:
       end
       end


==Check if Assignment is Calibrated==
Endpoint: GET /api/v1/assignments/{assignment_id}/is_calibrated
Test Scenario:
Check if an assignment is calibrated.
Attempt to check a non-existent assignment for calibration.
Expected Outcome:
HTTP status 200, assignment is calibrated.
HTTP status 404, assignment not found.
'''
get('Check if an assignment is calibrated') do
      tags 'Assignments'
      produces 'application/json'
      parameter name: 'Authorization', in: :header, type: :string
      parameter name: 'Content-Type', in: :header, type: :string
      let('Authorization') { "Bearer #{auth_token}" }
      let('Content-Type') { 'application/json' }
      response(200, 'successful') do
        let(:assignment) { create(:assignment) }
        let(:assignment_id) { assignment.id }
        run_test! do |response|
          expect(response).to have_http_status(:ok)
        end
      end
      response(404, 'Assignment not found') do
        let(:assignment_id) { 999 } # Non-existent ID
        run_test! do |response|
          data = JSON.parse(response.body)
          expect(data['error']).to eq('Assignment not found')
        end
      end
    end


==Check if Assignment has Teams==
====Check if Assignment has Teams====
Endpoint: GET /api/v1/assignments/{assignment_id}/has_teams
Endpoint: GET /api/v1/assignments/{assignment_id}/has_teams


Line 1,152: Line 862:
     end
     end


==Check if Assignment is Staggered and Has No Topic==
 
Endpoint: GET /api/v1/assignments/{assignment_id}/staggered_and_no_topic
====Create Node for Assignment====
Endpoint: POST /api/v1/assignments/{assignment_id}/create_node


Test Scenario:
Test Scenario:
Create a node for an assignment.


Check if an assignment is staggered and has no topic.
Attempt to create a node for a non-existent assignment.
 
Attempt to check a non-existent assignment for staggered and no topic.


Expected Outcome:
Expected Outcome:


HTTP status 200, assignment is staggered and has no topic.
HTTP status 200, node created successfully.


HTTP status 404, assignment not found.
HTTP status 404, assignment not found.
'''
'''
get('Check if an assignment is staggered and has no topic') do
post('Create a node for an assignment') do
       tags 'Assignments'
       tags 'Assignments'
       produces 'application/json'
       consumes 'application/json'
       parameter name: 'Authorization', in: :header, type: :string
       parameter name: 'Authorization', in: :header, type: :string
       parameter name: 'Content-Type', in: :header, type: :string
       parameter name: 'Content-Type', in: :header, type: :string
Line 1,194: Line 906:
     end
     end


==Create Node for Assignment==
====Check if Assignment has Varying Rubrics by Round====
Endpoint: POST /api/v1/assignments/{assignment_id}/create_node
Endpoint: GET /api/v1/assignments/{assignment_id}/varying_rubrics_by_round


Test Scenario:
Test Scenario:
Create a node for an assignment.


Attempt to create a node for a non-existent assignment.
Check if an assignment has varying rubrics by round.
 
Attempt to check a non-existent assignment for varying rubrics.


Expected Outcome:
Expected Outcome:


HTTP status 200, node created successfully.
HTTP status 200, assignment has varying rubrics by round.


HTTP status 404, assignment not found.
HTTP status 404, assignment not found.
'''
parameter name: 'assignment_id', in: :path, type: :integer, description: 'Assignment ID'


'''
  get('Check if an assignment has varying rubrics by round') do
post('Create a node for an assignment') do
    tags 'Assignments'
      tags 'Assignments'
    produces 'application/json'
      consumes 'application/json'
    parameter name: 'Authorization', in: :header, type: :string
      parameter name: 'Authorization', in: :header, type: :string
    parameter name: 'Content-Type', in: :header, type: :string
      parameter name: 'Content-Type', in: :header, type: :string
    let('Authorization') { "Bearer #{auth_token}" }
      let('Authorization') { "Bearer #{auth_token}" }
    let('Content-Type') { 'application/json' }
      let('Content-Type') { 'application/json' }


      response(200, 'successful') do
    response(200, 'successful') do
        let(:assignment) { create(:assignment) }
      let(:questionnaire) { create(:questionnaire) }
        let(:assignment_id) { assignment.id }
      let(:assignment) { create(:assignment) }
      let(:assignment_id) {assignment.id}
      let(:assignment_questionnaire) { create(:assignment_questionnaire, assignment: assignment, questionnaire: questionnaire, used_in_round: 1) }
     


        run_test! do |response|
      run_test! do |response|
          expect(response).to have_http_status(:ok)
        expect(response).to have_http_status(:ok)
        end
       end
       end
    end


      response(404, 'Assignment not found') do
    response(404, 'Assignment not found') do
        let(:assignment_id) { 999 } # Non-existent ID
      let(:assignment_id) { 999 } # Non-existent ID


        run_test! do |response|
      run_test! do |response|
          data = JSON.parse(response.body)
        data = JSON.parse(response.body)
          expect(data['error']).to eq('Assignment not found')
        expect(data['error']).to eq('Assignment not found')
        end
       end
       end
     end
     end
  end


==Test Coverage==
==Test Coverage==

Latest revision as of 19:34, 14 December 2023

Expertiza

Expertiza is a Ruby on Rails based open source project. Instructors have the ability to add new projects, assignments, etc., as well as edit existing ones. Later on, they can view student submissions and grade them. Students can also use Expertiza to organize into teams to work on different projects and assignments and submit their work. They can also review other students' submissions.

Introduction

This project is a reimplementation for the assignment model and assignment controller in the Expertiza backend.

Problem Statement

The following tasks were required for the reimplementation :

  1. Enhancing Code Clarity: This involves renaming methods with ambiguous names, optimizing loops and adding comments for unclear lines of code.
  2. Removing unused methods Removing unused methods will reduce complexity.
  3. Reimplement Assignment model and controller: Reimplement the methods from Assignment model that are complex and can be implemented in a better way to reduce complexity and by not violating the DRY principles.
  4. Writing tests for assignment models: Tests have to be written for Assignment Model.
  5. Writing tests for the assignment controller: RSwag tests should be written for Assignment Controller

Design Goal

While reimplementation of Assignment model and controller, the following design rules have to be ensured:

  • Validate proper functioning of all existing and anticipated methods, making any required enhancements or adjustments.
  • Establish loose coupling and tight cohesion for the model and controller to enhance code organization and maintainability.
  • Refactor redundant code in the previous implementation's controller and model methods using DRY principle, eliminating functionality duplications already present in the expertiza system.
  • Confirm the continued effectiveness of existing test cases following the aforementioned modifications and generate additional test cases as per the need.

Class UML Diagram

Implementation

Here are the methods that we reimplemented in the Assignment Model:


Model

valid_num_review

The method checks if the number of reviews or metareviews required exceeds the allowed limit. This method raises an error if more reviews are required than are allowed. But it is a single method for both reviews and meta reviews. In the reimplemented method, we have taken a new parameter - review_type which is the type of review and called the method only for the corresponding review type. We also removed the incorrect assignment of num_reviews_allowed to num_reviews which actually represents number of reviews done by a student for an assignment. This did not make any sense and hence was removed. The num_reviews_greater? method is called with different parameters based on the review_type, avoiding code duplication in order to follow DRY principles. The reimplementation separates the concerns more explicitly. It checks for the validity of either regular reviews or metareviews based on the value of review_type.

varying_rubrics_by_round?

This method checks whether different rubrics are being used in different rounds of review. It used to do it, however, by checking whether more than one rubric is listed as being used in Round 2 (used_in_round = 2). The reimplemented method checks if we have more than one rubric of a given type for an assignment. We also created migrations for adding the field used_in_round to the assignment questionnaire model to ensure the fulfilment of SOLID principles.

has_badge?

This method returns the value of the has_badge instance variable and this was initially known as badge?. The code is easy to read and understand. It explicitly checks if has_badge is nil and returns false in that case, otherwise returning the value of has_badge. This contributes to code that is easy to reason about. This method was reimplemented as an attr_accessor as the method only returned the value of the instance variable.

is_calibrated?

This method returns the value of the is_calibrated instance variable and was initially known as calibrated?. We reimplemented the method as an attr_accessor as it returns the value of an instance variable. This ensured reduced redundancy in the code.

current_stage(topic_id = nil)

This method returns the current stage (Submission, Review, Finished, etc.) potentially based on which topic the logged-in user holds. It returns a string, which is useful for printing. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic object oriented and SOLID principles.


current_stage_name(topic_id = nil)

This is a convoluted method that returns the same thing as current_stage, except in a few specialized cases, such as when the assignment has topics and the user hasn’t chosen a topic. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic object oriented and SOLID principles.

stage_deadline(topic_id = nil)

This method is similar to current_stage, but returns the time of the next deadline unless the assignment is finished. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic SOLID principles.


num_reviews_greater?(reviews_required, reviews_allowed)

This method checks whether the number of reviews required is greater than the number of reviews allowed. Basically, it just compares two instance variables of Assignment,but handles the special cases of reviews_allowed being null or negative. In the reimplemented method, we checked if reviews_allowed is valid or not in a separate function. We used a separate function (valid_reviews_allowed?) allows for potential reusability of the validation logic in other parts of the codebase. The code snippet separates the concern of checking if reviews_allowed is valid into a dedicated method (valid_reviews_allowed?). This is done so that the new code adheres to the Single Responsibility Principle by having each method focused on a single task and hence this aligns with the SOLID principles.


quiz_allowed(topic_id = nil)

This method returns the quiz_allowed_id field of the next due date, i.e., whether a participant is currently allowed to take a quiz. However, this method had to be removed from the reimplementation as it was better suited to be a part of due date model in order to follow basic SOLID principles.


response_map_to_metareview(metareviewer)

This method finds the “most appropriate” ResponseMap to use as the reviewed_object in a new MetareviewResponseMap. However, this method had to be removed from the reimplementation as it was better suited to be a part of MetareviewResponseMap model where it can be combined with the reviewer_metareviews_map method that is basically a step of this method in order to follow basic SOLID principles.


review_questionnaire_id(round_number = nil, topic_id = nil)

This method finds the questionnaire in use for reviews of work submitted for the assignment. This is a complicated method because it calculates to find the round number, instead of using number_of_current_round. In the reimplementation, instead of creating a new form, we can use an already existing form that is associated with the assignment. The logic behind determining a round number based on the topic can also be improved. However, this method had to be removed from the reimplementation as it was better suited to be a part of the Questionnaire model in order to follow basic SOLID principles.

reviewer_metareviews_map(response_map_set)

This is a step in finding the response_map_to_metareview. There is no need for a separate method for finding reviewer_metareviews_map. This method can be combined with response_map_to_metareview during reimplementation and moved to the MetareviewResponseMap Model.

pair_programming_enabled?

This method returns the value of the enable_pair_programming instance variable. We reimplemented the method as an attr_accessor to reduce redundancy in the code as a method for just accessing the instance variable was not required. We added the enabled_pair_programming field to the Assignment model through migrations.

staggered_and_no_topic?(topic_id)

This method checks if the assignment is a staggered-deadline assignment and the logged-in user has not chosen a topic then it returns true otherwise it returns false. We reimplemented this method as an an attr_accessor as it was returning the value of an instance variable staggered_deadline. To ensure the code follows Single Responsibility principle, checking if the team, with which the logged-in user is associated, has chosen a topic can be implemented as a separate method in other models such as Teams.

team_assignment?

This method checks whether max_team_size > 1. It was initially checking is the max_team_size >0. It focuses on a single responsibility and follows the right naming convention. It is made more concise and straightforward to make it easy to understand and follow the SOLID principles.

teams?

This method is used to check whether any team is associated with this assignment. For this, we added a has_teams field to the assignment model and a foreign key association between assignment and teams model with the help of migrations. It is made more concise and straightforward to make it easy to understand and follow the SOLID principles.

topics?

This method is used to check whether any topics exist for this assignment. It delegates the responsibility of determining the presence of topics to sign_up_topics. For this, we created a sign_up_topics model and has_topics field for assignment model. It is made more concise and straightforward to make it easy to understand and follow the SOLID principles.

create_node

This method creates an AssignmentNode for this assignment. The method has a single responsibility: creating an AssignmentNode and associating it with a parent (CourseNode). We added a nodes model and the subclasses CourseNode and Assignment Node. This aligns with the Single responsibility principle.It is readable and clear and the use of a conditional statement enhances readability by ensuring that the assignment node is associated with a parent only when a valid parent is found. It is well-designed to ensure that the code adheres to DRY principles by reusing logic and following SOLID principles, particularly the Single Responsibility Principle and encapsulation.

Controller

Check if Assignment Has Topics

Endpoint: GET api/v1/assignments/{assignment_id}/has_topics Description: Check if the assignment has topics. Input: ID of the assignment (Path Parameter) Output: Boolean value of the has_topics field in the assignment table. Response Body: true if the assignment has topics, false otherwise.


Check if Assignment is a Team Assignment

Endpoint: GET api/v1/assignments/{assignment_id}/team_assignment Description: Check if the assignment is a team assignment. Input: ID of the assignment (Path Parameter) Output: Boolean value which is set based on max_team_size field in the assignment table. Response Body: true if the assignment is a team assignment, false otherwise.


Check if Assignment Has Valid Number of Reviews

Endpoint: GET api/v1/assignments/{assignment_id}/valid_num_review/Template:Review type Description: Check if the assignment has a valid number of reviews for a specific review type. Input: ID of the assignment (Path Parameter) and Type of review (Path Parameter) Output: Boolean value indicating if the number of allowed reviews is greater than required rewievs for the specified review type. Response Body: true if valid, false otherwise.


Check if Assignment Has Teams

EndpointGET api/v1/assignments/{assignment_id}/has_teams Description: Check if the assignment has teams. Input: assignment_id (Path Parameter): ID of the assignment. Output: Boolean value of the has_teams field in the assignment table. Response Body: true if the assignment has teams, false otherwise.


Create Nodes for Assignment

Endpoint: POST api/v1/assignments/{assignment_id}/create_node Description: Create node for the assignment. Input: ID of the assignment (Path Parameter) Output: Returns the created node. Response Body: JSON representation of the created node.

Check if Assignment Has Varying Rubrics Across Rounds

Endpoint: GET api/v1/assignments/{assignment_id}/varying_rubrics_by_round Description: Check if the assignment has varying rubrics across rounds. Input: assignment_id (Path Parameter): ID of the assignment. Output: Boolean value indicating if rubrics vary across rounds in the assignment. Response Body: true if rubrics vary, false otherwise.

Files Modified/Added

  1. assignment.rb
  2. asssignments_controller.rb
  3. routes.rb

The following migration files were also added :

  1. 20231129021640_add_enable_pair_programming_to_assignment.rb
  2. 20231129023417_add_assignment_to_teams.rb
  3. 20231129024913_add_has_teams_to_assignments.rb
  4. 20231129050431_create_sign_up_topics.rb
  5. 20231129051018_add_has_topics_to_assignment.rb
  6. 20231130030500_create_signed_up_teams.rb
  7. 20231130030611_add_foreign_key_to_signed_up_teams_for_sign_up_topic.rb
  8. 20231130030646_add_foreign_key_to_signed_up_teams_for_team.rb
  9. 20231130033226_create_teams_users.rb
  10. 20231130033325_add_foreign_key_to_teams_users_for_user.rb
  11. 20231130033332_add_foreign_key_to_teams_users_for_team.rb
  12. 20231201012040_add_used_in_round_to_assignment_questionnaire.rb
  13. 20231201024204_create_nodes.rb

Test Plan

Testing model methods

We used the given test skeletons for assignment model and implement the respective Rspec test cases. For all the new methods reimplemented in the Assignment model, there are automated test cases implemented using Rspec Framework and FactoryBot. The automated testcases implemented are as follows :

test_valid_num_review

The testcases are designed to validate the relationship between the required and allowed numbers of reviews or metareviews, depending on the specified review_type. The tests cover scenarios where review_type is either "review" or "metareview" and evaluate the method's behavior in two cases:

  • when the required number is less than or equal to the allowed number (expected to return success)
  • when the required number is greater than the allowed number (expected to return an error message).
  • when review type is invalid

 describe '#valid_num_review' do
   let(:assignment) { Assignment.new }
   context 'when review_type is "review"' do
     it 'returns success: true if num_reviews_required is less than or equal to num_reviews_allowed' do
       assignment.num_reviews_required = 2
       assignment.num_reviews_allowed = 5
       result = assignment.valid_num_review('review')
       expect(result[:success]).to be true
       expect(result[:message]).to be_nil
     end
     it 'returns an error message if num_reviews_required is greater than num_reviews_allowed' do
       assignment.num_reviews_required = 5
       assignment.num_reviews_allowed = 2
       result = assignment.valid_num_review('review')
       expect(result[:success]).to be false
       expect(result[:message]).to eq('Number of reviews required cannot be greater than number of reviews allowed')
     end
   end
   context 'when review_type is "metareview"' do
     it 'returns success: true if num_metareviews_required is less than or equal to num_metareviews_allowed' do
       assignment.num_metareviews_required = 2
       assignment.num_metareviews_allowed = 5
       result = assignment.valid_num_review('metareview')
       expect(result[:success]).to be true
       expect(result[:message]).to be_nil
     end
     it 'returns an error message if num_metareviews_required is greater than num_metareviews_allowed' do
       assignment.num_metareviews_required = 5
       assignment.num_metareviews_allowed = 2
       result = assignment.valid_num_review('metareview')
       expect(result[:success]).to be false
       expect(result[:message]).to eq('Number of metareviews required cannot be greater than number of reviews allowed')
     end
   end
   context 'when review_type is invalid' do
     it 'returns an error message for an invalid review_type' do
       result = assignment.valid_num_review('invalid_type')
       expect(result[:success]).to be false
       expect(result[:message]).to eq('Please enter a valid review type.')
     end
   end
 end


test_teams?

The test covers two scenarios:

  • Context: 'when teams are associated with the assignment':

The test creates an Assignment object using the create(:assignment) factory. It creates a team associated with the assignment using FactoryBot (create(:team, assignment: assignment)). The expectation is that calling teams? on the assignment returns true.

  • Context: 'when no teams are associated with the assignment':

The test creates an Assignment object using the create(:assignment) factory. The expectation is that calling teams? on the assignment returns false.

describe '#teams?' do

   let(:assignment) {create(:assignment)}
   context 'when teams are associated with the assignment' do
     it 'returns true' do
       # Create a team associated with the assignment using FactoryBot
       team = create(:team, assignment: assignment)
       expect(assignment.teams?).to be true
     end
   end
   context 'when no teams are associated with the assignment' do
     it 'returns false' do
       expect(assignment.teams?).to be false
     end
   end
 end

test_topics?

The test covers two scenarios:

  • Context: 'when no topic is associated with the assignment':

The test creates an Assignment object using the create(:assignment) factory. It sets up a scenario where no topic is associated with the given assignment. The expectation is that calling topics? on the assignment returns false.

  • Context: 'when any topic is associated with the assignment':

The test creates an Assignment object using the create(:assignment) factory. It creates a sign_up_topic associated with the assignment using FactoryBot (create(:sign_up_topic, assignment: assignment)).. The expectation is that calling topics? on the assignment returns true.


 describe '#topics?' do
   let(:assignment) { create(:assignment) }
   context 'when sign_up_topics is empty' do
     it 'returns false' do
       # Assuming sign_up_topics is an empty collection
       expect(assignment.topics?).to be false
     end
   end
   context 'when sign_up_topics is not empty' do
     it 'returns true' do
       # Assuming sign_up_topics is a non-empty collection
       sign_up_topic = create(:sign_up_topic, assignment: assignment)
       expect(assignment.topics?).to be true
     end
   end
 end

test_create_node

The test covers two scenarios:

  • Context: "when the parent node exists":

The test creates an Assignment object using the create(:assignment) factory.It stubs CourseNode.find_by to return a mock parent node. It sets up expectations for the creation of an AssignmentNode, including setting the parent_id and saving the new node. The test calls the create_node method.

  • Context: "when the parent node does not exist":

Similar to the previous context, but it stubs CourseNode.find_by to return nil (indicating no parent node). It sets up expectations for the creation of an AssignmentNode, excluding the setting of parent_id (since there is no parent), and saving the new node. The test calls the create_node method.


 describe "#create_node" do
   let(:assignment) { create(:assignment) }
   context "when the parent node exists" do
     it "creates a new assignment node with the given id, sets parent_id, and saves the new node" do
       # Stub CourseNode.find_by to return a mock parent node
       allow(CourseNode).to receive(:find_by).and_return(double("CourseNode", id: 10))
       # Expectations for AssignmentNode creation
       expect(AssignmentNode).to receive(:create).with(node_object_id: assignment.id).and_call_original
       # Expectations for any_instance of AssignmentNode
       assignment_node_instance = instance_double(AssignmentNode)
       allow(assignment_node_instance).to receive(:parent_id=)
       allow(assignment_node_instance).to receive(:save)
       expect(AssignmentNode).to receive(:new).and_return(assignment_node_instance)
       # Call the method under test
       assignment.create_node
     end
   end
   context "when the parent node does not exist" do
     it "creates a new assignment node with the given id, does not set parent_id, and saves the new node" do
       # Stub CourseNode.find_by to return nil (no parent node)
       allow(CourseNode).to receive(:find_by).and_return(nil)
       # Expectations for AssignmentNode creation
       expect(AssignmentNode).to receive(:create).with(node_object_id: assignment.id).and_call_original
       # Expectations for any_instance of AssignmentNode
       assignment_node_instance = instance_double(AssignmentNode)
       expect(assignment_node_instance).not_to receive(:parent_id=)
       allow(assignment_node_instance).to receive(:save)
       expect(AssignmentNode).to receive(:new).and_return(assignment_node_instance)
       # Call the method under test
       assignment.create_node
     end
   end
 end

test_varying_rubrics_by_round?

The test is divided into two contexts:

  • Context 'when rubrics with specified rounds are present':

The test creates an Assignment and a Questionnaire. It then creates an AssignmentQuestionnaire associating the assignment with the questionnaire and specifying that it's used in round 1. The test expects the varying_rubrics_by_round? method to return true, indicating that rubrics with specified rounds are present for the assignment.

  • Context 'when no rubrics with specified rounds are present':

The test creates an Assignment. It expects the varying_rubrics_by_round? method to return false when no rubrics with specified rounds are associated with the assignment.

 describe '#varying_rubrics_by_round?' do
   let(:assignment) { create(:assignment) }
   let(:questionnaire) {create(:questionnaire)}
   context 'when rubrics with specified rounds are present' do
     it 'returns true' do
       # Assuming rubrics with specified rounds exist for the assignment
       create(:assignment_questionnaire, assignment: assignment, questionnaire: questionnaire, used_in_round: 1)
       expect(assignment.varying_rubrics_by_round?).to be true
     end
   end
   context 'when no rubrics with specified rounds are present' do
     it 'returns false' do
       # Assuming no rubrics with specified rounds exist for the assignment
       expect(assignment.varying_rubrics_by_round?).to be false
     end
   end
 end

test_ team_assignment?

The test covers three scenarios:

  • Context: 'when max_team_size is greater than 0':

The test creates an Assignment object using the create(:assignment) factory. It sets the max_team_size attribute to a value greater than 0. The test expects that calling team_assignment? on the assignment returns true.

  • Context: 'when max_team_size is equal to 0':

Similar to the previous context, but it sets max_team_size to 0. The test expects that calling team_assignment? on the assignment returns false.

  • Context: 'when max_team_size is less than 0':

Similar to the previous contexts, but it sets max_team_size to a negative value. The test expects that calling team_assignment? on the assignment returns false.


 describe 'team_assignment?' do
   let(:assignment) { create(:assignment) }
   context 'when max_team_size is greater than 0' do
     it 'returns true' do
       assignment.max_team_size = 5
       expect(assignment.team_assignment?).to be true
     end
   end
   context 'when max_team_size is equal to 0' do
     it 'returns false' do
       assignment.max_team_size = 0
       expect(assignment.team_assignment?).to be false
     end
   end
   context 'when max_team_size is less than 0' do
     it 'returns false' do
       assignment.max_team_size = -3
       expect(assignment.team_assignment?).to be false
     end
   end
 end

Controller test plan

Add Participant to Assignment

Endpoint: POST /api/v1/assignments/{assignment_id}/add_participant/{user_id}

Test Scenario:

Add a participant to an existing assignment.

Attempt to add a participant to a non-existent assignment.

Expected Outcome:

HTTP status 200, response contains participant details.

HTTP status 404, assignment not found.

post 'Adds a participant to an assignment' do
     tags 'Assignments'
     consumes 'application/json'
     produces 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response '200', 'participant added successfully' do
       let(:assignment) { create(:assignment) }
       let(:assignment_id) { assignment.id }
       let(:user_id) { user.id }
       run_test! do
         response_json = JSON.parse(response.body) # Parse the response body as JSON
         expect(response_json['id']).to be_present
         expect(response).to have_http_status(:ok)
       end
     end
     response '404', 'assignment not found' do
       let(:assignment_id) { 3 }
       let(:user_id) { 1 }
       run_test! do
         expect(response).to have_http_status(:not_found)
       end
     end
   end


Remove Participant from Assignment

Endpoint: DELETE /api/v1/assignments/{assignment_id}/remove_participant/{user_id}

Test Scenario:

Remove a participant from an assignment.

Attempt to remove a participant from a non-existent assignment.

Expected Outcome:

HTTP status 200, participant removed successfully.

HTTP status 404, assignment or user not found. delete 'Removes a participant from an assignment' do

     tags 'Assignments'
     consumes 'application/json'
     produces 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response '200', 'participant removed successfully' do
       let(:user_id) { user.id }
       let(:assignment) {create(:assignment)}
       let(:assignment_id) {assignment.id}
       before do
         assignment.add_participant(user.id)
       end
       run_test! do
         expect(response).to have_http_status(:ok)
       end
     end
     response '404', 'assignment or user not found' do
       let(:assignment_id) { 4 }
       let(:user_id) { 1 }
       run_test! do
         expect(response).to have_http_status(:not_found)
       end
     end
   end

Assign Course to Assignment

Endpoint: PATCH /api/v1/assignments/{assignment_id}/assign_courses_to_assignment/{course_id}

Test Scenario:

Assign a course to an assignment.

Attempt to assign a course to a non-existent assignment.

Expected Outcome:

HTTP status 200, course assigned successfully.

HTTP status 404, assignment not found. patch 'Make course_id of assignment null' do

     tags 'Assignments'
     consumes 'application/json'
     produces 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response '200', 'course_id assigned successfully' do
       let(:course) { create(:course)}
       let(:course_id) { course.id }
       let(:assignment) { create(:assignment) }
       let(:assignment_id) { assignment.id }
       run_test! do
         response_json = JSON.parse(response.body) # Parse the response body as JSON
         expect(response_json['course_id']).to eq(course.id)
         expect(response).to have_http_status(:ok)
       end
     end
     response '404', 'assignment not found' do
       let(:assignment_id) { 3 }
       let(:course) { create(:course)}
       let(:course_id) {course.id}
       run_test! do
         expect(response).to have_http_status(:not_found)
       end
     end
   end

Remove Assignment from Course

Endpoint: PATCH /api/v1/assignments/{assignment_id}/remove_assignment_from_course

Test Scenario:

Remove an assignment from a course.

Attempt to remove a non-existent assignment from a course.

Expected Outcome:

HTTP status 200, assignment removed from course.

HTTP status 404, assignment not found. patch 'Removes assignment from course' do

     tags 'Assignments'
     produces 'application/json'
     parameter name: :assignment_id, in: :path, type: :integer, required: true
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }


     response '200', 'assignment removed from course' do
       let(:course) { create(:course) }
       let(:assignment) { create(:assignment,  course: course)}
       let(:assignment_id) { assignment.id }
       let(:course_id) { course.id }
       run_test! do
         response_json = JSON.parse(response.body) # Parse the response body as JSON
         expect(response_json['course_id']).to be_nil
         expect(response).to have_http_status(:ok)
       end
     end
     response '404', 'assignment not found' do
       let(:assignment_id) { 4 }
       let(:course_id) {1}
       run_test! do
         response_json = JSON.parse(response.body)
         expect(response_json['error']).to eq('Assignment not found')
         expect(response).to have_http_status(:not_found)
       end
     end
   end

Copy Assignment

Endpoint: POST /api/v1/assignments/{assignment_id}/copy_assignment

Test Scenario:

Copy an existing assignment.

Attempt to copy a non-existent assignment.

Expected Outcome:

HTTP status 200, assignment copied successfully.

HTTP status 404, assignment not found. post 'Copy an existing assignment' do

     tags 'Assignments'
     consumes 'application/json'
     produces 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response '200', 'assignment copied successfully' do
       let(:assignment) { create(:assignment) } # Assuming you have a Factory for Assignment
       let(:assignment_id) { assignment.id }
       run_test! do
         response_json = JSON.parse(response.body) # Parse the response body as JSON
         expect(response_json['id']).to be_present
         expect(response).to have_http_status(:ok)
       end
     end
     response '404', 'assignment not found' do
       let(:assignment_id) { 4 }
       run_test! do
         expect(response).to have_http_status(:not_found)
       end
     end
   end

Delete Assignment

Endpoint: DELETE /api/v1/assignments/{id}

Test Scenario:

Delete an existing assignment.

Attempt to delete a non-existent assignment.

Expected Outcome:

HTTP status 200, assignment deleted successfully.

HTTP status 404, assignment not found. delete('Delete an assignment') do

     tags 'Assignments'
     produces 'application/json'
     consumes 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response(200, 'successful') do
       let(:assignment) { create(:assignment) }
       let(:id) { assignment.id }
       run_test! do |response|
         data = JSON.parse(response.body)
         expect(data['message']).to eq('Assignment deleted successfully!')
       end
     end
     response(404, 'Assignment not found') do
       let(:id) { 999 } # Non-existent ID
       run_test! do |response|
         data = JSON.parse(response.body)
         expect(data['error']).to eq('Assignment not found')
       end
     end
   end


Check if Assignment has Topics

Endpoint: GET /api/v1/assignments/{assignment_id}/has_topics

Test Scenario:

Check if an assignment has topics.

Attempt to check a non-existent assignment for topics.

Expected Outcome:

HTTP status 200, assignment has topics.

HTTP status 404, assignment not found. get('Check if an assignment has topics') do

     tags 'Assignments'
     produces 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response(200, 'successful') do
       let(:assignment) { create(:assignment) }
       let(:assignment_id) { assignment.id }
       run_test! do |response|
         expect(response).to have_http_status(:ok)
       end
     end
     response(404, 'Assignment not found') do
       let(:assignment_id) { 999 } # Non-existent ID
       run_test! do |response|
         data = JSON.parse(response.body)
         expect(data['error']).to eq('Assignment not found')
       end
     end
   end

Check if Assignment is a Team Assignment

Endpoint: GET /api/v1/assignments/{assignment_id}/team_assignment

Test Scenario:

Check if an assignment is a team assignment.

Attempt to check a non-existent assignment for being a team assignment.

Expected Outcome:

HTTP status 200, assignment is a team assignment.

HTTP status 404, assignment not found. get('Check if an assignment is a team assignment') do

     tags 'Assignments'
     produces 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response(200, 'successful') do
       let(:assignment) { create(:assignment) }
       let(:assignment_id) { assignment.id }
       run_test! do |response|
         expect(response).to have_http_status(:ok)
       end
     end
     response(404, 'Assignment not found') do
       let(:assignment_id) { 999 } # Non-existent ID
       run_test! do |response|
         data = JSON.parse(response.body)
         expect(data['error']).to eq('Assignment not found')
       end
     end
   end

Check if Assignment has a Valid Number of Reviews

Endpoint: GET /api/v1/assignments/{assignment_id}/valid_num_review/{review_type}

Test Scenario:

Check if an assignment has a valid number of reviews for a specific type.

Attempt to check a non-existent assignment for reviews.

Expected Outcome:

HTTP status 200, valid number of reviews for the specified type.

HTTP status 404, assignment not found. get('Check if an assignment has a valid number of reviews for a specific type') do

     tags 'Assignments'
     produces 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response(200, 'successful') do
       let(:assignment) { create(:assignment) }
       let(:assignment_id) { assignment.id }
       let(:review_type) { 'review' }
       run_test! do |response|
         expect(response).to have_http_status(:ok)
       end
     end
     response(404, 'Assignment not found') do
       let(:assignment_id) { 999 } # Non-existent ID
       let(:review_type) { 'some_type' }
       run_test! do |response|
         data = JSON.parse(response.body)
         expect(data['error']).to eq('Assignment not found')
       end
     end


Check if Assignment has Teams

Endpoint: GET /api/v1/assignments/{assignment_id}/has_teams

Test Scenario:

Check if an assignment has teams.

Attempt to check a non-existent assignment for teams.

Expected Outcome:

HTTP status 200, assignment has teams.

HTTP status 404, assignment not found. get('Check if an assignment has teams') do

     tags 'Assignments'
     produces 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response(200, 'successful') do
       let(:assignment) { create(:assignment) }
       let(:assignment_id) { assignment.id }
       run_test! do |response|
         expect(response).to have_http_status(:ok)
       end
     end
     response(404, 'Assignment not found') do
       let(:assignment_id) { 999 } # Non-existent ID
       run_test! do |response|
         data = JSON.parse(response.body)
         expect(data['error']).to eq('Assignment not found')
       end
     end
   end


Create Node for Assignment

Endpoint: POST /api/v1/assignments/{assignment_id}/create_node

Test Scenario:

Create a node for an assignment.

Attempt to create a node for a non-existent assignment.

Expected Outcome:

HTTP status 200, node created successfully.

HTTP status 404, assignment not found.

post('Create a node for an assignment') do

     tags 'Assignments'
     consumes 'application/json'
     parameter name: 'Authorization', in: :header, type: :string
     parameter name: 'Content-Type', in: :header, type: :string
     let('Authorization') { "Bearer #{auth_token}" }
     let('Content-Type') { 'application/json' }
     response(200, 'successful') do
       let(:assignment) { create(:assignment) }
       let(:assignment_id) { assignment.id }
       run_test! do |response|
         expect(response).to have_http_status(:ok)
       end
     end
     response(404, 'Assignment not found') do
       let(:assignment_id) { 999 } # Non-existent ID
       run_test! do |response|
         data = JSON.parse(response.body)
         expect(data['error']).to eq('Assignment not found')
       end
     end
   end

Check if Assignment has Varying Rubrics by Round

Endpoint: GET /api/v1/assignments/{assignment_id}/varying_rubrics_by_round

Test Scenario:

Check if an assignment has varying rubrics by round.

Attempt to check a non-existent assignment for varying rubrics.

Expected Outcome:

HTTP status 200, assignment has varying rubrics by round.

HTTP status 404, assignment not found. parameter name: 'assignment_id', in: :path, type: :integer, description: 'Assignment ID'

 get('Check if an assignment has varying rubrics by round') do
   tags 'Assignments'
   produces 'application/json'
   parameter name: 'Authorization', in: :header, type: :string
   parameter name: 'Content-Type', in: :header, type: :string
   let('Authorization') { "Bearer #{auth_token}" }
   let('Content-Type') { 'application/json' }
   response(200, 'successful') do
     let(:questionnaire) { create(:questionnaire) }
     let(:assignment) { create(:assignment) }
     let(:assignment_id) {assignment.id}
     let(:assignment_questionnaire) { create(:assignment_questionnaire, assignment: assignment, questionnaire: questionnaire, used_in_round: 1) }
     
     run_test! do |response|
       expect(response).to have_http_status(:ok)
     end
   end
   response(404, 'Assignment not found') do
     let(:assignment_id) { 999 } # Non-existent ID
     run_test! do |response|
       data = JSON.parse(response.body)
       expect(data['error']).to eq('Assignment not found')
     end
   end
 end

Test Coverage

We used SimpleCov to generate a coverage report for the Rspec test cases impleneted for Assignment model. The test coverage indicated 97.62% percent of code was covered through the testcases.

Team

Mentor
  • Ameya Vaichalkar
Members
  • Bhavya Harchandani <bharcha@ncsu.edu>
  • Akshat Saxena<asaxen24@ncsu.edu>
  • Mitali Sethi <msethi@ncsu.edu>

Links

Swagger Video Link:[1]

Pull request:[2]

Model Tests Video:[3]

Controller Tests Video:[4]

References