1package identity
2
3import (
4 "context"
5 "log/slog"
6 "net/http"
7 "strings"
8 "sync"
9 "testing"
10 "time"
11
12 "github.com/bluesky-social/indigo/atproto/syntax"
13 "golang.org/x/time/rate"
14
15 "github.com/stretchr/testify/assert"
16)
17
18// NOTE: this hits the open internet! marked as skip below by default
19func testDirectoryLive(t *testing.T, d Directory) {
20 assert := assert.New(t)
21 ctx := context.Background()
22
23 handle := syntax.Handle("atproto.com")
24 did := syntax.DID("did:plc:ewvi7nxzyoun6zhxrhs64oiz")
25 pdsSuffix := "host.bsky.network"
26
27 resp, err := d.LookupHandle(ctx, handle)
28 assert.NoError(err)
29 assert.Equal(handle, resp.Handle)
30 assert.Equal(did, resp.DID)
31 assert.True(strings.HasSuffix(resp.PDSEndpoint(), pdsSuffix))
32 dh, err := resp.DeclaredHandle()
33 assert.NoError(err)
34 assert.Equal(handle, dh)
35 pk, err := resp.PublicKey()
36 assert.NoError(err)
37 assert.NotNil(pk)
38
39 resp, err = d.LookupDID(ctx, did)
40 assert.NoError(err)
41 assert.Equal(handle, resp.Handle)
42 assert.Equal(did, resp.DID)
43 assert.True(strings.HasSuffix(resp.PDSEndpoint(), pdsSuffix))
44
45 _, err = d.LookupHandle(ctx, syntax.Handle("fake-dummy-no-resolve.atproto.com"))
46 assert.ErrorIs(err, ErrHandleNotFound)
47
48 _, err = d.LookupDID(ctx, syntax.DID("did:web:fake-dummy-no-resolve.atproto.com"))
49 assert.ErrorIs(err, ErrDIDNotFound)
50
51 _, err = d.LookupDID(ctx, syntax.DID("did:plc:fake-dummy-no-resolve.atproto.com"))
52 assert.ErrorIs(err, ErrDIDNotFound)
53
54 _, err = d.LookupHandle(ctx, syntax.HandleInvalid)
55 assert.Error(err)
56}
57
58func TestBaseDirectory(t *testing.T) {
59 t.Skip("TODO: skipping live network test")
60 d := BaseDirectory{}
61 testDirectoryLive(t, &d)
62}
63
64func TestCacheDirectory(t *testing.T) {
65 t.Skip("TODO: skipping live network test")
66 inner := BaseDirectory{}
67 d := NewCacheDirectory(&inner, 1000, time.Hour*1, time.Hour*1, time.Hour*1)
68 for i := 0; i < 3; i = i + 1 {
69 testDirectoryLive(t, &d)
70 }
71}
72
73func TestCacheCoalesce(t *testing.T) {
74 t.Skip("TODO: skipping live network test")
75
76 assert := assert.New(t)
77 handle := syntax.Handle("atproto.com")
78 did := syntax.DID("did:plc:ewvi7nxzyoun6zhxrhs64oiz")
79
80 base := BaseDirectory{
81 PLCURL: "https://plc.directory",
82 HTTPClient: http.Client{
83 Timeout: time.Second * 15,
84 },
85 // Limit the number of requests we can make to the PLC to 1 per second
86 PLCLimiter: rate.NewLimiter(1, 1),
87 TryAuthoritativeDNS: true,
88 SkipDNSDomainSuffixes: []string{".bsky.social"},
89 }
90 dir := NewCacheDirectory(&base, 1000, time.Hour*1, time.Hour*1, time.Hour*1)
91 // All 60 routines launch at the same time, so they should all miss the cache initially
92 routines := 60
93 wg := sync.WaitGroup{}
94
95 // Cancel the context after 2 seconds, if we're coalescing correctly, we should only make 1 request
96 ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
97 defer cancel()
98 for i := 0; i < routines; i++ {
99 wg.Add(1)
100 go func() {
101 defer wg.Done()
102 ident, err := dir.LookupDID(ctx, did)
103 if err != nil {
104 slog.Error("Failed lookup", "error", err)
105 }
106 assert.NoError(err)
107 assert.Equal(handle, ident.Handle)
108
109 ident, err = dir.LookupHandle(ctx, handle)
110 if err != nil {
111 slog.Error("Failed lookup", "error", err)
112 }
113 assert.NoError(err)
114 assert.Equal(did, ident.DID)
115 }()
116 }
117 wg.Wait()
118}
119
120func TestFallbackDNS(t *testing.T) {
121 t.Skip("TODO: skipping live network test")
122
123 assert := assert.New(t)
124 ctx := context.Background()
125 handle := syntax.Handle("no-such-record.atproto.com")
126 dir := BaseDirectory{
127 FallbackDNSServers: []string{"1.1.1.1:53", "8.8.8.8:53"},
128 }
129
130 // valid DNS server
131 _, err := dir.LookupHandle(ctx, handle)
132 assert.Error(err)
133 assert.ErrorIs(err, ErrHandleNotFound)
134
135 // invalid DNS server syntax
136 dir.FallbackDNSServers = []string{"_"}
137 _, err = dir.LookupHandle(ctx, handle)
138 assert.Error(err)
139 assert.ErrorIs(err, ErrHandleResolutionFailed)
140}
141
142func TestResolveNSID(t *testing.T) {
143 t.Skip("TODO: skipping live network test")
144 assert := assert.New(t)
145 ctx := context.Background()
146
147 dir := BaseDirectory{}
148 // NOTE: this was a very short temporary NSID when rkey restriction was short
149 nsid := syntax.NSID("net.bnewbold.m")
150 did, err := dir.ResolveNSID(ctx, nsid)
151
152 assert.NoError(err)
153 assert.Equal(did, syntax.DID("did:plc:nhxcyu4ewwhl5pqil4dotqjo"))
154}