103 lines
3.5 KiB
Go
103 lines
3.5 KiB
Go
package store
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// The filter predicate is the one piece of this feature worth pinning without a database:
|
||
|
|
// it is built by string concatenation with hand-numbered placeholders, and the failure it
|
||
|
|
// can produce is not a compile error or a crash but a *wrong answer* — a page of somebody
|
||
|
|
// else's sign-ins, or a total that does not match the rows above it.
|
||
|
|
|
||
|
|
func TestLoginWhereIsEmptyForAnEmptyFilter(t *testing.T) {
|
||
|
|
where, args := loginWhere(LoginFilter{})
|
||
|
|
if where != "TRUE" {
|
||
|
|
t.Fatalf("an unfiltered history should match everything, got %q", where)
|
||
|
|
}
|
||
|
|
if len(args) != 0 {
|
||
|
|
t.Fatalf("no filter means no arguments, got %v", args)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoginWhereNumbersPlaceholdersInOrder(t *testing.T) {
|
||
|
|
// Every argument must be referenced exactly once, by its own position. A duplicated or
|
||
|
|
// skipped number is how one filter silently applies another's value.
|
||
|
|
where, args := loginWhere(LoginFilter{
|
||
|
|
EmbyUserID: "user-1",
|
||
|
|
DeviceID: "device-1",
|
||
|
|
IPAddress: "10.0.0.9",
|
||
|
|
From: time.Now().Add(-time.Hour),
|
||
|
|
To: time.Now(),
|
||
|
|
Method: LoginMethodPassword,
|
||
|
|
})
|
||
|
|
if len(args) != 6 {
|
||
|
|
t.Fatalf("expected six bound values, got %d (%v)", len(args), args)
|
||
|
|
}
|
||
|
|
for position := 1; position <= len(args); position++ {
|
||
|
|
placeholder := "$" + itoa(position)
|
||
|
|
if strings.Count(where, placeholder) != 1 {
|
||
|
|
t.Fatalf("placeholder %s appears %d times in %q",
|
||
|
|
placeholder, strings.Count(where, placeholder), where)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoginWhereQueryReusesOneArgumentAcrossThreeColumns(t *testing.T) {
|
||
|
|
// The free-text control searches three columns from a single bound value. If it ever
|
||
|
|
// bound three, every later placeholder would be numbered wrongly.
|
||
|
|
where, args := loginWhere(LoginFilter{Query: "lounge"})
|
||
|
|
if len(args) != 1 {
|
||
|
|
t.Fatalf("free text should bind one value, got %d", len(args))
|
||
|
|
}
|
||
|
|
if strings.Count(where, "$1") != 3 {
|
||
|
|
t.Fatalf("free text should search three columns, got %q", where)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoginWhereOutcomeBindsNothing(t *testing.T) {
|
||
|
|
// Outcome is a literal predicate rather than a bound value; if it ever consumed an
|
||
|
|
// argument slot without appending to args, the numbering after it would be wrong.
|
||
|
|
for _, outcome := range []string{"success", "failure"} {
|
||
|
|
where, args := loginWhere(LoginFilter{Outcome: outcome, DeviceID: "device-1"})
|
||
|
|
if len(args) != 1 {
|
||
|
|
t.Fatalf("%s: expected one bound value, got %d", outcome, len(args))
|
||
|
|
}
|
||
|
|
if !strings.Contains(where, "$1") {
|
||
|
|
t.Fatalf("%s: the device filter lost its placeholder: %q", outcome, where)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoginWhereIgnoresBlankAndWhitespaceFilters(t *testing.T) {
|
||
|
|
// The console sends empty controls as empty strings. A blank that reached the query
|
||
|
|
// would match no rows at all rather than every row, which reads as "this television
|
||
|
|
// has never connected".
|
||
|
|
where, args := loginWhere(LoginFilter{EmbyUserID: " ", DeviceID: "", IPAddress: "\t"})
|
||
|
|
if where != "TRUE" || len(args) != 0 {
|
||
|
|
t.Fatalf("blank filters should not narrow anything, got %q %v", where, args)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoginWhereUnknownOutcomeMatchesBoth(t *testing.T) {
|
||
|
|
// An outcome the server does not recognise must mean "any", not "none": the filter is
|
||
|
|
// a convenience, and a typo in a bookmarked URL should not empty the page.
|
||
|
|
where, _ := loginWhere(LoginFilter{Outcome: "maybe"})
|
||
|
|
if strings.Contains(where, "success") {
|
||
|
|
t.Fatalf("an unrecognised outcome should not filter, got %q", where)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func itoa(value int) string {
|
||
|
|
if value == 0 {
|
||
|
|
return "0"
|
||
|
|
}
|
||
|
|
digits := ""
|
||
|
|
for value > 0 {
|
||
|
|
digits = string(rune('0'+value%10)) + digits
|
||
|
|
value /= 10
|
||
|
|
}
|
||
|
|
return digits
|
||
|
|
}
|