package bfcodegen import ( "git.blackforestbytes.com/BlackForestBytes/goext/langext" "git.blackforestbytes.com/BlackForestBytes/goext/tst" "os" "path/filepath" "strings" "testing" ) func TestProcessEnumFileBasicStringEnum(t *testing.T) { dir := t.TempDir() src := `package mymodels type Color string // @enum:type const ( ColorRed Color = "red" ColorBlue Color = "blue" ColorGreen Color = "green" ) ` fp := writeTestFile(t, dir, "color.go", src) enums, pkg, err := processEnumFile(dir, fp, false) tst.AssertNoErr(t, err) tst.AssertEqual(t, pkg, "mymodels") tst.AssertEqual(t, len(enums), 1) tst.AssertEqual(t, enums[0].EnumTypeName, "Color") tst.AssertEqual(t, enums[0].Type, "string") tst.AssertEqual(t, len(enums[0].Values), 3) tst.AssertEqual(t, enums[0].Values[0].VarName, "ColorRed") tst.AssertEqual(t, enums[0].Values[0].Value, `"red"`) tst.AssertEqual(t, enums[0].Values[1].VarName, "ColorBlue") tst.AssertEqual(t, enums[0].Values[2].VarName, "ColorGreen") } func TestProcessEnumFileIntEnum(t *testing.T) { dir := t.TempDir() src := `package m type Priority int // @enum:type const ( PriorityLow Priority = 0 PriorityMedium Priority = 1 PriorityHigh Priority = 2 ) ` fp := writeTestFile(t, dir, "prio.go", src) enums, pkg, err := processEnumFile(dir, fp, false) tst.AssertNoErr(t, err) tst.AssertEqual(t, pkg, "m") tst.AssertEqual(t, len(enums), 1) tst.AssertEqual(t, enums[0].EnumTypeName, "Priority") tst.AssertEqual(t, enums[0].Type, "int") tst.AssertEqual(t, len(enums[0].Values), 3) tst.AssertEqual(t, enums[0].Values[0].Value, "0") tst.AssertEqual(t, enums[0].Values[2].Value, "2") } func TestProcessEnumFileWithDescriptions(t *testing.T) { dir := t.TempDir() src := `package m type Status string // @enum:type const ( StatusActive Status = "active" // The active status StatusInactive Status = "inactive" // The inactive status ) ` fp := writeTestFile(t, dir, "s.go", src) enums, _, err := processEnumFile(dir, fp, false) tst.AssertNoErr(t, err) tst.AssertEqual(t, len(enums), 1) tst.AssertEqual(t, len(enums[0].Values), 2) v0 := enums[0].Values[0] tst.AssertTrue(t, v0.Description != nil) tst.AssertEqual(t, *v0.Description, "The active status") tst.AssertTrue(t, v0.Data == nil) } func TestProcessEnumFileWithDataComment(t *testing.T) { dir := t.TempDir() src := `package m type Severity string // @enum:type const ( SeverityLow Severity = "low" // {"description": "Low severity", "weight": 1} SeverityHigh Severity = "high" // {"description": "High severity", "weight": 9} ) ` fp := writeTestFile(t, dir, "sev.go", src) enums, _, err := processEnumFile(dir, fp, false) tst.AssertNoErr(t, err) tst.AssertEqual(t, len(enums), 1) tst.AssertEqual(t, len(enums[0].Values), 2) v0 := enums[0].Values[0] tst.AssertTrue(t, v0.Data != nil) tst.AssertTrue(t, v0.Description != nil) tst.AssertEqual(t, *v0.Description, "Low severity") } func TestProcessEnumFileNonMatchingValuesNotAttached(t *testing.T) { dir := t.TempDir() src := `package m type Color string // @enum:type const ( ColorRed Color = "red" ) const ( OtherX OtherType = "x" ) ` fp := writeTestFile(t, dir, "c.go", src) enums, _, err := processEnumFile(dir, fp, false) tst.AssertNoErr(t, err) tst.AssertEqual(t, len(enums), 1) tst.AssertEqual(t, len(enums[0].Values), 1) tst.AssertEqual(t, enums[0].Values[0].VarName, "ColorRed") } func TestProcessEnumFileGeneratedHeaderSkipped(t *testing.T) { dir := t.TempDir() src := `// Code generated by enum-generate.go DO NOT EDIT. package x type Foo string // @enum:type ` fp := writeTestFile(t, dir, "skip.go", src) enums, pkg, err := processEnumFile(dir, fp, false) tst.AssertNoErr(t, err) tst.AssertEqual(t, pkg, "") tst.AssertEqual(t, len(enums), 0) } func TestTryParseDataCommentValid(t *testing.T) { m, ok := tryParseDataComment(`{"description": "hello", "weight": 5}`) tst.AssertTrue(t, ok) descr, _ := m["description"].(string) tst.AssertEqual(t, descr, "hello") weight, _ := m["weight"].(float64) tst.AssertEqual(t, weight, float64(5)) } func TestTryParseDataCommentBool(t *testing.T) { m, ok := tryParseDataComment(`{"a": true, "b": false}`) tst.AssertTrue(t, ok) a, _ := m["a"].(bool) tst.AssertTrue(t, a) b, _ := m["b"].(bool) tst.AssertFalse(t, b) } func TestTryParseDataCommentRejectsNull(t *testing.T) { // null becomes a nil interface — its reflect.Kind is Invalid, not Pointer, // so it does not match any of the allowed kinds and is rejected. _, ok := tryParseDataComment(`{"x": null}`) tst.AssertFalse(t, ok) } func TestTryParseDataCommentInvalidJSON(t *testing.T) { _, ok := tryParseDataComment(`{not valid json}`) tst.AssertFalse(t, ok) } func TestTryParseDataCommentRejectsArrays(t *testing.T) { // arrays as values are not in the supported kinds list _, ok := tryParseDataComment(`{"x": [1, 2, 3]}`) tst.AssertFalse(t, ok) } func TestTryParseDataCommentRejectsObjects(t *testing.T) { _, ok := tryParseDataComment(`{"x": {"nested": 1}}`) tst.AssertFalse(t, ok) } func TestGenerateEnumSpecsEndToEnd(t *testing.T) { dir := t.TempDir() src := `package models type Color string // @enum:type const ( ColorRed Color = "red" ColorGreen Color = "green" ColorBlue Color = "blue" ) ` writeTestFile(t, dir, "color.go", src) dest := filepath.Join(dir, "enum_gen.go") err := GenerateEnumSpecs(dir, dest, EnumGenOptions{ DebugOutput: langext.PFalse, GoFormat: langext.PTrue, }) tst.AssertNoErr(t, err) out, err := os.ReadFile(dest) tst.AssertNoErr(t, err) outStr := string(out) tst.AssertTrue(t, strings.Contains(outStr, "package models")) tst.AssertTrue(t, strings.Contains(outStr, "ChecksumEnumGenerator")) tst.AssertTrue(t, strings.Contains(outStr, "ParseColor")) tst.AssertTrue(t, strings.Contains(outStr, "ColorValues")) tst.AssertTrue(t, strings.Contains(outStr, "ColorRed")) tst.AssertTrue(t, strings.Contains(outStr, "ColorBlue")) tst.AssertTrue(t, strings.Contains(outStr, "ColorGreen")) } func TestGenerateEnumSpecsDeterministic(t *testing.T) { dir := t.TempDir() src := `package models type Status string // @enum:type const ( StatusActive Status = "active" // The active one StatusOff Status = "off" // The off one ) ` writeTestFile(t, dir, "s.go", src) s1, cs1, changed1, err := _generateEnumSpecs(dir, "", "N/A", true, false) tst.AssertNoErr(t, err) tst.AssertTrue(t, changed1) s2, cs2, changed2, err := _generateEnumSpecs(dir, "", "N/A", true, false) tst.AssertNoErr(t, err) tst.AssertTrue(t, changed2) tst.AssertEqual(t, cs1, cs2) tst.AssertEqual(t, s1, s2) } func TestGenerateEnumSpecsNoChangeWhenChecksumMatches(t *testing.T) { dir := t.TempDir() src := `package models type Status string // @enum:type const ( StatusActive Status = "active" ) ` writeTestFile(t, dir, "s.go", src) _, cs, changed, err := _generateEnumSpecs(dir, "", "N/A", true, false) tst.AssertNoErr(t, err) tst.AssertTrue(t, changed) s2, cs2, changed2, err := _generateEnumSpecs(dir, "", cs, true, false) tst.AssertNoErr(t, err) tst.AssertFalse(t, changed2) tst.AssertEqual(t, cs2, cs) tst.AssertEqual(t, s2, "") } func TestGenerateEnumSpecsWithoutGoFormat(t *testing.T) { dir := t.TempDir() src := `package models type Color string // @enum:type const ( ColorRed Color = "red" ) ` writeTestFile(t, dir, "c.go", src) out, _, _, err := _generateEnumSpecs(dir, "", "N/A", false, false) tst.AssertNoErr(t, err) tst.AssertTrue(t, strings.Contains(out, "ColorRed")) tst.AssertTrue(t, strings.Contains(out, "package models")) } func TestGenerateEnumSpecsErrorsWithoutPackage(t *testing.T) { dir := t.TempDir() src := `// Code generated by enum-generate.go DO NOT EDIT. package x type Foo string // @enum:type ` writeTestFile(t, dir, "z.go", src) _, _, _, err := _generateEnumSpecs(dir, "", "N/A", false, false) tst.AssertTrue(t, err != nil) } func TestGenerateEnumSpecsMissingDir(t *testing.T) { dir := filepath.Join(t.TempDir(), "definitely-missing") _, _, _, err := _generateEnumSpecs(dir, "", "N/A", false, false) tst.AssertTrue(t, err != nil) } func TestFmtEnumOutputContainsTypes(t *testing.T) { descr := "the red one" enums := []EnumDef{ { File: "color.go", FileRelative: "color.go", EnumTypeName: "Color", Type: "string", Values: []EnumDefVal{ {VarName: "ColorRed", Value: `"red"`, Description: &descr}, {VarName: "ColorBlue", Value: `"blue"`, Description: &descr}, }, }, } out := fmtEnumOutput("CHK1", enums, "models") tst.AssertTrue(t, strings.Contains(out, "package models")) tst.AssertTrue(t, strings.Contains(out, "CHK1")) tst.AssertTrue(t, strings.Contains(out, "ColorRed")) tst.AssertTrue(t, strings.Contains(out, "ColorBlue")) tst.AssertTrue(t, strings.Contains(out, "ParseColor")) } func TestGenerateEnumSpecsSkipsGenFile(t *testing.T) { dir := t.TempDir() src := `package models type Color string // @enum:type const ( ColorRed Color = "red" ) ` writeTestFile(t, dir, "c.go", src) // generated file in same dir - should be filtered out gensrc := `package models type ShouldBeIgnored string // @enum:type ` writeTestFile(t, dir, "ignored_gen.go", gensrc) out, _, _, err := _generateEnumSpecs(dir, filepath.Join(dir, "enum_gen.go"), "N/A", false, false) tst.AssertNoErr(t, err) tst.AssertTrue(t, strings.Contains(out, "ColorRed")) tst.AssertFalse(t, strings.Contains(out, "ShouldBeIgnored")) }