mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-05 22:43:59 +00:00
fecef22f48
Sealed-interface union variants whose Type/Source field is declared as bare prose (e.g. "Type of the result, must be article" or "Scope type, must be all_private_chats") were skipped by extractEnumValues because the existing patterns require curly-quoted values. The genapi emitter already extracted these values via discBareRE for marshal-side discriminator injection; lifting the same detection into the scraper populates Field.EnumValues so planUnifiedUnionEnums folds them into shared union-level enums automatically. Unions newly unified (10): BotCommandScope, MenuButton, InputMedia, InputPaidMedia, InputPollMedia, InputPollOptionMedia, InputProfilePhoto, InputStoryContent, InlineQueryResult, PassportElementError. InputMessageContent stays excluded — its variants dispatch structurally on field presence and have no Type/Source field, so planUnifiedUnionEnums correctly skips it. Constants added: 60 typed enum constants across the 10 unions; the corresponding variant struct fields are retyped from string to the shared enum. Internal call-site cleanups: 0 — no internal package referenced these discriminator values via magic strings. False positives the prose detector explicitly rejects: terminal prose-word continuations like "must be sent", "must be shown above", "must be specified", "must be paid", "must be active", "must be one of 3, 6, or 12", "must be between 5 and 100000", "must be a Pay button", "must be repainted". Guarded via terminal-position regex anchor + closed-list isProseWord filter. Determinism verified across two consecutive make regen-from-fixture runs. go test -race ./..., go vet ./..., staticcheck ./... all clean.
240 lines
8.1 KiB
Go
240 lines
8.1 KiB
Go
package api
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
json "github.com/goccy/go-json"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestUnifiedEnum_BotCommandScopeType_Constants confirms the prose-form
|
|
// discriminator detection promoted BotCommandScope's per-variant Type
|
|
// fields into one shared enum.
|
|
func TestUnifiedEnum_BotCommandScopeType_Constants(t *testing.T) {
|
|
require.IsType(t, BotCommandScopeType(""), BotCommandScopeTypeDefault)
|
|
|
|
got := []BotCommandScopeType{
|
|
BotCommandScopeTypeDefault,
|
|
BotCommandScopeTypeAllPrivateChats,
|
|
BotCommandScopeTypeAllGroupChats,
|
|
BotCommandScopeTypeAllChatAdministrators,
|
|
BotCommandScopeTypeChat,
|
|
BotCommandScopeTypeChatAdministrators,
|
|
BotCommandScopeTypeChatMember,
|
|
}
|
|
want := []string{
|
|
"default", "all_private_chats", "all_group_chats",
|
|
"all_chat_administrators", "chat", "chat_administrators", "chat_member",
|
|
}
|
|
require.Len(t, got, len(want))
|
|
for i, v := range got {
|
|
require.Equal(t, want[i], string(v))
|
|
}
|
|
}
|
|
|
|
// TestUnifiedEnum_InlineQueryResultType_VariantFields walks the variants
|
|
// and asserts each one's Type field is the unified enum.
|
|
func TestUnifiedEnum_InlineQueryResultType_VariantFields(t *testing.T) {
|
|
require.IsType(t, InlineQueryResultType(""), InlineQueryResultTypeArticle)
|
|
|
|
wantType := reflect.TypeOf(InlineQueryResultType(""))
|
|
cases := []any{
|
|
&InlineQueryResultArticle{},
|
|
&InlineQueryResultPhoto{},
|
|
&InlineQueryResultGif{},
|
|
&InlineQueryResultMpeg4Gif{},
|
|
&InlineQueryResultVideo{},
|
|
&InlineQueryResultAudio{},
|
|
&InlineQueryResultVoice{},
|
|
&InlineQueryResultDocument{},
|
|
&InlineQueryResultLocation{},
|
|
&InlineQueryResultVenue{},
|
|
&InlineQueryResultContact{},
|
|
&InlineQueryResultGame{},
|
|
}
|
|
for _, c := range cases {
|
|
rt := reflect.TypeOf(c).Elem()
|
|
f, ok := rt.FieldByName("Type")
|
|
require.True(t, ok, "%s missing Type field", rt.Name())
|
|
require.Equal(t, wantType, f.Type, "%s.Type type mismatch", rt.Name())
|
|
}
|
|
}
|
|
|
|
// TestUnifiedEnum_PassportElementErrorSource_VariantFields asserts the
|
|
// retype landed on every variant of the PassportElementError union.
|
|
func TestUnifiedEnum_PassportElementErrorSource_VariantFields(t *testing.T) {
|
|
require.IsType(t, PassportElementErrorSource(""), PassportElementErrorSourceData)
|
|
|
|
wantType := reflect.TypeOf(PassportElementErrorSource(""))
|
|
cases := []any{
|
|
&PassportElementErrorDataField{},
|
|
&PassportElementErrorFrontSide{},
|
|
&PassportElementErrorReverseSide{},
|
|
&PassportElementErrorSelfie{},
|
|
&PassportElementErrorFile{},
|
|
&PassportElementErrorFiles{},
|
|
&PassportElementErrorTranslationFile{},
|
|
&PassportElementErrorTranslationFiles{},
|
|
&PassportElementErrorUnspecified{},
|
|
}
|
|
for _, c := range cases {
|
|
rt := reflect.TypeOf(c).Elem()
|
|
f, ok := rt.FieldByName("Source")
|
|
require.True(t, ok, "%s missing Source field", rt.Name())
|
|
require.Equal(t, wantType, f.Type, "%s.Source type mismatch", rt.Name())
|
|
}
|
|
}
|
|
|
|
// TestUnifiedEnum_InputMediaType_Constants covers a media-shaped union
|
|
// where the discriminator value is the wire identifier "animation",
|
|
// "photo", etc.
|
|
func TestUnifiedEnum_InputMediaType_Constants(t *testing.T) {
|
|
require.IsType(t, InputMediaType(""), InputMediaTypePhoto)
|
|
|
|
wantType := reflect.TypeOf(InputMediaType(""))
|
|
for _, c := range []any{
|
|
&InputMediaAnimation{},
|
|
&InputMediaAudio{},
|
|
&InputMediaDocument{},
|
|
&InputMediaPhoto{},
|
|
&InputMediaVideo{},
|
|
} {
|
|
rt := reflect.TypeOf(c).Elem()
|
|
f, ok := rt.FieldByName("Type")
|
|
require.True(t, ok, "%s missing Type field", rt.Name())
|
|
require.Equal(t, wantType, f.Type, "%s.Type type mismatch", rt.Name())
|
|
}
|
|
}
|
|
|
|
// TestUnifiedEnum_MenuButtonType_Constants covers the third single-Type
|
|
// union pulled in by the prose detector.
|
|
func TestUnifiedEnum_MenuButtonType_Constants(t *testing.T) {
|
|
require.IsType(t, MenuButtonType(""), MenuButtonTypeCommands)
|
|
|
|
wantType := reflect.TypeOf(MenuButtonType(""))
|
|
for _, c := range []any{
|
|
&MenuButtonCommands{},
|
|
&MenuButtonWebApp{},
|
|
&MenuButtonDefault{},
|
|
} {
|
|
rt := reflect.TypeOf(c).Elem()
|
|
f, ok := rt.FieldByName("Type")
|
|
require.True(t, ok, "%s missing Type field", rt.Name())
|
|
require.Equal(t, wantType, f.Type, "%s.Type type mismatch", rt.Name())
|
|
}
|
|
}
|
|
|
|
// TestUnifiedEnum_InlineQueryResultArticle_RoundTrip confirms the
|
|
// auto-injected discriminator survives a marshal-unmarshal cycle on the
|
|
// concrete variant and lands as the typed enum constant. There's no
|
|
// generated UnmarshalInlineQueryResult — the union has no entry in
|
|
// knownDiscriminators — so the round-trip targets the variant directly.
|
|
func TestUnifiedEnum_InlineQueryResultArticle_RoundTrip(t *testing.T) {
|
|
orig := &InlineQueryResultArticle{
|
|
ID: "x1",
|
|
Title: "test",
|
|
}
|
|
raw, err := json.Marshal(orig)
|
|
require.NoError(t, err)
|
|
|
|
var probe struct {
|
|
Type string `json:"type"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(raw, &probe))
|
|
require.Equal(t, "article", probe.Type)
|
|
|
|
// Strip InputMessageContent before re-decoding: it's a sealed
|
|
// interface and the variant has no UnmarshalJSON helper to dispatch
|
|
// it. The discriminator round-trip is the property under test, not
|
|
// nested-union deserialisation.
|
|
var round struct {
|
|
Type InlineQueryResultType `json:"type"`
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(raw, &round))
|
|
require.Equal(t, InlineQueryResultTypeArticle, round.Type)
|
|
require.Equal(t, orig.ID, round.ID)
|
|
require.Equal(t, orig.Title, round.Title)
|
|
}
|
|
|
|
// TestUnifiedEnum_PassportElementErrorDataField_RoundTrip mirrors the
|
|
// above for the Source-discriminated union.
|
|
func TestUnifiedEnum_PassportElementErrorDataField_RoundTrip(t *testing.T) {
|
|
orig := &PassportElementErrorDataField{
|
|
Type: "personal_details",
|
|
FieldName: "first_name",
|
|
DataHash: "abc",
|
|
Message: "boom",
|
|
}
|
|
raw, err := json.Marshal(orig)
|
|
require.NoError(t, err)
|
|
|
|
var probe struct {
|
|
Source string `json:"source"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(raw, &probe))
|
|
require.Equal(t, "data", probe.Source)
|
|
|
|
var round PassportElementErrorDataField
|
|
require.NoError(t, json.Unmarshal(raw, &round))
|
|
require.Equal(t, PassportElementErrorSourceData, round.Source)
|
|
require.Equal(t, orig.FieldName, round.FieldName)
|
|
}
|
|
|
|
// TestUnifiedEnum_BotCommandScopeChat_RoundTrip covers a bot-command
|
|
// scope variant with a non-trivial extra field (ChatID).
|
|
func TestUnifiedEnum_BotCommandScopeChat_RoundTrip(t *testing.T) {
|
|
orig := &BotCommandScopeChat{ChatID: ChatIDFromInt(42)}
|
|
raw, err := json.Marshal(orig)
|
|
require.NoError(t, err)
|
|
|
|
var probe struct {
|
|
Type string `json:"type"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(raw, &probe))
|
|
require.Equal(t, "chat", probe.Type)
|
|
|
|
var round BotCommandScopeChat
|
|
require.NoError(t, json.Unmarshal(raw, &round))
|
|
require.Equal(t, BotCommandScopeTypeChat, round.Type)
|
|
}
|
|
|
|
// TestUnifiedEnum_InputMessageContent_NoEnumEmitted confirms the IMC
|
|
// union — which dispatches structurally on field presence rather than a
|
|
// shared discriminator — does NOT get a unified enum, since none of its
|
|
// variants declare a single-value discriminator field.
|
|
func TestUnifiedEnum_InputMessageContent_NoEnumEmitted(t *testing.T) {
|
|
for _, name := range []string{
|
|
"InputTextMessageContent",
|
|
"InputLocationMessageContent",
|
|
"InputVenueMessageContent",
|
|
"InputContactMessageContent",
|
|
"InputInvoiceMessageContent",
|
|
} {
|
|
switch name {
|
|
case "InputTextMessageContent":
|
|
rt := reflect.TypeOf(&InputTextMessageContent{}).Elem()
|
|
_, ok := rt.FieldByName("Type")
|
|
require.False(t, ok, "%s unexpectedly grew a Type field", name)
|
|
case "InputLocationMessageContent":
|
|
rt := reflect.TypeOf(&InputLocationMessageContent{}).Elem()
|
|
_, ok := rt.FieldByName("Type")
|
|
require.False(t, ok, "%s unexpectedly grew a Type field", name)
|
|
case "InputVenueMessageContent":
|
|
rt := reflect.TypeOf(&InputVenueMessageContent{}).Elem()
|
|
_, ok := rt.FieldByName("Type")
|
|
require.False(t, ok, "%s unexpectedly grew a Type field", name)
|
|
case "InputContactMessageContent":
|
|
rt := reflect.TypeOf(&InputContactMessageContent{}).Elem()
|
|
_, ok := rt.FieldByName("Type")
|
|
require.False(t, ok, "%s unexpectedly grew a Type field", name)
|
|
case "InputInvoiceMessageContent":
|
|
rt := reflect.TypeOf(&InputInvoiceMessageContent{}).Elem()
|
|
_, ok := rt.FieldByName("Type")
|
|
require.False(t, ok, "%s unexpectedly grew a Type field", name)
|
|
}
|
|
}
|
|
}
|