v3.28.0 review: the batch failure classification

- A non-ValueError exception from the install run (a card removed
  mid-chain, a transport error) was recorded as a 'skipped' attempt with
  the error text - but the chain had started and died, which is a
  failure.  Only a ValueError (a request/eligibility error: the preset's
  ISD keyset, the plan) is a skip now; anything else is a 'failed' record
  carrying its error.  The PWA's failed row falls back to that error when
  there is no failing step / status word to show.
- The panel's Clear button is disabled while a batch is active (the
  server refuses the clear with 409 anyway) - no pointless error alert.
- api.md and the repo AGENTS document the two outcomes.  Tests: a
  transport error mid-chain is a failure (and the card stays seen), the
  plan's input validation (the block size, a missing cap_hex), the failed
  row without a step, the shared ram_format field, and the Clear state.
  915 frontend + 921 Python; sw cache simple-v438 (unchanged).
This commit is contained in:
2026-10-14 15:09:33 +03:00
parent ab373aff30
commit e8190c65f7
5 changed files with 73 additions and 5 deletions
+15
View File
@@ -140,6 +140,18 @@ test('ramBatchAttemptsHtml renders every verdict with its detail', () => {
// newest first
assert.ok(out.indexOf('8970119000004600114') < out.indexOf('8970119000004600098'));
assert.match(app.ramBatchAttemptsHtml([]), /No cards processed yet\./);
// a failure without a step (a lost card mid-chain) shows its error
assert.match(app.ramBatchAttemptsHtml([{ iccid: '8970119000004600122', result: 'failed',
error: 'card removed' }]), /card removed/);
});
test('the install and the batch send the form\'s ram_format', () => {
const fields = extractFunc(html, 'ramInstallCapFields');
assert.match(fields, /ram_format: \(document\.getElementById\('ram-format'\) \|\| \{\}\)\.value \|\| 'auto'/);
// the single install merges the shared fields into its request body
assert.match(extractFunc(html, 'ramInstallCap', true), /Object\.assign\(\{/);
assert.match(extractFunc(html, 'ramBatchStart', true),
/pysimFetchOk\('\/api\/ram-batch\/start', fields\)/);
});
test('ramBatchRender shows the phase, the counts and the stop button', () => {
@@ -152,12 +164,14 @@ test('ramBatchRender shows the phase, the counts and the stop button', () => {
setEl('ram-batch-notice', '');
setEl('ram-batch-counts', '');
setEl('ram-batch-attempts', '');
setEl('ram-batch-clear', '');
// waiting for a card
app.setLast({ active: true, phase: 'waiting_card', attempts: [], counts: {}, attempts_total: 0, notice: '' });
app.ramBatchRender();
assert.strictEqual(els['ram-batch-state'].textContent, 'Waiting for a card...');
assert.ok(els['ram-batch-stop'].classes['hidden'] === undefined);
assert.ok(els['ram-batch-start'].classes['hidden']);
assert.ok(els['ram-batch-clear'].disabled); // the server refuses a clear while active
// installing card X with the live step
app.setLast({ active: true, phase: 'installing', attempts: [], counts: { installed: 2, failed: 1, skipped: 0 },
attempts_total: 3, notice: 'Card 897… installed.', current: { iccid: '8970119000004600098', preset: 'Alfa' } });
@@ -177,6 +191,7 @@ test('ramBatchRender shows the phase, the counts and the stop button', () => {
assert.strictEqual(els['ram-batch-state'].textContent, 'Batch stopped');
assert.ok(els['ram-batch-start'].classes['hidden'] === undefined);
assert.ok(els['ram-batch-stop'].classes['hidden']);
assert.ok(!els['ram-batch-clear'].disabled); // clearable again
});
test('ramBatchApplyStatus fetches only when the summary changed', async () => {