trajectory 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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +57 -0
- data/Rakefile +18 -0
- data/config/env.yml +3 -0
- data/lib/trajectory/client.rb +24 -0
- data/lib/trajectory/core_ext/hash.rb +8 -0
- data/lib/trajectory/core_ext.rb +1 -0
- data/lib/trajectory/data_access/api.rb +54 -0
- data/lib/trajectory/data_access/data_store.rb +70 -0
- data/lib/trajectory/data_access.rb +2 -0
- data/lib/trajectory/domain/idea.rb +70 -0
- data/lib/trajectory/domain/ideas.rb +23 -0
- data/lib/trajectory/domain/iteration.rb +85 -0
- data/lib/trajectory/domain/iterations.rb +46 -0
- data/lib/trajectory/domain/project.rb +168 -0
- data/lib/trajectory/domain/projects.rb +55 -0
- data/lib/trajectory/domain/stories.rb +56 -0
- data/lib/trajectory/domain/story.rb +119 -0
- data/lib/trajectory/domain/update.rb +17 -0
- data/lib/trajectory/domain/user.rb +28 -0
- data/lib/trajectory/domain/users.rb +30 -0
- data/lib/trajectory/domain.rb +11 -0
- data/lib/trajectory/exceptions/bad_environment_error.rb +7 -0
- data/lib/trajectory/exceptions/missing_attribute_error.rb +12 -0
- data/lib/trajectory/exceptions/velocity_equal_to_zero_error.rb +11 -0
- data/lib/trajectory/exceptions.rb +3 -0
- data/lib/trajectory/version.rb +3 -0
- data/lib/trajectory.rb +15 -0
- data/spec/fabricators/iteration_fabricator.rb +18 -0
- data/spec/fabricators/project_fabricator.rb +11 -0
- data/spec/fabricators/story_fabricator.rb +18 -0
- data/spec/fixtures/vcr_cassettes/client.yml +853 -0
- data/spec/fixtures/vcr_cassettes/projects_and_ideas.yml +98 -0
- data/spec/fixtures/vcr_cassettes/projects_and_ideas_association.yml +52 -0
- data/spec/fixtures/vcr_cassettes/projects_and_iterations.yml +49 -0
- data/spec/fixtures/vcr_cassettes/projects_and_iterations_association.yml +49 -0
- data/spec/fixtures/vcr_cassettes/projects_and_stories.yml +101 -0
- data/spec/fixtures/vcr_cassettes/projects_and_stories_association.yml +147 -0
- data/spec/integration/client_spec.rb +96 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/env.rb +11 -0
- data/spec/support/fabrication.rb +1 -0
- data/spec/support/simple_cov.rb +11 -0
- data/spec/support/timecop.rb +1 -0
- data/spec/support/vcr.rb +8 -0
- data/spec/unit/client_spec.rb +27 -0
- data/spec/unit/core_ext/hash_spec.rb +11 -0
- data/spec/unit/data_access/api_spec.rb +100 -0
- data/spec/unit/data_access/data_store_spec.rb +86 -0
- data/spec/unit/domain/idea_spec.rb +46 -0
- data/spec/unit/domain/ideas_spec.rb +22 -0
- data/spec/unit/domain/iteration_spec.rb +51 -0
- data/spec/unit/domain/iterations_spec.rb +47 -0
- data/spec/unit/domain/project_spec.rb +163 -0
- data/spec/unit/domain/projects_spec.rb +84 -0
- data/spec/unit/domain/stories_spec.rb +84 -0
- data/spec/unit/domain/story_spec.rb +71 -0
- data/spec/unit/domain/user_spec.rb +29 -0
- data/spec/unit/domain/users_spec.rb +36 -0
- data/spec/unit/exceptions/bad_environment_error_spec.rb +9 -0
- data/spec/unit/exceptions/missing_attribute_error_spec.rb +10 -0
- data/spec/unit/exceptions/velocity_equal_to_zero_error_spec.rb +10 -0
- data/trajectory.gemspec +21 -0
- 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,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
|