Skip to content

Commit 95845ba

Browse files
josecolellaclaude
andauthored
feat: enable Gherkin feature tests (#50) (#233)
Signed-off-by: Jose Colella <jose.colella@gusto.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6aca72c commit 95845ba

16 files changed

Lines changed: 979 additions & 1 deletion

File tree

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "spec/open-feature-spec"]
2+
path = spec/open-feature-spec
3+
url = https://github.com/open-feature/spec.git

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ source "https://rubygems.org"
44

55
# Specify your gem's dependencies in openfeature-sdk.gemspec
66
gemspec
7+
8+
gem "cucumber", "~> 9.0", group: :test
9+
gem "logger", group: :test

Gemfile.lock

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,41 @@ GEM
77
remote: https://rubygems.org/
88
specs:
99
ast (2.4.3)
10+
bigdecimal (4.0.1)
11+
builder (3.3.0)
12+
cucumber (9.2.1)
13+
builder (~> 3.2)
14+
cucumber-ci-environment (> 9, < 11)
15+
cucumber-core (> 13, < 14)
16+
cucumber-cucumber-expressions (~> 17.0)
17+
cucumber-gherkin (> 24, < 28)
18+
cucumber-html-formatter (> 20.3, < 22)
19+
cucumber-messages (> 19, < 25)
20+
diff-lcs (~> 1.5)
21+
mini_mime (~> 1.1)
22+
multi_test (~> 1.1)
23+
sys-uname (~> 1.2)
24+
cucumber-ci-environment (10.0.1)
25+
cucumber-core (13.0.3)
26+
cucumber-gherkin (>= 27, < 28)
27+
cucumber-messages (>= 20, < 23)
28+
cucumber-tag-expressions (> 5, < 7)
29+
cucumber-cucumber-expressions (17.1.0)
30+
bigdecimal
31+
cucumber-gherkin (27.0.0)
32+
cucumber-messages (>= 19.1.4, < 23)
33+
cucumber-html-formatter (21.15.1)
34+
cucumber-messages (> 19, < 28)
35+
cucumber-messages (22.0.0)
36+
cucumber-tag-expressions (6.1.2)
1037
date (3.5.1)
1138
debug (1.11.1)
1239
irb (~> 1.10)
1340
reline (>= 0.3.8)
1441
diff-lcs (1.6.2)
1542
docile (1.4.1)
1643
erb (6.0.2)
44+
ffi (1.17.3)
1745
io-console (0.8.2)
1846
irb (1.17.0)
1947
pp (>= 0.6.0)
@@ -23,7 +51,11 @@ GEM
2351
json (2.18.1)
2452
language_server-protocol (3.17.0.5)
2553
lint_roller (1.1.0)
54+
logger (1.7.0)
2655
markly (0.15.2)
56+
memoist3 (1.0.0)
57+
mini_mime (1.1.5)
58+
multi_test (1.1.0)
2759
parallel (1.27.0)
2860
parser (3.3.10.2)
2961
ast (~> 2.4.1)
@@ -100,6 +132,9 @@ GEM
100132
lint_roller (~> 1.1)
101133
rubocop-performance (~> 1.26.0)
102134
stringio (3.2.0)
135+
sys-uname (1.5.0)
136+
ffi (~> 1.1)
137+
memoist3 (~> 1.0.0)
103138
timecop (0.9.10)
104139
tsort (0.2.0)
105140
unicode-display_width (3.2.0)
@@ -120,7 +155,9 @@ PLATFORMS
120155
x86_64-linux
121156

122157
DEPENDENCIES
158+
cucumber (~> 9.0)
123159
debug
160+
logger
124161
markly
125162
openfeature-sdk!
126163
rake (~> 13.0)

Rakefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@ RSpec::Core::RakeTask.new(:spec)
77

88
require "standard/rake"
99

10+
desc "Run Cucumber Gherkin feature tests"
11+
task :cucumber do
12+
sh "bundle exec cucumber"
13+
end
14+
1015
task default: %i[spec standard]

cucumber.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
default: spec/open-feature-spec/specification/assets/gherkin --require features --tags "not @deprecated" --publish-quiet
2+
deprecated: spec/open-feature-spec/specification/assets/gherkin --require features --publish-quiet
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# frozen_string_literal: true
2+
3+
# Before hook that adds context entries for "Before Hooks" level testing
4+
class ContextAddingBeforeHook
5+
include OpenFeature::SDK::Hooks::Hook
6+
7+
attr_reader :context_entries
8+
9+
def initialize
10+
@context_entries = {}
11+
end
12+
13+
def add_entry(key, value)
14+
@context_entries[key] = value
15+
end
16+
17+
def before(hook_context:, hints:)
18+
return nil if @context_entries.empty?
19+
20+
OpenFeature::SDK::EvaluationContext.new(**@context_entries.transform_keys(&:to_sym))
21+
end
22+
end
23+
24+
Given("a stable provider with retrievable context is registered") do
25+
@capturing_provider = ContextCapturingProvider.new
26+
OpenFeature::SDK.set_provider_and_wait(@capturing_provider)
27+
@context_hook = ContextAddingBeforeHook.new
28+
@invocation_context_entries = {}
29+
@client_context_entries = {}
30+
end
31+
32+
Given("A context entry with key {string} and value {string} is added to the {string} level") do |key, value, level|
33+
case level
34+
when "API"
35+
existing = OpenFeature::SDK.evaluation_context
36+
new_fields = existing ? existing.fields.merge(key => value) : {key => value}
37+
OpenFeature::SDK.configure do |config|
38+
config.evaluation_context = OpenFeature::SDK::EvaluationContext.new(**new_fields.transform_keys(&:to_sym))
39+
end
40+
when "Transaction"
41+
propagator = OpenFeature::SDK.configuration.transaction_context_propagator
42+
unless propagator
43+
propagator = OpenFeature::SDK::ThreadLocalTransactionContextPropagator.new
44+
OpenFeature::SDK.configure do |config|
45+
config.transaction_context_propagator = propagator
46+
end
47+
end
48+
existing = propagator.get_transaction_context
49+
new_fields = existing ? existing.fields.merge(key => value) : {key => value}
50+
propagator.set_transaction_context(
51+
OpenFeature::SDK::EvaluationContext.new(**new_fields.transform_keys(&:to_sym))
52+
)
53+
when "Client"
54+
@client_context_entries[key] = value
55+
when "Invocation"
56+
@invocation_context_entries[key] = value
57+
when "Before Hooks"
58+
@context_hook.add_entry(key, value)
59+
end
60+
end
61+
62+
When("Some flag was evaluated") do
63+
client_context = unless @client_context_entries.empty?
64+
OpenFeature::SDK::EvaluationContext.new(**@client_context_entries.transform_keys(&:to_sym))
65+
end
66+
67+
@client = OpenFeature::SDK.build_client(evaluation_context: client_context)
68+
@client.hooks = [@context_hook]
69+
70+
invocation_context = unless @invocation_context_entries.empty?
71+
OpenFeature::SDK::EvaluationContext.new(**@invocation_context_entries.transform_keys(&:to_sym))
72+
end
73+
74+
@client.fetch_boolean_value(
75+
flag_key: "boolean-flag",
76+
default_value: false,
77+
evaluation_context: invocation_context
78+
)
79+
end
80+
81+
Then("The merged context contains an entry with key {string} and value {string}") do |key, value|
82+
merged = @capturing_provider.last_context
83+
expect(merged).not_to be_nil, "Expected merged context to be present"
84+
expect(merged.fields[key]).to eq(value), "Expected context[#{key}] = #{value}, got #{merged.fields[key].inspect}"
85+
end
86+
87+
# -- Precedence table steps --
88+
89+
Given("A table with levels of increasing precedence") do |table|
90+
@precedence_levels = table.raw.flatten
91+
end
92+
93+
Given("Context entries for each level from API level down to the {string} level, with key {string} and value {string}") do |target_level, key, value_template|
94+
@precedence_levels.each do |level|
95+
step_value = level
96+
step "A context entry with key \"#{key}\" and value \"#{step_value}\" is added to the \"#{level}\" level"
97+
break if level == target_level
98+
end
99+
end

0 commit comments

Comments
 (0)