[🤖] Add Unit-Tests
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m34s

This commit is contained in:
2026-04-27 10:46:08 +02:00
parent dad0e3240d
commit 02d6894ec6
116 changed files with 18795 additions and 1 deletions
+160
View File
@@ -0,0 +1,160 @@
package bfcodegen
import (
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
"os"
"path/filepath"
"strings"
"testing"
)
func TestProcessCSIDFileSimple(t *testing.T) {
dir := t.TempDir()
src := `package mymodels
type UserID string // @csid:type [USR]
type OrderID string // @csid:type [ORD]
`
fp := writeTestFile(t, dir, "models.go", src)
ids, pkg, err := processCSIDFile(dir, fp, false)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, pkg, "mymodels")
tst.AssertEqual(t, len(ids), 2)
tst.AssertEqual(t, ids[0].Name, "UserID")
tst.AssertEqual(t, ids[0].Prefix, "USR")
tst.AssertEqual(t, ids[1].Name, "OrderID")
tst.AssertEqual(t, ids[1].Prefix, "ORD")
tst.AssertEqual(t, ids[0].FileRelative, "models.go")
}
func TestProcessCSIDFilePrefixMustBeUppercase(t *testing.T) {
dir := t.TempDir()
// lowercase prefix should not match the regex (only [A-Z0-9]{3})
src := `package x
type FooID string // @csid:type [usr]
`
fp := writeTestFile(t, dir, "x.go", src)
ids, pkg, err := processCSIDFile(dir, fp, false)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, pkg, "x")
tst.AssertEqual(t, len(ids), 0)
}
func TestProcessCSIDFileGeneratedHeaderSkipped(t *testing.T) {
dir := t.TempDir()
src := `// Code generated by csid-generate.go DO NOT EDIT.
package x
type SkipMeID string // @csid:type [SKP]
`
fp := writeTestFile(t, dir, "skip.go", src)
ids, pkg, err := processCSIDFile(dir, fp, false)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, pkg, "")
tst.AssertEqual(t, len(ids), 0)
}
func TestGenerateCharsetIDSpecsEndToEnd(t *testing.T) {
dir := t.TempDir()
src1 := `package models
type EntityID string // @csid:type [ENT]
type UserID string // @csid:type [USR]
`
writeTestFile(t, dir, "a_models.go", src1)
src2 := `package models
type OrderID string // @csid:type [ORD]
`
writeTestFile(t, dir, "b_models.go", src2)
dest := filepath.Join(dir, "csid_gen.go")
err := GenerateCharsetIDSpecs(dir, dest, CSIDGenOptions{DebugOutput: langext.PFalse})
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, "ChecksumCharsetIDGenerator"))
tst.AssertTrue(t, strings.Contains(outStr, "func NewUserID()"))
tst.AssertTrue(t, strings.Contains(outStr, "func NewOrderID()"))
tst.AssertTrue(t, strings.Contains(outStr, "func NewEntityID()"))
tst.AssertTrue(t, strings.Contains(outStr, `prefixUserID`) && strings.Contains(outStr, `"USR"`))
tst.AssertTrue(t, strings.Contains(outStr, `prefixOrderID`) && strings.Contains(outStr, `"ORD"`))
tst.AssertTrue(t, strings.Contains(outStr, `prefixEntityID`) && strings.Contains(outStr, `"ENT"`))
}
func TestGenerateCharsetIDSpecsIdempotentWhenUnchanged(t *testing.T) {
dir := t.TempDir()
src := `package models
type SomeID string // @csid:type [SOM]
`
writeTestFile(t, dir, "models.go", src)
dest := filepath.Join(dir, "csid_gen.go")
err := GenerateCharsetIDSpecs(dir, dest, CSIDGenOptions{})
tst.AssertNoErr(t, err)
content1, err := os.ReadFile(dest)
tst.AssertNoErr(t, err)
err = GenerateCharsetIDSpecs(dir, dest, CSIDGenOptions{})
tst.AssertNoErr(t, err)
content2, err := os.ReadFile(dest)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, string(content1), string(content2))
}
func TestGenerateCharsetIDSpecsErrorsWithoutPackage(t *testing.T) {
dir := t.TempDir()
src := `// Code generated by csid-generate.go DO NOT EDIT.
package x
type SkippedID string // @csid:type [SKP]
`
writeTestFile(t, dir, "z.go", src)
dest := filepath.Join(dir, "csid_gen.go")
err := GenerateCharsetIDSpecs(dir, dest, CSIDGenOptions{})
tst.AssertTrue(t, err != nil)
}
func TestGenerateCharsetIDSpecsMissingDir(t *testing.T) {
dir := filepath.Join(t.TempDir(), "definitely-missing")
err := GenerateCharsetIDSpecs(dir, filepath.Join(dir, "csid_gen.go"), CSIDGenOptions{})
tst.AssertTrue(t, err != nil)
}
func TestFmtCSIDOutputContainsAllNames(t *testing.T) {
ids := []CSIDDef{
{File: "a.go", FileRelative: "a.go", Name: "AlphaID", Prefix: "ALP"},
{File: "b.go", FileRelative: "b.go", Name: "BetaID", Prefix: "BET"},
}
out := fmtCSIDOutput("CHK_XYZ", ids, "models")
tst.AssertTrue(t, strings.Contains(out, "package models"))
tst.AssertTrue(t, strings.Contains(out, "CHK_XYZ"))
tst.AssertTrue(t, strings.Contains(out, "AlphaID"))
tst.AssertTrue(t, strings.Contains(out, "BetaID"))
tst.AssertTrue(t, strings.Contains(out, `prefixAlphaID`) && strings.Contains(out, `"ALP"`))
tst.AssertTrue(t, strings.Contains(out, `prefixBetaID`) && strings.Contains(out, `"BET"`))
}
+369
View File
@@ -0,0 +1,369 @@
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"))
}
+209
View File
@@ -0,0 +1,209 @@
package bfcodegen
import (
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
"os"
"path/filepath"
"strings"
"testing"
)
func writeTestFile(t *testing.T, dir string, name string, content string) string {
t.Helper()
p := filepath.Join(dir, name)
err := os.WriteFile(p, []byte(content), 0o644)
tst.AssertNoErr(t, err)
return p
}
func TestProcessIDFileSimple(t *testing.T) {
dir := t.TempDir()
src := `package mymodels
type UserID string // @id:type
type OrderID string // @id:type
`
fp := writeTestFile(t, dir, "models.go", src)
ids, pkg, err := processIDFile(dir, fp, false)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, pkg, "mymodels")
tst.AssertEqual(t, len(ids), 2)
tst.AssertEqual(t, ids[0].Name, "UserID")
tst.AssertEqual(t, ids[1].Name, "OrderID")
tst.AssertEqual(t, ids[0].FileRelative, "models.go")
}
func TestProcessIDFileNoMatches(t *testing.T) {
dir := t.TempDir()
src := `package x
type Foo string
type Bar int
type Baz string // not the right marker
`
fp := writeTestFile(t, dir, "x.go", src)
ids, pkg, err := processIDFile(dir, fp, false)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, pkg, "x")
tst.AssertEqual(t, len(ids), 0)
}
func TestProcessIDFileGeneratedHeaderSkipped(t *testing.T) {
dir := t.TempDir()
src := `// Code generated by id-generate.go DO NOT EDIT.
package x
type SkipMeID string // @id:type
`
fp := writeTestFile(t, dir, "skip.go", src)
ids, pkg, err := processIDFile(dir, fp, false)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, pkg, "")
tst.AssertEqual(t, len(ids), 0)
}
func TestProcessIDFileMissingFile(t *testing.T) {
_, _, err := processIDFile(t.TempDir(), filepath.Join(t.TempDir(), "does_not_exist.go"), false)
tst.AssertTrue(t, err != nil)
}
func TestGenerateIDSpecsEndToEnd(t *testing.T) {
dir := t.TempDir()
src1 := `package models
type UserID string // @id:type
type AnyID string // @id:type
`
writeTestFile(t, dir, "a_models.go", src1)
src2 := `package models
type OrderID string // @id:type
`
writeTestFile(t, dir, "b_models.go", src2)
dest := filepath.Join(dir, "id_gen.go")
err := GenerateIDSpecs(dir, dest, IDGenOptions{DebugOutput: langext.PFalse})
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, "ChecksumIDGenerator"))
tst.AssertTrue(t, strings.Contains(outStr, "func NewUserID()"))
tst.AssertTrue(t, strings.Contains(outStr, "func NewOrderID()"))
tst.AssertTrue(t, strings.Contains(outStr, "func NewAnyID()"))
tst.AssertTrue(t, strings.Contains(outStr, "AsAny()"))
}
func TestGenerateIDSpecsIdempotentWhenUnchanged(t *testing.T) {
dir := t.TempDir()
src := `package models
type SomeID string // @id:type
`
writeTestFile(t, dir, "models.go", src)
dest := filepath.Join(dir, "id_gen.go")
err := GenerateIDSpecs(dir, dest, IDGenOptions{})
tst.AssertNoErr(t, err)
stat1, err := os.Stat(dest)
tst.AssertNoErr(t, err)
content1, err := os.ReadFile(dest)
tst.AssertNoErr(t, err)
err = GenerateIDSpecs(dir, dest, IDGenOptions{})
tst.AssertNoErr(t, err)
stat2, err := os.Stat(dest)
tst.AssertNoErr(t, err)
content2, err := os.ReadFile(dest)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, stat1.ModTime().Equal(stat2.ModTime()), true)
tst.AssertEqual(t, string(content1), string(content2))
}
func TestGenerateIDSpecsRegeneratesAfterChange(t *testing.T) {
dir := t.TempDir()
src := `package models
type FirstID string // @id:type
`
fp := writeTestFile(t, dir, "models.go", src)
dest := filepath.Join(dir, "id_gen.go")
err := GenerateIDSpecs(dir, dest, IDGenOptions{})
tst.AssertNoErr(t, err)
content1, err := os.ReadFile(dest)
tst.AssertNoErr(t, err)
tst.AssertTrue(t, strings.Contains(string(content1), "FirstID"))
tst.AssertFalse(t, strings.Contains(string(content1), "SecondID"))
src2 := `package models
type FirstID string // @id:type
type SecondID string // @id:type
`
err = os.WriteFile(fp, []byte(src2), 0o644)
tst.AssertNoErr(t, err)
err = GenerateIDSpecs(dir, dest, IDGenOptions{})
tst.AssertNoErr(t, err)
content2, err := os.ReadFile(dest)
tst.AssertNoErr(t, err)
tst.AssertTrue(t, strings.Contains(string(content2), "SecondID"))
}
func TestGenerateIDSpecsErrorsWithoutPackage(t *testing.T) {
dir := t.TempDir()
src := `// Code generated by id-generate.go DO NOT EDIT.
package x
type SkippedID string // @id:type
`
writeTestFile(t, dir, "z.go", src)
dest := filepath.Join(dir, "id_gen.go")
err := GenerateIDSpecs(dir, dest, IDGenOptions{})
tst.AssertTrue(t, err != nil)
}
func TestGenerateIDSpecsMissingDir(t *testing.T) {
dir := filepath.Join(t.TempDir(), "definitely-missing")
err := GenerateIDSpecs(dir, filepath.Join(dir, "id_gen.go"), IDGenOptions{})
tst.AssertTrue(t, err != nil)
}
func TestFmtIDOutputContainsAllNames(t *testing.T) {
ids := []IDDef{
{File: "a.go", FileRelative: "a.go", Name: "AlphaID"},
{File: "b.go", FileRelative: "b.go", Name: "BetaID"},
}
out := fmtIDOutput("CHK_ABC", ids, "models")
tst.AssertTrue(t, strings.Contains(out, "package models"))
tst.AssertTrue(t, strings.Contains(out, "CHK_ABC"))
tst.AssertTrue(t, strings.Contains(out, "AlphaID"))
tst.AssertTrue(t, strings.Contains(out, "BetaID"))
}