···1+describe DIDKit::Resolver do
2+ let(:sample_did) { 'did:plc:qhfo22pezo44fa3243z2h4ny' }
3+4+ describe '#resolve_handle' do
5+ context 'when handle resolves via HTTP' do
6+ before do
7+ Resolv::DNS.stubs(:open).returns([])
8+ end
9+10+ let(:handle) { 'barackobama.bsky.social' }
11+12+ it 'should return a matching DID' do
13+ stub_request(:get, "https://#{handle}/.well-known/atproto-did")
14+ .to_return(body: sample_did)
15+16+ result = subject.resolve_handle(handle)
17+18+ result.should_not be_nil
19+ result.should be_a(DID)
20+ result.to_s.should == sample_did
21+ result.resolved_by.should == :http
22+ end
23+24+ it 'should check DNS first' do
25+ Resolv::DNS.expects(:open).returns([])
26+ stub_request(:get, "https://#{handle}/.well-known/atproto-did")
27+ .to_return(body: sample_did)
28+29+ result = subject.resolve_handle(handle)
30+ end
31+32+ context 'when HTTP returns invalid text' do
33+ it 'should return nil' do
34+ stub_request(:get, "https://#{handle}/.well-known/atproto-did")
35+ .to_return(body: "Welcome to nginx!")
36+37+ result = subject.resolve_handle(handle)
38+ result.should be_nil
39+ end
40+ end
41+42+ context 'when HTTP returns bad response' do
43+ it 'should return nil' do
44+ stub_request(:get, "https://#{handle}/.well-known/atproto-did")
45+ .to_return(status: 400, body: sample_did)
46+47+ result = subject.resolve_handle(handle)
48+ result.should be_nil
49+ end
50+ end
51+52+ context 'when HTTP throws an exception' do
53+ it 'should catch it and return nil' do
54+ stub_request(:get, "https://#{handle}/.well-known/atproto-did")
55+ .to_raise(Errno::ETIMEDOUT)
56+57+ result = 0
58+59+ expect {
60+ result = subject.resolve_handle(handle)
61+ }.to_not raise_error
62+63+ result.should be_nil
64+ end
65+ end
66+67+ context 'when HTTP response has a trailing newline' do
68+ it 'should accept it' do
69+ stub_request(:get, "https://#{handle}/.well-known/atproto-did")
70+ .to_return(body: sample_did + "\n")
71+72+ result = subject.resolve_handle(handle)
73+74+ result.should_not be_nil
75+ result.should be_a(DID)
76+ result.to_s.should == sample_did
77+ end
78+ end
79+ end
80+81+ context 'when handle has a leading @' do
82+ let(:handle) { '@pfrazee.com' }
83+84+ before do
85+ Resolv::DNS.stubs(:open).returns([])
86+ end
87+88+ it 'should also return a matching DID' do
89+ stub_request(:get, "https://pfrazee.com/.well-known/atproto-did")
90+ .to_return(body: sample_did)
91+92+ result = subject.resolve_handle(handle)
93+94+ result.should_not be_nil
95+ result.should be_a(DID)
96+ result.to_s.should == sample_did
97+ result.resolved_by.should == :http
98+ end
99+ end
100+101+ context 'when handle has a reserved TLD' do
102+ let(:handle) { 'example.test' }
103+104+ it 'should return nil' do
105+ subject.resolve_handle(handle).should be_nil
106+ end
107+ end
108+109+ context 'when a DID string is passed' do
110+ let(:handle) { BSKY_APP_DID }
111+112+ it 'should return that DID' do
113+ result = subject.resolve_handle(handle)
114+115+ result.should be_a(DID)
116+ result.to_s.should == BSKY_APP_DID
117+ end
118+ end
119+120+ context 'when a DID object is passed' do
121+ let(:handle) { DID.new(BSKY_APP_DID) }
122+123+ it 'should return a new DID object with that DID' do
124+ result = subject.resolve_handle(handle)
125+126+ result.should be_a(DID)
127+ result.to_s.should == BSKY_APP_DID
128+ result.equal?(handle).should == false
129+ end
130+ end
131+ end
132+133+ describe '#resolve_did' do
134+ context 'when passed a did:plc string' do
135+ let(:did) { 'did:plc:yk4dd2qkboz2yv6tpubpc6co' }
136+137+ it 'should return a parsed DID document object' do
138+ stub_request(:get, "https://plc.directory/#{did}")
139+ .to_return(body: load_did_file('dholms.json'), headers: { 'Content-Type': 'application/did+ld+json; charset=utf-8' })
140+141+ result = subject.resolve_did(did)
142+ result.should be_a(DIDKit::Document)
143+ result.handles.should == ['dholms.xyz']
144+ result.pds_endpoint.should == 'https://pds.dholms.xyz'
145+ end
146+147+ it 'should require a valid content type' do
148+ stub_request(:get, "https://plc.directory/#{did}")
149+ .to_return(body: load_did_file('dholms.json'), headers: { 'Content-Type': 'text/plain' })
150+151+ expect { subject.resolve_did(did) }.to raise_error(DIDKit::APIError)
152+ end
153+ end
154+155+ context 'when passed a did:web string' do
156+ let(:did) { 'did:web:witchcraft.systems' }
157+158+ it 'should return a parsed DID document object' do
159+ stub_request(:get, "https://witchcraft.systems/.well-known/did.json")
160+ .to_return(body: load_did_file('witchcraft.json'), headers: { 'Content-Type': 'application/did+ld+json; charset=utf-8' })
161+162+ result = subject.resolve_did(did)
163+ result.should be_a(DIDKit::Document)
164+ result.handles.should == ['witchcraft.systems']
165+ result.pds_endpoint.should == 'https://pds.witchcraft.systems'
166+ end
167+168+ it 'should NOT require a valid content type' do
169+ stub_request(:get, "https://witchcraft.systems/.well-known/did.json")
170+ .to_return(body: load_did_file('witchcraft.json'), headers: { 'Content-Type': 'text/plain' })
171+172+ result = subject.resolve_did(did)
173+ result.should be_a(DIDKit::Document)
174+ result.handles.should == ['witchcraft.systems']
175+ result.pds_endpoint.should == 'https://pds.witchcraft.systems'
176+ end
177+ end
178+ end
179+end
+13-5
spec/spec_helper.rb
···1# frozen_string_literal: true
23-require "didkit"
045RSpec.configure do |config|
6 # Enable flags like --only-failures and --next-failure
7 config.example_status_persistence_file_path = ".rspec_status"
89- # Disable RSpec exposing methods globally on `Module` and `main`
10- config.disable_monkey_patching!
11-12 config.expect_with :rspec do |c|
13- c.syntax = :expect
14 end
000000000015end
···1# frozen_string_literal: true
23+require 'didkit'
4+require 'webmock/rspec'
56RSpec.configure do |config|
7 # Enable flags like --only-failures and --next-failure
8 config.example_status_persistence_file_path = ".rspec_status"
900010 config.expect_with :rspec do |c|
11+ c.syntax = [:should, :expect]
12 end
13+14+ config.mock_with :mocha
15+end
16+17+BSKY_APP_DID = 'did:plc:z72i7hdynmk6r22z27h6tvur'
18+19+WebMock.enable!
20+21+def load_did_file(name)
22+ File.read(File.join(__dir__, 'dids', name))
23end