artisan-core 0.0.1

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.
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