Files
sencho/e2e/stack-file-explorer.spec.ts
Anso ebbd504a5a test(e2e): fix the file-explorer Rename locator collision and log failed requests in deploy specs (#2017)
The context-menu assertion matched both the Rename item and a leftover
lifecycle-rename-dst.txt row from an earlier test, failing the first attempt
in most CI runs. Match the menu item by role instead.

The deploy-feedback specs now log aborted requests and attach the browser
logger to the inline-progress group too, to diagnose an intermittent login
failure after deploy tests.
2026-09-26 13:12:54 -04:00

589 lines
26 KiB
TypeScript

/**
* Stack file explorer end-to-end coverage.
*
* Companion to e2e/stack-files.spec.ts (which covers the basic admin and
* community-tier upload/edit/delete/download flows). This spec adds the
* harder-to-reach surfaces the file-explorer audit flagged: path-traversal
* input corpus, protected-file enforcement at the API, optimistic-concurrency
* 412 on simultaneous edits, directory-truncation headers + filter behaviour,
* binary-detection override, the multer 25 MB cap, and the symlink delete
* semantics that landed in the M-3 PR.
*
* Items deliberately out of scope for this spec (each carries a `.skip()`
* with a one-line rationale near the relevant describe block):
* - Remote-node matrix: requires a real peer enrolled in the CI database.
* - Mid-op disconnect: same.
* - Developer-mode diagnostic-line matrix: requires reading the backend's
* stdout, which the Playwright harness does not have a stable hook for.
* - Viewer-role tier-persona matrix: requires seeding a viewer-role user
* and re-driving the auth flow under it; the API-only assertions below
* already cover the role-gated 403 paths.
*
* Fixture stack: a single `e2e-explorer-test` stack is seeded once per
* describe block. Helper writes go directly to COMPOSE_DIR; the API is used
* for stack create/delete only.
*/
import * as fs from 'node:fs/promises';
import * as nodePath from 'node:path';
import { test, expect, type Page } from '@playwright/test';
import { loginAs, waitForStacksLoaded } from './helpers';
const COMPOSE_DIR = process.env.COMPOSE_DIR ?? '/tmp/compose';
const STACK = 'e2e-explorer-test';
// ---------------------------------------------------------------------------
// Seed / teardown helpers
// ---------------------------------------------------------------------------
async function seedStack(page: Page): Promise<void> {
await page.evaluate(async (name: string) => {
const res = await fetch('/api/stacks', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ stackName: name }),
});
if (!res.ok && res.status !== 409) throw new Error(`stack create: ${res.status}`);
}, STACK);
}
async function teardownStack(page: Page): Promise<void> {
await page.evaluate(async (name: string) => {
await fetch(`/api/stacks/${encodeURIComponent(name)}`, {
method: 'DELETE',
credentials: 'include',
}).catch(() => {});
}, STACK);
}
async function seedSuite(browser: import('@playwright/test').Browser): Promise<void> {
const page = await browser.newPage();
try {
await loginAs(page);
await seedStack(page);
} finally {
await page.close();
}
}
async function teardownSuite(browser: import('@playwright/test').Browser): Promise<void> {
const page = await browser.newPage();
try {
await loginAs(page);
await teardownStack(page);
} catch (e) {
console.warn('teardown failed:', e);
} finally {
await page.close();
}
}
/** Read the cookie header so request-context calls inherit the browser session. */
async function cookieHeader(page: Page): Promise<string> {
const cookies = await page.context().cookies();
return cookies.map((c) => `${c.name}=${c.value}`).join('; ');
}
const stackDir = (): string => nodePath.join(COMPOSE_DIR, STACK);
// ---------------------------------------------------------------------------
// File-scope seed / teardown (single login pair shared across every describe)
// ---------------------------------------------------------------------------
// One file-scope beforeAll/afterAll replaces seven per-describe pairs. The
// per-describe pairs each did a fresh loginAs + stack create + stack delete;
// stacking that up made the post-spec sidebar load slow enough that
// downstream specs in the single-worker queue (stacks.spec.ts) timed out on
// their own loginAs dashboard wait. The test stack is idempotent across
// beforeAll calls anyway (the API returns 409 on a duplicate create), so
// collapsing the hooks costs nothing.
test.beforeAll(async ({ browser }) => { await seedSuite(browser); });
test.afterAll(async ({ browser }) => { await teardownSuite(browser); });
test.setTimeout(60_000);
// ---------------------------------------------------------------------------
// Path traversal corpus
// ---------------------------------------------------------------------------
test.describe('Stack file explorer: path traversal protection', () => {
test.beforeEach(async ({ page }) => { await loginAs(page); });
// Every entry must produce a 400 with code INVALID_PATH on the file-listing
// route. The validator is upstream of FileSystemService and the audit pinned
// it as the first defence; a regression here would expose path traversal.
const corpus: Array<{ label: string; query: string }> = [
{ label: 'parent-dir traversal', query: '..' },
{ label: 'parent-dir prefix', query: '../etc/passwd' },
{ label: 'embedded ..', query: 'a/../b' },
{ label: 'absolute POSIX path', query: '/etc/passwd' },
{ label: 'Windows drive prefix', query: 'C:/windows/system32' },
{ label: 'backslash separator', query: 'foo\\bar' },
{ label: 'double slash', query: 'foo//bar' },
{ label: 'NUL byte', query: 'foo\x00bar' },
{ label: 'single-segment dot', query: 'a/./b' },
];
for (const { label, query } of corpus) {
test(`rejects ${label} on GET /files with 400 INVALID_PATH`, async ({ page, request }) => {
const cookie = await cookieHeader(page);
const res = await request.get(
`/api/stacks/${encodeURIComponent(STACK)}/files?path=${encodeURIComponent(query)}`,
{ headers: { cookie } },
);
expect(res.status()).toBe(400);
const body = await res.json();
expect(body.code).toBe('INVALID_PATH');
});
}
});
// ---------------------------------------------------------------------------
// Protected file enforcement
// ---------------------------------------------------------------------------
test.describe('Stack file explorer: protected files', () => {
test.beforeEach(async ({ page }) => { await loginAs(page); });
test('DELETE compose.yaml via API returns 409 PROTECTED_FILE and does not remove it', async ({ page, request }) => {
const cookie = await cookieHeader(page);
const composePath = nodePath.join(stackDir(), 'compose.yaml');
// The stack-create seed wrote compose.yaml; confirm pre-state.
await fs.access(composePath);
const res = await request.delete(
`/api/stacks/${encodeURIComponent(STACK)}/files?path=${encodeURIComponent('compose.yaml')}`,
{ headers: { cookie } },
);
expect(res.status()).toBe(409);
const body = await res.json();
expect(body.code).toBe('PROTECTED_FILE');
// File still on disk.
await fs.access(composePath);
});
test('DELETE .env via API returns 409 PROTECTED_FILE', async ({ page, request }) => {
const cookie = await cookieHeader(page);
// Seed .env so the route does not 404 before the protected-file guard runs.
await fs.writeFile(nodePath.join(stackDir(), '.env'), 'KEY=value\n');
const res = await request.delete(
`/api/stacks/${encodeURIComponent(STACK)}/files?path=${encodeURIComponent('.env')}`,
{ headers: { cookie } },
);
expect(res.status()).toBe(409);
expect((await res.json()).code).toBe('PROTECTED_FILE');
});
test('DELETE a subdirectory file named compose.yaml is allowed (protection scoped to root)', async ({ page, request }) => {
const cookie = await cookieHeader(page);
const subdir = nodePath.join(stackDir(), 'backups');
await fs.mkdir(subdir, { recursive: true });
await fs.writeFile(nodePath.join(subdir, 'compose.yaml'), 'services: {}\n');
const res = await request.delete(
`/api/stacks/${encodeURIComponent(STACK)}/files?path=${encodeURIComponent('backups/compose.yaml')}`,
{ headers: { cookie } },
);
expect(res.status()).toBe(204);
await fs.rm(subdir, { recursive: true, force: true });
});
});
// ---------------------------------------------------------------------------
// Optimistic concurrency (412 PRECONDITION_FAILED)
// ---------------------------------------------------------------------------
test.describe('Stack file explorer: optimistic concurrency on edits', () => {
test.beforeEach(async ({ page }) => { await loginAs(page); });
test('second concurrent save returns 412 with currentContent + currentMtimeMs', async ({ page, request }) => {
const cookie = await cookieHeader(page);
const filePath = nodePath.join(stackDir(), 'concurrent.txt');
await fs.writeFile(filePath, 'initial\n');
// Read once to capture the baseline ETag.
const read = await request.get(
`/api/stacks/${encodeURIComponent(STACK)}/files/content?path=${encodeURIComponent('concurrent.txt')}`,
{ headers: { cookie } },
);
expect(read.status()).toBe(200);
const etag = read.headers()['etag'] ?? '';
expect(etag).toMatch(/^W\/"\d+"$/);
// First save with the captured ETag succeeds and bumps the on-disk mtime.
const firstWrite = await request.put(
`/api/stacks/${encodeURIComponent(STACK)}/files/content?path=${encodeURIComponent('concurrent.txt')}`,
{
headers: { cookie, 'If-Match': etag, 'Content-Type': 'application/json' },
data: { content: 'first writer\n' },
},
);
expect(firstWrite.status()).toBe(204);
// Second save with the OLD ETag must return 412 and carry the current
// server-side content so the client can reconcile.
const secondWrite = await request.put(
`/api/stacks/${encodeURIComponent(STACK)}/files/content?path=${encodeURIComponent('concurrent.txt')}`,
{
headers: { cookie, 'If-Match': etag, 'Content-Type': 'application/json' },
data: { content: 'second writer\n' },
},
);
expect(secondWrite.status()).toBe(412);
const conflict = await secondWrite.json();
expect(conflict.code).toBe('PRECONDITION_FAILED');
expect(conflict.currentContent).toBe('first writer\n');
expect(typeof conflict.currentMtimeMs).toBe('number');
// Disk content matches the winning writer; the losing writer never landed.
const onDisk = await fs.readFile(filePath, 'utf-8');
expect(onDisk).toBe('first writer\n');
});
});
// ---------------------------------------------------------------------------
// Binary detection + force=text override
// ---------------------------------------------------------------------------
test.describe('Stack file explorer: binary detection and force=text override', () => {
test.beforeEach(async ({ page }) => { await loginAs(page); });
test('default GET returns binary:true for a UTF-8 file carrying a NUL byte', async ({ page, request }) => {
const cookie = await cookieHeader(page);
const filePath = nodePath.join(stackDir(), 'utf8-with-nul.txt');
await fs.writeFile(filePath, Buffer.from('hello\0world rest is utf8 text', 'utf-8'));
const res = await request.get(
`/api/stacks/${encodeURIComponent(STACK)}/files/content?path=${encodeURIComponent('utf8-with-nul.txt')}`,
{ headers: { cookie } },
);
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.binary).toBe(true);
expect(body.content).toBeUndefined();
});
test('GET with ?force=text returns the bytes as UTF-8 content', async ({ page, request }) => {
const cookie = await cookieHeader(page);
const raw = Buffer.from('hello\0world rest is utf8 text', 'utf-8');
await fs.writeFile(nodePath.join(stackDir(), 'force-text.txt'), raw);
const res = await request.get(
`/api/stacks/${encodeURIComponent(STACK)}/files/content?path=${encodeURIComponent('force-text.txt')}&force=text`,
{ headers: { cookie } },
);
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.binary).toBe(false);
expect(body.content).toBe(raw.toString('utf-8'));
});
test('force=text on an oversized file still returns oversized:true (no inline content)', async ({ page, request }) => {
const cookie = await cookieHeader(page);
// 2.1 MB: just above the 2 MB inline-preview cap. Kept small on purpose
// so the test does not pile memory pressure on top of the rest of the
// CI run; the cap behaviour itself is exercised at every byte over the
// threshold by the route-level test.
await fs.writeFile(nodePath.join(stackDir(), 'oversized.txt'), 'x'.repeat(2_100_000));
const res = await request.get(
`/api/stacks/${encodeURIComponent(STACK)}/files/content?path=${encodeURIComponent('oversized.txt')}&force=text`,
{ headers: { cookie } },
);
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.oversized).toBe(true);
expect(body.content).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// Symlink semantics (delete + chmod)
// ---------------------------------------------------------------------------
test.describe('Stack file explorer: symlink semantics', () => {
test.beforeEach(async ({ page }) => { await loginAs(page); });
// Symlink creation on Windows requires admin or developer mode; this block
// runs on Linux CI where the harness has the privilege.
test.skip(process.platform === 'win32', 'symlink creation requires admin on Windows');
test('DELETE on a symlink removes the link entry; the target file stays intact', async ({ page, request }) => {
const cookie = await cookieHeader(page);
const target = nodePath.join(stackDir(), 'symlink-target.txt');
const link = nodePath.join(stackDir(), 'symlink-link.txt');
await fs.writeFile(target, 'survives\n');
await fs.symlink(target, link);
try {
const res = await request.delete(
`/api/stacks/${encodeURIComponent(STACK)}/files?path=${encodeURIComponent('symlink-link.txt')}`,
{ headers: { cookie } },
);
expect(res.status()).toBe(204);
await expect(fs.lstat(link)).rejects.toMatchObject({ code: 'ENOENT' });
const survived = await fs.readFile(target, 'utf-8');
expect(survived).toBe('survives\n');
} finally {
await fs.unlink(target).catch(() => {});
}
});
test('PUT permissions on a symlink returns 409 LINK_CHMOD_UNSUPPORTED', async ({ page, request }) => {
const cookie = await cookieHeader(page);
const target = nodePath.join(stackDir(), 'chmod-target.txt');
const link = nodePath.join(stackDir(), 'chmod-link.txt');
await fs.writeFile(target, 'payload\n');
await fs.chmod(target, 0o644);
await fs.symlink(target, link);
try {
const res = await request.put(
`/api/stacks/${encodeURIComponent(STACK)}/files/permissions?path=${encodeURIComponent('chmod-link.txt')}`,
{
headers: { cookie, 'Content-Type': 'application/json' },
data: { mode: 0o600 },
},
);
expect(res.status()).toBe(409);
expect((await res.json()).code).toBe('LINK_CHMOD_UNSUPPORTED');
// Target mode unchanged.
const stat = await fs.stat(target);
expect(stat.mode & 0o777).toBe(0o644);
} finally {
await fs.unlink(link).catch(() => {});
await fs.unlink(target).catch(() => {});
}
});
});
// ---------------------------------------------------------------------------
// UI lifecycle: mkdir, rename, chmod via the Files tab
// ---------------------------------------------------------------------------
test.describe('Stack file explorer: UI lifecycle', () => {
test.setTimeout(90_000);
function stackSlug(stackName: string): string {
return stackName.replace(/^-+/, '').replace(/\.(ya?ml)$/i, '');
}
/** Open the Files tab via the routable stack editor URL. */
async function openFilesTab(page: Page): Promise<void> {
const slug = stackSlug(STACK);
await page.goto(`/nodes/local/stacks/${encodeURIComponent(slug)}/files`);
await waitForStacksLoaded(page);
await expect(page.getByRole('button', { name: 'New file' })).toBeVisible({ timeout: 10_000 });
await expect(page.locator('span.font-mono').first()).toBeVisible({ timeout: 10_000 });
}
test.beforeEach(async ({ page }) => {
await loginAs(page);
await openFilesTab(page);
});
test('upload, rename via the API, then verify the renamed file appears in the tree', async ({ page, request }) => {
const cookie = await cookieHeader(page);
// Upload via the UI to exercise the dropzone path.
const input = page.locator('input[type="file"][aria-label="Upload file"]');
await input.setInputFiles({
name: 'lifecycle-rename-src.txt',
mimeType: 'text/plain',
buffer: Buffer.from('rename me\n'),
});
await expect(page.getByText(/uploaded/i).first()).toBeVisible({ timeout: 10_000 });
// Rename via the API since the rename dialog is exercised in
// stack-files.spec.ts; here we focus on the file appearing post-rename.
const renameRes = await request.patch(
`/api/stacks/${encodeURIComponent(STACK)}/files/rename`,
{
headers: { cookie, 'Content-Type': 'application/json' },
data: { from: 'lifecycle-rename-src.txt', to: 'lifecycle-rename-dst.txt' },
},
);
expect(renameRes.status()).toBe(204);
await openFilesTab(page);
await expect(
page.locator('span.font-mono').filter({ hasText: /^lifecycle-rename-dst\.txt$/ }).first(),
).toBeVisible({ timeout: 8_000 });
});
test('mkdir via the API then verify the new folder is visible in the tree', async ({ page, request }) => {
const cookie = await cookieHeader(page);
const res = await request.post(
`/api/stacks/${encodeURIComponent(STACK)}/files/folder?path=${encodeURIComponent('lifecycle-new-folder')}`,
{ headers: { cookie } },
);
expect(res.status()).toBe(204);
await openFilesTab(page);
await expect(
page.locator('span.font-mono').filter({ hasText: /^lifecycle-new-folder$/ }).first(),
).toBeVisible({ timeout: 8_000 });
});
for (const width of [1280]) {
for (const kind of ['file', 'folder'] as const) {
test(`modal footer contains New ${kind} actions with a long parent at ${width}px`, async ({ page }, testInfo) => {
const segments = ['footer-layout', 'nested-parent', 'x'.repeat(100)];
const parent = segments.join('/');
await fs.mkdir(nodePath.join(stackDir(), parent), { recursive: true });
await openFilesTab(page);
for (const segment of segments.slice(0, -1)) {
await page.getByRole('treeitem').filter({ has: page.getByText(segment, { exact: true }) }).click();
}
const row = page.getByRole('treeitem').filter({ has: page.getByText(segments[2], { exact: true }) });
await row.click({ button: 'right' });
await page.getByRole('menuitem', { name: new RegExp(`^New ${kind}$`, 'i') }).click();
await page.setViewportSize({ width, height: 844 });
const dialog = page.getByRole('dialog', { name: `New ${kind}`, exact: true });
await expect(dialog).toBeVisible();
await dialog.getByLabel(kind === 'file' ? 'File name' : 'Folder name').fill(`footer-${kind}-${width}`);
await expect(async () => {
const panel = await dialog.boundingBox();
expect(panel).not.toBeNull();
if (!panel) throw new Error('Dialog has no bounding box');
expect(panel.x).toBeGreaterThanOrEqual(8);
expect(panel.x + panel.width).toBeLessThanOrEqual(width - 8);
for (const name of ['Cancel', 'Create']) {
const action = await dialog.getByRole('button', { name, exact: true }).boundingBox();
expect(action).not.toBeNull();
if (!action) throw new Error(`${name} has no bounding box`);
expect(action.x).toBeGreaterThanOrEqual(panel.x);
expect(action.x + action.width).toBeLessThanOrEqual(panel.x + panel.width);
expect(action.y + action.height).toBeLessThanOrEqual(panel.y + panel.height);
}
expect(await dialog.evaluate((el) => el.scrollWidth <= el.clientWidth)).toBe(true);
}).toPass();
await testInfo.attach(`new-${kind}-${width}`, { body: await page.screenshot(), contentType: 'image/png' });
await dialog.getByRole('button', { name: 'Create', exact: true }).click();
await expect(dialog).toBeHidden();
const created = await fs.stat(nodePath.join(stackDir(), parent, `footer-${kind}-${width}`));
expect(kind === 'file' ? created.isFile() : created.isDirectory()).toBe(true);
});
}
}
test('the New file toolbar button creates a root-level file', async ({ page }) => {
await page.getByRole('button', { name: 'New file' }).click();
await page.getByLabel('File name').fill('ui-new-file.txt');
await page.getByRole('button', { name: /^create$/i }).click();
await expect(page.getByText(/file created/i).first()).toBeVisible({ timeout: 8_000 });
await expect(
page.locator('span.font-mono').filter({ hasText: /^ui-new-file\.txt$/ }).first(),
).toBeVisible({ timeout: 8_000 });
});
test('creating a duplicate name is blocked and does not overwrite the existing file', async ({ page, request }) => {
const cookie = await cookieHeader(page);
const seedRes = await request.put(
`/api/stacks/${encodeURIComponent(STACK)}/files/content?path=${encodeURIComponent('dup-guard.txt')}`,
{
headers: { cookie, 'Content-Type': 'application/json' },
data: { content: 'original\n' },
},
);
expect(seedRes.ok()).toBeTruthy();
await openFilesTab(page);
await expect(
page.locator('span.font-mono').filter({ hasText: /^dup-guard\.txt$/ }).first(),
).toBeVisible({ timeout: 8_000 });
await page.getByRole('button', { name: 'New file' }).click();
await page.getByLabel('File name').fill('dup-guard.txt');
await page.getByRole('button', { name: /^create$/i }).click();
await expect(page.getByText(/a file with that name already exists/i)).toBeVisible({ timeout: 8_000 });
// The server rejected the create, so the existing contents are intact.
const verifyRes = await request.get(
`/api/stacks/${encodeURIComponent(STACK)}/files/content?path=${encodeURIComponent('dup-guard.txt')}`,
{ headers: { cookie } },
);
expect(verifyRes.ok()).toBeTruthy();
const payload = await verifyRes.json() as { content?: string };
expect(payload.content).toBe('original\n');
});
test('right-clicking away from the filename still opens the Sencho context menu', async ({ page }) => {
const row = page.locator('[role="treeitem"]').filter({ hasText: 'compose.yaml' }).first();
await expect(row).toBeVisible({ timeout: 8_000 });
const box = await row.boundingBox();
if (!box) throw new Error('no bounding box for the compose.yaml row');
// Right-click near the right edge, well past the filename text: the whole
// row is the trigger, so the Sencho menu (not the native one) must open.
await row.click({ button: 'right', position: { x: box.width - 6, y: box.height / 2 } });
// Match the menu item by role: an earlier test leaves a file named
// lifecycle-rename-dst.txt in the tree, which a bare text match also hits.
await expect(page.getByRole('menuitem', { name: 'Rename', exact: true })).toBeVisible({ timeout: 5_000 });
});
test('a long filename overflows the pane horizontally instead of being clipped', async ({ page }) => {
const longName = 'a-really-long-file-name-that-should-overflow-the-narrow-file-tree-pane.txt';
await fs.writeFile(nodePath.join(stackDir(), longName), '');
await page.reload();
await openFilesTab(page);
await expect(
page.locator('span.font-mono').filter({ hasText: longName }).first(),
).toBeVisible({ timeout: 8_000 });
// The tree's scroll viewport overflows horizontally, so the name is reachable
// by scrolling rather than truncated.
const overflows = await page.getByTestId('file-tree-root-dropzone').evaluate((el) => {
const vp = el.closest('[data-radix-scroll-area-viewport]') as HTMLElement | null;
return vp ? vp.scrollWidth > vp.clientWidth : false;
});
expect(overflows).toBe(true);
});
});
// ---------------------------------------------------------------------------
// Out-of-scope coverage stubs (documented skips)
// ---------------------------------------------------------------------------
test.describe('Stack file explorer: deferred coverage', () => {
test.skip('remote-node matrix: upload through proxy to a peer', () => {
// Requires a remote node enrolled in the CI database and a reachable
// peer at its api_url. Covered at the unit-test layer by the multipart
// proxy regression guard in backend/src/__tests__/stack-files-remote-upload.test.ts.
});
test.skip('mid-op disconnect: stop the remote, retry upload, observe 502 toast', () => {
// Same constraint: a real peer plus a controlled-disconnect harness.
});
test.skip('developer-mode matrix: assert diag lines appear only when developer_mode = 1', () => {
// Requires reading the backend's stdout stream from the Playwright harness.
// The backend unit test in stack-files-routes.test.ts already pins this
// behaviour via a console.debug spy.
});
test.skip('viewer-role tier-persona matrix', () => {
// Requires seeding a non-admin viewer user and re-driving the auth flow.
// Role gating is covered at the route-test layer in stack-files-routes.test.ts;
// the viewer-can-read / admin-can-write split is asserted there.
});
test.skip('large directory truncation: 1100 entries surface 1000 + X-Truncated header', () => {
// Seeding 1100 files inline added enough wall-clock and inode-table
// churn that subsequent specs running in the single-worker queue saw
// their post-login dashboard render time exceed 10 s. The truncation
// contract is fully exercised at the route-test layer (the same case
// lives in backend/src/__tests__/stack-files-routes.test.ts) and at
// the service-test layer (filesystem-stack-paths.test.ts).
});
test.skip('25 MB upload cap: a 25 MB + 1 byte payload trips multer LIMIT_FILE_SIZE', () => {
// Sending a 25 MB buffer through supertest piled enough memory pressure
// on the shared worker that downstream specs timed out on their login
// dashboard wait. The cap behaviour is exercised at the route-test
// layer in backend/src/__tests__/stack-files-routes.test.ts.
});
});