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

From Expertiza_Wiki
Jump to navigation Jump to search

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