feat(secure_files): add FileExtension to SecureFile
What does this MR do?
I added FileExtension string with the json:"file_extension" tag to SecureFile.
GitLab sends file_extension on every endpoint that renders a secure file. GET /projects/:id/secure_files, GET /projects/:id/secure_files/:secure_file_id and POST /projects/:id/secure_files all present with Entities::Ci::SecureFile, and the struct did not carry the key, so the value was dropped on decode.
The entity is lib/api/entities/ci/secure_file.rb, where the exposure sits on line 15. It carries no condition: no if:, no feature flag and no license gate, so every response that renders the entity includes the key. The value comes from the model, which returns the file name's extension without the leading dot.
I used a plain string rather than a pointer. The model returns nil when the name has no extension, a JSON null decodes into the zero value without error, and every other string field on this struct is declared the same way.
Documentation: https://docs.gitlab.com/api/secure_files/. The example response bodies on that page were never updated with this key, so the entity linked above is the evidence that it is sent.
I found this while building gitlab-mcp-server, an MCP server that exposes GitLab through this SDK, by comparing the SDK structs against the Grape entities reported by a booted gitlab/gitlab-ee 19.3.1-ee.
Is this a breaking change?
No. This adds one field to a response struct, so every existing caller still compiles and behaves exactly as before.
How was this tested?
I extended the three existing tests rather than adding new ones, because those are exactly the three endpoints that render the entity: TestSecureFiles_ListProjectSecureFiles, TestSecureFiles_ShowSecureFileDetails and TestSecureFiles_CreateSecureFile. Each response fixture now carries file_extension (jks for the .jks files, cer for the .cer one) and each expected struct asserts the decoded value, including the second element of the list, so the field is covered on a slice decode as well as on a single object.
I checked that the assertions are load bearing by changing the json tag to a name the fixtures do not contain and running the package: all three tests fail on FileExtension, expecting jks or cer and getting the empty string. With the tag restored they pass again.
go build ./..., go vet ./..., golangci-lint run ./... and go test -race . are all clean.
Related to #2300