From 8bb59b71cb7330237ffed43e89b01e29cfe49128 Mon Sep 17 00:00:00 2001 From: Sharon Katz <121527325+sharon-fdm@users.noreply.github.com> Date: Thu, 21 May 2026 11:09:19 -0400 Subject: [PATCH] Fix List certificate templates API docs: parameter name is fleet_id, not fleet (#45969) (#45978) Closes #45969 ## Summary The `List certificate templates` API endpoint returned `null` for certificates because the **API docs documented the wrong query parameter name**. The docs said `fleet` but the code accepts `fleet_id` (or the deprecated `team_id`). Customers following the docs used `?fleet=11`, which was silently ignored, causing the endpoint to default to team 0 (unassigned) -- which typically has no certificates. - **Docs fix**: Changed the parameter name from `fleet` (string) to `fleet_id` (integer) in the REST API docs, matching how all other list endpoints document this parameter. - **API quality fix**: Initialize the `templates` slice in `GetCertificateTemplatesByTeamID` so that when no templates exist, the JSON response returns `"certificates": []` instead of `"certificates": null`. ## Root cause In `docs/REST API/rest-api.md`, the "List certificate templates" endpoint documented the query parameter as `fleet` (string), but the request struct accepts `fleet_id` or `team_id`: ```go type listCertificateTemplatesRequest struct { TeamID uint `query:"team_id,optional" renameto:"fleet_id"` } ``` When the customer used `?fleet=11` (as documented), the parameter was unrecognized and silently ignored. The endpoint defaulted to `team_id=0` (unassigned), which had no certificates. The nil Go slice then serialized to JSON `null`. Credit to Andrey Kizimenko for identifying the docs mismatch. ## Changes - `docs/REST API/rest-api.md` -- Fix parameter name from `fleet` (string) to `fleet_id` (integer) - `server/datastore/mysql/certificate_templates.go:174` -- Initialize slice to avoid `null` in JSON - `server/datastore/mysql/certificate_templates_test.go:489` -- Add `require.NotNil` regression test ## Testing All tests run locally against a real MySQL (Docker) and Redis instance: | Test suite | Command | Result | |---|---|---| | Datastore integration (all certificate tests) | `MYSQL_TEST=1 go test -run TestCertificates ./server/datastore/mysql/...` | 11 suites, 33 subtests, all PASS | | Service unit tests | `go test -run "TestCreateCertificateTemplate\|TestApplyCertificateTemplateSpecs\|..."` | 4 suites, all PASS | | Enterprise integration (full HTTP) | `MYSQL_TEST=1 REDIS_TEST=1 go test -run "TestIntegrationsEnterprise/TestCertificatesSpecs"` | PASS | | Enterprise integration (team delete) | `MYSQL_TEST=1 REDIS_TEST=1 go test -run "TestIntegrationsEnterprise/TestDeleteTeamCertificateTemplates"` | PASS | | Static analysis | `go build`, `go vet` | Clean | Andrey's reproduction confirmed via screenshots: - `?fleet_id=11` returns certificates correctly - `?fleet=11` (the documented param) returns `null` -- the bug - No param returns results when "unassigned" team has certificates ## QA steps 1. Follow the API docs to list certificate templates using `?fleet_id=` 2. Verify the response contains `"certificates": [...]` with the correct data 3. Call without `fleet_id` and verify `"certificates": []` (not `null`) for a team with no templates ## Summary by CodeRabbit * **Bug Fixes** * Fixed the "List certificate templates" API documentation with the correct query parameter name, enabling proper filtering of results. [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45978?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) --- changes/45969-list-certificate-templates-null | 1 + docs/REST API/rest-api.md | 2 +- server/datastore/mysql/certificate_templates.go | 2 +- server/datastore/mysql/certificate_templates_test.go | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changes/45969-list-certificate-templates-null diff --git a/changes/45969-list-certificate-templates-null b/changes/45969-list-certificate-templates-null new file mode 100644 index 0000000000..993d2c7f7e --- /dev/null +++ b/changes/45969-list-certificate-templates-null @@ -0,0 +1 @@ +- Fixed `List certificate templates` API docs: query parameter was incorrectly documented as `fleet` instead of `fleet_id`, causing the parameter to be silently ignored and returning no results. diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index 8e9ba2fc9d..88d47dc3ae 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -936,7 +936,7 @@ List certificate added to Fleet. Currently, they can only be added via GitOps. | Name | Type | In | Description | | ----------| ------- | ---- | -------------------------------------------------------------- | -| fleet | string | query | _Available in Fleet Premium_. The fleet ID to filter profiles. | +| fleet_id | integer | query | _Available in Fleet Premium_. The fleet ID to filter certificate templates. | | page | integer | query | Page number of the results to fetch. | | per_page | integer | query | Results per page. | | order_key | string | query | What to order results by. Allowed field is `id`. | diff --git a/server/datastore/mysql/certificate_templates.go b/server/datastore/mysql/certificate_templates.go index 70db9d8ead..d803163b05 100644 --- a/server/datastore/mysql/certificate_templates.go +++ b/server/datastore/mysql/certificate_templates.go @@ -171,7 +171,7 @@ func (ds *Datastore) GetCertificateTemplatesByTeamID(ctx context.Context, teamID return nil, nil, ctxerr.Wrap(ctx, err, "apply list options") } - var templates []*fleet.CertificateTemplateResponseSummary + templates := []*fleet.CertificateTemplateResponseSummary{} if err := sqlx.SelectContext(ctx, ds.reader(ctx), &templates, stmtPaged, args...); err != nil { return nil, nil, ctxerr.Wrap(ctx, err, "getting certificate_templates by team_id") } diff --git a/server/datastore/mysql/certificate_templates_test.go b/server/datastore/mysql/certificate_templates_test.go index bf17a05201..bccd91f44d 100644 --- a/server/datastore/mysql/certificate_templates_test.go +++ b/server/datastore/mysql/certificate_templates_test.go @@ -488,6 +488,7 @@ func testGetCertificateTemplatesByTeamID(t *testing.T, ds *Datastore) { func(t *testing.T, ds *Datastore) { templates, _, err := ds.GetCertificateTemplatesByTeamID(ctx, 1, fleet.ListOptions{Page: 0, PerPage: 10}) require.NoError(t, err) + require.NotNil(t, templates, "expected empty slice, not nil (nil marshals to JSON null)") require.Len(t, templates, 0) }, },