fix(storage): Test on a mirrored phone storage row probes the backends on the form

On the phone storage panel, Test on a mirrored backend sent the mirror
itself to POST /api/admin/storage/test. The server expands a mirror by
looking its primary and replicas up by name in the SAVED storage
config, so the phone tested the saved settings instead of what the
operator had just edited, and a replica added but not saved yet made
the whole test fail with "references unknown backend". The desktop
probes the primary and each replica with their draft options and
merges the results.

Cause: the phone called admin.test with the mirror candidate, while the
desktop called admin.testMirror with mirrorProbeTargets. The choice
lived in each shell's markup.

Fix: useStoragePanel now offers loaded.testRow(row), which tests a
plain row as it is and a mirrored row through admin.testMirror with
mirrorProbeTargets from the draft. Both shells call it.

Red test: FE-MOB-MSTOR-018 (new, MAdminStoragePanel.test.tsx) failed
with "AssertionError: expected [ 'mirror' ] to deeply equal
[ 'backups-local', 'off-box' ]". FE-ADMIN-STOR-PANEL-017 (new,
useStoragePanel.test.ts) covers testRow itself.

No existing assertion changed.
This commit is contained in:
Maurice
2026-10-09 17:49:44 +02:00
parent c8e21b9128
commit df7bb9a5e1
5 changed files with 62 additions and 20 deletions
@@ -190,7 +190,7 @@ export default function MAdminStoragePanel(): React.ReactElement {
)
}
const {
state, draft, backendNames, rows, degenerate, effective, usageSums, startAdd, startEdit, commitBackend,
state, draft, backendNames, rows, degenerate, effective, usageSums, startAdd, startEdit, testRow, commitBackend,
removeMessage, confirmRemoval, setCategory, save, moveAndSave, routeOnlySave,
} = loaded
@@ -236,11 +236,6 @@ export default function MAdminStoragePanel(): React.ReactElement {
{rows.map((row) => {
const resultKey = row.mirrorName ?? row.name
const result = admin.testResults[resultKey]
// row.mirrorName only exists when foldBackends adopted a draft mirror
// for this row, so the draft lookup below cannot miss.
const testCandidate = row.mirrorName
? draft.backends.find((b) => b.name === row.mirrorName)!
: row.backend
const rowUsage = usageSums?.[row.name]
const backfill = row.mirrorName ? state.backfills.find((b) => b.backend === row.mirrorName) : undefined
return (
@@ -285,7 +280,7 @@ export default function MAdminStoragePanel(): React.ReactElement {
<p className="mt-1 font-geist text-[0.625rem] text-m-muted">{t('storage.backends.envReadOnly')}</p>
)}
<div className="mt-2 flex gap-2">
<MAdminButton variant="ghost" onClick={() => admin.test(testCandidate)}>
<MAdminButton variant="ghost" onClick={() => void testRow(row)}>
{t('storage.actions.test')}
</MAdminButton>
{row.source !== 'env' && (