Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
featurevisor (3.0.0)
featurevisor (3.1.0)
benchmark (>= 0, < 1)

GEM
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ The build produces `featurevisor-VERSION.gem` and `featurevisor-openfeature-VERS
- Run `bundle install`
- Push commit to `main` branch
- Wait for CI to complete
- Tag the release with the same version number, for example `v3.0.0`
- Tag the release with the same version number, for example `v3.1.0`
- The workflow verifies that the tag matches the shared version
- The workflow publishes `featurevisor` first, followed by `featurevisor-openfeature`

Expand Down
97 changes: 67 additions & 30 deletions bin/commands/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -223,18 +223,13 @@ def resolve_datafile_for_assertion(assertion, datafiles_by_key)
environment = assertion.key?(:environment) ? assertion[:environment] : false
environment = false if environment.nil?

target_key = assertion[:target] ? target_datafile_key(environment, assertion[:target]) : nil
base_key = base_datafile_key(environment)
return datafiles_by_key[target_datafile_key(environment, assertion[:target])] if assertion[:target]

if target_key && datafiles_by_key.key?(target_key)
return datafiles_by_key[target_key]
end

datafiles_by_key[base_key]
datafiles_by_key[base_datafile_key(environment)]
end

def create_tester_instance(datafile, level, assertion)
sticky = parse_sticky(assertion[:sticky])
sticky = parse_sticky(assertion[:stickyFeatures] || assertion[:sticky])
sticky_variables = assertion[:stickyVariables].is_a?(Hash) ? assertion[:stickyVariables] : {}

Featurevisor.create_featurevisor(
Expand Down Expand Up @@ -301,13 +296,26 @@ def run_tests(tests, datafiles_by_key, segments_by_key, level, config)
puts ""
end

test_result = run_test_feature(assertion, test[:feature], instance, level)
begin
test_result = run_test_feature(assertion, test[:feature], instance, level)
ensure
instance.close
end
end
elsif test[:variable]
datafile = resolve_datafile_for_assertion(assertion, datafiles_by_key)
if datafile
if @options.show_datafile
puts ""
puts JSON.pretty_generate(datafile)
puts ""
end
instance = create_tester_instance(datafile, level, assertion)
test_result = run_test_variable(assertion, test[:variable], instance)
begin
test_result = run_test_variable(assertion, test[:variable], instance)
ensure
instance.close
end
else
test_result = { has_error: true, errors: " ✘ no datafile found for assertion target/environment combination\n", duration: 0 }
end
Expand Down Expand Up @@ -358,28 +366,61 @@ def run_tests(tests, datafiles_by_key, segments_by_key, level, config)
end

def run_test_variable(assertion, variable_key, instance)
context = parse_context(assertion[:context])
instance.set_context(parse_context(assertion[:context]), true)
options = {}
options[:default_variable_value] = assertion[:defaultVariableValue] if assertion.key?(:defaultVariableValue)
started = Time.now
errors = ""

if assertion.key?(:expectedValue)
actual = instance.get_variable(variable_key, context, options)
unless compare_values(actual, assertion[:expectedValue])
errors += " ✘ expectedValue: expected #{assertion[:expectedValue].inspect} but received #{actual.inspect}\n"
errors += test_variable_expectation(assertion, variable_key, instance, options)

Array(assertion[:children]).each_with_index do |child_assertion, child_index|
child = instance.spawn(
parse_context(child_assertion[:context]),
sticky_features: parse_sticky(child_assertion[:stickyFeatures]),
sticky_variables: child_assertion[:stickyVariables].is_a?(Hash) ? child_assertion[:stickyVariables] : {}
)
begin
child_options = {}
if child_assertion.key?(:defaultVariableValue)
child_options[:default_variable_value] = child_assertion[:defaultVariableValue]
end
errors += test_variable_expectation(
child_assertion,
variable_key,
child,
child_options,
"children[#{child_index}]."
)
ensure
child.close
end
end

{ has_error: !errors.empty?, errors: errors, duration: Time.now - started }
end

def test_variable_expectation(assertion, variable_key, evaluator, options, prefix = "")
evaluation = evaluator.evaluate_variable(variable_key, {}, options)
errors = ""
if assertion.key?(:expectedValue) && !compare_values(evaluation[:variable_value], assertion[:expectedValue])
errors += " ✘ #{prefix}expectedValue: expected #{format_test_value(assertion[:expectedValue])} but received #{format_test_value(evaluation[:variable_value])}\n"
end
if assertion[:expectedEvaluation].is_a?(Hash)
evaluation = instance.evaluate_variable(variable_key, context, options)
assertion[:expectedEvaluation].each do |key, expected|
actual = get_evaluation_value(evaluation, key)
errors += " ✘ expectedEvaluation.#{key}: expected #{expected.inspect} but received #{actual.inspect}\n" unless compare_values(actual, expected)
unless compare_values(actual, expected)
errors += " ✘ #{prefix}expectedEvaluation.#{key}: expected #{format_test_value(expected)} but received #{format_test_value(actual)}\n"
end
end
end
errors
end

{ has_error: !errors.empty?, errors: errors, duration: Time.now - started }
def format_test_value(value)
JSON.generate(value)
rescue JSON::GeneratorError
value.inspect
end

def run_test_feature(assertion, feature_key, instance, level)
Expand Down Expand Up @@ -518,20 +559,16 @@ def run_test_feature(assertion, feature_key, instance, level)
child_context = parse_context(child[:context])

# Create override options for child with sticky values
child_override_options = create_override_options(child)

# Pass sticky values to child instance
child_instance = instance.spawn(child_context, child_override_options)

# Set sticky values for child if they exist
# Create a local copy to ensure it's never nil
child_sticky = sticky || {}
if !child_sticky.empty?
child_instance.set_sticky_features(child_sticky, false)
child_instance = instance.spawn(
child_context,
sticky_features: sticky || {}
)
begin
child_result = run_test_feature_child(child, feature_key, child_instance, level)
ensure
child_instance.close
end

child_result = run_test_feature_child(child, feature_key, child_instance, level)

if child_result[:has_error]
has_error = true
errors += child_result[:errors]
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/base.gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
featurevisor (3.0.0)
featurevisor (3.1.0)
benchmark (>= 0, < 1)

GEM
Expand Down
2 changes: 1 addition & 1 deletion lib/featurevisor/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Featurevisor
VERSION = "3.0.0"
VERSION = "3.1.0"
end
8 changes: 8 additions & 0 deletions spec/test_command_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
datafile = command.send(:resolve_datafile_for_assertion, assertion, datafiles_by_key)
expect(datafile[:target]).to eq("checkout")
end

it "does not fall back when a target datafile is missing" do
command = described_class.new(options)
datafiles_by_key = { "production" => { schemaVersion: "2" } }
assertion = { environment: "production", target: "checkout" }

expect(command.send(:resolve_datafile_for_assertion, assertion, datafiles_by_key)).to be_nil
end
end

describe "build command generation" do
Expand Down