pact_broker 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +13 -5
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +5 -0
  4. data/README.md +2 -1
  5. data/Rakefile +1 -3
  6. data/db/migrations/07_increase_json_content_length.rb +6 -1
  7. data/db/migrations/08_create_latest_pact_view.rb +7 -5
  8. data/db/migrations/14_add_timestamps_to_pact_views.rb +28 -0
  9. data/lib/pact_broker/api.rb +0 -4
  10. data/lib/pact_broker/api/decorators/latest_pact_decorator.rb +4 -1
  11. data/lib/pact_broker/api/decorators/pact_decorator.rb +2 -2
  12. data/lib/pact_broker/api/decorators/pact_version_decorator.rb +3 -4
  13. data/lib/pact_broker/api/decorators/pact_versions_decorator.rb +4 -4
  14. data/lib/pact_broker/api/decorators/pacticipant_decorator.rb +3 -2
  15. data/lib/pact_broker/api/decorators/tag_decorator.rb +2 -2
  16. data/lib/pact_broker/api/decorators/timestamps.rb +24 -0
  17. data/lib/pact_broker/api/decorators/webhook_decorator.rb +2 -2
  18. data/lib/pact_broker/api/decorators/webhooks_decorator.rb +4 -4
  19. data/lib/pact_broker/api/renderers/html_pact_renderer.rb +16 -4
  20. data/lib/pact_broker/api/resources/base_resource.rb +0 -1
  21. data/lib/pact_broker/api/resources/group.rb +11 -6
  22. data/lib/pact_broker/api/resources/index.rb +5 -6
  23. data/lib/pact_broker/api/resources/latest_pact.rb +8 -5
  24. data/lib/pact_broker/api/resources/latest_pacts.rb +3 -3
  25. data/lib/pact_broker/api/resources/pact.rb +9 -5
  26. data/lib/pact_broker/api/resources/pact_webhooks.rb +9 -5
  27. data/lib/pact_broker/api/resources/pacticipant.rb +10 -5
  28. data/lib/pact_broker/api/resources/relationships.rb +3 -3
  29. data/lib/pact_broker/api/resources/tag.rb +8 -7
  30. data/lib/pact_broker/api/resources/webhook.rb +1 -1
  31. data/lib/pact_broker/api/resources/webhook_execution.rb +1 -1
  32. data/lib/pact_broker/app.rb +8 -57
  33. data/lib/pact_broker/logging.rb +1 -0
  34. data/lib/pact_broker/models/pact.rb +6 -8
  35. data/lib/pact_broker/repositories/pact.rb +29 -0
  36. data/lib/pact_broker/repositories/pact_content.rb +0 -0
  37. data/lib/pact_broker/repositories/pact_repository.rb +66 -21
  38. data/lib/pact_broker/repositories/tag_repository.rb +1 -1
  39. data/lib/pact_broker/repositories/version_repository.rb +2 -1
  40. data/lib/pact_broker/services/pact_service.rb +14 -16
  41. data/lib/pact_broker/ui.rb +2 -0
  42. data/lib/pact_broker/ui/app.rb +81 -0
  43. data/lib/pact_broker/ui/controllers/groups.rb +1 -1
  44. data/lib/pact_broker/version.rb +1 -1
  45. data/lib/rack/pact_broker/convert_file_extension_to_accept_header.rb +44 -0
  46. data/pact_broker.gemspec +1 -0
  47. data/public/javascripts/highlight.pack.js +1 -0
  48. data/public/stylesheets/github-json.css +127 -0
  49. data/public/stylesheets/pact.css +4 -0
  50. data/spec/fixtures/consumer-provider.json +21 -0
  51. data/spec/integration/app_spec.rb +165 -0
  52. data/spec/integration/endpoints/pact_put_spec.rb +43 -0
  53. data/spec/lib/pact_broker/api/decorators/latest_pact_decorator_spec.rb +42 -0
  54. data/spec/lib/pact_broker/api/decorators/pact_version_decorator_spec.rb +5 -0
  55. data/spec/lib/pact_broker/api/decorators/pacticipant_decorator_spec.rb +2 -1
  56. data/spec/lib/pact_broker/api/decorators/webhook_decorator_spec.rb +9 -2
  57. data/spec/lib/pact_broker/api/renderers/html_pact_renderer_spec.rb +14 -3
  58. data/spec/lib/pact_broker/repositories/pact_repository_spec.rb +88 -4
  59. data/spec/lib/pact_broker/repositories/tag_repository_spec.rb +2 -0
  60. data/spec/lib/pact_broker/repositories/version_repository_spec.rb +20 -2
  61. data/spec/lib/pact_broker/services/pacticipant_service_spec.rb +1 -1
  62. data/spec/service_consumers/pact_helper.rb +2 -17
  63. data/spec/spec_helper.rb +4 -22
  64. data/spec/support/database_cleaner.rb +18 -0
  65. data/spec/support/fixture_helpers.rb +10 -0
  66. data/spec/support/provider_state_builder.rb +4 -15
  67. metadata +82 -47
@@ -11,7 +11,7 @@ module PactBroker
11
11
 
12
12
  def find args
13
13
  PactBroker::Models::Tag
14
- .select(:tags__name, :tags__version_id)
14
+ .select(:tags__name, :tags__version_id, :tags__created_at, :tags__updated_at)
15
15
  .join(:versions, {id: :version_id})
16
16
  .join(:pacticipants, {pacticipants__id: :versions__pacticipant_id})
17
17
  .where(:tags__name => args.fetch(:tag_name))
@@ -19,7 +19,8 @@ module PactBroker
19
19
 
20
20
  def create args
21
21
  PactBroker.logger.info "Creating version #{args[:number]} for pacticipant_id=#{args[:pacticipant_id]}"
22
- PactBroker::Models::Version.new(number: args[:number], pacticipant_id: args[:pacticipant_id]).save
22
+ version = PactBroker::Models::Version.new(number: args[:number], pacticipant_id: args[:pacticipant_id]).save
23
+ PactBroker::Models::Version.find(id: version.id) # Need to reload with populated order
23
24
  end
24
25
 
25
26
  def find_by_pacticipant_id_and_number_or_create pacticipant_id, number
@@ -26,14 +26,13 @@ module PactBroker
26
26
  provider = pacticipant_repository.find_by_name_or_create params[:provider_name]
27
27
  consumer = pacticipant_repository.find_by_name_or_create params[:consumer_name]
28
28
  consumer_version = version_repository.find_by_pacticipant_id_and_number_or_create consumer.id, params[:consumer_version_number]
29
- pact = pact_repository.find_by_version_and_provider(consumer_version.id, provider.id)
29
+ existing_pact = pact_repository.find_by_version_and_provider(consumer_version.id, provider.id)
30
30
 
31
- if pact
32
- return update_pact params, pact
31
+ if existing_pact
32
+ update_pact params, existing_pact
33
33
  else
34
- return create_pact params, consumer_version, provider
34
+ create_pact params, consumer_version, provider
35
35
  end
36
-
37
36
  end
38
37
 
39
38
  def find_all_pacts_between consumer, options
@@ -63,24 +62,23 @@ module PactBroker
63
62
 
64
63
  private
65
64
 
66
- def update_pact params, pact
67
- previous_json_content = pact.json_content
68
- pact.update(json_content: params[:json_content])
69
- if previous_json_content != params[:json_content]
70
- webhook_service.execute_webhooks pact
65
+ def update_pact params, existing_pact
66
+ updated_pact = pact_repository.update existing_pact.id, params
67
+
68
+ if existing_pact.json_content != updated_pact.json_content
69
+ webhook_service.execute_webhooks updated_pact
71
70
  end
72
- return pact, false
71
+
72
+ updated_pact
73
73
  end
74
74
 
75
75
  def create_pact params, version, provider
76
76
  pact = pact_repository.create json_content: params[:json_content], version_id: version.id, provider_id: provider.id
77
- execute_webhooks pact
78
- return pact, true
77
+ trigger_webhooks pact
78
+ pact
79
79
  end
80
80
 
81
-
82
-
83
- def execute_webhooks pact
81
+ def trigger_webhooks pact
84
82
  if pact_has_changed_since_previous_version? pact
85
83
  webhook_service.execute_webhooks pact
86
84
  end
@@ -0,0 +1,2 @@
1
+ require 'pact_broker/ui/controllers/relationships'
2
+ require 'pact_broker/ui/controllers/groups'
@@ -0,0 +1,81 @@
1
+ require 'pact_broker/ui'
2
+ require 'pact_broker/doc/controllers/app'
3
+
4
+ module PactBroker
5
+ module UI
6
+ class App
7
+
8
+ def initialize
9
+ @app = ::Rack::Builder.new {
10
+
11
+ use HtmlFilter
12
+
13
+ map "/ui/relationships" do
14
+ run PactBroker::UI::Controllers::Relationships
15
+ end
16
+
17
+ map "/groups" do
18
+ run PactBroker::UI::Controllers::Groups
19
+ end
20
+
21
+ map "/doc" do
22
+ run PactBroker::Doc::Controllers::App
23
+ end
24
+
25
+ map "/" do
26
+ run RedirectRootToRelationships
27
+ end
28
+ }
29
+ end
30
+
31
+ def call env
32
+ @app.call(env)
33
+ end
34
+
35
+ class RedirectRootToRelationships
36
+
37
+ def self.call env
38
+ # A request for the root path in the browser (not the json index) should
39
+ # redirect to ui/relationships
40
+ if (env['PATH_INFO'].chomp("/") == "")
41
+ [303, {'Location' => 'ui/relationships'},[]]
42
+ else
43
+ [404, {},[]]
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ class HtmlFilter
50
+
51
+ def initialize app
52
+ @app = app
53
+ end
54
+
55
+ def call env
56
+ if accepts_html_and_not_json_or_csv env
57
+ @app.call(env)
58
+ else
59
+ [404, {},[]]
60
+ end
61
+ end
62
+
63
+ def accepts_html_and_not_json_or_csv env
64
+ accept = env['HTTP_ACCEPT'] || ''
65
+ accepts_html(accept) && !accepts_json_or_csv(accept)
66
+ end
67
+
68
+ def accepts_html(accept)
69
+ accept.include?("html")
70
+ end
71
+
72
+ def accepts_json_or_csv accept
73
+ accept.include?("json") || accept.include?("csv")
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
80
+
81
+ end
@@ -12,7 +12,7 @@ module PactBroker
12
12
  get ":name" do
13
13
  erb :'groups/show.html', {
14
14
  locals: {
15
- csv_path: "/groups/#{params[:name]}/csv",
15
+ csv_path: "/groups/#{params[:name]}.csv",
16
16
  pacticipant_name: params[:name]}
17
17
  }, {
18
18
  layout: 'layouts/main'
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = '1.2.0'
2
+ VERSION = '1.3.0'
3
3
  end
@@ -0,0 +1,44 @@
1
+ module Rack
2
+ module PactBroker
3
+
4
+ # If the HTML and the CSV group resources are both requested by the browser,
5
+ # Chrome gets confused by the content types, and when you click back, it tries to load the CSV
6
+ # instead of the HTML page. So we have to give the CSV resource a different URL (.csv)
7
+
8
+ class ConvertFileExtensionToAcceptHeader
9
+
10
+ EXTENSIONS = {".csv" => "text/csv"}
11
+ EXTENSION_REGEXP = /\.\w+$/
12
+
13
+ def initialize app
14
+ @app = app
15
+ end
16
+
17
+ def call env
18
+ file_extension = extension(env)
19
+ if convert_to_accept_header? file_extension
20
+ @app.call(set_accept_header_and_path_info(env, file_extension))
21
+ else
22
+ @app.call(env)
23
+ end
24
+ end
25
+
26
+ def convert_to_accept_header? file_extension
27
+ EXTENSIONS[file_extension]
28
+ end
29
+
30
+ def extension env
31
+ env["PATH_INFO"] =~ EXTENSION_REGEXP && $~[0]
32
+ end
33
+
34
+ def set_accept_header_and_path_info env, file_extension
35
+ env.merge(
36
+ "PATH_INFO" => env["PATH_INFO"].gsub(EXTENSION_REGEXP, ''),
37
+ "HTTP_ACCEPT" => EXTENSIONS[file_extension]
38
+ )
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
data/pact_broker.gemspec CHANGED
@@ -40,5 +40,6 @@ Gem::Specification.new do |gem|
40
40
  gem.add_development_dependency 'webmock'
41
41
  gem.add_development_dependency 'rspec', '~>3.0'
42
42
  gem.add_development_dependency 'rspec-its'
43
+ gem.add_development_dependency 'database_cleaner'
43
44
 
44
45
  end
@@ -0,0 +1 @@
1
+ var hljs=new function(){function j(v){return v.replace(/&/gm,"&amp;").replace(/</gm,"&lt;").replace(/>/gm,"&gt;")}function t(v){return v.nodeName.toLowerCase()}function h(w,x){var v=w&&w.exec(x);return v&&v.index==0}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^lang(uage)?-/,"")});return v.filter(function(x){return i(x)||/no(-?)highlight/.test(x)})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);if(!t(A).match(/br|hr|img|input/)){v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset<y[0].offset)?w:y}return y[0].event=="start"?w:y}function A(H){function G(I){return" "+I.nodeName+'="'+j(I.value)+'"'}F+="<"+t(H)+Array.prototype.map.call(H.attributes,G).join("")+">"}function E(G){F+="</"+t(G)+">"}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=j(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+j(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};var E=function(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})};if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b="\\b("+D.bK.split(" ").join("|")+")\\b"}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?("+F.b+")\\.?":F.b}).concat([D.tE,D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}}}x(y)}function c(T,L,J,R){function v(V,W){for(var U=0;U<W.c.length;U++){if(h(W.c[U].bR,V)){return W.c[U]}}}function z(V,U){if(h(V.eR,U)){return V}if(V.eW){return z(V.parent,U)}}function A(U,V){return !J&&h(V.iR,U)}function E(W,U){var V=M.cI?U[0].toLowerCase():U[0];return W.k.hasOwnProperty(V)&&W.k[V]}function w(aa,Y,X,W){var U=W?"":b.classPrefix,V='<span class="'+U,Z=X?"":"</span>";V+=aa+'">';return V+Y+Z}function N(){if(!I.k){return j(C)}var U="";var X=0;I.lR.lastIndex=0;var V=I.lR.exec(C);while(V){U+=j(C.substr(X,V.index-X));var W=E(I,V);if(W){H+=W[1];U+=w(W[0],j(V[0]))}else{U+=j(V[0])}X=I.lR.lastIndex;V=I.lR.exec(C)}return U+j(C.substr(X))}function F(){if(I.sL&&!f[I.sL]){return j(C)}var U=I.sL?c(I.sL,C,true,S):e(C);if(I.r>0){H+=U.r}if(I.subLanguageMode=="continuous"){S=U.top}return w(U.language,U.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(W,V){var U=W.cN?w(W.cN,"",true):"";if(W.rB){D+=U;C=""}else{if(W.eB){D+=j(V)+U;C=""}else{D+=U;C=V}}I=Object.create(W,{parent:{value:I}})}function G(U,Y){C+=U;if(Y===undefined){D+=Q();return 0}var W=v(Y,I);if(W){D+=Q();P(W,Y);return W.rB?0:Y.length}var X=z(I,Y);if(X){var V=I;if(!(V.rE||V.eE)){C+=Y}D+=Q();do{if(I.cN){D+="</span>"}H+=I.r;I=I.parent}while(I!=X.parent);if(V.eE){D+=j(Y)}C="";if(X.starts){P(X.starts,"")}return V.rE?0:Y.length}if(A(Y,I)){throw new Error('Illegal lexeme "'+Y+'" for mode "'+(I.cN||"<unnamed>")+'"')}C+=Y;return Y.length||1}var M=i(T);if(!M){throw new Error('Unknown language: "'+T+'"')}m(M);var I=R||M;var S;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D=w(K.cN,"",true)+D}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+="</span>"}}return{r:H,value:D,language:T,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:j(L)}}else{throw O}}}function e(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:j(y)};var w=v;x.forEach(function(z){if(!i(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function g(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g,"<br>")}return v}function p(A){var B=r(A);if(/no(-?)highlight/.test(B)){return}var y;if(b.useBR){y=document.createElementNS("http://www.w3.org/1999/xhtml","div");y.innerHTML=A.innerHTML.replace(/\n/g,"").replace(/<br[ \/]*>/g,"\n")}else{y=A}var z=y.textContent;var v=B?c(B,z,true):e(z);var x=u(y);if(x.length){var w=document.createElementNS("http://www.w3.org/1999/xhtml","div");w.innerHTML=v.value;v.value=q(x,u(w),z)}v.value=g(v.value);A.innerHTML=v.value;A.className+=" hljs "+(!B&&v.language||"");A.result={language:v.language,re:v.r};if(v.second_best){A.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function d(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function k(){return Object.keys(f)}function i(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=e;this.fixMarkup=g;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=d;this.listLanguages=k;this.getLanguage=i;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/};this.CLCM={cN:"comment",b:"//",e:"$",c:[this.PWM]};this.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[this.PWM]};this.HCM={cN:"comment",b:"#",e:"$",c:[this.PWM]};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.CSSNM={cN:"number",b:this.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0};this.RM={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("json",function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}});
@@ -0,0 +1,127 @@
1
+ /*
2
+
3
+ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
4
+
5
+ */
6
+
7
+ .hljs {
8
+ display: block;
9
+ overflow-x: auto;
10
+ padding: 0.5em;
11
+ color: #333;
12
+ background: #f8f8f8;
13
+ -webkit-text-size-adjust: none;
14
+ }
15
+
16
+ .hljs-comment,
17
+ .hljs-template_comment,
18
+ .diff .hljs-header,
19
+ .hljs-javadoc {
20
+ color: #998;
21
+ font-style: italic;
22
+ }
23
+
24
+ .hljs-keyword,
25
+ .css .rule .hljs-keyword,
26
+ .hljs-winutils,
27
+ .javascript .hljs-title,
28
+ .nginx .hljs-title,
29
+ .hljs-subst,
30
+ .hljs-request,
31
+ .hljs-status {
32
+ color: #333;
33
+ font-weight: bold;
34
+ }
35
+
36
+ .hljs-number,
37
+ .hljs-hexcolor,
38
+ .ruby .hljs-constant {
39
+ color: #008080;
40
+ }
41
+
42
+ .hljs-string,
43
+ .hljs-tag .hljs-value,
44
+ .hljs-phpdoc,
45
+ .hljs-dartdoc,
46
+ .tex .hljs-formula {
47
+ color: #d14;
48
+ }
49
+
50
+ .hljs-title,
51
+ .hljs-id,
52
+ .scss .hljs-preprocessor {
53
+ color: #900;
54
+ font-weight: bold;
55
+ }
56
+
57
+ .javascript .hljs-title,
58
+ .hljs-list .hljs-keyword,
59
+ .hljs-subst {
60
+ font-weight: normal;
61
+ }
62
+
63
+ .hljs-class .hljs-title,
64
+ .hljs-type,
65
+ .vhdl .hljs-literal,
66
+ .tex .hljs-command {
67
+ color: #458;
68
+ font-weight: bold;
69
+ }
70
+
71
+ .hljs-tag,
72
+ .hljs-tag .hljs-title,
73
+ .hljs-rules .hljs-property,
74
+ .django .hljs-tag .hljs-keyword {
75
+ color: #000080;
76
+ font-weight: normal;
77
+ }
78
+
79
+ .hljs-attribute,
80
+ .hljs-variable,
81
+ .lisp .hljs-body {
82
+ color: #000080;
83
+ }
84
+
85
+ .hljs-regexp {
86
+ color: #009926;
87
+ }
88
+
89
+ .hljs-symbol,
90
+ .ruby .hljs-symbol .hljs-string,
91
+ .lisp .hljs-keyword,
92
+ .clojure .hljs-keyword,
93
+ .scheme .hljs-keyword,
94
+ .tex .hljs-special,
95
+ .hljs-prompt {
96
+ color: #990073;
97
+ }
98
+
99
+ .hljs-built_in {
100
+ color: #0086b3;
101
+ }
102
+
103
+ .hljs-preprocessor,
104
+ .hljs-pragma,
105
+ .hljs-pi,
106
+ .hljs-doctype,
107
+ .hljs-shebang,
108
+ .hljs-cdata {
109
+ color: #999;
110
+ font-weight: bold;
111
+ }
112
+
113
+ .hljs-deletion {
114
+ background: #fdd;
115
+ }
116
+
117
+ .hljs-addition {
118
+ background: #dfd;
119
+ }
120
+
121
+ .diff .hljs-change {
122
+ background: #0086b3;
123
+ }
124
+
125
+ .hljs-chunk {
126
+ color: #aaa;
127
+ }
@@ -9,4 +9,8 @@ div.pact-metadata li {
9
9
 
10
10
  div.pact-metadata span.name {
11
11
  font-weight: bold ;
12
+ }
13
+
14
+ pre {
15
+ font-size: 1em
12
16
  }
@@ -0,0 +1,21 @@
1
+ {
2
+ "consumer": {
3
+ "name": "A Consumer"
4
+ },
5
+ "provider": {
6
+ "name": "A Provider"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "request": {
11
+ "description" : "a request for something",
12
+ "method": "get",
13
+ "path" : "/something"
14
+ },
15
+ "response": {
16
+ "status": 200,
17
+ "body" : "something"
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+ require 'pact_broker/app'
3
+ require 'db'
4
+
5
+ module PactBroker
6
+
7
+ describe App do
8
+
9
+ before do
10
+ ProviderStateBuilder.new.create_pact_with_hierarchy 'Some Consumer', '1.0', 'Some Provider'
11
+ end
12
+
13
+ let(:hal_browser_enabled) { true }
14
+ let(:group_url) { '/groups/Some%20Consumer' }
15
+
16
+ let(:app) do
17
+ app = PactBroker::App.new do | config |
18
+ config.auto_migrate_db = false
19
+ config.use_hal_browser = hal_browser_enabled
20
+ config.database_connection = ::DB::PACT_BROKER_DB
21
+ end
22
+
23
+ end
24
+
25
+ context "when Accept includes text/html" do
26
+ let(:env) { {'HTTP_ACCEPT' => 'text/html'} }
27
+
28
+ subject { get path, '', env; last_response }
29
+
30
+ describe "a request for root" do
31
+
32
+ let(:path) { '/' }
33
+
34
+ it "redirects to /ui/relationships" do
35
+ expect(subject.status).to eq 303
36
+ expect(subject.headers['Location']).to eq 'ui/relationships'
37
+ end
38
+
39
+ end
40
+
41
+ describe "a request for /ui/relationships" do
42
+ let(:path) { '/ui/relationships/' }
43
+
44
+ it "returns the relationships page" do
45
+ expect(subject.status).to eq 200
46
+ expect(subject.headers['Content-Type']).to include 'text/html'
47
+ expect(subject.body).to include 'Relationships'
48
+ end
49
+ end
50
+
51
+ describe "a request for /groups/:pacticipant_name" do
52
+ let(:path) { '/groups/Some%20Consumer' }
53
+
54
+ it "returns the group page" do
55
+ expect(subject.status).to eq 200
56
+ expect(subject.headers['Content-Type']).to include 'text/html'
57
+ expect(subject.body).to include 'Network graph'
58
+ expect(subject.body).to include 'Some Consumer'
59
+ end
60
+ end
61
+
62
+ describe "a request for /doc/:rel_name" do
63
+ let(:path) { '/doc/webhooks' }
64
+
65
+ it "returns the HAL docs for the given rel" do
66
+ expect(subject.status).to eq 200
67
+ expect(subject.headers['Content-Type']).to include 'text/html'
68
+ expect(subject.body).to include 'Webhooks'
69
+ end
70
+ end
71
+
72
+ describe "a request the group containing a pacticipant" do
73
+ let(:path) { group_url }
74
+
75
+ it "returns the HTML representation of the group" do
76
+ expect(subject.status).to eq 200
77
+ expect(subject.headers['Content-Type']).to include 'text/html'
78
+ expect(subject.body).to include 'Network graph'
79
+ end
80
+ end
81
+
82
+ describe "a request for a path that does not exist" do
83
+ let(:path) { '/foo' }
84
+
85
+ it "returns a 404" do
86
+ expect(subject.status).to eq 404
87
+ expect(subject.headers['Content-Type']).to include 'text/html'
88
+ end
89
+ end
90
+
91
+ context "when the HALBrowser is enabled" do
92
+ context "when application/hal+json is also specified as an Accept" do
93
+
94
+ let(:env) { {'HTTP_ACCEPT' => 'text/html;application/hal+json'} }
95
+ let(:path) { '/something' }
96
+
97
+ it "redirects to the HAL Browser" do
98
+ expect(subject.status).to eq 303
99
+ expect(subject.headers['Location']).to eq '/hal-browser/browser.html#/something'
100
+ end
101
+ end
102
+ end
103
+
104
+ context "when the HALBrowser is not enabled" do
105
+ context "when application/hal+json is also specified as an Accept" do
106
+
107
+ let(:hal_browser_enabled) { false }
108
+ let(:env) { {'HTTP_ACCEPT' => 'text/html;application/hal+json'} }
109
+ let(:path) { '/something' }
110
+
111
+ it "does not redirect to the HAL Browser" do
112
+ expect(subject.status).to_not eq 303
113
+ end
114
+ end
115
+ end
116
+
117
+ context "when a .csv extension is specified" do
118
+
119
+ let(:path) { '/groups/Some%20Consumer.csv' }
120
+
121
+ it "returns the CSV Content-Type" do
122
+ expect(subject.status).to eq 200
123
+ expect(subject.headers['Content-Type']).to eq "text/csv"
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+ context "when no Accept header is specified" do
130
+
131
+ let(:env) { {} }
132
+ subject { get path, '', env; last_response }
133
+
134
+ describe "a request for root" do
135
+ let(:path) { "/" }
136
+
137
+ it "returns an API response" do
138
+ expect(subject.status).to eq 200
139
+ expect(subject.headers['Content-Type']).to_not include 'html'
140
+ end
141
+ end
142
+
143
+ describe "a request the group containing a pacticipant" do
144
+ let(:path) { group_url }
145
+
146
+ it "returns the API representation of the group" do
147
+ expect(subject.status).to eq 200
148
+ expect(subject.headers['Content-Type']).to_not include 'html'
149
+ end
150
+ end
151
+
152
+ describe "a request for a stylesheet" do
153
+ let(:path) { '/stylesheets/pact.css' }
154
+
155
+ it "returns the stylesheet" do
156
+ expect(subject.status).to eq 200
157
+ expect(subject.headers['Content-Type']).to include 'text/css'
158
+ end
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+
165
+ end