(new): support Bot API 10.3
Golang lint / lint (push) Failing after 1m37s

(fix): finalize v2 contracts
(tests): cover v2 migration
(doc): prepare release guidance
This commit is contained in:
2026-09-08 23:21:38 +03:00
parent 24040fe164
commit d78526242b
88 changed files with 2321 additions and 918 deletions
+107 -19
View File
@@ -13,7 +13,10 @@ func TestInlineKeyboardWrapsRowsAndEncodesJSONPayloads(t *testing.T) {
AddCallbackButton("B", "cmd", 2).
AddCallbackButton("C", "cmd", 3)
markup := kb.Get()
markup, err := kb.Get()
if err != nil {
t.Fatalf("keyboard encoding error: %s", err)
}
if got := len(markup.InlineKeyboard); got != 2 {
t.Fatalf("unexpected row count: %d", got)
}
@@ -36,7 +39,11 @@ func TestInlineKeyboardBuilderPreservesConfiguredButtonFields(t *testing.T) {
SetURL("https://example.test"),
)
button := kb.Get().InlineKeyboard[0][0]
markup, err := kb.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
button := markup.InlineKeyboard[0][0]
if button.Style != ButtonStylePrimary {
t.Fatalf("unexpected style: %q", button.Style)
}
@@ -49,7 +56,11 @@ func TestInlineKeyboardButtonBuilderSetCallbackDataDefaultsToJSON(t *testing.T)
kb := NewInlineKeyboardBase64(1).
AddButton(NewInlineKeyboardButton("A").SetCallbackData("cmd", 1, "two"))
button := kb.Get().InlineKeyboard[0][0]
markup, err := kb.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
button := markup.InlineKeyboard[0][0]
if !strings.Contains(button.CallbackData, `"cmd":"cmd"`) {
t.Fatalf("expected JSON callback payload, got %q", button.CallbackData)
}
@@ -62,7 +73,12 @@ func TestInlineKeyboardButtonBuilderSetCallbackDataUsesConfiguredPayloadType(t *
SetCallbackData("cmd", 1, "two"),
)
got, _, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, false)
markup, err := kb.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
button := markup.InlineKeyboard[0][0]
got, _, err := decodePayload(BotPayloadJSON, button.CallbackData, false)
if err != nil {
t.Fatalf("decodePayload returned error: %v", err)
}
@@ -93,10 +109,16 @@ func TestInlineKeyboardButtonBuilderKeepsExactlyOneAction(t *testing.T) {
func TestInlineKeyboardGetReturnsIndependentMarkup(t *testing.T) {
keyboard := NewInlineKeyboardJSON(1).AddURLButton("Docs", "https://example.test")
first := keyboard.Get()
first, err := keyboard.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
first.InlineKeyboard[0][0].Text = "mutated"
second := keyboard.Get()
second, err := keyboard.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if got := second.InlineKeyboard[0][0].Text; got != "Docs" {
t.Fatalf("Get exposed builder state for mutation: got %q", got)
}
@@ -117,7 +139,11 @@ func TestDecodePayloadAcceptsBase64KeyboardPayloadWhenBotPrefersJSON(t *testing.
kb := NewInlineKeyboardBase64(1).
AddCallbackButton("A", "cmd", 1, "two")
got, _, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, false)
markup, err := kb.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
got, _, err := decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, false)
if err != nil {
t.Fatalf("decodePayload returned error: %v", err)
}
@@ -132,7 +158,11 @@ func TestDecodePayloadAcceptsJSONKeyboardPayloadWhenBotPrefersBase64(t *testing.
kb := NewInlineKeyboardJSON(1).
AddCallbackButton("A", "cmd", 1, "two")
got, _, err := decodePayload(BotPayloadBase64, kb.Get().InlineKeyboard[0][0].CallbackData, false)
markup, err := kb.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
got, _, err := decodePayload(BotPayloadBase64, markup.InlineKeyboard[0][0].CallbackData, false)
if err != nil {
t.Fatalf("decodePayload returned error: %v", err)
}
@@ -147,7 +177,11 @@ func TestDecodePayloadAcceptsCompactKeyboardPayloadWhenBotPrefersJSON(t *testing
kb := NewInlineKeyboardCompact(1).
AddCallbackButton("A", "cmd", 1, "two")
got, decodedType, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, false)
markup, err := kb.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
got, decodedType, err := decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, false)
if err != nil {
t.Fatalf("decodePayload returned error: %v", err)
}
@@ -165,7 +199,11 @@ func TestDecodePayloadAcceptsCompactBase64KeyboardPayloadWhenBotPrefersJSON(t *t
kb := NewInlineKeyboardCompactBase64(1).
AddCallbackButton("A", "cmd", 1, "two")
got, decodedType, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, false)
markup, err := kb.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
got, decodedType, err := decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, false)
if err != nil {
t.Fatalf("decodePayload returned error: %v", err)
}
@@ -237,7 +275,11 @@ func TestDecodePayloadStrictRejectsCompactMismatchedType(t *testing.T) {
kb := NewInlineKeyboardCompact(1).
AddCallbackButton("A", "cmd", 1)
_, _, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, true)
markup, err := kb.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
_, _, err = decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, true)
if !errors.Is(err, ErrPayloadTypeMismatch) {
t.Fatalf("expected ErrPayloadTypeMismatch, got %v", err)
}
@@ -247,7 +289,11 @@ func TestDecodePayloadStrictRejectsMismatchedType(t *testing.T) {
kb := NewInlineKeyboardBase64(1).
AddCallbackButton("A", "cmd", 1)
_, _, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, true)
markup, err := kb.Get()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
_, _, err = decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, true)
if !errors.Is(err, ErrPayloadTypeMismatch) {
t.Fatalf("expected ErrPayloadTypeMismatch, got %v", err)
}
@@ -265,12 +311,12 @@ func TestInlineKeyboardValidation(t *testing.T) {
}
keyboard := NewInlineKeyboardCompact(1).AddButton(button)
if _, err := keyboard.GetValidated(); !errors.Is(err, ErrCallbackDataLength) {
if _, err := keyboard.Get(); !errors.Is(err, ErrCallbackDataLength) {
t.Fatalf("expected validated keyboard to reject callback data, got %v", err)
}
}
func TestCallbackDataEncodeValidatedBoundaries(t *testing.T) {
func TestCallbackDataEncodeBoundaries(t *testing.T) {
tests := []struct {
name string
command string
@@ -283,7 +329,7 @@ func TestCallbackDataEncodeValidatedBoundaries(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
encoded, err := (CallbackData{Command: tt.command}).EncodeValidated(BotPayloadCompact)
encoded, err := (CallbackData{Command: tt.command}).Encode(BotPayloadCompact)
if !errors.Is(err, tt.wantErr) {
t.Fatalf("expected %v, got %v", tt.wantErr, err)
}
@@ -292,7 +338,7 @@ func TestCallbackDataEncodeValidatedBoundaries(t *testing.T) {
}
})
}
if _, err := (CallbackData{Command: "ok"}).EncodeValidated(BotPayloadType("unknown")); !errors.Is(err, ErrInvalidPayloadType) {
if _, err := (CallbackData{Command: "ok"}).Encode(BotPayloadType("unknown")); !errors.Is(err, ErrInvalidPayloadType) {
t.Fatalf("expected ErrInvalidPayloadType, got %v", err)
}
}
@@ -304,8 +350,50 @@ func TestInlineKeyboardLoweredMaxRowStillWraps(t *testing.T) {
SetMaxRow(1).
AddURLButton("C", "https://example.test/c")
markup := keyboard.Get()
if len(markup.InlineKeyboard) != 2 || len(markup.InlineKeyboard[0]) != 2 || len(markup.InlineKeyboard[1]) != 1 {
t.Fatalf("lowered maxRow stopped automatic wrapping: %#v", markup.InlineKeyboard)
markup, err := keyboard.Get()
if !errors.Is(err, ErrInlineKeyboardRowTooLong) || markup != nil {
t.Fatalf("expected strict validation failure for the existing oversized row: %v", err)
}
if len(keyboard.Lines) != 2 || len(keyboard.Lines[0]) != 2 || len(keyboard.Lines[1]) != 1 {
t.Fatalf("lowered maxRow stopped automatic wrapping: %#v", keyboard.Lines)
}
}
func TestInlineKeyboardUnlimitedRowsMustBeExplicit(t *testing.T) {
for _, maxRow := range []int{0, -1} {
keyboard := NewInlineKeyboardJSON(maxRow).AddURLButton("A", "https://example.test/a")
if markup, err := keyboard.Get(); !errors.Is(err, ErrInlineKeyboardMaxRow) || markup != nil {
t.Fatalf("expected invalid row limit %d, markup=%#v err=%v", maxRow, markup, err)
}
}
keyboard := NewInlineKeyboardJSON(1).
SetUnlimitedRows().
AddURLButton("A", "https://example.test/a").
AddURLButton("B", "https://example.test/b")
markup, err := keyboard.Get()
if err != nil {
t.Fatalf("explicit unlimited rows failed: %v", err)
}
if len(markup.InlineKeyboard) != 1 || len(markup.InlineKeyboard[0]) != 2 {
t.Fatalf("unexpected unlimited keyboard: %#v", markup.InlineKeyboard)
}
}
func TestInlineKeyboardDisabledButton(t *testing.T) {
button, err := NewInlineKeyboardButton("Unavailable").SetDisabled().Build()
if err != nil {
t.Fatalf("Build returned error: %v", err)
}
if button.Disabled == nil || button.URL != "" || button.CallbackData != "" {
t.Fatalf("unexpected disabled button: %#v", button)
}
button, err = NewInlineKeyboardButton("Open").SetDisabled().SetURL("https://example.test").Build()
if err != nil {
t.Fatalf("SetURL didn't replace disabled action: %v", err)
}
if button.Disabled != nil || button.URL == "" {
t.Fatalf("unexpected URL button: %#v", button)
}
}