diff --git a/.travis.yml b/.travis.yml index 867e15c..c987654 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,3 +6,9 @@ rvm: - 1.9.3 - jruby-19mode - 2.0.0 +env: + - RAILS_ENV=test RACK_ENV=test +branches: + only: + - master + - develop \ No newline at end of file diff --git a/Gemfile b/Gemfile index 132b668..204f146 100644 --- a/Gemfile +++ b/Gemfile @@ -18,4 +18,15 @@ gem 'tzinfo' gem 'pry' gem 'simplecov' gem 'simplecov-summary' +gem 'nori' +gem 'nokogiri' +gem 'capybara', '1.1.4' +gem 'selenium-webdriver', '~> 2.37.0' +gem 'headless' +gem 'capybara-firebug' +gem 'mocha', '~> 0.13.3', :require => false +gem 'factory_girl_rails' +gem 'forgery' +gem 'webmock' +gem 'rspec-instafail' diff --git a/Rakefile b/Rakefile index 44c1b46..18d3e54 100644 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,9 @@ require 'bundler/gem_tasks' require 'appraisal' require 'rspec/core/rake_task' -RSpec::Core::RakeTask.new(:spec) +RSpec::Core::RakeTask.new(:spec) do |spec| + # spec.rspec_opts = ['--backtrace '] +end desc "Default: run the unit tests." task :default => [:all] diff --git a/app/helpers/washout_builder_helper.rb b/app/helpers/washout_builder_helper.rb index 79cb521..65b4250 100644 --- a/app/helpers/washout_builder_helper.rb +++ b/app/helpers/washout_builder_helper.rb @@ -1,167 +1,5 @@ module WashoutBuilderHelper - def get_complex_class_name(p, defined = []) - complex_class = p.struct? ? p.basic_type : nil - complex_class = complex_class.include?(".") ? complex_class.gsub(".","/").camelize : complex_class.to_s.classify unless complex_class.nil? - - unless complex_class.nil? || defined.blank? - - complex_obj_found = defined.detect {|hash| hash[:class] == complex_class} - - if !complex_obj_found.nil? && p.struct? && !p.classified? && p.source_class_name.blank? - raise RuntimeError, "Duplicate use of `#{p.basic_type}` type name. Consider using classified types." - end - end - - return complex_class - end - - - def get_param_structure(param) - param.map.inject({}) {|h,element| h[element.name] = element.type;h } - end - - - def remove_type_inheritable_elements(param, keys) - param.map.delete_if{|element| keys.include?(element.name) } - end - - - def same_structure_as_ancestor?(param, ancestor) - param_structure = get_param_structure(param) - ancestor_structure = get_param_structure(ancestor) - if param_structure.keys == ancestor_structure.keys - return true - else - remove_type_inheritable_elements(param, ancestor_structure.keys) - return false - end - end - - - - - def get_class_ancestors(param,class_name, defined) - bool_the_same = false - param_class = class_name.is_a?(Class) ? class_name : class_name.constantize rescue nil - unless param_class.nil? - ancestors = (param_class.ancestors - param_class.included_modules).delete_if{ |x| x.to_s.downcase == class_name.to_s.downcase || x.to_s == "ActiveRecord::Base" || x.to_s == "Object" || x.to_s =="BasicObject" || x.to_s == "WashOut::Type" } - unless ancestors.blank? - ancestor_structure = { ancestors[0].to_s.downcase => ancestors[0].columns_hash.inject({}) {|h, (k,v)| h["#{k}"]="#{v.type}".to_sym; h } } - ancestor_object = WashOut::Param.parse_def(@soap_config,ancestor_structure)[0] - bool_the_same = same_structure_as_ancestor?(param, ancestor_object) - unless bool_the_same - top_ancestors = get_class_ancestors(ancestor_object,ancestors[0], defined) - defined << {:class =>ancestors[0], :obj =>ancestor_object , :ancestors => top_ancestors } - end - end - ancestors unless bool_the_same - end - end - - - def fix_descendant_wash_out_type(param, complex_class) - param_class = complex_class.is_a?(Class) ? complex_class : complex_class.constantize rescue nil - if !param_class.nil? && param_class.ancestors.include?(WashOut::Type) && !param.map[0].nil? - descendant = WashOut::Param.parse_def(@soap_config, param_class.wash_out_param_map)[0] - param.name = descendant.name - param.map = descendant.map - end - end - - def get_nested_complex_types(param, defined) - defined = [] if defined.blank? - complex_class = get_complex_class_name(param, defined) - fix_descendant_wash_out_type(param, complex_class) - defined << {:class =>complex_class, :obj => param, :ancestors => param.classified? ? get_class_ancestors(param, complex_class, defined) : nil } unless complex_class.nil? - if param.struct? - c_names = [] - param.map.each { |obj| c_names.concat(get_nested_complex_types(obj, defined)) } - defined.concat(c_names) - end - defined.sort_by { |hash| hash[:class].downcase }.uniq unless defined.blank? - end - - - def get_complex_types(map) - defined = [] - map.each do |operation, formats| - (formats[:in] + formats[:out]).each do |p| - defined.concat(get_nested_complex_types(p, defined)) - end - end - defined.sort_by { |hash| hash[:class].downcase }.uniq unless defined.blank? - end - - - def remove_fault_type_inheritable_elements(param, keys) - get_virtus_model_structure(param).delete_if{|key,value| keys.include?(key) } - end - - - - - - def get_fault_class_ancestors(fault, defined, debug = false) - bool_the_same = false - unless fault.nil? - ancestors = (fault.ancestors - fault.included_modules).delete_if{ |x| x.to_s.downcase == fault.to_s.downcase || x.to_s == "ActiveRecord::Base" || x.to_s == "Object" || x.to_s =="BasicObject" || x.to_s == "Exception" } - if ancestors.blank? - defined << {:fault => fault,:structure =>get_virtus_model_structure(fault) ,:ancestors => [] } - else - fault_structure = remove_fault_type_inheritable_elements(fault, get_virtus_model_structure(ancestors[0]).keys) - defined << {:fault => fault,:structure =>fault_structure ,:ancestors => ancestors } - get_fault_class_ancestors(ancestors[0], defined) - end - ancestors unless bool_the_same - end - end - - def get_virtus_model_structure(fault) - fault.attribute_set.inject({}) {|h, elem| h["#{elem.name}"]= { :primitive => "#{elem.primitive}", :options => elem.options }; h } - end - - - def get_fault_types(map) - defined = map.select{|operation, formats| !formats[:raises].blank? } - defined = defined.collect {|operation, formats| formats[:raises].is_a?(Array) ? formats[:raises] : [formats[:raises]] }.flatten.select { |x| x.is_a?(Class) && x.ancestors.include?(WashOut::SOAPError) } unless defined.blank? - fault_types = [] - if defined.blank? - defined = [WashOut::SOAPError] - else - defined << WashOut::SOAPError - end - defined.each{ |item| get_fault_class_ancestors(item, fault_types, true)} unless defined.blank? - - complex_types = [] - fault_types.each do |hash| - hash[:structure].each do |attribute, attr_details| - if attr_details[:primitive].to_s.downcase == "array" && !WashoutBuilder::Type::BASIC_TYPES.include?(attr_details[:options][:member_type].primitive.to_s.downcase) - complex_class = attr_details[:options][:member_type].primitive - elsif attr_details[:primitive].to_s.downcase != "array" && !WashoutBuilder::Type::BASIC_TYPES.include?(attr_details[:primitive].to_s.downcase) - complex_class = attr_details[:primitive] - end - - param_class = complex_class.is_a?(Class) ? complex_class : complex_class.constantize rescue nil - if !param_class.nil? && param_class.ancestors.include?(Virtus::Model::Core) - get_fault_class_ancestors(param_class, complex_types) - elsif !param_class.nil? && !param_class.ancestors.include?(Virtus::Model::Core) - raise RuntimeError, "Non-existent use of `#{param_class}` type name or this class does not use Virtus.model. Consider using classified types that include Virtus.mode for exception atribute types." - end - - end - end - complex_types.delete_if{ |hash| fault_types << hash if hash[:fault].ancestors.include?(WashOut::SOAPError) } unless complex_types.blank? - fault_types = fault_types.sort_by { |hash| hash[:fault].to_s.downcase }.uniq unless fault_types.blank? - complex_types = complex_types.sort_by { |hash| hash[:fault].to_s.downcase }.uniq unless complex_types.blank? - [fault_types, complex_types] - end - - def get_soap_action_names(map) - map.map{|operation, formats| operation.to_s }.sort_by { |name| name.downcase }.uniq unless map.blank? - end - - def create_html_complex_types(xml, types) types.each { |hash| create_complex_type_html(xml, hash[:obj], hash[:class], hash[:ancestors]) } end @@ -181,9 +19,10 @@ def create_complex_type_html(xml, param, class_name, ancestors) # raise YAML::dump(element) if class_name.include?("ype") and element.name == "members" xml.li { |pre| if WashoutBuilder::Type::BASIC_TYPES.include?(element.type) + element.type = "integer" if element.type == "int" pre << "#{element.type} #{element.name}" else - complex_class = get_complex_class_name(element) + complex_class = element.get_complex_class_name unless complex_class.nil? if element.multiplied == false pre << "#{complex_class} #{element.name}" @@ -228,9 +67,8 @@ def create_html_virtus_model_type(xml, param, fault_structure, ancestors) else if attr_details[:primitive].to_s.downcase == "array" - attr_primitive = attr_details[:options][:member_type].primitive.to_s - attr_primitive = WashoutBuilder::Type::BASIC_TYPES.include?(attr_primitive.downcase) ? attr_primitive.downcase : attr_primitive + attr_primitive = WashoutBuilder::Type::BASIC_TYPES.include?(attr_details[:member_type].to_s.downcase) ? attr_details[:member_type].to_s.downcase : attr_details[:member_type] pre << "Array of #{attr_primitive} #{attribute}" else pre << "#{attr_details[:primitive]} #{attribute}" @@ -259,7 +97,7 @@ def create_html_public_method(xml, operation, formats) xml.p("class" => "pre"){ |pre| unless formats[:out].nil? - complex_class = get_complex_class_name(formats[:out][0]) + complex_class = formats[:out][0].get_complex_class_name if WashoutBuilder::Type::BASIC_TYPES.include?(formats[:out][0].type) xml.span("class" => "blue") { |y| y<< "#{formats[:out][0].type}" } else @@ -274,7 +112,7 @@ def create_html_public_method(xml, operation, formats) else pre << "void" end - + xml.span("class" => "bold") {|y| y << "#{operation} (" } mlen = formats[:in].size xml.br if mlen > 1 @@ -283,7 +121,7 @@ def create_html_public_method(xml, operation, formats) j=0 while j 1 ? true : false if WashoutBuilder::Type::BASIC_TYPES.include?(param.type) pre << "#{use_spacer ? spacer: ''}#{param.type} #{param.name}" @@ -321,7 +159,7 @@ def create_html_public_method(xml, operation, formats) mlen = formats[:in].size while j "pre") { |pre| if WashoutBuilder::Type::BASIC_TYPES.include?(param.type) pre << "#{param.type} #{param.name}" diff --git a/app/views/wash_with_html/doc.builder b/app/views/wash_with_html/doc.builder index dac8f7e..84ac751 100644 --- a/app/views/wash_with_html/doc.builder +++ b/app/views/wash_with_html/doc.builder @@ -5,7 +5,7 @@ xml.html( "xmlns" => "http://www.w3.org/1999/xhtml" ) { xml.head { - xml.title "#{@service} interface description" + xml.title "#{@document.service} interface description" xml.style( "type"=>"text/css" ,"media" => "all" ) { xml.text! " body{font-family:Calibri,Arial;background-color:#fefefe;} @@ -31,27 +31,27 @@ xml.html( "xmlns" => "http://www.w3.org/1999/xhtml" ) { xml.body { - xml.h1 "#{ @service} Soap WebService interface description" + xml.h1 "#{ @document.service} Soap WebService interface description" xml.p{ |y| y << "Endpoint URI:"; - xml.span( "class" => "pre") { |y| y << "#{@endpoint}"}; + xml.span( "class" => "pre") { |y| y << "#{@document.endpoint}"}; } xml.p{ |y| y << "WSDL URI:"; xml.span( "class" => "pre") { - xml.a( "href" => "#{@namespace}") { |y| y << "#{@namespace}" } + xml.a( "href" => "#{@document.namespace}") { |y| y << "#{@document.namespace}" } };} - unless @soap_config.description.blank? - xml.h1 "#{@service}" - xml.p "#{@soap_config.description}" + unless @document.service_description.blank? + xml.h1 "#{@document.service}" + xml.p "#{@document.service_description}" end xml.div("class" => "noprint") { xml.h2 "Index " - @complex_types = get_complex_types(@map) - @fault_types, @fault_complex_types = get_fault_types(@map) + @complex_types = @document.complex_types + @fault_types, @fault_complex_types = @document.fault_types unless @complex_types.blank? xml.p "Complex Types: " @@ -78,7 +78,7 @@ xml.html( "xmlns" => "http://www.w3.org/1999/xhtml" ) { end end - @methods = get_soap_action_names(@map) + @methods = @document.get_soap_action_names unless @methods.blank? xml.p "Public Methods:" @@ -104,7 +104,7 @@ xml.html( "xmlns" => "http://www.w3.org/1999/xhtml" ) { end unless @methods.blank? xml.h2 "Public methods:" - create_html_public_methods(xml, @map) + create_html_public_methods(xml, @document.soap_actions) end if @complex_types.blank? && @fault_types.blank? && @methods.blank? diff --git a/lib/washout_builder.rb b/lib/washout_builder.rb index f55a518..3bb21b4 100644 --- a/lib/washout_builder.rb +++ b/lib/washout_builder.rb @@ -2,8 +2,13 @@ require 'virtus' require 'washout_builder/soap' require 'washout_builder/engine' +require 'washout_builder/document/complex_type' +require 'washout_builder/document/virtus_model' +require 'washout_builder/document/fault' +require 'washout_builder/document/generator' require 'washout_builder/dispatcher' require 'washout_builder/type' +require 'washout_builder/version' module ActionDispatch::Routing @@ -23,15 +28,21 @@ def wash_out(controller_name, options={}) end end +Virtus::InstanceMethods::Constructor.class_eval do + alias_method :original_initialize,:initialize + def initialize(attributes = nil) + if self.class.ancestors.include?(WashOut::SOAPError) || self.is_a?(WashOut::SOAPError) + attributes = {:message => attributes} unless attributes.is_a?(Hash) + end + original_initialize(attributes) + end +end +WashOut::Param.send :include, WashoutBuilder::Document::ComplexType +SOAPError.send :extend, WashoutBuilder::Document::Fault +WashOut::SOAPError.send :extend, WashoutBuilder::Document::Fault -WashOut::SOAPError.class_eval do - include Virtus.model - attribute :code, Integer - attribute :message, String - attribute :backtrace, String -end ActionController::Base.class_eval do diff --git a/lib/washout_builder/dispatcher.rb b/lib/washout_builder/dispatcher.rb index add1599..57269d7 100644 --- a/lib/washout_builder/dispatcher.rb +++ b/lib/washout_builder/dispatcher.rb @@ -6,13 +6,12 @@ module WashoutBuilder module Dispatcher def _generate_doc - @map = self.class.soap_actions - @namespace = soap_config.namespace - @name = controller_path.gsub('/', '_') - @service = self.class.name.underscore.gsub("_controller", "").camelize - @endpoint = @namespace.gsub("/wsdl", "/action") - @soap_config = soap_config - + @document = WashoutBuilder::Document::Generator.new( + :config => soap_config, + :service_class => self.class, + :soap_actions => self.class.soap_actions + ) + render :template => "wash_with_html/doc", :layout => false, :content_type => 'text/html' end diff --git a/lib/washout_builder/document/complex_type.rb b/lib/washout_builder/document/complex_type.rb new file mode 100644 index 0000000..7d95ebf --- /dev/null +++ b/lib/washout_builder/document/complex_type.rb @@ -0,0 +1,102 @@ +module WashoutBuilder + module Document + module ComplexType + extend ActiveSupport::Concern + + + def get_complex_class_name(defined = []) + complex_class = struct? ? basic_type : nil + complex_class = complex_class.include?(".") ? complex_class.gsub(".","/").camelize : complex_class.classify unless complex_class.nil? + + unless complex_class.nil? || defined.blank? + + complex_obj_found = defined.detect {|hash| hash[:class] == complex_class} + + if !complex_obj_found.nil? && struct? && !classified? + raise RuntimeError, "Duplicate use of `#{basic_type}` type name. Consider using classified types." + end + end + + return complex_class + end + + def get_param_structure + map.inject({}) {|h,element| h[element.name] = element.type;h } + end + + + def remove_type_inheritable_elements( keys) + self.map = self.map.delete_if{|element| keys.include?(element.name) } + end + + # def get_ancestor_structure + # {self.class.to_s.downcase => self.class.columns_hash.inject({}) {|h, (k,v)| h["#{k}"]="#{v.type}".to_sym; h } } + # end + + + def fix_descendant_wash_out_type(config, complex_class) + param_class = complex_class.is_a?(Class) ? complex_class : complex_class.constantize rescue nil + if !param_class.nil? && param_class.ancestors.include?(WashOut::Type) && !map[0].nil? + descendant = WashOut::Param.parse_def(config, param_class.wash_out_param_map)[0] + self.name = descendant.name + self.map = descendant.map + end + end + + + def same_structure_as_ancestor?(ancestor) + param_structure = get_param_structure + ancestor_structure = ancestor.get_param_structure + if param_structure.keys == ancestor_structure.keys + return true + else + remove_type_inheritable_elements( ancestor_structure.keys) + return false + end + end + + + def get_ancestors(class_name) + param_class = class_name.is_a?(Class) ? class_name : class_name.constantize rescue nil + if param_class.nil? + return nil + else + (param_class.ancestors - param_class.included_modules).delete_if{ |x| x.to_s.downcase == class_name.to_s.downcase || x.to_s == "ActiveRecord::Base" || x.to_s == "Object" || x.to_s =="BasicObject" || x.to_s == "WashOut::Type" } + end + end + + + + def get_nested_complex_types(config,defined) + defined = [] if defined.blank? + complex_class = get_complex_class_name( defined) + fix_descendant_wash_out_type( config, complex_class) + defined << {:class =>complex_class, :obj => self, :ancestors => classified? ? get_class_ancestors(config, complex_class, defined) : nil } unless complex_class.nil? + if struct? + c_names = [] + map.each { |obj| c_names.concat(obj.get_nested_complex_types(config, defined)) } + defined.concat(c_names) + end + defined.sort_by { |hash| hash[:class].to_s.downcase }.uniq unless defined.blank? + end + + + def get_class_ancestors( config, class_name, defined) + bool_the_same = false + ancestors = get_ancestors(class_name) + unless ancestors.blank? + ancestor_structure = { ancestors[0].to_s.downcase => ancestors[0].wash_out_param_map } + ancestor_object = WashOut::Param.parse_def(config,ancestor_structure)[0] + bool_the_same = same_structure_as_ancestor?( ancestor_object) + unless bool_the_same + top_ancestors = get_class_ancestors(config, ancestors[0], defined) + defined << {:class =>ancestors[0], :obj =>ancestor_object , :ancestors => top_ancestors } + end + ancestors unless bool_the_same + end + end + + + end + end +end \ No newline at end of file diff --git a/lib/washout_builder/document/fault.rb b/lib/washout_builder/document/fault.rb new file mode 100644 index 0000000..70a4170 --- /dev/null +++ b/lib/washout_builder/document/fault.rb @@ -0,0 +1,12 @@ +module WashoutBuilder + module Document + module Fault + extend WashoutBuilder::Document::VirtusModel + include Virtus.model + attribute :code, Integer + attribute :message, String + attribute :backtrace, String + + end + end +end diff --git a/lib/washout_builder/document/generator.rb b/lib/washout_builder/document/generator.rb new file mode 100644 index 0000000..da42dd7 --- /dev/null +++ b/lib/washout_builder/document/generator.rb @@ -0,0 +1,114 @@ +module WashoutBuilder + module Document + class Generator + + @attrs = [:soap_actions, :config, :service_class] + + attr_reader *@attrs + attr_accessor *@attrs + + def initialize(attrs = {}) + self.config = attrs[:config] + self.service_class = attrs[:service_class] + self.soap_actions = attrs[:soap_actions] + end + + def namespace + config.namespace + end + + + def endpoint + namespace.gsub("/wsdl", "/action") + end + + def service + service_class.name.underscore.gsub("_controller", "").camelize + end + + def service_description + config.description + end + + def operations + soap_actions.map { |operation, formats| operation } + end + + + def input_types + types = [] + soap_actions.each do |operation, formats| + (formats[:in]).each do |p| + types << p + end + end + types + end + + def output_types + types = [] + soap_actions.each do |operation, formats| + (formats[:out]).each do |p| + types << p + end + end + types + end + + def get_soap_action_names + operations.map(&:to_s).sort_by { |name| name.downcase }.uniq unless soap_actions.blank? + end + + + def complex_types + defined = [] + (input_types + output_types).each do |p| + defined.concat(p.get_nested_complex_types(config, defined)) + end + defined.sort_by { |hash| hash[:class].to_s.downcase }.uniq unless defined.blank? + end + + + def fault_types + defined = soap_actions.select{|operation, formats| !formats[:raises].blank? } + defined = defined.collect {|operation, formats| formats[:raises].is_a?(Array) ? formats[:raises] : [formats[:raises]] }.flatten.select { |x| x.is_a?(Class) && x.ancestors.include?(WashOut::SOAPError) } unless defined.blank? + fault_types = [] + if defined.blank? + defined = [WashOut::SOAPError] + else + defined << WashOut::SOAPError + end + defined.each{ |exception_class| exception_class.get_fault_class_ancestors( fault_types, true)} unless defined.blank? + complex_types = extract_nested_complex_types_from_exceptions(fault_types) + complex_types.delete_if{ |hash| fault_types << hash if hash[:fault].ancestors.include?(WashOut::SOAPError) } unless complex_types.blank? + fault_types = fault_types.sort_by { |hash| hash[:fault].to_s.downcase }.uniq unless fault_types.blank? + complex_types = complex_types.sort_by { |hash| hash[:fault].to_s.downcase }.uniq unless complex_types.blank? + [fault_types, complex_types] + end + + def extract_nested_complex_types_from_exceptions(fault_types) + complex_types = [] + fault_types.each do |hash| + hash[:structure].each do |attribute, attr_details| + complex_class = hash[:fault].get_virtus_member_type_primitive(attr_details) + unless complex_class.nil? + param_class = complex_class.is_a?(Class) ? complex_class : complex_class.constantize rescue nil + if !param_class.nil? && param_class.ancestors.include?(Virtus::Model::Core) + param_class.send :extend, WashoutBuilder::Document::VirtusModel + param_class.get_fault_class_ancestors( complex_types) + elsif !param_class.nil? && !param_class.ancestors.include?(Virtus::Model::Core) + raise RuntimeError, "Non-existent use of `#{param_class}` type name or this class does not use Virtus.model. Consider using classified types that include Virtus.mode for exception atribute types." + end + end + end + end + complex_types + end + + + + + + end + end +end \ No newline at end of file diff --git a/lib/washout_builder/document/virtus_model.rb b/lib/washout_builder/document/virtus_model.rb new file mode 100644 index 0000000..7145c41 --- /dev/null +++ b/lib/washout_builder/document/virtus_model.rb @@ -0,0 +1,45 @@ +module WashoutBuilder + module Document + module VirtusModel + extend ActiveSupport::Concern + + def get_fault_class_ancestors( defined, debug = false) + bool_the_same = false + ancestors = (self.ancestors - self.included_modules).delete_if{ |x| x.to_s.downcase == self.to_s.downcase || x.to_s == "ActiveRecord::Base" || x.to_s == "Object" || x.to_s =="BasicObject" || x.to_s == "Exception" } + if ancestors.blank? + defined << {:fault => self,:structure =>get_virtus_model_structure ,:ancestors => [] } + else + fault_structure = remove_fault_type_inheritable_elements( ancestors[0].get_virtus_model_structure.keys) + defined << {:fault => self,:structure =>fault_structure ,:ancestors => ancestors } + ancestors[0].get_fault_class_ancestors( defined) + end + ancestors unless bool_the_same + end + + + def remove_fault_type_inheritable_elements( keys) + get_virtus_model_structure.delete_if{|key,value| keys.include?(key) } + end + + + + + def get_virtus_member_type_primitive(attr_details) + complex_class = nil + if attr_details[:primitive].to_s.downcase == "array" && !WashoutBuilder::Type::BASIC_TYPES.include?(attr_details[:member_type].to_s.downcase) + complex_class = attr_details[:member_type] + elsif attr_details[:primitive].to_s.downcase != "array" && !WashoutBuilder::Type::BASIC_TYPES.include?(attr_details[:primitive].to_s.downcase) + complex_class = attr_details[:primitive] + end + complex_class + end + + + def get_virtus_model_structure + attribute_set.inject({}) {|h, elem| h["#{elem.name}"]= { :primitive => "#{elem.primitive}", :member_type => elem.options[:member_type].nil? ? nil: elem.options[:member_type].primitive }; h } + end + + + end + end +end diff --git a/lib/washout_builder/type.rb b/lib/washout_builder/type.rb index afd11b3..55d6c91 100644 --- a/lib/washout_builder/type.rb +++ b/lib/washout_builder/type.rb @@ -9,7 +9,8 @@ class Type "date", "datetime", "float", - "time" + "time", + "int" ] end diff --git a/lib/washout_builder/version.rb b/lib/washout_builder/version.rb index 6baf72a..384afdb 100644 --- a/lib/washout_builder/version.rb +++ b/lib/washout_builder/version.rb @@ -1,3 +1,3 @@ module WashoutBuilder - VERSION = "0.9.12" + VERSION = "0.10.0" end diff --git a/spec/app/controllers/washout_builder_controller_spec.rb b/spec/app/controllers/washout_builder_controller_spec.rb new file mode 100644 index 0000000..f3cecb9 --- /dev/null +++ b/spec/app/controllers/washout_builder_controller_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +describe WashoutBuilderController do + + + + +end \ No newline at end of file diff --git a/spec/app/helpers/washout_builder_helper_spec.rb b/spec/app/helpers/washout_builder_helper_spec.rb new file mode 100644 index 0000000..d0664bd --- /dev/null +++ b/spec/app/helpers/washout_builder_helper_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +describe WashoutBuilderHelper do + + + + +end \ No newline at end of file diff --git a/spec/app/integration/washout_builder_all_services_spec.rb b/spec/app/integration/washout_builder_all_services_spec.rb new file mode 100644 index 0000000..238edc6 --- /dev/null +++ b/spec/app/integration/washout_builder_all_services_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +feature "All Services" do + + + +end \ No newline at end of file diff --git a/spec/app/integration/washout_builder_service_spec.rb b/spec/app/integration/washout_builder_service_spec.rb new file mode 100644 index 0000000..ab48574 --- /dev/null +++ b/spec/app/integration/washout_builder_service_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +feature "Service" do + + + +end \ No newline at end of file diff --git a/spec/dummy/Rakefile b/spec/dummy/Rakefile new file mode 100644 index 0000000..9724472 --- /dev/null +++ b/spec/dummy/Rakefile @@ -0,0 +1,7 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) +require 'rake' + +Dummy::Application.load_tasks diff --git a/spec/dummy/app/controllers/application_controller.rb b/spec/dummy/app/controllers/application_controller.rb new file mode 100644 index 0000000..e8065d9 --- /dev/null +++ b/spec/dummy/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + protect_from_forgery +end diff --git a/spec/dummy/app/helpers/application_helper.rb b/spec/dummy/app/helpers/application_helper.rb new file mode 100644 index 0000000..de6be79 --- /dev/null +++ b/spec/dummy/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/spec/dummy/app/views/layouts/application.html.erb b/spec/dummy/app/views/layouts/application.html.erb new file mode 100644 index 0000000..a3b5a22 --- /dev/null +++ b/spec/dummy/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ + + + + Dummy + <%= stylesheet_link_tag :all %> + <%= javascript_include_tag :defaults %> + <%= csrf_meta_tag %> + + + +<%= yield %> + + + diff --git a/spec/dummy/config.ru b/spec/dummy/config.ru new file mode 100644 index 0000000..1989ed8 --- /dev/null +++ b/spec/dummy/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Dummy::Application diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb new file mode 100644 index 0000000..9ccb228 --- /dev/null +++ b/spec/dummy/config/application.rb @@ -0,0 +1,51 @@ +require File.expand_path('../boot', __FILE__) + +require "action_controller/railtie" +require "rails/test_unit/railtie" + +if Rails::VERSION::MAJOR >= 4 + # Ugly hack to make Rails 4 JRuby-compatible to escape Travis errors + Rails::Engine.class_eval do + def railties + @railties ||= self.class.const_get(:Railties).new + end + end +end + +Bundler.require +require "wash_out" + +module Dummy + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Custom directories with classes and modules you want to be autoloadable. + # config.autoload_paths += %W(#{config.root}/extras) + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named. + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Activate observers that should always be running. + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # JavaScript files you want as :defaults (application.js is always included). + # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) + + # Configure the default encoding used in templates for Ruby 1.9. + config.encoding = "utf-8" + + # Configure sensitive parameters which will be filtered from the log file. + config.filter_parameters += [:password] + end +end diff --git a/spec/dummy/config/boot.rb b/spec/dummy/config/boot.rb new file mode 100644 index 0000000..eba0681 --- /dev/null +++ b/spec/dummy/config/boot.rb @@ -0,0 +1,10 @@ +require 'rubygems' +gemfile = File.expand_path('../../../../Gemfile', __FILE__) + +if File.exist?(gemfile) + ENV['BUNDLE_GEMFILE'] = gemfile + require 'bundler' + Bundler.setup +end + +$:.unshift File.expand_path('../../../../lib', __FILE__) \ No newline at end of file diff --git a/spec/dummy/config/environment.rb b/spec/dummy/config/environment.rb new file mode 100644 index 0000000..3da5eb9 --- /dev/null +++ b/spec/dummy/config/environment.rb @@ -0,0 +1,5 @@ +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +Dummy::Application.initialize! diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb new file mode 100644 index 0000000..bac01db --- /dev/null +++ b/spec/dummy/config/environments/development.rb @@ -0,0 +1,23 @@ +Dummy::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the webserver when you make code changes. + config.cache_classes = false + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_view.debug_rjs = true + config.action_controller.perform_caching = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin +end + diff --git a/spec/dummy/config/environments/test.rb b/spec/dummy/config/environments/test.rb new file mode 100644 index 0000000..9302597 --- /dev/null +++ b/spec/dummy/config/environments/test.rb @@ -0,0 +1,29 @@ +Dummy::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + config.eager_load = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr +end diff --git a/spec/dummy/config/initializers/backtrace_silencers.rb b/spec/dummy/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..59385cd --- /dev/null +++ b/spec/dummy/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/spec/dummy/config/initializers/inflections.rb b/spec/dummy/config/initializers/inflections.rb new file mode 100644 index 0000000..9e8b013 --- /dev/null +++ b/spec/dummy/config/initializers/inflections.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end diff --git a/spec/dummy/config/initializers/mime_types.rb b/spec/dummy/config/initializers/mime_types.rb new file mode 100644 index 0000000..72aca7e --- /dev/null +++ b/spec/dummy/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/spec/dummy/config/initializers/secret_token.rb b/spec/dummy/config/initializers/secret_token.rb new file mode 100644 index 0000000..acba944 --- /dev/null +++ b/spec/dummy/config/initializers/secret_token.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +Dummy::Application.config.secret_token = 'b8d5d5687c012c2ef1a7a6e8006172402c48a3dcccca67c076eaad81c4712ad236ca2717c3706df7b286468c749d223f22acb0d96c27bdf33bbdbb9684ad46e5' +Dummy::Application.config.secret_key_base = 'ed27a919186a27649accc93ad8c2750a022ef255780e8a15a439658e9f8c520ed70ea071e596b2d23ed41163cf36c21ff5afcd31d19439bb1e4c420f2a57bce6' \ No newline at end of file diff --git a/spec/dummy/config/initializers/session_store.rb b/spec/dummy/config/initializers/session_store.rb new file mode 100644 index 0000000..aa2f512 --- /dev/null +++ b/spec/dummy/config/initializers/session_store.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +Dummy::Application.config.session_store :cookie_store, :key => '_dummy_session' + +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rails generate session_migration") +# Dummy::Application.config.session_store :active_record_store diff --git a/spec/dummy/config/locales/en.yml b/spec/dummy/config/locales/en.yml new file mode 100644 index 0000000..a747bfa --- /dev/null +++ b/spec/dummy/config/locales/en.yml @@ -0,0 +1,5 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + hello: "Hello world" diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb new file mode 100644 index 0000000..b20a20f --- /dev/null +++ b/spec/dummy/config/routes.rb @@ -0,0 +1,58 @@ +Dummy::Application.routes.draw do + # The priority is based upon order of creation: + # first created -> highest priority. + + # Sample of regular route: + # match 'products/:id' => 'catalog#view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase + # This route can be invoked with purchase_url(:id => product.id) + + # Sample resource route (maps HTTP verbs to controller actions automatically): + # resources :products + + # Sample resource route with options: + # resources :products do + # member do + # get 'short' + # post 'toggle' + # end + # + # collection do + # get 'sold' + # end + # end + + # Sample resource route with sub-resources: + # resources :products do + # resources :comments, :sales + # resource :seller + # end + + # Sample resource route with more complex sub-resources + # resources :products do + # resources :comments + # resources :sales do + # get 'recent', :on => :collection + # end + # end + + # Sample resource route within a namespace: + # namespace :admin do + # # Directs /admin/products/* to Admin::ProductsController + # # (app/controllers/admin/products_controller.rb) + # resources :products + # end + + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + # root :to => "welcome#index" + + # See how all your routes lay out with "rake routes" + + # This is a legacy wild controller route that's not recommended for RESTful applications. + # Note: This route will make all actions in every controller accessible via GET requests. + # match ':controller(/:action(/:id(.:format)))' +end diff --git a/spec/dummy/public/404.html b/spec/dummy/public/404.html new file mode 100644 index 0000000..9a48320 --- /dev/null +++ b/spec/dummy/public/404.html @@ -0,0 +1,26 @@ + + + + The page you were looking for doesn't exist (404) + + + + + +
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+ + diff --git a/spec/dummy/public/422.html b/spec/dummy/public/422.html new file mode 100644 index 0000000..83660ab --- /dev/null +++ b/spec/dummy/public/422.html @@ -0,0 +1,26 @@ + + + + The change you wanted was rejected (422) + + + + + +
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+ + diff --git a/spec/dummy/public/500.html b/spec/dummy/public/500.html new file mode 100644 index 0000000..b80307f --- /dev/null +++ b/spec/dummy/public/500.html @@ -0,0 +1,26 @@ + + + + We're sorry, but something went wrong (500) + + + + + +
+

We're sorry, but something went wrong.

+

We've been notified about this issue and we'll take a look at it shortly.

+
+ + diff --git a/spec/dummy/public/favicon.ico b/spec/dummy/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/spec/dummy/public/stylesheets/.gitkeep b/spec/dummy/public/stylesheets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/spec/dummy/script/rails b/spec/dummy/script/rails new file mode 100644 index 0000000..f8da2cf --- /dev/null +++ b/spec/dummy/script/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands' diff --git a/spec/lib/washout_builder/dispatcher_spec.rb b/spec/lib/washout_builder/dispatcher_spec.rb new file mode 100644 index 0000000..e2f8b3e --- /dev/null +++ b/spec/lib/washout_builder/dispatcher_spec.rb @@ -0,0 +1,51 @@ +#encoding:utf-8 + +require 'spec_helper' + +describe WashoutBuilder::Dispatcher do + + class Dispatcher < ApplicationController + soap_service + + def params + @_params + end + end + + + + + describe "#_load_params" do + let(:dispatcher) { Dispatcher.new } + let(:soap_config) { + OpenStruct.new( + WashOut::Rails::Engine.config.wash_out.merge( { camelize_wsdl: false }) + ) + } + it "should load params for an array" do + spec = WashOut::Param.parse_def(soap_config, {:my_array => [:integer] } ) + xml_data = {:my_array => [1, 2, 3]} + dispatcher._load_params(spec, xml_data).should == {"my_array" => [1, 2, 3]} + end + + it "should load params for an empty array" do + spec = WashOut::Param.parse_def(soap_config, {:my_array => [:integer] } ) + xml_data = {} + dispatcher._load_params(spec, xml_data).should == {} + end + + it "should load params for a nested array" do + spec = WashOut::Param.parse_def(soap_config, {:nested => {:my_array => [:integer]}} ) + xml_data = {:nested => {:my_array => [1, 2, 3]}} + dispatcher._load_params(spec, xml_data).should == {"nested" => {"my_array" => [1, 2, 3]}} + end + + it "should load params for an empty nested array" do + spec = WashOut::Param.parse_def(soap_config, {:nested => {:empty => [:integer] }} ) + xml_data = {:nested => nil} + dispatcher._load_params(spec, xml_data).should == {"nested" => {}} + end + + end + +end diff --git a/spec/lib/washout_builder/document/complex_type_spec.rb b/spec/lib/washout_builder/document/complex_type_spec.rb new file mode 100644 index 0000000..e69de29 diff --git a/spec/lib/washout_builder/document/generator_spec.rb b/spec/lib/washout_builder/document/generator_spec.rb new file mode 100644 index 0000000..e69de29 diff --git a/spec/lib/washout_builder/document/virtus_model_spec.rb b/spec/lib/washout_builder/document/virtus_model_spec.rb new file mode 100644 index 0000000..e69de29 diff --git a/spec/lib/washout_builder/engine_spec.rb b/spec/lib/washout_builder/engine_spec.rb new file mode 100644 index 0000000..bce3191 --- /dev/null +++ b/spec/lib/washout_builder/engine_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' + + +describe WashoutBuilder::Engine do + +end \ No newline at end of file diff --git a/spec/lib/washout_builder/soap_spec.rb b/spec/lib/washout_builder/soap_spec.rb new file mode 100644 index 0000000..d9386d8 --- /dev/null +++ b/spec/lib/washout_builder/soap_spec.rb @@ -0,0 +1,15 @@ +#encoding:utf-8 + +require 'spec_helper' + +describe WashoutBuilder::SOAP do + +# [ +# WashoutBuilder::Dispatcher, +# WashOut::Rails::Controller +# ].each do |extension| +# specify { WashoutBuilder::SOAP.included_modules.should include(extension) } +# end +# + +end diff --git a/spec/lib/washout_builder/type_spec.rb b/spec/lib/washout_builder/type_spec.rb new file mode 100644 index 0000000..ee779f7 --- /dev/null +++ b/spec/lib/washout_builder/type_spec.rb @@ -0,0 +1,21 @@ +#encoding:utf-8 + +require 'spec_helper' + +describe WashoutBuilder::Type do + + [ "string", + "integer", + "double", + "boolean", + "date", + "datetime", + "float", + "time", + "int"].each do |type| + it "defines a list of types" do + WashoutBuilder::Type::BASIC_TYPES.should include(type) + end + end + +end \ No newline at end of file diff --git a/spec/lib/washout_builder_spec.rb b/spec/lib/washout_builder_spec.rb new file mode 100644 index 0000000..f7fad37 --- /dev/null +++ b/spec/lib/washout_builder_spec.rb @@ -0,0 +1,697 @@ +#encoding:utf-8 + +require 'spec_helper' + +describe WashoutBuilder do + + before(:each) do + WashOut::Rails::Engine.config.wash_out[:wsdl_style] = 'rpc' + WashOut::Rails::Engine.config.wash_out[:parser] = :nokogiri + WashOut::Rails::Engine.config.wash_out[:catch_xml_errors] = true + end + + let :nori do + Nori.new( + :strip_namespaces => true, + :advanced_typecasting => true, + :convert_tags_to => lambda {|x| x.snakecase.to_sym} + ) + end + + def savon(method, message={}, &block) + message = {:value => message} unless message.is_a?(Hash) + + savon_client = Savon::Client.new(:log => false, :wsdl => 'http://app/api/wsdl', &block) + result = savon_client.call(method, :message => message) + result.respond_to?(:to_hash) ? result.to_hash : result + end + + def savon!(method, message={}, &block) + message = {:value => message} unless message.is_a?(Hash) + + savon_client = Savon::Client.new(:log => true, :wsdl => 'http://app/api/wsdl', &block) + result = savon_client.call(method, :message => message) + result.respond_to?(:to_hash) ? result.to_hash : result + end + + describe "Module" do + it "includes" do + lambda { + mock_controller do + # nothing + end + }.should_not raise_exception + end + + it "allows definition of a simple action" do + lambda { + mock_controller do + soap_action "answer", :args => nil, :return => :integer + end + }.should_not raise_exception + end + end + + describe "WSDL" do + let :wsdl do + mock_controller do + soap_action :result, :args => nil, :return => :int + + soap_action "getArea", :args => { + :circle => [{ + :center => { :x => [:integer], :y => :integer }, + :radius => :double + }]}, + :return => { :area => :double } + soap_action "rocky", :args => { :circle1 => { :x => :integer } }, + :return => { :circle2 => { :y => :integer } } + end + + HTTPI.get("http://app/api/wsdl").body + end + + let :xml do + nori.parse wsdl + end + + it "lists operations" do + operations = xml[:definitions][:binding][:operation] + operations.should be_a_kind_of(Array) + + operations.map{|e| e[:'@name']}.sort.should == ['Result', 'getArea', 'rocky'].sort + end + + it "defines complex types" do + wsdl.include?('').should == true + end + + it "defines arrays" do + x = xml[:definitions][:types][:schema][:complex_type]. + find{|x| x[:'@name'] == 'Center'}[:sequence][:element]. + find{|x| x[:'@name'] == 'X'} + + x[:'@min_occurs'].should == "0" + x[:'@max_occurs'].should == "unbounded" + end + end + + describe "Dispatcher" do + + context "simple actions" do + it "accepts requests with no HTTP header" do + mock_controller do + soap_action "answer", :args => nil, :return => :int + def answer + render :soap => "42" + end + end + + request = <<-XML + + + + + 42 + + + + XML + + HTTPI.post("http://app/api/action", request).body.should == <<-XML + + + + + 42 + + + + XML + end + + it "accept no parameters" do + mock_controller do + soap_action "answer", :args => nil, :return => :int + def answer + render :soap => "42" + end + end + + savon(:answer)[:answer_response][:value]. + should == "42" + end + + it "accept insufficient parameters" do + mock_controller do + soap_action "answer", :args => {:a => :integer}, :return => :integer + def answer + render :soap => "42" + end + end + + savon(:answer)[:answer_response][:value]. + should == "42" + end + + it "accept empty parameter" do + mock_controller do + soap_action "answer", :args => {:a => :string}, :return => {:a => :string} + def answer + render :soap => {:a => params[:a]} + end + end + savon(:answer, :a => '')[:answer_response][:a]. + should == {:"@xsi:type"=>"xsd:string"} + end + + it "accept one parameter" do + mock_controller do + soap_action "checkAnswer", :args => :integer, :return => :boolean, :to => 'check_answer' + def check_answer + render :soap => (params[:value] == 42) + end + end + + savon(:check_answer, 42)[:check_answer_response][:value].should == true + savon(:check_answer, 13)[:check_answer_response][:value].should == false + end + + it "accept two parameters" do + mock_controller do + soap_action "funky", :args => { :a => :integer, :b => :string }, :return => :string + def funky + render :soap => ((params[:a] * 10).to_s + params[:b]) + end + end + + savon(:funky, :a => 42, :b => 'k')[:funky_response][:value].should == '420k' + end + end + + context "complex actions" do + it "accept nested structures" do + mock_controller do + soap_action "getArea", :args => { :circle => { :center => { :x => :integer, + :y => :integer }, + :radius => :double } }, + :return => { :area => :double, + :distance_from_o => :double }, + :to => :get_area + def get_area + circle = params[:circle] + render :soap => { :area => Math::PI * circle[:radius] ** 2, + :distance_from_o => Math.sqrt(circle[:center][:x] ** 2 + circle[:center][:y] ** 2) } + end + end + + message = { :circle => { :center => { :x => 3, :y => 4 }, + :radius => 5 } } + + savon(:get_area, message)[:get_area_response]. + should == ({ :area => (Math::PI * 25).to_s, :distance_from_o => (5.0).to_s }) + end + + it "accept arrays" do + mock_controller do + soap_action "rumba", + :args => { + :rumbas => [:integer] + }, + :return => nil + def rumba + params.should == {"rumbas" => [1, 2, 3]} + render :soap => nil + end + end + + savon(:rumba, :rumbas => [1, 2, 3]) + end + + it "accept empty arrays" do + mock_controller do + soap_action "rumba", + :args => { + :my_array => [:integer] + }, + :return => nil + def rumba + params.should == {} + render :soap => nil + end + end + savon(:rumba, :empty => []) + end + + it "accept nested empty arrays" do + mock_controller do + soap_action "rumba", + :args => { + :nested => {:my_array => [:integer] } + }, + :return => nil + def rumba + params.should == {"nested" => {}} + render :soap => nil + end + end + savon(:rumba, :nested => {:my_array => []}) + end + + it "accept nested structures inside arrays" do + mock_controller do + soap_action "rumba", + :args => { + :rumbas => [ { + :zombies => :string, + :puppies => :string + } ] + }, + :return => nil + def rumba + params.should == { + "rumbas" => [ + {"zombies" => 'suck', "puppies" => 'rock'}, + {"zombies" => 'slow', "puppies" => 'fast'} + ] + } + render :soap => nil + end + end + + savon :rumba, :rumbas => [ + {:zombies => 'suck', :puppies => 'rock'}, + {:zombies => 'slow', :puppies => 'fast'} + ] + end + + it "respond with nested structures" do + mock_controller do + soap_action "gogogo", + :args => nil, + :return => { + :zoo => :string, + :boo => { :moo => :string, :doo => :string } + } + def gogogo + render :soap => { + :zoo => 'zoo', + :boo => { :moo => 'moo', :doo => 'doo' } + } + end + end + + savon(:gogogo)[:gogogo_response]. + should == {:zoo=>"zoo", :boo=>{:moo=>"moo", :doo=>"doo", :"@xsi:type"=>"tns:Boo"}} + end + + it "respond with arrays" do + mock_controller do + soap_action "rumba", + :args => nil, + :return => [:integer] + def rumba + render :soap => [1, 2, 3] + end + end + + savon(:rumba)[:rumba_response].should == {:value => ["1", "2", "3"]} + end + + it "respond with complex structures inside arrays" do + mock_controller do + soap_action "rumba", + :args => nil, + :return => { + :rumbas => [{:zombies => :string, :puppies => :string}] + } + def rumba + render :soap => + {:rumbas => [ + {:zombies => "suck1", :puppies => "rock1" }, + {:zombies => "suck2", :puppies => "rock2" } + ] + } + end + end + + savon(:rumba)[:rumba_response].should == { + :rumbas => [ + {:zombies => "suck1",:puppies => "rock1", :"@xsi:type"=>"tns:Rumbas"}, + {:zombies => "suck2", :puppies => "rock2", :"@xsi:type"=>"tns:Rumbas" } + ] + } + end + + it "respond with structs in structs in arrays" do + mock_controller do + soap_action "rumba", + :args => nil, + :return => [{:rumbas => {:zombies => :integer}}] + + def rumba + render :soap => [{:rumbas => {:zombies => 100000}}, {:rumbas => {:zombies => 2}}] + end + end + + savon(:rumba)[:rumba_response].should == { + :value => [ + { + :rumbas => { + :zombies => "100000", + :"@xsi:type" => "tns:Rumbas" + }, + :"@xsi:type" => "tns:Value" + }, + { + :rumbas => { + :zombies => "2", + :"@xsi:type" => "tns:Rumbas" + }, + :"@xsi:type"=>"tns:Value" + } + ] + } + end + + context "with arrays missing" do + it "respond with simple definition" do + mock_controller do + soap_action "rocknroll", + :args => nil, :return => { :my_value => [:integer] } + def rocknroll + render :soap => {} + end + end + + savon(:rocknroll)[:rocknroll_response].should be_nil + end + + it "respond with complext definition" do + mock_controller do + soap_action "rocknroll", + :args => nil, :return => { :my_value => [{ :value => :integer }] } + def rocknroll + render :soap => {} + end + end + + savon(:rocknroll)[:rocknroll_response].should be_nil + end + + it "respond with nested simple definition" do + mock_controller do + soap_action "rocknroll", + :args => nil, :return => { :my_value => { :my_array => [{ :value => :integer }] } } + def rocknroll + render :soap => {} + end + end + + savon(:rocknroll)[:rocknroll_response][:my_value]. + should == { :"@xsi:type" => "tns:MyValue" } + end + + it "handles incomplete array response" do + mock_controller do + soap_action "rocknroll", + :args => nil, :return => { :my_value => [{ :value => :string }] } + def rocknroll + render :soap => { :my_value => [nil] } + end + end + + expect{savon(:rocknroll)}.not_to raise_error + end + end + end + + context "types" do + it "recognize boolean" do + mock_controller do + soap_action "true", :args => :boolean, :return => :nil + def true + params[:value].should == true + render :soap => nil + end + + soap_action "false", :args => :boolean, :return => :nil + def false + params[:value].should == false + render :soap => nil + end + end + + savon(:true, :value => "true") + savon(:true, :value => "1") + savon(:false, :value => "false") + savon(:false, :value => "0") + end + + it "recognize dates" do + mock_controller do + soap_action "date", :args => :date, :return => :nil + def date + params[:value].should == Date.parse('2000-12-30') unless params[:value].blank? + render :soap => nil + end + end + + savon(:date, :value => '2000-12-30') + lambda { savon(:date) }.should_not raise_exception + end + + it "recognize base64Binary" do + mock_controller do + soap_action "base64", :args => :base64Binary, :return => :nil + def base64 + params[:value].should == 'test' unless params[:value].blank? + render :soap => nil + end + end + + savon(:base64, :value => Base64.encode64('test')) + lambda { savon(:base64) }.should_not raise_exception + end + end + + context "errors" do + it "raise for incorrect requests" do + mock_controller do + soap_action "duty", + :args => {:bad => {:a => :string, :b => :string}, :good => {:a => :string, :b => :string}}, + :return => nil + def duty + render :soap => nil + end + end + + lambda { + savon(:duty, :bad => 42, :good => nil) + }.should raise_exception(Savon::SOAPFault) + end + + it "raise for date in incorrect format" do + mock_controller do + soap_action "date", :args => :date, :return => :nil + def date + render :soap => nil + end + end + lambda { + savon(:date, :value => 'incorrect format') + }.should raise_exception(Savon::SOAPFault) + end + + it "raise to report SOAP errors", :fails =>true do + mock_controller do + soap_action "error", :args => { :need_error => :boolean }, :return => nil + def error + raise self.class.const_get(:SOAPError), "you wanted one" if params[:need_error] + render :soap => nil + end + end + + lambda { savon(:error, :need_error => false) }.should_not raise_exception + lambda { savon(:error, :need_error => true) }.should raise_exception(Savon::SOAPFault) + end + + it "raise for manual throws" do + mock_controller do + soap_action "error", :args => nil, :return => nil + def error + render_soap_error "a message" + end + end + + lambda { savon(:error) }.should raise_exception(Savon::SOAPFault) + end + + it "raise when response structure mismatches" do + mock_controller do + soap_action 'bad', :args => :integer, :return => { + :basic => :string, + :stallions => { + :stallion => [ + :name => :string, + :wyldness => :integer, + ] + }, + } + def bad + render :soap => { + :basic => 'hi', + :stallions => [{:name => 'ted', :wyldness => 11}] + } + end + + soap_action 'bad2', :args => :integer, :return => { + :basic => :string, + :telephone_booths => [:string] + } + def bad2 + render :soap => { + :basic => 'hihi', + :telephone_booths => 'oops' + } + end + end + + lambda { savon(:bad) }.should raise_exception( + WashOut::ProgrammerError, + /SOAP response .*wyldness.*Array.*Hash.*stallion/ + ) + + lambda { savon(:bad2) }.should raise_exception( + WashOut::ProgrammerError, + /SOAP response .*oops.*String.*telephone_booths.*Array/ + ) + end + end + + context "deprecates" do + it "old syntax" do + # save rspec context check + raise_runtime_exception = raise_exception(RuntimeError) + + mock_controller do + lambda { + soap_action "rumba", + :args => :integer, + :return => [] + }.should raise_runtime_exception + def rumba + render :soap => nil + end + end + end + end + + it "allows arbitrary action names" do + name = 'AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything' + + mock_controller do + soap_action name, :args => nil, :return => :integer, :to => :answer + def answer + render :soap => "forty two" + end + end + + savon(name.underscore.to_sym)["#{name.underscore}_response".to_sym][:value]. + should == "forty two" + end + + it "respects :response_tag option" do + mock_controller do + soap_action "specific", :response_tag => "test", :return => :string + def specific + render :soap => "test" + end + end + + savon(:specific).should == {:test => {:value=>"test"}} + end + + it "handles snakecase option properly" do + mock_controller(snakecase_input: false, camelize_wsdl: false) do + soap_action "rocknroll", :args => {:ZOMG => :string}, :return => nil + def rocknroll + params["ZOMG"].should == "yam!" + render :soap => nil + end + end + + savon(:rocknroll, "ZOMG" => 'yam!') + end + + end + + describe "WS Security" do + + it "appends username_token to params" do + mock_controller(wsse_username: "gorilla", wsse_password: "secret") do + soap_action "checkToken", :args => :integer, :return => nil, :to => 'check_token' + def check_token + request.env['WSSE_TOKEN']['username'].should == "gorilla" + request.env['WSSE_TOKEN']['password'].should == "secret" + render :soap => nil + end + end + + savon(:check_token, 42) do + wsse_auth "gorilla", "secret" + end + end + + it "handles PasswordText auth" do + mock_controller(wsse_username: "gorilla", wsse_password: "secret") do + soap_action "checkAuth", :args => :integer, :return => :boolean, :to => 'check_auth' + def check_auth + render :soap => (params[:value] == 42) + end + end + + # correct auth + lambda { savon(:check_auth, 42){ wsse_auth "gorilla", "secret" } }. + should_not raise_exception + + # wrong user + lambda { savon(:check_auth, 42){ wsse_auth "chimpanzee", "secret" } }. + should raise_exception(Savon::SOAPFault) + + # wrong pass + lambda { savon(:check_auth, 42){ wsse_auth "gorilla", "nicetry" } }. + should raise_exception(Savon::SOAPFault) + + # no auth + lambda { savon(:check_auth, 42) }. + should raise_exception(Savon::SOAPFault) + end + + it "handles PasswordDigest auth" do + mock_controller(wsse_username: "gorilla", wsse_password: "secret") do + soap_action "checkAuth", :args => :integer, :return => :boolean, :to => 'check_auth' + def check_auth + render :soap => (params[:value] == 42) + end + end + + # correct auth + lambda { savon(:check_auth, 42){ wsse_auth "gorilla", "secret", :digest } }. + should_not raise_exception + + # wrong user + lambda { savon(:check_auth, 42){ wsse_auth "chimpanzee", "secret", :digest } }. + should raise_exception(Savon::SOAPFault) + + # wrong pass + lambda { savon(:check_auth, 42){ wsse_auth "gorilla", "nicetry", :digest } }. + should raise_exception(Savon::SOAPFault) + + # no auth + lambda { savon(:check_auth, 42) }. + should raise_exception(Savon::SOAPFault) + end + + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 478658e..8563fea 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,8 @@ # Configure Rails Envinronment ENV["RAILS_ENV"] = "test" +require 'nori' +require 'nokogiri' require 'active_support' require "simplecov" SimpleCov.start do