#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
логических столбцов.