require 'spec_helper'

describe JSON::JWK do
  describe '#initialize' do
    let(:jwk) { JSON::JWK.new key }
    subject { jwk }

    shared_examples_for :jwk_with_kid do
      it { should be_instance_of JSON::JWK }
      describe 'kid' do
        subject { jwk[:kid] }
        it { should == jwk.thumbprint }
      end
    end

    shared_examples_for :jwk_without_kid do
      it { should be_instance_of JSON::JWK }
      describe 'kid' do
        subject { jwk[:kid] }
        it { should be_blank }
      end
    end

    context 'with OpenSSL::PKey::RSA' do
      let(:key) { public_key }
      it_behaves_like :jwk_with_kid
    end

    context 'with OpenSSL::PKey::EC' do
      let(:key) { public_key :ecdsa }
      it_behaves_like :jwk_with_kid
    end

    context 'with String' do
      let(:key) { 'secret' }
      it_behaves_like :jwk_with_kid
    end

    context 'with JSON::JWK' do
      let(:key) do
        JSON::JWK.new(
          k: 'secret',
          kty: :oct
        )
      end
      it_behaves_like :jwk_with_kid
    end

    context 'with Hash' do
      let(:key) do
        {
          k: 'secret',
          kty: :oct
        }
      end
      it_behaves_like :jwk_with_kid
    end

    context 'with nothing' do
      let(:jwk) { JSON::JWK.new }
      it_behaves_like :jwk_without_kid
    end
  end

  describe '#content_type' do
    let(:jwk) { JSON::JWK.new public_key }
    it do
      jwk.content_type.should == 'application/jwk+json'
    end
  end

  context 'when RSA public key given' do
    let(:jwk) { JSON::JWK.new public_key }
    it { jwk.keys.collect(&:to_sym).should include :kty, :e, :n }
    its(:kty) { jwk[:kty].should == :RSA }
    its(:e) { jwk[:e].should == UrlSafeBase64.encode64(public_key.e.to_s(2)) }
    its(:n) { jwk[:n].should == UrlSafeBase64.encode64(public_key.n.to_s(2)) }

    context 'when kid/use options given' do
      let(:jwk) { JSON::JWK.new public_key, kid: '12345', use: :sig }
      it { jwk.keys.collect(&:to_sym).should include :kid, :use }
      its(:kid) { jwk[:kid].should == '12345' }
      its(:use) { jwk[:use].should == :sig }
    end

    describe '#thumbprint' do
      context 'using default hash function' do
        subject { jwk.thumbprint }
        it { should == 'nuBTimkcSt_AuEsD8Yv3l8CoGV31bu_3gsRDGN1iVKA' }
      end

      context 'using SHA512 hash function' do
        subject { jwk.thumbprint :SHA512 }
        it { should == '6v7pXTnQLMiQgvJlPJUdhAUSuGLzgF8C1r3ABAMFet6bc53ea-Pq4ZGbGu3RoAFsNRT1-RhTzDqtqXuLU6NOtw' }
      end
    end

    describe '#to_key' do
      it { jwk.to_key.should be_instance_of OpenSSL::PKey::RSA }
    end
  end

  context 'when EC public key given' do
    let(:jwk) { JSON::JWK.new public_key(:ecdsa) }
    let(:expected_coordinates) do
      {
        256 => {
          x: 'saPyrO4Lh9kh2FxrF9y1QVmZznWnRRJwpr12UHqzrVY',
          y: 'MMz4W9zzqlrJhqr-JyrpvlnaIIyZQE6DfrgPkxMAw1M'
        },
        384 => {
          x: 'plzApyFnK7qzhg5XnIZbFj2hZoH2Vdl4-RFm7DnsNMG9tyqrpfq2RyjfKABbcFRt',
          y: 'ixBzffhk3fcbmeipGLkvQBNCzeNm6QL3hOUTH6IFBzOL0Y7HsGTopNTTspLjlivb'
        },
        512 => {
          x: 'AcMCD-a0a6rnE9TvC0mOqF_DGXRg5Y3iTb4eHNwTm2kD6iujx9M_f8d_FGHr0OhpqzEn4rYPYZouGsbIPEgL0q__',
          y: 'AULYEd8l-bV_BI289aezhSLZ1RDF2ltgDPEy9Y7YtqYa4cJcpiyzVDMpXWwBp6cjg6TXINkoVrVXZhN404ihu4I2'
        }
      }
    end

    [256, 384, 512].each do |digest_length|
      describe "EC#{digest_length}" do
        let(:jwk) { JSON::JWK.new public_key(:ecdsa, digest_length: digest_length) }
        it { jwk.keys.collect(&:to_sym).should include :kty, :crv, :x, :y }
        its(:kty) { jwk[:kty].should == :EC }
        its(:x) { jwk[:x].should == expected_coordinates[digest_length][:x] }
        its(:y) { jwk[:y].should == expected_coordinates[digest_length][:y] }
      end
    end

    describe 'unknown curve' do
      it do
        key = OpenSSL::PKey::EC.new('secp112r2').generate_key
        expect do
          JSON::JWK.new key
        end.to raise_error JSON::JWK::UnknownAlgorithm, 'Unknown EC Curve'
      end
    end

    describe '#thumbprint' do
      context 'using default hash function' do
        subject { jwk.thumbprint }
        it { should == '-egRpLjyZCqxBh4OOfd8JSvXwayHmNFAUNkbi8exfhc' }
      end

      context 'using SHA512 hash function' do
        subject { jwk.thumbprint :SHA512 }
        it { should == 'B_yXDZJ9doudaVCj5q5vqxshvVtW2IFnz_ypvRt5O60gemkDAhO78L6YMyTWH0ZRm15cO2_laTSaNO9yZQFsvQ' }
      end
    end

    describe '#to_key' do
      it { jwk.to_key.should be_instance_of OpenSSL::PKey::EC }
    end
  end

  context 'when shared secret given' do
    let(:jwk) { JSON::JWK.new 'secret' }
    its(:kty) { jwk[:kty].should == :oct }
    its(:x) { jwk[:k].should == 'secret' }

    describe '#thumbprint' do
      context 'using default hash function' do
        subject { jwk.thumbprint }
        it { should == 'XZPWsTEZFIerowAF9GHzBtq5CkAOcVvIBnkMu0IIQH0' }
      end

      context 'using SHA512 hash function' do
        subject { jwk.thumbprint :SHA512 }
        it { should == 'rK7EtcEe9Xr0kryR9lNnyOTRe7Vb_BglbTBtbcVG2LzvL26_PFaMCwOtiUiXWfCK-wV8vcxjmvbcvV4ZxDE0FQ' }
      end
    end

    describe '#to_key' do
      it { jwk.to_key.should be_instance_of String }
    end
  end

  describe 'unknown key type' do
    it do
      key = OpenSSL::PKey::DSA.generate 256
      expect do
        JSON::JWK.new key
      end.to raise_error JSON::JWK::UnknownAlgorithm, 'Unknown Key Type'
    end
  end
end