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=<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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed the "List certificate templates" API documentation with the
correct query parameter name, enabling proper filtering of results.

<!-- review_stack_entry_start -->

[![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)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Sharon Katz
2026-05-21 11:09:19 -04:00
committed by GitHub
parent 98eaa260cb
commit 8bb59b71cb
4 changed files with 4 additions and 2 deletions
@@ -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.
+1 -1
View File
@@ -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`. |
@@ -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")
}
@@ -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)
},
},