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