From 044bab75e4d6a5a80127f5540bf7a6c48c7538bc Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Mon, 15 Jan 2018 13:39:05 +1100 Subject: [PATCH] feat: created dashboard API --- lib/pact_broker/api.rb | 1 + .../api/decorators/dashboard_decorator.rb | 131 ++++++++++++++++++ lib/pact_broker/api/resources/dashboard.rb | 7 + lib/pact_broker/domain/index_item.rb | 6 +- pact_broker.gemspec | 1 + spec/fixtures/dashboard.json | 61 ++++++++ .../decorators/dashboard_decorator_spec.rb | 73 ++++++++++ spec/spec_helper.rb | 1 + 8 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 lib/pact_broker/api/decorators/dashboard_decorator.rb create mode 100644 spec/fixtures/dashboard.json create mode 100644 spec/lib/pact_broker/api/decorators/dashboard_decorator_spec.rb diff --git a/lib/pact_broker/api.rb b/lib/pact_broker/api.rb index 4b9813a13..458087f49 100644 --- a/lib/pact_broker/api.rb +++ b/lib/pact_broker/api.rb @@ -64,6 +64,7 @@ module PactBroker add ['matrix', 'provider', :provider_name, 'latest', :provider_tag, 'consumer', :consumer_name, 'latest', :tag, 'badge'], Api::Resources::MatrixBadge, {resource_name: "matrix_tag_badge"} add ['matrix'], Api::Resources::Matrix, {resource_name: "matrix"} + add ['dashboard'], Api::Resources::Dashboard, {resource_name: "dashboard"} add [], Api::Resources::Index, {resource_name: "index"} end end diff --git a/lib/pact_broker/api/decorators/dashboard_decorator.rb b/lib/pact_broker/api/decorators/dashboard_decorator.rb new file mode 100644 index 000000000..d8bf4427c --- /dev/null +++ b/lib/pact_broker/api/decorators/dashboard_decorator.rb @@ -0,0 +1,131 @@ +require 'ostruct' +require 'pact_broker/api/pact_broker_urls' + +module PactBroker + module Api + module Decorators + class DashboardDecorator + include PactBroker::Api::PactBrokerUrls + + def initialize(index_items) + @index_items = index_items + end + + def to_json(options) + to_hash(options).to_json + end + + def to_hash(options) + { + items: items(index_items, options[:user_options][:base_url]) + } + end + + private + + attr_reader :index_items + + def items(index_items, base_url) + index_items.collect do | index_item | + index_item_hash(index_item.consumer, index_item.provider, index_item.consumer_version, index_item, base_url) + end + end + + def index_item_hash(consumer, provider, consumer_version, index_item, base_url) + { + consumer: consumer_hash(index_item, consumer, consumer_version, base_url), + provider: provider_hash(index_item, provider, base_url), + pact: pact_hash(index_item, base_url), + latestVerificationResult: verification_hash(index_item, base_url), + verificationStatus: index_item.verification_status.to_s, + webhookStatus: index_item.webhook_status.to_s, + latestWebhookExecution: latest_webhook_execution(index_item, base_url), + _links: links(index_item, base_url) + + } + end + + def consumer_hash(index_item, consumer, consumer_version, base_url) + { + name: index_item.consumer_name, + version: { + number: index_item.consumer_version_number, + _links: { + self: { + href: version_url(base_url, index_item.consumer_version) + } + } + }, + _links: { + self: { + href: pacticipant_url(base_url, index_item.consumer) + } + } + } + end + + def provider_hash(index_item, provider, base_url) + hash = { + name: index_item.provider_name, + version: nil, + _links: { + self: { + href: pacticipant_url(base_url, index_item.provider) + } + } + } + + if index_item.latest_verification + hash[:version] = { number: index_item.provider_version_number } + end + + hash + end + + def pact_hash(index_item, base_url) + { + createdAt: index_item.latest_pact.created_at.to_datetime.xmlschema, + _links: { + self: { + href: pact_url(base_url, index_item.latest_pact) + } + } + } + end + + def verification_hash(index_item, base_url) + if index_item.latest_verification + { + success: index_item.latest_verification.success, + verifiedAt: index_item.latest_verification.created_at.to_datetime.xmlschema, + _links: { + self: { + href: verification_url(index_item.latest_verification, base_url) + } + } + } + else + nil + end + end + + def latest_webhook_execution(index_item, base_url) + if index_item.last_webhook_execution_date + { + triggeredAt: index_item.last_webhook_execution_date.to_datetime.xmlschema + } + end + end + + def links(index_item, base_url) + { + 'pb:webhook-status' => { + title: "Status of webhooks for #{index_item.consumer_name}/#{index_item.provider_name} pact", + href: webhooks_status_url(index_item.consumer, index_item.provider, base_url) + } + } + end + end + end + end +end diff --git a/lib/pact_broker/api/resources/dashboard.rb b/lib/pact_broker/api/resources/dashboard.rb index e4a54f25d..2267ae2d0 100644 --- a/lib/pact_broker/api/resources/dashboard.rb +++ b/lib/pact_broker/api/resources/dashboard.rb @@ -1,4 +1,5 @@ require 'pact_broker/api/resources/base_resource' +require 'pact_broker/api/decorators/dashboard_decorator' module PactBroker module Api @@ -15,7 +16,13 @@ def allowed_methods end def to_json + PactBroker::Api::Decorators::DashboardDecorator.new(index_items).to_json(user_options: decorator_context) + end + + private + def index_items + index_service.find_index_items end end end diff --git a/lib/pact_broker/domain/index_item.rb b/lib/pact_broker/domain/index_item.rb index 0d2220ac8..d63be0b51 100644 --- a/lib/pact_broker/domain/index_item.rb +++ b/lib/pact_broker/domain/index_item.rb @@ -5,7 +5,7 @@ module PactBroker module Domain class IndexItem - attr_reader :consumer, :provider, :latest_pact, :latest_verification, :webhooks + attr_reader :consumer, :provider, :latest_pact, :latest_verification, :webhooks, :triggered_webhooks def initialize consumer, provider, latest_pact = nil, latest = true, latest_verification = nil, webhooks = [], triggered_webhooks = [], tags = [] @consumer = consumer @@ -54,6 +54,10 @@ def consumer_version_number @latest_pact.consumer_version_number end + def consumer_version + @latest_pact.consumer_version + end + def provider_version_number @latest_verification ? @latest_verification.provider_version_number : nil end diff --git a/pact_broker.gemspec b/pact_broker.gemspec index f00f333cf..3e4760da9 100644 --- a/pact_broker.gemspec +++ b/pact_broker.gemspec @@ -40,6 +40,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'table_print', '~> 1.5' gem.add_development_dependency 'pact', '~>1.14' + gem.add_development_dependency 'rspec-pact-matchers', '~>0.1' gem.add_development_dependency 'bundler-audit', '~>0.4' gem.add_development_dependency 'sqlite3', '~>1.3' gem.add_development_dependency 'pry-byebug' diff --git a/spec/fixtures/dashboard.json b/spec/fixtures/dashboard.json new file mode 100644 index 000000000..fc23e09f8 --- /dev/null +++ b/spec/fixtures/dashboard.json @@ -0,0 +1,61 @@ +{ + "items": [ + { + "_links": { + "pb:webhook-status": { + "href": "webhooks_status_url", + "title": "Status of webhooks for Foo/Bar pact" + } + }, + "consumer": { + "_links": { + "self": { + "href": "consumer_url" + } + }, + "name": "Foo", + "version": { + "_links": { + "self": { + "href": "consumer_version_url" + } + }, + "number": "1" + } + }, + "latestVerificationResult": { + "_links": { + "self": { + "href": "verification_url" + } + }, + "success": true, + "verifiedAt": "2018-01-01T00:00:00+00:00" + }, + "latestWebhookExecution": { + "triggeredAt": "2018-01-01T00:00:00+00:00" + }, + "pact": { + "_links": { + "self": { + "href": "pact_url" + } + }, + "createdAt": "2018-01-01T00:00:00+00:00" + }, + "provider": { + "_links": { + "self": { + "href": "provider_url" + } + }, + "name": "Bar", + "version": { + "number": "2" + } + }, + "verificationStatus": "wiffle", + "webhookStatus": "blah" + } + ] +} \ No newline at end of file diff --git a/spec/lib/pact_broker/api/decorators/dashboard_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/dashboard_decorator_spec.rb new file mode 100644 index 000000000..4a4207f6a --- /dev/null +++ b/spec/lib/pact_broker/api/decorators/dashboard_decorator_spec.rb @@ -0,0 +1,73 @@ +require 'pact_broker/api/decorators/dashboard_decorator' + +module PactBroker + module Api + module Decorators + describe DashboardDecorator do + let(:index_item) do + instance_double('PactBroker::Domain::IndexItem', + consumer: consumer, + provider: provider, + consumer_name: consumer.name, + provider_name: provider.name, + latest_pact: pact, + latest_verification: verification, + provider_version: provider_version, + consumer_version: consumer_version, + last_webhook_execution_date: last_webhook_execution_date, + webhook_status: 'blah', + verification_status: 'wiffle', + provider_version_number: provider_version.number, + consumer_version_number: consumer_version.number + ) + end + let(:consumer) { instance_double('PactBroker::Domain::Pacticipant', name: 'Foo') } + let(:provider) { instance_double('PactBroker::Domain::Pacticipant', name: 'Bar') } + let(:pact) { instance_double('PactBroker::Domain::Pact', created_at: DateTime.new(2018)) } + let(:verification) { instance_double('PactBroker::Domain::Verification', success: true, created_at: DateTime.new(2018)) } + let(:consumer_version) { instance_double('PactBroker::Domain::Version', number: '1', pacticipant: consumer) } + let(:provider_version) { instance_double('PactBroker::Domain::Version', number: '2', pacticipant: provider) } + let(:last_webhook_execution_date) { Date.new(2018) } + let(:base_url) { 'http://example.org' } + let(:options) { { user_options: { base_url: base_url } } } + let(:dashboard_json) { DashboardDecorator.new([index_item]).to_json(options) } + + before do + allow_any_instance_of(DashboardDecorator).to receive(:pact_url).with(base_url, pact).and_return('pact_url') + allow_any_instance_of(DashboardDecorator).to receive(:verification_url).with(verification, base_url).and_return('verification_url') + allow_any_instance_of(DashboardDecorator).to receive(:pacticipant_url).with(base_url, consumer).and_return('consumer_url') + allow_any_instance_of(DashboardDecorator).to receive(:pacticipant_url).with(base_url, provider).and_return('provider_url') + allow_any_instance_of(DashboardDecorator).to receive(:version_url).with(base_url, consumer_version).and_return('consumer_version_url') + allow_any_instance_of(DashboardDecorator).to receive(:webhooks_status_url).with(consumer, provider, base_url).and_return('webhooks_status_url') + end + + let(:expected_hash) { JSON.parse(File.read('spec/fixtures/dashboard.json')) } + + subject { JSON.parse(dashboard_json) } + + it "creates some json" do + expect(subject).to match_pact(expected_hash, {allow_unexpected_keys: false}) + end + + context "when the pact has never been verified" do + let(:verification) { nil } + + it "has a null last verification and provider version" do + expected_hash['items'][0]['latestVerificationResult'] = nil + expected_hash['items'][0]['provider']['version'] = nil + expect(subject).to match_pact(expected_hash, {allow_unexpected_keys: false}) + end + end + + context "when no webhooks have been executed" do + let(:last_webhook_execution_date) { nil } + + it "has a null latestWebhookExecution" do + expected_hash['items'][0]['latestWebhookExecution'] = nil + expect(subject).to match_pact(expected_hash, {allow_unexpected_keys: false}) + end + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 956e1d9b7..a510046a0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -15,6 +15,7 @@ require 'rack/test' require 'pact_broker/api' require 'rspec/its' +require 'rspec/pact/matchers' require 'sucker_punch/testing/inline' require 'webmock/rspec'