[FIX] E2E testing - fix router, EmailService, frontend data mapping

Critical fixes discovered during end-to-end testing:

Router (index.php):
- Rewrote route resolution engine to properly handle /{id}/subAction patterns
- All routes like GET /assessments/{id}/questions, POST /incidents/{id}/early-warning,
  GET /organizations/{id}/members now resolve correctly
- Routes with kebab-case sub-actions (early-warning, ai-analyze) now convert to camelCase
- Controller methods receive correct arguments via spread operator

EmailService.php:
- Fix PHP parse error: ?? operator cannot be used inside string interpolation {}
- Extract incident_code to variable before interpolation (3 occurrences)

assessment.html:
- Fix data structure handling: API returns categories with nested questions array
- Fix field names: question_code (not question_id), response_value (not compliance_level)
- Fix answer enum values: not_implemented/partial/implemented (not Italian)
- Fix question text field: question_text (not text/question/title)
- Show NIS2 article and ISO 27001 control references
- Fix response restoration from existing answers

dashboard.html:
- Fix data mapping from overview API response structure
- risks.total instead of open_risks, policies array instead of approved_policies
- Calculate training completion percentage from training object
- Load deadlines/activity from dedicated endpoints (not included in overview)

onboarding.html:
- Fix field name mismatches: annual_turnover_eur, contact_email, contact_phone,
  full_name, phone (matching OnboardingController expected params)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 19:40:26 +01:00
co-authored by Claude Opus 4.6
parent 6f4b457ce0
commit bcc5a2b003
6 changed files with 262 additions and 318 deletions
+28 -18
View File
@@ -193,7 +193,16 @@
try {
const result = await api.getAssessmentQuestions(currentAssessmentId);
if (result.success && result.data) {
questions = result.data.questions || result.data;
// API returns array of {category_id, category_title, questions: [...]}
const data = result.data;
if (Array.isArray(data) && data.length > 0 && data[0].questions) {
questions = [];
data.forEach(cat => {
(cat.questions || []).forEach(q => questions.push(q));
});
} else {
questions = data;
}
organizeByCategory();
showWizard();
renderCurrentQuestion();
@@ -217,14 +226,14 @@
questions: catMap[name]
}));
// Ripristina risposte precedenti
// Ripristina risposte precedenti (backend puts response_value directly on question)
questions.forEach(q => {
if (q.response) {
if (q.response_value) {
responses[q.id] = {
answer: q.response.answer || q.response.compliance_level,
maturity: q.response.maturity_level,
notes: q.response.notes || '',
evidence: q.response.evidence_description || ''
answer: q.response_value,
maturity: q.maturity_level ? parseInt(q.maturity_level) : 0,
notes: q.notes || '',
evidence: q.evidence_description || ''
};
}
});
@@ -277,19 +286,20 @@
const r = responses[q.id] || {};
const answers = [
{ value: 'non_implementato', label: 'Non Implementato', cls: 'danger' },
{ value: 'parziale', label: 'Parziale', cls: 'warning' },
{ value: 'implementato', label: 'Implementato', cls: 'success' },
{ value: 'non_applicabile', label: 'Non Applicabile', cls: 'neutral' },
{ value: 'not_implemented', label: 'Non Implementato', cls: 'danger' },
{ value: 'partial', label: 'Parziale', cls: 'warning' },
{ value: 'implemented', label: 'Implementato', cls: 'success' },
{ value: 'not_applicable', label: 'Non Applicabile', cls: 'neutral' },
];
let html = `
<div style="margin-bottom:20px;">
<p style="font-size:1rem; font-weight:600; color:var(--gray-900); margin-bottom:4px;">
${escapeHtml(q.text || q.question || q.title || '')}
${escapeHtml(q.question_text || q.text || q.title || '')}
</p>
${q.description ? `<p class="text-muted" style="font-size:0.8125rem;">${escapeHtml(q.description)}</p>` : ''}
${q.reference ? `<span class="tag mt-8">Rif: ${escapeHtml(q.reference)}</span>` : ''}
${q.guidance_it ? `<p class="text-muted" style="font-size:0.8125rem;">${escapeHtml(q.guidance_it)}</p>` : ''}
${q.nis2_article ? `<span class="tag mt-8">Art. ${escapeHtml(q.nis2_article)}</span>` : ''}
${q.iso27001_control ? `<span class="tag mt-8" style="margin-left:4px;">ISO ${escapeHtml(q.iso27001_control)}</span>` : ''}
</div>
<div class="form-group">
@@ -392,8 +402,8 @@
try {
const result = await api.saveAssessmentResponse(currentAssessmentId, {
question_id: q.id,
compliance_level: r.answer,
question_code: q.question_code,
response_value: r.answer,
maturity_level: r.maturity,
notes: r.notes,
evidence_description: r.evidence
@@ -420,8 +430,8 @@
if (r && r.answer) {
// Salva in background
api.saveAssessmentResponse(currentAssessmentId, {
question_id: q.id,
compliance_level: r.answer,
question_code: q.question_code,
response_value: r.answer,
maturity_level: r.maturity,
notes: r.notes,
evidence_description: r.evidence