v3.29.2: the strict row rule for the outfile import

The format is positional: an empty value drops its token, so the parser
now requires every data row to have exactly the header's column count.
That catches an empty value anywhere - including a middle one, which
would otherwise shift the following columns (a missing ADM made the KIC
columns read the KIK values: valid hex, wrong preset) - and a KICn/KIDn
pair declared in the header must carry a usable hex value in every row
(a placeholder fails).

A violation makes the whole file invalid: the report's error names the
line, no presets are returned, and an import (mode) answers 400 - it is
never partially imported.  The PWA disables the Import button while the
report carries an error and guards the call.

Unchanged per-row reports: a non-hex PIN/PUK/ADM/PSK value (dropped, the
card kept), an unsupported key size (that keyset skipped), a card whose
remaining keysets are unusable (skipped).  All three real samples satisfy
the rule exactly (11/11, 18/18, 18/18).

Docs: help EN/RU, api.md, the repo AGENTS.  Tests: the short/long row and
the placeholder-keyset file errors, the valid-file round trip, the
endpoint's refusal (nothing stored), the PWA's disabled button.
926 frontend + 945 Python; sw cache simple-v449.
This commit is contained in:
2026-10-15 00:43:53 +03:00
parent 8a66ea5a0a
commit 94d7cff485
11 changed files with 105 additions and 24 deletions
+16
View File
@@ -310,6 +310,22 @@ test('a declined replace confirmation aborts the import', async () => {
assert.strictEqual(app.calls.length, 1);
});
test('the Import button is disabled while the report has a file error (v3.29.2)', () => {
clearEls();
setEl('cards-outfile-panel', '');
app.state = { text: TEXT, file: 'bad.out', name: 'x', algorithm16: '3des2', mode: 'merge',
preview: { presets: [], report: { error: 'line 2 has 2 values, the header declares 3 columns - the file is invalid' } } };
app.tars = 0;
app.cardsOutfileRender();
assert.match(els['cards-outfile-panel'].innerHTML, /id="cards-of-import" disabled/);
assert.match(els['cards-outfile-panel'].innerHTML, /the header declares 3 columns/);
// a valid report keeps it enabled
app.state.preview = { presets: [{ iccid: '8970119000004600098', keysets: [{ kic: '15', kid: '15' }] }],
report: { columns: ['ICCID', 'KIC1', 'KID1'], rows: 1, cards: 1 } };
app.cardsOutfileRender();
assert.match(els['cards-outfile-panel'].innerHTML, /id="cards-of-import" onclick/);
});
test('a row keeps its chosen keyset through the DOM round-trip', () => {
clearEls();
app.state = { text: TEXT, name: 'x', algorithm16: '3des2', mode: 'merge',