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:
+32
-10
@@ -244,19 +244,41 @@ class OptionTests(unittest.TestCase):
|
||||
self.assertNotIn('pskKey', res['presets'][0])
|
||||
self.assertTrue(any('PSK is not 16 bytes' in s for s in res['report']['skipped']))
|
||||
|
||||
def test_short_rows_and_bad_optional_codes_are_reported(self):
|
||||
# a row shorter than the *used* columns (the keyset) is skipped
|
||||
def test_a_row_with_a_missing_or_extra_value_is_a_file_error(self):
|
||||
"""The file is positional: an empty value drops its token, so a token
|
||||
count other than the header's column count means an empty (or extra)
|
||||
value - the whole file is refused (v3.29.2)."""
|
||||
# a missing trailing value
|
||||
text = ('ICCID KIC1 KID1\n' + DIGITS_F + ' ' + K16A + '\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertEqual(res['presets'], [])
|
||||
self.assertTrue(any('too few columns' in s for s in res['report']['skipped']))
|
||||
# a missing optional column is simply absent, the card kept
|
||||
self.assertIn('line 2 has 2 values, the header declares 3 columns',
|
||||
res['report']['error'])
|
||||
# ...a missing middle value (which used to shift the columns silently)
|
||||
text = ('ICCID KIC1 KID1 ADM\n' + DIGITS_F + ' ' + K16A + ' ' + K16B + '\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertEqual(res['presets'], [])
|
||||
self.assertIn('the file is invalid', res['report']['error'])
|
||||
# ...and an extra value
|
||||
text = ('ICCID KIC1 KID1\n' + DIGITS_F + ' ' + K16A + ' ' + K16B + ' EXTRA\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertEqual(res['presets'], [])
|
||||
self.assertIn('4 values', res['report']['error'])
|
||||
# a declared keyset without a usable value is a file error too
|
||||
text = ('ICCID KIC1 KID1 KIC2 KID2\n' + DIGITS_F + ' ' + K16A + ' ' + K16B +
|
||||
' - ' + K16C + '\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertEqual(res['presets'], [])
|
||||
self.assertIn('keyset 2 has no usable KIC/KID value', res['report']['error'])
|
||||
# a valid file keeps parsing (an exact count everywhere)
|
||||
text = ('ICCID KIC1 KID1 PIN1 ADM\n' + DIGITS_F + ' ' + K16A + ' ' + K16B +
|
||||
' 1234\n')
|
||||
' 1234 3632333239463337\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertEqual(len(res['presets']), 1)
|
||||
self.assertEqual(res['presets'][0]['pin1'], '1234')
|
||||
self.assertNotIn('adm', res['presets'][0])
|
||||
self.assertEqual(res['presets'][0]['adm'], '3632333239463337')
|
||||
|
||||
def test_bad_optional_codes_are_reported_per_row(self):
|
||||
# the ADM column is the key's hex value, stored as-is (v3.28.2): the
|
||||
# length varies, and the value is never ASCII-decoded
|
||||
text = ('ICCID KIC1 KID1 ADM\n' + DIGITS_F + ' ' + K16A + ' ' + K16B +
|
||||
@@ -314,9 +336,9 @@ class OptionTests(unittest.TestCase):
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertNotIn('puk1', res['presets'][0])
|
||||
self.assertTrue(any("PUK1 'abcd'" in s for s in res['report']['skipped']))
|
||||
# ...while a column absent from a short row stays silent (not present)
|
||||
# ...while an *empty* PUK1 token cannot exist in a whitespace-separated
|
||||
# row: the count would change, which is a file error (v3.29.2)
|
||||
text = ('ICCID KIC1 KID1 PIN1 PUK1\n' + DIGITS_F + ' ' + K16A + ' ' + K16B + ' 1234\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertEqual(res['presets'][0]['pin1'], '1234')
|
||||
self.assertNotIn('puk1', res['presets'][0])
|
||||
self.assertEqual([s for s in res['report']['skipped'] if 'PUK1' in s], [])
|
||||
self.assertEqual(res['presets'], [])
|
||||
self.assertIn('the file is invalid', res['report']['error'])
|
||||
|
||||
Reference in New Issue
Block a user