restpack_group_service 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/Rakefile +1 -1
  4. data/config/database.yml +8 -0
  5. data/db/migrate/20130918110826_create_groups.rb +14 -0
  6. data/db/migrate/20130918113133_create_invitations.rb +17 -0
  7. data/db/migrate/20130918113142_create_memberships.rb +13 -0
  8. data/lib/restpack_group_service.rb +2 -5
  9. data/lib/restpack_group_service/commands/group/create.rb +28 -0
  10. data/lib/restpack_group_service/commands/group/get.rb +25 -0
  11. data/lib/restpack_group_service/commands/group/list.rb +34 -0
  12. data/lib/restpack_group_service/commands/membership/list.rb +36 -0
  13. data/lib/restpack_group_service/configuration.rb +12 -0
  14. data/lib/restpack_group_service/models/group.rb +31 -0
  15. data/lib/restpack_group_service/models/invitation.rb +99 -0
  16. data/lib/restpack_group_service/models/membership.rb +11 -0
  17. data/lib/restpack_group_service/serializers/group.rb +12 -0
  18. data/lib/restpack_group_service/serializers/membership.rb +12 -0
  19. data/lib/restpack_group_service/tasks/db.rake +39 -0
  20. data/lib/restpack_group_service/tasks/tasks.rb +7 -0
  21. data/lib/restpack_group_service/version.rb +1 -1
  22. data/restpack_group_service.gemspec +0 -5
  23. data/spec/commands/group/create_spec.rb +39 -0
  24. data/spec/commands/group/get_spec.rb +54 -0
  25. data/spec/commands/group/list_spec.rb +62 -0
  26. data/spec/commands/membership/list_spec.rb +8 -0
  27. data/spec/factories/group_factory.rb +9 -0
  28. data/spec/factories/invitation_factory.rb +7 -0
  29. data/spec/models/group_spec.rb +27 -0
  30. data/spec/models/invitiation_spec.rb +123 -0
  31. data/spec/models/membership_spec.rb +10 -0
  32. data/spec/spec_helper.rb +22 -14
  33. metadata +37 -73
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 378ce67d9c3c33bf0b781aad08d422d4ed23e574
4
- data.tar.gz: 6342885afaed2a56951febdb25b2f8cdc9b6c97f
3
+ metadata.gz: 5f59fe4026cc51c3b027b697e09dbadb58659185
4
+ data.tar.gz: 9014a6a6cfaa115a7a5f10f84f89665c058ebf1b
5
5
  SHA512:
6
- metadata.gz: 4af82a4731de08ed6d1f2ab2d2501847a75b4eb5488b54607e6e4008268d926e1a8ec6b93091de887ff5bf326b5ee2a8a9385d67b99d2a30699fa0897a82cf53
7
- data.tar.gz: 41c1ad71dd8cc1ac9b4b5282a76730db371543addd34b7e6c1967f988e363ba1fe216adddc1d029100ae46f3ca851da071bf73d0ed230fb99d2715dd35341efb
6
+ metadata.gz: 8d85127110d7bd149d818661fe5f5f213b37b7df1adde720437ec30ce8033f7607193155370a2677ffeef9d3e3c62adde53aae7fb5ee2700a3278d69161bd376
7
+ data.tar.gz: 7c369ac7537e2121b8daf77f964136405c4a18d199f0567dde1cecbeed647b506423ac07ddb5e01b9b792b308d0990635613d54cbbc71097c1f804f622cd9d8d
data/README.md CHANGED
@@ -10,4 +10,5 @@ This gem provides services for managing Groups, Memberships and Invitations.
10
10
 
11
11
  1. install postgres
12
12
  2. create a `restpack_group_service_test` database
13
- 3. rake
13
+ 3. bundle
14
+ 4. rake
data/Rakefile CHANGED
@@ -1,2 +1,2 @@
1
- require "restpack_gem"
1
+ require 'restpack_gem'
2
2
  RestPack::Gem::Tasks.load_service_tasks
@@ -0,0 +1,8 @@
1
+ test:
2
+ adapter: postgresql
3
+ host: localhost
4
+ encoding: utf8
5
+ database: restpack_group_service_test
6
+ pool: 5
7
+ username:
8
+ password:
@@ -0,0 +1,14 @@
1
+ class CreateGroups < ActiveRecord::Migration
2
+ def change
3
+ create_table :restpack_groups do |t|
4
+ t.integer :application_id, :null => false
5
+ t.integer :account_id
6
+ t.integer :created_by, :null => false
7
+ t.string :name, :null => false, :limit => 256
8
+ t.string :description, :limit => 1024
9
+ t.boolean :invitation_required, :null => false
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ class CreateInvitations < ActiveRecord::Migration
2
+ def change
3
+ create_table :restpack_invitations do |t|
4
+ t.integer :application_id, :null => false
5
+ t.integer :group_id, :null => false
6
+ t.integer :inviter_id, :null => false
7
+ t.integer :invitee_id
8
+ t.integer :status_id, :null => false
9
+ t.string :email, :limit => 512
10
+ t.string :access_key, :limit => 128
11
+ t.datetime :expires_at
12
+ t.integer :remaining_uses
13
+
14
+ t.timestamps
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ class CreateMemberships < ActiveRecord::Migration
2
+ def change
3
+ create_table :restpack_memberships do |t|
4
+ t.integer :application_id, :null => false
5
+ t.integer :account_id
6
+ t.integer :group_id, :null => false
7
+ t.integer :user_id, :null => false
8
+ t.integer :invitation_id
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,2 @@
1
- require "restpack_group_service/version"
2
-
3
- module RestpackGroupService
4
- # Your code goes here...
5
- end
1
+ require 'restpack_service'
2
+ RestPack::Service::Loader.load 'group', Dir.pwd
@@ -0,0 +1,28 @@
1
+ module RestPack::Group::Service::Commands
2
+ module Group
3
+ class Create < RestPack::Service::Command
4
+ required do
5
+ array :groups do
6
+ hash do
7
+ required do
8
+ integer :application_id
9
+ integer :created_by
10
+ string :name
11
+ end
12
+
13
+ optional do
14
+ integer :account_id
15
+ string :description
16
+ boolean :invitation_required
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def execute
23
+ groups = Models::Group.create!(inputs[:groups])
24
+ Serializers::Group.serialize(groups)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ module RestPack::Group::Service::Commands
2
+ module Group
3
+ class Get < RestPack::Service::Command
4
+ required do
5
+ integer :id
6
+ integer :application_id
7
+ end
8
+
9
+ def execute
10
+ # TODO: GJ: remove the :application_id scope when we can specify custom serializer filters
11
+ # https://github.com/RestPack/restpack_serializer/issues/42
12
+ result = Serializers::Group.resource(
13
+ inputs,
14
+ Models::Group.where(application_id: inputs[:application_id])
15
+ )
16
+
17
+ if result[:groups].empty?
18
+ status :not_found
19
+ else
20
+ result
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module RestPack::Group::Service::Commands
2
+ module Group
3
+ class List < RestPack::Service::Command
4
+ required do
5
+ integer :application_id
6
+ end
7
+
8
+ optional do
9
+ integer :account_id
10
+ integer :created_by
11
+ boolean :is_account_group, default: false
12
+ integer :page
13
+ integer :page_size
14
+ end
15
+
16
+ def execute
17
+ # TODO: GJ: remove the scope when we can specify custom serializer filters
18
+ # https://github.com/RestPack/restpack_serializer/issues/42
19
+ scope = Models::Group.all
20
+ scope = scope.where(application_id: application_id)
21
+ scope = scope.where(account_id: account_id) if account_id
22
+ scope = scope.where(created_by: created_by) if created_by
23
+
24
+ if is_account_group
25
+ scope = scope.where("account_id IS NOT NULL")
26
+ else
27
+ scope = scope.where("account_id IS NULL") unless account_id
28
+ end
29
+
30
+ Serializers::Group.resource(inputs, scope)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ module RestPack::Group::Service::Commands
2
+ module Membership
3
+ class List < RestPack::Service::Command
4
+ required do
5
+ integer :application_id
6
+ end
7
+
8
+ optional do
9
+ integer :account_id
10
+ integer :group_id
11
+ integer :user_id
12
+ boolean :is_account_group, default: false
13
+ integer :page
14
+ integer :page_size
15
+ end
16
+
17
+ def execute
18
+ # TODO: GJ: remove the scope when we can specify custom serializer filters
19
+ # https://github.com/RestPack/restpack_serializer/issues/42
20
+ scope = Models::Membership.all
21
+ scope = scope.where(application_id: application_id)
22
+ scope = scope.where(account_id: account_id) if account_id
23
+ scope = scope.where(group_id: group_id) if group_id
24
+ scope = scope.where(user_id: user_id) if user_id
25
+
26
+ if is_account_group
27
+ scope = scope.where("account_id IS NOT NULL")
28
+ else
29
+ scope = scope.where("account_id IS NULL") unless account_id
30
+ end
31
+
32
+ Serializers::Membership.resource(inputs, scope)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+ module RestPack::Group::Service
2
+ class Configuration < RestPack::Service::Configuration
3
+
4
+ end
5
+
6
+ mattr_accessor :config
7
+ @@config = Configuration.new
8
+
9
+ def self.setup
10
+ yield @@config
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module RestPack::Group::Service::Models
2
+ class Group < ActiveRecord::Base
3
+ self.table_name = :restpack_groups
4
+
5
+ attr_accessible :application_id, :account_id, :created_by, :name, :description, :invitation_required
6
+ validates_presence_of :application_id, :created_by, :name
7
+
8
+ validates :name, :length => { :maximum => 256 }
9
+ validates :description, :length => { :maximum => 1024 }
10
+
11
+ has_many :memberships
12
+ has_many :invitations
13
+
14
+ after_initialize :set_defaults
15
+ after_create :create_default_member
16
+
17
+ private
18
+
19
+ def set_defaults
20
+ self.invitation_required ||= false
21
+ end
22
+
23
+ def create_default_member
24
+ self.memberships << Membership.new(
25
+ user_id: self.created_by,
26
+ application_id: self.application_id,
27
+ account_id: self.account_id
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,99 @@
1
+ module RestPack::Group::Service::Models
2
+ class Invitation < ActiveRecord::Base
3
+ self.table_name = :restpack_invitations
4
+
5
+ STATUS = { pending: 0, available: 1, accepted: 2, cancelled: 3, expired: 4 }
6
+ attr_accessor :access_key_length, :status
7
+ attr_accessible :access_key_length, :application_id, :email, :expires_at, :group_id, :invitee_id, :inviter_id, :remaining_uses, :status
8
+
9
+ validates_presence_of :application_id, :group_id, :inviter_id, :status_id
10
+ validates :email, :length => { maximum: 512 }
11
+ validates :access_key, :length => { maximum: 128 }
12
+
13
+ belongs_to :group
14
+ has_many :memberships
15
+
16
+ scope :available, -> { where(:status_id => STATUS[:available]) }
17
+
18
+ def self.by_application_id(application_id)
19
+ self.where 'application_id = ?', application_id
20
+ end
21
+
22
+ def self.by_access_key(key)
23
+ self.where 'access_key = ?', key
24
+ end
25
+
26
+ after_initialize :set_defaults
27
+ before_create :generate_access_key
28
+
29
+ def status
30
+ STATUS.key(read_attribute(:status_id))
31
+ end
32
+
33
+ def status=(status)
34
+ write_attribute(:status_id, STATUS[status])
35
+ end
36
+
37
+ def self.accept(application_id, user_id, access_key) #TODO: GJ: skip if already a member
38
+ invitation = self.available.by_application_id(application_id).by_access_key(access_key).first
39
+
40
+ raise "Invalid invitation" unless invitation
41
+
42
+ invitation.accept(user_id)
43
+ end
44
+
45
+ def accept(user_id)
46
+ validate! user_id
47
+
48
+ transaction do
49
+ membership = Membership.new application_id: application_id, group_id: group_id, user_id: user_id
50
+ self.memberships << membership
51
+ self.use!
52
+
53
+ membership
54
+ end
55
+ end
56
+
57
+ def self.access_key_unique?(application_id, access_key)
58
+ self.by_application_id(application_id).by_access_key(access_key).empty?
59
+ end
60
+
61
+ protected
62
+
63
+ def use!
64
+ if single_use?
65
+ self.status = :accepted
66
+ else
67
+ self.remaining_uses -= 1
68
+ end
69
+ self.save!
70
+ end
71
+
72
+ private
73
+
74
+ def validate!(user_id)
75
+ raise "Invitation is #{status}" unless status == :available
76
+ raise "Invalid invitee" unless invitee_id.nil? || invitee_id == user_id.to_i
77
+ raise "Invitation has expired" unless remaining_uses.nil? || remaining_uses > 0
78
+ raise "Invitation has expired" unless expires_at.nil? || expires_at > Time.now
79
+ end
80
+
81
+ def single_use?
82
+ remaining_uses.nil? || remaining_uses <= 1
83
+ end
84
+
85
+ def set_defaults
86
+ self.access_key_length ||= 16
87
+ self.status ||= :available
88
+ end
89
+
90
+ def generate_access_key
91
+ length = access_key_length.to_i
92
+
93
+ loop do
94
+ self.access_key = SecureRandom.urlsafe_base64(length).first(length)
95
+ break if Invitation.access_key_unique?(application_id, access_key)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,11 @@
1
+ module RestPack::Group::Service::Models
2
+ class Membership < ActiveRecord::Base
3
+ self.table_name = :restpack_memberships
4
+
5
+ attr_accessible :application_id, :account_id, :group_id, :invitation_id, :user_id
6
+ validates_presence_of :application_id, :group_id, :user_id
7
+
8
+ belongs_to :group
9
+ belongs_to :invitation
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module RestPack::Group::Service::Serializers
2
+ class Group
3
+ include RestPack::Serializer
4
+
5
+ self.model_class = Models::Group
6
+ self.key = :groups
7
+
8
+ attributes :id, :application_id, :account_id, :created_by, :name,
9
+ :description, :invitation_required, :href
10
+ can_include :memberships, :invitations
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module RestPack::Group::Service::Serializers
2
+ class Membership
3
+ include RestPack::Serializer
4
+
5
+ self.model_class = Models::Membership
6
+ self.key = :memberships
7
+
8
+ attributes :id, :application_id, :account_id, :group_id, :user_id,
9
+ :invitation_id, :href
10
+ can_include :groups, :invitations
11
+ end
12
+ end
@@ -0,0 +1,39 @@
1
+ namespace :restpack do
2
+ desc "Run any outstanding RestPack migrations"
3
+ task :migrate do
4
+ Rake::Task["restpack:group:migrate"].invoke
5
+ end
6
+
7
+ desc "List RestPack configuration"
8
+ task :configuration do
9
+ Rake::Task["restpack:group:configuration"].invoke
10
+ end
11
+
12
+ namespace :group do
13
+ desc "Run any outstanding RestPack::Core migrations"
14
+ task :migrate => ["connection"] do
15
+ source_migrations_path = File.dirname(__FILE__) + "/../../../db/migrate"
16
+ target_migrations_path = "db/migrate"
17
+
18
+ ActiveRecord::Migration.verbose = true
19
+ ActiveRecord::Migrator.migrate(source_migrations_path)
20
+
21
+ if File.directory?(target_migrations_path)
22
+ FileUtils.cp_r(Dir["#{source_migrations_path}/*"], target_migrations_path)
23
+ end
24
+ end
25
+
26
+ task :connection do
27
+ config = YAML.load(IO.read('config/database.yml'))
28
+ environment = ENV['RAILS_ENV'] || ENV['DB'] || 'development'
29
+ ActiveRecord::Base.establish_connection config[environment]
30
+ end
31
+
32
+ desc "List RestPack::Group::Service configuration"
33
+ task :configuration do
34
+ p "RestPack::Group::Service Configuration"
35
+ p "--------------------------------"
36
+ p "TODO"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module RestPack::Group::Service
2
+ class Tasks
3
+ def self.load_tasks
4
+ load "restpack_group_service/tasks/db.rake"
5
+ end
6
+ end
7
+ end
@@ -1,7 +1,7 @@
1
1
  module RestPack
2
2
  module Group
3
3
  module Service
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.5"
5
5
  end
6
6
  end
7
7
  end
@@ -19,11 +19,6 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "restpack_service"
22
- spec.add_dependency "restpack_serializer"
23
- spec.add_dependency "restpack_gem"
24
- spec.add_dependency "sinatra", "~> 1.4.3"
25
- spec.add_dependency "pg", "~> 0.16"
26
- spec.add_dependency "require_all", "~> 1.3.0"
27
22
 
28
23
  spec.add_development_dependency "bundler", "~> 1.3"
29
24
  spec.add_development_dependency "database_cleaner", "~> 1.0.1"
@@ -0,0 +1,39 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe Commands::Group::Create do
4
+ #TODO: GJ: validate array
5
+ # is_required :application_id, :created_by, :name
6
+ # is_optional :account_id, :description, :invitation_required
7
+
8
+ context 'creating a group' do
9
+ let(:response) { subject.class.run(params) }
10
+
11
+ context 'with valid params' do
12
+ let(:group) { {
13
+ application_id: 123,
14
+ created_by: 234,
15
+ name: 'My New Group',
16
+ account_id: 345,
17
+ description: 'this is the description',
18
+ invitation_required: true
19
+ } }
20
+ let(:params) { {
21
+ groups: [group]
22
+ } }
23
+
24
+ it 'returns the newly created group' do
25
+ response.success?.should == true
26
+
27
+ groups = response.result[:groups]
28
+ groups.length.should == 1
29
+
30
+ groups.first[:application_id].should == 123
31
+ groups.first[:created_by].should == 234
32
+ groups.first[:name].should == "My New Group"
33
+ groups.first[:account_id].should == 345
34
+ groups.first[:description].should == 'this is the description'
35
+ groups.first[:invitation_required].should == true
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,54 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe Commands::Group::Get do
4
+ is_required :id, :application_id
5
+
6
+ let(:response) { subject.class.run(params) }
7
+ let(:params) { {} }
8
+
9
+ before do
10
+ @group = create(:group)
11
+ end
12
+
13
+ context 'with valid params' do
14
+ let(:params) { {
15
+ id: @group.id,
16
+ application_id: @group.application_id
17
+ } }
18
+
19
+ it 'is valid' do
20
+ response.success?.should == true
21
+ end
22
+
23
+ it 'return the group' do
24
+ response.result[:groups].length.should == 1
25
+ response.result[:groups].first[:id].should == @group.id.to_s
26
+ end
27
+ end
28
+
29
+ context 'with invalid :id' do
30
+ let(:params) { {
31
+ id: 142857,
32
+ application_id: @group.application_id
33
+ } }
34
+
35
+ it 'is :not_found' do
36
+ response.success?.should == false
37
+ response.result.should == {}
38
+ response.status.should == :not_found
39
+ end
40
+ end
41
+
42
+ context 'with invalid :application_id' do
43
+ let(:params) { {
44
+ id: @group.id,
45
+ application_id: 142857
46
+ }}
47
+
48
+ it 'is :not_found' do
49
+ response.success?.should == false
50
+ response.result.should == {}
51
+ response.status.should == :not_found
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,62 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe Commands::Group::List do
4
+ is_required :application_id
5
+ is_optional :account_id, :created_by, :is_account_group, :page, :page_size
6
+
7
+ before do
8
+ @groups = create_list(:group, 5)
9
+ end
10
+
11
+ context 'listing groups' do
12
+ let(:response) { subject.class.run(params) }
13
+
14
+ context ':application_id' do
15
+ before { create_list(:group, 3, application_id: 123) }
16
+
17
+ context 'valid' do
18
+ let(:params) { { application_id: 123 } }
19
+ it 'returns groups' do
20
+ response.result[:meta][:groups][:count].should == 3
21
+ end
22
+ end
23
+
24
+ context 'invalid' do
25
+ let(:params) { { application_id: 999999 } }
26
+ it 'returns no groups' do
27
+ response.result[:meta][:groups][:count].should == 0
28
+ end
29
+ end
30
+
31
+ pending ':application_id is required'
32
+ end
33
+
34
+ context ':account_id' do
35
+ before do
36
+ create_list(:group, 2, application_id: 123)
37
+ create_list(:group, 2, application_id: 123, account_id: 345)
38
+ end
39
+
40
+ context 'valid' do
41
+ let(:params) { { application_id: 123, account_id: 345 } }
42
+ it 'returns groups' do
43
+ response.result[:meta][:groups][:count].should == 2
44
+ end
45
+ end
46
+
47
+ context 'invalid' do
48
+ let(:params) { { application_id: 123, account_id: 999999 } }
49
+ it 'returns no groups' do
50
+ response.result[:meta][:groups][:count].should == 0
51
+ end
52
+ end
53
+
54
+ context ':is_account_group' do
55
+ let(:params) { { application_id: 123, is_account_group: true } }
56
+ it 'returns groups that have an account' do
57
+ response.result[:meta][:groups][:count].should == 2
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe Commands::Membership::List do
4
+ is_required :application_id
5
+ is_optional :account_id, :group_id, :user_id, :is_account_group, :page, :page_size
6
+
7
+ pending
8
+ end
@@ -0,0 +1,9 @@
1
+ FactoryGirl.define do
2
+ factory :group, :class => Models::Group do
3
+ sequence(:name) {|n| "Group ##{n}" }
4
+ sequence(:description) {|n| "Description ##{n}" }
5
+ sequence(:application_id)
6
+ sequence(:created_by)
7
+ invitation_required true
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ FactoryGirl.define do
2
+ factory :invitation, :class => Models::Invitation do
3
+ sequence(:application_id)
4
+ sequence(:inviter_id)
5
+ group
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Models::Group do
4
+ it { should validate_presence_of(:application_id) }
5
+ it { should validate_presence_of(:created_by) }
6
+ it { should validate_presence_of(:name) }
7
+
8
+ it { should ensure_length_of(:name).is_at_most(256) }
9
+ it { should ensure_length_of(:description).is_at_most(1024) }
10
+
11
+ it { should have_many(:memberships) }
12
+ it { should have_many(:invitations) }
13
+
14
+ context "when creating" do
15
+ context "a valid group" do
16
+ before do
17
+ @group = create(:group, account_id: 999)
18
+ end
19
+
20
+ it "should create a default member" do
21
+ @group.memberships.count.should == 1
22
+ @group.memberships.first.user_id.should == @group.created_by
23
+ @group.memberships.first.account_id.should == @group.account_id
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe Models::Invitation do
4
+ it { should validate_presence_of(:application_id) }
5
+ it { should validate_presence_of(:group_id) }
6
+ it { should validate_presence_of(:inviter_id) }
7
+ it { should validate_presence_of(:status_id) }
8
+
9
+ it { should ensure_length_of(:email).is_at_most(512) }
10
+ it { should ensure_length_of(:access_key).is_at_most(128) }
11
+
12
+ it { should_not allow_mass_assignment_of(:access_key) }
13
+
14
+ it { should belong_to(:group) }
15
+ it { should have_many(:memberships) }
16
+
17
+ Membership = Models::Membership
18
+ Invitation = Models::Invitation
19
+
20
+ context "when accepting" do
21
+ context "a valid single-use user invitation" do
22
+ before do
23
+ @invitation = create(:invitation, invitee_id: 142857)
24
+
25
+ Membership.count.should == 1
26
+ @membership = Invitation.accept(@invitation.application_id, @invitation.invitee_id, @invitation.access_key)
27
+ Membership.count.should == 2
28
+ end
29
+
30
+ it "creates a new member for the group" do
31
+ @membership.user_id.should == 142857
32
+ @membership.application_id.should == @invitation.application_id
33
+ @membership.group_id.should == @invitation.group_id
34
+ @membership.invitation_id.should == @invitation.id
35
+ end
36
+
37
+ it "marks the invitation as accepted" do
38
+ @invitation.reload.status.should == :accepted
39
+ end
40
+
41
+ it "can only be used once" do
42
+ expect do
43
+ Invitation.accept(@invitation.application_id, @invitation.invitee_id, @invitation.access_key)
44
+ end.to raise_error('Invalid invitation')
45
+ end
46
+
47
+ it "can only be used by the invitee" do
48
+ @invitation = create(:invitation, invitee_id: 142857)
49
+ expect do
50
+ @membership = Invitation.accept(@invitation.application_id, 999, @invitation.access_key)
51
+ end.to raise_error('Invalid invitee')
52
+ end
53
+ end
54
+
55
+ context "a valid multi-use invitation" do
56
+ before do
57
+ @invitation = create(:invitation, remaining_uses: 3)
58
+ Membership.count.should == 1
59
+ end
60
+
61
+ it "can be accepted by a user" do
62
+ @membership = Invitation.accept(@invitation.application_id, 1234, @invitation.access_key)
63
+ Membership.count.should == 2
64
+ @membership.user_id.should == 1234
65
+ end
66
+
67
+ it "should decrement the remaining_uses by 1" do
68
+ @invitation.remaining_uses.should == 3
69
+ Invitation.accept(@invitation.application_id, 1234, @invitation.access_key)
70
+ @invitation.reload.remaining_uses.should == 2
71
+ end
72
+ end
73
+
74
+ context "a multi-use invitation with no remaining uses" do
75
+ before do
76
+ @invitation = create(:invitation, remaining_uses: 0)
77
+ end
78
+
79
+ it "can't be used" do
80
+ expect do
81
+ Invitation.accept(@invitation.application_id, 1234, @invitation.access_key)
82
+ end.to raise_error('Invitation has expired')
83
+ end
84
+ end
85
+
86
+ context "an expired invitation" do
87
+ before do
88
+ @invitation = create(:invitation, invitee_id: 142857, expires_at: 1.second.ago)
89
+ end
90
+
91
+ it "can't be used" do
92
+ expect do
93
+ Invitation.accept(@invitation.application_id, 142857, @invitation.access_key)
94
+ end.to raise_error('Invitation has expired')
95
+ end
96
+ end
97
+ end
98
+
99
+ context "access_key" do
100
+ it "defaults to a length of 16" do
101
+ invitation = create(:invitation)
102
+ invitation.access_key.length.should == 16
103
+ end
104
+ it "allows the access_key length to be set" do
105
+ invitation = create(:invitation, access_key_length: 5)
106
+ invitation.access_key.length.should == 5
107
+ end
108
+ end
109
+
110
+ context "status" do
111
+ it "defaults to :available" do
112
+ invitation = create(:invitation)
113
+ invitation.status.should == :available
114
+ end
115
+
116
+ [:pending, :available, :accepted, :cancelled, :expired].each do |status|
117
+ it "can be :#{status}" do
118
+ invitation = create(:invitation, status: status)
119
+ invitation.status.should == status
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Models::Membership do
4
+ it { should validate_presence_of(:application_id) }
5
+ it { should validate_presence_of(:group_id) }
6
+ it { should validate_presence_of(:user_id) }
7
+
8
+ it { should belong_to(:group) }
9
+ it { should belong_to(:invitation) }
10
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,17 +1,25 @@
1
- # This file was generated by the `rspec --init` command. Conventionally, all
2
- # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
- # Require this file using `require "spec_helper"` to ensure that it is only
4
- # loaded once.
5
- #
6
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
1
+ require 'restpack_service/support/spec_helper'
2
+ require 'restpack_group_service'
3
+
4
+ config = YAML.load_file('./config/database.yml')
5
+ ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'] || config['test'])
6
+
7
+ migrations_path = File.dirname(__FILE__) + "/../db/migrate"
8
+ migrator = ActiveRecord::Migrator.new(:up, migrations_path)
9
+ migrator.migrate
10
+
11
+ FactoryGirl.find_definitions
12
+
13
+ DatabaseCleaner.strategy = :transaction
14
+
7
15
  RSpec.configure do |config|
8
- config.treat_symbols_as_metadata_keys_with_true_values = true
9
- config.run_all_when_everything_filtered = true
10
- config.filter_run :focus
16
+ config.include FactoryGirl::Syntax::Methods
17
+
18
+ config.before(:each) do
19
+ DatabaseCleaner.start
20
+ end
11
21
 
12
- # Run specs in random order to surface order dependencies. If you find an
13
- # order dependency and want to debug it, you can fix the order by providing
14
- # the seed, which is printed after each run.
15
- # --seed 1234
16
- config.order = 'random'
22
+ config.after(:each) do
23
+ DatabaseCleaner.clean
24
+ end
17
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restpack_group_service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gavin Joyce
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-18 00:00:00.000000000 Z
11
+ date: 2013-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: restpack_service
@@ -24,76 +24,6 @@ dependencies:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: restpack_serializer
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - '>='
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '>='
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: restpack_gem
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - '>='
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - '>='
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: sinatra
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ~>
60
- - !ruby/object:Gem::Version
61
- version: 1.4.3
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ~>
67
- - !ruby/object:Gem::Version
68
- version: 1.4.3
69
- - !ruby/object:Gem::Dependency
70
- name: pg
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ~>
74
- - !ruby/object:Gem::Version
75
- version: '0.16'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ~>
81
- - !ruby/object:Gem::Version
82
- version: '0.16'
83
- - !ruby/object:Gem::Dependency
84
- name: require_all
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ~>
88
- - !ruby/object:Gem::Version
89
- version: 1.3.0
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ~>
95
- - !ruby/object:Gem::Version
96
- version: 1.3.0
97
27
  - !ruby/object:Gem::Dependency
98
28
  name: bundler
99
29
  requirement: !ruby/object:Gem::Requirement
@@ -206,9 +136,34 @@ files:
206
136
  - LICENSE.txt
207
137
  - README.md
208
138
  - Rakefile
139
+ - config/database.yml
140
+ - db/migrate/20130918110826_create_groups.rb
141
+ - db/migrate/20130918113133_create_invitations.rb
142
+ - db/migrate/20130918113142_create_memberships.rb
209
143
  - lib/restpack_group_service.rb
144
+ - lib/restpack_group_service/commands/group/create.rb
145
+ - lib/restpack_group_service/commands/group/get.rb
146
+ - lib/restpack_group_service/commands/group/list.rb
147
+ - lib/restpack_group_service/commands/membership/list.rb
148
+ - lib/restpack_group_service/configuration.rb
149
+ - lib/restpack_group_service/models/group.rb
150
+ - lib/restpack_group_service/models/invitation.rb
151
+ - lib/restpack_group_service/models/membership.rb
152
+ - lib/restpack_group_service/serializers/group.rb
153
+ - lib/restpack_group_service/serializers/membership.rb
154
+ - lib/restpack_group_service/tasks/db.rake
155
+ - lib/restpack_group_service/tasks/tasks.rb
210
156
  - lib/restpack_group_service/version.rb
211
157
  - restpack_group_service.gemspec
158
+ - spec/commands/group/create_spec.rb
159
+ - spec/commands/group/get_spec.rb
160
+ - spec/commands/group/list_spec.rb
161
+ - spec/commands/membership/list_spec.rb
162
+ - spec/factories/group_factory.rb
163
+ - spec/factories/invitation_factory.rb
164
+ - spec/models/group_spec.rb
165
+ - spec/models/invitiation_spec.rb
166
+ - spec/models/membership_spec.rb
212
167
  - spec/spec_helper.rb
213
168
  homepage: https://github.com/RestPack
214
169
  licenses:
@@ -230,9 +185,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
185
  version: '0'
231
186
  requirements: []
232
187
  rubyforge_project:
233
- rubygems_version: 2.0.7
188
+ rubygems_version: 2.0.5
234
189
  signing_key:
235
190
  specification_version: 4
236
191
  summary: Groups, Members and Invitations
237
192
  test_files:
193
+ - spec/commands/group/create_spec.rb
194
+ - spec/commands/group/get_spec.rb
195
+ - spec/commands/group/list_spec.rb
196
+ - spec/commands/membership/list_spec.rb
197
+ - spec/factories/group_factory.rb
198
+ - spec/factories/invitation_factory.rb
199
+ - spec/models/group_spec.rb
200
+ - spec/models/invitiation_spec.rb
201
+ - spec/models/membership_spec.rb
238
202
  - spec/spec_helper.rb