Опубликован: 27.01.2016 | Доступ: свободный | Студентов: 914 / 58 | Длительность: 23:07:00
Лекция 11:

Слежение за сообщениями пользователей

< Лекция 10 || Лекция 11: 12345678

Стандартный способ реализации кнопки "читать" (follow)

Теперь, когда наши представления в порядке, пришло время получить рабочие follow/unfollow кнопки. Тесты для этих кнопок комбинируют множество техник тестирования о которых было рассказано в этом учебнике и представляют из себя хорошее упражнение в чтении кода. Изучайте Листинг 11.32 до тех пор пока не убедитесь что вы понимаете что и почему мы тестируем. (Листинг 11.32 содержит одно небольшое упущение в безопасности; посмотрим, сможете ли вы выявить его. Мы скоро расскажем о нем.) Особенно обратите внимание на использование метода have_xpath использующего продвинутую и мощную технику XPath для навигации по XML документам (включая HTML5). Вы можете узнать больше о XPath с помощью поискового запроса XPath syntax.

require 'spec_helper'

describe "User pages" do
  .
  .
  .
  describe "profile page" do
    let(:user) { FactoryGirl.create(:user) }
    .
    .
    .
    describe "follow/unfollow buttons" do
      let(:other_user) { FactoryGirl.create(:user) }
      before { sign_in user }

      describe "following a user" do
        before { visit user_path(other_user) }

        it "should increment the followed user count" do
          expect do
            click_button "Follow"
          end.to change(user.followed_users, :count).by(1)
        end

        it "should increment the other user's followers count" do
          expect do
            click_button "Follow"
          end.to change(other_user.followers, :count).by(1)
        end

        describe "toggling the button" do
          before { click_button "Follow" }
          it { should have_xpath("//input[@value='Unfollow']") }
        end
      end

      describe "unfollowing a user" do
        before do
          user.follow!(other_user)
          visit user_path(other_user)
        end

        it "should decrement the followed user count" do
          expect do
            click_button "Unfollow"
          end.to change(user.followed_users, :count).by(-1)
        end

        it "should decrement the other user's followers count" do
          expect do
            click_button "Unfollow"
          end.to change(other_user.followers, :count).by(-1)
        end

        describe "toggling the button" do
          before { click_button "Unfollow" }
          it { should have_xpath("//input[@value='Follow']") }
        end
      end
    end
  end
  .
  .
  .
end
Листинг 11.32. Тесты для Follow/Unfollow кнопки. spec/requests/user_pages_spec.rb

Листинг 11.32 тестирует кнопки кликая по ним и проверяя соответствующее поведение. Написание реализации подразумевает чуть более глубокое погружения в тему: following и unfollowing включает создание и уничтожение взаимоотношений, что означает необходимость определения create и destroy действий в контроллере Relationships (который нам еще нужно создать). Хотя кнопки появляются только для вошедших пользователей, что дает нам безопасность верхнего уровня, тесты в Листинге 11.32 упускают из виду безопасность на уровне контроллера, а именно: сами create и destroy должны быть доступны только для вошедших пользователей. (Это та самая уязвимость о которой мы говорили выше.) Листинг 11.33 выражает эти требования с помощью post и delete методов вызывающих эти действия напрямую.

require 'spec_helper'

describe "Authentication" do
  .
  .
  .
  describe "authorization" do

    describe "for non-signed-in users" do
      let(:user) { FactoryGirl.create(:user) }
      .
      .
      .
      describe "in the Relationships controller" do
        describe "submitting to the create action" do
          before { post relationships_path }
          specify { expect(response).to redirect_to(signin_path) }
        end

        describe "submitting to the destroy action" do
          before { delete relationship_path(1) }
          specify { expect(response).to redirect_to(signin_path) }
        end
      end
      .
      .
      .
    end
  end
end
Листинг 11.33. Тестирование авторизации контроллера Relationships. spec/requests/authentication_pages_spec.rb

Обратите внимание, что, для того чтобы избежать лишней работы по созданию практически бесполезного объекта Relationship, тест delete хардкодит id 1 в именованном маршруте:

before { delete relationship_path(1) }

Это работает из-за того что пользователь должен быть перенаправлен прежде чем приложение даже попытается обратиться к взаимоотношению с этим id.

Код контроллера, необходимый для прохождения этих тестов удивительно краток: мы просто вытягиваем читаемого или читающего пользователя, а затем читаем или не читаем его сообщения используя соответствующий служебный метод. Полная реализация представлена в Листинге 11.34.

class RelationshipsController < ApplicationController
  before_action :signed_in_user

  def create
    @user = User.find(params[:relationship][:followed_id])
    current_user.follow!(@user)
    redirect_to @user
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow!(@user)
    redirect_to @user
  end
end
Листинг 11.34. Контроллер Relationships. app/controllers/relationships_controller.rb

В Листинге 11.34 мы можем видеть почему уязвимость отмеченная выше является незначительной: если невошедший пользователь попробовал бы обратиться к любому из действий напрямую (например, с помощью инструмента командной строки вроде curl), current_user был бы nil и в обоих случаях вторая строка действий вызвала бы исключение, что привело бы к ошибке, но не нанесло бы вреда приложению или его данным. Однако лучше на это не полагаться, так что мы предприняли дополнительный шаг и добавили дополнительный уровень безопасности.

С этим ядро функциональности follow/unfollow завершено, и любой пользователь может читать (или не читать) сообщения любого другого пользователя, что вам стоит проверить и в вашем браузере и запустив набор тестов:

$ bundle exec rspec spec/
< Лекция 10 || Лекция 11: 12345678
Вадим Обозин
Вадим Обозин

Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора?