簡體   English   中英

具有多租戶的RSpec-為什么此簡單測試失敗?

[英]RSpec with multi tenancy - Why is this simple test failing?

我在做什么

我最近以“帶范圍的多租戶” (需要訂閱)為指導,實現了多租戶(使用范圍)。 注意:我正在使用可怕的“ default_scope”來進行租戶作用域確定(如Ryan的Railscast中所示)。 一切都可以在瀏覽器中正常工作,但是我的很多(不是全部)測試都失敗了,我不知道為什么。

我從頭開始構建了身份驗證(基於此Railscast:從頭開始進行身份驗證(修訂) -需要訂閱),並使用auth_token來實現“記住我”功能(基於此Railscast: 記住我並重置密碼 )。

我的問題

為什么此測試失敗,以及兩個變通辦法為何起作用? 我已經難過了幾天,只是想不通。

我認為正在發生的事情

我正在調用Jobs#create操作,並且Job.count 減少 1而不是增加 1。我認為正在創建的是工作,然后應用程序丟失了“承租人”分配(承租人正在刪除設為零),並且測試正在計算喬布斯的錯誤租戶。

奇怪的是,它期望得到“ 1”並得到“ -1”(而不是“ 0”),這意味着它得到了計數(請注意,在before塊中已經創建了一個“種子”作業,因此它很可能在計數“ 1”,然后調用#create),調用create動作( 應該使總數增加1到2),然后失去租戶,並切換到零個有0個工作的租戶。 所以:

  • 數1(種子作業)
  • 創造工作
  • 失去房客
  • 在新的(可能為零)租戶中計入0個工作

...導致Job.count更改為-1。

您可以在下面看到我通過在測試中的Job.count行中添加“ .unscoped”來半確認這一點。 這意味着存在預期的工作數量,但是這些工作不在應用程序測試的租戶中。

我不明白的是如何失去租戶。

我嘗試獲取代碼的相關部分,並創建了專用的單項測試規范,以使其盡可能易於剖析。 如果我可以采取其他措施來簡化可能的回答者操作,請告訴我該怎么做!

# application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery
  include SessionsHelper

  around_filter :scope_current_tenant

  private

  def current_user
    @current_user ||= User.unscoped.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
  end
  helper_method :current_user

  def current_tenant
    @current_tenant ||= Tenant.find_by_id!(session[:tenant_id]) if session[:tenant_id]
  end
  helper_method :current_tenant

  def update_current_tenant
    Tenant.current_id = current_tenant.id if current_tenant
  end
  helper_method :set_current_tenant

  def scope_current_tenant
    update_current_tenant
    yield
  ensure
    Tenant.current_id = nil
  end
end

# sessions_controller.rb
class SessionsController < ApplicationController
  def create
    user = User.unscoped.authenticate(params[:session][:email], params[:session][:password])

    if user && user.active? && user.active_tenants.any?
      if params[:remember_me]
        cookies.permanent[:auth_token] = user.auth_token
      else
        cookies[:auth_token] = user.auth_token
      end

      if !user.default_tenant_id.nil? && (default_tenant = Tenant.find(user.default_tenant_id)) && default_tenant.active
        # The user has a default tenant set, and that tenant is active
        session[:tenant_id] = default_tenant.id
      else
        # The user doesn't have a default
        session[:tenant_id] = user.active_tenants.first.id
      end
      redirect_back_or  root_path
    else
      flash.now[:error] = "Invalid email/password combination."
      @title = "Sign in"
      render 'new'
    end  
  end

  def destroy
    cookies.delete(:auth_token)
    session[:tenant_id] = nil
    redirect_to root_path
  end
end

# jobs_controller.rb
class JobsController < ApplicationController
  before_filter :authenticate_admin

  # POST /jobs
  # POST /jobs.json
  def create    
    @job = Job.new(params[:job])
    @job.creator = current_user

    respond_to do |format|
      if @job.save
        format.html { redirect_to @job, notice: 'Job successfully created.' }
        format.json { render json: @job, status: :created, location: @job }
      else
        flash.now[:error] = 'There was a problem creating the Job.'
        format.html { render action: "new" }
        format.json { render json: @job.errors, status: :unprocessable_entity }
      end
    end
  end
end

# job.rb
class Job < ActiveRecord::Base
  has_ancestry

  default_scope { where(tenant_id: Tenant.current_id) }
  .
  .
  .

end

# sessions_helper.rb
module SessionsHelper

require 'bcrypt'

  def authenticate_admin
    deny_access unless admin_signed_in?
  end

  def deny_access
    store_location
    redirect_to signin_path, :notice => "Please sign in to access this page."
  end

  private

  def store_location
    session[:return_to] = request.fullpath
  end
end

# spec_test_helper.rb
module SpecTestHelper 
  def test_sign_in(user)
    request.cookies[:auth_token] = user.auth_token
    session[:tenant_id] = user.default_tenant_id
    current_user = user
    @current_user = user
  end

  def current_tenant
    @current_tenant ||= Tenant.find_by_id!(session[:tenant_id]) if session[:tenant_id]
  end
end


# test_jobs_controller_spec.rb
require 'spec_helper'

describe JobsController do
  before do
    # This is all just setup to support requirements that the admin is an "Admin" (role)
    # That there's a tenant for him to use
    # That there are some workdays - a basic requirement for the app - jobs, checklist
    # All of this is to satisfy assocations and 
    @role = FactoryGirl.create(:role)
    @role.name = "Admin"
    @role.save
    @tenant1 = FactoryGirl.create(:tenant)
    @tenant2 = FactoryGirl.create(:tenant)
    @tenant3 = FactoryGirl.create(:tenant)

    Tenant.current_id = @tenant1.id
    @user = FactoryGirl.create(:user)
    @workday1 = FactoryGirl.create(:workday)
    @workday1.name = Time.now.to_date.strftime("%A")
    @workday1.save
    @checklist1 = FactoryGirl.create(:checklist)
    @job = FactoryGirl.create(:job)
    @checklist1.jobs << @job
    @workday1.checklists << @checklist1
    @admin1 = FactoryGirl.create(:user)
    @admin1.tenants << @tenant1
    @admin1.roles << @role
    @admin1.default_tenant_id = @tenant1.id
    @admin1.pin = ""
    @admin1.save!
    # This is above in the spec_test_helper.rb code
    test_sign_in(@admin1)
  end

  describe "POST create" do
    context "with valid attributes" do      
      it "creates a new job" do
        expect{ # <-- This is line 33 that's mentioned in the failure below
          post :create, job: FactoryGirl.attributes_for(:job)
        # This will pass if I change the below to Job.unscoped
        # OR it will pass if I add Tenant.current_id = @tenant1.id right here.
        # But I shouldn't need to do either of those because
        # The tenant should be set by the around_filter in application_controller.rb
        # And the default_scope for Job should handle scoping
        }.to change(Job,:count).by(1)
      end
    end
  end
end

這是rspec的失敗:

Failures:

  1) JobsController POST create with valid attributes creates a new job
     Failure/Error: expect{
       count should have been changed by 1, but was changed by -1
     # ./spec/controllers/test_jobs_controller_spec.rb:33:in `block (4 levels) in <top (required)>'

Finished in 0.66481 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/test_jobs_controller_spec.rb:32 # JobsController POST create with valid attributes creates a new job

如果我添加一些“ puts”行以直接查看誰是current_tenant並通過檢查會話哈希,那么我會一直看到相同的租戶ID:

describe "POST create" do
  context "with valid attributes" do      
    it "creates a new job" do
      expect{
        puts current_tenant.id.to_s
        puts session[:tenant_id]
        post :create, job: FactoryGirl.attributes_for(:job)
        puts current_tenant.id.to_s
        puts session[:tenant_id]
      }.to change(Job,:count).by(1)
    end
  end
end

產量...

87
87
87
87
F

Failures:

  1) JobsController POST create with valid attributes creates a new job
     Failure/Error: expect{
       count should have been changed by 1, but was changed by -1
     # ./spec/controllers/test_jobs_controller_spec.rb:33:in `block (4 levels) in <top (required)>'

Finished in 0.66581 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/test_jobs_controller_spec.rb:32 # JobsController POST create with valid attributes creates a new job

我認為不是RSpec忽略了默認范圍,而是通過將當前用戶設置為nil在環境過濾器的ApplicationController中對其進行了重置。

我在使用Assigns(...)時遇到了這個問題,它的發生是因為在評估分配時,該關系實際上已解決。 我認為您的期望也可能是這種情況。

更新:在我的情況下,我能找到的最干凈的解決方案(盡管我仍然討厭它)是通過在測試環境中不將當前用戶設置為nil來泄漏默認范圍。

在您的情況下,總計為:

def scope_current_tenant
  update_current_tenant
  yield
ensure
  Tenant.current_id = nil unless Rails.env == 'test'
end

我尚未使用您的代碼對其進行測試,但這可能會有所幫助。

我設法通過了測試,盡管我仍然不確定為什么不能從頭開始。 這是我所做的:

  describe "POST create" do
    context "with valid attributes" do      
      it "creates a new job" do
        expect{ # <-- This is line 33 that's mentioned in the failure below
          post :create, job: FactoryGirl.attributes_for(:job)
        }.to change(Job.where(tenant_id: @tenant1.id),:count).by(1)
      end
    end
  end

我變了:

change(Job,:count).by(1)

...至:

change(Job.where(tenant_id: @tenant1.id),:count).by(1)

注意: @ tenant1是已登錄管理員的租戶。

我假設default_scopes將在RSpec中應用,但似乎沒有(或至少不在“ expect”塊的“:change”部分中)。 在這種情況下,Job的default_scope為:

default_scope { where(tenant_id: Tenant.current_id) }

實際上,如果我將該行更改為:

change(Job.where(tenant_id: Tenant.current_id),:count).by(1)

...它也會通過。 因此,如果我在規范中明確模仿Job的default_scope,它將通過。 這似乎證明RSpec忽略了Jobs上的default_scope。

在某種程度上,我認為我的新測試是確保租戶數據保持隔離的更好方法,因為我正在顯式檢查特定租戶內的計數,而不是隱式檢查租戶的計數(通過假定計數位於“當前”承租人”)。

我標記我的答案是正確的,因為它是唯一的答案,如果其他人遇到此問題,我認為我的答案將幫助他們解決問題。 就是說,我真的沒有回答我最初的問題,即為什么測試失敗。 如果有人對RSpec為何似乎忽略“ expect”塊中的default_scope有所了解,則可能有助於使該問題對其他人有用。

你們有同樣的問題。 我沒有做出讓自己感到滿意的方式來解決問題,但仍然比驗證您的RAILS_ENV好。 舉這個例子。

it "saves person" do
    expect {
      some_post_action
    }.to change(Person, :count).by(1)
  end

每次我嘗試保存count方法時,都會進行如下選擇:“從tenant_id為null的人中選擇count(*)”

我設法通過在更改方法中設置Person.unscoped來解決此問題:

        }.to change(Person, :count).by(1)

對此:

        }.to change(Person.unscoped, :count).by(1)

這不是最好的解決方案,但我仍在嘗試尋找一種方法來繞過default_scope。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM