trajectory 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +19 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +57 -0
  9. data/Rakefile +18 -0
  10. data/config/env.yml +3 -0
  11. data/lib/trajectory/client.rb +24 -0
  12. data/lib/trajectory/core_ext/hash.rb +8 -0
  13. data/lib/trajectory/core_ext.rb +1 -0
  14. data/lib/trajectory/data_access/api.rb +54 -0
  15. data/lib/trajectory/data_access/data_store.rb +70 -0
  16. data/lib/trajectory/data_access.rb +2 -0
  17. data/lib/trajectory/domain/idea.rb +70 -0
  18. data/lib/trajectory/domain/ideas.rb +23 -0
  19. data/lib/trajectory/domain/iteration.rb +85 -0
  20. data/lib/trajectory/domain/iterations.rb +46 -0
  21. data/lib/trajectory/domain/project.rb +168 -0
  22. data/lib/trajectory/domain/projects.rb +55 -0
  23. data/lib/trajectory/domain/stories.rb +56 -0
  24. data/lib/trajectory/domain/story.rb +119 -0
  25. data/lib/trajectory/domain/update.rb +17 -0
  26. data/lib/trajectory/domain/user.rb +28 -0
  27. data/lib/trajectory/domain/users.rb +30 -0
  28. data/lib/trajectory/domain.rb +11 -0
  29. data/lib/trajectory/exceptions/bad_environment_error.rb +7 -0
  30. data/lib/trajectory/exceptions/missing_attribute_error.rb +12 -0
  31. data/lib/trajectory/exceptions/velocity_equal_to_zero_error.rb +11 -0
  32. data/lib/trajectory/exceptions.rb +3 -0
  33. data/lib/trajectory/version.rb +3 -0
  34. data/lib/trajectory.rb +15 -0
  35. data/spec/fabricators/iteration_fabricator.rb +18 -0
  36. data/spec/fabricators/project_fabricator.rb +11 -0
  37. data/spec/fabricators/story_fabricator.rb +18 -0
  38. data/spec/fixtures/vcr_cassettes/client.yml +853 -0
  39. data/spec/fixtures/vcr_cassettes/projects_and_ideas.yml +98 -0
  40. data/spec/fixtures/vcr_cassettes/projects_and_ideas_association.yml +52 -0
  41. data/spec/fixtures/vcr_cassettes/projects_and_iterations.yml +49 -0
  42. data/spec/fixtures/vcr_cassettes/projects_and_iterations_association.yml +49 -0
  43. data/spec/fixtures/vcr_cassettes/projects_and_stories.yml +101 -0
  44. data/spec/fixtures/vcr_cassettes/projects_and_stories_association.yml +147 -0
  45. data/spec/integration/client_spec.rb +96 -0
  46. data/spec/spec_helper.rb +13 -0
  47. data/spec/support/env.rb +11 -0
  48. data/spec/support/fabrication.rb +1 -0
  49. data/spec/support/simple_cov.rb +11 -0
  50. data/spec/support/timecop.rb +1 -0
  51. data/spec/support/vcr.rb +8 -0
  52. data/spec/unit/client_spec.rb +27 -0
  53. data/spec/unit/core_ext/hash_spec.rb +11 -0
  54. data/spec/unit/data_access/api_spec.rb +100 -0
  55. data/spec/unit/data_access/data_store_spec.rb +86 -0
  56. data/spec/unit/domain/idea_spec.rb +46 -0
  57. data/spec/unit/domain/ideas_spec.rb +22 -0
  58. data/spec/unit/domain/iteration_spec.rb +51 -0
  59. data/spec/unit/domain/iterations_spec.rb +47 -0
  60. data/spec/unit/domain/project_spec.rb +163 -0
  61. data/spec/unit/domain/projects_spec.rb +84 -0
  62. data/spec/unit/domain/stories_spec.rb +84 -0
  63. data/spec/unit/domain/story_spec.rb +71 -0
  64. data/spec/unit/domain/user_spec.rb +29 -0
  65. data/spec/unit/domain/users_spec.rb +36 -0
  66. data/spec/unit/exceptions/bad_environment_error_spec.rb +9 -0
  67. data/spec/unit/exceptions/missing_attribute_error_spec.rb +10 -0
  68. data/spec/unit/exceptions/velocity_equal_to_zero_error_spec.rb +10 -0
  69. data/trajectory.gemspec +21 -0
  70. metadata +174 -0
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ module Trajectory
4
+ describe Client do
5
+ it 'raises an exceptions when environment variables are not set' do
6
+ begin
7
+ original_api_key = ENV['TRAJECTORY_API_KEY']
8
+ original_account_keyword = ENV['TRAJECTORY_ACCOUNT_KEYWORD']
9
+ ENV['TRAJECTORY_API_KEY'] = nil
10
+ ENV['TRAJECTORY_ACCOUNT_KEYWORD'] = nil
11
+
12
+ expect do
13
+ Client.new
14
+ end.to raise_error(BadEvnrionmentError)
15
+ ensure
16
+ ENV['TRAJECTORY_API_KEY'] = original_api_key
17
+ ENV['TRAJECTORY_ACCOUNT_KEYWORD'] = original_account_keyword
18
+ end
19
+ end
20
+
21
+ it 'delegate fetching of projects to the data store' do
22
+ DataStore.should_receive(:projects)
23
+
24
+ Client.new.projects
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hash do
4
+ it 'can symbolize hash keys' do
5
+ hash = {'foo' => 'bar', 'baz' => 'qux'}
6
+
7
+ hash.symbolize_keys!
8
+
9
+ hash.should == {:foo => 'bar', :baz => 'qux'}
10
+ end
11
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ module Trajectory
5
+ describe Api do
6
+ describe '#projects' do
7
+ it 'make an API call to retrieve all trajectory projects' do
8
+ query = '/projects.json'
9
+ body = [{'id' => '1234'}]
10
+ headers = {:headers => {'Content-Type' => 'application/json'}}
11
+
12
+ Api.should_receive(:get).
13
+ with(query, headers).
14
+ and_return(OpenStruct.new :headers => headers, :body => body.to_json)
15
+
16
+ Api.projects.should == body
17
+ end
18
+ end
19
+
20
+ let(:project) { double(:project, :keyword => 'project-keyword') }
21
+
22
+ describe '#users_for_project' do
23
+ it 'make an API call to retrieve all users of a given project' do
24
+
25
+ query = "/projects/#{project.keyword}/users.json"
26
+ body = [{'id' => '1234'}]
27
+ headers = {:headers => {'Content-Type' => 'application/json'}}
28
+
29
+ Api.should_receive(:get).
30
+ with(query, headers).
31
+ and_return(OpenStruct.new :headers => headers, :body => body.to_json)
32
+
33
+ Api.users_for_project(project).should == body
34
+ end
35
+ end
36
+
37
+ describe '#stories_for_project' do
38
+ it 'make an API call to retrieve all users of a given project' do
39
+ not_completed_stories_query = "/projects/#{project.keyword}/stories.json"
40
+ completed_stories_query = "/projects/#{project.keyword}/stories/completed.json"
41
+ not_completed_stories_body = {'stories' => [{'id' => '1234'}]}
42
+ completed_stories_body = {'stories' => [{'id' => '1234'}]}
43
+ headers = {:headers => {'Content-Type' => 'application/json'}}
44
+
45
+ Api.should_receive(:get).
46
+ with(not_completed_stories_query, headers).
47
+ and_return(OpenStruct.new :headers => headers, :body => not_completed_stories_body.to_json)
48
+ Api.should_receive(:get).
49
+ with(completed_stories_query, headers).
50
+ and_return(OpenStruct.new :headers => headers, :body => completed_stories_body.to_json)
51
+
52
+ Api.stories_for_project(project).should == completed_stories_body['stories'] + not_completed_stories_body['stories']
53
+ end
54
+ end
55
+
56
+ describe '#iterations_for_project' do
57
+ it 'make an API call to retrieve all users of a given project' do
58
+ query = "/projects/#{project.keyword}/iterations.json"
59
+ body = [{'id' => '1234'}]
60
+ headers = {:headers => {'Content-Type' => 'application/json'}}
61
+
62
+ Api.should_receive(:get).
63
+ with(query, headers).
64
+ and_return(OpenStruct.new :headers => headers, :body => body.to_json)
65
+
66
+ Api.iterations_for_project(project).should == body
67
+ end
68
+ end
69
+
70
+ describe '#ideas_for_project' do
71
+ it 'make an API call to retrieve all users of a given project' do
72
+ query = "/projects/#{project.keyword}/ideas.json"
73
+ body = [{'id' => '1234'}]
74
+ headers = {:headers => {'Content-Type' => 'application/json'}}
75
+
76
+ Api.should_receive(:get).
77
+ with(query, headers).
78
+ and_return(OpenStruct.new :headers => headers, :body => body.to_json)
79
+
80
+ Api.ideas_for_project(project).should == body
81
+ end
82
+ end
83
+
84
+ describe '#updates_for_project' do
85
+ it 'make an API call to retrieve all users of a given project' do
86
+ since = DateTime.now
87
+
88
+ query = "/projects/#{project.keyword}/updates.json?since=#{since}"
89
+ body = [{'id' => '1234'}]
90
+ headers = {:headers => {'Content-Type' => 'application/json'}}
91
+
92
+ Api.should_receive(:get).
93
+ with(query, headers).
94
+ and_return(OpenStruct.new :headers => headers, :body => body.to_json)
95
+
96
+ Api.updates_for_project(project, since).should == body
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ module Trajectory
4
+ describe DataStore do
5
+ before(:each) do
6
+ DataStore.instance_variable_set(:@projects, nil)
7
+ end
8
+
9
+ it 'maps JSON projects to actual collection of projects' do
10
+ json_projects_collection = double
11
+ Api.stub(:projects).and_return(json_projects_collection)
12
+ Projects.should_receive(:from_json).with(json_projects_collection)
13
+
14
+ DataStore.projects
15
+ end
16
+
17
+ let(:project) { double(:project, :id => 4567) }
18
+
19
+ it 'maps JSON users to actual collection of users' do
20
+ json_users_collection = double
21
+ Api.stub(:users_for_project).and_return(json_users_collection)
22
+ Users.should_receive(:from_json).with(json_users_collection)
23
+
24
+ DataStore.users_for_project(project)
25
+ end
26
+
27
+ it 'maps JSON stories to actual collection of stories' do
28
+ json_stories_collection = double
29
+ Api.stub(:stories_for_project).and_return(json_stories_collection)
30
+ Stories.should_receive(:from_json).with(project, json_stories_collection)
31
+
32
+ DataStore.stories_for_project(project)
33
+ end
34
+
35
+ it 'maps JSON iterations to actual collection of iterations' do
36
+ json_iterations_collection = double
37
+ Api.stub(:iterations_for_project).and_return(json_iterations_collection)
38
+ Iterations.should_receive(:from_json).with(project, json_iterations_collection)
39
+
40
+ DataStore.iterations_for_project(project)
41
+ end
42
+
43
+ it 'maps JSON ideas to actual collection of ideas' do
44
+ json_ideas_collection = double
45
+ Api.stub(:ideas_for_project).and_return(json_ideas_collection)
46
+ Ideas.should_receive(:from_json).with(project, json_ideas_collection)
47
+
48
+ DataStore.ideas_for_project(project)
49
+ end
50
+
51
+ it 'maps JSON updates to acutal array of updates with properly updated stories and iterations' do
52
+ updated_stories = double
53
+ updated_iterations = double
54
+
55
+ json_stories_collection = double
56
+ json_iterations_collection = double
57
+
58
+ Api.stub(:updates_for_project).and_return({'stories' => json_stories_collection, 'iterations' => json_iterations_collection})
59
+
60
+ Stories.should_receive(:from_json).with(project, json_stories_collection).and_return(updated_stories)
61
+ Iterations.should_receive(:from_json).with(project, json_iterations_collection).and_return(updated_iterations)
62
+
63
+ updates = DataStore.updates_for_project(project)
64
+
65
+ updates.stories.should == updated_stories
66
+ updates.iterations.should == updated_iterations
67
+ end
68
+
69
+ it 'delegates fetching of project by id to the project collection' do
70
+ projects_collection = double
71
+ DataStore.stub(:projects).and_return(projects_collection)
72
+
73
+ id = 12
74
+ projects_collection.should_receive(:find_by_id).with(id)
75
+
76
+ DataStore.find_project_by_id(id)
77
+ end
78
+
79
+ it 'delegates fetching of user of a project to the project' do
80
+ id = 12
81
+ project.should_receive(:find_user_by_id).with(id)
82
+
83
+ DataStore.find_user_of_project_with_id(project, id)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ module Trajectory
4
+ describe Idea do
5
+ let(:idea) { Idea.new(id: 42, subject: 'foo') }
6
+
7
+ it 'can be initialized with named parameters' do
8
+ idea.id.should == 42
9
+ idea.subject.should == 'foo'
10
+ end
11
+
12
+ it 'requires an id attribute' do
13
+ expect do
14
+ Idea.new.id
15
+ end.to raise_error(MissingAttributeError)
16
+ end
17
+
18
+ it 'is the same idea when ids are the same' do
19
+ idea.should == Idea.new(id: 42, subject: 'bar')
20
+ end
21
+
22
+ context 'it has attributes accessors' do
23
+ %w(id subject stories_count comments_count last_comment_user_name last_comment_created_at editable_by_current_user user_id subscribed_user_ids project_id).each do |attribute|
24
+ it "'#{attribute}' accessor" do
25
+ Idea.new.should respond_to(attribute.to_sym)
26
+ end
27
+ end
28
+ end
29
+
30
+ it 'delegates project fetching to data store' do
31
+ DataStore.should_receive(:find_project_by_id)
32
+
33
+ Idea.new.project
34
+ end
35
+
36
+ it 'delegates user fetching to data store' do
37
+ project = double
38
+ idea = Idea.new(user_id: 42)
39
+ idea.stub(:project).and_return(project)
40
+
41
+ DataStore.should_receive(:find_user_of_project_with_id).with(project, 42)
42
+
43
+ idea.user
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ module Trajectory
4
+ describe Ideas do
5
+ it 'can be initialized from json array of components attributes' do
6
+ project = double(:project, :id => 4567)
7
+ json_ideas_collection = [{'id' => 1234, 'subject' => 'idea-subject'}, {'id' => 42, 'subject' => 'other-idea-subject'}]
8
+
9
+ ideas = Ideas.from_json(project, json_ideas_collection)
10
+
11
+ ideas.should be_kind_of(Ideas)
12
+ ideas.first.id.should == 1234
13
+ ideas.first.subject.should == 'idea-subject'
14
+
15
+ ideas[1].id.should == 42
16
+ ideas[1].subject.should == 'other-idea-subject'
17
+
18
+ ideas.first.project_id.should == 4567
19
+ ideas[1].project_id.should == 4567
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ module Trajectory
4
+ describe Iteration do
5
+ let(:iteration) { Iteration.new(id: 42) }
6
+
7
+ it 'can be initialized with named parameters' do
8
+ iteration.id.should == 42
9
+ end
10
+
11
+ it 'requires an id attribute' do
12
+ expect do
13
+ Iteration.new.id
14
+ end.to raise_error(MissingAttributeError)
15
+ end
16
+
17
+ it 'is the same story when ids are the same' do
18
+ iteration.should == Iteration.new(id: 42)
19
+ end
20
+
21
+ it 'knows when the iteration is a past one' do
22
+ Iteration.new(:complete => true).past?.should == true
23
+ Iteration.new(:complete => false).past?.should == false
24
+ end
25
+
26
+ it 'knows when the iteration is a future one' do
27
+ Iteration.new(:complete => true, :current => true).future?.should == false
28
+ Iteration.new(:complete => true, :current => false).future?.should == false
29
+ Iteration.new(:complete => false, :current => true).future?.should == false
30
+ Iteration.new(:complete => false, :current => false).future?.should == true
31
+ end
32
+
33
+ context 'it has attributes accessors' do
34
+ %w(percent_complete started_stories_count created_at estimated_velocity delivered_stories_count updated_at unstarted_stories_count starts_on current stories_count id complete accepted_points estimated_points comments_count accepted_stories_count).each do |attribute|
35
+ it "'#{attribute}' accessor" do
36
+ Iteration.new.should respond_to(attribute.to_sym)
37
+ end
38
+ end
39
+ end
40
+
41
+ it 'delegates fetching of its stories to its project' do
42
+ project = double
43
+ iteration = Iteration.new(:id => 42)
44
+ iteration.project = project
45
+
46
+ project.should_receive(:stories_in_iteration).with(iteration)
47
+
48
+ iteration.stories
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ module Trajectory
4
+ describe Iterations do
5
+ it 'can be initializes from a json array of components attributes' do
6
+ project = double(:project, :id => 4567)
7
+ json_iterations_collection = [{'id' => 1234, 'estimated_points' => 12}, {'id' => 42, 'estimated_points' => 10}]
8
+
9
+ iterations = Iterations.from_json project, json_iterations_collection
10
+
11
+ iterations.should be_kind_of(Iterations)
12
+ iterations.first.id.should == 1234
13
+ iterations.first.estimated_points.should == 12
14
+
15
+ iterations[1].id.should == 42
16
+ iterations[1].estimated_points.should == 10
17
+
18
+ iterations.first.project_id.should == 4567
19
+ iterations[1].project_id.should == 4567
20
+ end
21
+
22
+ it 'can retrieve current iteration' do
23
+ current_iteration = double(:current? => true)
24
+ iterations = Iterations.new(double(:current? => false),
25
+ current_iteration,
26
+ double(:current? => false))
27
+
28
+ iterations.current.should == current_iteration
29
+ end
30
+
31
+ it 'can retrieve pasts iterations' do
32
+ past_iteration_1 = double(:past? => true)
33
+ past_iteration_2 = double(:past? => true)
34
+ iterations = Iterations.new(past_iteration_1, double(:past? => false), double(:past? => false), past_iteration_2)
35
+
36
+ iterations.past.should == Iterations.new(past_iteration_1, past_iteration_2)
37
+ end
38
+
39
+ it 'can retrieve future iterations' do
40
+ future_iteration_1 = double(:future? => true)
41
+ future_iteration_2 = double(:future? => true)
42
+ iterations = Iterations.new(future_iteration_1, double(:future? => false), double(:future? => false), future_iteration_2)
43
+
44
+ iterations.future.should == Iterations.new(future_iteration_1, future_iteration_2)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,163 @@
1
+ require 'spec_helper'
2
+
3
+ module Trajectory
4
+ describe Project do
5
+ let(:project) { Project.new(id: 42, name: 'foo') }
6
+
7
+ it 'can be initialized with named parameters' do
8
+ project.id.should == 42
9
+ project.name.should == 'foo'
10
+ end
11
+
12
+ it 'requires an id attribute' do
13
+ expect do
14
+ Project.new.id
15
+ end.to raise_error(MissingAttributeError)
16
+ end
17
+
18
+ it 'is the same project when ids are the same' do
19
+ project.should == Project.new(id: 42, name: 'bar')
20
+ end
21
+
22
+ context 'it has attributes accessors' do
23
+ %w(archived? created_at estimated_velocity historic_velocity id keyword updated_at completed_iterations_count completed_stories_count).each do |attribute|
24
+ it "'#{attribute}' accessor" do
25
+ Project.new.should respond_to(attribute.to_sym)
26
+ end
27
+ end
28
+ end
29
+
30
+ it 'can sum the points of all its stories' do
31
+ stories = Stories.new(double(:story, :points => 2),
32
+ double(:story, :points => 10),
33
+ double(:story, :points => 8))
34
+ DataStore.stub(:stories_for_project).and_return(stories)
35
+
36
+ Project.new.total_points.should == 20
37
+ end
38
+
39
+ it 'can evaluate remaining points' do
40
+ stories = Stories.new(double(:story, :points => 2, :completed? => false),
41
+ double(:story, :points => 10, :completed? => false),
42
+ double(:story, :points => 8, :completed? => true))
43
+ DataStore.stub(:stories_for_project).and_return(stories)
44
+
45
+ Project.new.remaining_points.should == 12
46
+ end
47
+
48
+ it 'can evaluate the number of days to the end' do
49
+ stories = Stories.new(double(:story, :points => 2, :completed? => false),
50
+ double(:story, :points => 10, :completed? => false),
51
+ double(:story, :points => 8, :completed? => false))
52
+ DataStore.stub(:stories_for_project).and_return(stories)
53
+
54
+ project = Project.new(:estimated_velocity => 20)
55
+
56
+ project.remaining_days.should == 7
57
+ end
58
+
59
+ it 'can evaluate project end date' do
60
+ stories = Stories.new(double(:story, :points => 20, :completed? => false),
61
+ double(:story, :points => 10, :completed? => false),
62
+ double(:story, :points => 10, :completed? => false))
63
+ DataStore.stub(:stories_for_project).and_return(stories)
64
+
65
+ project = Project.new(:estimated_velocity => 20)
66
+
67
+ project.estimated_end_date.should == Date.today + 14
68
+ end
69
+
70
+ it 'can evaluate velocity per day' do
71
+ Project.new(:estimated_velocity => 20).estimated_velocity_per_day.should == 20 / 7.0
72
+ Project.new(:estimated_velocity => 21).estimated_velocity_per_day.should == 21 / 7.0
73
+ Project.new(:estimated_velocity => 7).estimated_velocity_per_day.should == 7 / 7.0
74
+ end
75
+
76
+ it 'can count the number of remaining working days' do
77
+ stories = Stories.new(double(:story, :points => 2, :completed? => false),
78
+ double(:story, :points => 10, :completed? => false),
79
+ double(:story, :points => 8, :completed? => false))
80
+ DataStore.stub(:stories_for_project).and_return(stories)
81
+
82
+ project = Project.new(:estimated_velocity => 20)
83
+
84
+ project.remaining_working_days.should == 5
85
+ end
86
+
87
+ it 'can estimate velocity per working day' do
88
+ Project.new(:estimated_velocity => 20).estimated_velocity_per_working_day.should == 20 / 5.0
89
+ Project.new(:estimated_velocity => 21).estimated_velocity_per_working_day.should == 21 / 5.0
90
+ Project.new(:estimated_velocity => 7).estimated_velocity_per_working_day.should == 7 / 5.0
91
+ end
92
+
93
+ it 'can estimate the percentage of completion' do
94
+ stories = Stories.new(double(:story, :points => 1, :completed? => false),
95
+ double(:story, :points => 10, :completed? => false),
96
+ double(:story, :points => 9, :completed? => true))
97
+ DataStore.stub(:stories_for_project).and_return(stories)
98
+
99
+ Project.new.percent_complete.should == 45.0
100
+ end
101
+
102
+ it 'can return the sum of completed stories points' do
103
+ stories = Stories.new(double(:story, :points => 1, :completed? => false),
104
+ double(:story, :points => 10, :completed? => true),
105
+ double(:story, :points => 9, :completed? => true))
106
+ DataStore.stub(:stories_for_project).and_return(stories)
107
+
108
+ Project.new.accepted_points.should == 19
109
+ end
110
+
111
+ it 'delegates fetching of iterations of a project to the data store' do
112
+ DataStore.should_receive(:iterations_for_project).with(project)
113
+
114
+ project.iterations
115
+ end
116
+
117
+ it 'delegates fetching of updates of a project to the data store' do
118
+ since = double
119
+ DataStore.should_receive(:updates_for_project).with(project, since)
120
+
121
+ project.updates(since)
122
+ end
123
+
124
+ it 'delegates fetching of ideas of a project to the data store' do
125
+ DataStore.should_receive(:ideas_for_project).with(project)
126
+
127
+ project.ideas
128
+ end
129
+
130
+ it 'can get stories of a given iteration' do
131
+ stories = double
132
+ iteration = double
133
+
134
+ project.stories = stories
135
+ stories.should_receive(:in_iteration).with(iteration)
136
+
137
+ project.stories_in_iteration(iteration)
138
+ end
139
+
140
+ it 'can get one of its user by id' do
141
+ users = double
142
+
143
+ project.users_collection = users
144
+ users.should_receive(:find_by_id).with(1234)
145
+
146
+ project.find_user_by_id(1234)
147
+ end
148
+
149
+ context 'when estimated velocity is zero' do
150
+ it 'cannot estimate remaining days until the project end' do
151
+ expect do
152
+ Project.new(:estimated_velocity => 0).remaining_days
153
+ end.to raise_error(VelocityEqualToZeroError)
154
+ end
155
+
156
+ it 'cannot estimate remaining working days until the project end' do
157
+ expect do
158
+ Project.new(:estimated_velocity => 0).remaining_working_days
159
+ end.to raise_error(VelocityEqualToZeroError)
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ module Trajectory
4
+ describe Projects do
5
+ it 'can be initialized from an array of json attributes of its components' do
6
+ json_projects_collection = [{'id' => 1234, 'name' => 'foo'}, {'id' => 42, 'name' => 'bar'}]
7
+
8
+ projects = Projects.from_json(json_projects_collection)
9
+
10
+ projects.should be_kind_of(Projects)
11
+ projects.first.id.should == 1234
12
+ projects.first.name.should == 'foo'
13
+
14
+ projects[1].id.should == 42
15
+ projects[1].name.should == 'bar'
16
+ end
17
+
18
+ it 'can find a project by id' do
19
+ project = double(:project, id: 1234)
20
+ projects = Projects.new(double(:project, id: 1),
21
+ double(:project, id: 2),
22
+ project,
23
+ double(:project, id: 3))
24
+
25
+ projects.find_by_id(1234).should == project
26
+ end
27
+
28
+ it "returns false when it can't find a project by id" do
29
+ projects = Projects.new(double(:project, id: 1),
30
+ double(:project, id: 2),
31
+ double(:project, id: 3))
32
+
33
+ projects.find_by_id(1234).should == false
34
+ end
35
+
36
+ it 'can find a project by keyword' do
37
+ project = double(:project, keyword: 'a_keyword')
38
+ projects = Projects.new(double(:project, keyword: 'some_identifier'),
39
+ double(:project, keyword: 'another_identifier'),
40
+ project,
41
+ double(:project, keyword: 'the_last_identifier'))
42
+
43
+ projects.find_by_keyword('a_keyword').should == project
44
+ end
45
+
46
+ it "returns false when it can't find a project by keyword" do
47
+ projects = Projects.new(double(:project, keyword: 'some_identifier'),
48
+ double(:project, keyword: 'another_identifier'),
49
+ double(:project, keyword: 'the_last_identifier'))
50
+
51
+ projects.find_by_keyword('a_keyword').should == false
52
+ end
53
+
54
+ it 'can filter active projects' do
55
+ project_1 = double(:archived? => true)
56
+ project_2 = double(:archived? => true)
57
+ project_3 = double(:archived? => true)
58
+
59
+ projects = Projects.new(double(:archived? => false),
60
+ project_1,
61
+ double(:archived? => false),
62
+ project_2,
63
+ project_3,
64
+ double(:archived? => false))
65
+
66
+ projects.archived.should include(project_1, project_2, project_3)
67
+ end
68
+
69
+ it 'can filter archived projects' do
70
+ project_1 = double(:archived? => false)
71
+ project_2 = double(:archived? => false)
72
+ project_3 = double(:archived? => false)
73
+
74
+ projects = Projects.new(double(:archived? => true),
75
+ project_1,
76
+ double(:archived? => true),
77
+ project_2,
78
+ project_3,
79
+ double(:archived? => true))
80
+
81
+ projects.active.should include(project_1, project_2, project_3)
82
+ end
83
+ end
84
+ end