familia 0.10.2 → 1.0.0.pre.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.pre-commit-config.yaml +1 -1
  4. data/.rubocop.yml +75 -0
  5. data/.rubocop_todo.yml +63 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +47 -15
  8. data/README.md +65 -13
  9. data/VERSION.yml +4 -3
  10. data/familia.gemspec +18 -13
  11. data/lib/familia/base.rb +33 -0
  12. data/lib/familia/connection.rb +87 -0
  13. data/lib/familia/core_ext.rb +119 -124
  14. data/lib/familia/errors.rb +33 -0
  15. data/lib/familia/features/api_version.rb +19 -0
  16. data/lib/familia/features/atomic_saves.rb +8 -0
  17. data/lib/familia/features/quantizer.rb +35 -0
  18. data/lib/familia/features/safe_dump.rb +194 -0
  19. data/lib/familia/features.rb +51 -0
  20. data/lib/familia/horreum/class_methods.rb +292 -0
  21. data/lib/familia/horreum/commands.rb +106 -0
  22. data/lib/familia/horreum/relations_management.rb +141 -0
  23. data/lib/familia/horreum/serialization.rb +193 -0
  24. data/lib/familia/horreum/settings.rb +63 -0
  25. data/lib/familia/horreum/utils.rb +44 -0
  26. data/lib/familia/horreum.rb +248 -0
  27. data/lib/familia/logging.rb +232 -0
  28. data/lib/familia/redistype/commands.rb +56 -0
  29. data/lib/familia/redistype/serialization.rb +110 -0
  30. data/lib/familia/redistype.rb +185 -0
  31. data/lib/familia/refinements.rb +88 -0
  32. data/lib/familia/settings.rb +38 -0
  33. data/lib/familia/types/hashkey.rb +107 -0
  34. data/lib/familia/types/list.rb +155 -0
  35. data/lib/familia/types/sorted_set.rb +234 -0
  36. data/lib/familia/types/string.rb +115 -0
  37. data/lib/familia/types/unsorted_set.rb +123 -0
  38. data/lib/familia/utils.rb +125 -0
  39. data/lib/familia/version.rb +25 -0
  40. data/lib/familia.rb +57 -161
  41. data/lib/redis_middleware.rb +109 -0
  42. data/try/00_familia_try.rb +5 -4
  43. data/try/10_familia_try.rb +21 -17
  44. data/try/20_redis_type_try.rb +67 -0
  45. data/try/{21_redis_object_zset_try.rb → 21_redis_type_zset_try.rb} +2 -2
  46. data/try/{22_redis_object_set_try.rb → 22_redis_type_set_try.rb} +2 -2
  47. data/try/{23_redis_object_list_try.rb → 23_redis_type_list_try.rb} +2 -2
  48. data/try/{24_redis_object_string_try.rb → 24_redis_type_string_try.rb} +6 -6
  49. data/try/{25_redis_object_hash_try.rb → 25_redis_type_hash_try.rb} +3 -3
  50. data/try/26_redis_bool_try.rb +10 -6
  51. data/try/27_redis_horreum_try.rb +93 -0
  52. data/try/30_familia_object_try.rb +21 -20
  53. data/try/35_feature_safedump_try.rb +83 -0
  54. data/try/40_customer_try.rb +140 -0
  55. data/try/41_customer_safedump_try.rb +86 -0
  56. data/try/test_helpers.rb +194 -0
  57. metadata +51 -47
  58. data/lib/familia/helpers.rb +0 -70
  59. data/lib/familia/object.rb +0 -533
  60. data/lib/familia/redisobject.rb +0 -1017
  61. data/lib/familia/test_helpers.rb +0 -40
  62. data/lib/familia/tools.rb +0 -67
  63. data/try/20_redis_object_try.rb +0 -44
@@ -0,0 +1,67 @@
1
+
2
+ require_relative '../lib/familia'
3
+ require_relative '../lib/familia/features/quantizer'
4
+ require_relative './test_helpers'
5
+
6
+ #Familia.apiversion = 'v1'
7
+
8
+ @limiter1 = Limiter.new :requests
9
+
10
+
11
+ ## Redis Types are unique per instance of a Familia class
12
+ @a = Bone.new 'atoken1', :name1
13
+ @b = Bone.new 'atoken2', :name2
14
+ p [@a.object_id, @b.object_id]
15
+ p [@a.owners.parent.class, @b.owners.parent.class]
16
+ p [@a.owners.parent.object_id, @b.owners.parent.object_id]
17
+ p [@a.owners.rediskey, @b.owners.rediskey]
18
+ p [@a.token, @b.token]
19
+ p [@a.name, @b.name]
20
+ @a.owners.rediskey.eql?(@b.owners.rediskey)
21
+ #=> false
22
+
23
+ ## Redis Types are frozen
24
+ @a.owners.frozen?
25
+ #=> true
26
+
27
+
28
+ ## Limiter#qstamp
29
+ @limiter1.counter.qstamp(10.minutes, '%H:%M', 1302468980)
30
+ ##=> '20:50'
31
+
32
+ ## Redis Types can be stored to quantized stamp suffix
33
+ @limiter1.counter.rediskey
34
+ ##=> "v1:limiter:requests:counter:20:50"
35
+
36
+ ## Limiter#qstamp as a number
37
+ @limiter2 = Limiter.new :requests
38
+ @limiter2.counter.qstamp(10.minutes, pattern=nil, now=1302468980)
39
+ ##=> 1302468600
40
+
41
+ ## Redis Types can be stored to quantized numeric suffix. This
42
+ ## tryouts is disabled b/c `RedisType#rediskey` takes no args
43
+ ## and relies on the `class Limiter` definition in test_helpers.rb
44
+ ## for the `:quantize` option. The quantized suffix for the Limiter
45
+ ## class is `'%H:%M'` so its redis keys will always look like that.
46
+ @limiter2.counter.rediskey
47
+ ##=> "v1:limiter:requests:counter:1302468600"
48
+
49
+ ## Increment counter
50
+ @limiter1.counter.clear
51
+ @limiter1.counter.increment
52
+ #=> 1
53
+
54
+ ## Check ttl
55
+ @limiter1.counter.ttl
56
+ #=> 3600.0
57
+
58
+ ## Check ttl for a different instance
59
+ ## (this exists to make sure options are cloned for each instance)
60
+ @limiter3 = Limiter.new :requests
61
+ @limiter3.counter.ttl
62
+ #=> 3600.0
63
+
64
+ ## Check realttl
65
+ sleep 1 # Redis ttls are in seconds so we can't wait any less time than this (without mocking)
66
+ @limiter1.counter.realttl
67
+ #=> 3600-1
@@ -1,5 +1,5 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
1
+ require_relative '../lib/familia'
2
+ require_relative './test_helpers'
3
3
 
4
4
  # Familia.debug = true
5
5
 
@@ -1,6 +1,6 @@
1
1
 
2
- require 'familia'
3
- require 'familia/test_helpers'
2
+ require_relative '../lib/familia'
3
+ require_relative './test_helpers'
4
4
 
5
5
  @a = Bone.new 'atoken', 'akey'
6
6
 
@@ -1,6 +1,6 @@
1
1
 
2
- require 'familia'
3
- require 'familia/test_helpers'
2
+ require_relative '../lib/familia'
3
+ require_relative './test_helpers'
4
4
 
5
5
  @a = Bone.new 'atoken', 'akey'
6
6
 
@@ -1,13 +1,13 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
1
+ require_relative '../lib/familia'
2
+ require_relative './test_helpers'
3
3
 
4
- Familia.apiversion = 'v1'
4
+ #Familia.apiversion = 'v1'
5
5
 
6
6
  @a = Bone.new 'atoken2', 'akey'
7
7
 
8
8
  ## Bone#rediskey
9
9
  @a.rediskey
10
- #=> 'v1:bone:atoken2:akey:object'
10
+ #=> 'bone:atoken2:akey:object'
11
11
 
12
12
  ## Familia::String#value should give default value
13
13
  @a.value.value
@@ -39,7 +39,7 @@ Familia.apiversion = 'v1'
39
39
  #=> '1000'
40
40
 
41
41
  ## Familia::String#increment
42
- @ret.increment
42
+ @ret.increment
43
43
  #=> 1001
44
44
 
45
45
  ## Familia::String#incrementby
@@ -47,7 +47,7 @@ Familia.apiversion = 'v1'
47
47
  #=> 1100
48
48
 
49
49
  ## Familia::String#decrement
50
- @ret.decrement
50
+ @ret.decrement
51
51
  #=> 1099
52
52
 
53
53
  ## Familia::String#decrementby
@@ -1,5 +1,5 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
1
+ require_relative '../lib/familia'
2
+ require_relative './test_helpers'
3
3
 
4
4
  @a = Bone.new 'atoken', 'akey'
5
5
 
@@ -21,7 +21,7 @@ require 'familia/test_helpers'
21
21
  @a.props.has_key? 'fieldA'
22
22
  #=> true
23
23
 
24
- ## Familia::HashKey#all
24
+ ## Familia::HashKey#all
25
25
  @a.props.all.class
26
26
  #=> Hash
27
27
 
@@ -1,5 +1,5 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
1
+ require_relative '../lib/familia'
2
+ require_relative './test_helpers'
3
3
 
4
4
  Familia.debug = true
5
5
 
@@ -17,10 +17,10 @@ Familia.debug = true
17
17
  ## Trying to store a boolean value to a hash key raises an exception
18
18
  begin
19
19
  @hashkey["test"] = true
20
- rescue TypeError => e
20
+ rescue Familia::HighRiskFactor => e
21
21
  e.message
22
22
  end
23
- #=> "Cannot store test => true (TrueClass) in key"
23
+ #=> "High risk factor for serialization bugs: true<TrueClass>"
24
24
 
25
25
  ## Boolean values are returned as strings
26
26
  @hashkey["test"]
@@ -29,11 +29,15 @@ end
29
29
  ## Trying to store a nil value to a hash key raises an exception
30
30
  begin
31
31
  @hashkey["test"] = nil
32
- rescue TypeError => e
32
+ rescue Familia::HighRiskFactor => e
33
33
  e.message
34
34
  end
35
- #=> "Cannot store test => nil (NilClass) in key"
35
+ #=> "High risk factor for serialization bugs: <NilClass>"
36
36
 
37
37
  ## The exceptions prevented the hash from being updated
38
38
  @hashkey["test"]
39
39
  #=> "true"
40
+
41
+ ## Clear the hash key
42
+ @hashkey.clear
43
+ #=> 1
@@ -0,0 +1,93 @@
1
+ require_relative '../lib/familia'
2
+ require_relative './test_helpers'
3
+
4
+ Familia.debug = true
5
+
6
+ @identifier = 'tryouts-27@onetimesecret.com'
7
+ @customer = Customer.new @identifier
8
+ @hashkey = Familia::HashKey.new 'tryouts-27'
9
+
10
+ ## Customer passed as value is returned as string identifier, on assignment as string
11
+ @hashkey["test1"] = @customer.identifier
12
+ #=> @identifier
13
+
14
+ ## Customer passed as value is returned as string identifier
15
+ @hashkey["test1"]
16
+ #=> @identifier
17
+
18
+ ## Trying to store a customer to a hash key implicitly converts it to string identifier
19
+ ## we can't tell from the return value this way either. Here the store method return value
20
+ ## is either 1 (if the key is new) or 0 (if the key already exists).
21
+ @hashkey.store "test2", @customer
22
+ #=> 1
23
+
24
+ ## Trying to store a customer to a hash key implicitly converts it to string identifier
25
+ ## but we can't tell that from the return value. Here the hash syntax return value
26
+ ## is the value that is being assigned.
27
+ @hashkey["test2"] = @customer
28
+ #=> @customer
29
+
30
+ ## Trying store again with the same key returns 0
31
+ @hashkey.store "test2", @customer
32
+ #=> 0
33
+
34
+ ## Customer passed as value is returned as string identifier
35
+ @hashkey["test2"]
36
+ #=> @identifier
37
+
38
+ ## Remove the key
39
+ @hashkey.clear
40
+ #=> 1
41
+
42
+ ## Horreum objects can update and save their fields (1 of 2)
43
+ @customer.name = 'John Doe'
44
+ #=> "John Doe"
45
+
46
+ ## Horreum objects can update and save their fields (2 of 2)
47
+ @customer.save
48
+ #=> true
49
+
50
+ ## Horreum object fields have a fast writer method (1 of 2)
51
+ Familia.trace :LOAD, @customer.redis, @customer.redisuri, caller if Familia.debug?
52
+ @customer.name! 'Jane Doe'
53
+ #=> 0
54
+
55
+ ## Horreum object fields have a fast writer method (2 of 2)
56
+ @customer.refresh!
57
+ @customer.name
58
+ #=> "Jane Doe"
59
+
60
+ ## Unsaved changes are lost when an object reloads
61
+ @customer.name = 'John Doe'
62
+ @customer.refresh!
63
+ @customer.name
64
+ #=> "Jane Doe"
65
+
66
+ ## Horreum objects can be destroyed
67
+ @customer.destroy!
68
+ #=> true
69
+
70
+ ## All horrerum objects have a key field
71
+ @customer.key
72
+ #=> @identifier
73
+
74
+ ## Even ones that didn't define it
75
+ @cd = CustomDomain.new "www.example.com", "@identifier"
76
+ @cd.key
77
+ #=> nil
78
+
79
+ ## We can call #identifier directly if we want to "lasy load" the unique identifier
80
+ @cd.identifier
81
+ #=> "7565befd"
82
+
83
+ ## The #key field will still be nil
84
+ @cd.key
85
+ #=> nil
86
+
87
+ ## But once we save
88
+ @cd.save
89
+ #=> true
90
+
91
+ ## The key will be set
92
+ @cd.key
93
+ #=> "7565befd"
@@ -1,6 +1,6 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
3
- Familia.apiversion = 'v1'
1
+ require_relative '../lib/familia'
2
+ require_relative './test_helpers'
3
+ #Familia.apiversion = 'v1'
4
4
 
5
5
  @a = Bone.new 'atoken', 'akey'
6
6
 
@@ -8,8 +8,8 @@ Familia.apiversion = 'v1'
8
8
  Bone.prefix
9
9
  #=> :bone
10
10
 
11
- ## Familia#index
12
- @a.index
11
+ ## Familia#identifier
12
+ @a.identifier
13
13
  #=> 'atoken:akey'
14
14
 
15
15
  ## Familia.suffix
@@ -18,32 +18,34 @@ Bone.suffix
18
18
 
19
19
  ## Familia#rediskey
20
20
  @a.rediskey
21
- #=> 'v1:bone:atoken:akey:object'
21
+ #=> 'bone:atoken:akey:object'
22
22
 
23
23
  ## Familia#rediskey
24
24
  @a.rediskey
25
- #=> 'v1:bone:atoken:akey:object'
25
+ #=> 'bone:atoken:akey:object'
26
26
 
27
27
  ## Familia#save
28
28
  @cust = Customer.new :delano, "Delano Mandelbaum"
29
29
  @cust.save
30
30
  #=> true
31
31
 
32
- ## Customer.instances
33
- Customer.instances.all.collect(&:custid)
34
- #=> [:delano]
32
+ ## Customer.values (Updateing this sorted set of instance IDs is a Onetime
33
+ # Secret feature. Disabled here b/c there's no code in Familia or the test
34
+ # helpers that replicates this behaviour.) Leaving this here for reference.
35
+ Customer.values.all.collect(&:custid)
36
+ ##=> ['delano']
35
37
 
36
38
  ## Familia.from_redis
37
39
  obj = Customer.from_redis :delano
38
- obj.custid
39
- #=> :delano
40
+ [obj.class, obj.custid]
41
+ #=> [Customer, 'delano']
40
42
 
41
43
  ## Customer.destroy
42
44
  @cust.destroy!
43
- #=> 1
45
+ #=> true
44
46
 
45
47
  ## Customer.instances
46
- Customer.instances.size
48
+ Customer.values.size
47
49
  #=> 0
48
50
 
49
51
  ## Familia#save with an object that expires
@@ -57,7 +59,7 @@ Customer.customers.class
57
59
 
58
60
  ## Familia class rediskey
59
61
  Customer.customers.rediskey
60
- #=> 'v1:customer:customers'
62
+ #=> 'customer:customers'
61
63
 
62
64
  ## Familia.class_list
63
65
  Customer.customers << :delano << :tucker << :morton
@@ -69,19 +71,18 @@ Customer.customers.clear
69
71
  #=> 1
70
72
 
71
73
 
72
- ## Familia class replace 1
74
+ ## Familia class replace 1 of 4
73
75
  Customer.message.value = "msg1"
74
76
  #=> "msg1"
75
77
 
76
- ## Familia class replace 2
78
+ ## Familia class replace 2 of 4
77
79
  Customer.message.value
78
80
  #=> "msg1"
79
81
 
80
- ## Familia class replace 3
82
+ ## Familia class replace 3 of 4
81
83
  Customer.message = "msg2"
82
84
  #=> "msg2"
83
85
 
84
- ## Familia class replace 4
86
+ ## Familia class replace 4 of 4
85
87
  Customer.message.value
86
88
  #=> "msg2"
87
-
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ # These tryouts test the safe dumping functionality.
4
+
5
+ require_relative '../lib/familia'
6
+ require_relative './test_helpers'
7
+
8
+ ## By default Familia::Base has no safe_dump_fields method
9
+ Familia::Base.respond_to?(:safe_dump_fields)
10
+ #=> false
11
+
12
+ ## Implementing models like Customer can define safe dump fields
13
+ Customer.safe_dump_fields
14
+ #=> [:custid, :role, :verified, :updated, :created, :secrets_created, :active]
15
+
16
+ ## Implementing models like Customer can safely dump their fields
17
+ @cust = Customer.new
18
+ @cust.custid = "test@example.com"
19
+ @cust.role = "user"
20
+ @cust.verified = true
21
+ @cust.created = Time.now.to_i
22
+ @cust.updated = Time.now.to_i
23
+ @safe_dump = @cust.safe_dump
24
+ @safe_dump.keys.sort
25
+ #=> [:active, :created, :custid, :role, :secrets_created, :updated, :verified]
26
+
27
+ ## Implementing models like Customer do have other fields
28
+ ## that are by default considered not safe to dump.
29
+ @cust1 = Customer.new
30
+ @cust1.email = "test@example.com"
31
+
32
+ @all_non_safe_fields = @cust1.instance_variables.map { |el|
33
+ el.to_s[1..-1].to_sym # slice off the leading @
34
+ }.sort
35
+
36
+ (@all_non_safe_fields - Customer.safe_dump_fields).sort
37
+ #=> [:custom_domains, :email, :password_reset, :sessions, :stripe_customer, :timeline]
38
+
39
+ ## Implementing models like Customer can rest assured knowing
40
+ ## any other field not in the safe list will not be dumped.
41
+ @cust2 = Customer.new
42
+ @cust2.email = "test@example.com"
43
+ @cust2.custid = "test@example.com"
44
+ @all_safe_fields = @cust2.safe_dump.keys.sort
45
+
46
+ @all_non_safe_fields = @cust2.instance_variables.map { |el|
47
+ el.to_s[1..-1].to_sym # slice off the leading @
48
+ }.sort
49
+ p [1, all_non_safe_fields: @all_non_safe_fields]
50
+ # Check if any of the non-safe fields are in the safe dump
51
+ (@all_non_safe_fields & @all_safe_fields) - [:custid, :role, :verified, :updated, :created, :secrets_created]
52
+ #=> []
53
+
54
+ ## Bone does not have safe_dump feature enabled
55
+ Bone.respond_to?(:safe_dump_fields)
56
+ #=> false
57
+
58
+ ## Bone instances do not have safe_dump method
59
+ @bone = Bone.new(name: "Rex", age: 3)
60
+ @bone.respond_to?(:safe_dump)
61
+ #=> false
62
+
63
+ ## Blone has safe_dump feature enabled
64
+ Blone.respond_to?(:safe_dump_fields)
65
+ #=> true
66
+
67
+ ## Blone has empty safe_dump_fields
68
+ Blone.safe_dump_fields
69
+ #=> []
70
+
71
+ ## Blone instances have safe_dump method
72
+ @blone = Blone.new(name: "Fido", age: 5)
73
+ @blone.respond_to?(:safe_dump)
74
+ #=> true
75
+
76
+ ## Blone safe_dump returns an empty hash
77
+ @blone.safe_dump
78
+ #=> {}
79
+
80
+ # Teardown
81
+ @cust2.destroy!
82
+ @bone = nil
83
+ @blone = nil
@@ -0,0 +1,140 @@
1
+ # Customer Tryouts
2
+ require_relative '../lib/familia'
3
+ require_relative './test_helpers'
4
+
5
+ # Setup
6
+ @now = Time.now.to_f
7
+ @customer = Customer.new
8
+ @customer.custid = "test@example.com"
9
+ @customer.email = "test@example.com"
10
+ @customer.role = "user"
11
+ @customer.key = "abc123"
12
+ @customer.planid = "basic"
13
+ @customer.created = Time.now.to_i
14
+ @customer.updated = Time.now.to_i
15
+
16
+ ## Customer can be saved
17
+ @customer.save
18
+ #=> true
19
+
20
+ ## Customer can be retrieved by identifier
21
+ retrieved_customer = Customer.from_redis("test@example.com")
22
+ retrieved_customer.custid
23
+ #=> "test@example.com"
24
+
25
+ ## Customer fields can be accessed
26
+ @customer.email
27
+ #=> "test@example.com"
28
+
29
+ ## Customer role can be set and retrieved
30
+ @customer.role = "admin"
31
+ @customer.role
32
+ #=> "admin"
33
+
34
+ ## Customer can update fields
35
+ @customer.planid = "premium"
36
+ @customer.save
37
+ ident = @customer.identifier
38
+ Customer.from_redis(ident).planid
39
+ #=> "premium"
40
+
41
+ ## Customer can increment secrets_created counter
42
+ @customer.secrets_created.clear
43
+ @customer.secrets_created.increment
44
+ @customer.secrets_created.value
45
+ #=> '1'
46
+
47
+ ## Customer can add custom domain via add method
48
+ @customer.custom_domains.add(@now, "example.org")
49
+ @customer.custom_domains.members.include?("example.org")
50
+ #=> true
51
+
52
+ ## Customer can retrieve custom domain score via score method
53
+ @customer.custom_domains.score("example.org")
54
+ #=> @now
55
+
56
+ ## Customer can add custom domain via []= method
57
+ @customer.custom_domains["example2.org"] = @now
58
+ @customer.custom_domains.members.include?("example2.org")
59
+ #=> true
60
+
61
+ ## Customer can retrieve custom domain score via []
62
+ @customer.custom_domains["example.org"]
63
+ #=> @now
64
+
65
+
66
+ ## Customer can store timeline
67
+ @customer.timeline["last_login"] = @now
68
+ @customer.timeline["last_login"].to_i.positive?
69
+ #=> true
70
+
71
+ ## Customer can be added to class-level sorted set
72
+ Customer.instances << @customer
73
+ Customer.instances.member?(@customer)
74
+ #=> true
75
+
76
+ ## Customer can be removed from class-level sorted set
77
+ Customer.instances.delete(@customer)
78
+ Customer.instances.member?(@customer)
79
+ #=> false
80
+
81
+ ## Customer can add a session
82
+ @customer.sessions << "session123"
83
+ @customer.sessions.members.include?("session123")
84
+ #=> true
85
+
86
+ ## Customer can set and get password reset information
87
+ @customer.password_reset["token"] = "reset123"
88
+ @customer.password_reset["token"]
89
+ #=> "reset123"
90
+
91
+ ## Customer can be destroyed
92
+ ret = @customer.destroy!
93
+ cust = Customer.from_redis("test@example.com")
94
+ exists = Customer.exists?("test@example.com")
95
+ [ret, cust.nil?, exists]
96
+ #=> [true, true, false]
97
+
98
+ ## Customer.destroy! can be called on an already destroyed object
99
+ @customer.destroy!
100
+ #=> false
101
+
102
+ ## Customer.db returns the correct database number
103
+ Customer.db
104
+ #=> 15
105
+
106
+ ## Customer.db returns the correct database number
107
+ @customer.db
108
+ #=> 15
109
+
110
+ ## @customer.redis.connection returns the correct redis URI
111
+ @customer.redis.connection
112
+ #=> {:host=>"127.0.0.1", :port=>6379, :db=>15, :id=>"redis://127.0.0.1:6379/15", :location=>"127.0.0.1:6379"}
113
+
114
+ ## @customer.redis.uri returns the correct redis URI
115
+ @customer.secrets_created.db
116
+ #=> nil
117
+
118
+ ## @customer.redis.uri returns the correct redis URI
119
+ @customer.secrets_created.redis.connection
120
+ #=> {:host=>"127.0.0.1", :port=>6379, :db=>15, :id=>"redis://127.0.0.1:6379/15", :location=>"127.0.0.1:6379"}
121
+
122
+ ## Customer.url is nil by default
123
+ Customer.uri
124
+ #=> nil
125
+
126
+ ## Customer.destroy! makes only one call to Redis
127
+ RedisCommandCounter.count_commands { @customer.destroy! }
128
+ #=> 1
129
+
130
+ ## Customer.db returns the correct database number
131
+ Customer.instances.db
132
+ #=> nil
133
+
134
+ ## Customer.db returns the correct database number
135
+ Customer.instances.uri.to_s
136
+ #=> 'redis://127.0.0.1/15/'
137
+
138
+
139
+ # Teardown
140
+ Customer.instances.clear
@@ -0,0 +1,86 @@
1
+
2
+ require_relative '../lib/familia'
3
+ require_relative './test_helpers'
4
+
5
+ # Setup
6
+ @now = Time.now.to_i
7
+ @customer = Customer.new
8
+ @customer.custid = "test@example.com"
9
+ @customer.email = "test@example.com"
10
+ @customer.role = "user"
11
+ @customer.key = "abc123"
12
+ @customer.planid = "basic"
13
+ @customer.created = @now
14
+ @customer.updated = @now
15
+ @customer.verified = true
16
+ @customer.reset_requested = false
17
+ @customer.save
18
+ @safe_dump = @customer.safe_dump
19
+
20
+ ## Customer can be safely dumped
21
+ @safe_dump.keys.sort
22
+ #=> [:active, :created, :custid, :role, :secrets_created, :updated, :verified]
23
+
24
+ ## Safe dump includes correct custid
25
+ @safe_dump[:custid]
26
+ #=> "test@example.com"
27
+
28
+ ## Safe dump includes correct role
29
+ @safe_dump[:role]
30
+ #=> "user"
31
+
32
+ ## Safe dump includes correct verified status
33
+ @safe_dump[:verified]
34
+ #=> true
35
+
36
+ ## Safe dump includes correct created timestamp
37
+ @safe_dump[:created]
38
+ #=> @now
39
+
40
+ ## Safe dump includes correct updated timestamp
41
+ @safe_dump[:updated]
42
+ #=> @now
43
+
44
+ ## Safe dump includes correct secrets_created count
45
+ @customer.secrets_created.increment
46
+ @safe_dump = @customer.safe_dump
47
+ @safe_dump[:secrets_created]
48
+ #=> "2"
49
+
50
+ ## Safe dump includes correct active status when verified and not reset requested
51
+ @safe_dump[:active]
52
+ #=> true
53
+
54
+ ## Safe dump includes correct active status when not verified
55
+ @customer.verified = false
56
+ @customer.save
57
+ @safe_dump = @customer.safe_dump
58
+ @safe_dump[:active]
59
+ #=> false
60
+
61
+ ## Safe dump includes correct active status when reset requested
62
+ @customer.verified = true
63
+ @customer.reset_requested = true
64
+ @customer.save
65
+ @safe_dump = @customer.safe_dump
66
+ @safe_dump[:active]
67
+ #=> false
68
+
69
+ ## Safe dump excludes sensitive information (email)
70
+ @safe_dump.has_key?(:email)
71
+ #=> false
72
+
73
+ ## Safe dump excludes sensitive information (key)
74
+ @safe_dump.has_key?(:key)
75
+ #=> false
76
+
77
+ ## Safe dump excludes sensitive information (passphrase)
78
+ @safe_dump.has_key?(:passphrase)
79
+ #=> false
80
+
81
+ ## Safe dump excludes non-specified fields
82
+ @safe_dump.has_key?(:planid)
83
+ #=> false
84
+
85
+ # Teardown
86
+ @customer.destroy!