mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-11 11:57:12 +02:00
190 lines
5.2 KiB
Go
190 lines
5.2 KiB
Go
|
|
package quickconnect
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
"testing/synctest"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/navidrome/navidrome/model"
|
||
|
|
. "github.com/onsi/ginkgo/v2"
|
||
|
|
. "github.com/onsi/gomega"
|
||
|
|
)
|
||
|
|
|
||
|
|
var device = Device{ID: "dev-1", Name: "Pixel 7", App: "Finamp", AppVersion: "1.0.0"}
|
||
|
|
|
||
|
|
var _ = Describe("QuickConnect", func() {
|
||
|
|
var qc QuickConnect
|
||
|
|
|
||
|
|
BeforeEach(func() {
|
||
|
|
qc = New()
|
||
|
|
})
|
||
|
|
|
||
|
|
Describe("Initiate", func() {
|
||
|
|
It("creates a pending request with a 6-digit code and a random secret", func() {
|
||
|
|
req, err := qc.Initiate(device)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(req.Code).To(MatchRegexp(`^[1-9]\d{5}$`))
|
||
|
|
Expect(req.Secret).To(MatchRegexp(`^[0-9a-f]{64}$`))
|
||
|
|
Expect(req.Device).To(Equal(device))
|
||
|
|
Expect(req.DateAdded).ToNot(BeZero())
|
||
|
|
Expect(req.Authorized()).To(BeFalse())
|
||
|
|
})
|
||
|
|
|
||
|
|
It("never gives two live requests the same code or secret", func() {
|
||
|
|
codes := map[string]bool{}
|
||
|
|
secrets := map[string]bool{}
|
||
|
|
for range 500 {
|
||
|
|
req, err := qc.Initiate(device)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
codes[req.Code] = true
|
||
|
|
secrets[req.Secret] = true
|
||
|
|
}
|
||
|
|
Expect(codes).To(HaveLen(500))
|
||
|
|
Expect(secrets).To(HaveLen(500))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("refuses new requests when too many are pending", func() {
|
||
|
|
for range maxPending {
|
||
|
|
_, err := qc.Initiate(device)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
}
|
||
|
|
_, err := qc.Initiate(device)
|
||
|
|
Expect(err).To(MatchError(ErrTooManyRequests))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
Describe("Status", func() {
|
||
|
|
It("returns the request for a known secret", func() {
|
||
|
|
req, _ := qc.Initiate(device)
|
||
|
|
got, err := qc.Status(req.Secret)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(got).To(Equal(req))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns ErrNotFound for an unknown secret", func() {
|
||
|
|
_, err := qc.Status("nope")
|
||
|
|
Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
Describe("Lookup", func() {
|
||
|
|
It("finds a request by code, ignoring spaces", func() {
|
||
|
|
req, _ := qc.Initiate(device)
|
||
|
|
got, err := qc.Lookup(req.Code[:3] + " " + req.Code[3:])
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(got).To(Equal(req))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns ErrNotFound for an unknown code", func() {
|
||
|
|
_, err := qc.Lookup("000000")
|
||
|
|
Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("refuses a request that is already authorized", func() {
|
||
|
|
req, _ := qc.Initiate(device)
|
||
|
|
_, _ = qc.Authorize(req.Code, "user-1")
|
||
|
|
_, err := qc.Lookup(req.Code)
|
||
|
|
Expect(err).To(MatchError(ErrAlreadyAuthorized))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
Describe("Authorize", func() {
|
||
|
|
It("marks the request as authorized by the user", func() {
|
||
|
|
req, _ := qc.Initiate(device)
|
||
|
|
got, err := qc.Authorize(" "+req.Code+" ", "user-1")
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(got.Authorized()).To(BeTrue())
|
||
|
|
Expect(got.UserID).To(Equal("user-1"))
|
||
|
|
|
||
|
|
status, _ := qc.Status(req.Secret)
|
||
|
|
Expect(status.Authorized()).To(BeTrue())
|
||
|
|
})
|
||
|
|
|
||
|
|
It("refuses a request that is already authorized", func() {
|
||
|
|
req, _ := qc.Initiate(device)
|
||
|
|
_, _ = qc.Authorize(req.Code, "user-1")
|
||
|
|
_, err := qc.Authorize(req.Code, "user-2")
|
||
|
|
Expect(err).To(MatchError(ErrAlreadyAuthorized))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns ErrNotFound for an unknown code", func() {
|
||
|
|
_, err := qc.Authorize("000000", "user-1")
|
||
|
|
Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
Describe("Redeem", func() {
|
||
|
|
It("returns the approving user once, then forgets the request", func() {
|
||
|
|
req, _ := qc.Initiate(device)
|
||
|
|
_, _ = qc.Authorize(req.Code, "user-1")
|
||
|
|
|
||
|
|
userID, err := qc.Redeem(req.Secret)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(userID).To(Equal("user-1"))
|
||
|
|
|
||
|
|
_, err = qc.Redeem(req.Secret)
|
||
|
|
Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
_, err = qc.Status(req.Secret)
|
||
|
|
Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
_, err = qc.Lookup(req.Code)
|
||
|
|
Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("refuses a request that is not authorized yet", func() {
|
||
|
|
req, _ := qc.Initiate(device)
|
||
|
|
_, err := qc.Redeem(req.Secret)
|
||
|
|
Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
_, err = qc.Status(req.Secret)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
})
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
// Plain tests: testing/synctest needs a *testing.T, which Ginkgo doesn't give.
|
||
|
|
func TestPendingRequestExpires(t *testing.T) {
|
||
|
|
synctest.Test(t, func(t *testing.T) {
|
||
|
|
g := NewWithT(t)
|
||
|
|
qc := New()
|
||
|
|
req, _ := qc.Initiate(device)
|
||
|
|
|
||
|
|
time.Sleep(timeout - time.Second)
|
||
|
|
_, err := qc.Status(req.Secret)
|
||
|
|
g.Expect(err).ToNot(HaveOccurred())
|
||
|
|
|
||
|
|
time.Sleep(2 * time.Second)
|
||
|
|
_, err = qc.Status(req.Secret)
|
||
|
|
g.Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
_, err = qc.Authorize(req.Code, "user-1")
|
||
|
|
g.Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAuthorizeExtendsExpiry(t *testing.T) {
|
||
|
|
synctest.Test(t, func(t *testing.T) {
|
||
|
|
g := NewWithT(t)
|
||
|
|
qc := New()
|
||
|
|
req, _ := qc.Initiate(device)
|
||
|
|
|
||
|
|
time.Sleep(timeout - time.Second)
|
||
|
|
_, err := qc.Authorize(req.Code, "user-1")
|
||
|
|
g.Expect(err).ToNot(HaveOccurred())
|
||
|
|
|
||
|
|
time.Sleep(timeout - time.Second)
|
||
|
|
userID, err := qc.Redeem(req.Secret)
|
||
|
|
g.Expect(err).ToNot(HaveOccurred())
|
||
|
|
g.Expect(userID).To(Equal("user-1"))
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestExpiredRequestsFreeCapacity(t *testing.T) {
|
||
|
|
synctest.Test(t, func(t *testing.T) {
|
||
|
|
g := NewWithT(t)
|
||
|
|
qc := New()
|
||
|
|
for range maxPending {
|
||
|
|
_, _ = qc.Initiate(device)
|
||
|
|
}
|
||
|
|
time.Sleep(timeout + time.Second)
|
||
|
|
_, err := qc.Initiate(device)
|
||
|
|
g.Expect(err).ToNot(HaveOccurred())
|
||
|
|
})
|
||
|
|
}
|