Skip to content

Commit e42f430

Browse files
author
Kevin Paulisse
committed
Start spec test for API v1 config
1 parent 4769985 commit e42f430

5 files changed

Lines changed: 212 additions & 20 deletions

File tree

lib/octocatalog-diff/api/v1/config.rb

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ module API
77
module V1
88
# This class interacts with the configuration file typically named `.octocatalog-diff.cfg.rb`.
99
class Config
10+
# Error class for handled configuration file errors
11+
class ConfigurationFileNotFoundError < RuntimeError; end
12+
class ConfigurationFileContentError < RuntimeError; end
13+
1014
# Default directory paths: These are the documented default locations that will be checked
1115
# for the configuration file.
1216
DEFAULT_PATHS = [
@@ -24,7 +28,7 @@ class Config
2428
#
2529
# @param :filename [String] Specified file name (default = search the default paths)
2630
# @param :logger [Logger] Logger object
27-
# @param :test [Boolean] Configuration file test mode only (test then exit)
31+
# @param :test [Boolean] Configuration file test mode (log some extra debugging, raises errors)
2832
# @return [Hash] Parsed configuration file
2933
def self.config(options_in = {})
3034
# Initialize the logger - if not passed, set to a throwaway object.
@@ -33,23 +37,20 @@ def self.config(options_in = {})
3337
# Locate the configuration file
3438
paths = [options.fetch(:filename, DEFAULT_PATHS)].compact
3539
config_file = first_file(paths)
40+
41+
# Can't find the configuration file?
3642
if config_file.nil?
3743
message = "Unable to find configuration file in #{paths.join(':')}"
38-
raise Errno::ENOENT, message if options[:test]
44+
raise ConfigurationFileNotFoundError, message if options[:test]
3945
logger.debug message
4046
return {}
4147
end
4248

43-
# Load the configuration file
49+
# Load/parse the configuration file - this returns a hash
4450
settings = load_config_file(config_file, logger)
45-
raise 'Configuration file failed to return a hash' unless settings.is_a?(Hash)
4651

4752
# Debug the configuration file if requested.
48-
if options[:test]
49-
debug_config_file(settings, logger)
50-
logger.info 'Exiting now because --config-test was specified'
51-
exit 0
52-
end
53+
debug_config_file(settings, logger) if options[:test]
5354

5455
# Return the settings hash
5556
logger.debug "Loaded #{settings.keys.size} settings from #{config_file}"
@@ -61,9 +62,11 @@ def self.config(options_in = {})
6162
# @param settings [Hash] Parsed settings from load_config_file
6263
# @param logger [Logger] Logger object
6364
def self.debug_config_file(settings, logger)
64-
settings.each do |key, val|
65-
logger.debug ":#{key} => (#{val.class}) #{val.inspect}"
65+
unless settings.is_a?(Hash)
66+
raise ArgumentError, "Settings must be hash not #{settings.class}"
6667
end
68+
69+
settings.each { |key, val| logger.debug ":#{key} => (#{val.class}) #{val.inspect}" }
6770
end
6871

6972
# Private: Load the configuration file from a given path. Returns the settings hash.
@@ -72,25 +75,38 @@ def self.debug_config_file(settings, logger)
7275
# @param logger [Logger] Logger object
7376
# @return [Hash] Settings
7477
def self.load_config_file(filename, logger)
78+
# This should never happen unless somebody calls this method directly outside of
79+
# the published `.config` method. Check for problems anyway.
80+
raise Errno::ENOENT, "File #{filename} doesn't exist" unless File.file?(filename)
81+
82+
# Attempt to require in the file. Problems here will fall through to the rescued
83+
# exception below.
7584
logger.debug "Loading octocatalog-diff configuration from #{filename}"
76-
require filename
85+
load filename
7786

87+
# The required file should contain `OctocatalogDiff::Config` with `.config` method.
88+
# If this is undefined, raise an exception.
7889
begin
79-
options = OctocatalogDiff::Config.config
80-
rescue => exc
81-
logger.fatal "#{exc.class} error with #{filename}: #{exc.message}\n#{exc.backtrace}"
82-
exit 1
90+
loaded_class = Kernel.const_get(:OctocatalogDiff).const_get(:Config)
91+
rescue NameError
92+
raise ConfigurationFileContentError, 'Configuration must define OctocatalogDiff::Config!'
93+
end
94+
95+
unless loaded_class.respond_to?(:config)
96+
raise ConfigurationFileContentError, 'Configuration must define OctocatalogDiff::Config.config!'
8397
end
8498

99+
# The configuration file looks like it defines the correct method, so read it.
100+
# Make sure it's a hash.
101+
options = loaded_class.config
85102
unless options.is_a?(Hash)
86-
logger.fatal "Configuration must be Hash not #{options.class}!"
87-
exit 1
103+
raise ConfigurationFileContentError, "Configuration must be Hash not #{options.class}!"
88104
end
89105

90106
options
91-
rescue => exc
107+
rescue Exception => exc # rubocop:disable Lint/RescueException
92108
logger.fatal "#{exc.class} error with #{filename}: #{exc.message}\n#{exc.backtrace}"
93-
raise 'Unable to load octocatalog-diff configuration file'
109+
raise exc
94110
end
95111

96112
# Private: Find the first element of the given array that is a file and return it.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# This is a configuration file for octocatalog-diff
2+
3+
module OctocatalogDiff
4+
class Config
5+
def self.config
6+
%w(apple banana)
7+
end
8+
end
9+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# This is a configuration file for octocatalog-diff
2+
3+
module OctocatalogDiff
4+
class Config
5+
def self.foobuzz
6+
{
7+
header: :default,
8+
hiera_config: 'config/hiera.yaml',
9+
hiera_path: 'hieradata'
10+
}
11+
end
12+
end
13+
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Ruby is not here.
2+
It should fail to compile.
3+
And raise an error.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# frozen_string_literal: true
2+
3+
require 'ostruct'
4+
5+
require_relative '../../spec_helper'
6+
7+
require OctocatalogDiff::Spec.require_path('/api/v1/config')
8+
9+
describe OctocatalogDiff::API::V1::Config do
10+
before(:each) do
11+
@logger, @logger_str = OctocatalogDiff::Spec.setup_logger
12+
end
13+
14+
after(:each) do
15+
begin
16+
OctocatalogDiff.send(:remove_const, :Config)
17+
rescue NameError # rubocop:disable Lint/HandleExceptions
18+
# Don't care if it's not defined already
19+
end
20+
end
21+
22+
describe '#config' do
23+
end
24+
25+
describe '#debug_config_file' do
26+
it 'should raise error if non-hash is passed in' do
27+
expect do
28+
described_class.debug_config_file(nil, @logger)
29+
end.to raise_error(ArgumentError, /Settings must be hash not NilClass/)
30+
end
31+
32+
it 'should log keys and values in the hash' do
33+
described_class.debug_config_file({ foo: 'bar', baz: ['buzz'] }, @logger)
34+
expect(@logger_str.string).to match(/:foo => \(String\) "bar"/)
35+
expect(@logger_str.string).to match(/:baz => \(Array\) \["buzz"\]/)
36+
end
37+
end
38+
39+
describe '#load_config_file' do
40+
context 'with a non-existent file' do
41+
let(:filename) { OctocatalogDiff::Spec.fixture_path('this-does-not-exist.rb') }
42+
43+
it 'should raise Errno::ENOENT' do
44+
expect do
45+
described_class.load_config_file(filename, @logger)
46+
end.to raise_error(Errno::ENOENT, /File.+doesn't exist/)
47+
end
48+
49+
it 'should log fatal message' do
50+
exception = nil
51+
begin
52+
described_class.load_config_file(filename, @logger)
53+
rescue Errno::ENOENT => exc
54+
exception = exc
55+
end
56+
expect(exception.message).to match(/this-does-not-exist.rb doesn't exist/)
57+
expect(@logger_str.string).to match(/FATAL .+ Errno::ENOENT error with .+this-does-not-exist.rb/)
58+
end
59+
end
60+
61+
context 'with an invalid file' do
62+
let(:filename) { OctocatalogDiff::Spec.fixture_path('cli-configs/not-ruby.rb') }
63+
64+
it 'should raise SyntaxError' do
65+
expect do
66+
described_class.load_config_file(filename, @logger)
67+
end.to raise_error(SyntaxError)
68+
end
69+
70+
it 'should log fatal message' do
71+
exception = nil
72+
begin
73+
described_class.load_config_file(filename, @logger)
74+
rescue SyntaxError => exc
75+
exception = exc
76+
end
77+
expect(exception).to be_a_kind_of(SyntaxError)
78+
expect(exception.message).to match(/unexpected tIDENTIFIER/)
79+
expect(@logger_str.string).to match(/DEBUG -- : Loading octocatalog-diff configuration from/)
80+
expect(@logger_str.string).to match(/FATAL .+ SyntaxError error with .+not-ruby.rb/)
81+
end
82+
end
83+
84+
context 'with a file that throws an exception' do
85+
let(:filename) { OctocatalogDiff::Spec.fixture_path('cli-configs/invalid.rb') }
86+
87+
it 'should raise RuntimeError' do
88+
expect do
89+
described_class.load_config_file(filename, @logger)
90+
end.to raise_error(RuntimeError, /Fizz Buzz/)
91+
end
92+
93+
it 'should log fatal message' do
94+
exception = nil
95+
begin
96+
described_class.load_config_file(filename, @logger)
97+
rescue RuntimeError => exc
98+
exception = exc
99+
end
100+
expect(exception).to be_a_kind_of(RuntimeError)
101+
expect(exception.message).to eq('Fizz Buzz')
102+
expect(@logger_str.string).to match(/DEBUG -- : Loading octocatalog-diff configuration from/)
103+
expect(@logger_str.string).to match(/FATAL .+ RuntimeError error with .+invalid.rb/)
104+
end
105+
end
106+
107+
context 'with a file that does not define a .config method' do
108+
let(:filename) { OctocatalogDiff::Spec.fixture_path('cli-configs/not-proper.rb') }
109+
110+
it 'should raise ConfigurationFileContentError' do
111+
pattern = Regexp.new('must define OctocatalogDiff::Config.config!')
112+
expect do
113+
described_class.load_config_file(filename, @logger)
114+
end.to raise_error(OctocatalogDiff::API::V1::Config::ConfigurationFileContentError, pattern)
115+
end
116+
117+
it 'should log fatal message' do
118+
exception = nil
119+
begin
120+
described_class.load_config_file(filename, @logger)
121+
rescue OctocatalogDiff::API::V1::Config::ConfigurationFileContentError => exc
122+
exception = exc
123+
end
124+
expect(exception).to be_a_kind_of(OctocatalogDiff::API::V1::Config::ConfigurationFileContentError)
125+
expect(exception.message).to eq('Configuration must define OctocatalogDiff::Config.config!')
126+
expect(@logger_str.string).to match(/Configuration must define OctocatalogDiff::Config.config!/)
127+
end
128+
end
129+
130+
context 'with a file that does not define a hash' do
131+
end
132+
133+
context 'with a valid file' do
134+
let(:filename) { OctocatalogDiff::Spec.fixture_path('cli-configs/valid.rb') }
135+
136+
it 'should return the expected settings' do
137+
result = described_class.load_config_file(filename, @logger)
138+
answer = { header: :default, hiera_config: 'config/hiera.yaml', hiera_path: 'hieradata' }
139+
expect(result).to eq(answer)
140+
end
141+
142+
it 'should log debug message' do
143+
described_class.load_config_file(filename, @logger)
144+
expect(@logger_str.string).to match(/DEBUG -- : Loading octocatalog-diff configuration from/)
145+
end
146+
end
147+
end
148+
149+
describe '#first_file' do
150+
end
151+
end

0 commit comments

Comments
 (0)