diff --git a/internal/generator/e2e_combinations_test.go b/internal/generator/e2e_combinations_test.go index edbcd9a..3ad7555 100644 --- a/internal/generator/e2e_combinations_test.go +++ b/internal/generator/e2e_combinations_test.go @@ -77,6 +77,8 @@ func TestE2E_Combinations(t *testing.T) { {"operations.go", "params.Either", "a union-typed parameter"}, {"errors.go", "func (e *ProblemResponse) Error() string", "a typed error wrapper"}, {"errors.go", "e.Detail.Detail", "a message field found by its conventional name"}, + {"errors.go", "func parseFailureResponse", "a second error shape in one package"}, + {"errors.go", "func parseListAlertsResponse422Error", "an error body with no name of its own"}, {"types.go", "Name string `json:\"name\"`", "a property two allOf entries declare, required by one"}, } { if !containsCollapsed(byName[want.file], want.decl) { @@ -99,3 +101,52 @@ func TestE2E_Combinations(t *testing.T) { func containsCollapsed(haystack, needle string) bool { return strings.Contains(strings.Join(strings.Fields(haystack), " "), strings.Join(strings.Fields(needle), " ")) } + +// TestGenerationIsDeterministic generates one spec twice and compares the bytes. +// Generated clients are committed and reviewed, so output that shifts between +// runs shows up as churn in a diff nobody made, and the usual cause is a map +// iterated somewhere on the way out. +func TestGenerationIsDeterministic(t *testing.T) { + specPath := filepath.Join(projectRoot(), "testdata", "combinations.yaml") + + generate := func() map[string]string { + t.Helper() + result, err := parser.Parse(specPath, parser.Config{}) + if err != nil { + t.Fatalf("Parse: %v", err) + } + pkg, err := analyzer.New(result.Model).Analyze("combinations") + if err != nil { + t.Fatalf("Analyze: %v", err) + } + gen, err := New(pkg) + if err != nil { + t.Fatalf("New: %v", err) + } + files, err := gen.Generate() + if err != nil { + t.Fatalf("Generate: %v", err) + } + out := make(map[string]string, len(files)) + for _, f := range files { + out[f.Name] = string(f.Content) + } + return out + } + + first, second := generate(), generate() + + if len(first) != len(second) { + t.Fatalf("file counts differ: %d and %d", len(first), len(second)) + } + for name, content := range first { + other, ok := second[name] + if !ok { + t.Errorf("%s was generated once and not the other time", name) + continue + } + if content != other { + t.Errorf("%s differs between two runs of the same spec", name) + } + } +} diff --git a/testdata/combinations.yaml b/testdata/combinations.yaml index 7a5bfcc..56acc20 100644 --- a/testdata/combinations.yaml +++ b/testdata/combinations.yaml @@ -95,6 +95,15 @@ paths: content: application/json: schema: { type: array, items: { $ref: "#/components/schemas/Alert" } } + "422": + description: An error body with no name of its own. + content: + application/json: + schema: { type: array, items: { type: string } } + default: + description: A second named error shape, so the package holds more than one. + content: + application/json: { schema: { $ref: "#/components/schemas/Failure" } } /reports: get: operationId: listReports @@ -213,6 +222,11 @@ components: "^x-": { type: string } refined: { $ref: "#/components/schemas/Refined" } required: [id] + Failure: + type: object + properties: + code: { type: integer } + message: { type: string } Problem: type: object properties: