flipflop 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/.travis.yml +51 -0
- data/Gemfile +20 -0
- data/LICENSE +22 -0
- data/README.md +261 -0
- data/Rakefile +16 -0
- data/app/assets/stylesheets/flipflop.scss +109 -0
- data/app/controllers/concerns/flipflop/environment_filters.rb +5 -0
- data/app/controllers/flipflop/features_controller.rb +59 -0
- data/app/controllers/flipflop/strategies_controller.rb +30 -0
- data/app/models/flipflop/feature.rb +3 -0
- data/app/views/flipflop/features/index.html.erb +60 -0
- data/app/views/layouts/flipflop.html.erb +1 -0
- data/config/routes.rb +5 -0
- data/flipflop.gemspec +23 -0
- data/lib/flipflop/configurable.rb +27 -0
- data/lib/flipflop/engine.rb +58 -0
- data/lib/flipflop/facade.rb +23 -0
- data/lib/flipflop/feature_cache.rb +64 -0
- data/lib/flipflop/feature_definition.rb +15 -0
- data/lib/flipflop/feature_set.rb +99 -0
- data/lib/flipflop/strategies/abstract_strategy.rb +103 -0
- data/lib/flipflop/strategies/active_record_strategy.rb +43 -0
- data/lib/flipflop/strategies/cookie_strategy.rb +44 -0
- data/lib/flipflop/strategies/default_strategy.rb +15 -0
- data/lib/flipflop/strategies/lambda_strategy.rb +25 -0
- data/lib/flipflop/strategies/query_string_strategy.rb +17 -0
- data/lib/flipflop/strategies/session_strategy.rb +29 -0
- data/lib/flipflop/strategies/test_strategy.rb +40 -0
- data/lib/flipflop/version.rb +3 -0
- data/lib/flipflop.rb +26 -0
- data/lib/generators/flipflop/features/USAGE +8 -0
- data/lib/generators/flipflop/features/features_generator.rb +7 -0
- data/lib/generators/flipflop/features/templates/features.rb +21 -0
- data/lib/generators/flipflop/install/install_generator.rb +21 -0
- data/lib/generators/flipflop/migration/USAGE +5 -0
- data/lib/generators/flipflop/migration/migration_generator.rb +23 -0
- data/lib/generators/flipflop/migration/templates/create_features.rb +10 -0
- data/lib/generators/flipflop/routes/USAGE +7 -0
- data/lib/generators/flipflop/routes/routes_generator.rb +5 -0
- data/test/integration/app_test.rb +32 -0
- data/test/integration/dashboard_test.rb +162 -0
- data/test/test_helper.rb +96 -0
- data/test/unit/configurable_test.rb +104 -0
- data/test/unit/feature_cache_test.rb +142 -0
- data/test/unit/feature_definition_test.rb +42 -0
- data/test/unit/feature_set_test.rb +136 -0
- data/test/unit/flipflop_test.rb +99 -0
- data/test/unit/strategies/abstract_strategy_request_test.rb +42 -0
- data/test/unit/strategies/abstract_strategy_test.rb +124 -0
- data/test/unit/strategies/active_record_strategy_test.rb +157 -0
- data/test/unit/strategies/cookie_strategy_test.rb +126 -0
- data/test/unit/strategies/default_strategy_test.rb +44 -0
- data/test/unit/strategies/lambda_strategy_test.rb +137 -0
- data/test/unit/strategies/query_string_strategy_test.rb +70 -0
- data/test/unit/strategies/session_strategy_test.rb +101 -0
- data/test/unit/strategies/test_strategy_test.rb +76 -0
- metadata +134 -0
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            Flipflop.configure do
         | 
| 2 | 
            +
              # Strategies will be used in the order listed here.
         | 
| 3 | 
            +
              strategy :cookie
         | 
| 4 | 
            +
              strategy :active_record
         | 
| 5 | 
            +
              strategy :default
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              # Other strategies:
         | 
| 8 | 
            +
              #
         | 
| 9 | 
            +
              # strategy :session
         | 
| 10 | 
            +
              # strategy :query_string
         | 
| 11 | 
            +
              #
         | 
| 12 | 
            +
              # strategy :my_strategy do |feature|
         | 
| 13 | 
            +
              #   # ... your custom code here; return true/false/nil.
         | 
| 14 | 
            +
              # end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              # Declare your features, e.g:
         | 
| 17 | 
            +
              #
         | 
| 18 | 
            +
              # feature :world_domination,
         | 
| 19 | 
            +
              #   default: true,
         | 
| 20 | 
            +
              #   description: "Take over the world."
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            require "generators/flipflop/features/features_generator"
         | 
| 2 | 
            +
            require "generators/flipflop/migration/migration_generator"
         | 
| 3 | 
            +
            require "generators/flipflop/routes/routes_generator"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class Flipflop::InstallGenerator < Rails::Generators::Base
         | 
| 6 | 
            +
              def invoke_generators
         | 
| 7 | 
            +
                Flipflop::FeaturesGenerator.new([], options).invoke_all
         | 
| 8 | 
            +
                Flipflop::MigrationGenerator.new([], options).invoke_all
         | 
| 9 | 
            +
                Flipflop::RoutesGenerator.new([], options).invoke_all
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def configure_dashboard
         | 
| 13 | 
            +
                environment <<-CONFIG
         | 
| 14 | 
            +
            # Replace this with your own 'before_action' filter in ApplicationController
         | 
| 15 | 
            +
                # to implement access control for the Flipflop dashboard, or provide a
         | 
| 16 | 
            +
                # filter as lambda directly.
         | 
| 17 | 
            +
                config.flipflop.dashboard_access_filter = :require_development
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            CONFIG
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            require "rails"
         | 
| 2 | 
            +
            require "rails/generators/migration"
         | 
| 3 | 
            +
            require "rails/generators/active_record"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class Flipflop::MigrationGenerator < Rails::Generators::Base
         | 
| 6 | 
            +
              include Rails::Generators::Migration
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              source_root File.expand_path("../templates", __FILE__)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def create_migration_file
         | 
| 11 | 
            +
                migration_template("create_features.rb", "db/migrate/create_features.rb")
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              # Stubbed in railties/lib/rails/generators/migration.rb
         | 
| 15 | 
            +
              #
         | 
| 16 | 
            +
              # This implementation is a simplified version of:
         | 
| 17 | 
            +
              #   activerecord/lib/rails/generators/active_record/migration.rb
         | 
| 18 | 
            +
              #
         | 
| 19 | 
            +
              # See: http://www.ruby-forum.com/topic/203205
         | 
| 20 | 
            +
              def self.next_migration_number(dirname)
         | 
| 21 | 
            +
                Time.now.utc.strftime("%Y%m%d%H%M%S")
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            class CreateFeatures < ActiveRecord::Migration<%= Rails.version >= "5" ? "[#{ActiveRecord::Migration.current_version}]" : "" %>
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                create_table :features do |t|
         | 
| 4 | 
            +
                  t.string :key, null: false
         | 
| 5 | 
            +
                  t.boolean :enabled, null: false, default: false
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  t.timestamps null: false
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            require File.expand_path("../../test_helper", __FILE__)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Flipflop do
         | 
| 4 | 
            +
              before do
         | 
| 5 | 
            +
                @app = TestApp.new
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              subject do
         | 
| 9 | 
            +
                @app
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              describe "middleware" do
         | 
| 13 | 
            +
                it "should include cache middleware" do
         | 
| 14 | 
            +
                  middlewares = Rails.application.middleware.map(&:klass)
         | 
| 15 | 
            +
                  assert_includes middlewares, Flipflop::FeatureCache::Middleware
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              describe "module" do
         | 
| 20 | 
            +
                before do
         | 
| 21 | 
            +
                  Flipflop::FeatureSet.current.instance_variable_set(:@features, {})
         | 
| 22 | 
            +
                  Module.new do
         | 
| 23 | 
            +
                    extend Flipflop::Configurable
         | 
| 24 | 
            +
                    feature :world_domination
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                it "should allow querying for features" do
         | 
| 29 | 
            +
                  assert_equal false, Flipflop.world_domination?
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,162 @@ | |
| 1 | 
            +
            require File.expand_path("../../test_helper", __FILE__)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "capybara/dsl"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe Flipflop do
         | 
| 6 | 
            +
              include Capybara::DSL
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              before do
         | 
| 9 | 
            +
                @app = TestApp.new
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              subject do
         | 
| 13 | 
            +
                @app
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              describe "outside development and test" do
         | 
| 17 | 
            +
                before do
         | 
| 18 | 
            +
                  Rails.env.stub(:test?, false) do
         | 
| 19 | 
            +
                    visit "/flipflop"
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                it "should be forbidden" do
         | 
| 24 | 
            +
                  assert_equal 403, page.status_code
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              describe "without features" do
         | 
| 29 | 
            +
                before do
         | 
| 30 | 
            +
                  visit "/flipflop"
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                it "should show feature table with header" do
         | 
| 34 | 
            +
                  assert_equal ["Cookie", "Active record", "Default"],
         | 
| 35 | 
            +
                    all("thead th").map(&:text)[3..-1]
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                it "should show no features" do
         | 
| 39 | 
            +
                  assert all("tbody tr").empty?
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              describe "with features" do
         | 
| 44 | 
            +
                before do
         | 
| 45 | 
            +
                  Flipflop::FeatureSet.current.instance_variable_set(:@features, {})
         | 
| 46 | 
            +
                  Module.new do
         | 
| 47 | 
            +
                    extend Flipflop::Configurable
         | 
| 48 | 
            +
                    feature :world_domination, description: "Try and take over the world!"
         | 
| 49 | 
            +
                    feature :shiny_things, default: true
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  Capybara.current_session.driver.browser.clear_cookies
         | 
| 53 | 
            +
                  Flipflop::Feature.delete_all
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  visit "/flipflop"
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                it "should show feature rows" do
         | 
| 59 | 
            +
                  assert_equal ["World domination", "Shiny things"],
         | 
| 60 | 
            +
                    all("tr[data-feature] td.name").map(&:text)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                it "should show feature descriptions" do
         | 
| 64 | 
            +
                  assert_equal ["Try and take over the world!", "Shiny things."],
         | 
| 65 | 
            +
                    all("tr[data-feature] td.description").map(&:text)
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                describe "with cookie strategy" do
         | 
| 69 | 
            +
                  it "should enable feature" do
         | 
| 70 | 
            +
                    within("tr[data-feature=world-domination] td[data-strategy=cookie]") do
         | 
| 71 | 
            +
                      click_on "on"
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    within("tr[data-feature=world-domination]") do
         | 
| 75 | 
            +
                      assert_equal "on", first("td.status").text
         | 
| 76 | 
            +
                      assert_equal "on", first("td[data-strategy=cookie] input.active[type=submit]").value
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  it "should disable feature" do
         | 
| 81 | 
            +
                    within("tr[data-feature=world-domination] td[data-strategy=cookie]") do
         | 
| 82 | 
            +
                      click_on "off"
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    within("tr[data-feature=world-domination]") do
         | 
| 86 | 
            +
                      assert_equal "off", first("td.status").text
         | 
| 87 | 
            +
                      assert_equal "off", first("td[data-strategy=cookie] input.active[type=submit]").value
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  it "should enable and clear feature" do
         | 
| 92 | 
            +
                    within("tr[data-feature=world-domination] td[data-strategy=cookie]") do
         | 
| 93 | 
            +
                      click_on "on"
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    within("tr[data-feature=world-domination] td[data-strategy=cookie]") do
         | 
| 97 | 
            +
                      click_on "clear"
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    within("tr[data-feature=world-domination]") do
         | 
| 101 | 
            +
                      assert_equal "off", first("td.status").text
         | 
| 102 | 
            +
                      refute has_selector?("td[data-strategy=cookie] input.active[type=submit]")
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                describe "with active record strategy" do
         | 
| 108 | 
            +
                  it "should enable feature" do
         | 
| 109 | 
            +
                    within("tr[data-feature=world-domination] td[data-strategy=active-record]") do
         | 
| 110 | 
            +
                      click_on "on"
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                    within("tr[data-feature=world-domination]") do
         | 
| 114 | 
            +
                      assert_equal "on", first("td.status").text
         | 
| 115 | 
            +
                      assert_equal "on", first("td[data-strategy=active-record] input.active[type=submit]").value
         | 
| 116 | 
            +
                    end
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  it "should disable feature" do
         | 
| 120 | 
            +
                    within("tr[data-feature=world-domination] td[data-strategy=active-record]") do
         | 
| 121 | 
            +
                      click_on "off"
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                    within("tr[data-feature=world-domination]") do
         | 
| 125 | 
            +
                      assert_equal "off", first("td.status").text
         | 
| 126 | 
            +
                      assert_equal "off", first("td[data-strategy=active-record] input.active[type=submit]").value
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  it "should enable and clear feature" do
         | 
| 131 | 
            +
                    within("tr[data-feature=world-domination] td[data-strategy=active-record]") do
         | 
| 132 | 
            +
                      click_on "on"
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    within("tr[data-feature=world-domination] td[data-strategy=active-record]") do
         | 
| 136 | 
            +
                      click_on "clear"
         | 
| 137 | 
            +
                    end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                    within("tr[data-feature=world-domination]") do
         | 
| 140 | 
            +
                      assert_equal "off", first("td.status").text
         | 
| 141 | 
            +
                      refute has_selector?("td[data-strategy=active-record] input.active[type=submit]")
         | 
| 142 | 
            +
                    end
         | 
| 143 | 
            +
                  end
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
              end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
              describe "with hidden strategy" do
         | 
| 148 | 
            +
                before do
         | 
| 149 | 
            +
                  Flipflop::FeatureSet.current.instance_variable_set(:@strategies, {})
         | 
| 150 | 
            +
                  Module.new do
         | 
| 151 | 
            +
                    extend Flipflop::Configurable
         | 
| 152 | 
            +
                    strategy :query_string, hidden: true
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                  visit "/flipflop"
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                it "should not show hidden strategy" do
         | 
| 159 | 
            +
                  assert_equal [], all("thead th").map(&:text)[3..-1]
         | 
| 160 | 
            +
                end
         | 
| 161 | 
            +
              end
         | 
| 162 | 
            +
            end
         | 
    
        data/test/test_helper.rb
    ADDED
    
    | @@ -0,0 +1,96 @@ | |
| 1 | 
            +
            require "bundler/setup"
         | 
| 2 | 
            +
            require "flipflop"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            gem "minitest"
         | 
| 5 | 
            +
            require "minitest/autorun"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            require "action_controller"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # Who is setting this to true? :o
         | 
| 10 | 
            +
            $VERBOSE = false
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            def create_request
         | 
| 13 | 
            +
              env = Rack::MockRequest.env_for("/example")
         | 
| 14 | 
            +
              request = ActionDispatch::TestRequest.new(env)
         | 
| 15 | 
            +
              request.host = "example.com"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              class << request
         | 
| 18 | 
            +
                def cookie_jar
         | 
| 19 | 
            +
                  @cookie_jar ||= begin
         | 
| 20 | 
            +
                    method = ActionDispatch::Cookies::CookieJar.method(:build)
         | 
| 21 | 
            +
                    if method.arity == 2 # Rails 5.0
         | 
| 22 | 
            +
                      method.call(self, {})
         | 
| 23 | 
            +
                    else
         | 
| 24 | 
            +
                      method.call(self)
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              request
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            def reload_constant(name)
         | 
| 34 | 
            +
              ActiveSupport::Dependencies.remove_constant(name.to_s)
         | 
| 35 | 
            +
              path = ActiveSupport::Dependencies.search_for_file(name.to_s.underscore).sub!(/\.rb\z/, "")
         | 
| 36 | 
            +
              ActiveSupport::Dependencies.loaded.delete(path)
         | 
| 37 | 
            +
              Object.const_get(name)
         | 
| 38 | 
            +
            end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            class TestApp
         | 
| 41 | 
            +
              class << self
         | 
| 42 | 
            +
                def new
         | 
| 43 | 
            +
                  ActiveSupport::Dependencies.remove_constant("App")
         | 
| 44 | 
            +
                  super.tap do |current|
         | 
| 45 | 
            +
                    current.create!
         | 
| 46 | 
            +
                    current.load!
         | 
| 47 | 
            +
                    current.migrate!
         | 
| 48 | 
            +
                    reload_constant("Flipflop::Feature")
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def create!
         | 
| 54 | 
            +
                require "rails/generators"
         | 
| 55 | 
            +
                require "rails/generators/rails/app/app_generator"
         | 
| 56 | 
            +
                require "generators/flipflop/install/install_generator"
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                FileUtils.rm_rf(File.expand_path("../../tmp/app", __FILE__))
         | 
| 59 | 
            +
                Dir.chdir(File.expand_path("../..", __FILE__))
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                Rails::Generators::AppGenerator.new(["tmp/app"],
         | 
| 62 | 
            +
                  quiet: true,
         | 
| 63 | 
            +
                  skip_active_job: true,
         | 
| 64 | 
            +
                  skip_bundle: true,
         | 
| 65 | 
            +
                  skip_gemfile: true,
         | 
| 66 | 
            +
                  skip_git: true,
         | 
| 67 | 
            +
                  skip_javascript: true,
         | 
| 68 | 
            +
                  skip_keeps: true,
         | 
| 69 | 
            +
                  skip_spring: true,
         | 
| 70 | 
            +
                  skip_test_unit: true,
         | 
| 71 | 
            +
                  skip_turbolinks: true,
         | 
| 72 | 
            +
                ).invoke_all
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                Flipflop::InstallGenerator.new([],
         | 
| 75 | 
            +
                  quiet: true,
         | 
| 76 | 
            +
                ).invoke_all
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              def load!
         | 
| 80 | 
            +
                ENV["RAILS_ENV"] = "test"
         | 
| 81 | 
            +
                require "rails"
         | 
| 82 | 
            +
                require "flipflop/engine"
         | 
| 83 | 
            +
                require File.expand_path("../../tmp/app/config/environment", __FILE__)
         | 
| 84 | 
            +
                ActiveSupport::Dependencies.mechanism = :load
         | 
| 85 | 
            +
                load(Rails.application.paths["config/features.rb"].existent.first)
         | 
| 86 | 
            +
                require "capybara/rails"
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
              def migrate!
         | 
| 90 | 
            +
                ActiveRecord::Base.establish_connection
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                ActiveRecord::Tasks::DatabaseTasks.create_current
         | 
| 93 | 
            +
                ActiveRecord::Migration.verbose = false
         | 
| 94 | 
            +
                ActiveRecord::Migrator.migrate(Rails.application.paths["db/migrate"].to_a)
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
            end
         | 
| @@ -0,0 +1,104 @@ | |
| 1 | 
            +
            require File.expand_path("../../test_helper", __FILE__)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Flipflop::Configurable do
         | 
| 4 | 
            +
              subject do
         | 
| 5 | 
            +
                Flipflop::FeatureSet.current.reset!
         | 
| 6 | 
            +
                Module.new do
         | 
| 7 | 
            +
                  extend Flipflop::Configurable
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              describe "feature" do
         | 
| 12 | 
            +
                it "should append feature definition" do
         | 
| 13 | 
            +
                  subject.feature(:one, default: true)
         | 
| 14 | 
            +
                  subject.feature(:two, default: false)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  assert_equal [:one, :two],
         | 
| 17 | 
            +
                    Flipflop::FeatureSet.current.features.map(&:key)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                it "should append feature definition with default" do
         | 
| 21 | 
            +
                  subject.feature(:one, default: true)
         | 
| 22 | 
            +
                  subject.feature(:two, default: false)
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  assert_equal [true, false],
         | 
| 25 | 
            +
                    Flipflop::FeatureSet.current.features.map(&:default)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              describe "strategy" do
         | 
| 30 | 
            +
                it "should append strategy objects" do
         | 
| 31 | 
            +
                  strategy_class = Class.new(Flipflop::Strategies::AbstractStrategy)
         | 
| 32 | 
            +
                  strategies = [
         | 
| 33 | 
            +
                    strategy_class.new,
         | 
| 34 | 
            +
                    strategy_class.new,
         | 
| 35 | 
            +
                  ]
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  subject.strategy(strategies[0])
         | 
| 38 | 
            +
                  subject.strategy(strategies[1])
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  assert_equal strategies, Flipflop::FeatureSet.current.strategies
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                it "should append strategy classes" do
         | 
| 44 | 
            +
                  strategies = [
         | 
| 45 | 
            +
                    Class.new(Flipflop::Strategies::AbstractStrategy),
         | 
| 46 | 
            +
                    Class.new(Flipflop::Strategies::AbstractStrategy),
         | 
| 47 | 
            +
                  ]
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  subject.strategy(strategies[0])
         | 
| 50 | 
            +
                  subject.strategy(strategies[1])
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  assert_equal strategies, Flipflop::FeatureSet.current.strategies.map(&:class)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                it "should append strategy classes with options" do
         | 
| 56 | 
            +
                  strategy_class = Class.new(Flipflop::Strategies::AbstractStrategy)
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  subject.strategy(strategy_class, name: "my strategy")
         | 
| 59 | 
            +
                  subject.strategy(strategy_class, name: "awesome strategy")
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  assert_equal ["my strategy", "awesome strategy"],
         | 
| 62 | 
            +
                    Flipflop::FeatureSet.current.strategies.map(&:name)
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                it "should append strategy symbols" do
         | 
| 66 | 
            +
                  subject.strategy(:cookie)
         | 
| 67 | 
            +
                  subject.strategy(:query_string)
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  assert_equal [
         | 
| 70 | 
            +
                    Flipflop::Strategies::CookieStrategy,
         | 
| 71 | 
            +
                    Flipflop::Strategies::QueryStringStrategy
         | 
| 72 | 
            +
                  ], Flipflop::FeatureSet.current.strategies.map(&:class)
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                it "should append strategy symbols with options" do
         | 
| 76 | 
            +
                  subject.strategy(:cookie, name: "my strategy")
         | 
| 77 | 
            +
                  subject.strategy(:query_string, name: "awesome strategy")
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  assert_equal ["my strategy", "awesome strategy"],
         | 
| 80 | 
            +
                    Flipflop::FeatureSet.current.strategies.map(&:name)
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                it "should append strategy lambda" do
         | 
| 84 | 
            +
                  subject.strategy { |feature| "hi!" }
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  assert_equal [Flipflop::Strategies::LambdaStrategy],
         | 
| 87 | 
            +
                    Flipflop::FeatureSet.current.strategies.map(&:class)
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                it "should append strategy lambda with name" do
         | 
| 91 | 
            +
                  subject.strategy(:my_strategy) { |feature| "hi!" }
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  assert_equal ["my_strategy"],
         | 
| 94 | 
            +
                    Flipflop::FeatureSet.current.strategies.map(&:name)
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                it "should append strategy lambda with name and options" do
         | 
| 98 | 
            +
                  subject.strategy("my strategy", description: "awesome") { |feature| "hi!" }
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  assert_equal ["awesome"],
         | 
| 101 | 
            +
                    Flipflop::FeatureSet.current.strategies.map(&:description)
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
              end
         | 
| 104 | 
            +
            end
         | 
| @@ -0,0 +1,142 @@ | |
| 1 | 
            +
            require File.expand_path("../../test_helper", __FILE__)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Flipflop::FeatureCache do
         | 
| 4 | 
            +
              subject do
         | 
| 5 | 
            +
                Flipflop::FeatureCache.current
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              after do
         | 
| 9 | 
            +
                Flipflop::FeatureCache.current.disable!
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              describe "current" do
         | 
| 13 | 
            +
                it "should return same instance" do
         | 
| 14 | 
            +
                  current = subject
         | 
| 15 | 
            +
                  assert_equal current, Flipflop::FeatureCache.current
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it "should return new instance in different thread" do
         | 
| 19 | 
            +
                  current = subject
         | 
| 20 | 
            +
                  refute_equal current, Thread.new { Flipflop::FeatureCache.current }.value
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              describe "when enabled" do
         | 
| 25 | 
            +
                before do
         | 
| 26 | 
            +
                  subject.enable!
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                describe "enabled" do
         | 
| 30 | 
            +
                  it "should return true" do
         | 
| 31 | 
            +
                    assert_equal true, subject.enabled?
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                describe "fetch" do
         | 
| 36 | 
            +
                  it "should store value by key" do
         | 
| 37 | 
            +
                    subject.fetch(:key) { 1 }
         | 
| 38 | 
            +
                    assert_equal 1, subject.fetch(:key) { 2 }
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  it "should not call block if cached" do
         | 
| 42 | 
            +
                    called = false
         | 
| 43 | 
            +
                    subject.fetch(:key) { 1 }
         | 
| 44 | 
            +
                    subject.fetch(:key) { called = true }
         | 
| 45 | 
            +
                    assert_equal false, called
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                describe "clear" do
         | 
| 50 | 
            +
                  it "should empty cache" do
         | 
| 51 | 
            +
                    subject.fetch(:key) { 1 }
         | 
| 52 | 
            +
                    subject.clear!
         | 
| 53 | 
            +
                    assert_equal 2, subject.fetch(:key) { 2 }
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              describe "when disabled" do
         | 
| 59 | 
            +
                before do
         | 
| 60 | 
            +
                  subject.disable!
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                describe "enabled" do
         | 
| 64 | 
            +
                  it "should return false" do
         | 
| 65 | 
            +
                    assert_equal false, subject.enabled?
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                describe "fetch" do
         | 
| 70 | 
            +
                  it "should not store value" do
         | 
| 71 | 
            +
                    subject.fetch(:key) { 1 }
         | 
| 72 | 
            +
                    assert_equal 2, subject.fetch(:key) { 2 }
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  it "should always call block" do
         | 
| 76 | 
            +
                    called = false
         | 
| 77 | 
            +
                    subject.fetch(:key) { 1 }
         | 
| 78 | 
            +
                    subject.fetch(:key) { called = true }
         | 
| 79 | 
            +
                    assert_equal true, called
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              describe "enable" do
         | 
| 85 | 
            +
                it "should not clear cache" do
         | 
| 86 | 
            +
                  subject.enable!
         | 
| 87 | 
            +
                  subject.fetch(:key) { 1 }
         | 
| 88 | 
            +
                  subject.enable!
         | 
| 89 | 
            +
                  assert_equal 1, subject.fetch(:key) { 2 }
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              describe "disable" do
         | 
| 94 | 
            +
                it "should clear cache" do
         | 
| 95 | 
            +
                  subject.enable!
         | 
| 96 | 
            +
                  subject.fetch(:key) { 1 }
         | 
| 97 | 
            +
                  subject.disable!
         | 
| 98 | 
            +
                  subject.enable!
         | 
| 99 | 
            +
                  assert_equal 2, subject.fetch(:key) { 2 }
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              describe "middleware" do
         | 
| 104 | 
            +
                subject do
         | 
| 105 | 
            +
                  app = ->(env) {
         | 
| 106 | 
            +
                    raise env["error"] if env["error"]
         | 
| 107 | 
            +
                    env["cache"] = Flipflop::FeatureCache.current.enabled?
         | 
| 108 | 
            +
                    return [200, {}, ["ok"]]
         | 
| 109 | 
            +
                  }
         | 
| 110 | 
            +
                  Flipflop::FeatureCache::Middleware.new(app)
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                it "should call app" do
         | 
| 114 | 
            +
                  response = subject.call({})
         | 
| 115 | 
            +
                  assert_equal "ok", response[2].to_a.join
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                it "should enable cache before request" do
         | 
| 119 | 
            +
                  response = subject.call(env = {})
         | 
| 120 | 
            +
                  response[2].try(:close)
         | 
| 121 | 
            +
                  assert_equal true, env["cache"]
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                it "should disable cache after request" do
         | 
| 125 | 
            +
                  response = subject.call({})
         | 
| 126 | 
            +
                  response[2].try(:close)
         | 
| 127 | 
            +
                  assert_equal false, Flipflop::FeatureCache.current.enabled?
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                it "should disable cache after error" do
         | 
| 131 | 
            +
                  subject.call({ "error" => "boo!" }) rescue nil
         | 
| 132 | 
            +
                  assert_equal false, Flipflop::FeatureCache.current.enabled?
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                it "should not change cache if already enabled" do
         | 
| 136 | 
            +
                  Flipflop::FeatureCache.current.enable!
         | 
| 137 | 
            +
                  response = subject.call({})
         | 
| 138 | 
            +
                  response[2].try(:close)
         | 
| 139 | 
            +
                  assert_equal true, Flipflop::FeatureCache.current.enabled?
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
              end
         | 
| 142 | 
            +
            end
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            require File.expand_path("../../test_helper", __FILE__)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Flipflop::FeatureDefinition do
         | 
| 4 | 
            +
              describe "with defaults" do
         | 
| 5 | 
            +
                subject do
         | 
| 6 | 
            +
                  Flipflop::FeatureDefinition.new(:my_key)
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                it "should have specified key" do
         | 
| 10 | 
            +
                  assert_equal :my_key, subject.key
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                it "should have humanized description" do
         | 
| 14 | 
            +
                  assert_equal "My key.", subject.description
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                it "should default to false" do
         | 
| 18 | 
            +
                  assert_equal false, subject.default
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              describe "with options" do
         | 
| 23 | 
            +
                subject do
         | 
| 24 | 
            +
                  Flipflop::FeatureDefinition.new(:my_key,
         | 
| 25 | 
            +
                    default: true,
         | 
| 26 | 
            +
                    description: "Awesome feature",
         | 
| 27 | 
            +
                  )
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                it "should have specified key" do
         | 
| 31 | 
            +
                  assert_equal :my_key, subject.key
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                it "should have specified description" do
         | 
| 35 | 
            +
                  assert_equal "Awesome feature", subject.description
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                it "should have specified default" do
         | 
| 39 | 
            +
                  assert_equal true, subject.default
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         |