From 63c7fe8e4892b22e80c015bf0ecb04496318623b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 8 Jul 2019 12:03:45 +0200 Subject: Refactor controllers for statuses, accounts, and more (#11249) --- app/controllers/well_known/host_meta_controller.rb | 2 +- app/controllers/well_known/webfinger_controller.rb | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) (limited to 'app/controllers/well_known') diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb index 5fb70288a..2e9298c4a 100644 --- a/app/controllers/well_known/host_meta_controller.rb +++ b/app/controllers/well_known/host_meta_controller.rb @@ -13,7 +13,7 @@ module WellKnown format.xml { render content_type: 'application/xrd+xml' } end - expires_in(3.days, public: true) + expires_in 3.days, public: true end end end diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index 28654b61d..53f7f1e27 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -19,7 +19,7 @@ module WellKnown end end - expires_in(3.days, public: true) + expires_in 3.days, public: true rescue ActiveRecord::RecordNotFound head 404 end @@ -27,12 +27,9 @@ module WellKnown private def username_from_resource - resource_user = resource_param - + resource_user = resource_param username, domain = resource_user.split('@') - if Rails.configuration.x.alternate_domains.include?(domain) - resource_user = "#{username}@#{Rails.configuration.x.local_domain}" - end + resource_user = "#{username}@#{Rails.configuration.x.local_domain}" if Rails.configuration.x.alternate_domains.include?(domain) WebfingerResource.new(resource_user).username end -- cgit From 1955aa9f7da8c72312aaafde3142b5ba910d2d0d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2019 11:10:42 +0200 Subject: Bump active_model_serializers from 0.10.9 to 0.10.10 (#11311) * Bump active_model_serializers from 0.10.9 to 0.10.10 Bumps [active_model_serializers](https://github.com/rails-api/active_model_serializers) from 0.10.9 to 0.10.10. - [Release notes](https://github.com/rails-api/active_model_serializers/releases) - [Changelog](https://github.com/rails-api/active_model_serializers/blob/v0.10.10/CHANGELOG.md) - [Commits](https://github.com/rails-api/active_model_serializers/compare/v0.10.9...v0.10.10) Signed-off-by: dependabot-preview[bot] * Add root option to render method --- Gemfile.lock | 8 ++++---- app/controllers/api/v1/instances_controller.rb | 2 +- app/controllers/manifests_controller.rb | 2 +- app/controllers/well_known/keybase_proof_config_controller.rb | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'app/controllers/well_known') diff --git a/Gemfile.lock b/Gemfile.lock index 1a5d54b44..f435b3a35 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -45,9 +45,9 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_model_serializers (0.10.9) - actionpack (>= 4.1, < 6) - activemodel (>= 4.1, < 6) + active_model_serializers (0.10.10) + actionpack (>= 4.1, < 6.1) + activemodel (>= 4.1, < 6.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) active_record_query_trace (1.6.2) @@ -304,7 +304,7 @@ GEM json-ld (~> 3.0) multi_json (~> 1.12) rdf (~> 3.0) - jsonapi-renderer (0.2.0) + jsonapi-renderer (0.2.2) jwt (2.1.0) kaminari (1.1.1) activesupport (>= 4.1.0) diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index b68c78615..93e4f0003 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -6,6 +6,6 @@ class Api::V1::InstancesController < Api::BaseController def show expires_in 3.minutes, public: true - render_with_cache json: {}, serializer: REST::InstanceSerializer + render_with_cache json: {}, serializer: REST::InstanceSerializer, root: 'instance' end end diff --git a/app/controllers/manifests_controller.rb b/app/controllers/manifests_controller.rb index 1e5db4393..491cde745 100644 --- a/app/controllers/manifests_controller.rb +++ b/app/controllers/manifests_controller.rb @@ -5,6 +5,6 @@ class ManifestsController < ApplicationController def show expires_in 3.minutes, public: true - render json: InstancePresenter.new, serializer: ManifestSerializer + render json: InstancePresenter.new, serializer: ManifestSerializer, root: 'instance' end end diff --git a/app/controllers/well_known/keybase_proof_config_controller.rb b/app/controllers/well_known/keybase_proof_config_controller.rb index eb41e586f..e1d43ecbe 100644 --- a/app/controllers/well_known/keybase_proof_config_controller.rb +++ b/app/controllers/well_known/keybase_proof_config_controller.rb @@ -3,7 +3,7 @@ module WellKnown class KeybaseProofConfigController < ActionController::Base def show - render json: {}, serializer: ProofProvider::Keybase::ConfigSerializer + render json: {}, serializer: ProofProvider::Keybase::ConfigSerializer, root: 'keybase_config' end end end -- cgit From 8b9d0a05337b6bcf57b51abf45e21d9474bf2684 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 1 Aug 2019 19:14:02 +0200 Subject: Remove XML version of Webfinger and remove links to Atom feeds (#11460) Fix #11453 --- app/controllers/well_known/webfinger_controller.rb | 11 +---- app/serializers/webfinger_serializer.rb | 1 - app/views/accounts/show.html.haml | 1 - app/views/well_known/webfinger/show.xml.ruby | 51 ---------------------- .../well_known/webfinger_controller_spec.rb | 11 ----- spec/requests/webfinger_request_spec.rb | 17 -------- 6 files changed, 1 insertion(+), 91 deletions(-) delete mode 100644 app/views/well_known/webfinger/show.xml.ruby (limited to 'app/controllers/well_known') diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index 53f7f1e27..50bace217 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -9,17 +9,8 @@ module WellKnown def show @account = Account.find_local!(username_from_resource) - respond_to do |format| - format.any(:json, :html) do - render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json' - end - - format.xml do - render content_type: 'application/xrd+xml' - end - end - expires_in 3.days, public: true + render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json' rescue ActiveRecord::RecordNotFound head 404 end diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb index 008d0c182..c67363b8f 100644 --- a/app/serializers/webfinger_serializer.rb +++ b/app/serializers/webfinger_serializer.rb @@ -26,7 +26,6 @@ class WebfingerSerializer < ActiveModel::Serializer else [ { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) }, - { rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: account_url(object, format: 'atom') }, { rel: 'self', type: 'application/activity+json', href: account_url(object) }, { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" }, ] diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 6846abeb6..bf7a9f5f7 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -7,7 +7,6 @@ - if @account.user&.setting_noindex %meta{ name: 'robots', content: 'noindex, noarchive' }/ - %link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/ %link{ rel: 'alternate', type: 'application/rss+xml', href: account_url(@account, format: 'rss') }/ %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/ diff --git a/app/views/well_known/webfinger/show.xml.ruby b/app/views/well_known/webfinger/show.xml.ruby deleted file mode 100644 index f5a54052a..000000000 --- a/app/views/well_known/webfinger/show.xml.ruby +++ /dev/null @@ -1,51 +0,0 @@ -doc = Ox::Document.new(version: '1.0') - -doc << Ox::Element.new('XRD').tap do |xrd| - xrd['xmlns'] = 'http://docs.oasis-open.org/ns/xri/xrd-1.0' - - xrd << (Ox::Element.new('Subject') << @account.to_webfinger_s) - - if @account.instance_actor? - xrd << (Ox::Element.new('Alias') << instance_actor_url) - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'http://webfinger.net/rel/profile-page' - link['type'] = 'text/html' - link['href'] = about_more_url(instance_actor: true) - end - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'self' - link['type'] = 'application/activity+json' - link['href'] = instance_actor_url - end - else - xrd << (Ox::Element.new('Alias') << short_account_url(@account)) - xrd << (Ox::Element.new('Alias') << account_url(@account)) - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'http://webfinger.net/rel/profile-page' - link['type'] = 'text/html' - link['href'] = short_account_url(@account) - end - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'http://schemas.google.com/g/2010#updates-from' - link['type'] = 'application/atom+xml' - link['href'] = account_url(@account, format: 'atom') - end - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'self' - link['type'] = 'application/activity+json' - link['href'] = account_url(@account) - end - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'http://ostatus.org/schema/1.0/subscribe' - link['template'] = "#{authorize_interaction_url}?acct={uri}" - end - end -end - -('' + Ox.dump(doc, effort: :tolerant)).force_encoding('UTF-8') diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb index b05745ea3..20275aa63 100644 --- a/spec/controllers/well_known/webfinger_controller_spec.rb +++ b/spec/controllers/well_known/webfinger_controller_spec.rb @@ -56,17 +56,6 @@ PEM expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') end - it 'returns JSON when account can be found' do - get :show, params: { resource: alice.to_webfinger_s }, format: :xml - - xml = Nokogiri::XML(response.body) - - expect(response).to have_http_status(200) - expect(response.content_type).to eq 'application/xrd+xml' - expect(xml.at_xpath('//xmlns:Subject').content).to eq 'acct:alice@cb6e6126.ngrok.io' - expect(xml.xpath('//xmlns:Alias').map(&:content)).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') - end - it 'returns http not found when account cannot be found' do get :show, params: { resource: 'acct:not@existing.com' }, format: :json diff --git a/spec/requests/webfinger_request_spec.rb b/spec/requests/webfinger_request_spec.rb index 7f9e1162e..48823714e 100644 --- a/spec/requests/webfinger_request_spec.rb +++ b/spec/requests/webfinger_request_spec.rb @@ -12,23 +12,6 @@ describe 'The webfinger route' do end end - describe 'asking for xml format' do - it 'returns an xml response for xml format' do - get webfinger_url(resource: alice.to_webfinger_s, format: :xml) - - expect(response).to have_http_status(200) - expect(response.content_type).to eq 'application/xrd+xml' - end - - it 'returns an xml response for xml accept header' do - headers = { 'HTTP_ACCEPT' => 'application/xrd+xml' } - get webfinger_url(resource: alice.to_webfinger_s), headers: headers - - expect(response).to have_http_status(200) - expect(response.content_type).to eq 'application/xrd+xml' - end - end - describe 'asking for json format' do it 'returns a json response for json format' do get webfinger_url(resource: alice.to_webfinger_s, format: :json) -- cgit From 60e684af5f9bfb73cd3b7cfe1cf5678c6d8f69c8 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 30 Aug 2019 02:49:33 +0200 Subject: Fix uncaught error when resource param is missing in Webfinger request (#11701) --- app/controllers/well_known/webfinger_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/controllers/well_known') diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index 50bace217..d60bf98ab 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -11,7 +11,7 @@ module WellKnown expires_in 3.days, public: true render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json' - rescue ActiveRecord::RecordNotFound + rescue ActiveRecord::RecordNotFound, ActionController::ParameterMissing head 404 end -- cgit From b671b912113b8705729a44424946bb31ae445df5 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 17 Sep 2019 14:58:02 +0200 Subject: Fix webfinger response not returning 410 when account is suspended (#11869) --- app/controllers/well_known/webfinger_controller.rb | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'app/controllers/well_known') diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index d60bf98ab..480e58f3f 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -5,18 +5,22 @@ module WellKnown include RoutingHelper before_action { response.headers['Vary'] = 'Accept' } + before_action :set_account + before_action :check_account_suspension - def show - @account = Account.find_local!(username_from_resource) + rescue_from ActiveRecord::RecordNotFound, ActionController::ParameterMissing, with: :not_found + def show expires_in 3.days, public: true render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json' - rescue ActiveRecord::RecordNotFound, ActionController::ParameterMissing - head 404 end private + def set_account + @account = Account.find_local!(username_from_resource) + end + def username_from_resource resource_user = resource_param username, domain = resource_user.split('@') @@ -28,5 +32,17 @@ module WellKnown def resource_param params.require(:resource) end + + def check_account_suspension + expires_in(3.minutes, public: true) && gone if @account.suspended? + end + + def not_found + head 404 + end + + def gone + head 410 + end end end -- cgit From 5f69eb89e215fe7dc02cd0dc3f39b13f1945e88b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 29 Sep 2019 21:31:51 +0200 Subject: Add a nodeinfo endpoint (#12002) * Add nodeinfo endpoint * dont commit stuff from my local dev * consistant naming since we implimented 2.1 schema * Add some additional node info stuff * Add nodeinfo endpoint * dont commit stuff from my local dev * consistant naming since we implimented 2.1 schema * expanding this to include federation info * codeclimate feedback * CC feedback * using activeserializers seems like a good idea... * get rid of draft 2.1 version * Reimplement 2.1, also fix metaData -> metadata * Fix metaData -> metadata here too * Fix nodeinfo 2.1 tests * Implement cache for monthly user aggregate * Useless * Remove ostatus from the list of supported protocols * Fix nodeinfo's open_registration reading obsolete setting variable * Only serialize domain blocks with user-facing limitations * Do not needlessly list noop severity in nodeinfo * Only serialize domain blocks info in nodeinfo when they are set to be displayed to everyone * Enable caching for nodeinfo endpoints * Fix rendering nodeinfo * CodeClimate fixes * Please CodeClimate * Change InstancePresenter#active_user_count_months for clarity * Refactor NodeInfoSerializer#metadata * Remove nodeinfo 2.1 support as the schema doesn't exist * Clean-up --- app/controllers/well_known/nodeinfo_controller.rb | 19 ++++++++++ app/lib/activity_tracker.rb | 2 +- app/lib/nodeinfo/adapter.rb | 7 ++++ app/presenters/instance_presenter.rb | 4 +-- app/serializers/nodeinfo/discovery_serializer.rb | 11 ++++++ app/serializers/nodeinfo/serializer.rb | 41 ++++++++++++++++++++++ config/initializers/inflections.rb | 1 + config/routes.rb | 3 ++ .../well_known/nodeinfo_controller_spec.rb | 36 +++++++++++++++++++ 9 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 app/controllers/well_known/nodeinfo_controller.rb create mode 100644 app/lib/nodeinfo/adapter.rb create mode 100644 app/serializers/nodeinfo/discovery_serializer.rb create mode 100644 app/serializers/nodeinfo/serializer.rb create mode 100644 spec/controllers/well_known/nodeinfo_controller_spec.rb (limited to 'app/controllers/well_known') diff --git a/app/controllers/well_known/nodeinfo_controller.rb b/app/controllers/well_known/nodeinfo_controller.rb new file mode 100644 index 000000000..11a699ebc --- /dev/null +++ b/app/controllers/well_known/nodeinfo_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module WellKnown + class NodeInfoController < ActionController::Base + include CacheConcern + + before_action { response.headers['Vary'] = 'Accept' } + + def index + expires_in 3.days, public: true + render_with_cache json: {}, serializer: NodeInfo::DiscoverySerializer, adapter: NodeInfo::Adapter, expires_in: 3.days, root: 'nodeinfo' + end + + def show + expires_in 30.minutes, public: true + render_with_cache json: {}, serializer: NodeInfo::Serializer, adapter: NodeInfo::Adapter, expires_in: 30.minutes, root: 'nodeinfo' + end + end +end diff --git a/app/lib/activity_tracker.rb b/app/lib/activity_tracker.rb index ae3c11b6a..81303b715 100644 --- a/app/lib/activity_tracker.rb +++ b/app/lib/activity_tracker.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ActivityTracker - EXPIRE_AFTER = 90.days.seconds + EXPIRE_AFTER = 6.months.seconds class << self include Redisable diff --git a/app/lib/nodeinfo/adapter.rb b/app/lib/nodeinfo/adapter.rb new file mode 100644 index 000000000..1b48dcb98 --- /dev/null +++ b/app/lib/nodeinfo/adapter.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class NodeInfo::Adapter < ActiveModelSerializers::Adapter::Attributes + def self.default_key_transform + :camel_lower + end +end diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb index becc92c2d..c4caeaa8c 100644 --- a/app/presenters/instance_presenter.rb +++ b/app/presenters/instance_presenter.rb @@ -20,8 +20,8 @@ class InstancePresenter Rails.cache.fetch('user_count') { User.confirmed.joins(:account).merge(Account.without_suspended).count } end - def active_user_count - Rails.cache.fetch('active_user_count') { Redis.current.pfcount(*(0..3).map { |i| "activity:logins:#{i.weeks.ago.utc.to_date.cweek}" }) } + def active_user_count(weeks = 4) + Rails.cache.fetch('active_user_count') { Redis.current.pfcount(*(0...weeks).map { |i| "activity:logins:#{i.weeks.ago.utc.to_date.cweek}" }) } end def status_count diff --git a/app/serializers/nodeinfo/discovery_serializer.rb b/app/serializers/nodeinfo/discovery_serializer.rb new file mode 100644 index 000000000..07ab2a6ee --- /dev/null +++ b/app/serializers/nodeinfo/discovery_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class NodeInfo::DiscoverySerializer < ActiveModel::Serializer + include RoutingHelper + + attribute :links + + def links + [{ rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', href: nodeinfo_schema_url }] + end +end diff --git a/app/serializers/nodeinfo/serializer.rb b/app/serializers/nodeinfo/serializer.rb new file mode 100644 index 000000000..1a7d7a911 --- /dev/null +++ b/app/serializers/nodeinfo/serializer.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class NodeInfo::Serializer < ActiveModel::Serializer + include RoutingHelper + + attributes :version, :software, :protocols, :usage + + def version + '2.0' + end + + def software + { name: 'mastodon', version: Mastodon::Version.to_s } + end + + def services + { outbound: [], inbound: [] } + end + + def protocols + %w(activitypub) + end + + def usage + { + users: { + total: instance_presenter.user_count, + active_month: instance_presenter.active_user_count(4), + active_halfyear: instance_presenter.active_user_count(24), + }, + + local_posts: instance_presenter.status_count, + } + end + + private + + def instance_presenter + @instance_presenter ||= InstancePresenter.new + end +end diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index bf0cb52a3..c65153b0a 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -18,4 +18,5 @@ ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.acronym 'PubSubHubbub' inflect.acronym 'ActivityStreams' inflect.acronym 'JsonLd' + inflect.acronym 'NodeInfo' end diff --git a/config/routes.rb b/config/routes.rb index f1a69cf5c..e43e201a5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,10 +24,13 @@ Rails.application.routes.draw do end get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' } + get '.well-known/nodeinfo', to: 'well_known/nodeinfo#index', as: :nodeinfo, defaults: { format: 'json' } get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger get '.well-known/change-password', to: redirect('/auth/edit') get '.well-known/keybase-proof-config', to: 'well_known/keybase_proof_config#show' + get '/nodeinfo/2.0', to: 'well_known/nodeinfo#show', as: :nodeinfo_schema + get 'manifest', to: 'manifests#show', defaults: { format: 'json' } get 'intent', to: 'intents#show' get 'custom.css', to: 'custom_css#show', as: :custom_css diff --git a/spec/controllers/well_known/nodeinfo_controller_spec.rb b/spec/controllers/well_known/nodeinfo_controller_spec.rb new file mode 100644 index 000000000..12e1fa415 --- /dev/null +++ b/spec/controllers/well_known/nodeinfo_controller_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +describe WellKnown::NodeInfoController, type: :controller do + render_views + + describe 'GET #index' do + it 'returns json document pointing to node info' do + get :index + + expect(response).to have_http_status(200) + expect(response.content_type).to eq 'application/json' + + json = body_as_json + + expect(json[:links]).to be_an Array + expect(json[:links][0][:rel]).to eq 'http://nodeinfo.diaspora.software/ns/schema/2.0' + expect(json[:links][0][:href]).to include 'nodeinfo/2.0' + end + end + + describe 'GET #show' do + it 'returns json document with node info properties' do + get :show + + expect(response).to have_http_status(200) + expect(response.content_type).to eq 'application/json' + + json = body_as_json + + expect(json[:version]).to eq '2.0' + expect(json[:usage]).to be_a Hash + expect(json[:software]).to be_a Hash + expect(json[:protocols]).to be_an Array + end + end +end -- cgit