restpack_group_service 0.0.3 → 0.0.5

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