Files
2026-11-08 03:46:11 +00:00

191 lines
5.5 KiB
Go

package shared
import (
"strings"
"testing"
"git.entityseven.com/rilaxik/pllist/internal/domain"
)
func TestCallbackID(t *testing.T) {
tests := []struct {
name string
data string
action CallbackAction
want int64
wantErr bool
}{
{name: "valid", data: "v1:open_list:42", action: ActionListOpen, want: 42},
{name: "wrong action", data: "v1:rename_list:42", action: ActionListOpen, wantErr: true},
{name: "wrong version", data: "v2:open_list:42", action: ActionListOpen, wantErr: true},
{name: "unversioned", data: "open_list:42", action: ActionListOpen, wantErr: true},
{name: "not a number", data: "v1:open_list:abc", action: ActionListOpen, wantErr: true},
{name: "zero", data: "v1:open_list:0", action: ActionListOpen, wantErr: true},
{name: "negative", data: "v1:open_list:-1", action: ActionListOpen, wantErr: true},
{name: "missing id", data: "v1:open_list", action: ActionListOpen, wantErr: true},
{name: "extra arg", data: "v1:open_list:42:1", action: ActionListOpen, wantErr: true},
{name: "too long", data: "v1:open_list:" + strings.Repeat("1", MaxCallbackDataLength), action: ActionListOpen, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CallbackID(tt.data, tt.action)
if (err != nil) != tt.wantErr {
t.Fatalf("CallbackID() error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.want {
t.Fatalf("CallbackID() = %d, want %d", got, tt.want)
}
})
}
}
func TestListDataRoundTrip(t *testing.T) {
tests := []struct {
name string
data string
action CallbackAction
}{
{name: "open", data: ListData(42), action: ActionListOpen},
{name: "rename", data: RenameListData(42), action: ActionListRename},
{name: "delete", data: DeleteListData(42), action: ActionListDelete},
{name: "delete yes", data: DeleteListYesData(42), action: ActionListDeleteYes},
{name: "shares", data: SharesData(42), action: ActionShareManage},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if !strings.HasPrefix(tt.data, "v1:") {
t.Fatalf("callback data is not versioned: %q", tt.data)
}
id, err := CallbackID(tt.data, tt.action)
if err != nil {
t.Fatalf("CallbackID(%q) error = %v", tt.data, err)
}
if id != 42 {
t.Fatalf("CallbackID(%q) = %d, want 42", tt.data, id)
}
})
}
}
func TestPageCallbackRoundTrip(t *testing.T) {
data := PageData(42, 3)
listID, page, err := ParsePage(data)
if err != nil {
t.Fatalf("ParsePage(%q) error = %v", data, err)
}
if listID != 42 || page != 3 {
t.Fatalf("ParsePage(%q) = %d, %d", data, listID, page)
}
}
func TestPageCallbackRejectsBadData(t *testing.T) {
for _, data := range []string{
"v1:page",
"v1:page:42",
"v1:page:0:1",
"v1:page:42:0",
"v1:page:42:abc",
"v1:page:42:1:extra",
"page:42:1",
} {
if _, _, err := ParsePage(data); err == nil {
t.Fatalf("ParsePage(%q) accepted invalid data", data)
}
}
}
func TestShareCallbackRoundTrips(t *testing.T) {
for _, pending := range []bool{false, true} {
ref := ShareRef{ListID: 42, ShareID: 7, Pending: pending}
for _, tt := range []struct {
name string
data string
action CallbackAction
}{
{name: "view", data: ShareViewData(ref), action: ActionShareView},
{name: "revoke", data: ShareRevokeData(ref), action: ActionShareRevoke},
{name: "revoke yes", data: ShareRevokeYesData(ref), action: ActionShareRevokeYes},
} {
got, err := ParseShareRef(tt.data, tt.action)
if err != nil {
t.Fatalf("%s ParseShareRef(%q) error = %v", tt.name, tt.data, err)
}
if got != ref {
t.Fatalf("%s ParseShareRef(%q) = %+v, want %+v", tt.name, tt.data, got, ref)
}
}
got, role, err := ParseShareSet(ShareSetData(ref, domain.RoleReader))
if err != nil || role != domain.RoleReader || got != ref {
t.Fatalf("ParseShareSet() = %+v, %q, %v", got, role, err)
}
}
listID, role, err := ParseShareRole(ShareRoleData(42, domain.RoleEditor))
if err != nil || listID != 42 || role != domain.RoleEditor {
t.Fatalf("ParseShareRole() = %d, %q, %v", listID, role, err)
}
}
func TestShareCallbackRejectsBadData(t *testing.T) {
for _, data := range []string{
"v1:share_role:42",
"v1:share_role:0:editor",
"v1:share_role:42:owner",
"v1:share_view:42:u",
"v1:share_view:42:x:7",
"v1:share_view:42:u:0",
"v1:share_set:42:u:7",
"v1:share_set:42:u:7:owner",
"v1:share_revoke:42:u",
"share_view:42:u:7",
} {
if !rejectsShareData(data) {
t.Fatalf("accepted invalid callback data %q", data)
}
}
}
func rejectsShareData(data string) bool {
if _, _, err := ParseShareSet(data); err == nil {
return false
}
if _, err := ParseShareRef(data, ActionShareView); err == nil {
return false
}
if _, err := ParseShareRef(data, ActionShareRevoke); err == nil {
return false
}
if _, _, err := ParseShareRole(data); err == nil {
return false
}
return true
}
func TestCallbackBuildersFitTelegramLimit(t *testing.T) {
ref := ShareRef{ListID: 9223372036854775807, ShareID: 9223372036854775807}
data := []string{
NavLists, NavNew, NavHelp,
ListData(1 << 62),
RenameListData(1 << 62),
DeleteListData(1 << 62),
DeleteListYesData(1 << 62),
SharesData(1 << 62),
PageData(1<<62, 1<<30),
ShareAddData(1 << 62),
ShareRoleData(1<<62, domain.RoleEditor),
ShareViewData(ref),
ShareSetData(ref, domain.RoleEditor),
ShareRevokeData(ref),
ShareRevokeYesData(ref),
}
for _, item := range data {
if len(item) > MaxCallbackDataLength {
t.Fatalf("callback data %q is %d bytes, limit %d",
item, len(item), MaxCallbackDataLength)
}
}
}