Monorepo for Tangled
tangled.org
1package oauth
2
3import (
4 "context"
5 "io"
6 "log/slog"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/bluesky-social/indigo/atproto/syntax"
14 "tangled.org/core/appview/config"
15 "tangled.org/core/consts"
16)
17
18type fakeAcl struct {
19 member bool
20 gotHost string
21 gotDid string
22}
23
24func (f *fakeAcl) InvalidateMembers(host string) {}
25
26func (f *fakeAcl) IsKnotMember(ctx context.Context, host, userDid string) bool {
27 f.gotHost = host
28 f.gotDid = userDid
29 return f.member
30}
31
32func TestAddToDefaultKnot_ShortCircuitsWhenAlreadyMember(t *testing.T) {
33 acl := &fakeAcl{member: true}
34 o := &OAuth{
35 Acl: acl,
36 Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
37 Config: &config.Config{
38 Core: config.CoreConfig{Dev: true},
39 Knot: config.KnotConfig{Default: consts.DefaultKnot},
40 },
41 }
42
43 o.addToDefaultKnot(syntax.DID("did:plc:akshay"))
44
45 if acl.gotDid != "did:plc:akshay" {
46 t.Fatalf("IsKnotMember did = %q, want did:plc:akshay", acl.gotDid)
47 }
48 if acl.gotHost != consts.DefaultKnot {
49 t.Fatalf("IsKnotMember host = %q, want %q", acl.gotHost, consts.DefaultKnot)
50 }
51}
52
53func TestAddMemberViaKnotAdmin_HonorsDeadline(t *testing.T) {
54 release := make(chan struct{})
55 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56 <-release
57 }))
58 defer srv.Close()
59 defer close(release)
60
61 o := &OAuth{Config: &config.Config{
62 Core: config.CoreConfig{Dev: true},
63 Knot: config.KnotConfig{AdminSecret: "hunter2"},
64 }}
65
66 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
67 defer cancel()
68
69 done := make(chan error, 1)
70 go func() {
71 done <- o.addMemberViaKnotAdmin(ctx, strings.TrimPrefix(srv.URL, "http://"), syntax.DID("did:plc:whelk"))
72 }()
73
74 select {
75 case err := <-done:
76 if err == nil {
77 t.Fatal("a hung knot must surface an error, got nil")
78 }
79 case <-time.After(5 * time.Second):
80 t.Fatal("addMemberViaKnotAdmin blocked past its deadline; the request has no timeout")
81 }
82}
83
84func TestOnboardActionFor(t *testing.T) {
85 cases := []struct {
86 name string
87 state defaultKnotState
88 want onboardAction
89 }{
90 {"native default knot with admin secret uses the admin api", defaultKnotState{native: true, adminSecretSet: true}, onboardViaAdminAPI},
91 {"native default knot without admin secret is blocked", defaultKnotState{native: true, adminSecretSet: false}, onboardBlockedMissingSecret},
92 {"legacy default knot with admin secret skips the legacy record", defaultKnotState{native: false, adminSecretSet: true}, onboardBlockedSecretSet},
93 {"legacy default knot without admin secret writes the legacy record", defaultKnotState{native: false, adminSecretSet: false}, onboardViaRecord},
94 }
95 for _, c := range cases {
96 t.Run(c.name, func(t *testing.T) {
97 if got := onboardActionFor(c.state); got != c.want {
98 t.Fatalf("onboardActionFor(%+v) = %d, want %d", c.state, got, c.want)
99 }
100 })
101 }
102}