···11-defmodule Atex.Lexicon.ResolverTest do
22- use ExUnit.Case, async: true
33-44- import Mox
55-66- alias Atex.Lexicon.Resolver
77- alias Atex.Lexicon.Resolver.{MockDIDClient, MockDNSClient}
88- alias Atex.NSID
99-1010- setup :verify_on_exit!
1111-1212- # ---------------------------------------------------------------------------
1313- # NSID.authority_domain/1
1414- # ---------------------------------------------------------------------------
1515-1616- describe "NSID.authority_domain/1" do
1717- test "converts a standard 4-part NSID" do
1818- assert {:ok, "_lexicon.feed.bsky.app"} = NSID.authority_domain("app.bsky.feed.post")
1919- end
2020-2121- test "matches the spec example" do
2222- assert {:ok, "_lexicon.blogging.lab.dept.university.edu"} =
2323- NSID.authority_domain("edu.university.dept.lab.blogging.getBlogPost")
2424- end
2525-2626- test "handles a minimal 3-segment NSID" do
2727- assert {:ok, "_lexicon.example.com"} = NSID.authority_domain("com.example.record")
2828- end
2929-3030- test "handles NSIDs with numbers in segments" do
3131- assert {:ok, "_lexicon.v0.comet.sh"} = NSID.authority_domain("sh.comet.v0.feed")
3232- end
3333-3434- test "returns error for a plain string without dots" do
3535- assert {:error, :invalid_nsid} = NSID.authority_domain("invalid")
3636- end
3737-3838- test "returns error for an empty string" do
3939- assert {:error, :invalid_nsid} = NSID.authority_domain("")
4040- end
4141-4242- test "returns error for a string with invalid characters" do
4343- assert {:error, :invalid_nsid} = NSID.authority_domain("not.valid!")
4444- end
4545- end
4646-4747- # ---------------------------------------------------------------------------
4848- # Resolver.resolve/1 — invalid NSID (no network calls)
4949- # ---------------------------------------------------------------------------
5050-5151- describe "resolve/1 with invalid NSID" do
5252- test "returns :invalid_nsid for a plain string" do
5353- assert {:error, :invalid_nsid} = Resolver.resolve("notannsid")
5454- end
5555-5656- test "returns :invalid_nsid for an empty string" do
5757- assert {:error, :invalid_nsid} = Resolver.resolve("")
5858- end
5959-6060- test "returns :invalid_nsid for a string with illegal characters" do
6161- assert {:error, :invalid_nsid} = Resolver.resolve("not.valid!")
6262- end
6363- end
6464-6565- # ---------------------------------------------------------------------------
6666- # Resolver.resolve/1 — DNS failure
6767- # ---------------------------------------------------------------------------
6868-6969- describe "resolve/1 with DNS failure" do
7070- test "returns :dns_resolution_failed when DNS returns no TXT records" do
7171- expect(MockDNSClient, :lookup_txt, fn "_lexicon.feed.bsky.app" -> [] end)
7272-7373- assert {:error, :dns_resolution_failed} = Resolver.resolve("app.bsky.feed.post")
7474- end
7575-7676- test "returns :dns_resolution_failed when TXT record has wrong prefix" do
7777- expect(MockDNSClient, :lookup_txt, fn "_lexicon.feed.bsky.app" ->
7878- [[~c"notadid=something"]]
7979- end)
8080-8181- assert {:error, :dns_resolution_failed} = Resolver.resolve("app.bsky.feed.post")
8282- end
8383-8484- test "returns :dns_resolution_failed when TXT value is not a valid DID" do
8585- expect(MockDNSClient, :lookup_txt, fn "_lexicon.feed.bsky.app" ->
8686- [[~c"did=notadidatall"]]
8787- end)
8888-8989- assert {:error, :dns_resolution_failed} = Resolver.resolve("app.bsky.feed.post")
9090- end
9191- end
9292-9393- # ---------------------------------------------------------------------------
9494- # Resolver.resolve/1 — DID resolution failure
9595- # ---------------------------------------------------------------------------
9696-9797- describe "resolve/1 with DID resolution failure" do
9898- test "returns :did_resolution_failed when DID resolver errors" do
9999- expect(MockDNSClient, :lookup_txt, fn "_lexicon.feed.bsky.app" ->
100100- [[~c"did=did:plc:ewvi7nxzyoun6zhxrhs64oiz"]]
101101- end)
102102-103103- expect(MockDIDClient, :resolve, fn "did:plc:ewvi7nxzyoun6zhxrhs64oiz" ->
104104- {:error, :not_found}
105105- end)
106106-107107- assert {:error, :did_resolution_failed} = Resolver.resolve("app.bsky.feed.post")
108108- end
109109- end
110110-111111- # ---------------------------------------------------------------------------
112112- # Resolver.resolve/1 — no PDS endpoint
113113- # ---------------------------------------------------------------------------
114114-115115- @did "did:plc:ewvi7nxzyoun6zhxrhs64oiz"
116116- @pds "https://pds.example.com"
117117- @nsid "app.bsky.feed.post"
118118-119119- @did_doc_no_pds %Atex.DID.Document{
120120- "@context": ["https://www.w3.org/ns/did/v1"],
121121- id: @did
122122- }
123123-124124- @did_doc_with_pds %Atex.DID.Document{
125125- "@context": ["https://www.w3.org/ns/did/v1"],
126126- id: @did,
127127- service: [
128128- %Atex.DID.Document.Service{
129129- id: "#{@did}#atproto_pds",
130130- type: "AtprotoPersonalDataServer",
131131- service_endpoint: @pds
132132- }
133133- ]
134134- }
135135-136136- defp stub_dns_and_did(did_doc) do
137137- expect(MockDNSClient, :lookup_txt, fn "_lexicon.feed.bsky.app" ->
138138- [[~c"did=#{@did}"]]
139139- end)
140140-141141- expect(MockDIDClient, :resolve, fn @did -> {:ok, did_doc} end)
142142- end
143143-144144- describe "resolve/1 with no PDS endpoint" do
145145- test "returns :no_pds_endpoint when DID document has no PDS service" do
146146- stub_dns_and_did(@did_doc_no_pds)
147147-148148- assert {:error, :no_pds_endpoint} = Resolver.resolve(@nsid)
149149- end
150150- end
151151-152152- # ---------------------------------------------------------------------------
153153- # Resolver.resolve/1 — record fetch (Req.Test plug)
154154- # ---------------------------------------------------------------------------
155155-156156- @lexicon_value %{
157157- "lexicon" => 1,
158158- "id" => @nsid,
159159- "defs" => %{
160160- "main" => %{
161161- "type" => "record",
162162- "key" => "tid",
163163- "record" => %{"type" => "object", "properties" => %{}}
164164- }
165165- }
166166- }
167167-168168- defp stub_through_to_pds do
169169- expect(MockDNSClient, :lookup_txt, fn "_lexicon.feed.bsky.app" ->
170170- [[~c"did=#{@did}"]]
171171- end)
172172-173173- expect(MockDIDClient, :resolve, fn @did -> {:ok, @did_doc_with_pds} end)
174174- end
175175-176176- defp record_plug(record_resp) do
177177- fn conn ->
178178- case record_resp do
179179- :not_found ->
180180- Plug.Conn.send_resp(conn, 404, ~s({"error":"RecordNotFound"}))
181181-182182- :missing_value ->
183183- Req.Test.json(conn, %{"uri" => "at://#{@did}/com.atproto.lexicon.schema/#{@nsid}"})
184184-185185- value ->
186186- Req.Test.json(conn, %{
187187- "uri" => "at://#{@did}/com.atproto.lexicon.schema/#{@nsid}",
188188- "value" => value
189189- })
190190- end
191191- end
192192- end
193193-194194- describe "resolve/1 — record fetch (Req.Test plug)" do
195195- test "returns the lexicon value map on success" do
196196- stub_through_to_pds()
197197-198198- result = Resolver.resolve(@nsid, plug: record_plug(@lexicon_value))
199199-200200- assert {:ok, lexicon} = result
201201- assert lexicon["id"] == @nsid
202202- assert lexicon["lexicon"] == 1
203203- assert is_map(lexicon["defs"])
204204- end
205205-206206- test "returns :record_not_found when PDS returns 404" do
207207- stub_through_to_pds()
208208-209209- assert {:error, :record_not_found} =
210210- Resolver.resolve(@nsid, plug: record_plug(:not_found))
211211- end
212212-213213- test "returns :invalid_record when PDS response has no value key" do
214214- stub_through_to_pds()
215215-216216- assert {:error, :invalid_record} =
217217- Resolver.resolve(@nsid, plug: record_plug(:missing_value))
218218- end
219219- end
220220-end
+40
test/atex/nsid_test.exs
···11+defmodule Atex.NSIDTest do
22+ use ExUnit.Case, async: true
33+44+ alias Atex.NSID
55+66+ # ---------------------------------------------------------------------------
77+ # NSID.authority_domain/1
88+ # ---------------------------------------------------------------------------
99+1010+ describe "NSID.authority_domain/1" do
1111+ test "converts a standard 4-part NSID" do
1212+ assert {:ok, "_lexicon.feed.bsky.app"} = NSID.authority_domain("app.bsky.feed.post")
1313+ end
1414+1515+ test "matches the spec example" do
1616+ assert {:ok, "_lexicon.blogging.lab.dept.university.edu"} =
1717+ NSID.authority_domain("edu.university.dept.lab.blogging.getBlogPost")
1818+ end
1919+2020+ test "handles a minimal 3-segment NSID" do
2121+ assert {:ok, "_lexicon.example.com"} = NSID.authority_domain("com.example.record")
2222+ end
2323+2424+ test "handles NSIDs with numbers in segments" do
2525+ assert {:ok, "_lexicon.v0.comet.sh"} = NSID.authority_domain("sh.comet.v0.feed")
2626+ end
2727+2828+ test "returns error for a plain string without dots" do
2929+ assert {:error, :invalid_nsid} = NSID.authority_domain("invalid")
3030+ end
3131+3232+ test "returns error for an empty string" do
3333+ assert {:error, :invalid_nsid} = NSID.authority_domain("")
3434+ end
3535+3636+ test "returns error for a string with invalid characters" do
3737+ assert {:error, :invalid_nsid} = NSID.authority_domain("not.valid!")
3838+ end
3939+ end
4040+end