diff --git a/.github/workflows/rubyonrails.yml b/.github/workflows/rubyonrails.yml new file mode 100644 index 000000000..30342b153 --- /dev/null +++ b/.github/workflows/rubyonrails.yml @@ -0,0 +1,122 @@ +# This workflow uses actions that are not certified by GitHub. They are +# provided by a third-party and are governed by separate terms of service, +# privacy policy, and support documentation. +# +# This workflow will install a prebuilt Ruby version, install dependencies, and +# run tests and linters. +name: CI/CD + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + env: + DATABASE_URL: mysql2://root:expertiza@127.0.0.1:3306/expertiza_test + RAILS_ENV: test + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: expertiza + MYSQL_DATABASE: expertiza_test + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - uses: actions/checkout@v3 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2.7 + bundler-cache: true + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18.x' + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y netcat-traditional + + - name: Install Ruby dependencies + run: | + gem update --system + gem install bundler:2.4.7 + bundle install + + - name: Setup database + run: | + bundle exec rails db:create RAILS_ENV=test + bundle exec rails db:schema:load RAILS_ENV=test + + - name: Set up code climate test-reporter + run: | + curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + chmod +x ./cc-test-reporter + ./cc-test-reporter before-build + + - name: Run model tests + run: bundle exec rspec spec/models + + - name: Run controller tests + run: bundle exec rspec spec/requests/ + + - name: Format code coverage report + run: ./cc-test-reporter format-coverage -t simplecov -o "coverage/codeclimate.models.json" --debug + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v4 + with: + name: code-coverage-artifacts + path: coverage/ + + + publish_code_coverage: + needs: test # Ensures this job runs after the test job + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' # Only run when there's a push to main branch + + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v4 + with: + name: code-coverage-artifacts + path: coverage/ + + - name: Upload code-coverage report to code-climate + run: | + export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" + curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + chmod +x ./cc-test-reporter + ./cc-test-reporter sum-coverage coverage/codeclimate.*.json + ./cc-test-reporter after-build -t simplecov -r ${{ secrets.CC_TEST_REPORTER_ID }} + + docker: + needs: test + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build Docker image + uses: docker/build-push-action@v4 + with: + context: . + push: false + tags: expertiza-backend:latest + diff --git a/app/controllers/api/v1/account_requests_controller.rb b/app/controllers/api/v1/account_requests_controller.rb index 30cb17fa7..3a40eea7c 100644 --- a/app/controllers/api/v1/account_requests_controller.rb +++ b/app/controllers/api/v1/account_requests_controller.rb @@ -18,7 +18,7 @@ def create if @account_request.save response = { account_request: @account_request } if User.find_by(email: @account_request.email) - response[:warnings] = 'WARNING: User with this email already exists!' + response[:warnings] = I18n.t('account_requests.user_exists_warning') end render json: response, status: :created else @@ -60,7 +60,7 @@ def update def destroy @account_request = AccountRequest.find(params[:id]) @account_request.destroy - render json: { message: 'Account Request deleted' }, status: :no_content + render json: { message: I18n.t('account_requests.delete_success') }, status: :no_content rescue ActiveRecord::RecordNotFound => e render json: { error: e.message }, status: :not_found end @@ -74,7 +74,7 @@ def account_request_params params[:account_request][:status] = 'Under Review' # For Approval or Rejection of an existing request, raise error if user sends a status other than Approved or Rejected elsif !['Approved', 'Rejected'].include?(params[:account_request][:status]) - raise StandardError, 'Status can only be Approved or Rejected' + raise StandardError, I18n.t('account_requests.status_error') end params.require(:account_request).permit(:username, :full_name, :email, :status, :introduction, :role_id, :institution_id) end @@ -82,12 +82,12 @@ def account_request_params # Create a new user if account request is approved def create_approved_user if User.exists?(email: @account_request.email) - render json: { error: 'A user with this email already exists. Cannot approve the account request.' }, status: :unprocessable_entity + render json: { error: I18n.t('account_requests.user_exists') }, status: :unprocessable_entity return end @new_user = User.new(name: @account_request.username, role_id: @account_request.role_id, institution_id: @account_request.institution_id, full_name: @account_request.full_name, email: @account_request.email, password: 'password') if @new_user.save - render json: { success: 'Account Request Approved and User successfully created.', user: @new_user}, status: :ok + render json: { success: I18n.t('account_requests.create_success'), user: @new_user }, status: :ok else render json: @new_user.errors, status: :unprocessable_entity end diff --git a/app/controllers/api/v1/assignments_controller.rb b/app/controllers/api/v1/assignments_controller.rb index fdaf9bc75..bca8f677e 100644 --- a/app/controllers/api/v1/assignments_controller.rb +++ b/app/controllers/api/v1/assignments_controller.rb @@ -34,7 +34,7 @@ def update end def not_found - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found end # DELETE /api/v1/assignments/:id @@ -42,12 +42,12 @@ def destroy assignment = Assignment.find_by(id: params[:id]) if assignment if assignment.destroy - render json: { message: "Assignment deleted successfully!" }, status: :ok + render json: { message: I18n.t('assignment.deleted_successfully') }, status: :ok else - render json: { error: "Failed to delete assignment", details: assignment.errors.full_messages }, status: :unprocessable_entity + render json: { error: I18n.t('assignment.failed_to_delete'), details: assignment.errors.full_messages }, status: :unprocessable_entity end else - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found end end @@ -55,7 +55,7 @@ def destroy def add_participant assignment = Assignment.find_by(id: params[:assignment_id]) if assignment.nil? - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found else new_participant = assignment.add_participant(params[:user_id]) if new_participant.save @@ -73,12 +73,12 @@ def remove_participant if user && assignment assignment.remove_participant(user.id) if assignment.save - render json: { message: "Participant removed successfully!" }, status: :ok + render json: { message: I18n.t('assignment.participant_removed') }, status: :ok else render json: assignment.errors, status: :unprocessable_entity end else - not_found_message = user ? "Assignment not found" : "User not found" + not_found_message = user ? I18n.t('assignment.user_not_found') : I18n.t('assignment.not_found') render json: { error: not_found_message }, status: :not_found end end @@ -88,7 +88,7 @@ def remove_participant def remove_assignment_from_course assignment = Assignment.find(params[:assignment_id]) if assignment.nil? - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found else assignment = assignment.remove_assignment_from_course if assignment.save @@ -112,7 +112,7 @@ def assign_course render json: assignment.errors, status: :unprocessable_entity end else - not_found_message = course ? "Assignment not found" : "Course not found" + not_found_message = course ? I18n.t('assignment.course_not_found') : I18n.t('assignment.not_found') render json: { error: not_found_message }, status: :not_found end end @@ -121,7 +121,7 @@ def assign_course def copy_assignment assignment = Assignment.find_by(id: params[:assignment_id]) if assignment.nil? - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found else new_assignment = assignment.copy if new_assignment.save @@ -137,7 +137,7 @@ def copy_assignment def show_assignment_details assignment = Assignment.find_by(id: params[:assignment_id]) if assignment.nil? - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found else render json: { id: assignment.id, @@ -155,7 +155,7 @@ def show_assignment_details def has_topics assignment = Assignment.find_by(id: params[:assignment_id]) if assignment.nil? - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found else render json: assignment.topics?, status: :ok end @@ -166,7 +166,7 @@ def has_topics def team_assignment assignment = Assignment.find_by(id: params[:assignment_id]) if assignment.nil? - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found else render json: assignment.team_assignment?, status: :ok end @@ -178,7 +178,7 @@ def valid_num_review assignment = Assignment.find_by(id: params[:assignment_id]) review_type = params[:review_type] if assignment.nil? - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found else render json: assignment.valid_num_review(review_type), status: :ok end @@ -189,7 +189,7 @@ def valid_num_review def has_teams assignment = Assignment.find_by(id: params[:assignment_id]) if assignment.nil? - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found else render json: assignment.teams?, status: :ok end @@ -200,12 +200,12 @@ def has_teams def varying_rubrics_by_round? assignment = Assignment.find_by(id: params[:assignment_id]) if assignment.nil? - render json: { error: "Assignment not found" }, status: :not_found + render json: { error: I18n.t('assignment.not_found') }, status: :not_found else if AssignmentQuestionnaire.exists?(assignment_id: assignment.id) render json: assignment.varying_rubrics_by_round?, status: :ok else - render json: { error: "No questionnaire/rubric exists for this assignment." }, status: :not_found + render json: { error: I18n.t('assignment.no_questionnaire') }, status: :not_found end end end diff --git a/app/controllers/api/v1/bookmarks_controller.rb b/app/controllers/api/v1/bookmarks_controller.rb index 912d58d00..2e5e96dd5 100644 --- a/app/controllers/api/v1/bookmarks_controller.rb +++ b/app/controllers/api/v1/bookmarks_controller.rb @@ -49,7 +49,7 @@ def update # Handle the case when an invalid bookmark id is being passed def not_found - render json: { error: "Couldn't find Bookmark" }, status: :not_found + render json: { error: I18n.t('bookmarks.not_found') }, status: :not_found end # Destroy method deletes the bookmark object with id- {:id} diff --git a/app/controllers/api/v1/courses_controller.rb b/app/controllers/api/v1/courses_controller.rb index 2529613da..473ad0608 100644 --- a/app/controllers/api/v1/courses_controller.rb +++ b/app/controllers/api/v1/courses_controller.rb @@ -45,7 +45,7 @@ def update # Delete a course def destroy @course.destroy - render json: { message: "Course with id #{params[:id]}, deleted" }, status: :no_content + render json: { message: I18n.t('course.deleted', id: params[:id]) }, status: :no_content end # Adds a Teaching Assistant to the course @@ -69,7 +69,7 @@ def view_tas def remove_ta result = @course.remove_ta(params[:ta_id]) if result[:success] - render json: { message: "The TA #{result[:ta_name]} has been removed." }, status: :ok + render json: { message: I18n.t('course.ta_removed', ta_name: result[:ta_name]) }, status: :ok else render json: { status: "error", message: result[:message] }, status: :not_found end @@ -80,9 +80,9 @@ def copy # existing_course = Course.find(params[:id]) success = @course.copy_course if success - render json: { message: "The course #{@course.name} has been successfully copied" }, status: :ok + render json: { message: I18n.t('course.copy_success', name: @course.name) }, status: :ok else - render json: { message: "The course was not able to be copied" }, status: :unprocessable_entity + render json: { message: I18n.t('course.copy_failure') }, status: :unprocessable_entity end end @@ -99,10 +99,10 @@ def course_params end def course_not_found - render json: { error: "Course with id #{params[:id]} not found" }, status: :not_found + render json: { error: I18n.t('course.not_found', id: params[:id]) }, status: :not_found end def parameter_missing - render json: { error: "Parameter missing" }, status: :unprocessable_entity + render json: { error: I18n.t('course.parameter_missing') }, status: :unprocessable_entity end end diff --git a/app/controllers/api/v1/institutions_controller.rb b/app/controllers/api/v1/institutions_controller.rb index fa22de3b2..cb5d77e18 100644 --- a/app/controllers/api/v1/institutions_controller.rb +++ b/app/controllers/api/v1/institutions_controller.rb @@ -41,7 +41,7 @@ def update def destroy @institution = Institution.find(params[:id]) @institution.destroy - render json: { message: 'Institution deleted' }, status: :ok + render json: { message: I18n.t('institution.deleted') }, status: :ok end private @@ -52,6 +52,6 @@ def institution_params end def institution_not_found - render json: { error: 'Institution not found' }, status: :not_found + render json: { error: I18n.t('institution.not_found') }, status: :not_found end end \ No newline at end of file diff --git a/app/controllers/api/v1/join_team_requests_controller.rb b/app/controllers/api/v1/join_team_requests_controller.rb index 57a8ed383..0dc5fcac0 100644 --- a/app/controllers/api/v1/join_team_requests_controller.rb +++ b/app/controllers/api/v1/join_team_requests_controller.rb @@ -19,7 +19,7 @@ def action_allowed? # gets a list of all the join team requests def index unless @current_user.administrator? - return render json: { errors: 'Unauthorized' }, status: :unauthorized + return render json: { errors: I18n.t('join_team_requests.unauthorized') }, status: :unauthorized end join_team_requests = JoinTeamRequest.all render json: join_team_requests, status: :ok @@ -42,7 +42,7 @@ def create team = Team.find(params[:team_id]) if team.participants.include?(participant) - render json: { error: 'You already belong to the team' }, status: :unprocessable_entity + render json: { error: I18n.t('join_team_requests.already_in_team') }, status: :unprocessable_entity elsif participant join_team_request.participant_id = participant.id if join_team_request.save @@ -51,7 +51,7 @@ def create render json: { errors: join_team_request.errors.full_messages }, status: :unprocessable_entity end else - render json: { errors: 'Participant not found' }, status: :unprocessable_entity + render json: { errors: I18n.t('join_team_requests.participant_not_found') }, status: :unprocessable_entity end end @@ -59,7 +59,7 @@ def create # Updates a join team request def update if @join_team_request.update(join_team_request_params) - render json: { message: 'JoinTeamRequest was successfully updated' }, status: :ok + render json: { message: I18n.t('join_team_requests.update_success') }, status: :ok else render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity end @@ -69,9 +69,9 @@ def update # delete a join team request def destroy if @join_team_request.destroy - render json: { message: 'JoinTeamRequest was successfully deleted' }, status: :ok + render json: { message: I18n.t('join_team_requests.delete_success') }, status: :ok else - render json: { errors: 'Failed to delete JoinTeamRequest' }, status: :unprocessable_entity + render json: { errors: I18n.t('join_team_requests.delete_failure') }, status: :unprocessable_entity end end @@ -79,7 +79,7 @@ def destroy def decline @join_team_request.status = DECLINED if @join_team_request.save - render json: { message: 'JoinTeamRequest declined successfully' }, status: :ok + render json: { message: I18n.t('join_team_requests.decline_success') }, status: :ok else render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity end @@ -90,7 +90,7 @@ def decline def check_team_status team = Team.find(params[:team_id]) if team.full? - render json: { message: 'This team is full.' }, status: :unprocessable_entity + render json: { message: I18n.t('join_team_requests.team_full') }, status: :unprocessable_entity end end diff --git a/app/controllers/api/v1/participants_controller.rb b/app/controllers/api/v1/participants_controller.rb index 675ae7a1e..5410eb76b 100644 --- a/app/controllers/api/v1/participants_controller.rb +++ b/app/controllers/api/v1/participants_controller.rb @@ -113,9 +113,9 @@ def destroy render json: { error: 'Not Found' }, status: :not_found elsif participant.destroy successful_deletion_message = if params[:team_id].nil? - "Participant #{params[:id]} in Assignment #{params[:assignment_id]} has been deleted successfully!" + I18n.t('participants.destroy.success_assignment', id: params[:id], assignment_id: params[:assignment_id]) else - "Participant #{params[:id]} in Team #{params[:team_id]} of Assignment #{params[:assignment_id]} has been deleted successfully!" + I18n.t('participants.destroy.success_team', id: params[:id], team_id: params[:team_id], assignment_id: params[:assignment_id]) end render json: { message: successful_deletion_message }, status: :ok else @@ -154,7 +154,7 @@ def filter_assignment_participants(assignment) def find_user user_id = params[:user_id] user = User.find_by(id: user_id) - render json: { error: 'User not found' }, status: :not_found unless user + render json: { error: I18n.t('participants.user_not_found') }, status: :not_found unless user user end @@ -163,7 +163,7 @@ def find_user def find_assignment assignment_id = params[:assignment_id] assignment = Assignment.find_by(id: assignment_id) - render json: { error: 'Assignment not found' }, status: :not_found unless assignment + render json: { error: I18n.t('participants.assignment_not_found') }, status: :not_found unless assignment assignment end @@ -172,7 +172,7 @@ def find_assignment def find_participant participant_id = params[:id] participant = Participant.find_by(id: participant_id) - render json: { error: 'Participant not found' }, status: :not_found unless participant + render json: { error: I18n.t('participants.not_found') }, status: :not_found unless participant participant end @@ -184,12 +184,12 @@ def validate_authorization authorization = authorization.downcase if authorization.present? unless authorization - render json: { error: 'authorization is required' }, status: :unprocessable_entity + render json: { error: I18n.t('participants.authorization_required') }, status: :unprocessable_entity return end unless valid_authorizations.include?(authorization) - render json: { error: 'authorization not valid. Valid authorizations are: Reader, Reviewer, Submitter, Mentor' }, + render json: { error: I18n.t('participants.authorization_invalid') }, status: :unprocessable_entity return end diff --git a/app/controllers/api/v1/roles_controller.rb b/app/controllers/api/v1/roles_controller.rb index 55fa081f0..5a202c13a 100644 --- a/app/controllers/api/v1/roles_controller.rb +++ b/app/controllers/api/v1/roles_controller.rb @@ -43,7 +43,7 @@ def destroy role = Role.find(params[:id]) role_name = role.name role.destroy - render json: { message: "Role #{role_name} with id #{params[:id]} deleted successfully!" }, status: :no_content + render json: { message: I18n.t('roles.destroy.success', role_name: role_name, id: params[:id]) }, status: :no_content end def subordinate_roles @@ -64,6 +64,6 @@ def role_params # end def parameter_missing - render json: { error: 'Parameter missing' }, status: :unprocessable_entity + render json: { error: I18n.t('roles.parameter_missing') }, status: :unprocessable_entity end -end +end \ No newline at end of file diff --git a/app/controllers/api/v1/sign_up_topics_controller.rb b/app/controllers/api/v1/sign_up_topics_controller.rb index a736f3eaa..cf1929a8c 100644 --- a/app/controllers/api/v1/sign_up_topics_controller.rb +++ b/app/controllers/api/v1/sign_up_topics_controller.rb @@ -5,7 +5,7 @@ class Api::V1::SignUpTopicsController < ApplicationController # Retrieve SignUpTopics by two query parameters - assignment_id (compulsory) and an array of topic_ids (optional) def index if params[:assignment_id].nil? - render json: { message: 'Assignment ID is required!' }, status: :unprocessable_entity + render json: { message: I18n.t('sign_up_topics.assignment_id_required') }, status: :unprocessable_entity elsif params[:topic_ids].nil? @sign_up_topics = SignUpTopic.where(assignment_id: params[:assignment_id]) render json: @sign_up_topics, status: :ok @@ -25,8 +25,7 @@ def create @assignment = Assignment.find(params[:sign_up_topic][:assignment_id]) @sign_up_topic.micropayment = params[:micropayment] if @assignment.microtask? if @sign_up_topic.save - # undo_link "The topic: \"#{@sign_up_topic.topic_name}\" has been created successfully. " - render json: { message: "The topic: \"#{@sign_up_topic.topic_name}\" has been created successfully. " }, status: :created + render json: { message: I18n.t('sign_up_topics.create_success', topic_name: @sign_up_topic.topic_name) }, status: :created else render json: { message: @sign_up_topic.errors }, status: :unprocessable_entity end @@ -36,7 +35,7 @@ def create # updates parameters present in sign_up_topic_params. def update if @sign_up_topic.update(sign_up_topic_params) - render json: { message: "The topic: \"#{@sign_up_topic.topic_name}\" has been updated successfully. " }, status: 200 + render json: { message: I18n.t('sign_up_topics.update_success', topic_name: @sign_up_topic.topic_name) }, status: :ok else render json: @sign_up_topic.errors, status: :unprocessable_entity end @@ -54,7 +53,7 @@ def destroy # render json: {message: @sign_up_topic} # filters topics based on assignment id (required) and topic identifiers (optional) if params[:assignment_id].nil? - render json: { message: 'Assignment ID is required!' }, status: :unprocessable_entity + render json: { message: I18n.t('sign_up_topics.assignment_id_required') }, status: :unprocessable_entity elsif params[:topic_ids].nil? @sign_up_topics = SignUpTopic.where(assignment_id: params[:assignment_id]) # render json: @sign_up_topics, status: :ok @@ -64,7 +63,7 @@ def destroy end if @sign_up_topics.each(&:delete) - render json: { message: "The topic has been deleted successfully. " }, status: :no_content + render json: { message: I18n.t('sign_up_topics.delete_success') }, status: :no_content else render json: @sign_up_topic.errors, status: :unprocessable_entity end diff --git a/app/controllers/api/v1/signed_up_teams_controller.rb b/app/controllers/api/v1/signed_up_teams_controller.rb index 97ada5a24..0ac537d98 100644 --- a/app/controllers/api/v1/signed_up_teams_controller.rb +++ b/app/controllers/api/v1/signed_up_teams_controller.rb @@ -16,7 +16,7 @@ def create; end def update @signed_up_team = SignedUpTeam.find(params[:id]) if @signed_up_team.update(signed_up_teams_params) - render json: { message: "The team has been updated successfully. " }, status: 200 + render json: { message: I18n.t('signed_up_teams.update_success') }, status: :ok else render json: @signed_up_team.errors, status: :unprocessable_entity end @@ -29,7 +29,7 @@ def sign_up topic_id = params[:topic_id] @signed_up_team = SignedUpTeam.create_signed_up_team(topic_id, team_id) if @signed_up_team - render json: { message: "Signed up team successful!" }, status: :created + render json: { message: I18n.t('signed_up_teams.create_success') }, status: :created else render json: { message: @signed_up_team.errors }, status: :unprocessable_entity end @@ -48,7 +48,7 @@ def sign_up_student @signed_up_team = SignedUpTeam.create_signed_up_team(topic_id, team_id) # create(topic_id, team_id) if @signed_up_team - render json: { message: "Signed up team successful!" }, status: :created + render json: { message: I18n.t('signed_up_teams.create_success') }, status: :created else render json: { message: @signed_up_team.errors }, status: :unprocessable_entity end @@ -58,7 +58,7 @@ def sign_up_student def destroy @signed_up_team = SignedUpTeam.find(params[:id]) if SignedUpTeam.delete_signed_up_team(@signed_up_team.team_id) - render json: { message: 'Signed up teams was deleted successfully!' }, status: :ok + render json: { message: I18n.t('signed_up_teams.delete_success') }, status: :ok else render json: @signed_up_team.errors, status: :unprocessable_entity end diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb index 086376556..d3475c978 100644 --- a/app/controllers/api/v1/users_controller.rb +++ b/app/controllers/api/v1/users_controller.rb @@ -39,7 +39,7 @@ def update def destroy user = User.find(params[:id]) user.destroy - render json: { message: "User #{user.name} with id #{params[:id]} deleted successfully!" }, status: :no_content + render json: { message: I18n.t('users.destroy.success', name: user.name, id: params[:id]) }, status: :no_content end # GET /api/v1/users/institution/:id @@ -57,7 +57,7 @@ def institution_users def managed_users parent = User.find(params[:id]) if parent.student? - render json: { error: 'Students do not manage any users' }, status: :unprocessable_entity + render json: { error: I18n.t('users.managed_users.student_error') }, status: :unprocessable_entity return end parent = User.instantiate(parent) @@ -86,10 +86,10 @@ def user_params end def user_not_found - render json: { error: "User with id #{params[:id]} not found" }, status: :not_found + render json: { error: I18n.t('users.not_found', id: params[:id]) }, status: :not_found end def parameter_missing - render json: { error: 'Parameter missing' }, status: :unprocessable_entity + render json: { error: I18n.t('users.parameter_missing') }, status: :unprocessable_entity end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 12ffcf261..c0f3317e0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,5 +3,12 @@ class ApplicationController < ActionController::API include JwtToken before_action :authorize + before_action :set_locale + + private + + def set_locale + I18n.locale = current_user.try(:locale) || I18n.default_locale + end end diff --git a/config/application.rb b/config/application.rb index 4bd4ca23e..876ee6278 100644 --- a/config/application.rb +++ b/config/application.rb @@ -30,5 +30,10 @@ class Application < Rails::Application # Skip views, helpers and assets when generating a new resource. config.api_only = true config.cache_store = :redis_store, ENV['CACHE_STORE'], { expires_in: 3.days, raise_errors: false } + + # Internationalization (i18n) Settings + config.i18n.default_locale = :en_US + config.i18n.available_locales = [:en_US, :hi_IN] + config.i18n.fallbacks = [:en_US] end end diff --git a/config/locales/en.yml b/config/locales/en.yml deleted file mode 100644 index 8ca56fc74..000000000 --- a/config/locales/en.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t "hello" -# -# In views, this is aliased to just `t`: -# -# <%= t("hello") %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# The following keys must be escaped otherwise they will not be retrieved by -# the default I18n backend: -# -# true, false, on, off, yes, no -# -# Instead, surround them with single quotes. -# -# en: -# "true": "foo" -# -# To learn more, please read the Rails Internationalization guide -# available at https://guides.rubyonrails.org/i18n.html. - -en: - hello: "Hello world" diff --git a/config/locales/en_US.yml b/config/locales/en_US.yml new file mode 100644 index 000000000..5deb952ac --- /dev/null +++ b/config/locales/en_US.yml @@ -0,0 +1,97 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en_US: + assignment: + not_found: Assignment not found + deleted_successfully: Assignment deleted successfully! + failed_to_delete: Failed to delete assignment + participant_removed: Participant removed successfully! + user_not_found: User not found + course_not_found: Course not found + no_questionnaire: No questionnaire/rubric exists for this assignment. + institution: + not_found: "Institution not found" + deleted: "Institution deleted" + course: + not_found: "Course with id %{id} not found" + deleted: "Course with id %{id}, deleted" + ta_removed: "The TA %{ta_name} has been removed." + copy_success: "The course %{name} has been successfully copied" + copy_failure: "The course was not able to be copied" + parameter_missing: "Parameter missing" + roles: + destroy: + success: "Role %{role_name} with id %{id} deleted successfully!" + parameter_missing: "Parameter missing" + participants: + destroy: + success_assignment: "Participant %{id} in Assignment %{assignment_id} has been deleted successfully!" + success_team: "Participant %{id} in Team %{team_id} of Assignment %{assignment_id} has been deleted successfully!" + not_found: "Participant not found" + user_not_found: "User not found" + assignment_not_found: "Assignment not found" + authorization_required: "Authorization is required" + authorization_invalid: "Authorization not valid. Valid authorizations are: Reader, Reviewer, Submitter, Mentor" + users: + destroy: + success: "User %{name} with id %{id} deleted successfully!" + not_found: "User with id %{id} not found" + parameter_missing: "Parameter missing" + managed_users: + student_error: "Students do not manage any users" + bookmarks: + not_found: "Couldn't find Bookmark" + join_team_requests: + unauthorized: "Unauthorized" + already_in_team: "You already belong to the team" + participant_not_found: "Participant not found" + update_success: "JoinTeamRequest was successfully updated" + update_failure: "Failed to update JoinTeamRequest" + delete_success: "JoinTeamRequest was successfully deleted" + delete_failure: "Failed to delete JoinTeamRequest" + decline_success: "JoinTeamRequest declined successfully" + team_full: "This team is full." + signed_up_teams: + create_success: "Signed up team successful!" + update_success: "The team has been updated successfully." + delete_success: "Signed up team was deleted successfully!" + account_requests: + user_exists: "A user with this email already exists. Cannot approve the account request." + create_success: "Account Request Approved and User successfully created." + delete_success: "Account Request deleted" + status_error: "Status can only be Approved or Rejected" + user_exists_warning: "WARNING: User with this email already exists!" + sign_up_topics: + assignment_id_required: "Assignment ID is required!" + create_success: "The topic: \"%{topic_name}\" has been created successfully." + update_success: "The topic: \"%{topic_name}\" has been updated successfully." + delete_success: "The topic has been deleted successfully." diff --git a/config/locales/hi_IN.yml b/config/locales/hi_IN.yml new file mode 100644 index 000000000..58702a4ae --- /dev/null +++ b/config/locales/hi_IN.yml @@ -0,0 +1,66 @@ +hi_IN: + assignment: + not_found: असाइनमेंट नहीं मिला + deleted_successfully: असाइनमेंट सफलतापूर्वक हटा दिया गया! + failed_to_delete: असाइनमेंट को हटाने में विफल + participant_removed: प्रतिभागी को सफलतापूर्वक हटा दिया गया! + user_not_found: उपयोगकर्ता नहीं मिला + course_not_found: कोर्स नहीं मिला + no_questionnaire: इस असाइनमेंट के लिए कोई प्रश्नावली/रूब्रिक मौजूद नहीं है। + course: + not_found: "कोर्स आईडी %{id} के साथ नहीं मिला" + deleted: "कोर्स आईडी %{id} के साथ हटा दिया गया" + ta_removed: "टीए %{ta_name} को हटा दिया गया है।" + copy_success: "कोर्स %{name} को सफलतापूर्वक कॉपी किया गया है" + copy_failure: "कोर्स को कॉपी नहीं किया जा सका" + parameter_missing: "पैरामीटर गायब है" + institution: + not_found: "संस्थान नहीं मिला" + deleted: "संस्थान हटा दिया गया" + roles: + destroy: + success: "भूमिका %{role_name} आईडी %{id} के साथ सफलतापूर्वक हटा दी गई!" + parameter_missing: "पैरामीटर गायब है" + participants: + destroy: + success_assignment: "असाइनमेंट %{assignment_id} में प्रतिभागी %{id} को सफलतापूर्वक हटा दिया गया है!" + success_team: "असाइनमेंट %{assignment_id} की टीम %{team_id} में प्रतिभागी %{id} को सफलतापूर्वक हटा दिया गया है!" + not_found: "प्रतिभागी नहीं मिला" + user_not_found: "उपयोगकर्ता नहीं मिला" + assignment_not_found: "असाइनमेंट नहीं मिला" + authorization_required: "प्राधिकरण आवश्यक है" + authorization_invalid: "प्राधिकरण मान्य नहीं है। मान्य प्राधिकरण हैं: पाठक, समीक्षक, प्रस्तुतकर्ता, संरक्षक" + users: + destroy: + success: "उपयोगकर्ता %{name} आईडी %{id} के साथ सफलतापूर्वक हटा दिया गया है!" + not_found: "आईडी %{id} के साथ उपयोगकर्ता नहीं मिला" + parameter_missing: "पैरामीटर गायब है" + managed_users: + student_error: "छात्र किसी भी उपयोगकर्ताओं का प्रबंधन नहीं करते हैं" + bookmarks: + not_found: "बुकमार्क नहीं मिला" + join_team_requests: + unauthorized: "अनधिकृत" + already_in_team: "आप पहले से ही टीम में हैं" + participant_not_found: "प्रतिभागी नहीं मिला" + update_success: "JoinTeamRequest सफलतापूर्वक अपडेट किया गया" + update_failure: "JoinTeamRequest को अपडेट करने में विफल" + delete_success: "JoinTeamRequest सफलतापूर्वक हटा दिया गया" + delete_failure: "JoinTeamRequest को हटाने में विफल" + decline_success: "JoinTeamRequest को सफलतापूर्वक अस्वीकार कर दिया गया" + team_full: "यह टीम पूरी हो चुकी है।" + signed_up_teams: + create_success: "टीम सफलतापूर्वक साइन अप हो गई!" + update_success: "टीम को सफलतापूर्वक अपडेट कर दिया गया है।" + delete_success: "साइन अप की गई टीम को सफलतापूर्वक हटा दिया गया है!" + account_requests: + user_exists: "इस ईमेल के साथ एक उपयोगकर्ता पहले से मौजूद है। खाता अनुरोध को स्वीकृत नहीं किया जा सकता।" + create_success: "खाता अनुरोध स्वीकृत और उपयोगकर्ता सफलतापूर्वक बनाया गया।" + delete_success: "खाता अनुरोध हटा दिया गया" + status_error: "स्थिति केवल स्वीकृत या अस्वीकृत हो सकती है" + user_exists_warning: "चेतावनी: इस ईमेल के साथ एक उपयोगकर्ता पहले से मौजूद है!" + sign_up_topics: + assignment_id_required: "असाइनमेंट आईडी आवश्यक है!" + create_success: "विषय: \"%{topic_name}\" सफलतापूर्वक बनाया गया है।" + update_success: "विषय: \"%{topic_name}\" सफलतापूर्वक अपडेट किया गया है।" + delete_success: "विषय को सफलतापूर्वक हटा दिया गया है।" \ No newline at end of file diff --git a/spec/controllers/api/v1/assignments_spec.rb b/spec/controllers/api/v1/assignments_spec.rb new file mode 100644 index 000000000..a37b2367d --- /dev/null +++ b/spec/controllers/api/v1/assignments_spec.rb @@ -0,0 +1,78 @@ +require 'rails_helper' + +RSpec.describe 'Assignments API', type: :request do + + let!(:instructor) { User.create!(name: "Instructor", email: "instructor@example.com", password: "password123") } + let!(:course) { Course.create!(title: "Test Course", description: "Some desc") } # Add this line! + + let!(:valid_attributes) do + { + name: "Test Assignment", + description: "Test Desc", + due_date: "2025-12-31", + instructor_id: instructor.id, + course_id: course.id, + max_team_size: 2 + } + end + + let!(:assignment) do + assignment = Assignment.new(valid_attributes) + unless assignment.valid? + puts "Validation Errors: #{assignment.errors.full_messages}" + end + assignment.save! + assignment + end + + before do + allow_any_instance_of(Api::V1::AssignmentsController).to receive(:authenticate_user!).and_return(true) + end + + describe 'GET /api/v1/assignments' do + it 'returns all assignments' do + get '/api/v1/assignments' + expect(response).to have_http_status(:ok) + json = JSON.parse(response.body) + expect(json).to be_an(Array) + expect(json.first['name']).to eq('Test Assignment') + end + end + + describe 'GET /api/v1/assignments/:id' do + it 'returns a specific assignment' do + get "/api/v1/assignments/#{assignment.id}" + expect(response).to have_http_status(:ok) + json = JSON.parse(response.body) + expect(json['id']).to eq(assignment.id) + end + end + + describe 'POST /api/v1/assignments' do + it 'creates a new assignment with valid params' do + expect { + post '/api/v1/assignments', params: { assignment: { name: 'New Assignment', description: 'Desc', due_date: '2025-11-30', instructor_id: instructor.id, course_id: course.id, max_team_size: 2 } }, as: :json + }.to change(Assignment, :count).by(1) + expect(response).to have_http_status(:created) + end + end + + describe 'PUT /api/v1/assignments/:id' do + it 'updates an assignment' do + put "/api/v1/assignments/#{assignment.id}", params: { assignment: { name: 'Updated Name' } }, as: :json + expect(response).to have_http_status(:ok) + assignment.reload + expect(assignment.name).to eq('Updated Name') + end + end + + describe 'DELETE /api/v1/assignments/:id' do + it 'deletes an assignment' do + DueDate.where(parent: assignment).delete_all + expect { + delete "/api/v1/assignments/#{assignment.id}" + }.to change(Assignment, :count).by(-1) + expect(response).to have_http_status(:no_content) + end + end +end diff --git a/spec/controllers/api/v1/course_controller_spec.rb b/spec/controllers/api/v1/course_controller_spec.rb new file mode 100644 index 000000000..b3f4e5029 --- /dev/null +++ b/spec/controllers/api/v1/course_controller_spec.rb @@ -0,0 +1,81 @@ +require 'rails_helper' + +RSpec.describe "Courses", type: :request do + let!(:course) { Course.create(title: "Ruby on Rails", description: "Learn Rails") } + + describe "GET /courses" do + it "returns a successful response" do + get courses_path + expect(response).to have_http_status(:ok) + expect(response.body).to include("Ruby on Rails") + end + end + + describe "GET /courses/:id" do + context "when the course exists" do + it "returns the course details" do + get course_path(course) + expect(response).to have_http_status(:ok) + expect(response.body).to include("Ruby on Rails") + end + end + + context "when the course does not exist" do + it "returns a 404 status" do + get course_path(id: 999) + expect(response).to have_http_status(:not_found) + end + end + end + + describe "POST /courses" do + context "with valid parameters" do + it "creates a new course" do + course_params = { course: { title: "RSpec Testing", description: "Learn RSpec" } } + expect { + post courses_path, params: course_params + }.to change(Course, :count).by(1) + expect(response).to have_http_status(:created) + end + end + + context "with invalid parameters" do + it "does not create a new course" do + course_params = { course: { title: "", description: "" } } + expect { + post courses_path, params: course_params + }.not_to change(Course, :count) + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "PATCH /courses/:id" do + context "with valid parameters" do + it "updates the course" do + course_params = { course: { title: "Updated Title" } } + patch course_path(course), params: course_params + expect(response).to have_http_status(:ok) + expect(course.reload.title).to eq("Updated Title") + end + end + + context "with invalid parameters" do + it "does not update the course" do + course_params = { course: { title: "" } } + patch course_path(course), params: course_params + expect(response).to have_http_status(:unprocessable_entity) + expect(course.reload.title).to eq("Ruby on Rails") + end + end + end + + describe "DELETE /courses/:id" do + it "deletes the course" do + expect { + delete course_path(course) + }.to change(Course, :count).by(-1) + expect(response).to have_http_status(:no_content) + end + end +end diff --git a/spec/controllers/api/v1/institutions_controller_spec.rb b/spec/controllers/api/v1/institutions_controller_spec.rb new file mode 100644 index 000000000..a1b11b0ae --- /dev/null +++ b/spec/controllers/api/v1/institutions_controller_spec.rb @@ -0,0 +1,127 @@ +require 'rails_helper' + +RSpec.describe Api::V1::InstitutionsController, type: :controller do + let!(:institution) { create(:institution) } + + # MYSQL wait check before tests + before(:all) do + retries = 0 + begin + ActiveRecord::Base.establish_connection + ActiveRecord::Base.connection.execute('SELECT 1') + rescue => e + retries += 1 + if retries < 10 + puts "Waiting for MySQL... Retry #{retries}/10" + sleep 5 + retry + else + raise e + end + end + end + + + describe 'action_allowed?' do + context 'when user has Instructor role' do + it 'returns true' do + user = create(:user, :instructor) + sign_in user + expect(controller.send(:action_allowed?)).to eq(true) + end + end + + context 'when user does not have Instructor role' do + it 'returns false' do + user = create(:user, :student) + sign_in user + expect(controller.send(:action_allowed?)).to eq(false) + end + end + end + + describe 'GET #index' do + it 'returns a successful response' do + get :index + expect(response).to have_http_status(:ok) + expect(json_response).to eq([institution.as_json]) + end + end + + describe 'GET #show' do + context 'when institution exists' do + it 'returns a successful response' do + get :show, params: { id: institution.id } + expect(response).to have_http_status(:ok) + expect(json_response).to eq(institution.as_json) + end + end + + context 'when institution does not exist' do + it 'returns a not found error' do + get :show, params: { id: 99999 } + expect(response).to have_http_status(:not_found) + expect(json_response['error']).to include('Institution not found') + end + end + end + + describe 'POST #create' do + context 'with valid parameters' do + let(:valid_attributes) { { institution: { name: 'Test Institution' } } } + + it 'creates a new institution and returns a created response' do + post :create, params: valid_attributes + expect(response).to have_http_status(:created) + expect(json_response['name']).to eq('Test Institution') + end + end + + context 'with invalid parameters' do + let(:invalid_attributes) { { institution: { name: '' } } } + + it 'does not create an institution and returns an unprocessable entity response' do + post :create, params: invalid_attributes + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response).to include('name') + end + end + end + + describe 'PATCH #update' do + context 'with valid parameters' do + let(:valid_attributes) { { institution: { name: 'Updated Institution' } } } + + it 'updates the institution and returns a successful response' do + patch :update, params: { id: institution.id, institution: valid_attributes[:institution] } + institution.reload + expect(response).to have_http_status(:ok) + expect(institution.name).to eq('Updated Institution') + end + end + + context 'with invalid parameters' do + let(:invalid_attributes) { { institution: { name: '' } } } + + it 'does not update the institution and returns an unprocessable entity response' do + patch :update, params: { id: institution.id, institution: invalid_attributes[:institution] } + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response).to include('name') + end + end + end + + describe 'DELETE #destroy' do + it 'deletes the institution and returns a successful response' do + delete :destroy, params: { id: institution.id } + expect(response).to have_http_status(:ok) + expect(json_response['message']).to eq(I18n.t('institution.deleted')) + end + end + + private + + def json_response + JSON.parse(response.body) + end +end \ No newline at end of file diff --git a/spec/controllers/api/v1/participants_controller_spec.rb b/spec/controllers/api/v1/participants_controller_spec.rb new file mode 100644 index 000000000..feb8e0e6e --- /dev/null +++ b/spec/controllers/api/v1/participants_controller_spec.rb @@ -0,0 +1,163 @@ +require 'rails_helper' + +RSpec.describe Api::V1::ParticipantsController, type: :controller do + let!(:user) { create(:user) } + let!(:assignment) { create(:assignment) } + let!(:participant) { create(:participant, user: user, assignment: assignment) } + + # MYSQL wait check before tests + before(:all) do + retries = 0 + begin + ActiveRecord::Base.establish_connection + ActiveRecord::Base.connection.execute('SELECT 1') + rescue => e + retries += 1 + if retries < 10 + puts "Waiting for MySQL... Retry #{retries}/10" + sleep 5 + retry + else + raise e + end + end + end + + + describe 'GET #list_user_participants' do + context 'when user exists' do + it 'returns a list of participants' do + get :list_user_participants, params: { user_id: user.id } + expect(response).to have_http_status(:ok) + expect(json_response).to eq([participant.as_json]) + end + end + + context 'when user does not exist' do + it 'returns an error' do + get :list_user_participants, params: { user_id: 99999 } + expect(response).to have_http_status(:not_found) + expect(json_response['error']).to eq('User not found') + end + end + end + + + describe 'GET #list_assignment_participants' do + context 'when assignment exists' do + it 'returns a list of participants' do + get :list_assignment_participants, params: { assignment_id: assignment.id } + expect(response).to have_http_status(:ok) + expect(json_response).to eq([participant.as_json]) + end + end + + context 'when assignment does not exist' do + it 'returns an error' do + get :list_assignment_participants, params: { assignment_id: 99999 } + expect(response).to have_http_status(:not_found) + expect(json_response['error']).to eq('Assignment not found') + end + end + end + + + describe 'GET #show' do + context 'when participant exists' do + it 'returns the participant' do + get :show, params: { id: participant.id } + expect(response).to have_http_status(:created) # The controller uses :created for this response + expect(json_response).to eq(participant.as_json) + end + end + + context 'when participant does not exist' do + it 'returns an error' do + get :show, params: { id: 99999 } + expect(response).to have_http_status(:not_found) + expect(json_response['error']).to eq('Participant not found') + end + end + end + + + describe 'POST #add' do + context 'with valid parameters' do + let(:valid_params) { { user_id: user.id, assignment_id: assignment.id, authorization: 'submitter' } } + + it 'adds the participant and returns a successful response' do + post :add, params: valid_params + expect(response).to have_http_status(:created) + expect(json_response['user_id']).to eq(user.id) + expect(json_response['assignment_id']).to eq(assignment.id) + end + end + + context 'with invalid parameters' do + it 'returns an error when user is not found' do + post :add, params: { user_id: 99999, assignment_id: assignment.id, authorization: 'submitter' } + expect(response).to have_http_status(:not_found) + expect(json_response['error']).to eq('User not found') + end + + it 'returns an error when assignment is not found' do + post :add, params: { user_id: user.id, assignment_id: 99999, authorization: 'submitter' } + expect(response).to have_http_status(:not_found) + expect(json_response['error']).to eq('Assignment not found') + end + + it 'returns an error when authorization is invalid' do + post :add, params: { user_id: user.id, assignment_id: assignment.id, authorization: 'invalid' } + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response['error']).to eq('authorization not valid. Valid authorizations are: Reader, Reviewer, Submitter, Mentor') + end + end + end + + + describe 'PATCH #update_authorization' do + let(:new_authorization) { 'reviewer' } + + context 'with valid authorization' do + it 'updates the participant authorization' do + patch :update_authorization, params: { id: participant.id, authorization: new_authorization } + participant.reload + expect(response).to have_http_status(:created) + expect(participant.authorization).to eq(new_authorization) + end + end + + context 'with invalid authorization' do + it 'returns an error' do + patch :update_authorization, params: { id: participant.id, authorization: 'invalid' } + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response['error']).to eq('authorization not valid. Valid authorizations are: Reader, Reviewer, Submitter, Mentor') + end + end + end + + + describe 'DELETE #destroy' do + context 'when participant exists' do + it 'deletes the participant and returns a success message' do + delete :destroy, params: { id: participant.id } + expect(response).to have_http_status(:ok) + expect(json_response['message']).to eq("Participant #{participant.id} in Assignment #{assignment.id} has been deleted successfully!") + end + end + + context 'when participant does not exist' do + it 'returns an error' do + delete :destroy, params: { id: 99999 } + expect(response).to have_http_status(:not_found) + expect(json_response['error']).to eq('Not Found') + end + end + end + + private + + def json_response + JSON.parse(response.body) + end +end \ No newline at end of file diff --git a/spec/controllers/api/v1/roles_controller_spec.rb b/spec/controllers/api/v1/roles_controller_spec.rb new file mode 100644 index 000000000..d08030ae1 --- /dev/null +++ b/spec/controllers/api/v1/roles_controller_spec.rb @@ -0,0 +1,131 @@ +require 'rails_helper' + +RSpec.describe Api::V1::RolesController, type: :controller do + let!(:role) { create(:role) } + let!(:admin_user) { create(:user, role: create(:role, name: 'Administrator')) } + let!(:subordinate_role) { create(:role, parent: role) } + + # MYSQL wait check before tests + before(:all) do + retries = 0 + begin + ActiveRecord::Base.establish_connection + ActiveRecord::Base.connection.execute('SELECT 1') + rescue => e + retries += 1 + if retries < 10 + puts "Waiting for MySQL... Retry #{retries}/10" + sleep 5 + retry + else + raise e + end + end + end + + before do + sign_in admin_user + end + + describe 'GET #index' do + it 'returns all roles' do + get :index + expect(response).to have_http_status(:ok) + expect(json_response.length).to eq(1) + end + end + + describe 'GET #show' do + context 'when the role exists' do + it 'returns the role' do + get :show, params: { id: role.id } + expect(response).to have_http_status(:ok) + expect(json_response['name']).to eq(role.name) + end + end + + context 'when the role does not exist' do + it 'returns an error' do + get :show, params: { id: 99999 } + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response['error']).to eq('Parameter missing') + end + end + end + + describe 'POST #create' do + context 'with valid parameters' do + let(:valid_params) { { role: { name: 'New Role' } } } + + it 'creates a new role' do + post :create, params: valid_params + expect(response).to have_http_status(:created) + expect(json_response['name']).to eq('New Role') + end + end + + context 'with invalid parameters' do + let(:invalid_params) { { role: { name: '' } } } + + it 'returns an error' do + post :create, params: invalid_params + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response['name']).to include("can't be blank") + end + end + end + + describe 'PATCH #update' do + context 'with valid parameters' do + let(:valid_params) { { role: { name: 'Updated Role' } } } + + it 'updates the role' do + patch :update, params: { id: role.id, role: valid_params } + expect(response).to have_http_status(:ok) + expect(json_response['name']).to eq('Updated Role') + end + end + + context 'with invalid parameters' do + let(:invalid_params) { { role: { name: '' } } } + + it 'returns an error' do + patch :update, params: { id: role.id, role: invalid_params } + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response['name']).to include("can't be blank") + end + end + end + + describe 'DELETE #destroy' do + context 'when the role exists' do + it 'deletes the role and returns a success message' do + delete :destroy, params: { id: role.id } + expect(response).to have_http_status(:no_content) + expect(json_response['message']).to eq("Role #{role.name} with id #{role.id} deleted successfully!") + end + end + + context 'when the role does not exist' do + it 'returns an error' do + delete :destroy, params: { id: 99999 } + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response['error']).to eq('Parameter missing') + end + end + end + + describe 'GET #subordinate_roles' do + it 'returns the subordinate roles of the current user' do + get :subordinate_roles + expect(response).to have_http_status(:ok) + expect(json_response.length).to eq(1) + end + end + + private + + def json_response + JSON.parse(response.body) + end +end \ No newline at end of file diff --git a/spec/controllers/api/v1/users_controller_spec.rb b/spec/controllers/api/v1/users_controller_spec.rb new file mode 100644 index 000000000..75401087a --- /dev/null +++ b/spec/controllers/api/v1/users_controller_spec.rb @@ -0,0 +1,105 @@ +require 'rails_helper' + +RSpec.describe Api::V1::UsersController, type: :controller do + let!(:user) { User.create(name: "John Doe", email: "john@example.com", password: "password123") } + + # Stub authentication before each test + before do + allow(controller).to receive(:authenticate_user!).and_return(true) + allow(controller).to receive(:current_user).and_return(user) + end + + describe "GET #index" do + it "returns a successful response" do + get :index + expect(response).to have_http_status(:ok) + json_response = JSON.parse(response.body) + expect(json_response).to be_an(Array) + end + end + + describe "GET #show" do + context "when user exists" do + it "returns the user" do + get :show, params: { id: user.id } + expect(response).to have_http_status(:ok) + json_response = JSON.parse(response.body) + expect(json_response["id"]).to eq(user.id) + expect(json_response["name"]).to eq(user.name) + end + end + + context "when user does not exist" do + it "returns a 404 error" do + get :show, params: { id: 99999 } + expect(response).to have_http_status(:not_found) + json_response = JSON.parse(response.body) + expect(json_response["error"]).to eq("User not found") + end + end + end + + describe "POST #create" do + context "with valid parameters" do + it "creates a new user" do + expect { + post :create, params: { user: { name: "Alice", email: "alice@example.com", password: "password" } } + }.to change(User, :count).by(1) + expect(response).to have_http_status(:created) + json_response = JSON.parse(response.body) + expect(json_response["name"]).to eq("Alice") + end + end + + context "with invalid parameters" do + it "returns errors" do + post :create, params: { user: { name: "", email: "", password: "" } } + expect(response).to have_http_status(:unprocessable_entity) + json_response = JSON.parse(response.body) + expect(json_response["errors"]).not_to be_empty + end + end + end + + describe "PUT #update" do + context "with valid parameters" do + it "updates the user" do + put :update, params: { id: user.id, user: { name: "Updated Name" } } + expect(response).to have_http_status(:ok) + user.reload + expect(user.name).to eq("Updated Name") + end + end + + context "with invalid parameters" do + it "returns errors" do + put :update, params: { id: user.id, user: { email: "" } } + expect(response).to have_http_status(:unprocessable_entity) + json_response = JSON.parse(response.body) + expect(json_response["errors"]).not_to be_empty + end + end + end + + describe "DELETE #destroy" do + context "when user exists" do + it "deletes the user" do + expect { + delete :destroy, params: { id: user.id } + }.to change(User, :count).by(-1) + expect(response).to have_http_status(:no_content) + end + end + + context "when user does not exist" do + it "returns 404 error" do + delete :destroy, params: { id: 99999 } + expect(response).to have_http_status(:not_found) + json_response = JSON.parse(response.body) + expect(json_response["error"]).to eq("User not found") + end + end + end +end + +