xing-backend 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/lib/deprecated_classes.rb +28 -0
  3. data/lib/xing/controllers/base.rb +40 -0
  4. data/lib/xing/controllers/root_resources_controller.rb +12 -0
  5. data/lib/xing/engine.rb +27 -0
  6. data/lib/xing/mappers/base.rb +135 -0
  7. data/lib/xing/mappers.rb +6 -0
  8. data/lib/xing/serializers/base.rb +50 -0
  9. data/lib/xing/serializers/root_resources.rb +9 -0
  10. data/lib/xing/serializers.rb +7 -0
  11. data/lib/xing/services/error_converter.rb +47 -0
  12. data/lib/xing/services/json_tree_lister.rb +56 -0
  13. data/lib/xing/services/snapshot_fetcher.rb +33 -0
  14. data/lib/xing/services/snapshot_writer.rb +19 -0
  15. data/lib/xing/services.rb +9 -0
  16. data/lib/xing-backend.rb +34 -0
  17. data/spec/deprecated_classes/active_model_error_converter_spec.rb +11 -0
  18. data/spec/deprecated_classes/base_serializer_spec.rb +11 -0
  19. data/spec/deprecated_classes/hypermedia_json_mapper_spec.rb +11 -0
  20. data/spec/deprecated_classes/json_tree_lister_spec.rb +14 -0
  21. data/spec/deprecated_classes/remote_snapshot_fetcher_spec.rb +8 -0
  22. data/spec/deprecated_classes/resources_serializer_spec.rb +12 -0
  23. data/spec/xing/controllers/base_spec.rb +7 -0
  24. data/spec/xing/controllers/root_resources_controller_spec.rb +8 -0
  25. data/spec/xing/mappers/base_spec.rb +56 -0
  26. data/spec/xing/serializers/base_spec.rb +32 -0
  27. data/spec/xing/serializers/root_resources_spec.rb +20 -0
  28. data/spec/xing/services/error_converter_spec.rb +61 -0
  29. data/spec/xing/services/json_tree_lister_spec.rb +109 -0
  30. data/spec/xing/services/snapshot_fetcher_spec.rb +75 -0
  31. data/spec/xing_spec.rb +7 -0
  32. data/spec_help/dummy/README.rdoc +28 -0
  33. data/spec_help/dummy/Rakefile +6 -0
  34. data/spec_help/dummy/app/assets/javascripts/application.js +13 -0
  35. data/spec_help/dummy/app/assets/stylesheets/application.css +15 -0
  36. data/spec_help/dummy/app/controllers/application_controller.rb +5 -0
  37. data/spec_help/dummy/app/helpers/application_helper.rb +2 -0
  38. data/spec_help/dummy/app/views/layouts/application.html.erb +14 -0
  39. data/spec_help/dummy/bin/bundle +3 -0
  40. data/spec_help/dummy/bin/rails +4 -0
  41. data/spec_help/dummy/bin/rake +4 -0
  42. data/spec_help/dummy/bin/setup +29 -0
  43. data/spec_help/dummy/config/application.rb +25 -0
  44. data/spec_help/dummy/config/boot.rb +5 -0
  45. data/spec_help/dummy/config/database.yml +25 -0
  46. data/spec_help/dummy/config/environment.rb +5 -0
  47. data/spec_help/dummy/config/environments/development.rb +41 -0
  48. data/spec_help/dummy/config/environments/production.rb +79 -0
  49. data/spec_help/dummy/config/environments/test.rb +42 -0
  50. data/spec_help/dummy/config/initializers/assets.rb +11 -0
  51. data/spec_help/dummy/config/initializers/backtrace_silencers.rb +7 -0
  52. data/spec_help/dummy/config/initializers/cookies_serializer.rb +3 -0
  53. data/spec_help/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  54. data/spec_help/dummy/config/initializers/inflections.rb +16 -0
  55. data/spec_help/dummy/config/initializers/mime_types.rb +4 -0
  56. data/spec_help/dummy/config/initializers/session_store.rb +3 -0
  57. data/spec_help/dummy/config/initializers/wrap_parameters.rb +14 -0
  58. data/spec_help/dummy/config/locales/en.yml +23 -0
  59. data/spec_help/dummy/config/routes.rb +56 -0
  60. data/spec_help/dummy/config/secrets.yml +22 -0
  61. data/spec_help/dummy/config.ru +4 -0
  62. data/spec_help/dummy/db/test.sqlite3 +0 -0
  63. data/spec_help/dummy/log/test.log +48 -0
  64. data/spec_help/dummy/public/404.html +67 -0
  65. data/spec_help/dummy/public/422.html +67 -0
  66. data/spec_help/dummy/public/500.html +66 -0
  67. data/spec_help/dummy/public/favicon.ico +0 -0
  68. data/spec_help/spec_helper.rb +29 -0
  69. metadata +222 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 81e5fbc0eab9a8177181d26d407b447a7bde5e78
4
+ data.tar.gz: 5e55cf12697cab2d64f021c4e918643382867212
5
+ SHA512:
6
+ metadata.gz: ae67633934b7e3b807aca8f178895a52d1751188b04c4cebfd051abf5076280eb9c9063ab612cc8c12137bf70c02ebb64bd4999339c1451d953f9a932c0a6316
7
+ data.tar.gz: 0ea48e465a1e35135b46c093161ddd836c8117b3568a2765cb223044f9a53a3dfb745d4d12306fc6b9a320c7c6c5ae4def2ca7d32d68b8c4bef478b03a5219a6
@@ -0,0 +1,28 @@
1
+ require 'xing-backend'
2
+ require 'active_support/deprecation'
3
+
4
+ module Xing
5
+ DEPRECATED_CLASSES = {
6
+ :HypermediaJSONMapper => Xing::Mappers::Base,
7
+ :BaseSerializer => Xing::Serializers::Base,
8
+ :ResourcesSerializer => Xing::Serializers::RootResources,
9
+ :JsonTreeLister => Xing::Services::JsonTreeLister,
10
+ :ActiveModelErrorConverter => Xing::Services::ErrorConverter,
11
+ :RemoteSnapshotFetcher => Xing::Services::SnapshotFetcher
12
+ }
13
+ end
14
+
15
+ #Xing::DEPRECATED_CLASSES.each do |old, new|
16
+
17
+ ## with great power comes great responsibility
18
+ #Object.const_set(old, ActiveSupport::Deprecation::DeprecatedConstantProxy.new(old, new))
19
+ #end
20
+
21
+ def Object.const_missing(name)
22
+ if (klass = ::Xing::DEPRECATED_CLASSES[name.to_sym])
23
+ warn "[DEPRECATION] #{name} is deprecated. Please use #{klass.to_s} instead."
24
+ klass
25
+ else
26
+ super
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ require 'devise_token_auth'
2
+
3
+ module Xing
4
+ module Controllers
5
+ class Base < ActionController::Base
6
+ include DeviseTokenAuth::Concerns::SetUserByToken
7
+
8
+ respond_to :json
9
+
10
+ protect_from_forgery
11
+ before_filter :check_format
12
+
13
+ def check_format
14
+ if request.subdomains.include? Xing.backend_subdomain
15
+ if request.headers["Accept"] =~ /json/
16
+ params[:format] = :json
17
+ else
18
+ render :nothing => true, :status => 406
19
+ end
20
+ end
21
+ end
22
+
23
+ def json_body
24
+ @json_body ||= request.body.read
25
+ end
26
+
27
+ def parse_json
28
+ @parsed_json ||= JSON.parse(json_body)
29
+ end
30
+
31
+ def failed_to_process(error_document)
32
+ render :status => 422, :json => error_document
33
+ end
34
+
35
+ def successful_create(new_resource_path)
36
+ render :status => 201, :json => {}, :location => new_resource_path
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,12 @@
1
+ require 'rails/rfc6570'
2
+
3
+ module Xing
4
+ module Controllers
5
+ class RootResourcesController < ::Xing::Controllers::Base
6
+ def index
7
+ @resources = rfc6570_routes(ignore: %w(format), path_only: true)
8
+ render :json => Xing::Serializers::RootResources.new(@resources)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,27 @@
1
+ module Xing
2
+
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Xing
5
+
6
+ config.autoload_paths += Dir[File.join(__FILE__, '../controllers/**/')]
7
+
8
+ config.generators do |g|
9
+ g.test_framework :rspec
10
+ end
11
+
12
+ # The ErrorConverter leverages (abuses?) the I18n mechanism to translate
13
+ # ActiveModel validation errors into Xing JSON resource errors. Here we
14
+ # need to make sure the locales file for language 'json' is loaded for
15
+ # I18n.
16
+ initializer 'xing errors locales path' do
17
+ I18n.load_path += Dir[File.join(File.dirname(__FILE__), '..', 'config', 'locales', '*.{rb,yml}')]
18
+ end
19
+
20
+ # Set the backend subdomain if it hasn't been configured by the user.
21
+ initializer 'set subdomain' do
22
+ Xing.configure do |xng_config|
23
+ xng_config.backend_subdomain ||= 'api'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,135 @@
1
+ # This code is only used in the context of Rails apps, and depends pretty
2
+ # heavily on rails core extensions, so we are requiring them here.
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+
6
+ module Xing
7
+ module Mappers
8
+ class Base
9
+ class MissingLinkException < Exception; end
10
+
11
+ # Subclasses must define:
12
+ # aliases -- for self.record
13
+ # record_class -- if the mapper maps to an AR object
14
+ #
15
+ # Subclasses should usually define:
16
+ # assign_values -- move values from JSON into the mapped AR record
17
+ #
18
+ # Subclasses may also want to define:
19
+ # find_existing_record -- for locating the underlying AR record
20
+ # build_new_record -- for for instantiating a new underlying AR record
21
+ # map_nested_models
22
+ # build_errors -- if simply copying AR errors is insufficient
23
+ # save -- if they need to save more than 1 AR record
24
+
25
+ # When updating records, pass the locator (e.g. DB id, url_slug, or other
26
+ # unique resource extracted from the resource path) as the second argument.
27
+ def initialize(json, locator = nil)
28
+ @source_json = json
29
+ if @source_json.is_a? String
30
+ @source_hash = JSON.parse(json).with_indifferent_access
31
+ else
32
+ @source_hash = @source_json
33
+ end
34
+ @locator = locator
35
+ end
36
+ attr_accessor :locator, :error_data
37
+ attr_writer :record
38
+
39
+ def router
40
+ Rails.application.routes
41
+ end
42
+
43
+ def normalize_path(path)
44
+ path = "/#{path}"
45
+ path.squeeze!('/')
46
+ path.sub!(%r{/+\Z}, '')
47
+ path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
48
+ path = '/' if path == ''
49
+ path
50
+ end
51
+
52
+ # This helper is used to deconstruct a URL for the purpose of extracting
53
+ # components. For example, menu_item_mapper uses it to extract the url_slug
54
+ # component of a page route. We are here abusing recognize_path, which isn't
55
+ # supposed to be used outside of tests (see
56
+ # https://github.com/rails/rails/issues/2656), but we don't know what the
57
+ # alternative is.
58
+ def route_to(path)
59
+ path = "http://#{BACKEND_SUBDOMAIN}.example.com#{normalize_path(path)}";
60
+ router.recognize_path(path)
61
+ end
62
+
63
+ # Default save - subclasses might override
64
+ def save
65
+ perform_mapping
66
+ unless self.errors[:data].present?
67
+ self.record.save
68
+ end
69
+ end
70
+
71
+ # Default for finding an existing record - override this *or* define
72
+ # #record_class (e.g. `return Page`
73
+ def find_existing_record
74
+ @record = record_class.find(@locator)
75
+ end
76
+
77
+ # Default for building a new record - override this *or* define #record_class
78
+ # (e.g. `return Page`
79
+ def build_new_record
80
+ @record = record_class.new
81
+ end
82
+
83
+ def perform_mapping
84
+ data = unwrap_data(@source_hash)
85
+ self.error_data = Hash.new { |hash, key| hash[key] = {} }
86
+
87
+ assign_values(data)
88
+ map_nested_models
89
+ build_errors
90
+ end
91
+
92
+ def unwrap_data(hash)
93
+ hash['data'].with_indifferent_access
94
+ end
95
+
96
+ def wrap_data(hash)
97
+ {
98
+ data: hash
99
+ }
100
+ end
101
+
102
+ def record
103
+ @record ||= if !locator.nil?
104
+ find_existing_record
105
+ else
106
+ build_new_record
107
+ end
108
+ end
109
+
110
+ def assign_values(data_hash)
111
+ # Override in subclasses to assign needed values here
112
+ record # force loading or creation of the underlying DB record
113
+ update_record
114
+ end
115
+
116
+ # Do nothing if there are no nested models
117
+ # Override this method in subclass if necessary
118
+ def map_nested_models
119
+ end
120
+
121
+ def build_errors
122
+ self.add_ar_arrors(self.record)
123
+ end
124
+
125
+ def errors
126
+ wrap_data(error_data)
127
+ end
128
+
129
+ def add_ar_arrors(object)
130
+ object_errors = ActiveModelErrorConverter.new(object).convert
131
+ error_data.deep_merge!(object_errors)
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,6 @@
1
+ module Xing
2
+ module Mappers
3
+ end
4
+ end
5
+
6
+ require 'xing/mappers/base'
@@ -0,0 +1,50 @@
1
+ require 'rails'
2
+ require 'active_model_serializers'
3
+
4
+ module Xing
5
+ module Serializers
6
+
7
+ # The base class for all Xing serializers that produce
8
+ # Xing Hypermedia JSON resources. In general, subclasses
9
+ # of Xing::Serializers::Base should:
10
+ #
11
+ # * Define a links method that returns a hash of hypermedia links to
12
+ # related resources
13
+ #
14
+ # * Specify the attributes (via the ActiveModel::Serializers 'attributes'
15
+ # class method) that will be copied into the data: block
16
+ # of the generated resource.
17
+ #
18
+ # * Define methods for any attributes that do not exist as plain attributes
19
+ # in the ActiveModel being serialized. Note that this may (and often will) include
20
+ # calling other serializers on related resources or other data, in order to
21
+ # generate embedded resources.
22
+ #
23
+ # Xing serializers descend from ActiveModel::Serializer and are typically
24
+ # instantiated with an instance of an ActiveModel model in the usual way.
25
+ #
26
+ class Base < ActiveModel::Serializer
27
+
28
+ def routes
29
+ Rails.application.routes.url_helpers
30
+ end
31
+
32
+ def root
33
+ false
34
+ end
35
+
36
+ def as_json_with_wrap(options={})
37
+ {
38
+ :links => links,
39
+ :data => as_json_without_wrap
40
+ }
41
+ end
42
+
43
+ def links
44
+ {}
45
+ end
46
+
47
+ alias_method_chain :as_json, :wrap
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ module Xing
2
+ module Serializers
3
+ class RootResources < Base
4
+ def links
5
+ object
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Xing
2
+ module Serializers
3
+ end
4
+ end
5
+
6
+ require 'xing/serializers/base'
7
+ require 'xing/serializers/root_resources'
@@ -0,0 +1,47 @@
1
+ module Xing
2
+ module Services
3
+ class ErrorConverter
4
+
5
+
6
+ def initialize(am_object)
7
+ @am_object = am_object
8
+ end
9
+ attr_reader :am_object
10
+
11
+ # This is a terrible hack to preserve the semantic meaning of
12
+ # different error types -- neccesary because ActiveModel::Errors
13
+ # frustratingly translates semantic error messages through i18n
14
+ # as soon as it gets them
15
+ def json_errors
16
+ @json_errors ||= begin
17
+ old_locale = I18n.locale
18
+ I18n.locale = "json"
19
+ am_object.valid?
20
+ error_hash = am_object.errors.to_hash.deep_dup
21
+ I18n.locale = old_locale
22
+ error_hash
23
+ end
24
+ end
25
+
26
+ def regular_errors
27
+ @regular_errors ||= begin
28
+ am_object.valid?
29
+ am_object.errors.to_hash.deep_dup
30
+ end
31
+ end
32
+
33
+ def convert
34
+ final_errors = {}
35
+ json_errors.each_key do |key|
36
+ final_errors[key] = {
37
+ :type => json_errors[key][0],
38
+ :message => regular_errors[key][0]
39
+ }
40
+ end
41
+ final_errors
42
+ end
43
+
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,56 @@
1
+ require 'active_model_serializers'
2
+ require 'active_support/core_ext'
3
+
4
+ module Xing
5
+ module Services
6
+ class JsonTreeLister
7
+
8
+ class TreeNode
9
+ include ActiveModel::SerializerSupport
10
+
11
+ def initialize(node, children)
12
+ @node = node
13
+ @children = children
14
+ end
15
+
16
+ attr_reader :node, :children
17
+ end
18
+
19
+ def initialize(nodes, node_serializer)
20
+ @nodes = nodes
21
+ @node_serializer = node_serializer
22
+ @stack = [[]]
23
+ @path = []
24
+ end
25
+
26
+ def render_node(node, children)
27
+ @node_serializer.new(TreeNode.new(node, children)).as_json
28
+ end
29
+
30
+ def pop_level
31
+ children = @stack.pop
32
+ @stack.last << render_node(@path.pop, children)
33
+ end
34
+
35
+ def render
36
+ (@nodes + [nil]).each_cons(2) do |this, after|
37
+ until @path.empty? or @path.last.is_ancestor_of?(this)
38
+ pop_level
39
+ end
40
+ if after.nil? or !this.is_ancestor_of?(after)
41
+ @stack.last << render_node(this, [])
42
+ else
43
+ @path << this
44
+ @stack << []
45
+ end
46
+ end
47
+ until @path.empty?
48
+ pop_level
49
+ end
50
+ return @stack.last.first
51
+ end
52
+
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,33 @@
1
+ require 'typhoeus'
2
+ require 'addressable/uri'
3
+ require 'xing/services/snapshot_writer'
4
+ require 'sidekiq/worker'
5
+
6
+ module Xing::Services
7
+ class SnapshotFetcher
8
+ include Sidekiq::Worker
9
+ include SnapshotWriter
10
+
11
+ def perform(url, path)
12
+ admin_server = Rails.application.secrets.snapshot_server['url']
13
+ user_password = "#{Rails.application.secrets.snapshot_server['user']}:#{Rails.application.secrets.snapshot_server['password']}"
14
+ snapshot_url = Addressable::URI.join(url,path).to_s
15
+ request = Typhoeus::Request.new(admin_server, userpwd: user_password, params: { url: snapshot_url })
16
+
17
+ hydra = Typhoeus::Hydra.new
18
+ hydra.queue(request)
19
+ hydra.run
20
+
21
+ response = request.response
22
+
23
+ if response.success?
24
+ html = response.body
25
+ write(path, html)
26
+ else
27
+ logger.warn response.status_message
28
+ logger.warn response.body
29
+ raise "Query to #{admin_server} for #{path} failed!"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,19 @@
1
+ module Xing::Services
2
+ module SnapshotWriter
3
+ def write(path, html)
4
+ if Rails.env.test?
5
+ snapshot_file = "#{ Rails.root }/spec/fixtures/sitemap_scratch/#{path.present? ? path : 'index'}.html"
6
+ else
7
+ snapshot_file = "#{ Rails.root }/public/frontend_snapshots/#{path.present? ? path : 'index'}.html"
8
+ end
9
+ dirname = File.dirname(snapshot_file)
10
+ unless File.directory?(dirname)
11
+ FileUtils.mkdir_p(dirname)
12
+ end
13
+
14
+ File.open(snapshot_file, "w+:ASCII-8BIT:UTF-8") do |f|
15
+ f.write(html)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Xing
2
+ module Services
3
+ end
4
+ end
5
+
6
+ require 'xing/services/json_tree_lister'
7
+ require 'xing/services/error_converter'
8
+ require 'xing/services/snapshot_fetcher'
9
+ require 'xing/services/snapshot_writer'
@@ -0,0 +1,34 @@
1
+ require 'rails'
2
+ require 'xing_backend_token_auth'
3
+ require 'rails/rfc6570'
4
+ require 'sidekiq'
5
+
6
+ module Xing
7
+ mattr_accessor :backend_subdomain
8
+
9
+ # Configure xing via pattern similar to Rails:
10
+ #
11
+ # Xing.configure do |config|
12
+ # config.setting = 'value'
13
+ # end
14
+ #
15
+ # Supported settings right now are:
16
+ # * backend_subdomain (default: 'api')
17
+ def self.configure(&block)
18
+ yield self
19
+ end
20
+
21
+ module Controllers
22
+ autoload :Base, 'xing/controllers/base'
23
+
24
+ # NOTE: The rails router expects the the controller to have "Controller" as
25
+ # the suffix of the name, or it complains.
26
+ autoload :RootResourcesController, 'xing/controllers/root_resources_controller'
27
+ end
28
+ end
29
+
30
+ require 'xing/mappers'
31
+ require 'xing/engine'
32
+ require 'xing/serializers'
33
+ require 'xing/services'
34
+ require 'deprecated_classes'
@@ -0,0 +1,11 @@
1
+ require 'deprecated_classes'
2
+
3
+ describe ActiveModelErrorConverter, :type => :deprecation do
4
+ let :resource do
5
+ double('resource')
6
+ end
7
+
8
+ it "should be the correct class" do
9
+ expect(ActiveModelErrorConverter.new(resource)).to be_a(Xing::Services::ErrorConverter)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'deprecated_classes'
2
+
3
+ describe BaseSerializer, :type => :deprecation do
4
+ let :resource do
5
+ double('resource')
6
+ end
7
+
8
+ it "should be the correct class" do
9
+ expect(BaseSerializer.new(resource)).to be_a(Xing::Serializers::Base)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'deprecated_classes'
2
+
3
+ describe HypermediaJSONMapper, :type => :deprecation do
4
+ let :json do
5
+ double('json')
6
+ end
7
+
8
+ it "should be the correct class" do
9
+ expect(HypermediaJSONMapper.new(json)).to be_a(Xing::Mappers::Base)
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require 'deprecated_classes'
2
+
3
+ describe JsonTreeLister, :type => :deprecation do
4
+ let :models do
5
+ double('models')
6
+ end
7
+ let :serializer do
8
+ double('serializer')
9
+ end
10
+
11
+ it "should be the correct class" do
12
+ expect(JsonTreeLister.new(models, serializer)).to be_a(Xing::Services::JsonTreeLister)
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+
2
+ require 'deprecated_classes'
3
+
4
+ describe RemoteSnapshotFetcher do
5
+ it "should be the correct class" do
6
+ expect(RemoteSnapshotFetcher.new()).to be_a(Xing::Services::SnapshotFetcher)
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+
2
+ require 'deprecated_classes'
3
+
4
+ describe ResourcesSerializer do
5
+ let :resource do
6
+ double('hash_of_links')
7
+ end
8
+
9
+ it "should be the correct class" do
10
+ expect(ResourcesSerializer.new(resource)).to be_a(Xing::Serializers::RootResources)
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ require 'xing-backend'
2
+
3
+ describe Xing::Controllers::Base, :type => :controller do
4
+ it "should exist" do
5
+ expect(Xing::Controllers::Base).not_to be_nil
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ require 'xing-backend'
2
+
3
+ describe Xing::Controllers::RootResourcesController, :type => :controller do
4
+ it "should exist" do
5
+ expect(Xing::Controllers::RootResourcesController).not_to be_nil
6
+ end
7
+
8
+ end