diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aee24287b..20969f1d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,15 +50,16 @@ jobs: mongodb-version: 4.0 - name: Check out repository code uses: actions/checkout@v4 - - name: Do some action caching - uses: actions/cache@v3 with: - path: vendor/bundle - key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}- + # Install gems/npm packages from GitHub fails without this + persist-credentials: false - name: Install libpq-dev run: sudo apt-get -yqq install libpq-dev + - name: Set up Node + uses: actions/setup-node@v3 + with: + cache: 'npm' + - run: npm clean-install - name: Set up Ruby ${{ matrix.ruby }} uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 69a2738a2..39c8800d2 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -51,15 +51,13 @@ jobs: mongodb-version: 4.0 - name: Check out repository code uses: actions/checkout@v4 - - name: Do some action caching - uses: actions/cache@v3 - with: - path: vendor/bundle - key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}- - name: Install libpq-dev run: sudo apt-get -yqq install libpq-dev + - name: Set up Node + uses: actions/setup-node@v3 + with: + cache: 'npm' + - run: npm clean-install - name: Set up Ruby ${{ matrix.ruby }} uses: ruby/setup-ruby@v1 with: diff --git a/.gitignore b/.gitignore index 76cea35a6..8b4f94266 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ log .sass-cache bin .DS_Store +node_modules .tool-versions diff --git a/Guardfile b/Guardfile index 4777b30ee..ee0570a03 100644 --- a/Guardfile +++ b/Guardfile @@ -22,6 +22,9 @@ guard 'rspec', rspec_options do watch(/shared_adapter_specs\.rb$/) { 'spec' } watch('spec/helper.rb') { 'spec' } + watch(%r{lib/flipper/expressions}) { 'spec/flipper/expressions_schema_spec.rb' } + watch(%r{node_modules/@flippercloud.io/expressions}) { 'spec/flipper/expressions_schema_spec.rb' } + # To run all specs on every change... (useful with focus and fit) # watch(%r{.*}) { 'spec' } end diff --git a/Rakefile b/Rakefile index e72667b9f..2aadb6456 100644 --- a/Rakefile +++ b/Rakefile @@ -7,6 +7,7 @@ require 'flipper/version' # gem uninstall flipper flipper-ui flipper-redis desc 'Build gem into the pkg directory' task :build do + sh 'npm clean-install --silent' FileUtils.rm_rf('pkg') Dir['*.gemspec'].each do |gemspec| system "gem build #{gemspec}" diff --git a/flipper-expressions-schema.gemspec b/flipper-expressions-schema.gemspec new file mode 100644 index 000000000..c5ad9c72b --- /dev/null +++ b/flipper-expressions-schema.gemspec @@ -0,0 +1,37 @@ +# -*- encoding: utf-8 -*- +require File.expand_path('../lib/flipper/version', __FILE__) +require File.expand_path('../lib/flipper/metadata', __FILE__) + +Gem::Specification.new do |gem| + SCHEMAS_DIR = File.expand_path("node_modules/@flippercloud.io/expressions/schemas", __dir__) + + # Ensure schemas are downloaded when installed as a git dependency. + # This will be handled by rake when the gem is built and published. + Gem.post_install do + unless File.exist?(SCHEMAS_DIR) + warn "Getting schemas from @flippercloud.io/expressions..." + Dir.chdir(__dir__) { exec "npm install --silent" } + end + end + + gem.authors = ['John Nunemaker'] + gem.email = 'support@flippercloud.io' + gem.summary = 'ActiveRecord adapter for Flipper' + gem.license = 'MIT' + gem.homepage = 'https://www.flippercloud.io/docs/expressions' + + gem.files = [ + 'package.json', + 'package-lock.json', + 'lib/flipper-expressions-schema.rb', + 'lib/flipper/expression/schema.rb', + 'lib/flipper/version.rb', + ] + Dir['node_modules/@flippercloud.io/expressions/schemas/*.json'] + gem.name = 'flipper-expressions-schema' + gem.require_paths = ['lib'] + gem.version = Flipper::VERSION + gem.metadata = Flipper::METADATA + + gem.add_dependency 'flipper', "~> #{Flipper::VERSION}" + gem.add_dependency 'json_schemer', '~> 1.0' +end diff --git a/flipper.gemspec b/flipper.gemspec index ca48c66a8..d0bb068dd 100644 --- a/flipper.gemspec +++ b/flipper.gemspec @@ -6,7 +6,7 @@ plugin_files = [] plugin_test_files = [] Dir['flipper-*.gemspec'].map do |gemspec| - spec = eval(File.read(gemspec)) + spec = Bundler.load_gemspec(gemspec) plugin_files << spec.files plugin_test_files << spec.files end diff --git a/lib/flipper-expressions-schema.rb b/lib/flipper-expressions-schema.rb new file mode 100644 index 000000000..23dafb71e --- /dev/null +++ b/lib/flipper-expressions-schema.rb @@ -0,0 +1 @@ +require "flipper/expression/schema" diff --git a/lib/flipper/expression.rb b/lib/flipper/expression.rb index 8d08988c0..a4370569e 100644 --- a/lib/flipper/expression.rb +++ b/lib/flipper/expression.rb @@ -10,14 +10,18 @@ def self.build(object) case object when Hash - name = object.keys.first - args = object.values.first - unless name + if(object.keys.size != 1) raise ArgumentError, "#{object.inspect} cannot be converted into an expression" end - new(name, Array(args).map { |o| build(o) }) - when String, Numeric, FalseClass, TrueClass + name = object.keys.first + args = object.values.first + + # Ensure args are an array, but we can't just use Array(args) because it will convert a Hash to Array + args = args.is_a?(Hash) ? [args] : Array(args) + + new(name, args.map { |o| build(o) }) + when String, Numeric, FalseClass, TrueClass, nil Expression::Constant.new(object) when Symbol Expression::Constant.new(object.to_s) @@ -56,6 +60,10 @@ def value } end + def validate + Schema.new.validate(value) + end + private def call_with_context? diff --git a/lib/flipper/expression/constant.rb b/lib/flipper/expression/constant.rb index 35edca9e4..351d8ae7f 100644 --- a/lib/flipper/expression/constant.rb +++ b/lib/flipper/expression/constant.rb @@ -20,6 +20,10 @@ def eql?(other) other.is_a?(self.class) && other.value == value end alias_method :==, :eql? + + def validate + Schema.new.validate(value) + end end end end diff --git a/lib/flipper/expression/schema.rb b/lib/flipper/expression/schema.rb new file mode 100644 index 000000000..7d44f71be --- /dev/null +++ b/lib/flipper/expression/schema.rb @@ -0,0 +1,33 @@ +require "json_schemer" + +module Flipper + class Expression + class Schema < JSONSchemer::Schema::Draft7 + PATH = Pathname.new(File.expand_path("../../../node_modules/@flippercloud.io/expressions", __dir__)) + + def self.schemas + @schemas ||= + Hash[ + PATH + .glob("schemas/*.json") + .map { |path| [File.basename(path), JSON.parse(File.read(path))] } + ] + end + + def self.examples + PATH + .glob("examples/*.json") + .map { |path| [File.basename(path), JSON.parse(File.read(path))] } + end + + def initialize(schema = self.class.schemas["schema.json"]) + super( + schema, + insert_property_defaults: true, + ref_resolver: + lambda { |url| self.class.schemas[File.basename(url.path)] } + ) + end + end + end +end diff --git a/lib/flipper/expressions/add.rb b/lib/flipper/expressions/add.rb new file mode 100644 index 000000000..ef02e469d --- /dev/null +++ b/lib/flipper/expressions/add.rb @@ -0,0 +1,9 @@ +module Flipper + module Expressions + class Add + def self.call(left, right) + left + right + end + end + end +end diff --git a/lib/flipper/expressions/divide.rb b/lib/flipper/expressions/divide.rb new file mode 100644 index 000000000..b242bc3e4 --- /dev/null +++ b/lib/flipper/expressions/divide.rb @@ -0,0 +1,9 @@ +module Flipper + module Expressions + class Divide + def self.call(left, right) + left / right + end + end + end +end diff --git a/lib/flipper/expressions/duration.rb b/lib/flipper/expressions/duration.rb index b1ca50747..2a11ba658 100644 --- a/lib/flipper/expressions/duration.rb +++ b/lib/flipper/expressions/duration.rb @@ -11,7 +11,7 @@ class Duration "year" => 31_556_952 # length of a gregorian year (365.2425 days) }.freeze - def self.call(scalar, unit = 'second') + def self.call(scalar, unit) unit = unit.to_s.downcase.chomp("s") unless scalar.is_a?(Numeric) diff --git a/lib/flipper/expressions/max.rb b/lib/flipper/expressions/max.rb new file mode 100644 index 000000000..489b999b0 --- /dev/null +++ b/lib/flipper/expressions/max.rb @@ -0,0 +1,11 @@ +require "flipper/expression" + +module Flipper + module Expressions + class Max + def self.call(*args) + args.max + end + end + end +end diff --git a/lib/flipper/expressions/min.rb b/lib/flipper/expressions/min.rb new file mode 100644 index 000000000..c3bef5a71 --- /dev/null +++ b/lib/flipper/expressions/min.rb @@ -0,0 +1,11 @@ +require "flipper/expression" + +module Flipper + module Expressions + class Min + def self.call(*args) + args.min + end + end + end +end diff --git a/lib/flipper/expressions/multiply.rb b/lib/flipper/expressions/multiply.rb new file mode 100644 index 000000000..cb9002d99 --- /dev/null +++ b/lib/flipper/expressions/multiply.rb @@ -0,0 +1,9 @@ +module Flipper + module Expressions + class Multiply + def self.call(left, right) + left * right + end + end + end +end diff --git a/lib/flipper/expressions/percentage.rb b/lib/flipper/expressions/percentage.rb index ec79713a5..bdad81461 100644 --- a/lib/flipper/expressions/percentage.rb +++ b/lib/flipper/expressions/percentage.rb @@ -2,7 +2,7 @@ module Flipper module Expressions class Percentage def self.call(value) - value.to_f.clamp(0, 100) + value.clamp(0, 100) end end end diff --git a/lib/flipper/expressions/percentage_of_actors.rb b/lib/flipper/expressions/percentage_of_actors.rb index 869c2a2ad..efadab6b2 100644 --- a/lib/flipper/expressions/percentage_of_actors.rb +++ b/lib/flipper/expressions/percentage_of_actors.rb @@ -5,7 +5,7 @@ class PercentageOfActors def self.call(text, percentage, context: {}) prefix = context[:feature_name] || "" - Zlib.crc32("#{prefix}#{text}") % (100 * SCALING_FACTOR) < percentage * SCALING_FACTOR + Zlib.crc32("#{prefix}#{text}") % (100 * SCALING_FACTOR) < percentage.clamp(0, 100) * SCALING_FACTOR end end end diff --git a/lib/flipper/expressions/subtract.rb b/lib/flipper/expressions/subtract.rb new file mode 100644 index 000000000..fcd0026f1 --- /dev/null +++ b/lib/flipper/expressions/subtract.rb @@ -0,0 +1,9 @@ +module Flipper + module Expressions + class Subtract + def self.call(left, right) + left - right + end + end + end +end diff --git a/lib/flipper/expressions/time.rb b/lib/flipper/expressions/time.rb index 93780749a..0c8dd83c7 100644 --- a/lib/flipper/expressions/time.rb +++ b/lib/flipper/expressions/time.rb @@ -2,7 +2,7 @@ module Flipper module Expressions class Time def self.call(value) - ::Time.parse(value) + ::Time.iso8601(value) end end end diff --git a/lib/flipper/gates/expression.rb b/lib/flipper/gates/expression.rb index 53d4dc31e..2e3ac91df 100644 --- a/lib/flipper/gates/expression.rb +++ b/lib/flipper/gates/expression.rb @@ -39,7 +39,7 @@ def open?(context) end def protects?(thing) - thing.is_a?(Flipper::Expression) || thing.is_a?(Hash) + thing.is_a?(Flipper::Expression) || thing.is_a?(Flipper::Expression::Constant) || thing.is_a?(Hash) end def wrap(thing) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..abf445e83 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,99 @@ +{ + "name": "flipper", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@flippercloud.io/expressions": "github:flippercloud/expressions" + } + }, + "node_modules/@flippercloud.io/expressions": { + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/flippercloud/expressions.git#82e357d922ce6b00d343ba67900f749d0655b62c", + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "uuid": "^9.0.0" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..4d3b4f9a5 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "private": true, + "dependencies": { + "@flippercloud.io/expressions": "github:flippercloud/expressions" + } +} diff --git a/script/bootstrap b/script/bootstrap index adccebf92..a06aaa9c2 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -19,3 +19,4 @@ cd $(dirname "$0")/.. rm -rf .bundle/{binstubs,config} bundle install --binstubs --quiet "$@" +npm clean-install --silent diff --git a/spec/flipper/expression/schema_spec.rb b/spec/flipper/expression/schema_spec.rb new file mode 100644 index 000000000..83ac4dfe8 --- /dev/null +++ b/spec/flipper/expression/schema_spec.rb @@ -0,0 +1,42 @@ +require "json_schemer" +require "flipper-expressions-schema" + +RSpec.describe Flipper::Expression::Schema do + let(:schema) { Flipper::Expression::Schema.new } + + Flipper::Expression::Schema.examples.each do |name, examples| + describe(name) do + examples["valid"].each do |example| + expression, context, result = example.values_at("expression", "context", "result") + context&.transform_keys!(&:to_sym) + + describe expression.inspect do + it "is valid" do + errors = Flipper::Expression.build(expression).validate + expect(errors.to_a).to eq([]) + end + + it "evaluates to #{result.inspect}#{" with context " + context.inspect if context}" do + evaluated_result = Flipper::Expression.build(expression).evaluate(context || {}) + expected_schema = JSONSchemer.schema(result) + expect(expected_schema.validate(evaluated_result).to_a).to eq([]) + end + end + end + + examples["invalid"].each do |example| + context example.inspect do + it "is invalid" do + expect(schema.valid?(example)).not_to be(true) + end + + it "should not evaluate" do + expect { Flipper::Expression.build(example).evaluate }.to raise_error { |error| + expect([ArgumentError, TypeError, NoMethodError]).to include(error.class) + } + end + end + end + end + end +end diff --git a/spec/flipper/expressions/all_spec.rb b/spec/flipper/expressions/all_spec.rb deleted file mode 100644 index c12fe54ac..000000000 --- a/spec/flipper/expressions/all_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -RSpec.describe Flipper::Expressions::All do - describe "#call" do - it "returns true if all args evaluate as true" do - expect(described_class.call(true, true)).to be(true) - end - - it "returns false if any args evaluate as false" do - expect(described_class.call(false, true)).to be(false) - end - - it "returns true with empty args" do - expect(described_class.call).to be(true) - end - end -end diff --git a/spec/flipper/expressions/any_spec.rb b/spec/flipper/expressions/any_spec.rb deleted file mode 100644 index cd6b07f8a..000000000 --- a/spec/flipper/expressions/any_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -RSpec.describe Flipper::Expressions::Any do - describe "#call" do - it "returns true if any args evaluate as true" do - expect(described_class.call(true, false)).to be(true) - end - - it "returns false if all args evaluate as false" do - expect(described_class.call(false, false)).to be(false) - end - - it "returns false with empty args" do - expect(described_class.call).to be(false) - end - end -end diff --git a/spec/flipper/expressions/boolean_spec.rb b/spec/flipper/expressions/boolean_spec.rb deleted file mode 100644 index c85921e79..000000000 --- a/spec/flipper/expressions/boolean_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -RSpec.describe Flipper::Expressions::Boolean do - describe "#call" do - [true, 'true', 1, '1'].each do |value| - it "returns a true for #{value.inspect}" do - expect(described_class.call(value)).to be(true) - end - end - - [false, 'false', 0, '0', nil].each do |value| - it "returns a true for #{value.inspect}" do - expect(described_class.call(value)).to be(false) - end - end - end -end diff --git a/spec/flipper/expressions/duration_spec.rb b/spec/flipper/expressions/duration_spec.rb deleted file mode 100644 index e19e2aff0..000000000 --- a/spec/flipper/expressions/duration_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -RSpec.describe Flipper::Expressions::Duration do - describe "#call" do - it "raises error with invalid value" do - expect { described_class.call(false, 'minute') }.to raise_error(ArgumentError) - end - - it "raises error with invalid unit" do - expect { described_class.call(4, 'score') }.to raise_error(ArgumentError) - end - - it 'defaults unit to seconds' do - expect(described_class.call(10)).to eq(10) - end - - it "evaluates seconds" do - expect(described_class.call(10, 'seconds')).to eq(10) - end - - it "evaluates minutes" do - expect(described_class.call(2, 'minutes')).to eq(120) - end - - it "evaluates hours" do - expect(described_class.call(2, 'hours')).to eq(7200) - end - - it "evaluates days" do - expect(described_class.call(2, 'days')).to eq(172_800) - end - - it "evaluates weeks" do - expect(described_class.call(2, 'weeks')).to eq(1_209_600) - end - - it "evaluates months" do - expect(described_class.call(2, 'months')).to eq(5_259_492) - end - - it "evaluates years" do - expect(described_class.call(2, 'years')).to eq(63_113_904) - end - end -end diff --git a/spec/flipper/expressions/equal_spec.rb b/spec/flipper/expressions/equal_spec.rb deleted file mode 100644 index a2722810d..000000000 --- a/spec/flipper/expressions/equal_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -RSpec.describe Flipper::Expressions::Equal do - describe "#call" do - it "returns true when equal" do - expect(described_class.call("basic", "basic")).to be(true) - end - - it "returns false when not equal" do - expect(described_class.call("basic", "plus")).to be(false) - end - - it "returns false when value evaluates to nil" do - expect(described_class.call(nil, 1)).to be(false) - expect(described_class.call(1, nil)).to be(false) - end - - it "raises ArgumentError with no arguments" do - expect { described_class.call }.to raise_error(ArgumentError) - end - - it "raises ArgumentError with one argument" do - expect { described_class.call(10) }.to raise_error(ArgumentError) - end - end -end diff --git a/spec/flipper/expressions/greater_than_or_equal_to_spec.rb b/spec/flipper/expressions/greater_than_or_equal_to_spec.rb deleted file mode 100644 index 7e2e065a8..000000000 --- a/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -RSpec.describe Flipper::Expressions::GreaterThanOrEqualTo do - describe "#call" do - it "returns true when equal" do - expect(described_class.call(2, 2)).to be(true) - end - - it "returns true when greater" do - expect(described_class.call(2, 1)).to be(true) - end - - it "returns false when less" do - expect(described_class.call(1, 2)).to be(false) - end - - it "returns false when value evaluates to nil" do - expect(described_class.call(nil, 1)).to be(false) - expect(described_class.call(1, nil)).to be(false) - end - - it "raises ArgumentError with no arguments" do - expect { described_class.call }.to raise_error(ArgumentError) - end - - it "raises ArgumentError with one argument" do - expect { described_class.call(10) }.to raise_error(ArgumentError) - end - end -end diff --git a/spec/flipper/expressions/greater_than_spec.rb b/spec/flipper/expressions/greater_than_spec.rb deleted file mode 100644 index 9a89a858a..000000000 --- a/spec/flipper/expressions/greater_than_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -RSpec.describe Flipper::Expressions::GreaterThan do - describe "#call" do - it "returns false when equal" do - expect(described_class.call(2, 2)).to be(false) - end - - it "returns true when greater" do - expect(described_class.call(2, 1)).to be(true) - end - - it "returns false when less" do - expect(described_class.call(1, 2)).to be(false) - end - - it "returns false when value evaluates to nil" do - expect(described_class.call(nil, 1)).to be(false) - expect(described_class.call(1, nil)).to be(false) - end - - it "raises ArgumentError with no arguments" do - expect { described_class.call }.to raise_error(ArgumentError) - end - - it "raises ArgumentError with one argument" do - expect { described_class.call(10) }.to raise_error(ArgumentError) - end - end -end diff --git a/spec/flipper/expressions/less_than_or_equal_to_spec.rb b/spec/flipper/expressions/less_than_or_equal_to_spec.rb deleted file mode 100644 index 9fa1182cc..000000000 --- a/spec/flipper/expressions/less_than_or_equal_to_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -RSpec.describe Flipper::Expressions::LessThanOrEqualTo do - describe "#call" do - it "returns true when equal" do - expect(described_class.call(2, 2)).to be(true) - end - - it "returns true when less" do - expect(described_class.call(1, 2)).to be(true) - end - - it "returns false when greater" do - expect(described_class.call(2, 1)).to be(false) - end - - it "returns false when value evaluates to nil" do - expect(described_class.call(nil, 1)).to be(false) - expect(described_class.call(1, nil)).to be(false) - end - - it "raises ArgumentError with no arguments" do - expect { described_class.call }.to raise_error(ArgumentError) - end - - it "raises ArgumentError with one argument" do - expect { described_class.call(10) }.to raise_error(ArgumentError) - end - end -end diff --git a/spec/flipper/expressions/less_than_spec.rb b/spec/flipper/expressions/less_than_spec.rb deleted file mode 100644 index 414c0eaea..000000000 --- a/spec/flipper/expressions/less_than_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -RSpec.describe Flipper::Expressions::LessThan do - describe "#call" do - it "returns false when equal" do - expect(described_class.call(2, 2)).to be(false) - end - - it "returns true when less" do - expect(described_class.call(1, 2)).to be(true) - end - - it "returns true when less with args that need evaluation" do - expect(described_class.call(1, 2)).to be(true) - end - - it "returns false when greater" do - expect(described_class.call(2, 1)).to be(false) - end - - it "returns false when value evaluates to nil" do - expect(described_class.call(nil, 1)).to be(false) - expect(described_class.call(1, nil)).to be(false) - end - - it "raises ArgumentError with no arguments" do - expect { described_class.call }.to raise_error(ArgumentError) - end - - it "raises ArgumentError with one argument" do - expect { described_class.call(10) }.to raise_error(ArgumentError) - end - end -end diff --git a/spec/flipper/expressions/not_equal_spec.rb b/spec/flipper/expressions/not_equal_spec.rb deleted file mode 100644 index 91d8584ba..000000000 --- a/spec/flipper/expressions/not_equal_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -RSpec.describe Flipper::Expressions::NotEqual do - describe "#call" do - it "returns true when not equal" do - expect(described_class.call("basic", "plus")).to be(true) - end - - it "returns false when equal" do - expect(described_class.call("basic", "basic")).to be(false) - end - - it "raises ArgumentError for more arguments" do - expect { described_class.call(20, 10, 20).evaluate }.to raise_error(ArgumentError) - end - end -end diff --git a/spec/flipper/expressions/now_spec.rb b/spec/flipper/expressions/now_spec.rb deleted file mode 100644 index 95868f2bd..000000000 --- a/spec/flipper/expressions/now_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -RSpec.describe Flipper::Expressions::Now do - describe "#call" do - it "returns current time" do - expect(described_class.call).to be_within(2).of(Time.now.utc) - end - - it "defaults to UTC" do - expect(described_class.call.zone).to eq("UTC") - end - end -end diff --git a/spec/flipper/expressions/number_spec.rb b/spec/flipper/expressions/number_spec.rb deleted file mode 100644 index 003e61cc9..000000000 --- a/spec/flipper/expressions/number_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -RSpec.describe Flipper::Expressions::Number do - describe "#call" do - it "returns Integer for Integer" do - expect(described_class.call(10)).to be(10) - end - - it "returns Float for Float" do - expect(described_class.call(10.1)).to be(10.1) - expect(described_class.call(10.0)).to be(10.0) - end - - it "returns Integer for String" do - expect(described_class.call('10')).to be(10) - end - - it "returns Float for String" do - expect(described_class.call('10.0')).to be(10.0) - expect(described_class.call('10.1')).to be(10.1) - end - end -end diff --git a/spec/flipper/expressions/percentage_of_actors_spec.rb b/spec/flipper/expressions/percentage_of_actors_spec.rb deleted file mode 100644 index 180a4b881..000000000 --- a/spec/flipper/expressions/percentage_of_actors_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -RSpec.describe Flipper::Expressions::PercentageOfActors do - describe "#call" do - it "returns true when string in percentage enabled" do - expect(described_class.call("User;1", 42)).to be(true) - end - - it "returns true when string in fractional percentage enabled" do - expect(described_class.call("User;1", 41.687)).to be(true) - end - - it "returns false when string in percentage enabled" do - expect(described_class.call("User;1", 0)).to be(false) - end - - it "changes value based on feature_name so not all actors get all features first" do - expect(described_class.call("User;1", 70, context: {feature_name: "a"})).to be(true) - expect(described_class.call("User;1", 70, context: {feature_name: "b"})).to be(false) - end - end -end diff --git a/spec/flipper/expressions/percentage_spec.rb b/spec/flipper/expressions/percentage_spec.rb deleted file mode 100644 index 1b31347b5..000000000 --- a/spec/flipper/expressions/percentage_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -RSpec.describe Flipper::Expressions::Percentage do - describe "#call" do - it "returns numeric" do - expect(described_class.call(10)).to be(10.0) - end - - it "returns 0 if less than 0" do - expect(described_class.call(-1)).to be(0) - end - - it "returns 100 if greater than 100" do - expect(described_class.call(101)).to be(100) - end - end -end diff --git a/spec/flipper/expressions/property_spec.rb b/spec/flipper/expressions/property_spec.rb deleted file mode 100644 index 3d168f215..000000000 --- a/spec/flipper/expressions/property_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -RSpec.describe Flipper::Expressions::Property do - describe "#call" do - it "returns value for property key" do - context = { properties: { "flipper_id" => "User;1" } } - expect(described_class.call("flipper_id", context: context)).to eq("User;1") - end - - it "returns nil if key not found in properties" do - context = { properties: { } } - expect(described_class.call("flipper_id", context: context)).to be(nil) - end - end -end diff --git a/spec/flipper/expressions/random_spec.rb b/spec/flipper/expressions/random_spec.rb deleted file mode 100644 index 708f0f716..000000000 --- a/spec/flipper/expressions/random_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -RSpec.describe Flipper::Expressions::Random do - describe "#call" do - it "returns random number based on max" do - 100.times do - expect(described_class.call(10)).to be_between(0, 10) - end - end - end -end diff --git a/spec/flipper/expressions/string_spec.rb b/spec/flipper/expressions/string_spec.rb deleted file mode 100644 index 9107e33fd..000000000 --- a/spec/flipper/expressions/string_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -RSpec.describe Flipper::Expressions::String do - describe "#call" do - it "returns String for Numeric" do - expect(described_class.call(10)).to eq("10") - end - - it "returns String" do - expect(described_class.call("test")).to eq("test") - end - end -end diff --git a/spec/flipper/expressions/time_spec.rb b/spec/flipper/expressions/time_spec.rb deleted file mode 100644 index 55674cd08..000000000 --- a/spec/flipper/expressions/time_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -RSpec.describe Flipper::Expressions::Time do - let(:time) { Time.now.round } - - describe "#call" do - it "returns time for #to_s format" do - expect(described_class.call(time.to_s)).to eq(time) - end - - it "returns time for #iso8601 format" do - expect(described_class.call(time.iso8601)).to eq(time) - end - end -end diff --git a/spec/flipper/gates/expression_spec.rb b/spec/flipper/gates/expression_spec.rb index 21509d2d7..74361bba7 100644 --- a/spec/flipper/gates/expression_spec.rb +++ b/spec/flipper/gates/expression_spec.rb @@ -83,6 +83,11 @@ def context(expression, properties: {}) expect(subject.protects?(expression)).to be(true) end + it 'returns true for Flipper::Constant' do + expression = Flipper.boolean(true) + expect(subject.protects?(expression)).to be(true) + end + it 'returns true for Hash' do expression = Flipper.number(20).eq(20) expect(subject.protects?(expression.value)).to be(true)