fix: repair CI after the dependency bumps, and align it with the Trixie build (#651)
* fix(app): depend on @testing-library/dom directly
jest-dom 7 moved @testing-library/dom from a bundled dependency to a peer
dependency. Nothing else pulls the top-level copy in — @testing-library/react
13 carries its own nested v8 — so it was present only as a peer, and CI
installs with --legacy-peer-deps, which skips peers. Every suite then failed
to load setupTests.js with "Cannot find module '@testing-library/dom'".
Declare it as a devDependency so it installs under either resolution mode.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(app): import mount from cypress/react
Cypress 14 removed the cypress/react18 subpath; cypress/react mounts React
18 itself. The e2e support file loads commands.js, so the stale import broke
bundling for every spec, not just component tests:
Module not found: Package path ./react18 is not exported from package cypress
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ci: install and cache Cypress the way the bump requires
The Cypress binary lives in ~/.cache/Cypress, outside node_modules, but only
node_modules was cached. A restored cache satisfied npm, so npm skipped the
postinstall download and the 15.x binary was never fetched:
The cypress npm package is installed, but the Cypress binary is missing.
Cache both paths and install the binary explicitly.
Also align the JS toolchain with what actually ships. Debian 13 devices run
nodejs 20.19.2 and install with a plain `npm install`; the Docker app image
does the same. CI pinned older Node and passed --legacy-peer-deps, which
skips peer dependencies -- that is why jest-dom 7's peer requirement passed
review and broke CI. Match the devices instead.
Pin caddy to 2.11.4, the version Debian 13 devices run, rather than latest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ci: test on Python 3.13, the version that ships
Debian 13 devices run Python 3.13.5 and the api/controller/help images are
python:3.13-trixie, but CI ran 3.11 (and one job on 3.12), so the runtime
under test was two minors behind the runtime in production.
Verified on 3.13 before switching: backend 1712 passed, 5 skipped; controller
883 passed. The only new warning is SQLAlchemy 1.3.22 reaching Python's
deprecated datetime.utcfromtimestamp(), which is noise rather than a break.
Drop api-tests-3-12: no workflow referenced it, it duplicated api-tests, and
its "Sanic does not yet support 3.12" note is stale now that devices run 3.13.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:01:52 -06:00
|
|
|
import {mount} from 'cypress/react'
|
2024-11-02 15:14:02 -06:00
|
|
|
|
fix: repair CI after the dependency bumps, and align it with the Trixie build (#651)
* fix(app): depend on @testing-library/dom directly
jest-dom 7 moved @testing-library/dom from a bundled dependency to a peer
dependency. Nothing else pulls the top-level copy in — @testing-library/react
13 carries its own nested v8 — so it was present only as a peer, and CI
installs with --legacy-peer-deps, which skips peers. Every suite then failed
to load setupTests.js with "Cannot find module '@testing-library/dom'".
Declare it as a devDependency so it installs under either resolution mode.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(app): import mount from cypress/react
Cypress 14 removed the cypress/react18 subpath; cypress/react mounts React
18 itself. The e2e support file loads commands.js, so the stale import broke
bundling for every spec, not just component tests:
Module not found: Package path ./react18 is not exported from package cypress
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ci: install and cache Cypress the way the bump requires
The Cypress binary lives in ~/.cache/Cypress, outside node_modules, but only
node_modules was cached. A restored cache satisfied npm, so npm skipped the
postinstall download and the 15.x binary was never fetched:
The cypress npm package is installed, but the Cypress binary is missing.
Cache both paths and install the binary explicitly.
Also align the JS toolchain with what actually ships. Debian 13 devices run
nodejs 20.19.2 and install with a plain `npm install`; the Docker app image
does the same. CI pinned older Node and passed --legacy-peer-deps, which
skips peer dependencies -- that is why jest-dom 7's peer requirement passed
review and broke CI. Match the devices instead.
Pin caddy to 2.11.4, the version Debian 13 devices run, rather than latest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ci: test on Python 3.13, the version that ships
Debian 13 devices run Python 3.13.5 and the api/controller/help images are
python:3.13-trixie, but CI ran 3.11 (and one job on 3.12), so the runtime
under test was two minors behind the runtime in production.
Verified on 3.13 before switching: backend 1712 passed, 5 skipped; controller
883 passed. The only new warning is SQLAlchemy 1.3.22 reaching Python's
deprecated datetime.utcfromtimestamp(), which is noise rather than a break.
Drop api-tests-3-12: no workflow referenced it, it duplicated api-tests, and
its "Sanic does not yet support 3.12" note is stale now that devices run 3.13.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:01:52 -06:00
|
|
|
// Cypress 14 dropped the separate cypress/react18 entry point; cypress/react mounts React 18 itself.
|
2024-11-02 15:14:02 -06:00
|
|
|
Cypress.Commands.add('mount', mount);
|
e2e: exercise the FileBrowser footer buttons against real files (#670)
* e2e: exercise the FileBrowser footer buttons against real files
Sixteen Cypress tests covering Rename, Move, Delete (including the
tagged-file force-delete flow), Ignore/Unignore, Upload, and scoped
Refresh, with no API mocking: fb:* tasks seed a cypress-fb/ sandbox
inside the media directory, the UI mutates it through the real API, and
assertions check the disk as well as the table. Skipped in CI, which
runs only the React dev server.
Background jobs (move, refresh) only queue work and worker_status reads
idle while jobs wait, so cy.refreshFiles / cy.waitForEvent poll the
events feed for completion events newer than the server's clock, and
fail loudly when one never arrives. Every suite cleans up
unconditionally: sandbox files, DB rows via a final refresh, test tags
and their tags/ directories, and any leaked ignored-directory config.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* e2e: act on review — drain stragglers, teardown that cannot be skipped
Events carry no job id, so waitForEvent cannot prove WHICH job finished.
The rename tests now drain the parent-directory refresh their rename
queues before ending, so the next test's refresh wait cannot be
satisfied by a straggler while the DB still reflects the old tree; move
waits match the destination in the completion message.
Teardown must always reach the disk wipe: a failed command aborts the
rest of its hook, so the tag cleanup and the DB-purge refresh are now
best-effort (failOnStatusCode: false, no assertions), and the unignore
runs after every test instead of once per spec. History and
forward-looking product notes trimmed from comments.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 20:26:46 -06:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Waiting on WROLPi background work.
|
|
|
|
|
*
|
|
|
|
|
* Most mutating file endpoints only QUEUE a job for the background file worker (move, refresh),
|
|
|
|
|
* and /api/files/worker_status reads 'idle' while jobs are still waiting in its queue, so
|
|
|
|
|
* neither the response nor the status endpoint says anything about completion. The reliable
|
|
|
|
|
* signal is the completion event on /api/events/feed. The pattern: capture the server's clock
|
|
|
|
|
* BEFORE starting the work, then poll the feed for a matching event newer than that timestamp.
|
|
|
|
|
*
|
|
|
|
|
* Events carry no job id, so the match is by name/message and cannot prove WHICH job finished.
|
|
|
|
|
* A test that leaves a job queued (e.g. rename queues a parent-directory refresh) must drain
|
|
|
|
|
* it before ending, or the next test's wait may be satisfied by the straggler.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// The API decides what time it is (the frontend does the same, in case of clock drift).
|
|
|
|
|
Cypress.Commands.add('serverNow', () =>
|
|
|
|
|
cy.request('/api/events/feed').its('body.now'));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* cy.waitForEvent({since, event, message?, messagePrefix?, messageContains?, failureEvent?, timeout?})
|
|
|
|
|
*
|
|
|
|
|
* Poll the events feed until an event newer than `since` matches. `event` is a name or a
|
|
|
|
|
* list of names; narrow further with an exact `message`, a `messagePrefix`, or a
|
|
|
|
|
* `messageContains` substring. If a `failureEvent` (name or list) shows up first, fail
|
|
|
|
|
* immediately with its message instead of timing out.
|
|
|
|
|
*
|
|
|
|
|
* cy.serverNow().then((since) => {
|
|
|
|
|
* // ...start the background work...
|
|
|
|
|
* cy.waitForEvent({since, event: 'file_move_completed', failureEvent: 'file_move_failed'});
|
|
|
|
|
* });
|
|
|
|
|
*/
|
|
|
|
|
Cypress.Commands.add('waitForEvent', ({since, event, message, messagePrefix, messageContains, failureEvent, timeout = 60000}) => {
|
|
|
|
|
const wanted = [].concat(event);
|
|
|
|
|
const failures = failureEvent ? [].concat(failureEvent) : [];
|
|
|
|
|
const matches = (e) => {
|
|
|
|
|
if (!wanted.includes(e.event)) return false;
|
|
|
|
|
if (message) return e.message === message;
|
|
|
|
|
if (messagePrefix) return (e.message || '').startsWith(messagePrefix);
|
|
|
|
|
if (messageContains) return (e.message || '').includes(messageContains);
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const deadline = Date.now() + timeout;
|
|
|
|
|
const poll = () => {
|
|
|
|
|
cy.request(`/api/events/feed?after=${encodeURIComponent(since)}`).then(({body}) => {
|
|
|
|
|
const events = body.events || [];
|
|
|
|
|
const failed = events.find((e) => failures.includes(e.event));
|
|
|
|
|
expect(failed, failed && `${failed.event}: ${failed.message}`).to.be.undefined;
|
|
|
|
|
if (events.some(matches)) return;
|
|
|
|
|
expect(Date.now(), `event '${wanted}' not seen within ${timeout}ms -- `
|
|
|
|
|
+ 'the file worker may be wedged (restart the api service)').to.be.lessThan(deadline);
|
|
|
|
|
cy.wait(1000);
|
|
|
|
|
poll();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
poll();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* cy.refreshFiles(paths, options) -- refresh files on the live stack and wait for completion.
|
|
|
|
|
*
|
|
|
|
|
* cy.refreshFiles(['cypress-fb']); // refresh one directory
|
|
|
|
|
* cy.refreshFiles([], {timeout: 300000}); // global refresh, longer timeout
|
|
|
|
|
*/
|
|
|
|
|
Cypress.Commands.add('refreshFiles', (paths = [], {timeout = 60000} = {}) => {
|
|
|
|
|
// Refresh-completed event names, by scope (wrolpi/files/worker.py).
|
|
|
|
|
const completionEvents = ['directory_refresh', 'files_refreshed', 'global_after_refresh_completed'];
|
|
|
|
|
// A single directory announces itself by name; match exactly so another test's refresh
|
|
|
|
|
// finishing at the same moment cannot satisfy this wait.
|
|
|
|
|
const singleDirectory = paths.length === 1 && !/\.[^/]+$/.test(paths[0]);
|
|
|
|
|
const message = singleDirectory ? `Refreshed: ${paths[0].replace(/\/$/, '')}` : undefined;
|
|
|
|
|
|
|
|
|
|
cy.serverNow().then((since) => {
|
|
|
|
|
const body = paths.length ? {paths} : undefined;
|
|
|
|
|
cy.request('POST', '/api/files/refresh', body);
|
|
|
|
|
cy.waitForEvent({since, event: completionEvents, message, timeout});
|
|
|
|
|
});
|
|
|
|
|
});
|