suit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.DS_Store ADDED
Binary file
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "activesupport", ">= 2.3.5"
4
+ gem "rspec", "~> 2.8.0"
5
+ gem "factory_girl", ">= 1.3.0"
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "rdoc", "~> 3.12"
11
+ gem "bundler", "~> 1.0.0"
12
+ gem "jeweler", "~> 1.8.3"
13
+ gem "rcov", ">= 0"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,40 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.1.3)
5
+ multi_json (~> 1.0)
6
+ diff-lcs (1.1.3)
7
+ factory_girl (2.3.2)
8
+ activesupport
9
+ git (1.2.5)
10
+ jeweler (1.8.3)
11
+ bundler (~> 1.0)
12
+ git (>= 1.2.5)
13
+ rake
14
+ rdoc
15
+ json (1.6.5)
16
+ multi_json (1.0.4)
17
+ rake (0.9.2.2)
18
+ rcov (1.0.0)
19
+ rdoc (3.12)
20
+ json (~> 1.4)
21
+ rspec (2.8.0)
22
+ rspec-core (~> 2.8.0)
23
+ rspec-expectations (~> 2.8.0)
24
+ rspec-mocks (~> 2.8.0)
25
+ rspec-core (2.8.0)
26
+ rspec-expectations (2.8.0)
27
+ diff-lcs (~> 1.1.2)
28
+ rspec-mocks (2.8.0)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ activesupport (>= 2.3.5)
35
+ bundler (~> 1.0.0)
36
+ factory_girl (>= 1.3.0)
37
+ jeweler (~> 1.8.3)
38
+ rcov
39
+ rdoc (~> 3.12)
40
+ rspec (~> 2.8.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Justin Ball
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,53 @@
1
+ = suit
2
+
3
+ A set of rspec scope matchers.
4
+
5
+ == Matchers for common controller methods:
6
+
7
+ describe UsersController, "on GET to show with a valid id" do
8
+
9
+ it { should require_login :index, :get }
10
+ it { should require_role('admin', :index, :get) }
11
+ end
12
+
13
+
14
+ == Matchers for common active record scopes:
15
+
16
+ scope :by_title, order("title ASC")
17
+ scope :by_name, order("name ASC")
18
+ scope :by_newest, order("created_at DESC")
19
+ scope :by_oldest, order("created_at ASC")
20
+ scope :by_latest, order("updated_at DESC")
21
+ scope :newer_than, lambda { |*args| where("created_at > ?", args.first || DateTime.now) }
22
+ scope :older_than, lambda { |*args| where("created_at < ?", args.first || 1.day.ago.to_s(:db)) }
23
+ scope :is_public, where(["is_public = ?", true])
24
+ scope :created_by, lambda { |creator| where("creator_id = ? AND creator_type = ?", creator.id, creator.class.to_s) } }
25
+ scope :sorted, order("sort ASC")
26
+ scope :sorted_id, order("id ASC")
27
+
28
+ These matchers will test common scopes used in active record models:
29
+
30
+ describe User do
31
+ it { should scope_by_title }
32
+ it { should scope_by_name }
33
+ it { should scope_by_latest }
34
+ it { should scope_by_newest }
35
+ it { should scope_by_oldest }
36
+
37
+ it { should scope_newer_than }
38
+ it { should scope_older_than }
39
+
40
+ it { should scope_is_public }
41
+
42
+ it { should scope_created_by }
43
+
44
+ it { should sanitize :title }
45
+ it { should scope_sorted }
46
+ it { should scope_sorted_id }
47
+ end
48
+
49
+ == Copyright
50
+
51
+ Copyright (c) 2012 Tatemae.com. See LICENSE.txt for
52
+ further details.
53
+
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "suit"
18
+ gem.homepage = "http://github.com/tatemae/suit"
19
+ gem.license = "MIT"
20
+ gem.summary = "common rspec matchers"
21
+ gem.description = %Q{A collection of commonly used rspec matchers}
22
+ gem.email = "justinball@gmail.com"
23
+ gem.authors = ["Justin Ball"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "suit #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))
2
+
3
+ require 'matchers/login_matcher'
4
+ require 'matchers/role_matcher'
5
+
6
+ module Suit # :nodoc:
7
+ module Controllers # :nodoc:
8
+ # = Matchers for common controller methods:
9
+ #
10
+ # describe UsersController, "on GET to show with a valid id" do
11
+ #
12
+ # it { should require_login :index, :get }
13
+ # it { should require_role('admin', :index, :get) }
14
+ # end
15
+ #
16
+ module Matchers
17
+ end
18
+ end
19
+ end
Binary file
@@ -0,0 +1,50 @@
1
+ module Suit # :nodoc:
2
+ module Controllers # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensure a login is required for the given action
6
+ # Parameters:
7
+ # action - Name of the action in the control that should require a login ie "index"
8
+ # verb - Method to use to call the action ie :post, :get
9
+ # login_url - Optional url the user should be redirected to if they aren't logged in. Defaults to '/login'
10
+ #
11
+ # Example:
12
+ # it { should require_login 'index', :get, '/signup' }
13
+ def require_login(action, verb, login_url = '/login')
14
+ RequireLoginMatcher.new(action, verb, login_url, self)
15
+ end
16
+
17
+ class RequireLoginMatcher
18
+
19
+ def initialize(action, verb, login_url, context)
20
+ @action = action
21
+ @verb = verb
22
+ @login_url = login_url
23
+ @context = context
24
+ end
25
+
26
+ def matches?(controller)
27
+ @controller = controller
28
+ requires_login?
29
+ end
30
+
31
+ def failure_message
32
+ "Expected a #{@action} to #{@action} to require login"
33
+ end
34
+
35
+ def description
36
+ "require login #{@action}"
37
+ end
38
+
39
+ private
40
+
41
+ def requires_login?
42
+ response = @context.send(@verb, @action, :id => 1)
43
+ @context.send(:assert_redirected_to, @login_url)
44
+ true
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,72 @@
1
+ module Suit # :nodoc:
2
+ module Controllers # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensure a role is required for the given action
6
+ # Parameters:
7
+ # role - Role require to access the url.
8
+ # action - Name of the action in the control that should require a role ie "index"
9
+ # verb - Method to use to call the action ie :post, :get
10
+ # flash_message - Flash message indicating that the user was denied access
11
+ # role_url - Optional url the user should be redirected to if they aren't logged in. Defaults to '/role'
12
+ #
13
+ # Example:
14
+ # it { should require_role 'index', :get, /access denied/i, '/signup' }
15
+ def require_role(role, action, verb, flash_message = /permission/i, role_url = '/login')
16
+ RequireRoleMatcher.new(role, action, verb, role_url, flash_message, self)
17
+ end
18
+
19
+ class RequireRoleMatcher
20
+
21
+ def initialize(role, action, verb, role_url, flash_message, context)
22
+ @role = role
23
+ @action = action
24
+ @verb = verb
25
+ @role_url = role_url
26
+ @context = context
27
+ @flash_message = flash_message
28
+ end
29
+
30
+ def matches?(controller)
31
+ @controller = controller
32
+ requires_role?
33
+ end
34
+
35
+ def failure_message
36
+ "Expected a '#{@action}' to '#{@action}' to require role '#{@role}'"
37
+ end
38
+
39
+ def description
40
+ "require role #{@action}"
41
+ end
42
+
43
+ private
44
+
45
+ def requires_role?
46
+ response = @context.send(@verb, @action, :id => 1)
47
+ @context.send(:assert_redirected_to, @role_url)
48
+ if flash.values.any? {|value| value =~ @flash_message }
49
+ true
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def flash
56
+ return @flash if @flash
57
+ flash_and_now = @controller.request.session["flash"].dup if @controller.request.session["flash"]
58
+ flash = @controller.send(:flash)
59
+
60
+ @flash = if @now
61
+ flash.keys.each {|key| flash_and_now.delete(key) }
62
+ flash_and_now
63
+ else
64
+ flash
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,53 @@
1
+ $LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))
2
+
3
+ require 'matchers/suit_matcher_base'
4
+ require 'matchers/scope_creator_matchers'
5
+ require 'matchers/scope_is_public_matchers'
6
+ require 'matchers/scope_ordinal_matchers'
7
+ require 'matchers/scope_sorting_matchers'
8
+ require 'matchers/scope_time_matchers'
9
+ require 'matchers/scope_active_matchers'
10
+ require 'matchers/nested_attribute_matcher'
11
+ require 'matchers/sanitize_matcher'
12
+
13
+ module Suit # :nodoc:
14
+ module Models # :nodoc:
15
+ # = Matchers for common active record scopes:
16
+ #
17
+ # scope :by_title, order("title ASC")
18
+ # scope :by_name, order("name ASC")
19
+ # scope :by_newest, order("created_at DESC")
20
+ # scope :by_oldest, order("created_at ASC")
21
+ # scope :by_latest, order("updated_at DESC")
22
+ # scope :newer_than, lambda { |*args| where("created_at > ?", args.first || DateTime.now) }
23
+ # scope :older_than, lambda { |*args| where("created_at < ?", args.first || 1.day.ago.to_s(:db)) }
24
+ # scope :is_public, where(["is_public = ?", true])
25
+ # scope :created_by, lambda { |creator| where("creator_id = ? AND creator_type = ?", creator.id, creator.class.to_s) } }
26
+ # scope :sorted, order("sort ASC")
27
+ # scope :sorted_id, order("id ASC")
28
+ #
29
+ # These matchers will test common scopes used in active record models:
30
+ #
31
+ # describe User do
32
+ # it { should scope_by_title }
33
+ # it { should scope_by_name }
34
+ # it { should scope_by_latest }
35
+ # it { should scope_by_newest }
36
+ # it { should scope_by_oldest }
37
+
38
+ # it { should scope_newer_than }
39
+ # it { should scope_older_than }
40
+
41
+ # it { should scope_is_public }
42
+
43
+ # it { should scope_created_by }
44
+
45
+ # it { should sanitize :title }
46
+ # it { should scope_sorted }
47
+ # it { should scope_sorted_id }
48
+ # end
49
+
50
+ module Matchers
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ module Suit # :nodoc:
2
+ module Models # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model can accept nested attributes for the given model
6
+ def accept_nested_attributes_for(nested_model)
7
+ NestedAttributeMatcher.new(nested_model)
8
+ end
9
+
10
+ class NestedAttributeMatcher < SuitMatcherBase # :nodoc:
11
+
12
+ def initialize(nested_model)
13
+ @nested_model = nested_model
14
+ end
15
+
16
+ def matches?(subject)
17
+ @subject = subject
18
+ @subject.methods.include?("#{@nested_model}_attributes=")
19
+ end
20
+
21
+ def failure_message
22
+ "#{factory_name} does not accept nested attributes for #{@nested_model}"
23
+ end
24
+
25
+ def description
26
+ "accepts nested attributes for"
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,59 @@
1
+ module Suit # :nodoc:
2
+ module Models # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model sanitizes the given attributes
6
+ def sanitize(attribute)
7
+ SanitizeMatcher.new(attribute)
8
+ end
9
+
10
+ class SanitizeMatcher < SuitMatcherBase # :nodoc:
11
+
12
+ def initialize(attribute)
13
+ @attribute = attribute
14
+ end
15
+
16
+ def matches?(subject)
17
+ @subject = subject
18
+ sanitizes?
19
+ end
20
+
21
+ def failure_message
22
+ "#{factory_name} does not correctly sanitize the attribute #{@attribute}"
23
+ end
24
+
25
+ def description
26
+ "sanitizes attribute"
27
+ end
28
+
29
+ private
30
+
31
+ def sanitizes?
32
+ bad_scripts = [
33
+ %|';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//\";alert(String.fromCharCode(88,83,83))//--></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>|,
34
+ %|'';!--"<XSS>=&{()}|,
35
+ %|<SCRIPT SRC=http://ha.ckers.org/xss.js></SCRIPT>|,
36
+ %|<IMG SRC="javascript:alert('XSS');">|,
37
+ %|<IMG SRC=javascript:alert('XSS')>|,
38
+ %|<IMG SRC=JaVaScRiPt:alert('XSS')>|,
39
+ %|<IMG SRC=JaVaScRiPt:alert('XSS')>|,
40
+ %|<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>|,
41
+ %|<IMG """><SCRIPT>alert("XSS")</SCRIPT>">|,
42
+ %|<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>|,
43
+ %|<A HREF="h
44
+ tt p://6&#9;6.000146.0x7.147/">XSS</A>|,
45
+ %|<script>alert('message');</script>| ]
46
+ bad_scripts.each do |bad_value|
47
+ @subject.send("#{@attribute}=", bad_value)
48
+ @subject.save
49
+ clean_value = @subject.send(@attribute)
50
+ return false if clean_value.include?(bad_value)
51
+ end
52
+ true
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,43 @@
1
+ module Suit # :nodoc:
2
+ module Models # :nodoc:
3
+ module Matchers
4
+
5
+ # 'active' named scope which indicates whether an item is active or not
6
+ # requires that the class have a factory
7
+ # Tests:
8
+ # scope :newer_than, lambda { |time| {:conditions => ["created_at < ?", time || 1.day.ago] } }
9
+ # Examples:
10
+ # it { should scope_active }
11
+ def scope_active
12
+ ActiveMatcher.new(:active)
13
+ end
14
+
15
+ class ActiveMatcher < SuitMatcherBase # :nodoc:
16
+
17
+ def initialize(scope, field = :active)
18
+ @scope = scope
19
+ @field = field
20
+ end
21
+
22
+ def matches?(subject)
23
+ @subject = subject
24
+ @subject.class.delete_all
25
+ active_item = Factory(factory_name, @field => true)
26
+ not_active_item = Factory(factory_name, @field => false)
27
+ @subject.class.send(@scope).include?(active_item) &&
28
+ !@subject.class.send(@scope).include?(not_active_item)
29
+ end
30
+
31
+ def failure_message
32
+ "Expected #{factory_name} to scope by #{@scope} on #{@field} and only find active items. But the call failed"
33
+ end
34
+
35
+ def description
36
+ "active items"
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,65 @@
1
+ module Suit # :nodoc:
2
+ module Models # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model can scope by 'source'
6
+ # requires that the class have a factory and that a user factory exist
7
+ # Tests:
8
+ # scope :created_by, lambda { |item_object| {:conditions => ["items.source_id = ? AND items.source_type = ?", item_object.id, item_object.class.to_s] } }
9
+ # Examples:
10
+ # it { should scope_created_by }
11
+ def scope_source
12
+ CreatedByMatcher.new(:source, :source)
13
+ end
14
+
15
+ # Ensures that the model can scope by created_by
16
+ # requires that the class have a factory and that a user factory exist
17
+ # Tests:
18
+ # scope :created_by, lambda { |user| where(['user_id = ?', user.id]) } }
19
+ # Examples:
20
+ # it { should scope_created_by }
21
+ def scope_created_by
22
+ CreatedByMatcher.new(:created_by, :user)
23
+ end
24
+
25
+ # Ensures that the model can scope by created_by
26
+ # requires that the class have a factory and that a user factory exist
27
+ # Tests:
28
+ # scope :by_creator, lambda { |creator| where(['creator_id = ?', creator.id]) } }
29
+ # Examples:
30
+ # it { should scope_by_creator }
31
+ def scope_by_creator
32
+ CreatedByMatcher.new(:by_creator, :creator)
33
+ end
34
+
35
+ class CreatedByMatcher < SuitMatcherBase # :nodoc:
36
+
37
+ def initialize(scope, field)
38
+ @scope = scope
39
+ @field = field
40
+ end
41
+
42
+ def matches?(subject)
43
+ @subject = subject
44
+ @subject.class.delete_all
45
+ @user = Factory(:user)
46
+ @user1 = Factory(:user)
47
+ @item = Factory(factory_name, @field => @user)
48
+ @item1 = Factory(factory_name, @field => @user1)
49
+ items = @subject.class.send(@scope, @user)
50
+ items.include?(@item) && !items.include?(@item1)
51
+ end
52
+
53
+ def failure_message
54
+ "Expected #{factory_name} to have scope created_by and to be able to successfully find #{@subject}'s creator"
55
+ end
56
+
57
+ def description
58
+ "scope created_by"
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ module Suit # :nodoc:
2
+ module Models # :nodoc:
3
+ module Matchers
4
+
5
+ # 'is_public' named scope which retrieves items that are marked public
6
+ # requires that the class have a factory
7
+ # Tests:
8
+ # scope :newer_than, lambda { |time| {:conditions => ["created_at < ?", time || 1.day.ago] } }
9
+ # Examples:
10
+ # it { should scope_is_public }
11
+ def scope_is_public
12
+ IsPublicMatcher.new(:is_public)
13
+ end
14
+
15
+ class IsPublicMatcher < SuitMatcherBase # :nodoc:
16
+
17
+ def initialize(scope, field = :is_public)
18
+ @scope = scope
19
+ @field = field
20
+ end
21
+
22
+ def matches?(subject)
23
+ @subject = subject
24
+ @subject.class.delete_all
25
+ public_item = Factory(factory_name, @field => true)
26
+ not_public_item = Factory(factory_name, @field => false)
27
+ @subject.class.send(@scope).include?(public_item) &&
28
+ !@subject.class.send(@scope).include?(not_public_item)
29
+ end
30
+
31
+ def failure_message
32
+ "Expected #{factory_name} to scope by #{@scope} on #{@field} and only find public items. But the call failed"
33
+ end
34
+
35
+ def description
36
+ "public items"
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,47 @@
1
+ module Suit # :nodoc:
2
+ module Models # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model can sort by 'sorted'
6
+ # requires that the class have a factory
7
+ # Tests:
8
+ # scope :sorted, order("sort ASC")
9
+ # Examples:
10
+ # it { should scope_sorted }
11
+ def scope_sorted
12
+ OrdinalMatcher.new(:sorted, :sort)
13
+ end
14
+
15
+ # it { should scope_sorted_id }
16
+ def scope_sorted_id
17
+ OrdinalMatcher.new(:sorted_id, :sort)
18
+ end
19
+
20
+ class OrdinalMatcher < SuitMatcherBase # :nodoc:
21
+
22
+ def initialize(scope, field)
23
+ @scope = scope
24
+ @field = field
25
+ end
26
+
27
+ def matches?(subject)
28
+ @subject = subject
29
+ @subject.class.delete_all
30
+ @first = Factory(factory_name, @field => 1)
31
+ @second = Factory(factory_name, @field => 2)
32
+ @first == @subject.class.send(@scope)[0] && @second == @subject.class.send(@scope)[1]
33
+ end
34
+
35
+ def failure_message
36
+ "Expected #{factory_name} to have scope #{@scope} and order by #{@field}"
37
+ end
38
+
39
+ def description
40
+ "sort by ordinal"
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,95 @@
1
+ module Suit # :nodoc:
2
+ module Models # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model can sort by_title
6
+ # requires that the class have a factory
7
+ # Tests:
8
+ # scope :by_title, order("title ASC")
9
+ # Examples:
10
+ # it { should scope_by_title }
11
+ def scope_by_title
12
+ SortingMatcher.new(:by_title, :title)
13
+ end
14
+
15
+ # Test for 'by_name' named scope which orders by name
16
+ # requires that the class have a shoulda factory
17
+ # Tests:
18
+ # scope :by_name, order("name ASC")
19
+ # Examples:
20
+ # it { should scope_by_name }
21
+ def scope_by_name
22
+ SortingMatcher.new(:by_name, :name)
23
+ end
24
+
25
+ # For 'by_latest named scope which orders by updated at:
26
+ # Tests:
27
+ # scope :by_latest, order("updated_at DESC")
28
+ # Examples:
29
+ # it { should scope_by_latest }
30
+ def scope_by_latest
31
+ SortingMatcher.new(:by_latest, :updated_at)
32
+ end
33
+
34
+ # Test for 'by_newest' named scope which orders by 'created_at DESC'
35
+ # requires that the class have a shoulda factory
36
+ # Tests:
37
+ # scope :by_newest, order("created_at DESC")
38
+ # Examples:
39
+ # it { should scope_by_newest }
40
+ def scope_by_newest
41
+ SortingMatcher.new(:by_newest, :created_at)
42
+ end
43
+
44
+ # Test for 'by_oldest' named scope which orders by 'created_at ASC'
45
+ # requires that the class have a shoulda factory
46
+ # Tests:
47
+ # scope :oldest, order("created_at ASC")
48
+ # Examples:
49
+ # it { should scope_oldest }
50
+ def scope_by_oldest
51
+ SortingMatcher.new(:by_oldest, :created_at)
52
+ end
53
+
54
+ class SortingMatcher < SuitMatcherBase # :nodoc:
55
+
56
+ def initialize(scope, field)
57
+ @scope = scope
58
+ @field = field
59
+ end
60
+
61
+ def matches?(subject)
62
+ @subject = subject
63
+ @subject.class.delete_all
64
+ if @scope == :by_newest
65
+ first = Factory(factory_name, :created_at => 1.hour.ago)
66
+ second = Factory(factory_name, :created_at => 1.day.ago)
67
+ first == @subject.class.send(@scope)[0] && second == @subject.class.send(@scope)[1]
68
+ elsif @scope == :by_oldest
69
+ first = Factory(factory_name, :created_at => 1.day.ago)
70
+ second = Factory(factory_name, :created_at => 1.hour.ago)
71
+ first == @subject.class.send(@scope)[0] && second == @subject.class.send(@scope)[1]
72
+ elsif @scope == :by_latest
73
+ first = Factory(factory_name, :updated_at => 1.hour.ago)
74
+ second = Factory(factory_name, :updated_at => 1.day.ago)
75
+ first == @subject.class.send(@scope)[0] && second == @subject.class.send(@scope)[1]
76
+ else
77
+ first = Factory(factory_name, @field => 'a')
78
+ second = Factory(factory_name, @field => 'b')
79
+ first == @subject.class.send(@scope)[0] && second == @subject.class.send(@scope)[1]
80
+ end
81
+ end
82
+
83
+ def failure_message
84
+ "Expected #{factory_name} to scope #{@scope} on #{@field}"
85
+ end
86
+
87
+ def description
88
+ "sorting"
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,59 @@
1
+ module Suit # :nodoc:
2
+ module Models # :nodoc:
3
+ module Matchers
4
+
5
+ # 'newer_than' named scope which retrieves items newer than given date
6
+ # requires that the class have a factory
7
+ # Tests:
8
+ # scope :newer_than, lambda { |time| {:conditions => ["created_at < ?", time || 1.day.ago] } }
9
+ # Examples:
10
+ # it { should scope_recent }
11
+ def scope_newer_than
12
+ TimeMatcher.new(:newer_than)
13
+ end
14
+
15
+ # 'scope_older_than' named scope which retrieves items older than the given date
16
+ # requires that the class have a factory
17
+ # Tests:
18
+ # scope :newer_than, lambda { |time| {:conditions => ["created_at < ?", time || 1.day.ago] } }
19
+ # Examples:
20
+ # it { should scope_recent }
21
+ def scope_older_than
22
+ TimeMatcher.new(:older_than)
23
+ end
24
+
25
+ class TimeMatcher < SuitMatcherBase # :nodoc:
26
+
27
+ def initialize(scope, field = :created_at)
28
+ @scope = scope
29
+ @field = field
30
+ end
31
+
32
+ def matches?(subject)
33
+ @subject = subject
34
+ @subject.class.delete_all
35
+ old_item = Factory(factory_name, @field => 1.month.ago)
36
+ new_item = Factory(factory_name, @field => 1.day.ago)
37
+ items = @subject.class.send(@scope, 1.week.ago)
38
+ if @scope == :newer_than
39
+ items.include?(new_item) && !items.include?(old_item)
40
+ elsif @scope == :older_than
41
+ !items.include?(new_item) && items.include?(old_item)
42
+ else
43
+ raise "matcher not implemented for #{@scope}"
44
+ end
45
+ end
46
+
47
+ def failure_message
48
+ "Expected #{factory_name} to scope by #{@scope} on #{@field} and be successful. But the call failed"
49
+ end
50
+
51
+ def description
52
+ "items scoped #{@scope}"
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,17 @@
1
+ module Suit # :nodoc:
2
+ module Models # :nodoc:
3
+ module Matchers
4
+
5
+ class SuitMatcherBase # :nodoc:
6
+
7
+ private
8
+
9
+ def factory_name
10
+ @subject.class.name.underscore.to_sym
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+ end
17
+ end
data/lib/suit.rb ADDED
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))
2
+
3
+ module Suit
4
+ end
5
+
6
+ require 'rspec/core'
7
+ require 'rspec/mocks'
8
+ require 'rspec/expectations'
9
+
10
+ require 'factory_girl'
11
+ require 'suit_factories'
12
+ require 'models/matchers'
13
+ require 'controllers/matchers'
14
+
15
+ RSpec.configure do |config|
16
+ config.include(Suit::Models::Matchers)
17
+ config.include(Suit::Controllers::Matchers)
18
+ config.color_enabled = true
19
+ end
@@ -0,0 +1,45 @@
1
+ FactoryGirl.define do
2
+
3
+ sequence :email do |n|
4
+ "somebody#{n}@example.com"
5
+ end
6
+
7
+ sequence :login do |n|
8
+ "inquire#{n}"
9
+ end
10
+
11
+ sequence :password do |n|
12
+ "password#{n}"
13
+ end
14
+
15
+ sequence :name do |n|
16
+ "a_name#{n}"
17
+ end
18
+
19
+ sequence :title do |n|
20
+ "a_title#{n}"
21
+ end
22
+
23
+ sequence :abbr do |n|
24
+ "abbr#{n}"
25
+ end
26
+
27
+ sequence :uri do |n|
28
+ "n#{n}.example.com"
29
+ end
30
+
31
+ sequence :description do |n|
32
+ "This is the description: #{n}"
33
+ end
34
+
35
+ sequence :locale do |n|
36
+ "a#{n}"
37
+ end
38
+
39
+ sequence :address do |n|
40
+ "#{n} West #{n} South"
41
+ end
42
+
43
+ end
44
+
45
+
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Suit::Controllers:::Matchers do
4
+ describe "require_login" do
5
+ it "ensures that login is required" do
6
+
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'suit'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
data/suit.gemspec ADDED
@@ -0,0 +1,86 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "suit"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Justin Ball"]
12
+ s.date = "2012-02-07"
13
+ s.description = "A collection of commonly used rspec matchers"
14
+ s.email = "justinball@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".DS_Store",
21
+ ".document",
22
+ ".rspec",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.rdoc",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "lib/.DS_Store",
30
+ "lib/controllers/matchers.rb",
31
+ "lib/controllers/matchers/.DS_Store",
32
+ "lib/controllers/matchers/login_matcher.rb",
33
+ "lib/controllers/matchers/role_matcher.rb",
34
+ "lib/models/matchers.rb",
35
+ "lib/models/matchers/nested_attribute_matcher.rb",
36
+ "lib/models/matchers/sanitize_matcher.rb",
37
+ "lib/models/matchers/scope_active_matchers.rb",
38
+ "lib/models/matchers/scope_creator_matchers.rb",
39
+ "lib/models/matchers/scope_is_public_matchers.rb",
40
+ "lib/models/matchers/scope_ordinal_matchers.rb",
41
+ "lib/models/matchers/scope_sorting_matchers.rb",
42
+ "lib/models/matchers/scope_time_matchers.rb",
43
+ "lib/models/matchers/suit_matcher_base.rb",
44
+ "lib/suit.rb",
45
+ "lib/suit_factories.rb",
46
+ "spec/controllers/matchers/login_matcher_spec.rb",
47
+ "spec/spec_helper.rb",
48
+ "suit.gemspec"
49
+ ]
50
+ s.homepage = "http://github.com/tatemae/suit"
51
+ s.licenses = ["MIT"]
52
+ s.require_paths = ["lib"]
53
+ s.rubygems_version = "1.8.12"
54
+ s.summary = "common rspec matchers"
55
+
56
+ if s.respond_to? :specification_version then
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
60
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.3.5"])
61
+ s.add_runtime_dependency(%q<rspec>, ["~> 2.8.0"])
62
+ s.add_runtime_dependency(%q<factory_girl>, [">= 1.3.0"])
63
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
64
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
65
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
66
+ s.add_development_dependency(%q<rcov>, [">= 0"])
67
+ else
68
+ s.add_dependency(%q<activesupport>, [">= 2.3.5"])
69
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
70
+ s.add_dependency(%q<factory_girl>, [">= 1.3.0"])
71
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
72
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
73
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
74
+ s.add_dependency(%q<rcov>, [">= 0"])
75
+ end
76
+ else
77
+ s.add_dependency(%q<activesupport>, [">= 2.3.5"])
78
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
79
+ s.add_dependency(%q<factory_girl>, [">= 1.3.0"])
80
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
81
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
82
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
83
+ s.add_dependency(%q<rcov>, [">= 0"])
84
+ end
85
+ end
86
+
metadata ADDED
@@ -0,0 +1,202 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: suit
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Justin Ball
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-02-07 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ type: :runtime
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 9
28
+ segments:
29
+ - 2
30
+ - 3
31
+ - 5
32
+ version: 2.3.5
33
+ version_requirements: *id001
34
+ name: activesupport
35
+ prerelease: false
36
+ - !ruby/object:Gem::Dependency
37
+ type: :runtime
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 47
44
+ segments:
45
+ - 2
46
+ - 8
47
+ - 0
48
+ version: 2.8.0
49
+ version_requirements: *id002
50
+ name: rspec
51
+ prerelease: false
52
+ - !ruby/object:Gem::Dependency
53
+ type: :runtime
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 27
60
+ segments:
61
+ - 1
62
+ - 3
63
+ - 0
64
+ version: 1.3.0
65
+ version_requirements: *id003
66
+ name: factory_girl
67
+ prerelease: false
68
+ - !ruby/object:Gem::Dependency
69
+ type: :development
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ hash: 31
76
+ segments:
77
+ - 3
78
+ - 12
79
+ version: "3.12"
80
+ version_requirements: *id004
81
+ name: rdoc
82
+ prerelease: false
83
+ - !ruby/object:Gem::Dependency
84
+ type: :development
85
+ requirement: &id005 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ hash: 23
91
+ segments:
92
+ - 1
93
+ - 0
94
+ - 0
95
+ version: 1.0.0
96
+ version_requirements: *id005
97
+ name: bundler
98
+ prerelease: false
99
+ - !ruby/object:Gem::Dependency
100
+ type: :development
101
+ requirement: &id006 !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ~>
105
+ - !ruby/object:Gem::Version
106
+ hash: 49
107
+ segments:
108
+ - 1
109
+ - 8
110
+ - 3
111
+ version: 1.8.3
112
+ version_requirements: *id006
113
+ name: jeweler
114
+ prerelease: false
115
+ - !ruby/object:Gem::Dependency
116
+ type: :development
117
+ requirement: &id007 !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ hash: 3
123
+ segments:
124
+ - 0
125
+ version: "0"
126
+ version_requirements: *id007
127
+ name: rcov
128
+ prerelease: false
129
+ description: A collection of commonly used rspec matchers
130
+ email: justinball@gmail.com
131
+ executables: []
132
+
133
+ extensions: []
134
+
135
+ extra_rdoc_files:
136
+ - LICENSE.txt
137
+ - README.rdoc
138
+ files:
139
+ - .DS_Store
140
+ - .document
141
+ - .rspec
142
+ - Gemfile
143
+ - Gemfile.lock
144
+ - LICENSE.txt
145
+ - README.rdoc
146
+ - Rakefile
147
+ - VERSION
148
+ - lib/.DS_Store
149
+ - lib/controllers/matchers.rb
150
+ - lib/controllers/matchers/.DS_Store
151
+ - lib/controllers/matchers/login_matcher.rb
152
+ - lib/controllers/matchers/role_matcher.rb
153
+ - lib/models/matchers.rb
154
+ - lib/models/matchers/nested_attribute_matcher.rb
155
+ - lib/models/matchers/sanitize_matcher.rb
156
+ - lib/models/matchers/scope_active_matchers.rb
157
+ - lib/models/matchers/scope_creator_matchers.rb
158
+ - lib/models/matchers/scope_is_public_matchers.rb
159
+ - lib/models/matchers/scope_ordinal_matchers.rb
160
+ - lib/models/matchers/scope_sorting_matchers.rb
161
+ - lib/models/matchers/scope_time_matchers.rb
162
+ - lib/models/matchers/suit_matcher_base.rb
163
+ - lib/suit.rb
164
+ - lib/suit_factories.rb
165
+ - spec/controllers/matchers/login_matcher_spec.rb
166
+ - spec/spec_helper.rb
167
+ - suit.gemspec
168
+ homepage: http://github.com/tatemae/suit
169
+ licenses:
170
+ - MIT
171
+ post_install_message:
172
+ rdoc_options: []
173
+
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ hash: 3
182
+ segments:
183
+ - 0
184
+ version: "0"
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ hash: 3
191
+ segments:
192
+ - 0
193
+ version: "0"
194
+ requirements: []
195
+
196
+ rubyforge_project:
197
+ rubygems_version: 1.8.12
198
+ signing_key:
199
+ specification_version: 3
200
+ summary: common rspec matchers
201
+ test_files: []
202
+