🏡 index : quine-global/quintodrome.git

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())
	})
}