From cff1bdf587c34bc73e89961fe7154836c9cc4a2d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Franti=C5=A1ek=20Dvo=C5=99=C3=A1k?= Date: Wed, 22 Jun 2016 00:02:54 +0200 Subject: [PATCH] Refactoring of the model classes, created unit-tests. --- .rubocop.yml | 4 - .travis.yml | 1 + Gemfile.devel | 6 +- application.rb | 2 +- lib/nebula.rb | 5 +- models/helpers/nowhash.rb | 45 ++++++++ models/network.rb | 257 +++++++++----------------------------------- models/range.rb | 198 +++++++--------------------------- spec/models/network_spec.rb | 195 +++++++++++++++++++++++++++++++++ spec/models/range_spec.rb | 92 ++++++++++++++++ spec/spec_helper.rb | 5 + swagger.yaml | 3 + 12 files changed, 439 insertions(+), 374 deletions(-) create mode 100644 models/helpers/nowhash.rb create mode 100644 spec/models/network_spec.rb create mode 100644 spec/models/range_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.rubocop.yml b/.rubocop.yml index f297369..41683a4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,10 +1,6 @@ AllCops: DisplayCopNames: true DisplayStyleGuide: true - Exclude: - # exclude generated code (maybe we'll rewrite it anyway one day) - - models/network.rb - - models/range.rb # Assignment Branch Condition size for initialize is too high # Perceived complexity for deep_merge is too high diff --git a/.travis.yml b/.travis.yml index 6c696f5..808e015 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,5 @@ rvm: script: - bundle exec rubocop + - bundle exec rspec - bundle exec ruby -rminitest/autorun -Ilib:test -e 'Dir.glob "./test/*_test.rb", &method(:require)' diff --git a/Gemfile.devel b/Gemfile.devel index 7ba90f6..f63e521 100644 --- a/Gemfile.devel +++ b/Gemfile.devel @@ -1,11 +1,13 @@ source 'https://rubygems.org' +gem 'ipaddress' gem 'opennebula' gem 'sinatra' gem 'sinatra-cross_origin' # recommended for sinatra gem 'thin' -gem 'rubocop', group: :development, require: false -gem 'rack-test', group: :development gem 'minitest', group: :development +gem 'rack-test', group: :development +gem 'rspec', group: :development +gem 'rubocop', group: :development, require: false diff --git a/application.rb b/application.rb index bd44200..3c03db5 100644 --- a/application.rb +++ b/application.rb @@ -1,6 +1,6 @@ require 'logger' -Dir['./models/*.rb'].each do |file| +Dir['./models/helpers/*.rb', './models/*.rb'].each do |file| require file end require './version' diff --git a/lib/nebula.rb b/lib/nebula.rb index a5c6ca0..7c8704d 100644 --- a/lib/nebula.rb +++ b/lib/nebula.rb @@ -1,5 +1,6 @@ require 'opennebula' require 'yaml' +require 'ipaddress' module Now @@ -138,7 +139,7 @@ module Now mask = addr_size - Math.log(size, 2).ceil logger.debug "[parse_range] id=#{id}, address=#{ip}/#{mask} (size #{size})" - return Now::Range.new(address: "#{ip}/#{mask}", allocation: 'dynamic') + return Now::Range.new(address: IPAddress.parse("#{ip}/#{mask}"), allocation: 'dynamic') end def parse_ranges(vn_id, vn) @@ -166,7 +167,7 @@ module Now end def parse_network(vn) - logger.debug "[parse_network] #{vn.to_hash}" + logger.debug "[parse_network] #{vn.to_xml}" id = vn.id title = vn.name diff --git a/models/helpers/nowhash.rb b/models/helpers/nowhash.rb new file mode 100644 index 0000000..e13d257 --- /dev/null +++ b/models/helpers/nowhash.rb @@ -0,0 +1,45 @@ +module Now + + # Generic hash class with custom accessors and helper methods + class NowHash < ::Hash + def self.my_accessor(*keys) + keys.each do |key| + define_method(key) do + return nil if !key?(key) + fetch(key) + end + define_method("#{key}=") do |new_value| + if new_value.nil? + delete(key) + else + store(key, new_value) + end + end + end + end + + def initialize(parameters = {}) + parameters.select! { |_k, v| !v.nil? } + replace(parameters) + end + + # Conversion of the data structure to hash. Arrays and hashes are browsed, the leafs are converted by calling to_hash method or directly copied. + # @param [Object] value Any valid value + # @return [Hash] Returns the value in the form of hash + def _to_hash(value) + if value.is_a?(Array) + value.map { |v| _to_hash(v) } + # beware we're the Hash!!! + elsif value.is_a?(Hash) && !value.is_a?(Now::NowHash) + {}.tap do |hash| + value.each { |k, v| hash[k] = _to_hash(v) } + end + elsif value.respond_to? :to_hash + value.to_hash + else + value + end + end + end + +end diff --git a/models/network.rb b/models/network.rb index ba966db..23e2b40 100644 --- a/models/network.rb +++ b/models/network.rb @@ -1,156 +1,81 @@ -=begin -Network Orchestrator API - -OpenAPI spec version: 0.0.0 - -Partially generated by: https://github.com/swagger-api/swagger-codegen.git - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -=end - require 'date' module Now + # Network object - class Network + class Network < NowHash # OpenNebula ID - attr_accessor :id + my_accessor :id # Network title - attr_accessor :title + my_accessor :title # Network summary - attr_accessor :description + my_accessor :description # Owner - attr_accessor :user + my_accessor :user # VLAN ID - attr_accessor :vlan - - attr_accessor :range - - # Network state (active, inactive, error) - attr_accessor :state - - # Availability zone (cluster) - attr_accessor :zone - - - # Attribute mapping from ruby-style variable name to JSON key. - def self.attribute_map - { - :'id' => :'id', - :'title' => :'title', - :'description' => :'description', - :'user' => :'user', - :'vlan' => :'vlan', - :'range' => :'range', - :'state' => :'state', - :'zone' => :'zone' - } - end + my_accessor :vlan - # Attribute type mapping. - def self.swagger_types - { - :'id' => :'Integer', - :'title' => :'String', - :'description' => :'String', - :'user' => :'String', - :'vlan' => :'Integer', - :'range' => :'Range', - :'state' => :'String', - :'zone' => :'String' - } + # IP address range (reader) + def range + return nil if !key?(:range) + fetch(:range) end - # Initializes the object - # @param [Hash] attributes Model attributes in the form of hash - def initialize(attributes = {}) - return unless attributes.is_a?(Hash) - - # convert string to symbol for hash key - attributes = attributes.each_with_object({}){|(k,v), h| h[k.to_sym] = v} - - if attributes.has_key?(:'id') - self.id = attributes[:'id'] - end - - if attributes.has_key?(:'title') - self.title = attributes[:'title'] - end - - if attributes.has_key?(:'description') - self.description = attributes[:'description'] - end - - if attributes.has_key?(:'user') - self.user = attributes[:'user'] + # IP address range (writer) + def range=(new_value) + if !valid_range?(new_value) + raise NowError.new(500), 'Invalid range type' end + store(:range, new_value) + end - if attributes.has_key?(:'vlan') - self.vlan = attributes[:'vlan'] - end + # Network state (active, inactive, error) + my_accessor :state - if attributes.has_key?(:'range') - self.range = attributes[:'range'] - end + # Availability zone (cluster) + my_accessor :zone - if attributes.has_key?(:'state') - self.state = attributes[:'state'] + def initialize(parameters = {}) + if !parameters.key?(:id) + raise NowError.new(500), 'ID required in network object' end - - if attributes.has_key?(:'zone') - self.zone = attributes[:'zone'] + if parameters.key?(:range) && !valid_range?(parameters[:range]) + raise NowError.new(500), 'Valid range object required in network object' end - - end - - # Show invalid properties with the reasons. Usually used together with valid? - # @return Array for valid properies with the reasons - def list_invalid_properties - invalid_properties = Array.new - return invalid_properties + super end # Check to see if the all the properties in the model are valid # @return true if the model is valid def valid? - return false if @id.nil? + return false if id.nil? + return false if !valid_range?(range) return true end # Checks equality by comparing each attribute. # @param [Object] Object to be compared - def ==(o) - return true if self.equal?(o) - self.class == o.class && - id == o.id && - title == o.title && - description == o.description && - user == o.user && - vlan == o.vlan && - range == o.range && - state == o.state && - zone == o.zone + def ==(other) + return true if equal?(other) + self.class == other.class && + id == other.id && + title == other.title && + description == other.description && + user == other.user && + vlan == other.vlan && + range == other.range && + state == other.state && + zone == other.zone end # @see the `==` method # @param [Object] Object to be compared - def eql?(o) - self == o + def eql?(other) + self == other end # Calculates hash code according to all attributes. @@ -159,110 +84,28 @@ module Now [id, title, description, user, vlan, range, state, zone].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.swagger_types.each_pair do |key, type| - if type =~ /^Array<(.*)>/i - # check to ensure the input is an array given that the the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map{ |v| _deserialize($1, v) } ) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end # or else data not found in attributes(hash), not an issue as the data can be optional - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :DateTime - DateTime.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :BOOLEAN - if value.to_s =~ /^(true|t|yes|y|1)$/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - {}.tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - temp_model = Now.const_get(type).new - temp_model.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s to_hash.to_s end - # to_body is an alias to to_hash (backward compatibility) - # @return [Hash] Returns the object in the form of hash - def to_body - to_hash - end - # Returns the object in the form of hash # @return [Hash] Returns the object in the form of hash def to_hash hash = {} - self.class.attribute_map.each_pair do |attr, param| - value = self.send(attr) - next if value.nil? - hash[param] = _to_hash(value) + each_pair do |attr, value| + hash[attr] = _to_hash(value) end - hash - end - # Outputs non-array value in the form of hash - # For object, use to_hash. Otherwise, just return the value - # @param [Object] value Any valid value - # @return [Hash] Returns the value in the form of hash - def _to_hash(value) - if value.is_a?(Array) - value.compact.map{ |v| _to_hash(v) } - elsif value.is_a?(Hash) - {}.tap do |hash| - value.each { |k, v| hash[k] = _to_hash(v) } - end - elsif value.respond_to? :to_hash - value.to_hash - else - value - end + return hash end + private + + def valid_range?(value) + value.nil? || value.is_a?(Now::Range) + end end end diff --git a/models/range.rb b/models/range.rb index 0b4f556..0a17712 100644 --- a/models/range.rb +++ b/models/range.rb @@ -1,96 +1,56 @@ -=begin -Network Orchestrator API - -OpenAPI spec version: 0.0.0 - -Partially generated by: https://github.com/swagger-api/swagger-codegen.git - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -=end - -require 'date' +require 'ipaddress' module Now - # Address range - class Range - # Address range (CIDR notation) - attr_accessor :address - - # Address allocation type (static, dynamic) - attr_accessor :allocation + # Address range + class Range < NowHash - # Attribute mapping from ruby-style variable name to JSON key. - def self.attribute_map - { - :'address' => :'address', - :'allocation' => :'allocation' - } + # Address range in CIDR notation (reader) + def address + fetch(:address) end - # Attribute type mapping. - def self.swagger_types - { - :'address' => :'String', - :'allocation' => :'String' - } + # Address range in CIDR notation (writer) + def address=(new_value) + if !valid_address?(new_value) + raise NowError.new(500), 'Internal error: Invalid IP network address' + end + store(:address, new_value) end - # Initializes the object - # @param [Hash] attributes Model attributes in the form of hash - def initialize(attributes = {}) - return unless attributes.is_a?(Hash) - - # convert string to symbol for hash key - attributes = attributes.each_with_object({}){|(k,v), h| h[k.to_sym] = v} + # Address allocation type (static, dynamic) + my_accessor :allocation - if attributes.has_key?(:'address') - self.address = attributes[:'address'] + def initialize(parameters = {}) + if !parameters.key?(:address) + raise NowError.new(500), 'Internal error: IP network address required' end - - if attributes.has_key?(:'allocation') - self.allocation = attributes[:'allocation'] + if !valid_address?(parameters[:address]) + raise NowError.new(500), 'Internal error: Invalid IP network address' end - - end - - # Show invalid properties with the reasons. Usually used together with valid? - # @return Array for valid properies with the reasons - def list_invalid_properties - invalid_properties = Array.new - return invalid_properties + super end # Check to see if the all the properties in the model are valid # @return true if the model is valid def valid? + return false if !valid_address?(address) return true end # Checks equality by comparing each attribute. # @param [Object] Object to be compared - def ==(o) - return true if self.equal?(o) - self.class == o.class && - address == o.address && - allocation == o.allocation + def ==(other) + return true if equal?(other) + self.class == other.class && + address == other.address && + allocation == other.allocation end # @see the `==` method # @param [Object] Object to be compared - def eql?(o) - self == o + def eql?(other) + self == other end # Calculates hash code according to all attributes. @@ -99,108 +59,30 @@ module Now [address, allocation].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.swagger_types.each_pair do |key, type| - if type =~ /^Array<(.*)>/i - # check to ensure the input is an array given that the the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map{ |v| _deserialize($1, v) } ) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end # or else data not found in attributes(hash), not an issue as the data can be optional - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :DateTime - DateTime.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :BOOLEAN - if value.to_s =~ /^(true|t|yes|y|1)$/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - {}.tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - temp_model = Now.const_get(type).new - temp_model.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s to_hash.to_s end - # to_body is an alias to to_hash (backward compatibility) - # @return [Hash] Returns the object in the form of hash - def to_body - to_hash - end - # Returns the object in the form of hash # @return [Hash] Returns the object in the form of hash def to_hash - hash = {} - self.class.attribute_map.each_pair do |attr, param| - value = self.send(attr) - next if value.nil? - hash[param] = _to_hash(value) + h = {} + if key?(:address) + h[:address] = "#{address}/#{address.prefix}" end - hash + if key?(:allocation) + h[:allocation] = allocation + end + + return h end - # Outputs non-array value in the form of hash - # For object, use to_hash. Otherwise, just return the value - # @param [Object] value Any valid value - # @return [Hash] Returns the value in the form of hash - def _to_hash(value) - if value.is_a?(Array) - value.compact.map{ |v| _to_hash(v) } - elsif value.is_a?(Hash) - {}.tap do |hash| - value.each { |k, v| hash[k] = _to_hash(v) } - end - elsif value.respond_to? :to_hash - value.to_hash - else - value - end + private + + def valid_address?(value) + !value.nil? && value.is_a?(IPAddress) end end diff --git a/spec/models/network_spec.rb b/spec/models/network_spec.rb new file mode 100644 index 0000000..c85a010 --- /dev/null +++ b/spec/models/network_spec.rb @@ -0,0 +1,195 @@ +require 'spec_helper' + +describe Now::Network do + context '#type check' do + it 'no id raises NowError' do + expect { Now::Network.new }.to raise_error(Now::NowError) + end + it 'string range raises NowError' do + expect { Now::Network.new(id: 0, range: 'eee') }.to raise_error(Now::NowError) + end + end + + context '#no addess range' do + let(:network) { Now::Network.new(id: 0) } + let(:range) { Now::Range.new(address: IPAddress.parse('fd00::/8')) } + let(:range2) { Now::Range.new(address: IPAddress.parse('fd00::/8')) } + let(:hash) { { id: 0 } } + let(:hash_rich) do + { + id: 1, + title: 'Title 1', + description: 'Description 1', + user: 'spike', + vlan: 100, + range: { + address: 'fd00::/8', + }, + zone: '2', + } + end + + it 'is a network' do + expect(network).to be_kind_of Now::Network + end + it 'address range is nil' do + expect(network.range).to be nil + end + it 'is valid' do + expect(network.valid?).to be true + end + it 'still valid with addess range' do + network.range = range + expect(network.valid?).to be true + end + it 'attributes can be set, rich to_hash works' do + network.id = 1 + network.title = 'Title 1' + network.description = 'Description 1' + network.user = 'spike' + network.vlan = 100 + network.range = range + network.zone = '2' + + expect(network.id).to eq(1) + expect(network.title).to eq('Title 1') + expect(network.description).to eq('Description 1') + expect(network.user).to eq('spike') + expect(network.vlan).to eq(100) + expect(network.range).to eq(range2) + expect(network.zone).to eq('2') + + expect(network.to_hash).to eq(hash_rich) + end + it 'to_hash works' do + expect(network.to_hash).to eq(hash) + end + end + + context '#basic' do + let(:range) { Now::Range.new(address: IPAddress.parse('192.168.0.1/24')) } + let(:network) { Now::Network.new(id: 0, range: range) } + let(:hash) do + { + id: 0, + range: { + address: '192.168.0.1/24', + }, + } + end + + it 'is a network' do + expect(network).to be_kind_of Now::Network + end + it 'is valid' do + expect(network.valid?).to be true + end + it 'still valid with nil address range' do + network.range = nil + expect(network.valid?).to be true + end + it 'to_hash works' do + expect(network.to_hash).to eq(hash) + end + end + + context '#basic IPv6' do + let(:range) { Now::Range.new(address: IPAddress.parse('fd00::/8')) } + let(:network) { Now::Network.new(id: 1, range: range) } + let(:hash) do + { + id: 1, + range: { + address: 'fd00::/8', + } + } + end + it 'is a network' do + expect(network).to be_kind_of Now::Network + end + it 'is valid' do + expect(network.valid?).to be true + end + it 'still valid with nil address range' do + network.range = nil + expect(network.valid?).to be true + end + it 'to_hash works' do + expect(network.to_hash).to eq(hash) + end + end + + context '#basic set' do + let(:range) { Now::Range.new(address: IPAddress.parse('172.16.0.0/12')) } + let(:network) do + n = Now::Network.new(id: 2) + n.range = range + n.title = 'Title' + n.description = 'Description' + n.user = 'fluttershy' + n + end + let(:hash) do + { + id: 2, + title: 'Title', + description: 'Description', + user: 'fluttershy', + range: { + address: '172.16.0.0/12', + }, + } + end + + it 'is a network' do + expect(network).to be_kind_of Now::Network + end + it 'is valid' do + expect(network.valid?).to be true + end + it 'still valid with nil address range' do + network.range = nil + expect(network.valid?).to be true + end + it 'to_hash works' do + expect(network.to_hash).to eq(hash) + end + end + + context '#basic IPv6 set' do + let(:range) { Now::Range.new(address: IPAddress.parse('fd00::/8')) } + let(:network) do + n = Now::Network.new(id: 2) + n.range = range + n.title = 'Title' + n.description = 'Description' + n.user = 'fluttershy' + n + end + let(:hash) do + { + id: 2, + title: 'Title', + description: 'Description', + user: 'fluttershy', + range: { + address: 'fd00::/8', + }, + } + end + + it 'is a network' do + expect(network).to be_kind_of Now::Network + end + it 'is valid' do + expect(network.valid?).to be true + end + it 'still valid with nil address range' do + network.range = nil + expect(network.valid?).to be true + end + it 'to_hash works' do + expect(network.to_hash).to eq(hash) + end + end +end diff --git a/spec/models/range_spec.rb b/spec/models/range_spec.rb new file mode 100644 index 0000000..69523ce --- /dev/null +++ b/spec/models/range_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' + +describe Now::Range do + context '#type check' do + it 'string address raises NowError' do + expect { Now::Range.new(address: 'eee') }.to raise_error(Now::NowError) + end + it 'no address raises NowError' do + expect { Now::Range.new }.to raise_error(Now::NowError) + end + end + + context '#basic' do + let(:range) { Now::Range.new(address: IPAddress.parse('192.168.0.1/24')) } + let(:hash) { { address: '192.168.0.1/24' } } + + it 'is a range' do + expect(range).to be_kind_of Now::Range + end + it 'is valid' do + expect(range.valid?).to be true + end + it 'setting nil address raises error' do + expect { range.address = nil }.to raise_error(Now::NowError) + end + it 'to_hash works' do + expect(range.to_hash).to eq(hash) + end + end + + context '#basic IPv6' do + let(:range) { Now::Range.new(address: IPAddress.parse('fd00::/8')) } + let(:hash) { { address: 'fd00::/8' } } + + it 'is a range' do + expect(range).to be_kind_of Now::Range + end + it 'is valid' do + expect(range.valid?).to be true + end + it 'setting nil address raises error' do + expect { range.address = nil }.to raise_error(Now::NowError) + end + it 'to_hash works' do + expect(range.to_hash).to eq(hash) + end + end + + context '#basic set' do + let(:range) do + r = Now::Range.new(address: IPAddress.parse('172.16.0.0/12')) + r.allocation = 'dynamic' + r + end + let(:hash) { { address: '172.16.0.0/12', allocation: 'dynamic' } } + + it 'is a range' do + expect(range).to be_kind_of Now::Range + end + it 'is valid' do + expect(range.valid?).to be true + end + it 'setting nil address raises error' do + expect { range.address = nil }.to raise_error(Now::NowError) + end + it 'to_hash works' do + expect(range.to_hash).to eq(hash) + end + end + + context '#basic IPv6 set' do + let(:range) do + r = Now::Range.new(address: IPAddress.parse('fd00::/8')) + r.allocation = 'dynamic' + r + end + let(:hash) { { address: 'fd00::/8', allocation: 'dynamic' } } + + it 'is a range' do + expect(range).to be_kind_of Now::Range + end + it 'is valid' do + expect(range.valid?).to be true + end + it 'setting nil address raises error' do + expect { range.address = nil }.to raise_error(Now::NowError) + end + it 'to_hash works' do + expect(range.to_hash).to eq(hash) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..6398913 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,5 @@ +require 'rspec' + +Dir['./models/helpers/*.rb', './models/*.rb', './lib/*.rb'].each do |file| + require file +end diff --git a/swagger.yaml b/swagger.yaml index aeebeaf..47735a4 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -66,6 +66,8 @@ definitions: Range: description: "Address range" type: "object" + required: + - "address" properties: address: type: "string" @@ -97,6 +99,7 @@ definitions: type: "integer" format: "int64" range: + description: "IP address range" $ref: "#/definitions/Range" zone: description: "Availability zone (cluster)" -- 1.8.2.3