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: 2 additions & 0 deletions components/ruby/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# Feel free to correct anything in this file

AllCops:
TargetRubyVersion: 3.1
67 changes: 35 additions & 32 deletions components/ruby/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,36 +1,23 @@
PATH
remote: .
specs:
chef-licensing (1.2.0)
activesupport (~> 7.2, >= 7.2.2.1)
chef-licensing (1.2.1)
chef-config (>= 15)
faraday (>= 1, < 2)
faraday-http-cache
faraday_middleware (~> 1.0)
mixlib-log (~> 3.0)
ostruct (~> 0.1.0)
pstore (~> 0.1.1)
tty-prompt (~> 0.23)
tty-spinner (~> 0.9.3)

GEM
remote: https://rubygems.org/
specs:
activesupport (7.2.2.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
ast (2.4.3)
base64 (0.3.0)
benchmark (0.5.0)
bigdecimal (3.3.1)
byebug (12.0.0)
chef-config (18.8.46)
Expand All @@ -44,14 +31,12 @@ GEM
concurrent-ruby
coderay (1.1.3)
concurrent-ruby (1.3.5)
connection_pool (2.5.4)
cookstyle (8.5.0)
rubocop (= 1.81.0)
crack (1.0.1)
crack (1.0.0)
bigdecimal
rexml
diff-lcs (1.6.2)
drb (2.2.3)
faraday (1.10.4)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
Expand All @@ -77,23 +62,34 @@ GEM
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.1)
faraday (~> 1.0)
ffi (1.17.2)
ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-aarch64-linux-musl)
ffi (1.17.2-arm-linux-gnu)
ffi (1.17.2-arm-linux-musl)
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x64-mingw-ucrt)
ffi (1.17.2-x86-linux-gnu)
ffi (1.17.2-x86-linux-musl)
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
ffi (1.17.2-x86_64-linux-musl)
ffi-win32-extensions (1.0.4)
ffi
fuzzyurl (0.9.0)
hashdiff (1.2.1)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
json (2.15.1)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
method_source (1.1.0)
minitest (5.26.0)
mixlib-config (3.0.27)
tomlrb
mixlib-log (3.2.3)
ffi (>= 1.15.5)
mixlib-shellout (3.3.9)
chef-utils
mixlib-shellout (3.3.9-x64-mingw-ucrt)
chef-utils
ffi-win32-extensions (~> 1.0.3)
Expand All @@ -111,17 +107,18 @@ GEM
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
pstore (0.1.4)
public_suffix (6.0.2)
racc (1.8.1)
rainbow (3.1.1)
rake (13.3.0)
regexp_parser (2.11.3)
rexml (3.4.4)
rspec (3.13.2)
rspec (3.13.1)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.6)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
Expand All @@ -146,7 +143,6 @@ GEM
prism (~> 1.4)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
securerandom (0.4.1)
tomlrb (1.3.0)
tty-color (0.6.0)
tty-cursor (0.7.1)
Expand All @@ -160,11 +156,7 @@ GEM
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.1.0)
unicode-display_width (2.6.0)
webmock (3.25.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
Expand All @@ -175,7 +167,18 @@ GEM
wmi-lite (1.0.7)

PLATFORMS
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-musl
arm64-darwin
ruby
x64-mingw-ucrt
x86-linux-gnu
x86-linux-musl
x86_64-darwin
x86_64-linux-gnu
x86_64-linux-musl

DEPENDENCIES
byebug
Expand All @@ -187,4 +190,4 @@ DEPENDENCIES
webmock (~> 3.25, >= 3.25.1)

BUNDLED WITH
2.3.27
2.6.9
3 changes: 2 additions & 1 deletion components/ruby/chef-licensing.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ Gem::Specification.new do |spec|
spec.add_dependency "tty-prompt", "~> 0.23"
spec.add_dependency "faraday", ">= 1", "< 2"
spec.add_dependency "faraday-http-cache"
spec.add_dependency "activesupport", "~> 7.2", ">= 7.2.2.1"
spec.add_dependency "faraday_middleware", "~> 1.0"
spec.add_dependency "tty-spinner", "~> 0.9.3"
spec.add_dependency "mixlib-log", "~> 3.0"

# Gem dependency needed with Ruby 3.4 upgrade
spec.add_dependency "ostruct", "~> 0.1.0"
spec.add_dependency "pstore", "~> 0.1.1"
end
2 changes: 2 additions & 0 deletions components/ruby/lib/chef-licensing/list_license_keys.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
require_relative "exceptions/describe_error"
require "pastel" unless defined?(Pastel)
require_relative "config"
require_relative "string_refinements"

module ChefLicensing
class ListLicenseKeys
using StringRefinements

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick, I see we use string refinements only here, and only for the purpose of pluralize may be we should just call it as a util and use the util directly something like below,
Chef::Licensing::StringUtils.present?(str)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I did it as a string refinement was because was one of the prior uses for ActiveSupport. I'm absolutely fine with a module with an explicit method call, but assumed that that wasn't the desire of the original code.

def self.display(opts = {})
new(opts).display
end
Expand Down
36 changes: 36 additions & 0 deletions components/ruby/lib/chef-licensing/pstore_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require "pstore"

module ChefLicensing
# Simple adapter to make PStore compatible with Faraday::HttpCache
class PStoreAdapter
def initialize(cache_dir)
@store_path = File.join(cache_dir, "chef_licensing_cache.pstore")
@store = PStore.new(@store_path)
end

# Interface methods required by faraday-http-cache
def read(key)
@store.transaction { @store[key] }
end

def write(key, value, options = {})
# PStore handles persistence, Faraday::HttpCache handles
# HTTP cache headers for expiration
@store.transaction { @store[key] = value }
end

def delete(key)
@store.transaction { @store.delete(key) }
end

def exist?(key)
@store.transaction { @store.root?(key) }
end

def clear
@store.transaction do
@store.roots.each { |root| @store.delete(root) }
end
end
end
end
5 changes: 3 additions & 2 deletions components/ruby/lib/chef-licensing/restful_client/base.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
require "faraday" unless defined?(Faraday)
require "faraday_middleware"
require "faraday/http_cache"
require "active_support"
require "tmpdir" unless defined?(Dir.mktmpdir)
require_relative "../exceptions/restful_client_error"
require_relative "../exceptions/restful_client_connection_error"
require_relative "../exceptions/missing_api_credentials_error"
require_relative "../config"
require_relative "../pstore_adapter"
require_relative "middleware/exceptions_handler"
require_relative "middleware/content_type_validator"

Expand Down Expand Up @@ -132,7 +133,7 @@ def handle_post_connection(url = nil)
end

def get_connection(url = nil)
store = ::ActiveSupport::Cache.lookup_store(:file_store, Dir.tmpdir)
store = PStoreAdapter.new(Dir.tmpdir)
Faraday.new(url: url) do |config|
config.request :json
config.response :json, parser_options: { object_class: OpenStruct }
Expand Down
25 changes: 25 additions & 0 deletions components/ruby/lib/chef-licensing/string_refinements.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module ChefLicensing
# String refinements to provide pluralization functionality
# Replaces ActiveSupport::Inflector for our specific use case
module StringRefinements
refine String do
def pluralize(count = 2)
return self if count == 1

# Simple pluralization rules
case downcase
when /s$/, /sh$/, /ch$/, /x$/, /z$/
"#{self}es"
when /[^aeiou]y$/
"#{self[0..-2]}ies"
when /f$/
"#{self[0..-2]}ves"
when /fe$/
"#{self[0..-3]}ves"
else
"#{self}s"
Comment on lines +9 to +20

Copilot AI Nov 3, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The downcase method is called on the string but the original case is used in the return values. This will cause incorrect capitalization in pluralized strings. For example, 'Day'.pluralize(2) will return 'Days' but should preserve the original case as 'Days'.

Suggested change
# Simple pluralization rules
case downcase
when /s$/, /sh$/, /ch$/, /x$/, /z$/
"#{self}es"
when /[^aeiou]y$/
"#{self[0..-2]}ies"
when /f$/
"#{self[0..-2]}ves"
when /fe$/
"#{self[0..-3]}ves"
else
"#{self}s"
# Determine the pluralized form in lowercase
word = self
plural =
case word.downcase
when /s$/, /sh$/, /ch$/, /x$/, /z$/
"#{word}es"
when /[^aeiou]y$/
"#{word[0..-2]}ies"
when /f$/
"#{word[0..-2]}ves"
when /fe$/
"#{word[0..-3]}ves"
else
"#{word}s"
end
# Preserve original capitalization pattern
if word.match?(/\A[A-Z]+\z/)
plural.upcase
elsif word.match?(/\A[A-Z][a-z]+\z/)
plural.capitalize
elsif word.match?(/\A[a-z]+\z/)
plural.downcase
else
plural

Copilot uses AI. Check for mistakes.
end
end
end
end
end
61 changes: 61 additions & 0 deletions components/ruby/spec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
Test harness notes for the chef-licensing specs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Purpose
-------
This file documents the decisions made in `spec/spec_helper.rb` to keep the
suite deterministic and hermetic.

Key points
----------
- WebMock is required before the library (`require "webmock/rspec"`) so any
HTTP client initialization that happens at require-time is intercepted.
This prevents accidental real HTTP connections during test bootstrap.

- The tests set a deterministic license server URL via
`ENV["CHEF_LICENSE_SERVER"]` and `ENV["LICENSE_SERVER"]` so tests don't
rely on developer/CI environment values or persisted files that might
point to production servers.

- `ENV.delete("CHEF_LICENSE_KEY")` is used to ensure tests don't pick up
real credentials from the environment.

- A suite-level temporary HOME (`TMP_TEST_HOME`) is created early so
require-time code that consults `ENV["HOME"]` or `ENV["USERPROFILE"]`
doesn't see a developer/CI home. Some specs still require a fresh HOME
per-example; the harness provides an `around(:each)` hook that swaps in a
temporary HOME for the duration of those examples.

- Tests that need HTTP interactions should stub them explicitly using
WebMock's `stub_request`. Consider adding common stubs to
`spec/support/license_server_stubs.rb` to reduce duplication.

Running the tests
-----------------
From the `components/ruby` directory run:

```bash
bundle exec rspec --format=documentation
```

If you only want to run a single spec file:

```bash
bundle exec rspec spec/path/to/file_spec.rb
```

Adding a new spec
-----------------
- If your spec touches the user's HOME, prefer creating temporary dirs
under `Dir.mktmpdir` and avoid writing into the suite-level HOME
directly. Use the provided per-example HOME swap when you need a
pristine HOME for each example.

- If your spec makes HTTP calls, add a `stub_request` for the exact
request the code will issue. If multiple specs need the same stubs,
factor them into `spec/support/license_server_stubs.rb` and require that
from `spec/spec_helper.rb`.

Contact
-------
If you have questions about why this setup exists, check the Git history
for `spec/spec_helper.rb` or ask the maintainer team.
Loading
Loading