A library for handling DID identifiers used in Bluesky AT Protocol
1describe DIDKit::DID do
2 subject { described_class }
3
4 let(:plc_did) { 'did:plc:vc7f4oafdgxsihk4cry2xpze' }
5 let(:web_did) { 'did:web:taylorswift.com' }
6
7 describe '#initialize' do
8 context 'with a valid did:plc' do
9 it 'should return an initialized DID object' do
10 did = subject.new(plc_did)
11
12 did.should be_a(DIDKit::DID)
13 did.type.should == :plc
14 did.did.should be_a(String)
15 did.did.should == plc_did
16 did.resolved_by.should be_nil
17 end
18 end
19
20 context 'with a valid did:web' do
21 it 'should return an initialized DID object' do
22 did = subject.new(web_did)
23
24 did.should be_a(DIDKit::DID)
25 did.type.should == :web
26 did.did.should be_a(String)
27 did.did.should == web_did
28 did.resolved_by.should be_nil
29 end
30 end
31
32 context 'with another DID object' do
33 it 'should create a copy of the DID' do
34 other = subject.new(plc_did)
35 did = subject.new(other)
36
37 did.did.should == plc_did
38 did.type.should == :plc
39 did.equal?(other).should == false
40 end
41 end
42
43 context 'with a string that is not a DID' do
44 it 'should raise an error' do
45 expect {
46 subject.new('not-a-did')
47 }.to raise_error(DIDKit::DIDError)
48 end
49 end
50
51 context 'when an unrecognized did: type' do
52 it 'should raise an error' do
53 expect {
54 subject.new('did:example:123')
55 }.to raise_error(DIDKit::DIDError)
56 end
57 end
58 end
59
60 describe '#web_domain' do
61 context 'for a did:web' do
62 it 'should return the domain part' do
63 did = subject.new('did:web:site.example.com')
64
65 did.web_domain.should == 'site.example.com'
66 end
67 end
68
69 context 'for a did:plc' do
70 it 'should return nil' do
71 did = subject.new('did:plc:yk4dd2qkboz2yv6tpubpc6co')
72
73 did.web_domain.should be_nil
74 end
75 end
76 end
77
78 describe '#==' do
79 let(:did_string) { 'did:plc:vc7f4oafdgxsihk4cry2xpze' }
80 let(:other_string) { 'did:plc:oio4hkxaop4ao4wz2pp3f4cr' }
81
82 let(:did) { subject.new(did_string) }
83 let(:other) { subject.new(other_string) }
84
85 context 'given a DID string' do
86 it 'should compare its string value to the other DID' do
87 did.should == did_string
88 did.should_not == other_string
89 end
90 end
91
92 context 'given another DID object' do
93 it "should compare its string value to the other DID's string value" do
94 copy = subject.new(did_string)
95
96 did.should == copy
97 did.should_not == other
98 end
99 end
100
101 context 'given something that is not a DID' do
102 it 'should return false' do
103 did.should_not == :didplc
104 did.should_not == [did_string]
105 end
106 end
107 end
108
109 describe '#to_s' do
110 it "should return the DID's string value" do
111 did = subject.new(plc_did)
112
113 did.to_s.should be_a(String)
114 did.to_s.should == plc_did
115 end
116 end
117
118 describe 'account status' do
119 let(:document) { stub(:pds_endpoint => 'https://pds.ruby.space') }
120 let(:did) { subject.new(plc_did) }
121
122 before do
123 did.stubs(:document).returns(document)
124
125 stub_request(:get, 'https://pds.ruby.space/xrpc/com.atproto.sync.getRepoStatus')
126 .with(query: { did: plc_did })
127 .to_return(http_response) if defined?(http_response)
128 end
129
130 context 'when repo is active' do
131 let(:http_response) {
132 { body: { active: true }.to_json, headers: { 'Content-Type' => 'application/json' }}
133 }
134
135 it 'should report active account state' do
136 did.account_status.should == :active
137 did.account_active?.should == true
138 did.account_exists?.should == true
139 end
140 end
141
142 context 'when repo is inactive' do
143 let(:http_response) {
144 { body: { active: false, status: 'takendown' }.to_json, headers: { 'Content-Type' => 'application/json' }}
145 }
146
147 it 'should report an inactive existing account' do
148 did.account_status.should == :takendown
149 did.account_active?.should == false
150 did.account_exists?.should == true
151 end
152 end
153
154 context 'when repo is not found' do
155 let(:http_response) {
156 { status: 400, body: { error: 'RepoNotFound' }.to_json, headers: { 'Content-Type' => 'application/json' }}
157 }
158
159 it 'should return nil status and report the account as missing' do
160 did.account_status.should be_nil
161 did.account_active?.should == false
162 did.account_exists?.should == false
163 end
164 end
165
166 context 'when the document has no pds endpoint' do
167 before do
168 did.stubs(:document).returns(stub(:pds_endpoint => nil))
169 end
170
171 it 'should return nil status and report the account as missing' do
172 did.account_status.should be_nil
173 did.account_active?.should == false
174 did.account_exists?.should == false
175 end
176 end
177
178 context 'when active field is not set' do
179 let(:http_response) {
180 { body: { active: nil, status: 'unknown' }.to_json, headers: { 'Content-Type' => 'application/json' }}
181 }
182
183 it 'should raise APIError' do
184 expect { did.account_status }.to raise_error(DIDKit::APIError)
185 expect { did.account_active? }.to raise_error(DIDKit::APIError)
186 expect { did.account_exists? }.to raise_error(DIDKit::APIError)
187 end
188 end
189
190 context 'when active is false but status is not set' do
191 let(:http_response) {
192 { body: { active: false, status: nil }.to_json, headers: { 'Content-Type' => 'application/json' }}
193 }
194
195 it 'should raise APIError' do
196 expect { did.account_status }.to raise_error(DIDKit::APIError)
197 expect { did.account_active? }.to raise_error(DIDKit::APIError)
198 expect { did.account_exists? }.to raise_error(DIDKit::APIError)
199 end
200 end
201
202 context 'when an error different than RepoNotFound is returned' do
203 let(:http_response) {
204 { status: 400, body: { error: 'UserIsJerry' }.to_json, headers: { 'Content-Type' => 'application/json' }}
205 }
206
207 it 'should raise APIError' do
208 expect { did.account_status }.to raise_error(DIDKit::APIError)
209 expect { did.account_active? }.to raise_error(DIDKit::APIError)
210 expect { did.account_exists? }.to raise_error(DIDKit::APIError)
211 end
212 end
213
214 context 'when the response is not application/json' do
215 let(:http_response) {
216 { status: 400, body: 'error', headers: { 'Content-Type' => 'text/html' }}
217 }
218
219 it 'should raise APIError' do
220 expect { did.account_status }.to raise_error(DIDKit::APIError)
221 expect { did.account_active? }.to raise_error(DIDKit::APIError)
222 expect { did.account_exists? }.to raise_error(DIDKit::APIError)
223 end
224 end
225
226 context 'when the response is not 200 or 400' do
227 let(:http_response) {
228 { status: 500, body: { error: 'RepoNotFound' }.to_json, headers: { 'Content-Type' => 'application/json' }}
229 }
230
231 it 'should raise APIError' do
232 expect { did.account_status }.to raise_error(DIDKit::APIError)
233 expect { did.account_active? }.to raise_error(DIDKit::APIError)
234 expect { did.account_exists? }.to raise_error(DIDKit::APIError)
235 end
236 end
237 end
238end