artisan-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/lib/artisan/activity/activity_interactor.rb +18 -0
  2. data/lib/artisan/activity/activity_presenter.rb +35 -0
  3. data/lib/artisan/activity/formatters/diff_changes.rb +41 -0
  4. data/lib/artisan/activity/formatters/name.rb +22 -0
  5. data/lib/artisan/activity/formatters/source.rb +16 -0
  6. data/lib/artisan/activity/formatters/temporal.rb +20 -0
  7. data/lib/artisan/activity/iteration_auditor.rb +44 -0
  8. data/lib/artisan/activity/project_auditor.rb +28 -0
  9. data/lib/artisan/activity/story_auditor.rb +42 -0
  10. data/lib/artisan/callbacks.rb +27 -0
  11. data/lib/artisan/crud_strategy.rb +23 -0
  12. data/lib/artisan/event_mailer.rb +22 -0
  13. data/lib/artisan/inviter.rb +36 -0
  14. data/lib/artisan/iterations/iteration_point_calculator.rb +28 -0
  15. data/lib/artisan/iterations/iteration_presenter.rb +20 -0
  16. data/lib/artisan/iterations/iteration_workflow_interactor.rb +60 -0
  17. data/lib/artisan/iterations/iteration_workflow_presenter.rb +11 -0
  18. data/lib/artisan/iterations/iterations.rb +47 -0
  19. data/lib/artisan/iterations/iterations_interactor.rb +48 -0
  20. data/lib/artisan/iterations/move_to_backlog.rb +28 -0
  21. data/lib/artisan/iterations/story_tags.rb +15 -0
  22. data/lib/artisan/member.rb +52 -0
  23. data/lib/artisan/no_op_callbacks.rb +6 -0
  24. data/lib/artisan/projects/api_key_generator.rb +26 -0
  25. data/lib/artisan/projects/archive_interactor.rb +29 -0
  26. data/lib/artisan/projects/completed_stories_presenter.rb +20 -0
  27. data/lib/artisan/projects/iteration_numberer.rb +15 -0
  28. data/lib/artisan/projects/project_creator.rb +51 -0
  29. data/lib/artisan/projects/projects_interactor.rb +58 -0
  30. data/lib/artisan/projects/projects_presenter.rb +46 -0
  31. data/lib/artisan/projects/story_point_summer.rb +16 -0
  32. data/lib/artisan/reports/average_stat_per_iteration_data.rb +55 -0
  33. data/lib/artisan/reports/burn_up_chart.rb +46 -0
  34. data/lib/artisan/reports/high_charts_data_retriever.rb +77 -0
  35. data/lib/artisan/reports/high_charts_interactor.rb +31 -0
  36. data/lib/artisan/reports/percentage_of_commitments_met_data.rb +47 -0
  37. data/lib/artisan/reports/signoff/pdf.rb +161 -0
  38. data/lib/artisan/reports/signoff/report.rb +120 -0
  39. data/lib/artisan/reports/velocity_report.rb +43 -0
  40. data/lib/artisan/repository.rb +51 -0
  41. data/lib/artisan/stories/pert_calculator.rb +62 -0
  42. data/lib/artisan/stories/stories_interactor.rb +78 -0
  43. data/lib/artisan/stories/story_collection.rb +66 -0
  44. data/lib/artisan/stories/story_exporter.rb +45 -0
  45. data/lib/artisan/stories/story_numberer.rb +15 -0
  46. data/lib/artisan/stories/story_presenter.rb +23 -0
  47. data/lib/artisan/stories/story_sorter.rb +37 -0
  48. data/lib/artisan/story_board.rb +45 -0
  49. data/lib/artisan/story_column_changer.rb +71 -0
  50. data/lib/artisan/story_exporter.rb +14 -0
  51. data/lib/artisan/team.rb +50 -0
  52. data/spec/crud_strategy_spec.rb +100 -0
  53. data/spec/event_mailer_spec.rb +86 -0
  54. data/spec/inviter_spec.rb +58 -0
  55. data/spec/member_spec.rb +58 -0
  56. data/spec/repository_spec.rb +32 -0
  57. data/spec/story_board_spec.rb +36 -0
  58. data/spec/story_collection_spec.rb +61 -0
  59. data/spec/story_column_changer_spec.rb +222 -0
  60. data/spec/story_exporter_spec.rb +25 -0
  61. data/spec/story_sorter_spec.rb +67 -0
  62. data/spec/team_spec.rb +107 -0
  63. metadata +128 -0
@@ -0,0 +1,58 @@
1
+ require "artisan/repository"
2
+ require "artisan/inviter"
3
+ require "spec_helper"
4
+
5
+ class FutureUsers; end
6
+ class Invites; end
7
+
8
+ describe Inviter do
9
+ let(:email) { 'test@user.com' }
10
+ let(:name) { 'Mister Person' }
11
+
12
+ before do
13
+ Invites.stub!(:invite).and_return(mock('invites', :deliver => nil))
14
+ end
15
+
16
+ context "alerts" do
17
+ it "alerts if name and email are nil" do
18
+ inviter = Inviter.new(nil, nil)
19
+ lambda{inviter.invite}.should raise_exception("Name and email fields are required, please fill out all fields and try again.")
20
+ end
21
+
22
+ it "alerts if name is blank" do
23
+ inviter = Inviter.new("", email)
24
+ lambda{inviter.invite}.should raise_exception("Name and email fields are required, please fill out all fields and try again.")
25
+ end
26
+
27
+ it "alerts if email is blank" do
28
+ inviter = Inviter.new(name, "")
29
+ lambda{inviter.invite}.should raise_exception("Name and email fields are required, please fill out all fields and try again.")
30
+ end
31
+
32
+ it "alerts if email is invalid" do
33
+ inviter = Inviter.new(name, "totallyNotAnEmail")
34
+ lambda{inviter.invite}.should raise_exception("Name and email fields are required, please fill out all fields and try again.")
35
+ end
36
+ end
37
+
38
+ context "sends invitations" do
39
+ it "delivers mail with valid addressed" do
40
+ mail = mock('invite mailer')
41
+ Invites.should_receive(:invite).with(name, email).and_return(mail)
42
+ mail.should_receive(:deliver)
43
+ inviter = Inviter.new(name, email)
44
+ inviter.invite
45
+ end
46
+ end
47
+
48
+ context "future users" do
49
+ it "Adds the email to the future users table" do
50
+ project = Artisan::Repository.project.create(:name => "Test")
51
+
52
+ inviter = Inviter.new(name, email)
53
+ inviter.add_to_future_users(project)
54
+
55
+ Artisan::Repository.future_user.find_by_email(email).project_id.should == project.id
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,58 @@
1
+ require "artisan/member"
2
+ require "artisan-memory-repository/models/user"
3
+ require "artisan-memory-repository/models/project"
4
+ require "artisan-memory-repository/models/member"
5
+
6
+ describe Artisan::Member do
7
+
8
+ before(:each) do
9
+ @user = Artisan::Repository.user.new(:email => "test@gmail.com")
10
+ @project = Artisan::Repository.project.new
11
+ @member = Artisan::Repository.member.new
12
+ @member.stub!(:project).and_return(@project)
13
+ @member.stub!(:user).and_return(@user)
14
+ @members = Artisan::Member.new(@member)
15
+ end
16
+
17
+ it "adds the member to the project's completed story email list" do
18
+ @members.member.project.project_configuration = stub(:story_completed_email_list => []).as_null_object
19
+
20
+ @members.add_to_completed_list
21
+
22
+ @project.project_configuration.story_completed_email_list.should == [@user.email]
23
+ end
24
+
25
+ it "adds the member to the project's assigned story email list" do
26
+ @members.member.project.project_configuration = stub(:story_assigned_email_list => []).as_null_object
27
+
28
+ @members.add_to_assigned_list
29
+
30
+ @project.project_configuration.story_assigned_email_list.should == [@user.email]
31
+ end
32
+
33
+ it "removes the member from the project's completed story email list" do
34
+ @members.member.project.project_configuration = stub(:story_completed_email_list => [@user.email]).as_null_object
35
+
36
+ @members.remove_from_completed_list
37
+
38
+ @project.project_configuration.story_completed_email_list.should == []
39
+ end
40
+
41
+ it "removes the member from the project's assigned story email list" do
42
+ @members.member.project.project_configuration = stub(:story_assigned_email_list => [@user.email]).as_null_object
43
+
44
+ @members.remove_from_assigned_list
45
+
46
+ @project.project_configuration.story_assigned_email_list.should == []
47
+ end
48
+
49
+ it "removes the member from the project's email lists" do
50
+ @members.member.project.project_configuration = stub(:story_completed_email_list => [@user.email], :story_assigned_email_list => [@user.email]).as_null_object
51
+
52
+ @members.remove_from_email_lists
53
+
54
+ @project.project_configuration.story_completed_email_list.should == []
55
+ @project.project_configuration.story_assigned_email_list.should == []
56
+ end
57
+
58
+ end
@@ -0,0 +1,32 @@
1
+ require 'artisan/repository'
2
+
3
+ test_repo = Class.new(Artisan::Repository) {}
4
+
5
+ describe Artisan::Repository do
6
+ let(:fake_repo) do
7
+ Class.new do
8
+ def project
9
+ "fake-projects"
10
+ end
11
+
12
+ def story
13
+ "fake-stories"
14
+ end
15
+ end
16
+ end
17
+
18
+ let(:repo_instance) { fake_repo.new }
19
+ before { test_repo.register_repo(repo_instance) }
20
+
21
+ it "registers a top level repository" do
22
+ test_repo.repo.should == repo_instance
23
+ end
24
+
25
+ it "delegates to the given repository for stories" do
26
+ test_repo.story.should == "fake-stories"
27
+ end
28
+
29
+ it "delegates to the given repository for stories" do
30
+ test_repo.project.should == "fake-projects"
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ require "artisan/story_board"
2
+ require "artisan/stories/stories_interactor"
3
+
4
+ describe Artisan::StoryBoard do
5
+ let(:repository) { stub }
6
+ let(:iteration) { Artisan::Repository.iteration.create }
7
+
8
+ before do
9
+ Artisan::Repository.stub(:story).and_return(repository)
10
+ end
11
+
12
+ it "calculates total points for stories in backlog" do
13
+ iteration.stub!(:project).and_return(nil)
14
+ Artisan::Stories::StoriesInteractor.stub_chain(:new, :backlog, :points).and_return(5)
15
+ board = Artisan::StoryBoard.new(iteration)
16
+ board.backlog_total.should == 5
17
+ end
18
+
19
+ it "calculates total points for 'ready' stories" do
20
+ repository.should_receive(:ready).with(iteration).and_return([double(:estimate => 2), double(:estimate => 3)])
21
+ board = Artisan::StoryBoard.new(iteration)
22
+ board.ready_total.should == 5
23
+ end
24
+
25
+ it "calculates total points for 'working' stories" do
26
+ repository.should_receive(:working).with(iteration).and_return([double(:estimate => 2), double(:estimate => 3)])
27
+ board = Artisan::StoryBoard.new(iteration)
28
+ board.working_total.should == 5
29
+ end
30
+
31
+ it "calculates total points for 'complete' stories" do
32
+ repository.should_receive(:completed).with(iteration).and_return([double(:estimate => 2), double(:estimate => 3)])
33
+ board = Artisan::StoryBoard.new(iteration)
34
+ board.complete_total.should == 5
35
+ end
36
+ end
@@ -0,0 +1,61 @@
1
+ require 'artisan/stories/story_collection'
2
+
3
+ describe Artisan::Stories::StoryCollection do
4
+ context "on init" do
5
+ it "has empty list of stories" do
6
+ Artisan::Stories::StoryCollection.new.stories.should == []
7
+ end
8
+
9
+ it "#new saves list of stories" do
10
+ story1, story2 = stub, stub
11
+ Artisan::Stories::StoryCollection.new([story1, story2]).all.should == [story1, story2]
12
+ end
13
+ end
14
+
15
+ context "#estimated_points" do
16
+ it "sums the estimates of all stories" do
17
+ stories = [ stub(:estimate => 1.5), stub(:estimate => 2) ]
18
+ Artisan::Stories::StoryCollection.new(stories).estimated_points.should == 3.5
19
+ end
20
+ end
21
+
22
+ context "#completed_points" do
23
+ it "sums the estimates of all completed stories" do
24
+ complete = stub(:estimate => 1.5, :complete? => true)
25
+ incomplete = stub(:estimate => 3, :complete? => false)
26
+ Artisan::Stories::StoryCollection.new([complete, incomplete]).completed_points.should == 1.5
27
+ end
28
+ end
29
+
30
+ context "#billed points" do
31
+ it "sums the estimates of all billable, completed stories" do
32
+ billable = stub(:estimate => 1.5, :complete? => true, :nonbillable? => false)
33
+ nonbillable = stub(:estimate => 1.5, :complete? => true, :nonbillable? => true)
34
+ incomplete = stub(:estimate => 3, :complete? => false, :nonbillable? => false)
35
+ Artisan::Stories::StoryCollection.new([billable, nonbillable, incomplete]).billed_points.should == 1.5
36
+ end
37
+ end
38
+
39
+ context "#billable points" do
40
+ it "sums the estimates of all billable stories" do
41
+ billable = stub(:estimate => 1.5, :complete? => true, :nonbillable? => false)
42
+ nonbillable = stub(:estimate => 1.5, :complete? => true, :nonbillable? => true)
43
+ incomplete = stub(:estimate => 3, :complete? => false, :nonbillable? => false)
44
+ Artisan::Stories::StoryCollection.new([billable, nonbillable, incomplete]).billable_points.should == 4.5
45
+ end
46
+ end
47
+
48
+ context "#all_complete?" do
49
+ it "is true if all stories are complete" do
50
+ complete1 = stub(:complete? => true)
51
+ complete2 = stub(:complete? => true)
52
+ Artisan::Stories::StoryCollection.new([complete1, complete2]).all_complete?.should be_true
53
+ end
54
+
55
+ it "is false if not all stories are complete" do
56
+ complete = stub(:complete? => true)
57
+ incomplete = stub(:complete? => false)
58
+ Artisan::Stories::StoryCollection.new([complete, incomplete]).all_complete?.should be_false
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,222 @@
1
+ require "artisan/story_column_changer"
2
+ require "spec_helper"
3
+ require "artisan-memory-repository/models/project"
4
+ require "artisan-memory-repository/models/iteration"
5
+ require "artisan-memory-repository/models/user"
6
+ require "artisan-memory-repository/models/story"
7
+
8
+ class Artisan::Activity::StoryAuditor;end;
9
+
10
+ describe Artisan::StoryColumnChanger do
11
+ let(:story_repository) { Artisan::Repository.story }
12
+
13
+ context "status changes" do
14
+ before(:each) do
15
+ @user = Artisan::Repository.user.new(:login => "mike", :password => "password", :email => "mike@example.com", :full_name => "mike jansen")
16
+ @user2 = Artisan::Repository.user.new(:login => "brimans", :password => "password", :email => "briman@thegmails.com", :full_name => "briman pratt")
17
+
18
+ @project = Artisan::Repository.project.new(:name => "Artisan")
19
+ @iteration = Artisan::Repository.iteration.new(:project_id => @project.id, :committed_points => 10, :start_date => "11/05/2010", :finish_date => "11/06/2010")
20
+ end
21
+
22
+ describe "move to backlog" do
23
+
24
+ before(:each) do
25
+ Artisan::Activity::StoryAuditor.stub!(:backlogged)
26
+ @story = Artisan::Repository.story.new(:name => 'abc', :assigned_user => @user, :complete => true, :completed_at => Date.today, :iteration => @iteration,
27
+ :accepted_at => Date.today, :accepted_by_user => @user2, :project => @project)
28
+ @project.stories = [@story]
29
+
30
+ @changer = Artisan::StoryColumnChanger.new(@story, @user)
31
+ end
32
+
33
+ it "moves a story to the backlog" do
34
+ @changer.move_to_backlog
35
+ @story.iteration.should be_nil
36
+ end
37
+
38
+ it "marks story as incomplete" do
39
+ @changer.move_to_backlog
40
+ @story.should_not be_complete
41
+ end
42
+
43
+ it "clears completed at" do
44
+ @changer.move_to_backlog
45
+ @story.completed_at.should be_nil
46
+ end
47
+
48
+ it "drops assigned user" do
49
+ @changer.move_to_backlog
50
+ @story.assigned_user_id.should be_nil
51
+ end
52
+
53
+ it "saves" do
54
+ @changer.move_to_backlog
55
+ @story.id.should_not be_nil
56
+ end
57
+
58
+ it "creates a story auditor" do
59
+ Artisan::Activity::StoryAuditor.should_receive(:backlogged).with(@story.id, @project.id, @user.id)
60
+ @changer.move_to_backlog
61
+ end
62
+
63
+ end
64
+
65
+ describe "move to ready" do
66
+ before :each do
67
+ @story = Artisan::Repository.story.new(:name => 'abc', :assigned_user => @user, :complete => true, :completed_at => Time.now,
68
+ :accepted_at => Time.now, :accepted_by_user => @user2, :project => @project)
69
+
70
+ @changer = Artisan::StoryColumnChanger.new(@story, @user)
71
+ Artisan::Activity::StoryAuditor.stub!(:readied)
72
+ end
73
+
74
+ it "assigns iteration to story" do
75
+ @changer.move_to_ready(@iteration)
76
+ story_repository.ready(@iteration).should == [@story]
77
+ end
78
+
79
+ it "disassociates assigned user from story when state is changed from working" do
80
+ @changer.move_to_ready(@iteration)
81
+ @story.assigned_user_id.should be_nil
82
+ end
83
+
84
+ it "disassociates assigned user from story and marks story incomplete when state is changed from completed" do
85
+ @changer.move_to_ready(@iteration)
86
+ @story.should_not be_complete
87
+ end
88
+
89
+ it "disassociates assigned user from story and removes completed at time" do
90
+ @changer.move_to_ready(@iteration)
91
+ @story.completed_at.should be_nil
92
+ end
93
+
94
+ it "saves" do
95
+ @changer.move_to_ready(@iteration)
96
+ @story.id.should_not be_nil
97
+ end
98
+
99
+ it "creates a story auditor" do
100
+ Artisan::Activity::StoryAuditor.should_receive(:readied).with(@story.id, @project.id, @user.id)
101
+ @changer.move_to_ready(@iteration)
102
+ end
103
+ end
104
+
105
+ describe "move to working" do
106
+ before :each do
107
+ @story = Artisan::Repository.story.new(:name => 'abc', :assigned_user => @user, :complete => true, :completed_at => Time.now, :accepted_at => Time.now,
108
+ :accepted_by_user => @user, :project => @project)
109
+ Artisan::Activity::StoryAuditor.stub!(:worked)
110
+ end
111
+
112
+ it "assigns story to an iteration" do
113
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
114
+ changer.move_to_working(@user, @iteration)
115
+
116
+ story_repository.working(@iteration).should == [@story]
117
+ end
118
+
119
+ it "assigns current user to story when state is changed from ready" do
120
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
121
+ changer.move_to_working(@user, @iteration)
122
+
123
+ @story.assigned_user.should == @user
124
+ end
125
+
126
+ it "marks story incomplete when state is changed from complete" do
127
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
128
+ changer.move_to_working(@user, @iteration)
129
+
130
+ @story.should_not be_complete
131
+ end
132
+
133
+ it "marks story completed at time as nil when state is changed from complete" do
134
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
135
+ changer.move_to_working(@user, @iteration)
136
+
137
+ @story.completed_at.should be_nil
138
+ end
139
+
140
+ it "marks only if there is no current user" do
141
+ @story.update_attributes(:assigned_user => @user2)
142
+
143
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
144
+ changer.move_to_working(@user, @iteration)
145
+
146
+ @story.assigned_user.should == @user2
147
+ end
148
+
149
+ it "saves" do
150
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
151
+ changer.move_to_working(@user, @iteration)
152
+
153
+ @story.id.should_not be_nil
154
+ end
155
+
156
+ it "creates a story auditor" do
157
+ Artisan::Activity::StoryAuditor.should_receive(:worked).with(@story.id, @project.id, @user.id)
158
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
159
+ changer.move_to_working(@user, @iteration)
160
+ end
161
+ end
162
+
163
+ describe "move to completed" do
164
+ before :each do
165
+ Artisan::Activity::StoryAuditor.stub!(:completed)
166
+ Artisan::Activity::StoryAuditor.stub!(:worked)
167
+ Artisan::Activity::StoryAuditor.stub!(:accepted)
168
+ @story = Artisan::Repository.story.new(:name => 'abc', :project => @project)
169
+ end
170
+
171
+ it "saves" do
172
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
173
+ changer.move_to_completed(@user, @iteration)
174
+
175
+ @story.id.should_not be_nil
176
+ end
177
+
178
+ it "assigns story to iteration" do
179
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
180
+ changer.move_to_completed(@user, @iteration)
181
+
182
+ story_repository.completed(@iteration).should == [@story]
183
+ end
184
+
185
+ it "marks story complete" do
186
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
187
+ changer.move_to_completed(@user, @iteration)
188
+
189
+ @story.should be_complete
190
+ end
191
+
192
+ it "marks story completed at time" do
193
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
194
+ changer.move_to_completed(@user, @iteration)
195
+
196
+ @story.completed_at.should_not be_nil
197
+ end
198
+
199
+ it "sets assigned user if not set" do
200
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
201
+ changer.move_to_completed(@user, @iteration)
202
+
203
+ @story.assigned_user.should == @user
204
+ end
205
+
206
+ it "marks only if there is no current user" do
207
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
208
+ changer.move_to_working(@user2, @iteration)
209
+
210
+ changer.move_to_completed(@user, @iteration)
211
+
212
+ @story.assigned_user.should == @user2
213
+ end
214
+
215
+ it "creates a story auditor" do
216
+ Artisan::Activity::StoryAuditor.should_receive(:completed).with(@story.id, @project.id, @user.id)
217
+ changer = Artisan::StoryColumnChanger.new(@story, @user)
218
+ changer.move_to_completed(@user, @iteration)
219
+ end
220
+ end
221
+ end
222
+ end