Files
wehub-resource-sync e071084ebe
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:40:33 +08:00

419 lines
11 KiB
Go

package generate
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestToTitle(t *testing.T) {
tests := []struct {
in, want string
}{
{"order-service", "OrderService"},
{"task", "Task"},
{"inventory_item", "InventoryItem"},
{"hello world", "HelloWorld"},
{"a-b-c", "ABC"},
{"already", "Already"},
}
for _, tt := range tests {
if got := toTitle(tt.in); got != tt.want {
t.Errorf("toTitle(%q) = %q, want %q", tt.in, got, tt.want)
}
}
}
func TestProtoType(t *testing.T) {
tests := []struct {
in, want string
}{
{"string", "string"},
{"int64", "int64"},
{"int32", "int32"},
{"bool", "bool"},
{"float64", "double"},
{"unknown", "string"},
{"", "string"},
}
for _, tt := range tests {
if got := protoType(tt.in); got != tt.want {
t.Errorf("protoType(%q) = %q, want %q", tt.in, got, tt.want)
}
}
}
func TestFirstNonEmpty(t *testing.T) {
if got := firstNonEmpty("", "", "c"); got != "c" {
t.Errorf("got %q, want %q", got, "c")
}
if got := firstNonEmpty("a", "b"); got != "a" {
t.Errorf("got %q, want %q", got, "a")
}
if got := firstNonEmpty("", ""); got != "" {
t.Errorf("got %q, want %q", got, "")
}
}
func TestExtractJSON(t *testing.T) {
tests := []struct {
name, in, want string
}{
{
"fenced json",
"Here's the design:\n```json\n{\"services\": []}\n```\nDone.",
`{"services": []}`,
},
{
"fenced no lang",
"```\n{\"a\": 1}\n```",
`{"a": 1}`,
},
{
"raw json",
`some text {"key": "val"} trailing`,
`{"key": "val"}`,
},
{
"nested braces",
`{"a": {"b": 1}}`,
`{"a": {"b": 1}}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := extractJSON(tt.in)
if got != tt.want {
t.Errorf("extractJSON() = %q, want %q", got, tt.want)
}
})
}
}
func TestExtractCode(t *testing.T) {
tests := []struct {
name, in string
wantPrefix string
}{
{
"go fence",
"Here:\n```go\npackage handler\n\nfunc Foo() {}\n```\nDone.",
"package handler",
},
{
"generic fence",
"```\npackage main\n```",
"package main",
},
{
"raw code",
"Sure, here's the code:\npackage handler\n\ntype X struct{}",
"package handler",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := extractCode(tt.in)
if !strings.HasPrefix(got, tt.wantPrefix) {
t.Errorf("extractCode() = %q, want prefix %q", got, tt.wantPrefix)
}
})
}
}
func TestBuildProto(t *testing.T) {
svc := ServiceSpec{
Name: "task-service",
Description: "Manages tasks",
Fields: []FieldSpec{
{Name: "id", Type: "string", Description: "Task ID"},
{Name: "title", Type: "string", Description: "Task title"},
{Name: "done", Type: "bool", Description: "Completion status"},
{Name: "created", Type: "int64", Description: "Created timestamp"},
{Name: "updated", Type: "int64", Description: "Updated timestamp"},
},
Endpoints: []EndpointSpec{
{Name: "Create", Description: "Create a task"},
{Name: "Read", Description: "Get a task"},
{Name: "Update", Description: "Update a task"},
{Name: "Delete", Description: "Delete a task"},
{Name: "List", Description: "List tasks"},
{Name: "ToggleComplete", Description: "Toggle completion"},
},
}
proto := buildProto("taskservice", "TaskService", svc)
checks := []string{
`syntax = "proto3"`,
`package taskservice`,
`service TaskService`,
`rpc Create(CreateRequest) returns (CreateResponse)`,
`rpc ToggleComplete(ToggleCompleteRequest) returns (ToggleCompleteResponse)`,
`message TaskServiceRecord`,
`string title = 2`,
`bool done = 3`,
`message CreateRequest`,
`message ReadRequest`,
`message DeleteRequest`,
`message ListRequest`,
`message ToggleCompleteRequest`,
}
for _, c := range checks {
if !strings.Contains(proto, c) {
t.Errorf("buildProto() missing %q", c)
}
}
// Create should not include id, created, updated
createIdx := strings.Index(proto, "message CreateRequest")
createEnd := strings.Index(proto[createIdx:], "}")
createBlock := proto[createIdx : createIdx+createEnd]
for _, skip := range []string{"string id", "int64 created", "int64 updated"} {
if strings.Contains(createBlock, skip) {
t.Errorf("CreateRequest should not contain %q", skip)
}
}
}
func TestBuildMain(t *testing.T) {
// New naming: no -service suffix
main := buildMain("order", "Order")
checks := []string{
`"order/handler"`,
`pb "order/proto"`,
`micro.NewService("order"`,
`pb.RegisterOrderHandler`,
`handler.New()`,
}
for _, c := range checks {
if !strings.Contains(main, c) {
t.Errorf("buildMain(order) missing %q", c)
}
}
// Legacy naming: -service suffix stripped
main = buildMain("order-service", "OrderService")
checks = []string{
`"order-service/handler"`,
`pb "order-service/proto"`,
`micro.NewService("order"`,
`pb.RegisterOrderServiceHandler`,
`handler.New()`,
}
for _, c := range checks {
if !strings.Contains(main, c) {
t.Errorf("buildMain() missing %q", c)
}
}
}
func TestHandlerModifiedTracking(t *testing.T) {
dir := t.TempDir()
handlerDir := filepath.Join(dir, "handler")
os.MkdirAll(handlerDir, 0755)
handlerFile := filepath.Join(handlerDir, "test.go")
// No .micro file → not modified
os.WriteFile(handlerFile, []byte("package handler\n"), 0644)
if handlerModified(dir, handlerFile) {
t.Error("expected not modified when no .micro exists")
}
// Record hash → not modified
recordHandlerHash(dir, handlerFile)
if handlerModified(dir, handlerFile) {
t.Error("expected not modified after recording hash")
}
// Edit the file → modified
os.WriteFile(handlerFile, []byte("package handler\n\nfunc Foo() {}\n"), 0644)
if !handlerModified(dir, handlerFile) {
t.Error("expected modified after editing file")
}
// Re-record → not modified again
recordHandlerHash(dir, handlerFile)
if handlerModified(dir, handlerFile) {
t.Error("expected not modified after re-recording hash")
}
}
func TestMetaReadWrite(t *testing.T) {
dir := t.TempDir()
m := readMeta(dir)
if len(m) != 0 {
t.Error("expected empty meta for new dir")
}
m["handler_hash"] = "abc123"
m["version"] = "1"
writeMeta(dir, m)
m2 := readMeta(dir)
if m2["handler_hash"] != "abc123" || m2["version"] != "1" {
t.Errorf("readMeta() = %v, want handler_hash=abc123, version=1", m2)
}
}
func TestGenerateStructure(t *testing.T) {
dir := t.TempDir()
svcDir := filepath.Join(dir, "test-svc")
svc := ServiceSpec{
Name: "test-svc",
Description: "Test service",
Fields: []FieldSpec{
{Name: "id", Type: "string"},
{Name: "name", Type: "string"},
},
Endpoints: []EndpointSpec{
{Name: "Create"},
{Name: "Read"},
},
}
if err := generateStructure(svcDir, svc); err != nil {
t.Fatal(err)
}
// Check files exist
for _, f := range []string{
"proto/test-svc.proto",
"handler/test-svc.go",
"main.go",
"go.mod",
"Makefile",
".gitignore",
} {
if _, err := os.Stat(filepath.Join(svcDir, f)); err != nil {
t.Errorf("missing %s: %v", f, err)
}
}
// Check .micro was created with handler hash
meta := readMeta(svcDir)
if meta["handler_hash"] == "" {
t.Error("expected handler_hash in .micro after generateStructure")
}
// Run again — should not overwrite main.go
mainBefore, _ := os.ReadFile(filepath.Join(svcDir, "main.go"))
os.WriteFile(filepath.Join(svcDir, "main.go"), []byte("// user edited\n"), 0644)
if err := generateStructure(svcDir, svc); err != nil {
t.Fatal(err)
}
mainAfter, _ := os.ReadFile(filepath.Join(svcDir, "main.go"))
if string(mainAfter) == string(mainBefore) {
t.Error("expected main.go to keep user edit on re-run")
}
// Proto should be protected if user modified it
protoFile := filepath.Join(svcDir, "proto", "test-svc.proto")
os.WriteFile(protoFile, []byte("// user-edited proto\n"), 0644)
if err := generateStructure(svcDir, svc); err != nil {
t.Fatal(err)
}
protoAfter, _ := os.ReadFile(protoFile)
if string(protoAfter) != "// user-edited proto\n" {
t.Error("expected proto to be preserved after user edit")
}
// Proto should regenerate if NOT modified
recordFileHash(svcDir, "proto_hash", protoFile)
if err := generateStructure(svcDir, svc); err != nil {
t.Fatal(err)
}
protoAfter2, _ := os.ReadFile(protoFile)
_ = protoAfter2
}
func TestFileModified(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "test.txt")
os.WriteFile(f, []byte("original"), 0644)
// No hash → not modified
if fileModified(dir, "test_hash", f) {
t.Error("expected not modified with no saved hash")
}
recordFileHash(dir, "test_hash", f)
// Same content → not modified
if fileModified(dir, "test_hash", f) {
t.Error("expected not modified with matching hash")
}
// Changed content → modified
os.WriteFile(f, []byte("changed"), 0644)
if !fileModified(dir, "test_hash", f) {
t.Error("expected modified after content change")
}
}
func TestDiscoverExisting(t *testing.T) {
dir := t.TempDir()
// Empty directory → empty string
if got := discoverExisting(dir); got != "" {
t.Errorf("expected empty for empty dir, got %q", got)
}
// Non-service directory (no proto) → empty
os.MkdirAll(filepath.Join(dir, "not-a-service"), 0755)
if got := discoverExisting(dir); got != "" {
t.Errorf("expected empty for dir without proto, got %q", got)
}
// Create a real service directory with proto
svcDir := filepath.Join(dir, "order-service")
os.MkdirAll(filepath.Join(svcDir, "proto"), 0755)
os.WriteFile(filepath.Join(svcDir, "proto", "order-service.proto"),
[]byte("syntax = \"proto3\";\nservice OrderService {}"), 0644)
got := discoverExisting(dir)
if !strings.Contains(got, "order-service") {
t.Errorf("expected to find order-service, got %q", got)
}
if !strings.Contains(got, "OrderService") {
t.Errorf("expected to find proto content, got %q", got)
}
// Add a second service
svc2Dir := filepath.Join(dir, "user-service")
os.MkdirAll(filepath.Join(svc2Dir, "proto"), 0755)
os.WriteFile(filepath.Join(svc2Dir, "proto", "user-service.proto"),
[]byte("syntax = \"proto3\";\nservice UserService {}"), 0644)
got = discoverExisting(dir)
if !strings.Contains(got, "order-service") || !strings.Contains(got, "user-service") {
t.Errorf("expected both services, got %q", got)
}
}
func TestIsTruncated(t *testing.T) {
tests := []struct {
name string
code string
want bool
}{
{"complete", "package handler\n\nfunc New() *H { return &H{} }\n", false},
{"empty", "", true},
{"no closing brace", "package handler\n\nfunc Foo() {", true},
{"unbalanced", "package handler\n\nfunc Foo() {\n\tif true {", true},
{"balanced", "package handler\n\nfunc Foo() {\n\tif true {\n\t}\n}", false},
{"trailing whitespace ok", "package handler\n\ntype X struct{}\n\n", false},
{"mid-expression", "package handler\n\nfunc F() {\n\tx := 1 +", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isTruncated(tt.code); got != tt.want {
t.Errorf("isTruncated() = %v, want %v", got, tt.want)
}
})
}
}