Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159package xrpc
import ( "context" "encoding/json" "io" "log/slog" "net/http" "net/http/httptest" "path/filepath" "testing" "time"
"github.com/bluesky-social/indigo/atproto/atcrypto" "github.com/bluesky-social/indigo/atproto/auth" "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/appview/config" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/xrpc/serviceauth")
const ( testActor = "did:plc:tester" testAudience = "did:web:test.example")
// newTestXrpc builds an Xrpc backed by a fresh temp DB, with service auth wired// to a mock directory holding testActor's key. It returns the router, the DB,// and a function that signs a service-auth token for a given lexicon method.func newTestXrpc(t *testing.T) (http.Handler, *db.DB, func(nsid string) string) { t.Helper()
d, err := db.Make(context.Background(), filepath.Join(t.TempDir(), "test.db")) if err != nil { t.Fatalf("db.Make: %v", err) } t.Cleanup(func() { d.Close() })
priv, err := atcrypto.GeneratePrivateKeyP256() if err != nil { t.Fatalf("generate key: %v", err) } pub, err := priv.PublicKey() if err != nil { t.Fatalf("derive pubkey: %v", err) }
dir := identity.NewMockDirectory() dir.Insert(identity.Identity{ DID: syntax.DID(testActor), Keys: map[string]identity.VerificationMethod{ "atproto": {Type: "Multikey", PublicKeyMultibase: pub.Multibase()}, }, })
logger := slog.New(slog.NewTextHandler(io.Discard, nil)) x := &Xrpc{ DB: d, Config: &config.Config{}, Logger: logger, ServiceAuth: serviceauth.NewServiceAuth(logger, dir, testAudience), }
sign := func(nsid string) string { lxm := syntax.NSID(nsid) token, err := auth.SignServiceAuth(syntax.DID(testActor), testAudience, time.Minute, &lxm, priv) if err != nil { t.Fatalf("sign service auth: %v", err) } return token }
return x.Router(), d, sign}
func TestHealth(t *testing.T) { router, _, _ := newTestXrpc(t)
rec := httptest.NewRecorder() router.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/_health", nil))
if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } var body map[string]string if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { t.Fatalf("decode: %v; body=%s", err, rec.Body.String()) } if body["version"] == "" { t.Fatalf("missing version in %s", rec.Body.String()) }}
func TestServiceAuthRequired(t *testing.T) { router, _, _ := newTestXrpc(t)
rec := httptest.NewRecorder() router.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/org.tangled.temp.notification.getUnreadCount", nil))
if rec.Code != http.StatusForbidden { t.Fatalf("status = %d, want 403 without a service-auth token", rec.Code) }}
func TestNotificationGetUnreadCount(t *testing.T) { router, d, sign := newTestXrpc(t)
nsid := "org.tangled.temp.notification.getUnreadCount" call := func() int { req := httptest.NewRequest(http.MethodGet, "/"+nsid, nil) req.Header.Set("Authorization", "Bearer "+sign(nsid)) rec := httptest.NewRecorder() router.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String()) } var out struct { Count int `json:"count"` } if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { t.Fatalf("decode: %v; body=%s", err, rec.Body.String()) } return out.Count }
if got := call(); got != 0 { t.Fatalf("empty db count = %d, want 0", got) }
if err := db.CreateNotification(d, &models.Notification{ RecipientDid: testActor, ActorDid: "did:plc:someone", Type: models.NotificationTypeRepoStarred, Read: false, }); err != nil { t.Fatalf("CreateNotification: %v", err) }
if got := call(); got != 1 { t.Fatalf("count after one unread = %d, want 1", got) }}
func TestWrongLexiconTokenRejected(t *testing.T) { router, _, sign := newTestXrpc(t)
// a token minted for a different method must not authorize this call req := httptest.NewRequest(http.MethodGet, "/org.tangled.temp.notification.getUnreadCount", nil) req.Header.Set("Authorization", "Bearer "+sign("org.tangled.temp.notification.listNotifications")) rec := httptest.NewRecorder() router.ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden { t.Fatalf("status = %d, want 403 for a token bound to a different method", rec.Code) }}