Complete package for generating ocds structs (with extensions) in golang

This commit is contained in:
bwilliams committed 2026-10-01 14:50:07 +01:00
1 parent da726550d4
commit 35974fa070
47 files changed
+9861 -299

No files matched your search

+151
View File
@@ -0,0 +1,151 @@
package cli
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// run executes the command tree with args and returns stdout, stderr and the error.
func run(t *testing.T, args ...string) (string, string, error) {
t.Helper()
var stdout, stderr bytes.Buffer
cmd := NewRootCmd()
cmd.SetOut(&stdout)
cmd.SetErr(&stderr)
cmd.SetArgs(args)
err := cmd.Execute()
return stdout.String(), stderr.String(), err
}
func TestGenerate(t *testing.T) {
out := filepath.Join(t.TempDir(), "tenders", "ocds_gen.go")
if err := os.MkdirAll(filepath.Dir(out), 0o755); err != nil {
t.Fatal(err)
}
if _, stderr, err := run(t, "generate", "-c", "testdata/widgets.json", "-o", out); err != nil {
t.Fatalf("generate: %v\n%s", err, stderr)
}
raw, err := os.ReadFile(out)
if err != nil {
t.Fatal(err)
}
// gofmt aligns struct columns, so compare with whitespace collapsed.
src := strings.Join(strings.Fields(string(raw)), " ")
for _, want := range []string{
"package tenders", // derived from the output directory
"Date time.Time", // required date-time is not a pointer
"ID ID `json:\"id\"`", // ["string","integer"]
"Widgets []Widget", // extension field on a core type
"Count *int", // extension definition
"Releases []Release", // external $ref to the release schema
"Widgets (declared)", // header records resolution
"Orphan (dependency)",
} {
if !strings.Contains(src, want) {
t.Errorf("generated code is missing %q", want)
}
}
}
func TestGenerateStdoutNeedsPackage(t *testing.T) {
_, _, err := run(t, "generate", "-c", "testdata/core.json", "-o", "-")
if err == nil || !strings.Contains(err.Error(), "--package") {
t.Fatalf("want --package error, got %v", err)
}
stdout, _, err := run(t, "generate", "-c", "testdata/core.json", "-o", "-", "-p", "core")
if err != nil || !strings.HasPrefix(stdout, "// Code generated") {
t.Fatalf("stdout generation failed: %v", err)
}
}
func TestCheck(t *testing.T) {
out := filepath.Join(t.TempDir(), "ocds_gen.go")
args := []string{"generate", "-q", "-c", "testdata/core.json", "-o", out, "-p", "core"}
if _, _, err := run(t, append(args, "--check")...); err == nil || !strings.Contains(err.Error(), "does not exist") {
t.Fatalf("missing file: want error, got %v", err)
}
if _, _, err := run(t, args...); err != nil {
t.Fatal(err)
}
if _, _, err := run(t, append(args, "--check")...); err != nil {
t.Fatalf("fresh file: want no error, got %v", err)
}
f, _ := os.OpenFile(out, os.O_APPEND|os.O_WRONLY, 0)
f.WriteString("// hand edit\n")
f.Close()
if _, _, err := run(t, append(args, "--check")...); err == nil || !strings.Contains(err.Error(), "out of date") {
t.Fatalf("edited file: want out-of-date error, got %v", err)
}
}
func TestStrict(t *testing.T) {
out := filepath.Join(t.TempDir(), "ocds_gen.go")
args := []string{"generate", "-c", "testdata/widgets.json", "-o", out, "-p", "x"}
// Gadget is defined by the Orphan extension but nothing refers to it.
_, stderr, err := run(t, args...)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(stderr, "definition Gadget is never referenced") {
t.Errorf("want unreachable warning, got stderr:\n%s", stderr)
}
if _, _, err := run(t, append(args, "--strict")...); err == nil {
t.Fatal("--strict: want error")
}
if _, stderr, _ := run(t, append(args, "-q")...); stderr != "" {
t.Errorf("--quiet: want no stderr, got:\n%s", stderr)
}
}
func TestResolveJSON(t *testing.T) {
stdout, _, err := run(t, "resolve", "-c", "testdata/widgets.json", "--json")
if err != nil {
t.Fatal(err)
}
var rows []struct {
Name string `json:"name"`
Declared bool `json:"declared"`
}
if err := json.Unmarshal([]byte(stdout), &rows); err != nil {
t.Fatal(err)
}
if len(rows) != 2 || rows[0].Name != "Orphan" || rows[0].Declared || rows[1].Name != "Widgets" || !rows[1].Declared {
t.Fatalf("want [Orphan (dependency), Widgets (declared)], got %+v", rows)
}
}
func TestSchema(t *testing.T) {
stdout, _, err := run(t, "schema", "-q", "-c", "testdata/widgets.json")
if err != nil {
t.Fatal(err)
}
// Key order is preserved, and the extension's patch is merged in.
if !strings.Contains(stdout, `"widgets"`) || strings.Index(stdout, `"ocid"`) > strings.Index(stdout, `"tender"`) {
t.Fatalf("unexpected merged schema:\n%s", stdout)
}
if _, _, err := run(t, "schema", "--kind", "record", "-c", "testdata/core.json"); err == nil {
t.Fatal("--kind record: want error")
}
}
func TestPackageFromPath(t *testing.T) {
for in, want := range map[string]string{
"internal/tenders/ocds_gen.go": "tenders",
"profiles/uk/ocds_gen.go": "uk",
"pkg/eu-profile/x.go": "euprofile",
} {
got, err := packageFromPath(in)
if err != nil || got != want {
t.Errorf("packageFromPath(%q) = %q, %v; want %q", in, got, err, want)
}
}
if _, err := packageFromPath("pkg/func/x.go"); err == nil {
t.Error("keyword directory: want error")
}
}