From 1e5e17d9164f5badcfe1181f64716a9b95c6b8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Allavena?= Date: Tue, 23 May 2017 10:12:53 +1000 Subject: [PATCH] Adding Pact::Jwt object, to handle json web tokens in the pact definitions. Note, this adds a dependency on gem jwt --- lib/pact/jwt.rb | 54 +++++++++++++++++++++++++ lib/pact/matchers/matchers.rb | 10 +++++ lib/pact/reification.rb | 2 + lib/pact/support.rb | 1 + pact-support.gemspec | 1 + spec/lib/pact/matchers/matchers_spec.rb | 25 ++++++++++++ spec/lib/pact/reification_spec.rb | 9 +++++ 7 files changed, 102 insertions(+) create mode 100644 lib/pact/jwt.rb diff --git a/lib/pact/jwt.rb b/lib/pact/jwt.rb new file mode 100644 index 0000000..6fae49d --- /dev/null +++ b/lib/pact/jwt.rb @@ -0,0 +1,54 @@ +require 'pact/symbolize_keys' +module Pact + + # Specifies that the actual object should be considered a match if + # it includes the same keys, and the values of the keys are of the same class. + + class Jwt + include SymbolizeKeys + + attr_reader :contents, :key, :algo + + def initialize contents, key, algo + @contents = contents + @key = key + @algo = algo + end + + def to_hash + { + :json_class => self.class.name, + :contents => contents, + :algo => algo, + :key => key + } + end + + def as_json opts = {} + to_hash + end + + def to_json opts = {} + as_json.to_json opts + end + + def self.json_create hash + h = symbolize_keys(hash) + new(h[:contents],h[:key],h[:algo]) + end + + def eq other + self == other + end + + def == other + other.is_a?(Jwt) && other.contents == self.contents + end + + def generate + contents + end + end +end + + diff --git a/lib/pact/matchers/matchers.rb b/lib/pact/matchers/matchers.rb index 92fa6ff..f8c71f9 100644 --- a/lib/pact/matchers/matchers.rb +++ b/lib/pact/matchers/matchers.rb @@ -1,5 +1,6 @@ require 'pact/term' require 'pact/something_like' +require 'pact/jwt' require 'pact/shared/null_expectation' require 'pact/shared/key_not_found' require 'pact/matchers/unexpected_key' @@ -39,6 +40,7 @@ def calculate_diff expected, actual, opts = {} when Regexp then regexp_diff(expected, actual, options) when Pact::SomethingLike then calculate_diff(expected.contents, actual, options.merge(:type => true)) when Pact::ArrayLike then array_like_diff(expected, actual, options) + when Pact::Jwt then jwt_diff(expected,actual,options) else object_diff(expected, actual, options) end end @@ -61,6 +63,14 @@ def array_diff expected, actual, options end end + def jwt_diff expected, actual, options + if actual.is_a? String + diff(expected.contents, JWT.decode(actual,nil,nil)[0], options) + else + Difference.new expected, actual + end + end + def actual_array_diff expected, actual, options difference = [] diff_found = false diff --git a/lib/pact/reification.rb b/lib/pact/reification.rb index 33eab0c..c4ab303 100644 --- a/lib/pact/reification.rb +++ b/lib/pact/reification.rb @@ -9,6 +9,8 @@ def self.from_term(term) case term when Pact::Term, Regexp, Pact::SomethingLike, Pact::ArrayLike from_term(term.generate) + when Pact::Jwt + JWT.encode(from_term(term.generate),term.key,term.algo) when Hash term.inject({}) do |mem, (key,term)| mem[key] = from_term(term) diff --git a/lib/pact/support.rb b/lib/pact/support.rb index 7edf6d9..df486f0 100644 --- a/lib/pact/support.rb +++ b/lib/pact/support.rb @@ -6,6 +6,7 @@ require 'pact/helpers' require 'pact/configuration' require 'pact/reification' +require 'jwt' module Pact include Pact::Helpers diff --git a/pact-support.gemspec b/pact-support.gemspec index 665eebf..ca77b0a 100644 --- a/pact-support.gemspec +++ b/pact-support.gemspec @@ -27,6 +27,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'find_a_port', '~> 1.0.1' gem.add_runtime_dependency 'thor' gem.add_runtime_dependency 'awesome_print', '~> 1.1' + gem.add_runtime_dependency 'jwt' gem.add_development_dependency 'rake', '~> 10.0.3' gem.add_development_dependency 'webmock', '~> 2.0.0' diff --git a/spec/lib/pact/matchers/matchers_spec.rb b/spec/lib/pact/matchers/matchers_spec.rb index 5e18b89..c5c6f99 100644 --- a/spec/lib/pact/matchers/matchers_spec.rb +++ b/spec/lib/pact/matchers/matchers_spec.rb @@ -55,6 +55,31 @@ module Pact::Matchers end + describe 'matching with jwt' do + + context 'when the actual is a simple hash like the expected' do + let(:expected) { Pact::Jwt.new( { "a" => 1 },'JWT_KEY','HS256' ) } + let(:actual) { JWT.encode({ a: 1},'JWT_KEY', 'HS256') } + + it 'returns an empty diff' do + expect(diff(expected, actual)).to eq({}) + end + + end + + context 'when the actual is a Pact term like the expected' do + let(:expected) { Pact::Jwt.new( { "key" => Pact::Term.new(:matcher => /.*llo/, :generate => 'hello') }, 'JWT_KEY','HS256' ) } + let(:actual) { JWT.encode({ key: 'allo'},'JWT_KEY', 'HS256') } + + it 'returns an empty diff' do + expect(diff(expected, actual)).to eq({}) + end + + end + + end + + describe 'option {allow_unexpected_keys: false}' do context "when an unexpected key is found" do let(:expected) { {:a => 1} } diff --git a/spec/lib/pact/reification_spec.rb b/spec/lib/pact/reification_spec.rb index e43c39c..2ba48bb 100644 --- a/spec/lib/pact/reification_spec.rb +++ b/spec/lib/pact/reification_spec.rb @@ -9,6 +9,8 @@ module Pact britney: 'britney', nested: { foo: /bar/, baz: 'qux' }, my_term: Term.new(generate: 'wiffle', matcher: /^wif/), + jwt: Jwt.new({foo: 'john'}, 'JWT_KEY','HS256'), + nested_jwt: Jwt.new({foo: Term.new(generate: 'wiffle', matcher: /^wif/)}, 'JWT_KEY','HS256'), array: ['first', /second/] } end @@ -37,6 +39,13 @@ module Pact expect(subject[:array]).to eql ['first', 'second'] end + it "handles jwt" do + expect(subject[:jwt]).to eql 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJqb2huIn0.HWnd4KMrX6QyzQ78P93t-avWabIrURe6aX1M6Nh1Jn4' + end + + it "handles nested jwt" do + expect(subject[:nested_jwt]).to eql 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJ3aWZmbGUifQ.FFQQzR6BEuNWZnTlVYjo3zR-rs6Sl_p3lU2EDdTkpE8' + end end context "when reifying a Request" do