88 lines
2.7 KiB
Go
88 lines
2.7 KiB
Go
package api
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"sync"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// The whole point of the claim is that it is true exactly once. A progress report arrives
|
||
|
|
// every ten seconds for the length of an episode and its own guard — half watched — is
|
||
|
|
// true for every one of those after the halfway mark, so without this the check behind it
|
||
|
|
// ran two Emby lookups, a Sonarr catalogue read and a Postgres write a hundred times per
|
||
|
|
// episode and discarded all but the first.
|
||
|
|
func TestFollowCheckIsClaimedOnce(t *testing.T) {
|
||
|
|
var checks followChecks
|
||
|
|
if !checks.claim("user-1", "episode-1") {
|
||
|
|
t.Fatal("the first report should claim the check")
|
||
|
|
}
|
||
|
|
for range 10 {
|
||
|
|
if checks.claim("user-1", "episode-1") {
|
||
|
|
t.Fatal("a repeat report must not claim the check again")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Two people watching the same episode on two televisions are two viewers, and each of
|
||
|
|
// them has their own My Shows list.
|
||
|
|
func TestFollowCheckIsPerViewerAndPerEpisode(t *testing.T) {
|
||
|
|
var checks followChecks
|
||
|
|
checks.claim("user-1", "episode-1")
|
||
|
|
if !checks.claim("user-2", "episode-1") {
|
||
|
|
t.Fatal("another viewer must get their own check")
|
||
|
|
}
|
||
|
|
if !checks.claim("user-1", "episode-2") {
|
||
|
|
t.Fatal("the next episode must get its own check")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// An empty id is not a claim to record. The alternative is one entry that swallows the
|
||
|
|
// first real check for whichever viewer or episode arrives unnamed.
|
||
|
|
func TestFollowCheckRefusesEmptyIdentifiers(t *testing.T) {
|
||
|
|
var checks followChecks
|
||
|
|
if checks.claim("", "episode-1") || checks.claim("user-1", "") {
|
||
|
|
t.Fatal("an unnamed viewer or episode is not a check to claim")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The memory is bounded, and what falls out of it falls out oldest first. A gateway that
|
||
|
|
// has been up for a month must not be holding every episode the house has ever watched.
|
||
|
|
func TestFollowCheckForgetsTheOldestFirst(t *testing.T) {
|
||
|
|
var checks followChecks
|
||
|
|
for index := range consideredFollows + 1 {
|
||
|
|
checks.claim("user-1", fmt.Sprintf("episode-%d", index))
|
||
|
|
}
|
||
|
|
if !checks.claim("user-1", "episode-0") {
|
||
|
|
t.Fatal("the oldest entry should have been evicted")
|
||
|
|
}
|
||
|
|
if checks.claim("user-1", fmt.Sprintf("episode-%d", consideredFollows)) {
|
||
|
|
t.Fatal("the newest entry should still be held")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Checking and recording are one call because two would be a race, and the thing being
|
||
|
|
// protected is precisely a burst of concurrent reports for one playback.
|
||
|
|
func TestFollowCheckClaimsOnceUnderConcurrency(t *testing.T) {
|
||
|
|
var (
|
||
|
|
checks followChecks
|
||
|
|
claimed int
|
||
|
|
mu sync.Mutex
|
||
|
|
wg sync.WaitGroup
|
||
|
|
)
|
||
|
|
for range 32 {
|
||
|
|
wg.Add(1)
|
||
|
|
go func() {
|
||
|
|
defer wg.Done()
|
||
|
|
if checks.claim("user-1", "episode-1") {
|
||
|
|
mu.Lock()
|
||
|
|
claimed++
|
||
|
|
mu.Unlock()
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
wg.Wait()
|
||
|
|
if claimed != 1 {
|
||
|
|
t.Fatalf("claimed %d times, want exactly 1", claimed)
|
||
|
|
}
|
||
|
|
}
|