Unit/System Tests Updates Requested bug Webservices PR-6.2-dev Pending

User tests: Successful: Unsuccessful:

avatar genr8r
genr8r
22 Oct 2026
  • I read the Generative AI policy and my contribution is either not created with the help of AI or is compatible with the policy and GNU/GPL 2 or later.

Hi all,

We run several Joomla sites that are written to through the Web Services API, and found records staying checked out after a PATCH was refused. The next person to open the record in the backend was told it was checked out by the API user. Related, but not the same case: #42219 (that report was about successful updates and could not be reproduced).

Summary of Changes

ApiController::edit() checks the record out and then calls save(). save() throws when the form validation fails (InvalidParameterException) or when the model refuses the save (Exception\Save), and both happen before the check-in at the end of save(). So every refused PATCH left the record checked out to the API user.

edit() now wraps the save() call and checks the record back in before rethrowing. It only does so when this request took the check-out, so a lock the user already held stays in place. This mirrors the HTML forms: FormController keeps the record checked out after a refused save because the user returns to the edit form and later saves or cancels, but an API request has no follow-up edit or cancel, so the refusal ends the edit. A successful save is unchanged (save() still does its own check-in), and FormModel::checkin() already returns early for tables without check-out columns.

Tests:

  • Unit: new tests/Unit/Libraries/Cms/MVC/Controller/ApiControllerTest.php covers a refused edit and a successful edit.
  • System: new case in tests/System/integration/api/com_content/Articles.cy.js, "does not leave an article checked out when an update is refused", which sends a PATCH with an empty title and checks checked_out afterwards.

Both new tests fail without this change (the Cypress case reports the API user's id as the checked_out value) and pass with it. Run locally with the system-test Docker image: npx cypress run --spec tests/System/integration/install/Installation.cy.js,tests/System/integration/api/com_content/Articles.cy.js.

Testing Instructions

Test on current 6.2-dev.

  1. Enable the Web Services plugin for Content and the API Authentication - Token plugin, and create an API token for a Super User.
  2. Create an article (any category).
  3. Send a PATCH that the form refuses, for example an empty title:
    PATCH /api/index.php/v1/content/articles/{id} with body {"title": ""}
  4. The API answers with "Field required: Title". Open Content > Articles in the backend.

Actual result BEFORE applying this Pull Request

The article shows the checked-out lock, held by the API user. Opening it as another user reports that it is checked out, and it stays that way until someone checks it in.

Expected result AFTER applying this Pull Request

The API still answers with "Field required: Title", and the article is not checked out. A valid PATCH (for example {"title": "New title"}) still saves and leaves the article checked in, as before.

Link to documentations

Please select:

  • Documentation link for guide.joomla.org:

  • No documentation changes for guide.joomla.org needed

  • Pull Request link for manual.joomla.org:

  • No documentation changes for manual.joomla.org needed

Thanks for taking a look. Happy to change the approach or the test if you'd prefer it done differently.

avatar genr8r genr8r - open - 22 Oct 2026
avatar genr8r genr8r - change - 22 Oct 2026
Status New ⇒ Pending
avatar joomla-cms-bot
joomla-cms-bot - comment - 22 Oct 2026

Please add more information to your issue. Without test instructions and/or any description we will close this issue within 4 weeks. Thanks.
This is an automated message from the J!Tracker Application.

avatar joomla-cms-bot joomla-cms-bot - change - 22 Oct 2026
Category ⇒ Libraries JavaScript Unit Tests
avatar genr8r genr8r - change - 22 Oct 2026
The description was changed
avatar genr8r genr8r - edited - 22 Oct 2026
avatar brianteeman
brianteeman - comment - 22 Oct 2026

Only security fixes go into 5.4 now so please rebase to 6.2-dev

avatar genr8r genr8r - change - 22 Oct 2026
Labels Added: Unit/System Tests Test instructions missing PR-5.4-dev
avatar genr8r genr8r - change - 22 Oct 2026
Title
…
[5.4] [AI] Check the record back in when an API edit is refused
[6.2] [AI] Check the record back in when an API edit is refused
avatar genr8r genr8r - edited - 22 Oct 2026
avatar genr8r
genr8r - comment - 22 Oct 2026

Rebased onto 6.2-dev as requested, thanks @brianteeman. I re-ran the tests locally on 6.2-dev after the rebase: the full unit suite and the new system test both pass. Could someone remove the PR-5.4-dev and "Test instructions missing" labels? The test instructions are in the description.

avatar genr8r genr8r - change - 22 Oct 2026
The description was changed
Labels Added: PR-6.2-dev
avatar alikon alikon - test_item - 23 Oct 2026 - Tested successfully
avatar alikon
alikon - comment - 23 Oct 2026

I have tested this item ✅ successfully on b2f7a43


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/48688.

avatar alikon
alikon - comment - 23 Oct 2026

I have tested this item ✅ successfully on b2f7a43


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/48688.

avatar exlemor exlemor - test_item - 25 Oct 2026 - Tested successfully
avatar exlemor
exlemor - comment - 25 Oct 2026

I have tested this item ✅ successfully on b2f7a43

Hi @genr8r, thank you for the PR, I have successfully tested your PR fix.


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/48688.
avatar exlemor
exlemor - comment - 25 Oct 2026

I have tested this item ✅ successfully on b2f7a43

Hi @genr8r, thank you for the PR, I have successfully tested your PR fix.


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/48688.
avatar richard67 richard67 - change - 26 Oct 2026
The description was changed
Status Pending ⇒ Ready to Commit
avatar richard67
richard67 - comment - 26 Oct 2026

RTC


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/48688.

avatar richard67
richard67 - comment - 26 Oct 2026

RTC


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/48688.

avatar richard67 richard67 - edited - 26 Oct 2026
avatar MacJoom MacJoom - change - 6 Nov 2026
Labels Added: RTC bug Webservices
Removed: Test instructions missing PR-5.4-dev
avatar MacJoom
MacJoom - comment - 7 Nov 2026

Hi @genr8r – thanks for the PR. If the article is already checked out by the token's user (e.g. open in the backend), checkout() still succeeds, so a refused PATCH now checks it in and removes that lock. Could edit() only check in on failure if this request took the lock?

avatar MacJoom MacJoom - change - 7 Nov 2026
Status Ready to Commit ⇒ Pending
avatar MacJoom
MacJoom - comment - 7 Nov 2026

Back to pending


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/48688.

avatar MacJoom
MacJoom - comment - 7 Nov 2026

Back to pending


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/48688.

avatar genr8r genr8r - change - 9 Nov 2026
Labels Added: Updates Requested
Removed: RTC
avatar genr8r genr8r - change - 9 Nov 2026
The description was changed
avatar genr8r genr8r - edited - 9 Nov 2026
avatar genr8r
genr8r - comment - 9 Nov 2026

Thanks @MacJoom, good catch. edit() now records whether the row was already checked out before calling checkout(), and only checks it back in on a refused save if this request took the lock. I added a unit test for that case, and confirmed it against a real database with a temporary Cypress spec: a refused PATCH keeps a lock the token's user already held, and still releases one it took itself. One thing I left alone: a successful PATCH still releases an existing lock through the check-in at the end of save(). That is existing behaviour, so I kept it out of this PR, but I can change it here if you'd prefer.

Add a Comment

Login with GitHub to post a comment