flipper 0.10.2 → 0.11.0.beta1

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +42 -0
  3. data/.rubocop_todo.yml +188 -0
  4. data/Changelog.md +10 -0
  5. data/Gemfile +6 -3
  6. data/README.md +4 -3
  7. data/Rakefile +13 -13
  8. data/docs/Adapters.md +2 -1
  9. data/docs/DockerCompose.md +6 -3
  10. data/docs/Gates.md +25 -3
  11. data/docs/Optimization.md +27 -5
  12. data/docs/api/README.md +73 -32
  13. data/docs/http/README.md +34 -0
  14. data/docs/read-only/README.md +22 -0
  15. data/examples/percentage_of_actors_group.rb +49 -0
  16. data/flipper.gemspec +15 -15
  17. data/lib/flipper.rb +2 -5
  18. data/lib/flipper/adapter.rb +10 -0
  19. data/lib/flipper/adapters/http.rb +147 -0
  20. data/lib/flipper/adapters/http/client.rb +83 -0
  21. data/lib/flipper/adapters/http/error.rb +14 -0
  22. data/lib/flipper/adapters/instrumented.rb +36 -36
  23. data/lib/flipper/adapters/memoizable.rb +2 -6
  24. data/lib/flipper/adapters/memory.rb +10 -9
  25. data/lib/flipper/adapters/operation_logger.rb +1 -1
  26. data/lib/flipper/adapters/pstore.rb +12 -11
  27. data/lib/flipper/adapters/read_only.rb +6 -6
  28. data/lib/flipper/dsl.rb +1 -3
  29. data/lib/flipper/feature.rb +11 -16
  30. data/lib/flipper/gate.rb +3 -3
  31. data/lib/flipper/gate_values.rb +6 -6
  32. data/lib/flipper/gates/group.rb +2 -2
  33. data/lib/flipper/gates/percentage_of_actors.rb +2 -2
  34. data/lib/flipper/instrumentation/log_subscriber.rb +2 -4
  35. data/lib/flipper/instrumentation/metriks.rb +1 -1
  36. data/lib/flipper/instrumentation/statsd.rb +1 -1
  37. data/lib/flipper/instrumentation/statsd_subscriber.rb +1 -3
  38. data/lib/flipper/instrumentation/subscriber.rb +11 -10
  39. data/lib/flipper/instrumenters/memory.rb +1 -5
  40. data/lib/flipper/instrumenters/noop.rb +1 -1
  41. data/lib/flipper/middleware/memoizer.rb +11 -27
  42. data/lib/flipper/middleware/setup_env.rb +44 -0
  43. data/lib/flipper/registry.rb +8 -10
  44. data/lib/flipper/spec/shared_adapter_specs.rb +45 -67
  45. data/lib/flipper/test/shared_adapter_test.rb +25 -31
  46. data/lib/flipper/typecast.rb +2 -2
  47. data/lib/flipper/types/actor.rb +2 -4
  48. data/lib/flipper/types/group.rb +1 -1
  49. data/lib/flipper/types/percentage.rb +2 -1
  50. data/lib/flipper/version.rb +1 -1
  51. data/spec/fixtures/feature.json +31 -0
  52. data/spec/flipper/adapters/http_spec.rb +148 -0
  53. data/spec/flipper/adapters/instrumented_spec.rb +20 -20
  54. data/spec/flipper/adapters/memoizable_spec.rb +59 -59
  55. data/spec/flipper/adapters/operation_logger_spec.rb +16 -16
  56. data/spec/flipper/adapters/pstore_spec.rb +6 -6
  57. data/spec/flipper/adapters/read_only_spec.rb +28 -34
  58. data/spec/flipper/dsl_spec.rb +73 -84
  59. data/spec/flipper/feature_check_context_spec.rb +27 -27
  60. data/spec/flipper/feature_spec.rb +186 -196
  61. data/spec/flipper/gate_spec.rb +11 -11
  62. data/spec/flipper/gate_values_spec.rb +46 -45
  63. data/spec/flipper/gates/actor_spec.rb +2 -2
  64. data/spec/flipper/gates/boolean_spec.rb +24 -23
  65. data/spec/flipper/gates/group_spec.rb +19 -19
  66. data/spec/flipper/gates/percentage_of_actors_spec.rb +10 -10
  67. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  68. data/spec/flipper/instrumentation/log_subscriber_spec.rb +20 -20
  69. data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +20 -20
  70. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +11 -11
  71. data/spec/flipper/instrumenters/memory_spec.rb +5 -5
  72. data/spec/flipper/instrumenters/noop_spec.rb +6 -6
  73. data/spec/flipper/middleware/memoizer_spec.rb +83 -100
  74. data/spec/flipper/middleware/setup_env_spec.rb +76 -0
  75. data/spec/flipper/registry_spec.rb +35 -39
  76. data/spec/flipper/typecast_spec.rb +18 -18
  77. data/spec/flipper/types/actor_spec.rb +30 -29
  78. data/spec/flipper/types/boolean_spec.rb +8 -8
  79. data/spec/flipper/types/group_spec.rb +28 -28
  80. data/spec/flipper/types/percentage_spec.rb +14 -14
  81. data/spec/flipper_spec.rb +61 -54
  82. data/spec/helper.rb +26 -21
  83. data/spec/integration_spec.rb +121 -113
  84. data/spec/support/fake_udp_socket.rb +1 -1
  85. data/spec/support/spec_helpers.rb +32 -4
  86. data/test/adapters/pstore_test.rb +3 -3
  87. data/test/test_helper.rb +1 -1
  88. metadata +20 -5
@@ -2,23 +2,23 @@ require 'helper'
2
2
  require 'flipper/types/boolean'
3
3
 
4
4
  RSpec.describe Flipper::Types::Boolean do
5
- it "defaults value to true" do
6
- boolean = Flipper::Types::Boolean.new
5
+ it 'defaults value to true' do
6
+ boolean = described_class.new
7
7
  expect(boolean.value).to be(true)
8
8
  end
9
9
 
10
- it "allows overriding default value" do
11
- boolean = Flipper::Types::Boolean.new(false)
10
+ it 'allows overriding default value' do
11
+ boolean = described_class.new(false)
12
12
  expect(boolean.value).to be(false)
13
13
  end
14
14
 
15
- it "returns true for nil value" do
16
- boolean = Flipper::Types::Boolean.new(nil)
15
+ it 'returns true for nil value' do
16
+ boolean = described_class.new(nil)
17
17
  expect(boolean.value).to be(true)
18
18
  end
19
19
 
20
- it "typecasts value" do
21
- boolean = Flipper::Types::Boolean.new(1)
20
+ it 'typecasts value' do
21
+ boolean = described_class.new(1)
22
22
  expect(boolean.value).to be(true)
23
23
  end
24
24
  end
@@ -2,85 +2,85 @@ require 'helper'
2
2
  require 'flipper/types/group'
3
3
 
4
4
  RSpec.describe Flipper::Types::Group do
5
- let(:fake_context) { double("FeatureCheckContext") }
5
+ let(:fake_context) { double('FeatureCheckContext') }
6
6
 
7
7
  subject do
8
- Flipper.register(:admins) { |actor| actor.admin? }
8
+ Flipper.register(:admins, &:admin?)
9
9
  end
10
10
 
11
- describe ".wrap" do
12
- context "with group instance" do
13
- it "returns group instance" do
11
+ describe '.wrap' do
12
+ context 'with group instance' do
13
+ it 'returns group instance' do
14
14
  expect(described_class.wrap(subject)).to eq(subject)
15
15
  end
16
16
  end
17
17
 
18
- context "with Symbol group name" do
19
- it "returns group instance" do
18
+ context 'with Symbol group name' do
19
+ it 'returns group instance' do
20
20
  expect(described_class.wrap(subject.name)).to eq(subject)
21
21
  end
22
22
  end
23
23
 
24
- context "with String group name" do
25
- it "returns group instance" do
24
+ context 'with String group name' do
25
+ it 'returns group instance' do
26
26
  expect(described_class.wrap(subject.name.to_s)).to eq(subject)
27
27
  end
28
28
  end
29
29
  end
30
30
 
31
- it "initializes with name" do
32
- group = Flipper::Types::Group.new(:admins)
33
- expect(group).to be_instance_of(Flipper::Types::Group)
31
+ it 'initializes with name' do
32
+ group = described_class.new(:admins)
33
+ expect(group).to be_instance_of(described_class)
34
34
  end
35
35
 
36
- describe "#name" do
37
- it "returns name" do
36
+ describe '#name' do
37
+ it 'returns name' do
38
38
  expect(subject.name).to eq(:admins)
39
39
  end
40
40
  end
41
41
 
42
- describe "#match?" do
43
- let(:admin_actor) { double('Actor', :admin? => true) }
44
- let(:non_admin_actor) { double('Actor', :admin? => false) }
42
+ describe '#match?' do
43
+ let(:admin_actor) { double('Actor', admin?: true) }
44
+ let(:non_admin_actor) { double('Actor', admin?: false) }
45
45
 
46
- it "returns true if block matches" do
46
+ it 'returns true if block matches' do
47
47
  expect(subject.match?(admin_actor, fake_context)).to eq(true)
48
48
  end
49
49
 
50
- it "returns false if block does not match" do
50
+ it 'returns false if block does not match' do
51
51
  expect(subject.match?(non_admin_actor, fake_context)).to eq(false)
52
52
  end
53
53
 
54
- it "returns true for truthy block results" do
55
- group = Flipper::Types::Group.new(:examples) do |actor|
54
+ it 'returns true for truthy block results' do
55
+ group = described_class.new(:examples) do |actor|
56
56
  actor.email =~ /@example\.com/
57
57
  end
58
- expect(group.match?(double('Actor', :email => "foo@example.com"), fake_context)).to be_truthy
58
+ expect(group.match?(double('Actor', email: 'foo@example.com'), fake_context)).to be_truthy
59
59
  end
60
60
 
61
- it "returns false for falsey block results" do
62
- group = Flipper::Types::Group.new(:examples) do |actor|
61
+ it 'returns false for falsey block results' do
62
+ group = described_class.new(:examples) do |_actor|
63
63
  nil
64
64
  end
65
65
  expect(group.match?(double('Actor'), fake_context)).to be_falsey
66
66
  end
67
67
 
68
- it "can yield without context as block argument" do
68
+ it 'can yield without context as block argument' do
69
69
  context = Flipper::FeatureCheckContext.new(
70
70
  feature_name: :my_feature,
71
71
  values: Flipper::GateValues.new({}),
72
- thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1)),
72
+ thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
73
73
  )
74
74
  group = Flipper.register(:group_with_context) { |actor| actor }
75
75
  yielded_actor = group.match?(admin_actor, context)
76
76
  expect(yielded_actor).to be(admin_actor)
77
77
  end
78
78
 
79
- it "can yield with context as block argument" do
79
+ it 'can yield with context as block argument' do
80
80
  context = Flipper::FeatureCheckContext.new(
81
81
  feature_name: :my_feature,
82
82
  values: Flipper::GateValues.new({}),
83
- thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1)),
83
+ thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
84
84
  )
85
85
  group = Flipper.register(:group_with_context) { |actor, context| [actor, context] }
86
86
  yielded_actor, yielded_context = group.match?(admin_actor, context)
@@ -2,45 +2,45 @@ require 'helper'
2
2
  require 'flipper/types/percentage_of_actors'
3
3
 
4
4
  RSpec.describe Flipper::Types::Percentage do
5
- subject {
5
+ subject do
6
6
  described_class.new(5)
7
- }
7
+ end
8
8
  it_should_behave_like 'a percentage'
9
9
 
10
- describe ".wrap" do
11
- context "with percentage instance" do
12
- it "returns percentage instance" do
10
+ describe '.wrap' do
11
+ context 'with percentage instance' do
12
+ it 'returns percentage instance' do
13
13
  expect(described_class.wrap(subject)).to eq(subject)
14
14
  end
15
15
  end
16
16
 
17
- context "with Integer" do
18
- it "returns percentage instance" do
17
+ context 'with Integer' do
18
+ it 'returns percentage instance' do
19
19
  expect(described_class.wrap(subject.value)).to eq(subject)
20
20
  end
21
21
  end
22
22
 
23
- context "with String" do
24
- it "returns percentage instance" do
23
+ context 'with String' do
24
+ it 'returns percentage instance' do
25
25
  expect(described_class.wrap(subject.value.to_s)).to eq(subject)
26
26
  end
27
27
  end
28
28
  end
29
29
 
30
- describe "#eql?" do
31
- it "returns true for same class and value" do
30
+ describe '#eql?' do
31
+ it 'returns true for same class and value' do
32
32
  expect(subject.eql?(described_class.new(subject.value))).to eq(true)
33
33
  end
34
34
 
35
- it "returns false for different value" do
35
+ it 'returns false for different value' do
36
36
  expect(subject.eql?(described_class.new(subject.value + 1))).to eq(false)
37
37
  end
38
38
 
39
- it "returns false for different class" do
39
+ it 'returns false for different class' do
40
40
  expect(subject.eql?(Object.new)).to eq(false)
41
41
  end
42
42
 
43
- it "is aliased to ==" do
43
+ it 'is aliased to ==' do
44
44
  expect((subject == described_class.new(subject.value))).to eq(true)
45
45
  end
46
46
  end
@@ -1,109 +1,116 @@
1
1
  require 'helper'
2
2
 
3
3
  RSpec.describe Flipper do
4
- describe ".new" do
5
- it "returns new instance of dsl" do
6
- instance = Flipper.new(double('Adapter'))
4
+ describe '.new' do
5
+ it 'returns new instance of dsl' do
6
+ instance = described_class.new(double('Adapter'))
7
7
  expect(instance).to be_instance_of(Flipper::DSL)
8
8
  end
9
9
  end
10
10
 
11
- describe ".group_exists" do
12
- it "returns true if the group is already created" do
13
- group = Flipper.register('admins') { |actor| actor.admin? }
14
- expect(Flipper.group_exists?(:admins)).to eq(true)
11
+ describe '.group_exists' do
12
+ it 'returns true if the group is already created' do
13
+ group = described_class.register('admins', &:admin?)
14
+ expect(described_class.group_exists?(:admins)).to eq(true)
15
15
  end
16
16
 
17
- it "returns false when the group is not yet registered" do
18
- expect(Flipper.group_exists?(:non_existing)).to eq(false)
17
+ it 'returns false when the group is not yet registered' do
18
+ expect(described_class.group_exists?(:non_existing)).to eq(false)
19
19
  end
20
20
  end
21
21
 
22
- describe ".groups_registry" do
23
- it "returns a registry instance" do
24
- expect(Flipper.groups_registry).to be_instance_of(Flipper::Registry)
22
+ describe '.groups_registry' do
23
+ it 'returns a registry instance' do
24
+ expect(described_class.groups_registry).to be_instance_of(Flipper::Registry)
25
25
  end
26
26
  end
27
27
 
28
- describe ".groups_registry=" do
29
- it "sets groups_registry registry" do
28
+ describe '.groups_registry=' do
29
+ it 'sets groups_registry registry' do
30
30
  registry = Flipper::Registry.new
31
- Flipper.groups_registry = registry
32
- expect(Flipper.instance_variable_get("@groups_registry")).to eq(registry)
31
+ described_class.groups_registry = registry
32
+ expect(described_class.instance_variable_get('@groups_registry')).to eq(registry)
33
33
  end
34
34
  end
35
35
 
36
- describe ".register" do
37
- it "adds a group to the group_registry" do
36
+ describe '.register' do
37
+ it 'adds a group to the group_registry' do
38
38
  registry = Flipper::Registry.new
39
- Flipper.groups_registry = registry
40
- group = Flipper.register(:admins) { |actor| actor.admin? }
39
+ described_class.groups_registry = registry
40
+ group = described_class.register(:admins, &:admin?)
41
41
  expect(registry.get(:admins)).to eq(group)
42
42
  end
43
43
 
44
- it "adds a group to the group_registry for string name" do
44
+ it 'adds a group to the group_registry for string name' do
45
45
  registry = Flipper::Registry.new
46
- Flipper.groups_registry = registry
47
- group = Flipper.register('admins') { |actor| actor.admin? }
46
+ described_class.groups_registry = registry
47
+ group = described_class.register('admins', &:admin?)
48
48
  expect(registry.get(:admins)).to eq(group)
49
49
  end
50
50
 
51
- it "raises exception if group already registered" do
52
- Flipper.register(:admins) { }
51
+ it 'raises exception if group already registered' do
52
+ described_class.register(:admins) {}
53
53
 
54
- expect {
55
- Flipper.register(:admins) { }
56
- }.to raise_error(Flipper::DuplicateGroup, "Group :admins has already been registered")
54
+ expect do
55
+ described_class.register(:admins) {}
56
+ end.to raise_error(Flipper::DuplicateGroup, 'Group :admins has already been registered')
57
57
  end
58
58
  end
59
59
 
60
- describe ".unregister_groups" do
61
- it "clear group registry" do
62
- expect(Flipper.groups_registry).to receive(:clear)
63
- Flipper.unregister_groups
60
+ describe '.unregister_groups' do
61
+ it 'clear group registry' do
62
+ expect(described_class.groups_registry).to receive(:clear)
63
+ described_class.unregister_groups
64
64
  end
65
65
  end
66
66
 
67
- describe ".group" do
68
- context "for registered group" do
67
+ describe '.group' do
68
+ context 'for registered group' do
69
69
  before do
70
- @group = Flipper.register(:admins) { }
70
+ @group = described_class.register(:admins) {}
71
71
  end
72
72
 
73
- it "returns group" do
74
- expect(Flipper.group(:admins)).to eq(@group)
73
+ it 'returns group' do
74
+ expect(described_class.group(:admins)).to eq(@group)
75
75
  end
76
76
 
77
- it "returns group with string key" do
78
- expect(Flipper.group('admins')).to eq(@group)
77
+ it 'returns group with string key' do
78
+ expect(described_class.group('admins')).to eq(@group)
79
79
  end
80
80
  end
81
81
 
82
- context "for unregistered group" do
83
- it "raises group not registered error" do
84
- expect {
85
- Flipper.group(:cats)
86
- }.to raise_error(Flipper::GroupNotRegistered, 'Group :cats has not been registered')
82
+ context 'for unregistered group' do
83
+ before do
84
+ @group = described_class.group(:cats)
85
+ end
86
+
87
+ it 'returns group' do
88
+ expect(@group).to be_instance_of(Flipper::Types::Group)
89
+ expect(@group.name).to eq(:cats)
90
+ end
91
+
92
+ it 'does not add group to registry' do
93
+ expect(described_class.group_exists?(@group.name)).to be(false)
87
94
  end
88
95
  end
89
96
  end
90
97
 
91
- describe ".groups" do
92
- it "returns array of group instances" do
93
- admins = Flipper.register(:admins) { |actor| actor.admin? }
94
- preview_features = Flipper.register(:preview_features) { |actor| actor.preview_features? }
95
- expect(Flipper.groups).to eq(Set[
98
+ describe '.groups' do
99
+ it 'returns array of group instances' do
100
+ admins = described_class.register(:admins, &:admin?)
101
+ preview_features = described_class.register(:preview_features, &:preview_features?)
102
+ expect(described_class.groups).to eq(Set[
96
103
  admins,
97
104
  preview_features,
98
105
  ])
99
106
  end
100
107
  end
101
108
 
102
- describe ".group_names" do
103
- it "returns array of group names" do
104
- Flipper.register(:admins) { |actor| actor.admin? }
105
- Flipper.register(:preview_features) { |actor| actor.preview_features? }
106
- expect(Flipper.group_names).to eq(Set[
109
+ describe '.group_names' do
110
+ it 'returns array of group names' do
111
+ described_class.register(:admins, &:admin?)
112
+ described_class.register(:preview_features, &:preview_features?)
113
+ expect(described_class.group_names).to eq(Set[
107
114
  :admins,
108
115
  :preview_features,
109
116
  ])
@@ -1,4 +1,4 @@
1
- $:.unshift(File.expand_path('../../lib', __FILE__))
1
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
2
2
 
3
3
  require 'pp'
4
4
  require 'pathname'
@@ -9,11 +9,14 @@ require 'bundler'
9
9
 
10
10
  Bundler.setup(:default)
11
11
 
12
+ require 'webmock/rspec'
13
+ WebMock.disable_net_connect!(allow_localhost: true)
14
+
12
15
  require 'flipper'
13
16
  require 'flipper-ui'
14
17
  require 'flipper-api'
15
18
 
16
- Dir[FlipperRoot.join("spec/support/**/*.rb")].each { |f| require f }
19
+ Dir[FlipperRoot.join('spec/support/**/*.rb')].each { |f| require f }
17
20
 
18
21
  RSpec.configure do |config|
19
22
  config.before(:example) do
@@ -27,64 +30,66 @@ RSpec.configure do |config|
27
30
  end
28
31
 
29
32
  RSpec.shared_examples_for 'a percentage' do
30
- it "initializes with value" do
33
+ it 'initializes with value' do
31
34
  percentage = described_class.new(12)
32
35
  expect(percentage).to be_instance_of(described_class)
33
36
  end
34
37
 
35
- it "converts string values to integers when initializing" do
38
+ it 'converts string values to integers when initializing' do
36
39
  percentage = described_class.new('15')
37
40
  expect(percentage.value).to eq(15)
38
41
  end
39
42
 
40
- it "has a value" do
43
+ it 'has a value' do
41
44
  percentage = described_class.new(19)
42
45
  expect(percentage.value).to eq(19)
43
46
  end
44
47
 
45
- it "raises exception for value higher than 100" do
46
- expect {
48
+ it 'raises exception for value higher than 100' do
49
+ expect do
47
50
  described_class.new(101)
48
- }.to raise_error(ArgumentError, "value must be a positive number less than or equal to 100, but was 101")
51
+ end.to raise_error(ArgumentError,
52
+ 'value must be a positive number less than or equal to 100, but was 101')
49
53
  end
50
54
 
51
- it "raises exception for negative value" do
52
- expect {
55
+ it 'raises exception for negative value' do
56
+ expect do
53
57
  described_class.new(-1)
54
- }.to raise_error(ArgumentError, "value must be a positive number less than or equal to 100, but was -1")
58
+ end.to raise_error(ArgumentError,
59
+ 'value must be a positive number less than or equal to 100, but was -1')
55
60
  end
56
61
  end
57
62
 
58
63
  RSpec.shared_examples_for 'a DSL feature' do
59
- it "returns instance of feature" do
64
+ it 'returns instance of feature' do
60
65
  expect(feature).to be_instance_of(Flipper::Feature)
61
66
  end
62
67
 
63
- it "sets name" do
68
+ it 'sets name' do
64
69
  expect(feature.name).to eq(:stats)
65
70
  end
66
71
 
67
- it "sets adapter" do
72
+ it 'sets adapter' do
68
73
  expect(feature.adapter.name).to eq(dsl.adapter.name)
69
74
  end
70
75
 
71
- it "sets instrumenter" do
76
+ it 'sets instrumenter' do
72
77
  expect(feature.instrumenter).to eq(dsl.instrumenter)
73
78
  end
74
79
 
75
- it "memoizes the feature" do
80
+ it 'memoizes the feature' do
76
81
  expect(dsl.send(method_name, :stats)).to equal(feature)
77
82
  end
78
83
 
79
- it "raises argument error if not string or symbol" do
80
- expect {
84
+ it 'raises argument error if not string or symbol' do
85
+ expect do
81
86
  dsl.send(method_name, Object.new)
82
- }.to raise_error(ArgumentError, /must be a String or Symbol/)
87
+ end.to raise_error(ArgumentError, /must be a String or Symbol/)
83
88
  end
84
89
  end
85
90
 
86
- RSpec.shared_examples_for "a DSL boolean method" do
87
- it "returns boolean with value set" do
91
+ RSpec.shared_examples_for 'a DSL boolean method' do
92
+ it 'returns boolean with value set' do
88
93
  result = subject.send(method_name, true)
89
94
  expect(result).to be_instance_of(Flipper::Types::Boolean)
90
95
  expect(result.value).to be(true)