Bug 2065171 - Migrate BugUserLastVisit REST resource to native Mojo API - #2743
Conversation
|
Reading API doc, I realized that => Fixed in "Bug 2065171 - Fix ids/include_fields precedence: query string wins over body" |
|
Pushed a follow-up commit: |
`_request_params->{ids} // []` made a missing ids param filter to nothing instead of returning every visited bug, since an empty arrayref is truthy. Legacy left $ids undef when the param is absent, skipping filter entirely. Return undef in that case matches legacy behavior. An empty array still filters to nothing.
_request_params duplicated the query-string/JSON-body merge logic. Now call a single shared Bugzilla::WebService::Util::merge_request_params helper instead, so it's a one-place change to drop later if query-string-on-POST support is ever removed.
_ids_from_request short-circuited to the path id whenever present, never consulting the merged
query-string/body params. Legacy's _retrieve_json_params merges non-GET request-body/query
params in *after* the path-derived params, so those win for POST. For GET, the path id still wins
(legacy's override step only ran for non-GET requests), so that precedence is unchanged.
Also switch from $self->param('id') (a truthiness check that also falls back to a same-named
query param) to $self->stash('id') (defined check, route-placeholder only). This fixes two more bugs:
- /bug_user_last_visit/0 was falling through to the no-ids branch since "0" is falsy
- a stray ?id=5 query parameter (distinct from ids) was being treated as if it were a path id
Covers: anonymous access requiring login, OPTIONS, POST via path id, POST via a JSON ids body, POST with a JSON body posted with no Content-Type header, GET via path id vs query-string ids precedence, GET via query-string ids, and GET with no ids returning every visited bug
merge_request_params collapsed every param to a scalar, which dropped all but the last value of a repeated key such as ?ids=1&ids=2. Callers now declare their list params, so a param's type no longer depends on how many times it was sent.
_request_params ran twice per request and merge_request_params read the body twice, so one POST slurped a file-backed request asset four times and decoded the JSON twice. get/update now compute the params once and pass them into _ids_from_request, and the helper reads the body into a variable.
OPTIONS /rest/bug_user_last_visit/abc answered 200 Allow: GET, POST while GET and POST on that path 404, advertising methods that do not exist there.
Adds the failure paths that prove parity with the legacy endpoint: a bug in a group the user is not in, anonymous POST, a nonexistent bug id and the mid-loop rollback it triggers, POST with no ids anywhere, and include_fields/exclude_fields.
…t_visit.t A nonexistent bug id reports improper_bug_id_field_value, not bug_id_does_not_exist, because Bug->new only sets NotFound for a bare scalar. The private bug needs no groups or milestone, since its product carries the group as mandatory. Drops the OPTIONS assertion on a non-numeric id: the route constraint sends it to the legacy rest.cgi catch-all instead of a 404.
…ees.t does Filing directly into the QA-Selenium-TEST product returned 400. Create the bug in Another Product with create_bug_fields() and restrict it afterwards, which is the setup already proven in CI. It is filed by the private user so the editbugs user is not its reporter and cannot see it that way.
|
The branch has been rebased onto master now that #2751 landed, so it no longer carries its own copy of Two things surfaced while writing the tests. Neither belongs in this PR, and I would rather not repeat the Unmatched
I hit this with the This affects every native REST controller in the 2057358 series that constrains a placeholder, so it seems worth its own bug. A nonexistent bug id produces a misleading error.
Behaviour is identical in the legacy endpoint, so this PR preserves it deliberately and the new test asserts the real behaviour with a comment explaining why. But it is a confusing response for API callers, and it applies to any endpoint that calls |
Summary
Ports
Bugzilla::WebService::BugUserLastVisit'sget/updatemethods into a nativeBugzilla::API::V1::BugUserLastVisitMojo controller, mirroring the pattern already used for Classification/Component/Teams/Reminders/Configuration/Bugzilla (system info).This is a child bug of 2057358, see there for details.
Rebased onto master now that #2751 has landed, so the branch no longer carries its own copy of
merge_request_paramsand uses the shared one.Changes
Bugzilla/API/V1/BugUserLastVisit.pm:GET/POST /rest/bug_user_last_visitand/rest/bug_user_last_visit/<id>(login required), same JSON response shape as the legacy endpointsBugzilla/WebService/BugUserLastVisit.pmandBugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pmBugUserLastVisitentry fromWS_DISPATCHinBugzilla/WebService/Constants.pmand drop the correspondinguseline inBugzilla/WebService/Server/REST.pmBugzilla/WebService/Util.pm:merge_request_paramstakes an optional list of parameter names that are lists, and reads the request body once. See the section belowqa/t/rest_bug_user_last_visit.twhich this endpoint did not haveBreaking change: removing the
WS_DISPATCHentry also removesBugUserLastVisit.get/updatefrom JSON-RPC and XML-RPC, not just the legacy REST dispatcher, since all three share that table. Native Mojo routes only serve REST. This matches the same tradeoff already made in the Classification and Bugzilla (system-info) migrations earlier in this series.Changes to the shared merge_request_params helper
Flagging these explicitly, since
Bugzilla/WebService/Util.pmis shared and the changes affect more than this endpoint.merge_request_paramsnow accepts an optional arrayref of parameter names that are lists, called here asmerge_request_params($self, ['ids']). Those parameters always come back as arrayrefs; every other parameter always comes back as a scalar. The argument is optional and behaviour is unchanged without it.This is needed because the collapse-to-scalar added in #2751 uses
$c->req->param, which keeps only the last value of a repeated key, so?ids=1&ids=2silently became a single id.Rather than work around it locally, the helper now makes list-ness explicit, because the previous
->to_hashhad the mirror-image flaw: it returned a scalar for one occurrence and an arrayref for two, which is whyGroup.pmhas to run its parameters back throughvalidate()to recoerce them. A parameter's type should not depend on how many times it was sent.This also fixes the same latent bug in bug 2065173, where
GET /rest/group?ids=1&ids=2currently returns a single group.Separately, the helper now reads
$c->req->bodyinto a variable instead of calling it twice, which matters for file-backed request assets.Test plan
GET /rest/bug_user_last_visit(anonymous =>login_required, authenticated => list of last-visited bugs)GET /rest/bug_user_last_visit/<id>GET /rest/bug_user_last_visit?ids=<id>&ids=<id>POST /rest/bug_user_last_visit/<id>POST /rest/bug_user_last_visitwith{"ids":[...]}body, and with a body but noContent-TypePOSTagainst a bug in a group the caller is not a member of => 401bug_access_denied, and the same id filtered out ofGETPOSTwith a nonexistent bug id => 404, and the visit recorded earlier in the same loop is rolled backPOSTwith noidsin the path, query string or body =>param_requiredrest_malformed_jsoninclude_fields/exclude_fieldsOPTIONSon both routes returnsAllow: GET, POST, andOPTIONSon a non-numeric id 404slast_visit_tswith trailingZ) matches the legacy endpointReferences