From b8ba6f9556b5ec00ee8fb9d485d0d1712e2f6370 Mon Sep 17 00:00:00 2001 From: Kuba Suder Date: Wed, 17 Dec 2025 23:47:02 +0200 Subject: [PATCH] added some tests --- Gemfile | 2 + spec/dids/dholms.json | 26 ++++++ spec/dids/witchcraft.json | 26 ++++++ spec/resolver_spec.rb | 179 ++++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 18 ++-- 5 files changed, 246 insertions(+), 5 deletions(-) create mode 100644 spec/dids/dholms.json create mode 100644 spec/dids/witchcraft.json create mode 100644 spec/resolver_spec.rb diff --git a/Gemfile b/Gemfile index 57769e9..342af26 100644 --- a/Gemfile +++ b/Gemfile @@ -8,3 +8,5 @@ gemspec gem "rake", "~> 13.0" gem "rspec", "~> 3.0" gem 'irb' +gem 'mocha' +gem 'webmock' diff --git a/spec/dids/dholms.json b/spec/dids/dholms.json new file mode 100644 index 0000000..0dd9e1e --- /dev/null +++ b/spec/dids/dholms.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/suites/secp256k1-2019/v1" + ], + "id": "did:plc:yk4dd2qkboz2yv6tpubpc6co", + "alsoKnownAs": [ + "at://dholms.xyz" + ], + "verificationMethod": [ + { + "id": "did:plc:yk4dd2qkboz2yv6tpubpc6co#atproto", + "type": "Multikey", + "controller": "did:plc:yk4dd2qkboz2yv6tpubpc6co", + "publicKeyMultibase": "zQ3shsJcHdhfpKyF3U6rBQziDHsY1ikwCAsqGWhdC1tgaPQxq" + } + ], + "service": [ + { + "id": "#atproto_pds", + "type": "AtprotoPersonalDataServer", + "serviceEndpoint": "https://pds.dholms.xyz" + } + ] +} diff --git a/spec/dids/witchcraft.json b/spec/dids/witchcraft.json new file mode 100644 index 0000000..e4832e7 --- /dev/null +++ b/spec/dids/witchcraft.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/suites/secp256k1-2019/v1" + ], + "id": "did:web:witchcraft.systems", + "alsoKnownAs": [ + "at://witchcraft.systems" + ], + "verificationMethod": [ + { + "id": "did:web:witchcraft.systems#atproto", + "type": "Multikey", + "controller": "did:web:witchcraft.systems", + "publicKeyMultibase": "zQ3shqRWPzo6kSi1PDn1VXTVeaRiigsK3bxKLQ1gQ6UHqnVxW" + } + ], + "service": [ + { + "id": "#atproto_pds", + "type": "AtprotoPersonalDataServer", + "serviceEndpoint": "https://pds.witchcraft.systems" + } + ] +} diff --git a/spec/resolver_spec.rb b/spec/resolver_spec.rb new file mode 100644 index 0000000..8a0cc64 --- /dev/null +++ b/spec/resolver_spec.rb @@ -0,0 +1,179 @@ +describe DIDKit::Resolver do + let(:sample_did) { 'did:plc:qhfo22pezo44fa3243z2h4ny' } + + describe '#resolve_handle' do + context 'when handle resolves via HTTP' do + before do + Resolv::DNS.stubs(:open).returns([]) + end + + let(:handle) { 'barackobama.bsky.social' } + + it 'should return a matching DID' do + stub_request(:get, "https://#{handle}/.well-known/atproto-did") + .to_return(body: sample_did) + + result = subject.resolve_handle(handle) + + result.should_not be_nil + result.should be_a(DID) + result.to_s.should == sample_did + result.resolved_by.should == :http + end + + it 'should check DNS first' do + Resolv::DNS.expects(:open).returns([]) + stub_request(:get, "https://#{handle}/.well-known/atproto-did") + .to_return(body: sample_did) + + result = subject.resolve_handle(handle) + end + + context 'when HTTP returns invalid text' do + it 'should return nil' do + stub_request(:get, "https://#{handle}/.well-known/atproto-did") + .to_return(body: "Welcome to nginx!") + + result = subject.resolve_handle(handle) + result.should be_nil + end + end + + context 'when HTTP returns bad response' do + it 'should return nil' do + stub_request(:get, "https://#{handle}/.well-known/atproto-did") + .to_return(status: 400, body: sample_did) + + result = subject.resolve_handle(handle) + result.should be_nil + end + end + + context 'when HTTP throws an exception' do + it 'should catch it and return nil' do + stub_request(:get, "https://#{handle}/.well-known/atproto-did") + .to_raise(Errno::ETIMEDOUT) + + result = 0 + + expect { + result = subject.resolve_handle(handle) + }.to_not raise_error + + result.should be_nil + end + end + + context 'when HTTP response has a trailing newline' do + it 'should accept it' do + stub_request(:get, "https://#{handle}/.well-known/atproto-did") + .to_return(body: sample_did + "\n") + + result = subject.resolve_handle(handle) + + result.should_not be_nil + result.should be_a(DID) + result.to_s.should == sample_did + end + end + end + + context 'when handle has a leading @' do + let(:handle) { '@pfrazee.com' } + + before do + Resolv::DNS.stubs(:open).returns([]) + end + + it 'should also return a matching DID' do + stub_request(:get, "https://pfrazee.com/.well-known/atproto-did") + .to_return(body: sample_did) + + result = subject.resolve_handle(handle) + + result.should_not be_nil + result.should be_a(DID) + result.to_s.should == sample_did + result.resolved_by.should == :http + end + end + + context 'when handle has a reserved TLD' do + let(:handle) { 'example.test' } + + it 'should return nil' do + subject.resolve_handle(handle).should be_nil + end + end + + context 'when a DID string is passed' do + let(:handle) { BSKY_APP_DID } + + it 'should return that DID' do + result = subject.resolve_handle(handle) + + result.should be_a(DID) + result.to_s.should == BSKY_APP_DID + end + end + + context 'when a DID object is passed' do + let(:handle) { DID.new(BSKY_APP_DID) } + + it 'should return a new DID object with that DID' do + result = subject.resolve_handle(handle) + + result.should be_a(DID) + result.to_s.should == BSKY_APP_DID + result.equal?(handle).should == false + end + end + end + + describe '#resolve_did' do + context 'when passed a did:plc string' do + let(:did) { 'did:plc:yk4dd2qkboz2yv6tpubpc6co' } + + it 'should return a parsed DID document object' do + stub_request(:get, "https://plc.directory/#{did}") + .to_return(body: load_did_file('dholms.json'), headers: { 'Content-Type': 'application/did+ld+json; charset=utf-8' }) + + result = subject.resolve_did(did) + result.should be_a(DIDKit::Document) + result.handles.should == ['dholms.xyz'] + result.pds_endpoint.should == 'https://pds.dholms.xyz' + end + + it 'should require a valid content type' do + stub_request(:get, "https://plc.directory/#{did}") + .to_return(body: load_did_file('dholms.json'), headers: { 'Content-Type': 'text/plain' }) + + expect { subject.resolve_did(did) }.to raise_error(DIDKit::APIError) + end + end + + context 'when passed a did:web string' do + let(:did) { 'did:web:witchcraft.systems' } + + it 'should return a parsed DID document object' do + stub_request(:get, "https://witchcraft.systems/.well-known/did.json") + .to_return(body: load_did_file('witchcraft.json'), headers: { 'Content-Type': 'application/did+ld+json; charset=utf-8' }) + + result = subject.resolve_did(did) + result.should be_a(DIDKit::Document) + result.handles.should == ['witchcraft.systems'] + result.pds_endpoint.should == 'https://pds.witchcraft.systems' + end + + it 'should NOT require a valid content type' do + stub_request(:get, "https://witchcraft.systems/.well-known/did.json") + .to_return(body: load_did_file('witchcraft.json'), headers: { 'Content-Type': 'text/plain' }) + + result = subject.resolve_did(did) + result.should be_a(DIDKit::Document) + result.handles.should == ['witchcraft.systems'] + result.pds_endpoint.should == 'https://pds.witchcraft.systems' + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0dd747d..6b2ce9c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,15 +1,23 @@ # frozen_string_literal: true -require "didkit" +require 'didkit' +require 'webmock/rspec' RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" - # Disable RSpec exposing methods globally on `Module` and `main` - config.disable_monkey_patching! - config.expect_with :rspec do |c| - c.syntax = :expect + c.syntax = [:should, :expect] end + + config.mock_with :mocha +end + +BSKY_APP_DID = 'did:plc:z72i7hdynmk6r22z27h6tvur' + +WebMock.enable! + +def load_did_file(name) + File.read(File.join(__dir__, 'dids', name)) end -- 2.51.2