132 lines
5.6 KiB
Go
132 lines
5.6 KiB
Go
package domain
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestValidateLoginCodeMessageTemplate(t *testing.T) {
|
|
if err := ValidateLoginCodeMessageTemplate("Your code is {{code}}."); err != nil {
|
|
t.Fatalf("exactly one {{code}} should be valid, got %v", err)
|
|
}
|
|
if err := ValidateLoginCodeMessageTemplate("No placeholder here."); !errors.Is(err, ErrLoginCodeMessageTemplateMissingCode) {
|
|
t.Fatalf("zero occurrences should be rejected, got %v", err)
|
|
}
|
|
if err := ValidateLoginCodeMessageTemplate("{{code}} and again {{code}}."); !errors.Is(err, ErrLoginCodeMessageTemplateMissingCode) {
|
|
t.Fatalf("two occurrences should be rejected, got %v", err)
|
|
}
|
|
if err := ValidateLoginCodeMessageTemplate(""); !errors.Is(err, ErrLoginCodeMessageTemplateMissingCode) {
|
|
t.Fatalf("empty template should be rejected, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestResolveLoginCodeMessageTemplatePrecedence(t *testing.T) {
|
|
const panel = "panel override {{code}}"
|
|
const env = "env default {{code}}"
|
|
|
|
if got := ResolveLoginCodeMessageTemplate(panel, env); got != panel {
|
|
t.Fatalf("panel override should win, got %q", got)
|
|
}
|
|
if got := ResolveLoginCodeMessageTemplate("", env); got != env {
|
|
t.Fatalf("env default should win when panel unset, got %q", got)
|
|
}
|
|
if got := ResolveLoginCodeMessageTemplate(" ", env); got != env {
|
|
t.Fatalf("whitespace-only panel override should be treated as unset, got %q", got)
|
|
}
|
|
if got := ResolveLoginCodeMessageTemplate("", ""); got != DefaultLoginCodeMessageTemplate {
|
|
t.Fatalf("built-in default should be the final fallback, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestOfficialLoginCodeMessageDynamicEntityOffset(t *testing.T) {
|
|
SetOfficialSystemUserDisplayName("")
|
|
defer SetOfficialSystemUserDisplayName("")
|
|
|
|
// {{code}} is nowhere near a fixed prefix here -- it sits at the end of
|
|
// a sentence, after other text -- proving the entity offset is computed
|
|
// from where the placeholder actually landed, not assumed from a
|
|
// hardcoded "Login code: " prefix the way the old %s-based
|
|
// implementation did.
|
|
template := "Please do not share your one-time code, which is: {{code}} -- thanks!"
|
|
msg, err := OfficialLoginCodeMessage(7, template, "998877", 1700000000)
|
|
if err != nil {
|
|
t.Fatalf("OfficialLoginCodeMessage: %v", err)
|
|
}
|
|
wantBody := "Please do not share your one-time code, which is: 998877 -- thanks!"
|
|
if msg.Body != wantBody {
|
|
t.Fatalf("body = %q, want %q", msg.Body, wantBody)
|
|
}
|
|
if len(msg.Entities) != 1 {
|
|
t.Fatalf("expected exactly one entity, got %d: %+v", len(msg.Entities), msg.Entities)
|
|
}
|
|
entity := msg.Entities[0]
|
|
if entity.Type != MessageEntityBold {
|
|
t.Fatalf("expected bold entity, got %v", entity.Type)
|
|
}
|
|
wantOffset := automaticEntityUTF16Length("Please do not share your one-time code, which is: ")
|
|
if entity.Offset != wantOffset {
|
|
t.Fatalf("offset = %d, want %d", entity.Offset, wantOffset)
|
|
}
|
|
if entity.Length != automaticEntityUTF16Length("998877") {
|
|
t.Fatalf("length = %d, want %d", entity.Length, automaticEntityUTF16Length("998877"))
|
|
}
|
|
}
|
|
|
|
func TestOfficialLoginCodeMessageOffsetShiftsWithServerNameSubstitution(t *testing.T) {
|
|
SetOfficialSystemUserDisplayName("A Much Longer Custom Server Name")
|
|
defer SetOfficialSystemUserDisplayName("")
|
|
|
|
// {{server_name}} is substituted BEFORE {{code}}'s position is located,
|
|
// so a longer server name shifts the code's offset. If the offset math
|
|
// were still relying on a fixed/original position (e.g. computed
|
|
// against the raw un-substituted template), this would land on the
|
|
// wrong text.
|
|
template := "Server {{server_name}} says your code is {{code}}."
|
|
msg, err := OfficialLoginCodeMessage(7, template, "42", 1)
|
|
if err != nil {
|
|
t.Fatalf("OfficialLoginCodeMessage: %v", err)
|
|
}
|
|
wantBody := "Server A Much Longer Custom Server Name says your code is 42."
|
|
if msg.Body != wantBody {
|
|
t.Fatalf("body = %q, want %q", msg.Body, wantBody)
|
|
}
|
|
wantOffset := automaticEntityUTF16Length("Server A Much Longer Custom Server Name says your code is ")
|
|
if len(msg.Entities) != 1 || msg.Entities[0].Offset != wantOffset {
|
|
t.Fatalf("entities = %+v, want single bold entity at offset %d", msg.Entities, wantOffset)
|
|
}
|
|
}
|
|
|
|
func TestOfficialLoginCodeMessageFallsBackWhenTemplateInvalid(t *testing.T) {
|
|
SetOfficialSystemUserDisplayName("")
|
|
defer SetOfficialSystemUserDisplayName("")
|
|
|
|
// Defense in depth: a template that somehow reaches here without
|
|
// {{code}} (or with it more than once) must never ship a message
|
|
// silently missing the actual OTP -- it falls back to the compiled-in
|
|
// default instead.
|
|
for _, template := range []string{"", "no placeholder", "{{code}} twice {{code}}"} {
|
|
msg, err := OfficialLoginCodeMessage(7, template, "13579", 1)
|
|
if err != nil {
|
|
t.Fatalf("template %q: OfficialLoginCodeMessage: %v", template, err)
|
|
}
|
|
if !strings.Contains(msg.Body, "13579") {
|
|
t.Fatalf("template %q: fallback body missing code: %q", template, msg.Body)
|
|
}
|
|
if len(msg.Entities) != 1 || msg.Entities[0].Length != automaticEntityUTF16Length("13579") {
|
|
t.Fatalf("template %q: unexpected entities: %+v", template, msg.Entities)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOfficialLoginCodeMessageValidation(t *testing.T) {
|
|
if _, err := OfficialLoginCodeMessage(0, "{{code}}", "12345", 1); !errors.Is(err, ErrLoginCodeDeliveryInvalid) {
|
|
t.Fatalf("expected invalid user id to be rejected, got %v", err)
|
|
}
|
|
if _, err := OfficialLoginCodeMessage(7, "{{code}}", "", 1); !errors.Is(err, ErrLoginCodeDeliveryInvalid) {
|
|
t.Fatalf("expected empty code to be rejected, got %v", err)
|
|
}
|
|
if _, err := OfficialLoginCodeMessage(OfficialSystemUserID, "{{code}}", "12345", 1); !errors.Is(err, ErrLoginCodeDeliveryInvalid) {
|
|
t.Fatalf("expected system user id to be rejected, got %v", err)
|
|
}
|
|
}
|