trello_effort_tracker 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +25 -0
- data/.rspec +3 -0
- data/.rvmrc.template +2 -0
- data/.travis.yml +21 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +101 -0
- data/LICENSE.txt +15 -0
- data/README.md +159 -0
- data/Rakefile +72 -0
- data/config/config.template.yml +7 -0
- data/config/mongoid.template.yml +24 -0
- data/lib/patches/trello/member.rb +20 -0
- data/lib/startup_trello.rb +2 -0
- data/lib/trello_effort_tracker/effort.rb +27 -0
- data/lib/trello_effort_tracker/estimate.rb +25 -0
- data/lib/trello_effort_tracker/google_docs_exporter.rb +67 -0
- data/lib/trello_effort_tracker/member.rb +46 -0
- data/lib/trello_effort_tracker/mongoid_helper.rb +13 -0
- data/lib/trello_effort_tracker/tracked_card.rb +103 -0
- data/lib/trello_effort_tracker/tracking.rb +130 -0
- data/lib/trello_effort_tracker/trello_authorize.rb +32 -0
- data/lib/trello_effort_tracker/trello_configuration.rb +33 -0
- data/lib/trello_effort_tracker/trello_tracker.rb +48 -0
- data/lib/trello_effort_tracker/version.rb +3 -0
- data/lib/trello_effort_tracker.rb +23 -0
- data/script/ci/before_script.sh +1 -0
- data/script/ci/run_build.sh +2 -0
- data/script/crontab.template +8 -0
- data/script/mate.sh +1 -0
- data/spec/effort_spec.rb +52 -0
- data/spec/estimate_spec.rb +38 -0
- data/spec/integration/trello_authorization_spec.rb +12 -0
- data/spec/member_spec.rb +45 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/tracked_card_spec.rb +267 -0
- data/spec/tracking_spec.rb +236 -0
- data/spec/trello_authorize_spec.rb +71 -0
- data/spec/trello_configuration_spec.rb +43 -0
- data/trello_effort_tracker.gemspec +19 -0
- metadata +86 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
class TrackedCard
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Timestamps
|
4
|
+
extend MongoidHelper
|
5
|
+
|
6
|
+
field :name
|
7
|
+
field :description
|
8
|
+
field :short_id, type: Integer
|
9
|
+
field :trello_id
|
10
|
+
field :due, type: Date
|
11
|
+
field :closed, type: Boolean
|
12
|
+
field :url
|
13
|
+
field :pos
|
14
|
+
|
15
|
+
embeds_many :estimates
|
16
|
+
embeds_many :efforts
|
17
|
+
|
18
|
+
validates_presence_of :name, :short_id, :trello_id
|
19
|
+
validates_numericality_of :short_id
|
20
|
+
|
21
|
+
def self.find_by_trello_id(trello_id)
|
22
|
+
without_mongo_raising_errors do
|
23
|
+
find_by(trello_id: trello_id)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.update_or_create_with(trello_card)
|
28
|
+
card = TrackedCard.find_or_create_by(trello_id: trello_card.id)
|
29
|
+
trello_card.attributes.delete(:id)
|
30
|
+
success = card.update_attributes(trello_card.attributes)
|
31
|
+
return card if success
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.build_from(trello_card)
|
35
|
+
trello_card_id = trello_card.id
|
36
|
+
trello_card.attributes.delete(:id)
|
37
|
+
new(trello_card.attributes.merge(trello_id: trello_card_id))
|
38
|
+
end
|
39
|
+
|
40
|
+
def add(tracking)
|
41
|
+
if tracking.estimate? && estimates.none? {|e| e.tracking_notification_id == tracking.estimate.tracking_notification_id}
|
42
|
+
estimates << tracking.estimate
|
43
|
+
elsif tracking.effort? && efforts.none? {|e| e.tracking_notification_id == tracking.effort.tracking_notification_id}
|
44
|
+
efforts << tracking.effort
|
45
|
+
else
|
46
|
+
Trello.logger.warn "Ignoring tracking notification: #{tracking}" if tracking.unknown_format?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def no_tracking?
|
51
|
+
first_activity_date.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def first_activity_date
|
55
|
+
[working_start_date, first_estimate_date].compact.min
|
56
|
+
end
|
57
|
+
|
58
|
+
def working_start_date
|
59
|
+
efforts.sort_by(&:date).first.date if efforts.present?
|
60
|
+
end
|
61
|
+
|
62
|
+
def first_estimate_date
|
63
|
+
estimates.sort_by(&:date).first.date if estimates.present?
|
64
|
+
end
|
65
|
+
|
66
|
+
def last_estimate_date
|
67
|
+
estimates.sort_by(&:date).last.date if estimates.present?
|
68
|
+
end
|
69
|
+
|
70
|
+
def total_effort
|
71
|
+
efforts.map(&:amount).inject(0, &:+)
|
72
|
+
end
|
73
|
+
|
74
|
+
def members
|
75
|
+
efforts.map(&:members).flatten.uniq
|
76
|
+
end
|
77
|
+
|
78
|
+
def last_estimate_error
|
79
|
+
estimate_errors.last
|
80
|
+
end
|
81
|
+
|
82
|
+
def estimate_errors
|
83
|
+
return [] if estimates.empty? || efforts.empty?
|
84
|
+
|
85
|
+
estimate_errors = []
|
86
|
+
estimates.each do |each|
|
87
|
+
estimate_errors << (100 * ((total_effort - each.amount) / each.amount * 1.0)).round(2)
|
88
|
+
end
|
89
|
+
|
90
|
+
estimate_errors
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_s
|
94
|
+
"[#{name}]. Total effort: #{total_effort}h. Estimates #{estimates.map(&:to_s)}. Efforts: #{efforts.map(&:to_s)}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def ==(other)
|
98
|
+
return true if other.equal?(self)
|
99
|
+
return false unless other.kind_of?(self.class)
|
100
|
+
trello_id == other.trello_id
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
class Tracking
|
2
|
+
extend Forwardable
|
3
|
+
include TrelloConfiguration
|
4
|
+
|
5
|
+
TIME_CONVERTERS = {
|
6
|
+
'h' => lambda { |estimate| estimate },
|
7
|
+
'd' => lambda { |estimate| estimate * 8 },
|
8
|
+
'g' => lambda { |estimate| estimate * 8 },
|
9
|
+
'p' => lambda { |estimate| estimate / 2 }
|
10
|
+
}
|
11
|
+
|
12
|
+
DURATION_REGEXP = '(\d+\.?\d*[phdg])'
|
13
|
+
DATE_REGEXP = /(\d{2})\.(\d{2})\.(\d{4})/
|
14
|
+
|
15
|
+
# delegate to the trello notification the member_creator method aliased as 'notifier'
|
16
|
+
def_delegator :@tracking_notification, :member_creator, :notifier
|
17
|
+
|
18
|
+
def initialize(tracking_notification)
|
19
|
+
@tracking_notification = tracking_notification
|
20
|
+
end
|
21
|
+
|
22
|
+
def date
|
23
|
+
Chronic.parse(date_as_string).to_date
|
24
|
+
end
|
25
|
+
|
26
|
+
def estimate?
|
27
|
+
!raw_estimate.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def estimate
|
31
|
+
estimate = convert_to_hours(raw_estimate)
|
32
|
+
Estimate.new(amount: estimate, date: date, tracking_notification_id: @tracking_notification.id) if estimate
|
33
|
+
end
|
34
|
+
|
35
|
+
def effort?
|
36
|
+
!raw_effort.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def effort
|
40
|
+
effort_amount = convert_to_hours(raw_effort)
|
41
|
+
if effort_amount
|
42
|
+
total_effort = effort_amount * effort_members.size
|
43
|
+
Effort.new(amount: total_effort, date: date, members: effort_members, tracking_notification_id: @tracking_notification.id)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def unknown_format?
|
48
|
+
!estimate? && !effort?
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
"[#{date}] From #{notifier.username.color(:green)}\t on card '#{trello_card.name.color(:yellow)}': #{raw_text}"
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def effort_members
|
58
|
+
@effort_members ||= users_involved_in_the_effort.map do |username|
|
59
|
+
Member.build_from(Trello::Member.find(username))
|
60
|
+
end
|
61
|
+
|
62
|
+
@effort_members
|
63
|
+
end
|
64
|
+
|
65
|
+
def users_involved_in_the_effort
|
66
|
+
users_involved_in_the_effort = raw_tracking.scan(/@(\w+)/).flatten
|
67
|
+
users_involved_in_the_effort << notifier.username unless should_count_only_listed_members?
|
68
|
+
|
69
|
+
users_involved_in_the_effort
|
70
|
+
end
|
71
|
+
|
72
|
+
def should_count_only_listed_members?
|
73
|
+
raw_tracking =~ /\((@\w+\W*\s*)+\)/
|
74
|
+
end
|
75
|
+
|
76
|
+
def raw_tracking
|
77
|
+
raw_text.gsub("@#{tracker_username}", "")
|
78
|
+
end
|
79
|
+
|
80
|
+
def raw_text
|
81
|
+
@tracking_notification.data['text']
|
82
|
+
end
|
83
|
+
|
84
|
+
def raw_estimate
|
85
|
+
extract_match_from_raw_tracking(/\[#{DURATION_REGEXP}\]/)
|
86
|
+
end
|
87
|
+
|
88
|
+
def raw_effort
|
89
|
+
extract_match_from_raw_tracking(/\+#{DURATION_REGEXP}/)
|
90
|
+
end
|
91
|
+
|
92
|
+
def convert_to_hours(duration_as_string)
|
93
|
+
return if duration_as_string.nil?
|
94
|
+
|
95
|
+
time_scale = duration_as_string.slice!(-1)
|
96
|
+
converter = TIME_CONVERTERS[time_scale]
|
97
|
+
converter.call(Float(duration_as_string))
|
98
|
+
end
|
99
|
+
|
100
|
+
def date_as_string
|
101
|
+
case raw_tracking
|
102
|
+
when DATE_REGEXP
|
103
|
+
day, month, year = raw_tracking.scan(DATE_REGEXP).flatten
|
104
|
+
"#{year}-#{month}-#{day}"
|
105
|
+
when /yesterday\s+\+#{DURATION_REGEXP}/, /\+#{DURATION_REGEXP}\s+yesterday/
|
106
|
+
(notification_date - 1).to_s
|
107
|
+
else
|
108
|
+
@tracking_notification.date
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def extract_match_from_raw_tracking(regexp)
|
113
|
+
extracted = nil
|
114
|
+
raw_tracking.scan(regexp) do |match|
|
115
|
+
extracted = match.first
|
116
|
+
end
|
117
|
+
|
118
|
+
extracted
|
119
|
+
end
|
120
|
+
|
121
|
+
def trello_card
|
122
|
+
@trello_card ||= @tracking_notification.card
|
123
|
+
end
|
124
|
+
|
125
|
+
def notification_date
|
126
|
+
Chronic.parse(@tracking_notification.date).to_date
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module TrelloAuthorize
|
2
|
+
include TrelloConfiguration
|
3
|
+
include Trello::Authorization
|
4
|
+
|
5
|
+
def authorize_on_trello(auth_params={})
|
6
|
+
%w{developer_public_key access_token_key developer_secret}.each do |key|
|
7
|
+
auth_params[key] ||= ENV[key] || authorization_params_from_config_file[key]
|
8
|
+
end
|
9
|
+
|
10
|
+
init_trello(auth_params)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def init_trello(auth_params)
|
16
|
+
ignoring_warnings do
|
17
|
+
Trello::Authorization.const_set(:AuthPolicy, OAuthPolicy)
|
18
|
+
end
|
19
|
+
|
20
|
+
OAuthPolicy.consumer_credential = OAuthCredential.new(auth_params["developer_public_key"], auth_params["developer_secret"])
|
21
|
+
OAuthPolicy.token = OAuthCredential.new(auth_params["access_token_key"], nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ignoring_warnings(&block)
|
25
|
+
begin
|
26
|
+
v, $VERBOSE = $VERBOSE, nil
|
27
|
+
block.call if block
|
28
|
+
ensure
|
29
|
+
$VERBOSE = v
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module TrelloConfiguration
|
2
|
+
|
3
|
+
def tracker_username
|
4
|
+
@tracker_username ||= ENV["tracker_username"] || configuration["tracker_username"]
|
5
|
+
end
|
6
|
+
|
7
|
+
def authorization_params_from_config_file
|
8
|
+
configuration["trello"]
|
9
|
+
end
|
10
|
+
|
11
|
+
class Database
|
12
|
+
def self.load_env(db_env)
|
13
|
+
ENV['MONGOID_ENV'] = db_env
|
14
|
+
Mongoid.load!("config/mongoid.yml", db_env)
|
15
|
+
Trello.logger.info "Mongo db env: #{db_env.color(:green)}."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def db_environment
|
22
|
+
ENV['MONGOID_ENV']
|
23
|
+
end
|
24
|
+
|
25
|
+
def configuration
|
26
|
+
@configuration ||= load_configuration
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_configuration
|
30
|
+
YAML.load_file("config/config.yml")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class TrelloTracker
|
2
|
+
include TrelloAuthorize
|
3
|
+
include Trello
|
4
|
+
|
5
|
+
trap("SIGINT") { exit! }
|
6
|
+
|
7
|
+
def initialize(custom_auth_params = {})
|
8
|
+
authorize_on_trello(custom_auth_params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def track(starting_date=Date.today)
|
12
|
+
notifications = tracker.notifications_from(starting_date)
|
13
|
+
|
14
|
+
oldest, latest = boundary_dates_in(notifications)
|
15
|
+
Trello.logger.info "Processing #{notifications.size} tracking notifications (from #{oldest} to #{latest}) starting from #{starting_date}..."
|
16
|
+
|
17
|
+
notifications.each do |notification|
|
18
|
+
tracking = Tracking.new(notification)
|
19
|
+
begin
|
20
|
+
card = TrackedCard.update_or_create_with(notification.card)
|
21
|
+
card.add(tracking)
|
22
|
+
Trello.logger.info tracking
|
23
|
+
|
24
|
+
rescue StandardError => e
|
25
|
+
Trello.logger.error "skipping tracking: #{e.message}".color(:magenta)
|
26
|
+
Trello.logger.error "#{e.backtrace}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
Trello.logger.info "Done tracking cards!".color(:green)
|
30
|
+
print_all_cards
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def tracker
|
36
|
+
@tracker ||= Member.find(tracker_username)
|
37
|
+
end
|
38
|
+
|
39
|
+
def boundary_dates_in(notifications)
|
40
|
+
dates = notifications.map { |each_notification| Chronic.parse(each_notification.date) }
|
41
|
+
[dates.min, dates.max]
|
42
|
+
end
|
43
|
+
|
44
|
+
def print_all_cards
|
45
|
+
TrackedCard.all.each { |tracked_card| Trello.logger.info(tracked_card.to_s.color(:yellow)) }
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'trello'
|
2
|
+
require 'rainbow'
|
3
|
+
require 'set'
|
4
|
+
require 'yaml'
|
5
|
+
require 'chronic'
|
6
|
+
require 'mongoid'
|
7
|
+
|
8
|
+
require 'trello_effort_tracker/mongoid_helper'
|
9
|
+
require 'trello_effort_tracker/trello_configuration'
|
10
|
+
require 'trello_effort_tracker/trello_authorize'
|
11
|
+
require 'trello_effort_tracker/tracked_card'
|
12
|
+
require 'trello_effort_tracker/member'
|
13
|
+
require 'trello_effort_tracker/estimate'
|
14
|
+
require 'trello_effort_tracker/effort'
|
15
|
+
require 'trello_effort_tracker/tracking'
|
16
|
+
require 'trello_effort_tracker/trello_tracker'
|
17
|
+
require 'trello_effort_tracker/google_docs_exporter'
|
18
|
+
|
19
|
+
require 'patches/trello/member'
|
20
|
+
|
21
|
+
TrelloConfiguration::Database.load_env(ENV['MONGOID_ENV'] || "development")
|
22
|
+
|
23
|
+
Trello.logger.level = Logger::DEBUG
|
@@ -0,0 +1 @@
|
|
1
|
+
cp -f config/mongoid.template.yml config/mongoid.yml
|
@@ -0,0 +1,8 @@
|
|
1
|
+
SHELL=/Users/pierodibello/.rvm/bin/rvm-shell
|
2
|
+
|
3
|
+
GEMSET="ruby-1.9.3-p194@spikes"
|
4
|
+
PROJECT_PATH="/Users/$USER/Documents/workspace/trello_effort_tracker"
|
5
|
+
LC_ALL=en_US.UTF-8
|
6
|
+
|
7
|
+
# m h dom mon dow command
|
8
|
+
*/10 * * * * rvm-shell $GEMSET -c "cd $PROJECT_PATH; bundle exec rake run:today[production]" >> /tmp/crontab.out 2>&1
|
data/script/mate.sh
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
mate $(ls -a | grep -v '\.\.$' | grep -v '\.$' | grep -v '\.git$'| grep -v '\.DS_Store'| grep -v '\.tmtags' | grep -v '\tags')
|
data/spec/effort_spec.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mongoid-rspec'
|
3
|
+
|
4
|
+
describe Effort do
|
5
|
+
|
6
|
+
it { should have_fields(:amount, :date) }
|
7
|
+
it { should be_embedded_in(:tracked_card) }
|
8
|
+
it { should embed_many(:members) }
|
9
|
+
|
10
|
+
describe "validation" do
|
11
|
+
it { should validate_presence_of(:amount) }
|
12
|
+
it { should validate_presence_of(:date) }
|
13
|
+
it { should validate_presence_of(:members) }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "equality" do
|
17
|
+
%w{piero tommaso tom ugo}.each do |username|
|
18
|
+
let(username.to_sym) { Member.new(username: username) }
|
19
|
+
end
|
20
|
+
|
21
|
+
it "is equal to another effort with same amount, date and members" do
|
22
|
+
effort = Effort.new(amount: 3, date: Date.parse("2012-11-09"), members: [piero, tommaso])
|
23
|
+
same_effort = Effort.new(amount: 3, date: Date.parse("2012-11-09"), members: [piero, tommaso])
|
24
|
+
yet_same_effort = Effort.new(amount: 3, date: Date.parse("2012-11-09"), members: [tommaso, piero])
|
25
|
+
|
26
|
+
effort.should == same_effort
|
27
|
+
effort.should == yet_same_effort
|
28
|
+
end
|
29
|
+
|
30
|
+
it "is not equal when amount differs" do
|
31
|
+
effort = Effort.new(amount: 3, date: Date.today, members: [tom, ugo])
|
32
|
+
another_effort = Effort.new(amount: 1, date: Date.today, members: [tom, ugo])
|
33
|
+
|
34
|
+
effort.should_not == another_effort
|
35
|
+
end
|
36
|
+
|
37
|
+
it "is not equal when date differs" do
|
38
|
+
effort = Effort.new(amount: 3, date: Date.parse("2012-11-09"), members: [tom, ugo])
|
39
|
+
another_effort = Effort.new(amount: 3, date: Date.parse("2011-10-08"), members: [tom, ugo])
|
40
|
+
|
41
|
+
effort.should_not == another_effort
|
42
|
+
end
|
43
|
+
|
44
|
+
it "is not equal when members differ" do
|
45
|
+
effort = Effort.new(amount: 3, date: Date.today, members: [tom, ugo])
|
46
|
+
another_effort = Effort.new(amount: 3, date: Date.today, members: [piero, ugo])
|
47
|
+
|
48
|
+
effort.should_not == another_effort
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mongoid-rspec'
|
3
|
+
|
4
|
+
describe Estimate do
|
5
|
+
|
6
|
+
it { should have_fields(:amount, :date) }
|
7
|
+
it { should be_embedded_in(:tracked_card) }
|
8
|
+
|
9
|
+
describe "validation" do
|
10
|
+
it { should validate_presence_of(:amount) }
|
11
|
+
it { should validate_presence_of(:date) }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "equality" do
|
15
|
+
|
16
|
+
it "is equal to another estimate with same amount and date" do
|
17
|
+
estimate = Estimate.new(amount: 3, date: Date.parse("2012-11-09"))
|
18
|
+
same_estimate = Estimate.new(amount: 3, date: Date.parse("2012-11-09"))
|
19
|
+
|
20
|
+
estimate.should == same_estimate
|
21
|
+
end
|
22
|
+
|
23
|
+
it "is not equal when amount differs" do
|
24
|
+
estimate = Estimate.new(amount: 3, date: Date.today)
|
25
|
+
another_estimate = Estimate.new(amount: 1, date: Date.today)
|
26
|
+
|
27
|
+
estimate.should_not == another_estimate
|
28
|
+
end
|
29
|
+
|
30
|
+
it "is not equal when date differs" do
|
31
|
+
estimate = Estimate.new(amount: 3, date: Date.parse("2012-11-09"))
|
32
|
+
another_estimate = Estimate.new(amount: 3, date: Date.parse("2011-10-08"))
|
33
|
+
|
34
|
+
estimate.should_not == another_estimate
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'trello'
|
3
|
+
|
4
|
+
describe "TrelloAuthorization" do
|
5
|
+
include TrelloAuthorize
|
6
|
+
|
7
|
+
it "authorizes connection to Trello", :needs_valid_configuration => true do
|
8
|
+
authorize_on_trello
|
9
|
+
|
10
|
+
Trello::Member.find("me").should_not be_nil
|
11
|
+
end
|
12
|
+
end
|
data/spec/member_spec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mongoid-rspec'
|
3
|
+
|
4
|
+
describe Member do
|
5
|
+
|
6
|
+
it { should have_fields(:trello_id, :username, :full_name, :avatar_id, :bio, :url) }
|
7
|
+
it { should be_embedded_in(:effort) }
|
8
|
+
|
9
|
+
describe "validation" do
|
10
|
+
it { should validate_presence_of(:username) }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "equality" do
|
14
|
+
it "is equal to another member with the same username" do
|
15
|
+
member = Member.new(username: "piero")
|
16
|
+
same_member = Member.new(username: "piero")
|
17
|
+
different_member = Member.new(username: "tommaso")
|
18
|
+
|
19
|
+
member.should == same_member
|
20
|
+
member.should_not == different_member
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".build_from" do
|
25
|
+
it "builds a Member from a Trello Member" do
|
26
|
+
member = Member.build_from(Trello::Member.new("username" => "piero"))
|
27
|
+
|
28
|
+
member.username.should == "piero"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "takes the Trello Member id and set it as trello_id" do
|
32
|
+
member = Member.build_from(Trello::Member.new("username" => "piero", "id" => "1234567abc"))
|
33
|
+
|
34
|
+
member.id.should_not == "1234567abc"
|
35
|
+
member.trello_id.should == "1234567abc"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#avatar_url" do
|
40
|
+
it "points to the avatar thumbnail image" do
|
41
|
+
member = Member.new(avatar_id: "123xyz")
|
42
|
+
member.avatar_url.should == "https://trello-avatars.s3.amazonaws.com/123xyz/30.png"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
# Set up gems listed in the Gemfile.
|
4
|
+
begin
|
5
|
+
ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', File.dirname(__FILE__))
|
6
|
+
require 'bundler'
|
7
|
+
Bundler.setup
|
8
|
+
rescue Bundler::GemNotFound => e
|
9
|
+
STDERR.puts e.message
|
10
|
+
STDERR.puts "Try running `bundle install`."
|
11
|
+
exit!
|
12
|
+
end
|
13
|
+
|
14
|
+
Bundler.require
|
15
|
+
|
16
|
+
RSpec.configure do |configuration|
|
17
|
+
configuration.include Mongoid::Matchers
|
18
|
+
end
|
19
|
+
|
20
|
+
# force test env for the mongodb configuration
|
21
|
+
TrelloConfiguration::Database.load_env("test")
|