Перекрестное тестирование контроллеров в рельсах

#ruby-on-rails #ruby

Вопрос:

В API rails 6.1.3.1 я хотел бы реализовать необычный тест. В принципе, пользователь может создать команду с реляционной моделью «многие ко многим». Создание команды автоматически запускает новую запись в таблице JoinedTeamUser с двумя ссылками ( user_id и team_id ) и логическим can_edit значением, чтобы узнать, может ли пользователь обновлять/удалять или нет сведения о команде.
Я хотел бы проверить, имеет ли пользователь, обращающийся к update destroy методу или контроллера команды, право на это (имеется в виду правильная запись в модели, зависящая от другого контроллера).

Чтобы дать вам лучшее представление о процессе, вот файл seed.rb .

 3.times do |i|
  team = Team.create(name: Faker::Company.name, description: Faker::Company.catch_phrase)
  puts "Created TEAM ##{i 1} - #{team.name}"
end

10.times do |i|
  name = Faker::Name.first_name.downcase
  user = User.create! username: "#{name}", email: "#{name}@email.com", password: "azerty"
  puts "Created USER ##{i 1} - #{user.username}"
  
  joined_team_users = JoinedTeamUser.create!(
    user_id: user.id,
    team_id: Team.all.sample.id,
    can_edit: true
  )
  puts "USER ##{joined_team_users.user_id} joined TEAM ##{joined_team_users.team_id}"
end
 

Изменить:
Как и было запрошено, вот контроллер команды (фильтр авторизованного пользователя для обновления все еще не реализован) :

 class Api::V1::TeamsController < ApplicationController
  before_action :set_team, only: %i[show update destroy]
  before_action :check_login

  def index
    render json: TeamSerializer.new(Team.all).serializable_hash.to_json
  end

  def show
    render json: TeamSerializer.new(@team).serializable_hash.to_json
  end

  def create
    team = current_user.teams.build(team_params)

    if team.save 
      if current_useramp;.is_admin === false
        JoinedTeamUser.create!(
          user_id: current_user.id,
          team_id: team.id,
          can_edit: true
        )
        render json: TeamSerializer.new(team).serializable_hash.to_json, status: :created
      else
        render json: TeamSerializer.new(team).serializable_hash.to_json, status: :created
      end
    else 
      render json: {errors: team.errors }, status: :unprocessable_entity
    end
  end

  def update
    if @team.update(team_params)
      render json: TeamSerializer.new(@team).serializable_hash.to_json, status: :ok
    else
      render json: @team.errors, status: :unprocessable_entity
    end
  end

  def destroy
    @team.destroy
    head 204
  end

  private
  
  def team_params
    params.require(:team).permit(:name, :description, :created_at, :updated_at)
  end

  def set_team
    @team = Team.find(params[:id])
  end

end
 

и тесты командного контроллера :

 require "test_helper"

class Api::V1::TeamsControllerTest < ActionDispatch::IntegrationTest
  setup do
    @team = teams(:one)
    @user = users(:one)
  end

  # INDEX
  test "should access team index" do
    get api_v1_teams_url,
    headers: { Authorization: JsonWebToken.encode(user_id: @user.id) }, 
    as: :json
    assert_response :success
  end

  test "should forbid team index" do
    get api_v1_teams_url, as: :json
    assert_response :forbidden
  end

  # SHOW
  test "should show team" do
    get api_v1_team_url(@team), 
    headers: { Authorization: JsonWebToken.encode(user_id: @user.id) }, 
    as: :json
    assert_response :success
  end

  # SHOW
  test "should forbid show team" do
    get api_v1_team_url(@team),
    as: :json
    assert_response :forbidden
  end

  # CREATE
  test "should create team" do
    assert_difference('Team.count') do
      post api_v1_teams_url,
      params: { team: { name: "Random name", description: "Random description" } },
      headers: { Authorization: JsonWebToken.encode(user_id: @user.id) },
      as: :json
    end
    assert_response :created
  end

  test "should not create team when not logged in" do
    assert_no_difference('Team.count') do
      post api_v1_teams_url,
      params: { team: { name: "Random name", description: "Random description" } },
      as: :json
    end
    assert_response :forbidden
  end

  test "should not create team with taken name" do
    assert_no_difference('Team.count') do
      post api_v1_teams_url,
      params: { team: { name: @team.name, description: "Random description" } },
      headers: { Authorization: JsonWebToken.encode(user_id: @user.id) },
      as: :json
    end
    assert_response :unprocessable_entity
  end

  test "should not create team without name" do
    assert_no_difference('Team.count') do
      post api_v1_teams_url,
      params: { team: { description: "Random description"} },
      headers: { Authorization: JsonWebToken.encode(user_id: @user.id) },
      as: :json
    end
    assert_response :unprocessable_entity
  end
  
  # UPDATE
  test "should update team" do
    patch api_v1_team_url(@team),
    params: { team: { name: "New name", description: "New description" } },
    headers: { Authorization: JsonWebToken.encode(user_id: @user.id) },
    as: :json
    assert_response :success
  end

  test "should not update team " do
    patch api_v1_team_url(@team),
    params: { team: { name: "New name", description: "New description" } },
    as: :json
    assert_response :forbidden
  end


  # DESTROY
  test "should destroy team" do
    assert_difference('Team.count', -1) do
      delete api_v1_team_url(@team),
      headers: { Authorization: JsonWebToken.encode(user_id: @user.id) },
      as: :json
    end
    assert_response :no_content
  end

  test "should forbid destroy team" do
    assert_no_difference('Team.count') do
      delete api_v1_team_url(@team), as: :json
    end
    assert_response :forbidden
  end
end
 

Спасибо!

Комментарии:

1. вы пытаетесь протестировать свой контроллер? где находится ваш файл спецификации контроллера?

2. @SaiqulHaq : Да, я пишу тесты для своего контроллера. Файл спецификации находится в предопределенной папке тестирования (/my-app/test/контроллеры/некоторые пространства имен/teams_controller_test.rb).

3. Как выглядит ваш контроллер и ваш тест контроллера? Пожалуйста, поделитесь кодом обоих.

4. Похоже, вам нужен интеграционный тест или, возможно, системный тест

5. Я бы сказал, что вам, вероятно, следует начать с того, чтобы убрать все провода в слой модели. Например, вы не должны вызывать JoinedTeamUser.create! свой контроллер — вместо этого у вас должен быть метод в команде или у пользователя, который предоставляет права на редактирование и тесты для модели.

Ответ №1:

Я бы начал с того, что на самом деле исправил модели.

 class Team < ApplicationRecord
  has_many :memberships
  has_many :members, through: :memberships
end

class Membership < ApplicationRecord
  belongs_to :team
  belongs_to :member, class_name: 'User'
end

class User < ApplicationRecord
  has_many :memberships, 
     foreign_key: :member_id
  has_many :teams, 
     through: :memberships
end
 

Joins никогда не должно быть частью названия вашей модели. Назовите свои модели в честь того, что они представляют в вашем домене, а не как образцы сантехники.

Все is_admin , что есть, должно быть admin? .

Если вы хотите создать команду и одновременно сделать участником пользователя, создавшего команду, вы хотите это сделать:

 # See https://github.com/rubocop/ruby-style-guide#namespace-definition
module Api
  module V1
    class TeamsController < ApplicationController
      def create
        @team = Team.new(team_params)
        @team.memberships.new(
          member: current_user,
          can_edit: current_user.admin?
        )
        if team.save 
          # why is your JSON rendering so clunky?
          render json: TeamSerializer.new(team).serializable_hash.to_json, status: :created
        else
          render json: {errors: team.errors }, status: :unprocessable_entity
        end
      end

      # ...
    end
  end
end
 

Это позволит объединить команду и членство в одной транзакции и избежать создания дополнительных путей кода в контроллере.

 require "test_helper"

module API
  module V1
    class TeamsControllerTest < ActionDispatch::IntegrationTest
      setup do
        @team = teams(:one)
        @user = users(:one)
      end

      # ...
      test "when creating a team the creator is added as a member" do
        post api_v1_teams_url,
          # add valid parameters to create a team
          params: { team: { ... } }, 
          # repeating this all over your tests is very smelly
          headers: { Authorization: JsonWebToken.encode(user_id: @user.id) },
          as: :json
        assert @user.team_memberships.exist?(
          team_id: Team.last.id
        ), 'expected user to be a member of team'
      end
    end
  end
end
 

Что касается фактического добавления проверок авторизации, я бы рекомендовал вам изучить Pundit вместо того, чтобы изобретать велосипед, это также в значительной степени совпадает с Rolify, что является гораздо лучшей альтернативой, чем добавление растущего числа can_x логических столбцов.