package main import ( "testing" "github.com/lukaszraczylo/go-telegram/internal/spec" "github.com/stretchr/testify/require" ) // --------------------------------------------------------------------------- // goType — all branches // --------------------------------------------------------------------------- func TestGoType_Primitive(t *testing.T) { cases := []struct { name string optional bool want string }{ {"bool", false, "bool"}, {"bool", true, "*bool"}, {"int64", false, "int64"}, {"int64", true, "*int64"}, {"float64", false, "float64"}, {"float64", true, "*float64"}, {"string", false, "string"}, {"string", true, "string"}, // string is not pointer-wrapped } for _, c := range cases { tr := spec.TypeRef{Kind: spec.KindPrimitive, Name: c.name} got := goType(tr, c.optional) require.Equal(t, c.want, got, "goType(%q, optional=%v)", c.name, c.optional) } } func TestGoType_Named_Required(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindNamed, Name: "Message"} require.Equal(t, "Message", goType(tr, false)) require.Equal(t, "*Message", goType(tr, true)) } func TestGoType_Named_InputFile(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindNamed, Name: "InputFile"} // InputFile is always pointer even when required. require.Equal(t, "*InputFile", goType(tr, false)) require.Equal(t, "*InputFile", goType(tr, true)) } func TestGoType_Named_UnionInterface(t *testing.T) { // ChatMember is a known discriminated union — no * even when optional. tr := spec.TypeRef{Kind: spec.KindNamed, Name: "ChatMember"} require.Equal(t, "ChatMember", goType(tr, false)) require.Equal(t, "ChatMember", goType(tr, true)) } func TestGoType_Array_NilElem(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindArray} require.Equal(t, "[]any", goType(tr, false)) } func TestGoType_Array_WithElem(t *testing.T) { elem := spec.TypeRef{Kind: spec.KindNamed, Name: "Update"} tr := spec.TypeRef{Kind: spec.KindArray, ElemType: &elem} require.Equal(t, "[]Update", goType(tr, false)) } func TestGoType_OneOf_ChatID(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"int64", "string"}} require.Equal(t, "ChatID", goType(tr, false)) require.Equal(t, "*ChatID", goType(tr, true)) } func TestGoType_OneOf_InputFile(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"InputFile", "string"}} require.Equal(t, "*InputFile", goType(tr, false)) } func TestGoType_OneOf_SealedInterface(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"A", "B"}} require.Equal(t, "any", goType(tr, false)) } func TestGoType_Unknown(t *testing.T) { tr := spec.TypeRef{Kind: spec.Kind(99)} require.Equal(t, "any", goType(tr, false)) } // --------------------------------------------------------------------------- // returnGoType — all branches // --------------------------------------------------------------------------- func TestReturnGoType_Primitive(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindPrimitive, Name: "bool"} require.Equal(t, "bool", returnGoType(tr)) } func TestReturnGoType_Named(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindNamed, Name: "Message"} require.Equal(t, "*Message", returnGoType(tr)) } func TestReturnGoType_Array_NilElem(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindArray} require.Equal(t, "[]any", returnGoType(tr)) } func TestReturnGoType_Array_WithElem(t *testing.T) { elem := spec.TypeRef{Kind: spec.KindNamed, Name: "Update"} tr := spec.TypeRef{Kind: spec.KindArray, ElemType: &elem} require.Equal(t, "[]Update", returnGoType(tr)) } func TestReturnGoType_OneOf_ChatID(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"int64", "string"}} require.Equal(t, "ChatID", returnGoType(tr)) } func TestReturnGoType_OneOf_Other(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"A", "B"}} require.Equal(t, "any", returnGoType(tr)) } func TestReturnGoType_Unknown(t *testing.T) { tr := spec.TypeRef{Kind: spec.Kind(99)} require.Equal(t, "any", returnGoType(tr)) } // --------------------------------------------------------------------------- // returnGoElem — all branches // --------------------------------------------------------------------------- func TestReturnGoElem_Primitive(t *testing.T) { require.Equal(t, "int64", returnGoElem(spec.TypeRef{Kind: spec.KindPrimitive, Name: "int64"})) } func TestReturnGoElem_Named(t *testing.T) { require.Equal(t, "Message", returnGoElem(spec.TypeRef{Kind: spec.KindNamed, Name: "Message"})) } func TestReturnGoElem_Array_NilElem(t *testing.T) { require.Equal(t, "any", returnGoElem(spec.TypeRef{Kind: spec.KindArray})) } func TestReturnGoElem_Array_WithElem(t *testing.T) { elem := spec.TypeRef{Kind: spec.KindNamed, Name: "PhotoSize"} tr := spec.TypeRef{Kind: spec.KindArray, ElemType: &elem} require.Equal(t, "[]PhotoSize", returnGoElem(tr)) } func TestReturnGoElem_Unknown(t *testing.T) { require.Equal(t, "any", returnGoElem(spec.TypeRef{Kind: spec.Kind(99)})) } // --------------------------------------------------------------------------- // multipartFieldEntry — all branches // --------------------------------------------------------------------------- func makeField(name, jname, typName string, kind spec.Kind, required bool) spec.Field { return spec.Field{ Name: name, JSONName: jname, Type: spec.TypeRef{Kind: kind, Name: typName}, Required: required, } } func makeFieldVariants(name, jname string, kind spec.Kind, variants []string, required bool) spec.Field { return spec.Field{ Name: name, JSONName: jname, Type: spec.TypeRef{Kind: kind, Variants: variants}, Required: required, } } func TestMultipartFieldEntry_Int64Required(t *testing.T) { f := makeField("ChatID", "chat_id", "int64", spec.KindPrimitive, true) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `FormatInt`) require.NotContains(t, got, "if p.") } func TestMultipartFieldEntry_Int64Optional(t *testing.T) { f := makeField("MessageThreadID", "message_thread_id", "int64", spec.KindPrimitive, false) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `FormatInt`) require.Contains(t, got, "if p.") } func TestMultipartFieldEntry_StringRequired(t *testing.T) { f := makeField("Text", "text", "string", spec.KindPrimitive, true) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `out["text"]`) require.NotContains(t, got, "if p.Text") } func TestMultipartFieldEntry_StringOptional(t *testing.T) { f := makeField("ParseMode", "parse_mode", "string", spec.KindPrimitive, false) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `if p.ParseMode`) } func TestMultipartFieldEntry_BoolRequired(t *testing.T) { f := makeField("DisableNotification", "disable_notification", "bool", spec.KindPrimitive, true) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `FormatBool`) require.NotContains(t, got, "if p.") } func TestMultipartFieldEntry_BoolOptional(t *testing.T) { f := makeField("Protected", "protect_content", "bool", spec.KindPrimitive, false) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `FormatBool`) require.Contains(t, got, "if p.") } func TestMultipartFieldEntry_Float64Required(t *testing.T) { f := makeField("Latitude", "latitude", "float64", spec.KindPrimitive, true) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `FormatFloat`) require.NotContains(t, got, "if p.") } func TestMultipartFieldEntry_Float64Optional(t *testing.T) { f := makeField("Longitude", "longitude", "float64", spec.KindPrimitive, false) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `FormatFloat`) require.Contains(t, got, "if p.") } func TestMultipartFieldEntry_OneOf_ChatIDRequired(t *testing.T) { f := makeFieldVariants("ChatID", "chat_id", spec.KindOneOf, []string{"int64", "string"}, true) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `.String()`) require.NotContains(t, got, "IsZero") } func TestMultipartFieldEntry_OneOf_ChatIDOptional(t *testing.T) { f := makeFieldVariants("ChatID", "chat_id", spec.KindOneOf, []string{"int64", "string"}, false) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `IsZero`) } func TestMultipartFieldEntry_OneOf_InputFileOrString(t *testing.T) { f := makeFieldVariants("Photo", "photo", spec.KindOneOf, []string{"InputFile", "string"}, false) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `PathOrID`) } func TestMultipartFieldEntry_OneOf_SealedRequired(t *testing.T) { f := makeFieldVariants("Markup", "reply_markup", spec.KindOneOf, []string{"A", "B"}, true) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `json.Marshal`) } func TestMultipartFieldEntry_OneOf_SealedOptional(t *testing.T) { f := makeFieldVariants("Markup", "reply_markup", spec.KindOneOf, []string{"A", "B"}, false) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `json.Marshal`) require.Contains(t, got, "if p.Markup") } func TestMultipartFieldEntry_Named_Required(t *testing.T) { f := makeField("Entities", "entities", "MessageEntity", spec.KindNamed, true) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `json.Marshal`) require.NotContains(t, got, "if p.") } func TestMultipartFieldEntry_Named_Optional(t *testing.T) { f := makeField("Entities", "entities", "MessageEntity", spec.KindNamed, false) got := multipartFieldEntry(nil, "", f) require.Contains(t, got, `json.Marshal`) require.Contains(t, got, "if p.") } // --------------------------------------------------------------------------- // unionTypeFor — all branches // --------------------------------------------------------------------------- func TestUnionTypeFor_DirectNamed(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindNamed, Name: "ChatMember"} name, ok := unionTypeFor(tr) require.True(t, ok) require.Equal(t, "ChatMember", name) } func TestUnionTypeFor_Array(t *testing.T) { elem := spec.TypeRef{Kind: spec.KindNamed, Name: "ChatMember"} tr := spec.TypeRef{Kind: spec.KindArray, ElemType: &elem} name, ok := unionTypeFor(tr) require.True(t, ok) require.Equal(t, "ChatMember", name) } func TestUnionTypeFor_ArrayNilElem(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindArray} _, ok := unionTypeFor(tr) require.False(t, ok) } func TestUnionTypeFor_NotUnion(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindNamed, Name: "Message"} _, ok := unionTypeFor(tr) require.False(t, ok) } func TestUnionTypeFor_Unknown(t *testing.T) { tr := spec.TypeRef{Kind: spec.Kind(99)} _, ok := unionTypeFor(tr) require.False(t, ok) } // --------------------------------------------------------------------------- // unionNameByVariants // --------------------------------------------------------------------------- func TestUnionNameByVariants_ChatMember(t *testing.T) { // Use the actual variants from knownDiscriminators["ChatMember"]. variants := []string{ "ChatMemberOwner", "ChatMemberAdministrator", "ChatMemberMember", "ChatMemberRestricted", "ChatMemberLeft", "ChatMemberBanned", } name := unionNameByVariants(variants) require.Equal(t, "ChatMember", name) } func TestUnionNameByVariants_Unknown(t *testing.T) { name := unionNameByVariants([]string{"X", "Y", "Z"}) require.Equal(t, "", name) } // --------------------------------------------------------------------------- // hasUnionElem // --------------------------------------------------------------------------- func TestHasUnionElem_NonArray(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindNamed, Name: "ChatMember"} require.False(t, hasUnionElem(tr)) } func TestHasUnionElem_ArrayNilElem(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindArray} require.False(t, hasUnionElem(tr)) } func TestHasUnionElem_ArrayUnionElem(t *testing.T) { elem := spec.TypeRef{Kind: spec.KindNamed, Name: "ChatMember"} tr := spec.TypeRef{Kind: spec.KindArray, ElemType: &elem} require.True(t, hasUnionElem(tr)) } func TestHasUnionElem_ArrayNonUnionElem(t *testing.T) { elem := spec.TypeRef{Kind: spec.KindNamed, Name: "Message"} tr := spec.TypeRef{Kind: spec.KindArray, ElemType: &elem} require.False(t, hasUnionElem(tr)) } // --------------------------------------------------------------------------- // unionFieldsOf // --------------------------------------------------------------------------- func TestUnionFieldsOf_WithUnionField(t *testing.T) { td := spec.TypeDecl{ Name: "ChatMemberUpdated", Fields: []spec.Field{ {Name: "NewChatMember", JSONName: "new_chat_member", Type: spec.TypeRef{Kind: spec.KindNamed, Name: "ChatMember"}}, {Name: "OldChatMember", JSONName: "old_chat_member", Type: spec.TypeRef{Kind: spec.KindNamed, Name: "ChatMember"}}, {Name: "Date", JSONName: "date", Type: spec.TypeRef{Kind: spec.KindPrimitive, Name: "int64"}}, }, } uf := unionFieldsOf(td) require.Len(t, uf, 2) require.Equal(t, "ChatMember", uf[0].UnionName) } // --------------------------------------------------------------------------- // splitLines — edge cases // --------------------------------------------------------------------------- func TestSplitLines_Empty(t *testing.T) { require.Empty(t, splitLines("")) } func TestSplitLines_NoNewline(t *testing.T) { got := splitLines("hello world") require.Equal(t, []string{"hello world"}, got) } func TestSplitLines_TrailingNewline(t *testing.T) { got := splitLines("line1\nline2\n") require.Equal(t, []string{"line1", "line2"}, got) } func TestSplitLines_MultiLine(t *testing.T) { got := splitLines("a\nb\nc") require.Equal(t, []string{"a", "b", "c"}, got) } // --------------------------------------------------------------------------- // docComment // --------------------------------------------------------------------------- func TestDocComment_Empty(t *testing.T) { require.Equal(t, "", docComment("")) } func TestDocComment_SingleLine(t *testing.T) { got := docComment("Hello world.") require.Equal(t, "// Hello world.\n", got) } func TestDocComment_MultiLine(t *testing.T) { got := docComment("Line 1\nLine 2") require.Contains(t, got, "// Line 1\n") require.Contains(t, got, "// Line 2\n") } // --------------------------------------------------------------------------- // title // --------------------------------------------------------------------------- func TestTitle_Empty(t *testing.T) { require.Equal(t, "", title("")) } func TestTitle_Lowercase(t *testing.T) { require.Equal(t, "SendMessage", title("sendMessage")) } func TestTitle_AlreadyUpper(t *testing.T) { require.Equal(t, "GetMe", title("GetMe")) } // --------------------------------------------------------------------------- // mentionsInputFileTr // --------------------------------------------------------------------------- func TestMentionsInputFileTr_Named(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindNamed, Name: "InputFile"} require.True(t, mentionsInputFileTr(tr)) } func TestMentionsInputFileTr_NotInputFile(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindNamed, Name: "Message"} require.False(t, mentionsInputFileTr(tr)) } func TestMentionsInputFileTr_Array(t *testing.T) { elem := spec.TypeRef{Kind: spec.KindNamed, Name: "InputFile"} tr := spec.TypeRef{Kind: spec.KindArray, ElemType: &elem} require.True(t, mentionsInputFileTr(tr)) } func TestMentionsInputFileTr_ArrayNilElem(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindArray} require.False(t, mentionsInputFileTr(tr)) } func TestMentionsInputFileTr_OneOf_WithInputFile(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"InputFile", "string"}} require.True(t, mentionsInputFileTr(tr)) } func TestMentionsInputFileTr_OneOf_WithoutInputFile(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"A", "B"}} require.False(t, mentionsInputFileTr(tr)) } // --------------------------------------------------------------------------- // loadAPI — error paths // --------------------------------------------------------------------------- func TestLoadAPI_MissingFile(t *testing.T) { _, err := loadAPI("/nonexistent/path/api.json") require.Error(t, err) } // --------------------------------------------------------------------------- // runtimeTypes filter in emitTypes // --------------------------------------------------------------------------- func TestRuntimeTypes_NeverEmitted(t *testing.T) { for name := range runtimeTypes { require.True(t, runtimeTypes[name], "runtimeType %q should be true", name) } require.True(t, runtimeTypes["InputFile"]) require.True(t, runtimeTypes["ChatID"]) require.True(t, runtimeTypes["MessageOrBool"]) require.True(t, runtimeTypes["ResponseParameters"]) } // --------------------------------------------------------------------------- // sentinelForField — all branches // --------------------------------------------------------------------------- func TestSentinelForField(t *testing.T) { unionTypes := map[string]bool{"ChatMember": true} cases := []struct { name string field spec.Field contains string }{ { name: "int64 primitive", field: makeField("Count", "count", "int64", spec.KindPrimitive, true), contains: "42", }, { name: "string primitive", field: makeField("Text", "text", "string", spec.KindPrimitive, true), contains: "test_value", }, { name: "bool primitive", field: makeField("Flag", "flag", "bool", spec.KindPrimitive, true), contains: "true", }, { name: "float64 primitive", field: makeField("Lat", "lat", "float64", spec.KindPrimitive, true), contains: "1.0", }, { name: "named ChatID", field: makeField("ChatID", "chat_id", "ChatID", spec.KindNamed, true), contains: "ChatIDFromInt", }, { name: "named InputFile", field: makeField("Photo", "photo", "InputFile", spec.KindNamed, true), contains: "InputFile", }, { name: "named union (nil-able)", field: makeField("Member", "member", "ChatMember", spec.KindNamed, true), contains: "nil", }, { name: "named required struct", field: makeField("Chat", "chat", "Chat", spec.KindNamed, true), contains: "Chat{}", }, { name: "named optional struct", field: makeField("Chat", "chat", "Chat", spec.KindNamed, false), contains: "&Chat{}", }, { name: "array", field: spec.Field{Name: "Items", JSONName: "items", Type: spec.TypeRef{Kind: spec.KindArray}}, contains: "nil", }, { name: "oneOf ChatID variants", field: makeFieldVariants("ChatID", "chat_id", spec.KindOneOf, []string{"int64", "string"}, true), contains: "ChatIDFromInt", }, { name: "oneOf InputFile variants", field: makeFieldVariants("Photo", "photo", spec.KindOneOf, []string{"InputFile", "string"}, true), contains: "InputFile", }, { name: "oneOf sealed", field: makeFieldVariants("Markup", "markup", spec.KindOneOf, []string{"A", "B"}, true), contains: "nil", }, { name: "unknown kind", field: spec.Field{Name: "X", JSONName: "x", Type: spec.TypeRef{Kind: spec.Kind(99)}}, contains: "nil", }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := sentinelForField(c.field, unionTypes, nil) require.Contains(t, got, c.contains, "sentinelForField for %q", c.name) }) } } // --------------------------------------------------------------------------- // successBody — all branches // --------------------------------------------------------------------------- func TestSuccessBody(t *testing.T) { cases := []struct { name string tr spec.TypeRef want string }{ {"bool", spec.TypeRef{Kind: spec.KindPrimitive, Name: "bool"}, "true"}, {"int64", spec.TypeRef{Kind: spec.KindPrimitive, Name: "int64"}, "0"}, {"float64", spec.TypeRef{Kind: spec.KindPrimitive, Name: "float64"}, "0"}, {"string", spec.TypeRef{Kind: spec.KindPrimitive, Name: "string"}, `""`}, {"MessageOrBool", spec.TypeRef{Kind: spec.KindNamed, Name: "MessageOrBool"}, "true"}, {"named", spec.TypeRef{Kind: spec.KindNamed, Name: "Message"}, "{}"}, {"array", spec.TypeRef{Kind: spec.KindArray}, "[]"}, {"oneOf", spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"A", "B"}}, "null"}, {"unknown", spec.TypeRef{Kind: spec.Kind(99)}, "null"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := successBody(c.tr) require.Equal(t, c.want, got) }) } } // --------------------------------------------------------------------------- // unionTypeFor — KindOneOf branch (variant-set match) // --------------------------------------------------------------------------- func TestUnionTypeFor_OneOfVariants(t *testing.T) { // These variant *type names* match the ChatMember discriminator. tr := spec.TypeRef{ Kind: spec.KindOneOf, Variants: []string{ "ChatMemberOwner", "ChatMemberAdministrator", "ChatMemberMember", "ChatMemberRestricted", "ChatMemberLeft", "ChatMemberBanned", }, } name, ok := unionTypeFor(tr) require.True(t, ok) require.Equal(t, "ChatMember", name) } func TestUnionTypeFor_OneOfNoMatch(t *testing.T) { tr := spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"Foo", "Bar"}} _, ok := unionTypeFor(tr) require.False(t, ok) } // --------------------------------------------------------------------------- // funcs() returns a non-nil FuncMap with expected keys // --------------------------------------------------------------------------- func TestFuncs_HasExpectedKeys(t *testing.T) { fm := funcs(nil) require.NotNil(t, fm) for _, key := range []string{"goType", "docComment", "returnGoType", "unionFields"} { require.NotNil(t, fm[key], "funcs() missing key %q", key) } }