CSC/ECE 517 Fall 2023 - E2369. Reimplement duties controller.rb and badges controller.rb

From Expertiza_Wiki
Jump to navigation Jump to search

Topic Overview & Prior Work

Background

duties_controller.rb

The actions defined by this duties_controller include create, update, and remove. The new duty is saved to the database using the create action. The update action is used to update the duty in the database, while the edit action renders the form for editing an existing duty. Lastly, a duty can be removed from the database by using the delete action. All things considered, Expertiza's Duties module aids in the management of duties.

badges_controller.rb

A new Badge instance is created by the controller using the user-supplied parameters through the use of the create action. If an image file is attached, it additionally saves it and modifies the badge instance's image_name attribute.

Previous Work

The prior implementation of the duties and badges controllers was based on a Rails MVC application rather than strictly as an API built on Rails. The handling of requests and responses did not align with the API architecture, thus needing adjustments to ensure a more suitable API-style communication. Furthermore, the previous implementation lacked comprehensive testing, a crucial aspect that needs attention in the reimplementation.

Planned Work

Functions to implement

Functionalities for Duties

  • Create Action: Implement the functionality to add and persist new duties to the database.
  • Edit Action: Develop the mechanism to modify existing duties, providing a form for editing duties.
  • Update Action: Enhance the capability to update existing duties already stored in the database.

Functionalities for Badges

  • Create Action: Implement the creation process for new badges, utilizing user-provided parameters.
  • Image Handling: Develop a mechanism to handle attached image files, ensuring their storage.
  • Update Action: Enhance the functionality to update the image_name attribute associated with a badge instance, especially when an image is attached.

Notes for Reimplementation

  • Ensure that all interactions with the controllers adhere to API conventions, returning data in JSON format.
  • Thoroughly test all REST endpoints for both duties and badges, covering various scenarios to validate their functionality. Comprehensive testing should encompass both success and failure scenarios.

Implementation

Duties Controller

class Api::V1::DutiesController < ApplicationController
 before_action :set_duty, only: %i[ show update destroy ]

index Method:

Function: Retrieves all duties.

HTTP Verb: GET

URL Endpoint: /duties

Description: Fetches all duties from the database using the Duty.all method and renders a JSON response containing all duties.

 # GET /duties
 def index
   @duties = Duty.all
   render json: @duties, status: :ok
 end

show Method:

Function: Retrieves a specific duty.

HTTP Verb: GET

URL Endpoint: /duties/:id

Description: Finds a duty by its ID and renders a JSON response containing the specific duty

 # GET /duties/1
 def show
   render json: @duty, status: :ok
 end

create Method:

Function: Creates a new duty.

HTTP Verb: POST

URL Endpoint: /duties

Description: Creates a new duty by instantiating a Duty object with the provided duty parameters from the request (duty_params).

 # POST /duties
 def create
   @duty = Duty.new(duty_params)
   if @duty.save
     render json: @duty, status: :created, location: @duty
   else
     render json: @duty.errors, status: :unprocessable_entity
   end
 end

update Method:

Function: Updates an existing duty.

HTTP Verb: PATCH/PUT

URL Endpoint: /duties/:id

Description: Updates an existing duty specified by its ID using the provided duty parameters from the request (duty_params).

 # PATCH/PUT /duties/1
 def update
   if @duty.update(duty_params)
     render json: @duty
   else
     render json: @duty.errors, status: :unprocessable_entity
   end
 end

destroy Method:

Function: Deletes a duty.

HTTP Verb: DELETE

URL Endpoint: /duties/:id

Description: Finds a specific duty by its ID and destroys (deletes) it. It then renders a JSON response with a success message

 # DELETE /duties/1
 def destroy
   @duty.destroy
   render json: { message: "Duty was successfully destroyed." }, status: :ok
 end

set_duty Method (Private):

This method is used as a callback to set the @duty instance variable by finding the duty based on the :id parameter. It is used before the show, update, and destroy actions to set up the specific duty being operated on.

 private
   # Use callbacks to share common setup or constraints between actions.
   def set_duty
     @duty = Duty.find(params[:id])
   end

duty_params Method (Private):

Filters the duty parameters received from the request, allowing only specific parameters (:name, :assignment_id, :max_members_for_duties) to be used for creating or updating a duty. It uses Rails' strong parameters for security.

   # Only allow a list of trusted parameters through.
   def duty_params
     params.require(:duty).permit(:name, :assignment_id, :max_members_for_duties)
   end
end

Badges Controller

class Api::V1::BadgesController < ApplicationController
 before_action :set_badge, only: %i[ show update destroy ]

index Method:

HTTP Verb: GET

Endpoint: /badges

Functionality: Retrieves all badges.

Description: Fetches all existing badges from the database using Badge.all and returns a JSON array containing all badge records.

 # GET /badges
 def index
   @badges = Badge.all
   render json: @badges, status: :ok
 end

show Method:

HTTP Verb: GET

Endpoint: /badges/:id

Functionality: Retrieves a specific badge.

Description: Finds and retrieves a particular badge by its ID from the database. The method responds with a JSON object containing the details of the specific badge.

 # GET /badges/1
 def show
   render json: @badge, status: :ok
 end

create Method:

HTTP Verb: POST

Endpoint: /badges

Functionality: Creates a new badge.

Description: Instantiates a new badge using the parameters provided in the request (badge_params).

 # POST /badges
 def create
   @badge = Badge.new(badge_params)
   if @badge.save
     render json: @badge, status: :created, location: @badge
   else
     render json: @badge.errors, status: :unprocessable_entity
   end
 end

update Method:

HTTP Verb: PATCH/PUT

Endpoint: /badges/:id

Functionality: Updates an existing badge.

Description: Modifies the attributes of an existing badge based on the parameters received in the request (badge_params).

 # PATCH/PUT /badges/1
 def update
   if @badge.update(badge_params)
     render json: @badge, status: :ok
   else
     render json: @badge.errors, status: :unprocessable_entity
   end
 end

destroy Method:

HTTP Verb: DELETE

Endpoint: /badges/:id

Functionality: Deletes a badge.

Description: Locates a specific badge by its ID and removes it from the database.

 # DELETE /badges/1
 def destroy
   @badge.destroy
   render json: { message: 'Badge was successfully destroyed.'}, status: :ok
 end

set_badge Method (Private):

Function: Sets up a specific badge for other actions.

Description: A callback method that finds and assigns the @badge instance variable based on the :id parameter before executing the show, update, and destroy actions.

 private
   # Use callbacks to share common setup or constraints between actions.
   def set_badge
     @badge = Badge.find(params[:id])
   end

badge_params Method (Private):

Function: Filters and permits badge parameters for security.

Description: Filters the badge parameters received in the request, allowing only specific parameters (:name, :description, :image_name, :image_file) to be utilized for creating or updating a badge.

   # Only allow a list of trusted parameters through.
   def badge_params
     params.require(:badge).permit(:name, :description, :image_name, :image_file)
   end
end

Testing Methodology

During our reimplementation project, Postman was exclusively used for testing API endpoints. Postman allowed us to systematically test these endpoints, covering positive, negative, and edge cases. We systematically tested various scenarios, ensuring that authorized access was granted when required and denied when not.

In our testing process, we harnessed the power of Swagger UI to comprehensively evaluate the functionality and security of our API endpoints. Swagger UI provided an intuitive and interactive platform for sending various types of requests and scrutinizing responses. However, to ensure that the endpoints were secure, we implemented an authorization mechanism using a YAML file. This YAML file allowed us to generate tokens, particularly for the /login endpoint, which was pivotal in our authentication process. By leveraging Swagger UI a, we methodically tested the endpoints, covering a spectrum of scenarios, including positive and negative cases.

Testing DutiesController

The DutiesController is responsible for managing duties in the application. Below is an outline of the RSpec tests applied to verify its functionality.


Scenario 1: Retrieving All Duties

Verifies the retrieval of all duties when present in the database.

Expectation: Expects an OK status with the correct JSON response containing all duties.

RSpec.describe Api::V1::DutiesController, type: :controller do
 describe "index" do
   it "returns all duties" do
     # Test scenario 1: When there are duties in the database
     # Expect the method to return all duties in JSON format
     duty1 = Duty.create(name: 'Duty 1')
     duty2 = Duty.create(name: 'Duty 2')
     get :index
     expect(response).to have_http_status(:ok)
     expect(JSON.parse(response.body)).to include(
                                            { 'name' => 'Duty 1' },
                                            { 'name' => 'Duty 2' }
                                          )
   end

Scenario 2: No Duties in the Database

Ensures the method returns an empty JSON array when no duties exist.

Expectation: Expects an OK status with an empty JSON array.

   it "returns an empty JSON array when there are no duties" do
     # Test scenario 2: When there are no duties in the database
     # Expect the method to return an empty JSON array
     get :index
     expect(response).to have_http_status(:ok)
     expect(JSON.parse(response.body)).to eq([])
   end
 end

Scenario: Initializing a New Duty Object

Validates the initialization of a new Duty object.

Expectation: Expects a new Duty object to be initialized.

 describe "#new" do
   context "when called" do
     it "initializes a new Duty object" do
       get :new
       expect(assigns(:duty)).to be_a_new(Duty)
     end
     it "assigns the value of params[:id] to @id" do
       id = 123
       get :new, params: { id: id }
       expect(assigns(:id)).to eq(id)
     end
   end
 end

Scenario: Rendering a Duty as JSON

Checks the rendering of a duty in JSON format.

Expectation: Expects an OK status with the correct duty details in JSON.

 describe "#show" do
   context "when called" do
     it "renders the duty as JSON" do
       duty = Duty.create(name: 'Test Duty')
       get :show, params: { id: duty.id }
       expect(response).to have_http_status(:ok)
       expect(JSON.parse(response.body)).to include('name' => 'Test Duty')
     end
   end
 end

Scenario: Rendering a Duty for Editing

Tests the rendering of a duty for editing in JSON.

Expectation: Expects an OK status with the correct duty details in JSON.

 describe "#edit" do
   context "when called" do
     it "renders the duty as JSON" do
       duty = Duty.create(name: 'Test Duty')
       get :edit, params: { id: duty.id }
       expect(response).to have_http_status(:ok)
       expect(JSON.parse(response.body)).to include('name' => 'Test Duty')
     end
   end
 end

Scenario: Creating a New Duty

Validates the creation of a new duty with valid parameters.

Expectation: Expects a 'created' status and the created duty data in the JSON response.

 describe "#create" do
   context "when duty params are valid" do
     it "creates a new duty" do
       duty_params = { name: 'New Duty' }
       post :create, params: { duty: duty_params }
       expect(response).to have_http_status(:created)
       expect(Duty.last.name).to eq('New Duty')
     end

Scenario: Error in Creating Duty

Tests the scenario when invalid duty parameters are provided.

Expectation: Expects an 'unprocessable entity' status with an error message concerning the invalid parameters.

     it "returns a JSON response with the created duty and status code 201" do
       duty_params = { name: 'New Duty' }
       post :create, params: { duty: duty_params }
       expect(response).to have_http_status(:created)
       expect(JSON.parse(response.body)).to include('name' => 'New Duty')
     end
   end
   context "when duty params are invalid" do
     it "does not create a new duty" do
       duty_params = { name:  }
       post :create, params: { duty: duty_params }
       expect(response).to have_http_status(:unprocessable_entity)
       expect(Duty.last).to be_nil
     end
     it "returns a JSON response with the error messages and status code 422" do
       duty_params = { name:  }
       post :create, params: { duty: duty_params }
       expect(response).to have_http_status(:unprocessable_entity)
       expect(JSON.parse(response.body)).to include('name' => ["can't be blank"])
     end
   end
 end

Scenario: Successfully Updating Duty

Verifies the successful update of an existing duty.

Expectation: Expects an OK status with the updated duty data in JSON.

 describe "#update" do
   context "when duty is successfully updated" do
     it "returns the updated duty as JSON" do
       duty = Duty.create(name: 'Old Duty')
       updated_name = 'Updated Duty'
       patch :update, params: { id: duty.id, duty: { name: updated_name } }
       expect(response).to have_http_status(:ok)
       expect(JSON.parse(response.body)).to include('name' => updated_name)
     end
   end

Scenario: Error in Updating Duty

Tests the scenario when an existing duty is updated with invalid parameters.

Expectation: Expects an 'unprocessable entity' status with an error message concerning the invalid parameters.

   context "when duty fails to update" do
     it "returns an error message as JSON" do
       duty = Duty.create(name: 'Old Duty')
       patch :update, params: { id: duty.id, duty: { name:  } }
       expect(response).to have_http_status(:unprocessable_entity)
       expect(JSON.parse(response.body)).to include('name' => ["can't be blank"])
     end
   end
 end

Scenario: Destroying a Duty

Validates the destruction of an existing duty.

Expectation: Expects an OK status and confirms the duty's destruction in the database.

 describe "#destroy" do
   it "destroys the duty" do
     duty = Duty.create(name: 'Duty to be Destroyed')
     delete :destroy, params: { id: duty.id }
     expect(response).to have_http_status(:ok)
     expect(Duty.find_by(id: duty.id)).to be_nil
   end

Scenario: Non-existent or Destroyed Duties

Checks the response when attempting to destroy non-existent or already destroyed duties.

Expectation: Expects 'not found' status or an 'unprocessable entity' status with corresponding error messages.

   it "returns an error message for a non-existent duty" do
     delete :destroy, params: { id: 9999 } # Assuming 9999 is an invalid duty ID.
     expect(response).to have_http_status(:not_found)
     expect(JSON.parse(response.body)).to include('message' => 'Duty not found')
   end
   it "returns an error message for a previously destroyed duty" do
     duty = Duty.create(name: 'Destroyed Duty')
     duty.destroy
     delete :destroy, params: { id: duty.id }
     expect(response).to have_http_status(:unprocessable_entity)
     expect(JSON.parse(response.body)).to include('message' => 'Duty already destroyed')
   end
 end

Scenario: Setting a Duty by ID

Validates the setting of @duty based on a valid duty ID.

Expectation: Expects @duty to match the duty with the provided ID.

 describe "#set_duty" do
   context "when a valid duty id is provided" do
     it "sets @duty to the duty with the provided id" do
       duty = Duty.create(name: 'Test Duty')
       get :show, params: { id: duty.id }
       expect(assigns(:duty)).to eq(duty)
     end
   end

Scenario: Invalid Duty ID

Checks for error handling when an invalid duty ID is provided.

Expectation: Expects an error raising ActiveRecord::RecordNotFound.

   context "when an invalid duty id is provided" do
     it "does not set @duty and raises an error" do
       expect { get :show, params: { id: 9999 } }.to raise_error(ActiveRecord::RecordNotFound)
     end
   end
 end

Scenario: Valid and Missing Parameters

Ensures the return of permitted parameters for a duty object.

Expectation: Expects permitted parameters for a valid duty and error raises for missing parameters.

 describe "#duty_params" do
   context "when valid parameters are provided" do
     it "returns the permitted parameters for duty" do
       valid_params = { name: 'Valid Duty', assignment_id: 1, max_members_for_duties: 5 }
       duty = Duty.new(valid_params)
       duty_params = controller.send(:duty_params, duty)
       expect(duty_params).to eq(valid_params)
     end
   end
   context "when assignment_id is missing" do
     it "raises an error" do
       invalid_params = { name: 'Invalid Duty', max_members_for_duties: 5 }
       duty = Duty.new(invalid_params)
       expect { controller.send(:duty_params, duty) }.to raise_error(ActionController::ParameterMissing)
     end
   end
   context "when max_members_for_duty is missing" do
     it "raises an error" do
       invalid_params = { name: 'Invalid Duty', assignment_id: 1 }
       duty = Duty.new(invalid_params)
       expect { controller.send(:duty_params, duty) }.to raise_error(ActionController::ParameterMissing)
     end
     context "when name is missing" do
       it "raises an error" do
         invalid_params = { assignment_id: 1, max_members_for_duties: 5 }
         duty = Duty.new(invalid_params)
         expect { controller.send(:duty_params, duty) }.to raise_error(ActionController::ParameterMissing)
       end
     end
   end
 end
end

Testing BadgesController

The BadgesController is a crucial component that handles the CRUD operations for managing badges in the application. To ensure its functionalities are reliable and error-free, comprehensive testing has been implemented using RSpec.


Scenario: Retrieving All Badges

Verifies that the controller returns all badges when the database is empty.

Expectation: Expect an OK status with an empty JSON response.

RSpec.describe Api::V1::BadgesController, type: :controller do
 describe "index" do
   it "returns all badges" do
     # Test scenario: When there are no badges in the database initially.
     get :index
     expect(response).to have_http_status(:ok)
     expect(JSON.parse(response.body)).to eq([]) # Use JSON.parse for comparing JSON responses.
   end

Scenario: Error in Retrieving Badges

Simulates an error in retrieving badges.

Expectation: The response status should indicate an error.

   it "returns a successful response status" do
     # Test scenario: When there is an error in retrieving badges.
     # Expected behavior: The response status should indicate an error.
     allow(Badge).to receive(:all).and_raise(StandardError, 'Database connection error')
     get :index
     expect(response).to have_http_status(:error) # Adjust this to the actual error status code.
   end
 end

Scenario: Valid Badge Request

Ensures the controller returns a specific badge based on a valid ID.

Expectation: Expects an OK status and the correct badge data.

 describe "show" do
   it "returns a specific badge" do
     # Test scenario: When a badge with a valid ID is requested.
     badge = Badge.create(name: 'Test Badge')
     get :show, params: { id: badge.id }
     expect(response).to have_http_status(:ok)
     expect(JSON.parse(response.body)).to include('name' => 'Test Badge')
   end

Scenario: Invalid Badge Request

Tests when a non-existent badge is requested.

Expectation: The response should be successful with an empty JSON body.

   it "returns an error message for a non-existent badge" do
     # Test scenario: When a badge with an invalid ID is requested.
     get :show, params: { id: 9999 } # Assuming 9999 is an invalid badge ID.
     expect(response).to have_http_status(:ok)
     expect(JSON.parse(response.body)).to be_empty
   end
 end

Scenario: Creating a New Badge

Validates the creation of a new badge with valid parameters.

Expectation: Expects a 'created' status and the created badge data in the JSON response.

 describe "create" do
   it "creates a new badge" do
     # Test scenario: Creating a new badge with valid parameters.
     post :create, params: { badge: { name: 'New Badge' } }
     expect(response).to have_http_status(:created)
     expect(JSON.parse(response.body)).to include('name' => 'New Badge')
   end

Scenario: Error in Creating Badge

Tests the scenario when invalid badge parameters are provided (e.g., empty name).

Expectation: The response should have an 'unprocessable entity' status with an error message regarding the invalid parameters.

   it "returns an error message for invalid badge parameters" do
     # Test scenario: Creating a new badge with invalid parameters (empty name).
     post :create, params: { badge: { name:  } }
     expect(response).to have_http_status(:unprocessable_entity)
     expect(JSON.parse(response.body)).to include('name' => ["can't be blank"])
   end
 end

Scenario: Updating an Existing Badge

Verifies the update process of an existing badge with valid parameters.

Expectation: Expects an OK status and the updated badge data.

 describe "update" do
   it "updates an existing badge" do
     # Test scenario: Updating an existing badge with valid parameters.
     badge = Badge.create(name: 'Old Badge')
     patch :update, params: { id: badge.id, badge: { name: 'Updated Badge' } }
     expect(response).to have_http_status(:ok)
     expect(JSON.parse(response.body)).to include('name' => 'Updated Badge')
   end

Scenario: Error in Updating Badge

Tests updating an existing badge with invalid parameters (e.g., empty name).

Expectation: The response should have an 'unprocessable entity' status with an error message concerning the invalid parameters.

   it "returns an error message for invalid badge parameters" do
     # Test scenario: Updating an existing badge with invalid parameters (empty name).
     badge = Badge.create(name: 'Old Badge')
     patch :update, params: { id: badge.id, badge: { name:  } }
     expect(response).to have_http_status(:unprocessable_entity)
     expect(JSON.parse(response.body)).to include('name' => ["can't be blank"])
   end
 end

Scenario: Destroying an Existing Badge

Validates the destruction of an existing badge.

Expectation: Expects an OK status and a success message confirming the badge's destruction.

 describe "destroy" do
   it "destroys an existing badge" do
     # Test scenario: Destroying an existing badge.
     badge = Badge.create(name: 'Badge to be Destroyed')
     delete :destroy, params: { id: badge.id }
     expect(response).to have_http_status(:ok)
     expect(JSON.parse(response.body)).to include('message' => 'Badge was successfully destroyed.')
   end

Scenario: Attempting to Destroy Non-existent Badge

Simulates attempting to delete a non-existent badge.

Expectation: Expects a 'not found' status for the non-existent badge.

   it "returns an error message for a non-existent badge" do
     # Test scenario: Attempting to destroy a non-existent badge.
     delete :destroy, params: { id: 9999 } # Assuming 9999 is an invalid badge ID.
     expect(response).to have_http_status(:not_found)
   end
 end
end

Conclusion

In conclusion, the reimplementation project for the Duties and Badges modules within Expertiza holds significant importance in aligning the existing functionalities with API standards and rectifying the deficiencies identified in the previous implementation.

The Duties module, managed by the duties_controller.rb, plays a pivotal role in handling duties' creation, editing, and deletion within the system. Likewise, the Badges module, governed by the badges_controller.rb, oversees the creation and management of badges, including the handling of image files.

The prior implementation's primary shortcomings revolved around treating the system as a traditional Rails MVC application rather than as a Rails API. This resulted in non-compliance with API standards in handling requests and responses, which this reimplementation aims to rectify. Additionally, the inadequacy of testing procedures highlighted the necessity for a comprehensive testing suite covering various scenarios to ensure the robustness and reliability of the system.

The planned work includes adhering strictly to API-style interactions, returning data in JSON format, and implementing thorough testing for all REST endpoints, ensuring the functionalities for both Duties and Badges are rigorously verified across success and failure scenarios.

By addressing these issues and meticulously implementing the planned functionalities, this reimplementation aims to enhance the system's efficiency, robustness, and adherence to API conventions, ensuring a more reliable and scalable platform for managing duties and badges within Expertiza.

Useful Links

Contributors

Group members:

Siddhi Mule

Rishab Muzhangathu

Aditi Gulabchand Varma

Mentor:

Renji Joseph Sabu