A library for handling DID identifiers used in Bluesky AT Protocol
1describe 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
179end