fix(csv): export dal browser legge nominale/tolleranze/descrizione da m.subtask (#31)
buildMeasurementCSV leggeva m.subtask_name/unit/nominal/tolerance_plus/ tolerance_minus/operator_name: nessuno di questi campi esiste su MeasurementResponse. I dati reali stanno annidati in m.subtask (description, unit, nominal, utl, ltl - misurati.py:task_complete() li allega lato server). tolleranza + = utl-nominal, tolleranza - = nominal-ltl, solo quando entrambi i lati sono presenti (nessun NaN su tolleranza a un lato solo). Unità dalla quota, "mm" solo se assente. Operatore: measured_by (id), l'unico campo reale - non esiste un nome operatore da nessuna parte in questi dati. Test node in tests/js/test_csv_export.js (stesso pattern di test_numpad.js). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* csv-export.js behaviour, run with plain node (no browser, no test framework).
|
||||
*
|
||||
* Covers gitea:Adriano/TieMeasureFlow#31: buildMeasurementCSV used to read
|
||||
* m.subtask_name/m.unit/m.nominal/m.tolerance_plus/m.tolerance_minus/
|
||||
* m.operator_name, none of which MeasurementResponse (or the server's
|
||||
* task_complete() enrichment) ever puts directly on the measurement - the
|
||||
* real data is nested under m.subtask (description/unit/nominal/utl/ltl),
|
||||
* so every one of those columns came out empty. #22 (formula injection) is
|
||||
* covered separately, by t_csv_injection tests elsewhere.
|
||||
*
|
||||
* Run from src/frontend/flask_app: node tests/js/test_csv_export.js
|
||||
*/
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// csv-export.js reads window.CSV_I18N for header labels; no browser here.
|
||||
global.window = {};
|
||||
|
||||
const src = fs.readFileSync(
|
||||
path.join(__dirname, '..', '..', 'static', 'js', 'csv-export.js'), 'utf8',
|
||||
);
|
||||
eval(src + ';global.csvExport = csvExport;');
|
||||
|
||||
let failures = 0;
|
||||
function check(label, cond, extra) {
|
||||
if (cond) {
|
||||
console.log('ok - ' + label);
|
||||
} else {
|
||||
failures++;
|
||||
console.error('NOT OK - ' + label + (extra ? ' (' + extra + ')' : ''));
|
||||
}
|
||||
}
|
||||
|
||||
// A measurement shaped exactly as task_complete() builds it: the flat
|
||||
// MeasurementResponse fields plus the server-attached "subtask" and
|
||||
// "task_info" nested objects (measure.py, task_complete()).
|
||||
const MEASUREMENT = {
|
||||
id: 42,
|
||||
subtask_id: 21,
|
||||
version_id: 5,
|
||||
measured_by: 3,
|
||||
value: 10.234,
|
||||
pass_fail: 'pass',
|
||||
deviation: 0.234,
|
||||
lot_number: 'L1',
|
||||
serial_number: 'S1',
|
||||
input_method: 'usb_caliper',
|
||||
measured_at: '2026-09-21T10:15:00',
|
||||
subtask: {
|
||||
id: 21, task_id: 11, marker_number: 1,
|
||||
description: "Altezza foro",
|
||||
nominal: 10.0, utl: 10.5, uwl: 10.2, lwl: 9.8, ltl: 9.5,
|
||||
unit: 'mm',
|
||||
},
|
||||
task_info: { id: 11, title: 'Quota 1', order_index: 0 },
|
||||
};
|
||||
|
||||
{
|
||||
const e = csvExport();
|
||||
const csv = e.buildMeasurementCSV([MEASUREMENT]);
|
||||
const lines = csv.split('\n');
|
||||
const header = lines[0];
|
||||
const row = lines[1].split(';');
|
||||
|
||||
check('riga generata', lines.length >= 2, csv);
|
||||
check('descrizione dal subtask', row.includes('Altezza foro'), row.join('|'));
|
||||
check('nominale valorizzato (10,000)', row.some(c => c === '10,000' || c === '10'), row.join('|'));
|
||||
// utl(10.5) - nominal(10.0) = 0.5 ; nominal(10.0) - ltl(9.5) = 0.5
|
||||
check('tolleranza + = utl - nominal (0,5)', row.some(c => c.replace(',', '.') === '0.5'), row.join('|'));
|
||||
check('unita dalla quota (mm)', row.includes('mm'), row.join('|'));
|
||||
check('operatore = measured_by (3), non vuoto', row.includes('3'), row.join('|'));
|
||||
check("nessuna colonna 'undefined'", !csv.includes('undefined'), csv);
|
||||
}
|
||||
|
||||
// unit falls back to 'mm' only when the subtask itself has none.
|
||||
{
|
||||
const e = csvExport();
|
||||
const noUnit = { ...MEASUREMENT, subtask: { ...MEASUREMENT.subtask, unit: undefined } };
|
||||
const csv = e.buildMeasurementCSV([noUnit]);
|
||||
const row = csv.split('\n')[1].split(';');
|
||||
check('unita assente -> ricade su mm', row.includes('mm'), row.join('|'));
|
||||
}
|
||||
|
||||
// A one-sided tolerance (only utl, say) must not fabricate the missing side.
|
||||
{
|
||||
const e = csvExport();
|
||||
const oneSided = {
|
||||
...MEASUREMENT,
|
||||
subtask: { ...MEASUREMENT.subtask, utl: 10.5, ltl: null },
|
||||
};
|
||||
const csv = e.buildMeasurementCSV([oneSided]);
|
||||
const row = csv.split('\n')[1].split(';');
|
||||
// tolerance_minus column (index 7) must be empty, not "NaN" or "0".
|
||||
check('tolleranza - assente resta vuota (ltl null)', row[7] === '', row.join('|'));
|
||||
check('tolleranza + presente comunque (0,5)', row[6].replace(',', '.') === '0.5', row.join('|'));
|
||||
}
|
||||
|
||||
if (failures > 0) {
|
||||
console.error(failures + ' test falliti su csv-export.js');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('tutti i test csv-export.js superati');
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Runs the plain-node csv-export.js behaviour test (tests/js/test_csv_export.js).
|
||||
|
||||
Same thin pytest wrapper pattern as test_numpad_js.py: shells out to ``node``
|
||||
and asserts the script's own exit code. Skips (does not fail) when node is
|
||||
not installed.
|
||||
|
||||
Covers gitea:Adriano/TieMeasureFlow#31.
|
||||
"""
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
FLASK_APP_DIR = Path(__file__).resolve().parent.parent
|
||||
JS_TEST = Path(__file__).resolve().parent / "js" / "test_csv_export.js"
|
||||
|
||||
|
||||
def test_csv_export_js_measurement_row_behaviour():
|
||||
node = shutil.which("node")
|
||||
if not node:
|
||||
pytest.skip("node non installato: test JS di csv-export.js saltato")
|
||||
|
||||
result = subprocess.run(
|
||||
[node, str(JS_TEST)],
|
||||
cwd=str(FLASK_APP_DIR),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, (
|
||||
f"tests/js/test_csv_export.js fallito (exit {result.returncode})\n"
|
||||
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
|
||||
)
|
||||
Reference in New Issue
Block a user