Set up rspec, add specs for models

This commit is contained in:
Anton Chuchkalov 2015-11-03 11:46:54 +03:00
parent 391f2add9c
commit 65b2e02fe9
57 changed files with 1551 additions and 95 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
devops-service/tests/features/support/config.yml
.devops_files/
devops-service/plugins
devops-service/spec/examples.txt

3
devops-service/.rspec Normal file
View File

@ -0,0 +1,3 @@
--color
--format progress
--require spec_helper

View File

@ -23,12 +23,17 @@ gem 'hooks'
#gem "devops-nibr", :path => "../../devops-nibr"
group :test do
gem 'cucumber'
gem 'test-unit'
gem 'httpclient'
gem 'rspec', '~>3.3'
gem 'factory_girl', '~>4.5'
gem 'activesupport'
end
group :devepoment do
gem 'byebug'
gem 'guard-rspec', require: false
end

View File

@ -2,6 +2,12 @@ GEM
remote: https://rubygems.org/
specs:
CFPropertyList (2.3.1)
activesupport (4.2.4)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.3.8)
backports (3.6.4)
bson (1.12.3)
@ -60,6 +66,8 @@ GEM
erubis (2.7.0)
eventmachine (1.0.7)
excon (0.45.3)
factory_girl (4.5.0)
activesupport (>= 3.0.0)
ffi (1.9.9)
ffi-yajl (1.4.0)
ffi (~> 1.5)
@ -163,18 +171,38 @@ GEM
formatador (0.2.5)
gherkin (2.12.2)
multi_json (~> 1.3)
guard (2.13.0)
formatador (>= 0.2.4)
listen (>= 2.7, <= 4.0)
lumberjack (~> 1.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-rspec (4.6.4)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
hashie (2.1.2)
highline (1.7.2)
hooks (0.4.0)
uber (~> 0.0.4)
httpclient (2.6.0.1)
i18n (0.7.0)
inflecto (0.0.2)
ipaddress (0.8.0)
json (1.8.3)
libyajl2 (1.2.0)
listen (3.0.4)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
lumberjack (1.0.9)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.2)
minitest (5.8.2)
mixlib-authentication (1.3.0)
mixlib-log
mixlib-cli (1.5.0)
@ -185,6 +213,7 @@ GEM
bson (= 1.12.3)
multi_json (1.7.8)
multi_test (0.1.2)
nenv (0.2.0)
net-dhcp (1.3.2)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
@ -196,6 +225,9 @@ GEM
net-ssh-gateway (>= 1.2.0)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
notiffany (0.0.8)
nenv (~> 0.1)
shellany (~> 0.0)
ohai (8.0.1)
ffi (~> 1.9)
ffi-yajl (~> 1.1)
@ -222,6 +254,9 @@ GEM
rack-test (0.6.3)
rack (>= 1.0)
rake (10.2.0)
rb-fsevent (0.9.6)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
redis (3.2.1)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
@ -249,6 +284,7 @@ GEM
rspec (~> 3.0)
rspec-its
specinfra (~> 2.35)
shellany (0.0.1)
sidekiq (3.2.6)
celluloid (= 0.15.2)
connection_pool (>= 2.0.0)
@ -281,8 +317,12 @@ GEM
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
thor (0.19.1)
thread_safe (0.3.5)
tilt (1.4.1)
timers (1.1.0)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uber (0.0.13)
uuidtools (2.1.5)
wisper (1.6.0)
@ -292,11 +332,14 @@ PLATFORMS
ruby
DEPENDENCIES
activesupport
bson_ext
byebug
chef (>= 12)
cucumber
factory_girl (~> 4.5)
fog (~> 1.20)
guard-rspec
hooks
httpclient
mime-types (~> 1.25.1)
@ -306,6 +349,7 @@ DEPENDENCIES
rack (= 1.5.2)
rack-accept-media-types
rake (= 10.2.0)
rspec (~> 3.3)
sidekiq (= 3.2.6)
sinatra (= 1.4.5)
sinatra-contrib

44
devops-service/Guardfile Normal file
View File

@ -0,0 +1,44 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
## Uncomment and set this to only include directories you want to watch
# directories %w(app lib config test spec features) \
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
## Note: if you are using the `directories` clause above and you are not
## watching the project directory ('.'), then you will want to move
## the Guardfile to a watched dir and symlink it back, e.g.
#
# $ mkdir config
# $ mv Guardfile config/
# $ ln -s config/Guardfile .
#
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
# Note: The cmd option is now required due to the increasing number of ways
# rspec may be run, below are examples of the most common uses.
# * bundler: 'bundle exec rspec'
# * bundler binstubs: 'bin/rspec'
# * spring: 'bin/rspec' (This will use spring if running and you have
# installed the spring binstubs per the docs)
# * zeus: 'zeus rspec' (requires the server to be started separately)
# * 'just' rspec: 'rspec'
guard :rspec, cmd: "rspec" do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
# Feel free to open issues for suggestions and improvements
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
# Factories files
watch(%r{spec/factories/.+\.rb}) { rspec.spec_dir }
# Devops files
watch(%r{db/.+\.rb}) { rspec.spec_dir }
end

View File

@ -5,7 +5,7 @@ module Devops
def self.inherited(base)
@@applications << base.new
puts "Devops application '#{base}' has been added"
puts "Devops application '#{base}' has been added" unless ENV['RACK_ENV'] == 'test'
end
def self.applications

View File

@ -12,21 +12,7 @@ module Devops
class CloudDeployEnv < DeployEnvBase
attr_accessor :flavor, :image, :subnets, :groups, :stack_template
=begin
@Deprecated
types :identifier => {:type => String, :empty => false},
:image => {:type => String, :empty => false},
:flavor => {:type => String, :empty => false},
:provider => {:type => String, :empty => false},
:expires => {:type => String, :empty => false, :nil => true},
:run_list => {:type => Array, :empty => true},
:users => {:type => Array, :empty => true},
:subnets => {:type => Array, :empty => true},
:groups => {:type => Array, :empty => false},
:stack_template => {:type => String, :empty => false, :nil => true}
=end
#TODO: account validator
set_validators ::Validators::DeployEnv::Flavor,
::Validators::DeployEnv::Image,
::Validators::DeployEnv::Groups,

View File

@ -69,7 +69,7 @@ module Devops
role = res[:new] || res[:exist]
self.run_list << "role[#{role[0]}]" unless role.nil?
Devops::Db.connector.set_project_deploy_env_field(project_id, old_name, {"identifier" => new_name, "run_list" => self.run_list})
msg = "Project '#{project_id}': environment '#{old_name}' has been renamed to '#{new_name}'." + Project.create_roles_response(res)
msg = "Project '#{project_id}': environment '#{old_name}' has been renamed to '#{new_name}'." + ::Devops::Model::Project.create_roles_response(res)
DevopsLogger.logger.info(msg)
msg
end

View File

@ -40,8 +40,6 @@ module Devops
DeployEnvEc2.new(hash)
end
private
def subnets_filter
networks = provider_instance.networks

View File

@ -4,16 +4,16 @@ module Devops
module Model
class DeployEnvOpenstack < CloudDeployEnv
types :identifier => {:type => String, :empty => false},
:image => {:type => String, :empty => false},
:flavor => {:type => String, :empty => false},
:provider => {:type => String, :empty => false},
:expires => {:type => String, :empty => false, :nil => true},
:run_list => {:type => Array, :empty => true},
:users => {:type => Array, :empty => true},
:subnets => {:type => Array, :empty => true},
:groups => {:type => Array, :empty => false},
:stack_template => {:type => String, :empty => false, :nil => true}
# types :identifier => {:type => String, :empty => false},
# :image => {:type => String, :empty => false},
# :flavor => {:type => String, :empty => false},
# :provider => {:type => String, :empty => false},
# :expires => {:type => String, :empty => false, :nil => true},
# :run_list => {:type => Array, :empty => true},
# :users => {:type => Array, :empty => true},
# :subnets => {:type => Array, :empty => true},
# :groups => {:type => Array, :empty => false},
# :stack_template => {:type => String, :empty => false, :nil => true}
=begin
set_validators ::Validators::DeployEnv::RunList,
@ -31,8 +31,6 @@ module Devops
DeployEnvOpenstack.new(hash)
end
private
def subnets_filter
nil
end

View File

@ -7,6 +7,7 @@ module Devops
module Model
class DeployEnvStatic < DeployEnvBase
# TODO: all code except self.create could be safely removed
types :identifier => {:type => String, :empty => false},
:provider => {:type => String, :empty => false},
:expires => {:type => String, :empty => false, :nil => true},

View File

@ -65,13 +65,12 @@ module Devops
end
def to_hash_without_id
o = {
"provider" => self.provider,
"name" => self.name,
"remote_user" => self.remote_user
{
"provider" => provider,
"name" => name,
"remote_user" => remote_user,
"bootstrap_template" => bootstrap_template
}
o["bootstrap_template"] = self.bootstrap_template
o
end
def self.create_from_json! json

View File

@ -39,11 +39,10 @@ module Devops
end
def to_hash_without_id
o = {
{
"path" => self.path,
"scope" => self.scope
}
o
end
end

View File

@ -59,6 +59,7 @@ module Devops
# TODO: we should validate type in request parser
# self.validate_fields_types
self.class.validate_model(self)
validate_fields!
true
rescue InvalidRecord => e
error_message = self.build_error_message(e.message)
@ -81,6 +82,13 @@ module Devops
true
end
def valid?
validate!
true
rescue InvalidRecord
false
end
def build_error_message(message)
# overrided in descendants
message
@ -157,6 +165,7 @@ module Devops
end
end
# validate field value
# if method validate! returns false, then stop validation without error
def set_field_validators field, *validators

View File

@ -1,7 +1,7 @@
require "db/mongo/models/mongo_model"
require "db/mongo/models/model_with_provider"
require "db/validators/deploy_env/run_list"
require "providers/static"
module Devops
module Model
@ -62,16 +62,9 @@ module Devops
self
end
def create provider, image, flavor, subnets, groups, out
res = {}
res[:before] = self.run_hook :before_create
return false unless provider.create_server(self, image, flavor, subnets, groups, out)
Devops::Db.connector.server_insert self
res[:after] = self.run_hook :after_create
res
end
def validate!
# remove it after updating from .types to validators
validate_fields_types
super
true
end

View File

@ -19,6 +19,38 @@ module Devops
run_list: {type: Array, value_type: String, empty: true, nil: true}
# details: {type: Hash, nil: true} # Hash type isn't supported yet
set_field_validators :id, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_field_validators :provider, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_field_validators :project, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_field_validators :deploy_env, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_field_validators :stack_template, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_field_validators :name, [::Validators::FieldValidator::Nil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_field_validators :owner, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_field_validators :run_list, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::Array,
::Validators::FieldValidator::RunList]
def initialize attrs={}
# self.provider = self.class.provider
self.set_provider(attrs)
@ -34,6 +66,7 @@ module Devops
self
end
# TODO: use string keys
def to_hash_without_id
{
project: project,
@ -44,7 +77,8 @@ module Devops
# details are required to proper status handling
details: bson_safe_details,
stack_status: stack_status,
owner: owner
owner: owner,
run_list: run_list
}.merge(provider_hash)
end

View File

@ -1,16 +1,19 @@
require 'db/mongo/models/stack/stack_base'
module Devops
module Model
class StackEc2 < StackBase
def initialize attr={}
self.provider = 'ec2'
super(attr)
self.provider = 'ec2'
end
def stack_status
self.details['StackStatus'] if self.details
end
# creation date is instance of unsupported by mongo class, so stringify it
def bson_safe_details
return unless details
result = details.dup

View File

@ -1,3 +1,4 @@
require 'db/mongo/models/stack/stack_base'
module Devops
module Model
class StackOpenstack < StackBase

View File

@ -16,6 +16,23 @@ module Devops
template_body: {type: String, empty: false},
owner: {type: String, empty: false}
set_field_validators :id, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty,
::Validators::FieldValidator::Name]
set_field_validators :provider, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_field_validators :template_body, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_field_validators :owner, [::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
::Validators::FieldValidator::NotEmpty]
set_validators ::Validators::StackTemplate::TemplateContent
def initialize(attrs)

View File

@ -5,7 +5,7 @@ module Devops
# In Amazon Cloudformation the template file must be stored on an Amazon S3 bucket.
attr_accessor :template_url
types template_url: {type: String, empty: false}
# types template_url: {type: String, empty: false}
def initialize(attrs)
self.template_url = attrs['template_url']

View File

@ -1,5 +1,6 @@
require "exceptions/invalid_record"
require "exceptions/invalid_command"
require "exceptions/invalid_privileges"
require "db/mongo/models/mongo_model"
module Devops
@ -12,10 +13,7 @@ module Devops
PRIVILEGES = ["r", "w", "x"]
PRIVILEGES_REGEX = /^r?w?x?$/
attr_accessor :id, :password, :privileges, :email
types :id => {:type => String, :empty => false},
:email => {:type => String, :empty => false},
:password => {:type => String, :empty => true}
KNOWN_ENTITIES = %w(flavor group image project server key user filter network provider script templates stack_template stack)
set_field_validators :id, ::Validators::FieldValidator::NotNil,
::Validators::FieldValidator::FieldType::String,
@ -54,7 +52,7 @@ module Devops
if !priv.empty? and PRIVILEGES_REGEX.match(priv).to_s.empty?
raise InvalidCommand.new "Invalid privileges '#{priv}'. Available values are '#{PRIVILEGES.join("', '")}'"
end
raise InvalidPrivileges.new "Can't grant privileges to root" if self.id == ROOT_USER_NAME
raise ::InvalidPrivileges.new "Can't grant privileges to root" if self.id == ROOT_USER_NAME
case cmd
when "all"
@ -114,22 +112,7 @@ module Devops
def privileges_with_value value, options={}
privileges = {}
[
'flavor',
'group',
'image',
'project',
'server',
'key',
'user',
'filter',
'network',
'provider',
'script',
'templates',
'stack_template',
'stack'
].each { |t| privileges.store(t, value) }
KNOWN_ENTITIES.each { |t| privileges.store(t, value) }
privileges.merge(options)
end

View File

@ -8,6 +8,8 @@ module Validators
def validate!
raise InvalidRecord.new(message) unless valid?
rescue StandardError => e
raise InvalidRecord.new("An error raised during validation with #{self.class}: #{e.class}: #{e.message}")
end
def valid?

View File

@ -2,7 +2,8 @@ module Validators
class DeployEnv::Flavor < Base
def valid?
@model.provider_instance.flavors.detect do |flavor|
return true unless @model.flavor
available_flavors.detect do |flavor|
flavor['id'] == @model.flavor
end
end
@ -10,5 +11,11 @@ module Validators
def message
"Invalid flavor '#{@model.flavor}'."
end
private
def available_flavors
@model.provider_instance.flavors
end
end
end

View File

@ -2,13 +2,20 @@ module Validators
class DeployEnv::Groups < Base
def valid?
subnets_filter = @model.send(:subnets_filter)
@invalid_groups = @model.groups - @model.provider_instance.groups(subnets_filter).keys
return true if @model.groups.nil?
@invalid_groups = @model.groups - available_groups
@invalid_groups.empty?
end
def message
"Invalid groups '#{@invalid_groups.join("', '")}'."
end
private
def available_groups
subnets_filter = @model.subnets_filter
@model.provider_instance.groups(subnets_filter).keys
end
end
end

View File

@ -5,8 +5,8 @@ module Validators
include ::ImageCommands
def valid?
images = get_available_provider_images(::Devops::Db.connector, @model.provider)
images.detect do |image|
return true unless @model.image
available_images.detect do |image|
image["id"] == @model.image
end
end
@ -14,5 +14,11 @@ module Validators
def message
"Invalid image '#{@model.image}'."
end
private
def available_images
get_available_provider_images(::Devops::Db.connector, @model.provider)
end
end
end

View File

@ -2,20 +2,23 @@ module Validators
class DeployEnv::StackTemplate < Base
def valid?
if @model.stack_template
begin
Devops::Db.connector.stack_template(@model.stack_template)
true
rescue RecordNotFound => e
false
end
else
true
return true unless @model.stack_template
available_stack_templates.detect do |template|
template['id'] == @model.stack_template
end
end
def message
"Invalid stack template '#{@model.stack_template}'."
end
private
def available_stack_templates
# map to hash to simplify mocks. Later replace this method with something more suitable
Devops::Db.connector.stack_templates.map(&:to_hash)
end
end
end

View File

@ -4,7 +4,7 @@ module Validators
class Flavor < Base
def valid?
@model.provider_instance.flavors.detect do |flavor|
available_flavors.detect do |flavor|
flavor['id'] == @value
end
end
@ -12,6 +12,12 @@ module Validators
def message
"Invalid flavor '#{@value}'."
end
private
def available_flavors
@model.provider_instance.flavors
end
end
end
end

View File

@ -7,8 +7,7 @@ module Validators
include ::ImageCommands
def valid?
images = get_available_provider_images(::Devops::Db.connector, @model.provider)
images.detect do |image|
available_images.detect do |image|
image["id"] == @value
end
end
@ -16,6 +15,12 @@ module Validators
def message
"Invalid image '#{@value}'."
end
private
def available_images
get_available_provider_images(::Devops::Db.connector, @model.provider)
end
end
end
end

View File

@ -2,13 +2,18 @@ module Validators
class Helpers::Users < Base
def valid?
mongo_users = ::Devops::Db.connector.users_names(@model)
@nonexistent_users = @model - mongo_users
@nonexistent_users = (@model || []) - available_users
@nonexistent_users.empty?
end
def message
Devops::Messages.t("project.deploy_env.validation.users.not_exist", users: @nonexistent_users.join("', '"))
end
private
def available_users
::Devops::Db.connector.users_names(@model)
end
end
end

View File

@ -9,8 +9,7 @@ module Validators
def valid?
if @model.bootstrap_template
templates = get_templates
templates.include?(@model.bootstrap_template)
available_templates.include?(@model.bootstrap_template)
else
true
end
@ -19,6 +18,12 @@ module Validators
def message
"Invalid bootstrap template '#{@model.bootstrap_template}' for image '#{@model.id}'"
end
private
def available_templates
get_templates
end
end
end
end

View File

@ -5,12 +5,17 @@ module Validators
include ImageCommands
def valid?
images = get_available_provider_images(::Devops::Db.connector, @model.provider)
images.map{|i| i["id"]}.include?(@model.id)
available_images.map{|i| i["id"]}.include?(@model.id)
end
def message
"Invalid image id '#{@model.id}' for provider '#{@model.provider}', please check image filters"
end
private
def available_images
get_available_provider_images(::Devops::Db.connector, @model.provider)
end
end
end

View File

@ -0,0 +1,28 @@
require 'db/mongo/models/deploy_env/deploy_env_ec2'
require 'db/mongo/models/deploy_env/deploy_env_openstack'
require 'db/mongo/models/deploy_env/deploy_env_static'
FactoryGirl.define do
factory :deploy_env, class: Devops::Model::DeployEnvBase do
identifier 'name'
run_list []
users ['root']
factory :deploy_env_static, class: Devops::Model::DeployEnvStatic do
provider 'static'
end
factory :cloud_deploy_env, class: Devops::Model::CloudDeployEnv do
flavor 'flavor'
image 'image'
factory :deploy_env_ec2, class: Devops::Model::DeployEnvEc2 do
provider 'ec2'
end
factory :deploy_env_openstack, class: Devops::Model::DeployEnvOpenstack do
provider 'openstack'
end
end
end
end

View File

@ -0,0 +1,10 @@
require 'db/mongo/models/image'
FactoryGirl.define do
factory :image, class: Devops::Model::Image do
id 'test_image'
remote_user 'root'
name 'my_image'
provider 'ec2'
end
end

View File

@ -0,0 +1,8 @@
require 'db/mongo/models/key'
FactoryGirl.define do
factory :key, class: Devops::Model::Key do
path SpecSupport::BLANK_FILE
scope 'user'
end
end

View File

@ -0,0 +1,20 @@
require 'db/mongo/models/report'
FactoryGirl.define do
factory :report, class: Devops::Model::Report do
id 'id'
file 'path'
created_by 'root'
project 'project'
deploy_env 'env'
type 1
created_at Time.now
updated_at Time.now
status 'completed'
initialize_with {
new(attributes.stringify_keys)
}
end
end

View File

@ -0,0 +1,19 @@
require 'db/mongo/models/server'
FactoryGirl.define do
factory :server, class: Devops::Model::Server do
id 'server2'
provider 'ec2'
remote_user 'root'
project 'project'
deploy_env 'env'
private_ip '192.168.0.1'
key 'key_id'
created_by 'root'
chef_node_name 'chef_node_name'
reserved_by 'root'
run_list []
last_operation_type 'creation'
last_operation_at Time.now
end
end

View File

@ -0,0 +1,24 @@
require 'db/mongo/models/stack/stack_ec2'
require 'db/mongo/models/stack/stack_openstack'
FactoryGirl.define do
factory :stack, class: Devops::Model::StackBase do
id 'name'
project 'project'
deploy_env 'deploy_env'
stack_template 'template_id'
name 'iamstack'
owner 'root'
run_list []
initialize_with { new(attributes.stringify_keys) }
factory :stack_ec2, class: Devops::Model::StackEc2 do
provider 'ec2'
end
factory :stack_openstack, class: Devops::Model::StackOpenstack do
provider 'openstack'
end
end
end

View File

@ -0,0 +1,20 @@
require 'db/mongo/models/stack_template/stack_template_ec2'
require 'db/mongo/models/stack_template/stack_template_openstack'
FactoryGirl.define do
factory :stack_template, class: Devops::Model::StackTemplateBase do
id 'name'
template_body '{}'
owner 'root'
initialize_with { new(attributes.stringify_keys) }
factory :stack_template_ec2, class: Devops::Model::StackTemplateEc2 do
provider 'ec2'
end
factory :stack_template_openstack, class: Devops::Model::StackTemplateOpenstack do
provider 'openstack'
end
end
end

View File

@ -0,0 +1,9 @@
require 'db/mongo/models/user'
FactoryGirl.define do
factory :user, class: Devops::Model::User do
id 'john'
password '123456'
email 'test@google.com'
end
end

View File

@ -0,0 +1,66 @@
require 'db/mongo/models/deploy_env/deploy_env_ec2'
RSpec.describe Devops::Model::DeployEnvEc2, type: :model do
let(:env) { build(:deploy_env_ec2) }
before do
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(ec2))
allow_any_instance_of(Validators::Helpers::Users).to receive(:available_users).and_return(['root'])
allow_any_instance_of(Validators::DeployEnv::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}])
allow_any_instance_of(Validators::FieldValidator::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}])
allow_any_instance_of(Validators::DeployEnv::Groups).to receive(:available_groups).and_return(['default'])
allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}])
allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}])
allow_any_instance_of(Validators::DeployEnv::StackTemplate).to receive(:available_stack_templates).and_return([{'id' => 'template'}])
allow_any_instance_of(Validators::FieldValidator::Image).to receive(:available_images).and_return([{'id' => 'image'}])
end
it_behaves_like 'deploy env'
it_behaves_like 'cloud deploy env'
describe '#initialize' do
it 'keep only first subnet in given array' do
env = described_class.new('subnets' => %w(foo bar))
expect(env.subnets).to match_array(%w(foo))
end
end
describe '#to_hash' do
it 'includes vpc_id' do
expect(env.to_hash).to include('vpc_id')
end
end
describe '.create' do
it 'returns instance of DeployEnvEc2' do
expect(described_class.create({})).to be_an_instance_of(described_class)
end
end
describe '#subnets_filter' do
before do
allow_any_instance_of(described_class).to receive_message_chain('provider_instance.networks') {
[{'name' => 'foo', 'vpcId' => 'bar'}]
}
end
it 'returns nil if #subnets are empty' do
expect(build(:deploy_env_ec2, subnets: []).subnets_filter).to be_nil
end
context "when provider has env's network" do
it 'returns hash with vpcId of that network' do
env = build(:deploy_env_ec2, subnets: ['foo'])
expect(env.subnets_filter).to eq({'vpc-id' => 'bar'})
end
end
context "when provider hasn't env's network" do
it 'returns nil' do
env = build(:deploy_env_ec2, subnets: ['wrong'])
expect(env.subnets_filter).to be nil
end
end
end
end

View File

@ -0,0 +1,32 @@
require 'db/mongo/models/deploy_env/deploy_env_openstack'
RSpec.describe Devops::Model::DeployEnvOpenstack, type: :model do
let(:env) { build(:deploy_env_openstack) }
before do
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(openstack))
allow_any_instance_of(Validators::Helpers::Users).to receive(:available_users).and_return(['root'])
allow_any_instance_of(Validators::DeployEnv::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}])
allow_any_instance_of(Validators::FieldValidator::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}])
allow_any_instance_of(Validators::DeployEnv::Groups).to receive(:available_groups).and_return(['default'])
allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}])
allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}])
allow_any_instance_of(Validators::DeployEnv::StackTemplate).to receive(:available_stack_templates).and_return([{'id' => 'template'}])
allow_any_instance_of(Validators::FieldValidator::Image).to receive(:available_images).and_return([{'id' => 'image'}])
end
it_behaves_like 'deploy env'
it_behaves_like 'cloud deploy env'
describe '.create' do
it 'returns instance of DeployEnvOpenstack' do
expect(described_class.create({})).to be_an_instance_of(described_class)
end
end
describe '#subnets_filter' do
it 'always returns nil' do
expect(env.subnets_filter).to be nil
end
end
end

View File

@ -0,0 +1,19 @@
require 'db/mongo/models/deploy_env/deploy_env_static'
RSpec.describe Devops::Model::DeployEnvStatic, type: :model do
let(:env) { build(:deploy_env_static) }
before do
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(static))
allow_any_instance_of(Validators::Helpers::Users).to receive(:available_users).and_return(['root'])
end
it_behaves_like 'deploy env'
describe '.create' do
it 'returns instance of DeployEnvStatic' do
expect(described_class.create({})).to be_an_instance_of(Devops::Model::DeployEnvStatic)
end
end
end

View File

@ -0,0 +1,47 @@
require 'db/mongo/models/image'
RSpec.describe Devops::Model::Image, type: :model do
let(:image) { build(:image) }
let(:name_with_dash) { 'asd-asd' }
let(:name_with_slash) { 'asd/asd' }
before do
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(openstack ec2 static'))
allow_any_instance_of(Validators::Image::ImageInFilter).to receive(:available_images).and_return([{'id' => 'test_image'}, {'id' => name_with_dash}, {'id' => name_with_slash}])
end
it 'is valid with correct attrs' do
expect(image).to be_valid
end
describe 'validation' do
include_examples 'field type validation', :id, :not_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :remote_user, :not_nil, :non_empty_string, :only_word_symbols, :field_validator
include_examples 'field type validation', :name, :not_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :bootstrap_template, :maybe_nil, :non_empty_string, :only_word_symbols, :field_validator
it 'id should contain only letters, digits and dashes' do
expect(build(:image, id: name_with_dash)).to be_valid
expect(build(:image, id: name_with_slash)).not_to be_valid
end
it "id should be included in image filters" do
expect(build(:image, id: 'wrong')).not_to be_valid
end
it 'name should contain only letters, digits and dashes' do
expect(build(:image, name: name_with_slash)).not_to be_valid
end
it 'bootstrap_template should be included in available bootstrap templates' do
expect(build(:image, bootstrap_template: 'wrong')).not_to be_valid
end
end
it '#to_hash_without_id returns provider, name, remote_user and bootstrap_template' do
expect(image.to_hash_without_id.keys).to match_array(%w(provider name remote_user bootstrap_template))
end
end

View File

@ -0,0 +1,28 @@
require 'db/mongo/models/key'
RSpec.describe Devops::Model::Key, type: :model do
let(:key) { build(:key) }
it 'is valid with correct attrs' do
expect(key).to be_valid
end
describe 'validations' do
it 'key file should exist in file system' do
expect(build(:key, path: './not_exist')).not_to be_valid
end
it "scope could be 'user' or 'system'" do
expect(build(:key, scope: 'system')).to be_valid
expect(build(:key, scope: 'wrong')).not_to be_valid
end
end
it '#filename returns base name of key file' do
expect(key.filename).to eq('blank_file')
end
it '#to_hash_without_id returns path and scope' do
expect(key.to_hash_without_id.keys).to eq(%w(path scope))
end
end

View File

@ -0,0 +1,43 @@
require 'db/mongo/models/report'
RSpec.describe Devops::Model::Report, type: :model do
let(:report) { build(:report) }
describe '#initialize' do
it 'converts created_at to localtime' do
now = Time.now.utc
expect(now.zone).to eq 'UTC'
expect(
build(:report, created_at: now).created_at.zone
).not_to eq 'UTC'
end
it 'converts updated_at to localtime' do
now = Time.now.utc
expect(now.zone).to eq 'UTC'
expect(
build(:report, updated_at: now).updated_at.zone
).not_to eq 'UTC'
end
end
describe '#to_hash_without_id' do
it 'contains several fields' do
expect(report.to_hash_without_id.keys).to match_array(%w(
file
created_at
updated_at
created_by
project
deploy_env
type
chef_node_name
host
status
stack
subreports
job_result_code
))
end
end
end

View File

@ -0,0 +1,95 @@
require 'db/mongo/models/server'
RSpec.describe Devops::Model::Server, type: :model do
let(:server) { build(:server) }
before do
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(openstack ec2 static'))
end
it 'is valid with correct attrs' do
expect(server).to be_valid
end
describe 'validation rules:' do
include_examples 'field type validation', :id, :not_nil, :non_empty_string
include_examples 'field type validation', :provider, :not_nil, :non_empty_string
include_examples 'field type validation', :remote_user, :not_nil, :non_empty_string
include_examples 'field type validation', :project, :not_nil, :non_empty_string
include_examples 'field type validation', :deploy_env, :not_nil, :non_empty_string
include_examples 'field type validation', :private_ip, :not_nil, :non_empty_string
include_examples 'field type validation', :public_ip, :maybe_nil, :maybe_empty_string
include_examples 'field type validation', :key, :not_nil, :non_empty_string
include_examples 'field type validation', :created_by, :not_nil, :non_empty_string
include_examples 'field type validation', :chef_node_name, :not_nil, :maybe_empty_string
include_examples 'field type validation', :reserved_by, :not_nil, :maybe_empty_string
include_examples 'field type validation', :stack, :maybe_nil, :non_empty_string
include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array
include_examples 'field type validation', :run_list, :run_list
end
describe '#initialize' do
it 'sets run_list to empty array if it is nil' do
server = described_class.new()
expect(server.run_list).to eq []
end
end
describe '.fields' do
it 'is array with several strings' do
expect(described_class.fields).to match_array(%w(
chef_node_name project deploy_env provider remote_user
private_ip public_ip created_at created_by key reserved_by
run_list stack last_operation_type last_operation_at
))
end
end
it '#to_hash_without_id returns not nil fields' do
server = described_class.new('run_list' => [], 'project' => 'asd')
expect(server.to_hash_without_id.keys).to match_array(%w(run_list project))
server.stack = 'stack_id'
expect(server.to_hash_without_id.keys).to match_array(%w(run_list project stack))
end
describe '#info' do
it 'returns string' do
expect(server.info).to be_a(String)
end
end
describe '#static?' do
it 'returns true for servers with provider==static' do
expect(build(:server, provider: 'static').static?).to be true
end
it 'returns false for servers with provider==ec2' do
expect(server.static?).to be false
end
end
describe '#set_last_operation' do
it 'sets #last_operation_at to Time.now' do
prev_operation_time = server.last_operation_at
server.set_last_operation('deploy')
expect(server.last_operation_at).to be > prev_operation_time
end
it 'updates last_operation_type' do
server.set_last_operation('deploy')
expect(server.last_operation_type).to eq 'deploy'
end
it 'it raises error if given type is not supported' do
expect {
server.set_last_operation(:wrong)
}.to raise_error ArgumentError
end
it 'expects string as an argument' do
expect {
server.set_last_operation(:deploy)
}.to raise_error ArgumentError
end
end
end

View File

@ -0,0 +1,174 @@
require 'db/mongo/models/stack/stack_ec2'
RSpec.describe Devops::Model::StackEc2, type: :model do
let(:stack) { build(:stack_ec2) }
before do
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(ec2))
end
it 'is valid with factory attrs' do
expect(build(:stack_ec2)).to be_valid
end
describe 'validation rules:' do
include_examples 'field type validation', :id, :not_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :project, :not_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :deploy_env, :not_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :stack_template, :not_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :name, :maybe_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :owner, :not_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :run_list, :maybe_nil, :maybe_empty_array, :run_list, :field_validator
it 'validates provider' do
stack.provider = nil
expect(stack).not_to be_valid
stack.provider = ''
expect(stack).not_to be_valid
end
end
describe '#to_hash_without_id' do
it 'returns hash with several fields' do
expect(stack.to_hash_without_id.keys).to include('provider', :project, :deploy_env, :stack_template, :name, :owner, :run_list)
end
end
describe '#initialize' do
it 'sets provider to ec2 even if given attrs don\'t contain it' do
expect(described_class.new.provider).to eq 'ec2'
end
it 'sets run_list to empty array if it\'s blank' do
expect(described_class.new.run_list).to eq []
end
end
describe '#bson_safe_details' do
it 'stringify value of CreationTime key' do
stack.details = {'CreationTime' => Time.now}
expect(stack.bson_safe_details['CreationTime']).to be_a(String)
end
end
describe '#create_stack_in_cloud!' do
it 'calls create_stack method of provider instance' do
expect(stack).to receive_message_chain('provider_instance.create_stack')
stack.create_stack_in_cloud!('')
end
it 'raises InvalidRecord if stack with such name already exists in cloud' do
allow(stack).to receive_message_chain('provider_instance.create_stack').and_raise(ProviderErrors::NameConflict)
expect { stack.create_stack_in_cloud!('') }.to raise_error(InvalidRecord)
end
end
describe '#delete_stack_in_cloud!' do
it 'calls delete_stack method of provider instance' do
expect(stack).to receive_message_chain('provider_instance.delete_stack').with(stack)
stack.delete_stack_in_cloud!
end
end
describe '#template_body' do
it 'is a String' do
allow(Devops::Db).to receive_message_chain('connector.stack_template.template_body') {'body'}
expect(stack.template_body).to be_a(String)
end
end
describe '#sync_details!' do
let(:stack_with_blank_info) { stack.details = nil; stack.events=nil; stack }
let(:fresh_details) { {'StackStatus' => 'CREATE_COMPLETE'} }
let(:fresh_events) { [] }
subject { stack_with_blank_info.sync_details! }
before do
allow(stack_with_blank_info).to receive_message_chain('provider_instance.stack_details') {fresh_details}
allow(stack_with_blank_info).to receive_message_chain('provider_instance.stack_events') {fresh_events}
end
it "get fresh stack details and stores it in @details" do
expect(stack_with_blank_info).to receive_message_chain('provider_instance.stack_details')
subject
expect(stack_with_blank_info.details).to eq fresh_details
end
it "get fresh stack events and stores it in @events" do
expect(stack_with_blank_info).to receive_message_chain('provider_instance.stack_events')
subject
expect(stack_with_blank_info.events).to eq fresh_events
end
it 'updates stack status' do
subject
expect(stack_with_blank_info.stack_status).to eq 'CREATE_COMPLETE'
end
end
describe '#update_in_cloud!' do
before { allow(stack).to receive_message_chain('provider_instance.update_stack') }
it 'calls to method update_stack of provider instance' do
expect(stack).to receive_message_chain('provider_instance.update_stack')
stack.update_in_cloud!({})
end
it 'filters params to include only Parameters, TemplateBody, TemplateURL and Capabilities' do
permitted_keys = %w(Parameters TemplateBody TemplateURL Capabilities)
permitted_hash = permitted_keys.inject({}) {|hash, key| hash[key] = 1; hash}
unfiltered_hash = permitted_hash.merge('wrong' => 1)
expect(stack).to receive_message_chain('provider_instance.update_stack').with(stack, permitted_hash)
stack.update_in_cloud!(unfiltered_hash)
end
end
describe '#change_stack_template!' do
let(:new_template) { 'brand_new_template' }
before do
allow(stack).to receive(:template_body)
allow(stack).to receive(:update_in_cloud!)
allow(Devops::Db).to receive_message_chain('connector.stack_update')
end
it 'updates stack_template' do
stack.change_stack_template!(new_template)
expect(stack.stack_template).to eq new_template
end
end
describe '.create' do
subject { described_class.create({}, '') }
before do
allow_any_instance_of(described_class).to receive(:create_stack_in_cloud!)
allow_any_instance_of(described_class).to receive(:sync_details!)
end
it "returns instance of #{described_class.name}" do
expect(subject).to be_a(described_class)
end
it 'creates stack in AWS' do
expect_any_instance_of(described_class).to receive(:create_stack_in_cloud!)
subject
end
it 'synchronizes details' do
expect_any_instance_of(described_class).to receive(:sync_details!)
subject
end
end
describe '.build_from_bson' do
subject { described_class.build_from_bson('_id' => 'foo', 'name' => 'bar') }
it "returns instance of #{described_class.name} with given parameters" do
expect(subject.id).to eq 'foo'
expect(subject.name).to eq 'bar'
end
end
end

View File

@ -0,0 +1,13 @@
require 'db/mongo/models/stack_template/stack_template_ec2'
RSpec.describe Devops::Model::StackTemplateEc2, type: :model do
let(:stack_template) { build(:stack_template_ec2) }
before do
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(ec2))
allow_any_instance_of(Devops::Model::StackTemplateEc2).to receive_message_chain('provider_instance.validate_stack_template') { true }
end
it_behaves_like 'stack template'
end

View File

@ -0,0 +1,13 @@
require 'db/mongo/models/stack_template/stack_template_openstack'
RSpec.describe Devops::Model::StackTemplateOpenstack, type: :model do
let(:stack_template) { build(:stack_template_openstack) }
before do
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(openstack))
allow_any_instance_of(Devops::Model::StackTemplateOpenstack).to receive_message_chain('provider_instance.validate_stack_template') { true }
end
it_behaves_like 'stack template'
end

View File

@ -0,0 +1,107 @@
require 'db/mongo/models/user'
RSpec.describe Devops::Model::User, type: :model do
let(:user) { build(:user) }
it 'is valid with correct attrs' do
expect(user).to be_valid
end
describe 'validation' do
include_examples 'field type validation', :id, :not_nil, :non_empty_string, :only_word_symbols, :field_validator
include_examples 'field type validation', :password, :not_nil, :maybe_empty_string, :field_validator
include_examples 'field type validation', :email, :not_nil, :maybe_empty_string, :field_validator
it "privileges is set to default value if nil is passed" do
expect(user.privileges).not_to be nil
end
it 'privileges should be a hash' do
expect(build(:user, privileges: 'root')).not_to be_valid
end
end
it '#to_hash_without_id returns email, password and privileges' do
expect(user.to_hash_without_id.keys).to match_array(%w(email password privileges))
end
describe '#privileges' do
subject {user.privileges}
it { should be_a(Hash) }
it 'has entity types as keys' do
expect(subject.keys).to match_array(Devops::Model::User::KNOWN_ENTITIES)
end
end
describe '#all_privileges' do
it 'all privileges are set to rwx' do
expect(user.all_privileges.values.uniq).to eq ['rwx']
end
end
describe '#default_privileges' do
it 'user privilege is empty' do
expect(user.default_privileges['user']).to be_empty
end
it 'others are r' do
result = user.default_privileges
result.delete('user')
expect(result.values.uniq).to eq ['r']
end
end
describe '#grant' do
it 'sets privilege' do
expect {
user.grant('key', 'rw')
}.to change{ user.privileges['key'] }.from('r').to('rw')
end
it 'could not be applied to root' do
root = build(:user, id: 'root')
expect{root.grant('user', 'r')}.to raise_error(InvalidPrivileges)
end
it 'recognize all value of entity' do
user.grant('all', 'rw')
expect(user.privileges.values.uniq).to eq ['rw']
end
it 'sets default privileges if cmd is empty' do
user.grant('user', 'rw')
user.grant('', 'rwx')
expect(user.privileges).to eq user.default_privileges
end
it 'could not be applied with unknown privilege value' do
expect{user.grant('user', 'q')}.to raise_error(InvalidCommand)
end
end
describe '#check_privileges' do
it "raises InvalidPrivileges if user hasn't specified privilege" do
expect { user.check_privileges('key', 'w') }.to raise_error(InvalidPrivileges)
end
it 'does nothing is user has specified privilege' do
user.check_privileges('key', 'r')
end
end
describe '.create_root' do
subject {Devops::Model::User.create_root}
it { should be_valid }
it 'his name is root' do
expect(subject.id).to eq Devops::Model::User::ROOT_USER_NAME
end
it 'his password is root password' do
expect(subject.password).to eq Devops::Model::User::ROOT_PASSWORD
end
it 'has all privileges' do
expect(subject.privileges).to eq user.all_privileges
end
end
end

View File

@ -0,0 +1,78 @@
ENV['RACK_ENV'] ||= 'test'
require 'byebug'
require 'factory_girl'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/inflector'
# setup load_path and require support files
root = File.join(File.dirname(__FILE__), "..")
$LOAD_PATH.push root unless $LOAD_PATH.include? root
Dir[("./spec/support/**/*.rb")].each { |f| require f }
# Factory girl configuration
FactoryGirl.define do
# do not try to persist, but raise validation errors
to_create { |model| model.validate! }
end
FactoryGirl.find_definitions
# RSpec configuration
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# Allows RSpec to persist some state between runs in order to support
# the `--only-failures` and `--next-failure` CLI options. We recommend
# you configure your source control system to ignore this file.
config.example_status_persistence_file_path = "spec/examples.txt"
# Limits the available syntax to the non-monkey patched syntax that is
# recommended. For more details, see:
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
config.disable_monkey_patching!
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = 'doc'
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
# config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
end

View File

View File

@ -0,0 +1,38 @@
RSpec.shared_examples 'cloud deploy env' do
validated_model_name = described_class.name.demodulize.underscore
describe 'validation rules:' do
include_examples 'field type validation', :flavor, :maybe_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :image, :maybe_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :groups, :maybe_nil, :maybe_empty_array, :field_validator
include_examples 'field type validation', :subnets, :maybe_nil, :maybe_empty_array, :field_validator
include_examples 'field type validation', :stack_template, :maybe_nil, :non_empty_string, :field_validator
it 'flavor should be available' do
expect(build(validated_model_name, flavor: 'flavor')).to be_valid
expect(build(validated_model_name, flavor: 'wrong')).not_to be_valid
end
it 'image should be available' do
expect(build(validated_model_name, image: 'image')).to be_valid
expect(build(validated_model_name, image: 'wrong')).not_to be_valid
end
it 'all groups should be available' do
expect(build(validated_model_name, groups: ['default'])).to be_valid
expect(build(validated_model_name, groups: ['wrong'])).not_to be_valid
expect(build(validated_model_name, groups: ['wrong', 'default'])).not_to be_valid
end
it 'stack_template should be available' do
expect(build(validated_model_name, stack_template: 'template')).to be_valid
expect(build(validated_model_name, stack_template: 'wrong')).not_to be_valid
end
end
describe '#to_hash' do
it 'includes flavor, image, subnets, groups and stack_template in keys' do
expect(described_class.new.to_hash).to include('flavor', 'image', 'subnets', 'groups', 'stack_template')
end
end
end

View File

@ -0,0 +1,211 @@
require 'core/devops-application'
require 'core/devops-db'
RSpec.shared_examples 'deploy env' do
validated_model_name = described_class.name.demodulize.underscore
include SpecSupport
it 'is valid with correct attrs' do
expect(env).to be_valid
end
describe 'validation rules:' do
include_examples 'field type validation', :identifier, :not_nil, :non_empty_string, :only_word_symbols, :field_validator
include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array, :run_list, :field_validator
include_examples 'field type validation', :users, :not_nil, :maybe_empty_array, :field_validator
include_examples 'field type validation', :expires, :maybe_nil, :non_empty_string, :field_validator
it 'should be valid only with all users available' do
expect(build(validated_model_name, users: ['root'])).to be_valid
expect(build(validated_model_name, users: ['wrong'])).not_to be_valid
expect(build(validated_model_name, users: ['wrong', 'root'])).not_to be_valid
end
it 'expires should be smth like "2d" or "15h"' do
expect(build(validated_model_name, expires: '2d')).to be_valid
expect(build(validated_model_name, expires: 'wrong')).not_to be_valid
end
describe '#validate_provider!' do
it 'raises InvalidRecord if provider is nil' do
env.provider = nil
expect {env.validate_provider!}.to raise_error InvalidRecord
end
end
end
describe '#initialize' do
it 'sets identifier from given hash' do
model = described_class.new('identifier' => 'a')
expect(model.identifier).to eq 'a'
end
it 'sets provider from given hash' do
model = described_class.new('provider' => 'ec2')
expect(model.provider).to eq 'ec2'
end
it 'sets uniq elements from run list from given hash' do
model = described_class.new('run_list' => ['role[a]', 'role[a]', 'recipe[e]'])
expect(model.run_list).to match_array ['role[a]', 'recipe[e]']
end
it 'sets empty array if run list is empty' do
model = described_class.new
expect(model.run_list).to match_array []
end
it 'sets expires from given hash' do
model = described_class.new('expires' => '3d')
expect(model.expires).to eq '3d'
end
it 'sets uniq users from given hash' do
model = described_class.new('users' => ['root', 'root', 'user'])
expect(model.users).to match_array ['root', 'user']
end
end
describe '#to_hash' do
it 'includes identifier, run_list, expires, users and provider in keys' do
expect(described_class.new.to_hash).to include('identifier', 'run_list', 'expires', 'users', 'provider')
end
end
describe '#add_users' do
it "doesn't add duplications of already added users" do
env = described_class.new('users' => %w(root user))
env.add_users %w(root user)
expect(env.users).to match_array %w(root user)
end
it "adds new users" do
env = described_class.new('users' => %w(root))
env.add_users ['user1', 'user2']
expect(env.users).to match_array %w(root user1 user2)
end
end
describe '#update_field' do
subject { env.update_field('project_name', 'run_list', ['role[asd]']) }
before do
allow_message_expectations_on_nil
allow(Devops::Db.connector).to receive(:set_project_deploy_env_field)
end
it 'validate it' do
expect(env).to receive(:validate_run_list!)
subject
end
it 'updates attribute value' do
expect { subject }.to change {env.run_list}.to(['role[asd]'])
end
it 'saves it' do
expect(Devops::Db.connector).to receive(:set_project_deploy_env_field).with('project_name', env.identifier, {'run_list' => ['role[asd]']})
subject
end
end
describe '#create_role' do
subject { env.create_role('project_name') }
before do
stub_loggers
allow(env).to receive_message_chain('knife_instance.role_name') { 'role_name' }
end
it 'returns nil if knife_instance is nil' do
allow(env).to receive(:knife_instance)
expect(subject).to be nil
end
it 'returns hash with :error if knife.roles is nil' do
allow(env).to receive_message_chain('knife_instance.roles')
expect(subject).to be_a(Hash).and include(:error)
end
it 'returns hash with :error when creating a role raises an error' do
allow(env).to receive_message_chain('knife_instance.roles') { [] }
allow(env).to receive_message_chain('knife_instance.create_role') { raise StandardError }
expect(subject).to be_a(Hash).and include(error: ['role_name'])
end
context 'when a role already exists in chef' do
before { allow(env).to receive_message_chain('knife_instance.roles') { ['role_name'] } }
it 'returns hash with :exist' do
expect(subject).to be_a(Hash).and include(exist: ['role_name'])
end
it "adds the role to env's run_list" do
subject
expect(env.run_list).to include('role[role_name]')
end
end
context 'when a role is missing in chef' do
before { allow(env).to receive_message_chain('knife_instance.roles') { [] } }
it 'returns hash with :new when a role has been created' do
expect(env).to receive_message_chain('knife_instance.create_role')
expect(subject).to be_a(Hash).and include(new: ['role_name'])
end
it "adds the role to env's run_list" do
allow(env).to receive_message_chain('knife_instance.create_role')
subject
expect(env.run_list).to include('role[role_name]')
end
end
end
describe '#rename' do
subject { env.rename('project_id', 'new_name') }
let(:old_role_name) {'project_id_name'}
let(:new_role_name) {'project_id_new_name'}
let(:suggested_old_roles) {["role[#{old_role_name}]"]}
let(:suggested_new_roles) {["role[#{new_role_name}]"]}
before do
stub_loggers
# simulate correct start conditions
env.run_list = suggested_old_roles
# stub calls to knife
allow(env).to receive(:create_role) { {exist: [new_role_name]} }
allow(env).to receive_message_chain('knife_instance.role_name') { old_role_name }
# there is a call to Project.create_roles_response, stub it
allow_message_expectations_on_nil
project_class_double = double()
allow(project_class_double).to receive(:create_roles_response) { ''}
stub_const('Devops::Model::Project', project_class_double)
end
it 'raises InvalidRecord if env with such name already exists in project' do
allow(Devops::Db.connector).to receive_message_chain('project.deploy_env')
expect{subject}.to raise_error(InvalidRecord)
end
context 'when there is no env with such name already' do
before do
allow(Devops::Db.connector).to receive_message_chain('project.deploy_env') {raise RecordNotFound}
allow(Devops::Db.connector).to receive(:set_project_deploy_env_field)
end
it 'validates new identifier' do
expect(env).to receive(:validate_identifier!)
subject
end
it 'removes old role and adds new one' do
# somewhy 'expect to change' did't work
expect(env.run_list).to match_array(suggested_old_roles)
subject
expect(env.run_list).to match_array(suggested_new_roles)
end
end
end
describe '#knife_instance' do
it 'calls KnifeFactory.instance' do
expect(KnifeFactory).to receive(:instance)
env.knife_instance
end
end
end

View File

@ -0,0 +1,53 @@
RSpec.shared_examples 'stack template' do
validated_model_name = described_class.name.demodulize.underscore
it 'is valid with factory attrs' do
expect(build(validated_model_name)).to be_valid
end
describe 'validation rules' do
include_examples 'field type validation', :id, :not_nil, :non_empty_string, :only_word_symbols, :field_validator
include_examples 'field type validation', :provider, :not_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :template_body, :not_nil, :non_empty_string, :field_validator
include_examples 'field type validation', :owner, :not_nil, :non_empty_string, :field_validator
end
describe '#to_hash_without_id' do
it 'returns hash with provider, template_body and owner' do
expect(stack_template.to_hash_without_id).to include(:provider, :template_body, :owner)
end
end
describe '.create' do
it "returns instance of #{described_class.name}" do
params = {
'id' => 'foo',
'template_body' => '{}',
'owner' => 'root',
'provider' => 'ec2'
}
expect(described_class.create(params)).to be_an_instance_of(described_class)
end
end
describe '#build_from_bson' do
let(:params) { {
'_id' => 'foo',
'template_body' => '{}',
'owner' => 'root',
'provider' => 'ec2'
}}
subject { described_class.build_from_bson(params) }
it 'sets id from _id' do
expect(subject.id).to eq 'foo'
end
it 'sets other params from given hash' do
expect(subject.template_body).to eq '{}'
expect(subject.owner).to eq 'root'
expect(subject.provider).to eq 'ec2'
end
end
end

View File

@ -0,0 +1,121 @@
SUPPORTED_MODEL_PROPERTIES = [:not_nil, :maybe_nil, :non_empty_string, :maybe_empty_string, :non_empty_array, :maybe_empty_array, :only_word_symbols, :run_list, :field_validator]
RSpec.shared_examples 'field type validation' do |field, *properties|
validated_model_name = described_class.name.demodulize.underscore
unsupported_properties = properties - SUPPORTED_MODEL_PROPERTIES
with_field_validators = properties.include?(:field_validator)
raise_error "Unsupported properties: #{unsupported_properties.join(', ')}" unless unsupported_properties.empty?
describe field do
it 'should not be nil' do
expect(build(validated_model_name, field => nil)).not_to be_valid
end if properties.include?(:not_nil)
it 'may be nil' do
expect(build(validated_model_name, field => nil)).to be_valid
end if properties.include?(:maybe_nil)
it 'should be non empty string' do
expect(build(validated_model_name, field => 0)).not_to be_valid
expect(build(validated_model_name, field => '')).not_to be_valid
end if properties.include?(:non_empty_string)
it 'may be empty string' do
expect(build(validated_model_name, field => 0)).not_to be_valid
expect(build(validated_model_name, field => '')).to be_valid
end if properties.include?(:maybe_empty_string)
it 'should be non empty array' do
expect(build(validated_model_name, field => 0)).not_to be_valid
expect(build(validated_model_name, field => [])).not_to be_valid
end if properties.include?(:non_empty_array)
it 'may be empty array' do
expect(build(validated_model_name, field => 0)).not_to be_valid
expect(build(validated_model_name, field => [])).to be_valid
end if properties.include?(:maybe_empty_array)
it 'should contain only word symbols' do
expect(build(validated_model_name, field => 'asd-asd')).not_to be_valid
end if properties.include?(:only_word_symbols)
it 'should contain elements like role[asd] or recipe[asd]' do
expect(build(validated_model_name, field => ['role[asd]'])).to be_valid
expect(build(validated_model_name, field => ['recipe[asd]'])).to be_valid
expect(build(validated_model_name, field => ['wrong_format'])).not_to be_valid
end if properties.include?(:run_list)
end
if with_field_validators
field_validation_method = "validate_#{field}!"
describe field_validation_method do
it 'should not be nil' do
expect{
build(validated_model_name, field => nil).send(field_validation_method)
}.to raise_error InvalidRecord
end if properties.include?(:not_nil)
it 'may be nil' do
expect{
build(validated_model_name, field => nil).send(field_validation_method)
}.not_to raise_error
end if properties.include?(:maybe_nil)
it 'should be non empty string' do
expect{
build(validated_model_name, field => 0).send(field_validation_method)
}.to raise_error InvalidRecord
expect{
build(validated_model_name, field => '').send(field_validation_method)
}.to raise_error InvalidRecord
end if properties.include?(:non_empty_string)
it 'may be empty string' do
expect{
build(validated_model_name, field => 0).send(field_validation_method)
}.to raise_error InvalidRecord
expect{
build(validated_model_name, field => '').send(field_validation_method)
}.not_to raise_error
end if properties.include?(:maybe_empty_string)
it 'should be non empty array' do
expect{
build(validated_model_name, field => 0).send(field_validation_method)
}.to raise_error InvalidRecord
expect{
build(validated_model_name, field => []).send(field_validation_method)
}.to raise_error InvalidRecord
end if properties.include?(:non_empty_array)
it 'may be empty array' do
expect{
build(validated_model_name, field => 0).send(field_validation_method)
}.to raise_error InvalidRecord
expect{
build(validated_model_name, field => []).send(field_validation_method)
}.not_to raise_error
end if properties.include?(:maybe_empty_array)
it 'should contain only word symbols' do
expect{
build(validated_model_name, field => 'asd-asd').send(field_validation_method)
}.to raise_error InvalidRecord
end if properties.include?(:only_word_symbols)
it 'should contain elements like role[asd] or recipe[asd]' do
expect{
build(validated_model_name, field => ['role[asd]']).send(field_validation_method)
}.not_to raise_error
expect{
build(validated_model_name, field => ['recipe[asd]']).send(field_validation_method)
}.not_to raise_error
expect{
build(validated_model_name, field => ['wrong_format']).send(field_validation_method)
}.to raise_error InvalidRecord
end if properties.include?(:run_list)
end
end
end

View File

@ -0,0 +1,11 @@
require 'core/devops-application'
module SpecSupport
BLANK_FILE = File.join(Devops::Application.root, 'spec/support/blank_file')
def stub_loggers
allow(DevopsLogger).to receive_message_chain('logger.debug')
allow(DevopsLogger).to receive_message_chain('logger.info')
allow(DevopsLogger).to receive_message_chain('logger.error')
end
end