Files
frankenphp/internal/extgen/docs_test.go
2025-08-15 00:22:44 +02:00

386 lines
9.2 KiB
Go

package extgen
import (
"github.com/stretchr/testify/require"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDocumentationGenerator_Generate(t *testing.T) {
tests := []struct {
name string
generator *Generator
expectError bool
}{
{
name: "simple extension with functions",
generator: &Generator{
BaseName: "testextension",
BuildDir: "",
Functions: []phpFunction{
{
Name: "greet",
ReturnType: phpString,
Params: []phpParameter{
{Name: "name", PhpType: phpString},
},
Signature: "greet(string $name): string",
},
},
Classes: []phpClass{},
},
expectError: false,
},
{
name: "extension with classes",
generator: &Generator{
BaseName: "classextension",
BuildDir: "",
Functions: []phpFunction{},
Classes: []phpClass{
{
Name: "TestClass",
Properties: []phpClassProperty{
{Name: "name", PhpType: phpString},
{Name: "count", PhpType: phpInt, IsNullable: true},
},
},
},
},
expectError: false,
},
{
name: "extension with both functions and classes",
generator: &Generator{
BaseName: "fullextension",
BuildDir: "",
Functions: []phpFunction{
{
Name: "calculate",
ReturnType: phpInt,
IsReturnNullable: true,
Params: []phpParameter{
{Name: "base", PhpType: phpInt},
{Name: "multiplier", PhpType: phpInt, HasDefault: true, DefaultValue: "2", IsNullable: true},
},
Signature: "calculate(int $base, ?int $multiplier = 2): ?int",
},
},
Classes: []phpClass{
{
Name: "Calculator",
Properties: []phpClassProperty{
{Name: "precision", PhpType: phpInt},
},
},
},
},
expectError: false,
},
{
name: "empty extension",
generator: &Generator{
BaseName: "emptyextension",
BuildDir: "",
Functions: []phpFunction{},
Classes: []phpClass{},
},
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir := t.TempDir()
tt.generator.BuildDir = tempDir
docGen := &DocumentationGenerator{
generator: tt.generator,
}
err := docGen.generate()
if tt.expectError {
assert.Error(t, err, "generate() expected error but got none")
return
}
assert.NoError(t, err, "generate() unexpected error")
readmePath := filepath.Join(tempDir, "README.md")
require.FileExists(t, readmePath)
content, err := os.ReadFile(readmePath)
require.NoError(t, err, "Failed to read generated README.md")
contentStr := string(content)
assert.Contains(t, contentStr, "# "+tt.generator.BaseName+" Extension", "README should contain extension title")
assert.Contains(t, contentStr, "Auto-generated PHP extension from Go code.", "README should contain description")
if len(tt.generator.Functions) > 0 {
assert.Contains(t, contentStr, "## Functions", "README should contain functions section when functions exist")
for _, fn := range tt.generator.Functions {
assert.Contains(t, contentStr, "### "+fn.Name, "README should contain function %s", fn.Name)
assert.Contains(t, contentStr, fn.Signature, "README should contain function signature for %s", fn.Name)
}
}
if len(tt.generator.Classes) > 0 {
assert.Contains(t, contentStr, "## Classes", "README should contain classes section when classes exist")
for _, class := range tt.generator.Classes {
assert.Contains(t, contentStr, "### "+class.Name, "README should contain class %s", class.Name)
}
}
})
}
}
func TestDocumentationGenerator_GenerateMarkdown(t *testing.T) {
tests := []struct {
name string
generator *Generator
contains []string
notContains []string
}{
{
name: "function with parameters",
generator: &Generator{
BaseName: "testextension",
Functions: []phpFunction{
{
Name: "processData",
ReturnType: phpArray,
Params: []phpParameter{
{Name: "data", PhpType: phpString},
{Name: "options", PhpType: phpArray, IsNullable: true},
{Name: "count", PhpType: phpInt, HasDefault: true, DefaultValue: "10"},
},
Signature: "processData(string $data, ?array $options, int $count = 10): array",
},
},
Classes: []phpClass{},
},
contains: []string{
"# testextension Extension",
"## Functions",
"### processData",
"**Parameters:**",
"- `data` (string)",
"- `options` (array) (nullable)",
"- `count` (int) (default: 10)",
"**Returns:** array",
},
},
{
name: "nullable return type",
generator: &Generator{
BaseName: "nullableext",
Functions: []phpFunction{
{
Name: "maybeGetValue",
ReturnType: phpString,
IsReturnNullable: true,
Params: []phpParameter{},
Signature: "maybeGetValue(): ?string",
},
},
Classes: []phpClass{},
},
contains: []string{
"**Returns:** string (nullable)",
},
},
{
name: "class with properties",
generator: &Generator{
BaseName: "classext",
Functions: []phpFunction{},
Classes: []phpClass{
{
Name: "DataProcessor",
Properties: []phpClassProperty{
{Name: "name", PhpType: phpString},
{Name: "config", PhpType: phpArray, IsNullable: true},
{Name: "enabled", PhpType: phpBool},
},
},
},
},
contains: []string{
"## Classes",
"### DataProcessor",
"**Properties:**",
"- `name`: string",
"- `config`: array (nullable)",
"- `enabled`: bool",
},
},
{
name: "extension with no functions or classes",
generator: &Generator{
BaseName: "emptyext",
Functions: []phpFunction{},
Classes: []phpClass{},
},
contains: []string{
"# emptyext Extension",
"Auto-generated PHP extension from Go code.",
},
notContains: []string{
"## Functions",
"## Classes",
},
},
{
name: "function with no parameters",
generator: &Generator{
BaseName: "noparamext",
Functions: []phpFunction{
{
Name: "getCurrentTime",
ReturnType: phpInt,
Params: []phpParameter{},
Signature: "getCurrentTime(): int",
},
},
Classes: []phpClass{},
},
contains: []string{
"### getCurrentTime",
"**Returns:** int",
},
notContains: []string{
"**Parameters:**",
},
},
{
name: "class with no properties",
generator: &Generator{
BaseName: "nopropsext",
Functions: []phpFunction{},
Classes: []phpClass{
{
Name: "EmptyClass",
Properties: []phpClassProperty{},
},
},
},
contains: []string{
"### EmptyClass",
},
notContains: []string{
"**Properties:**",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
docGen := &DocumentationGenerator{
generator: tt.generator,
}
result, err := docGen.generateMarkdown()
if !assert.NoError(t, err, "generateMarkdown() unexpected error") {
return
}
for _, expected := range tt.contains {
assert.Contains(t, result, expected, "generateMarkdown() should contain '%s'", expected)
}
for _, notExpected := range tt.notContains {
assert.NotContains(t, result, notExpected, "generateMarkdown() should NOT contain '%s'", notExpected)
}
})
}
}
func TestDocumentationGenerator_Generate_InvalidDirectory(t *testing.T) {
generator := &Generator{
BaseName: "test",
BuildDir: "/nonexistent/directory",
Functions: []phpFunction{},
Classes: []phpClass{},
}
docGen := &DocumentationGenerator{
generator: generator,
}
err := docGen.generate()
assert.Error(t, err, "generate() expected error for invalid directory but got none")
}
func TestDocumentationGenerator_TemplateError(t *testing.T) {
generator := &Generator{
BaseName: "test",
Functions: []phpFunction{
{
Name: "test",
ReturnType: phpString,
Signature: "test(): string",
},
},
Classes: []phpClass{},
}
docGen := &DocumentationGenerator{
generator: generator,
}
result, err := docGen.generateMarkdown()
assert.NoError(t, err, "generateMarkdown() unexpected error")
assert.NotEmpty(t, result, "generateMarkdown() returned empty result")
}
func BenchmarkDocumentationGenerator_GenerateMarkdown(b *testing.B) {
generator := &Generator{
BaseName: "benchext",
Functions: []phpFunction{
{
Name: "function1",
ReturnType: phpString,
Params: []phpParameter{
{Name: "param1", PhpType: phpString},
{Name: "param2", PhpType: phpInt, HasDefault: true, DefaultValue: "0"},
},
Signature: "function1(string $param1, int $param2 = 0): string",
},
{
Name: "function2",
ReturnType: phpArray,
IsReturnNullable: true,
Params: []phpParameter{
{Name: "data", PhpType: phpArray, IsNullable: true},
},
Signature: "function2(?array $data): ?array",
},
},
Classes: []phpClass{
{
Name: "TestClass",
Properties: []phpClassProperty{
{Name: "prop1", PhpType: phpString},
{Name: "prop2", PhpType: phpInt, IsNullable: true},
},
},
},
}
docGen := &DocumentationGenerator{
generator: generator,
}
for b.Loop() {
_, err := docGen.generateMarkdown()
assert.NoError(b, err)
}
}