Add tests for swagger datasource generation#6219
Conversation
Add unit tests for the `mindev datasource generate` command covering
Swagger v2 datasource generation and expected failures.
The new tests cover:
- single endpoints without parameters
- path parameters
- POST/PUT body handling
- multiple endpoints
- invalid or empty Swagger input
- unsupported header/body parameters
- duplicate generated operation names
This also fixes a few generator issues found while adding coverage:
- preserve templated path params instead of URL-escaping `{...}`
- reject duplicate generated operation names
- reject unsupported parameter locations with clear errors
- encode required input schema fields in a structpb-compatible form
Signed-off-by: Prince Oforh Asiedu <prince14asiedu@gmail.com>
There was a problem hiding this comment.
Pull request overview
Adds unit test coverage for mindev datasource generate (Swagger v2 → Minder REST datasource YAML), and updates the generator to handle several edge/error cases more explicitly.
Changes:
- Added table-driven tests validating generated datasource YAML by unmarshaling into the datasource proto.
- Updated path joining to preserve templated Swagger paths (e.g.
/users/{id}) and added validation for unsupported parameter locations. - Added generator safeguards for duplicate generated operation names and fixed
requiredencoding to bestructpb-compatible.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| cmd/dev/app/datasource/generate.go | Adjusts datasource generation logic (path joining, parameter validation, duplicate op-name detection, required-field encoding). |
| cmd/dev/app/datasource/generate_test.go | Introduces success/error-path tests for Swagger datasource generation and CLI parsing behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func testSwagger(paths map[string]spec.PathItem) *spec.Swagger { | ||
| return &spec.Swagger{ | ||
| SwaggerProps: spec.SwaggerProps{ | ||
| Swagger: "2.0", | ||
| BasePath: "/api/v1", | ||
| Info: &spec.Info{ | ||
| InfoProps: spec.InfoProps{ | ||
| Title: "users-api", | ||
| }, | ||
| }, | ||
| Paths: &spec.Paths{ | ||
| Paths: paths, | ||
| }, | ||
| }, | ||
| } |
There was a problem hiding this comment.
The new tests validate generated REST defs but never assert the top-level DataSource.Name, even though it’s derived from swagger.Info.Title and is part of the generated YAML contract. Adding an assertion for ds.GetName() would strengthen coverage and would also catch that swaggerTitleToDataSourceName currently replaces a fully-valid title (e.g. users-api) with "-" due to using an anchored regexp with ReplaceAllString.
Signed-off-by: Prince Oforh Asiedu <prince14asiedu@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Prince Oforh Asiedu <prince14asiedu@gmail.com>
…andling in tests Signed-off-by: Prince Oforh Asiedu <prince14asiedu@gmail.com>
evankanderson
left a comment
There was a problem hiding this comment.
This looks very good overall! Thanks for adding the coverage (which seems to have motivated some improvements in the actual tool as well)!
| return &ds, nil | ||
| } | ||
|
|
||
| func testSwagger(paths map[string]spec.PathItem) *spec.Swagger { |
There was a problem hiding this comment.
Another option here would be to create a testdata directory, and store actual sample Swagger JSON documents in the directory. I don't feel strongly about that approach, but it has the advantage of being able to add real-world examples with less Go typing boilerplate.
| assert: func(t *testing.T, ds *minderv1.DataSource) { | ||
| t.Helper() | ||
| assert.Equal(t, "users-api", ds.GetName()) | ||
| require.NotNil(t, ds.GetRest()) | ||
| require.Len(t, ds.GetRest().GetDef(), 1) | ||
|
|
||
| def := ds.GetRest().GetDef()["get_users"] | ||
| require.NotNil(t, def) | ||
| assert.Equal(t, "GET", def.GetMethod()) | ||
| assert.Equal(t, "/api/v1/users", def.GetEndpoint()) | ||
| assert.Equal(t, "json", def.GetParse()) | ||
| assert.Empty(t, def.GetInputSchema().AsMap()) |
There was a problem hiding this comment.
I know we do this in a bunch of our tests, but this sort of helper function in a table test is a little bit of a "smell". In this case, you might look at using go-cmp or protocmp to allow specifying the expected result (with a side effect of getting nice-looking diffs when there's a problem).
Using prototext, this could be:
| assert: func(t *testing.T, ds *minderv1.DataSource) { | |
| t.Helper() | |
| assert.Equal(t, "users-api", ds.GetName()) | |
| require.NotNil(t, ds.GetRest()) | |
| require.Len(t, ds.GetRest().GetDef(), 1) | |
| def := ds.GetRest().GetDef()["get_users"] | |
| require.NotNil(t, def) | |
| assert.Equal(t, "GET", def.GetMethod()) | |
| assert.Equal(t, "/api/v1/users", def.GetEndpoint()) | |
| assert.Equal(t, "json", def.GetParse()) | |
| assert.Empty(t, def.GetInputSchema().AsMap()) | |
| expected: ` | |
| name: users-api | |
| rest [ | |
| def : { | |
| key: "get_users" | |
| value: { | |
| method: "GET" | |
| endpoint: "/api/v1/users" | |
| parse: "json" | |
| } | |
| } | |
| ] | |
| `, |
| "/users/id/": pathItem("GET", op()), | ||
| "/users/{id}": pathItem("GET", op(param("id", "path"))), | ||
| }), | ||
| wantErr: `duplicate generated operation name "get_users_id_"`, |
There was a problem hiding this comment.
❤️ wantErr as a string to test the error messages. Thanks!
| if out := cmd.OutOrStdout(); out != os.Stdout { | ||
| if f, ok := out.(*os.File); ok { | ||
| prev := os.Stdout | ||
| os.Stdout = f | ||
| defer func() { | ||
| os.Stdout = prev | ||
| }() | ||
| } | ||
| } |
There was a problem hiding this comment.
Rather than overwriting os.Stdout, let's pass the file from cmd.OutOrStdout to the writers, and use Command.SetOut() in the tests to override Stdout. This avoids mutating global state across tests run with t.Parallel().
There was a problem hiding this comment.
I think this is just adding an argument to writeDataSourceToFile
| func joinPaths(basepath, path string) string { | ||
| basepath = strings.TrimSuffix(basepath, "/") | ||
| path = strings.TrimPrefix(path, "/") | ||
| if path == "" { | ||
| return basepath | ||
| } | ||
|
|
||
| return basepath + "/" + path | ||
| } |
There was a problem hiding this comment.
url.JoinPath should do this. Is it not?
| if !slices.Contains([]string{"path", "query"}, p.In) { | ||
| return fmt.Errorf("unsupported parameter %q in %q", p.Name, p.In) |
There was a problem hiding this comment.
We could support e.g. headers, but we don't at the moment, right? It seems worth a comment to indicate whether this is a limitation of the generation tool, or a limit of the underlying datasource API.
| os.Stdout = stdoutFile | ||
| defer func() { | ||
| os.Stdout = originalStdout | ||
| }() |
There was a problem hiding this comment.
Prefer to use Command.SetOut here -- all you need is an io.Writer, so this could simply be a bytes.Buffer rather than needing any actual files at all. (and then you won't need the lock!)
| stdoutFile, err := os.CreateTemp(t.TempDir(), "stdout-*.yaml") | ||
| require.NoError(t, err) | ||
|
|
||
| stdoutMu.Lock() | ||
| defer stdoutMu.Unlock() | ||
|
|
||
| originalStdout := os.Stdout | ||
| os.Stdout = stdoutFile | ||
| defer func() { | ||
| os.Stdout = originalStdout | ||
| }() |
There was a problem hiding this comment.
Again, prefer Command.SetOut here.
evankanderson
left a comment
There was a problem hiding this comment.
Add a comment when this is ready for review; at a minimum, we should use Command.SetOut rather than overwriting os.Stdout, but you may want to review the other comments as well.
evankanderson
left a comment
There was a problem hiding this comment.
Marking as "Request changes" (for at least cmd.Stdout), so I can more easily track which PRs need maintainer attention again.
|
This PR needs additional information before we can continue. It is now marked as stale because it has been open for 30 days with no activity. Please provide the necessary details to continue or it will be closed in 30 days. |
|
@PrinceAsiedu -- are you likely to get back to this PR? |
Summary
Add coverage for
mindev datasource generateincmd/dev/app/datasource/generate_test.go.This exercises Swagger v2 datasource generation across the main success
paths and the expected error cases called out in #5300.
What changed
Follow-up fixes included
While writing the tests, a few issues in the generator surfaced and were fixed:
/users/{id}instead of emitting URL-escaped pathsstructpbCloses #5300