feat(system-hub): extend diagnostics + tests

This commit is contained in:
CyberMind-FR 2025-12-30 16:21:37 +01:00
parent eeaa0d2f37
commit 22e0bc9272
10 changed files with 1890 additions and 34 deletions

View File

@ -176,7 +176,8 @@
"Bash(pkill -f \"local-build.sh build secubox-app-crowdsec\")",
"Bash(go version:*)",
"Bash(timeout 600 ./secubox-tools/local-build.sh:*)",
"Bash(timeout 300 ./secubox-tools/local-build.sh:*)"
"Bash(timeout 300 ./secubox-tools/local-build.sh:*)",
"Bash(paste:*)"
]
}
}

View File

@ -142,6 +142,19 @@ var callUploadDiagnostics = rpc.declare({
expect: {}
});
var callListDiagnosticProfiles = rpc.declare({
object: 'luci.system-hub',
method: 'list_diagnostic_profiles',
expect: {}
});
var callGetDiagnosticProfile = rpc.declare({
object: 'luci.system-hub',
method: 'get_diagnostic_profile',
params: ['name'],
expect: {}
});
var callRemoteStatus = rpc.declare({
object: 'luci.system-hub',
method: 'remote_status',
@ -206,16 +219,24 @@ return baseclass.extend({
getSettings: callGetSettings,
saveSettings: callSaveSettings,
collectDiagnostics: function(includeLogs, includeConfig, includeNetwork, anonymize) {
collectDiagnostics: function(includeLogs, includeConfig, includeNetwork, anonymize, profile) {
return callCollectDiagnostics({
include_logs: includeLogs ? 1 : 0,
include_config: includeConfig ? 1 : 0,
include_network: includeNetwork ? 1 : 0,
anonymize: anonymize ? 1 : 0
anonymize: anonymize ? 1 : 0,
profile: profile || 'manual'
});
},
listDiagnostics: callListDiagnostics,
listDiagnosticProfiles: callListDiagnosticProfiles,
getDiagnosticProfile: function(name) {
return callGetDiagnosticProfile({ name: name });
},
downloadDiagnostic: function(name) {
return callDownloadDiagnostic({ name: name });
},

View File

@ -15,11 +15,16 @@ Theme.init({ language: shLang });
return view.extend({
load: function() {
return API.listDiagnostics();
return Promise.all([
API.listDiagnostics(),
API.listDiagnosticProfiles()
]);
},
render: function(data) {
this.currentArchives = (data && data.archives) || [];
this.currentArchives = (data && data[0] && data[0].archives) || [];
this.profiles = (data && data[1] && data[1].profiles) || [];
this.selectedProfile = null;
var archives = this.currentArchives;
var view = E('div', { 'class': 'system-hub-dashboard' }, [
@ -27,7 +32,28 @@ return view.extend({
ThemeAssets.stylesheet('common.css'),
ThemeAssets.stylesheet('dashboard.css'),
HubNav.renderTabs('diagnostics'),
// Profile Selector
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '📋'),
'Profils de Diagnostic'
])
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', {
'class': 'profile-grid',
'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1rem;'
}, this.renderProfileButtons()),
E('div', {
'id': 'profile-description',
'class': 'sh-info-box',
'style': 'display: none; padding: 0.75rem; background: rgba(66, 153, 225, 0.1); border-left: 3px solid #4299e1; border-radius: 4px;'
})
])
]),
// Collect Diagnostics
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
@ -55,17 +81,14 @@ return view.extend({
// Quick Tests
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🧪'), 'Tests Rapides' ])
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🧪'), 'Tests Rapides' ]),
E('div', { 'id': 'test-profile-info', 'style': 'font-size: 12px; color: #888; display: none;' })
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;' }, [
this.renderTestButton('🌐', 'Test Connectivité', 'Ping WAN & DNS', 'connectivity'),
this.renderTestButton('📡', 'Test DNS', 'Résolution de noms', 'dns'),
this.renderTestButton('⚡', 'Test Latence', 'Ping vers Google', 'latency'),
this.renderTestButton('💾', 'Test Disque', 'Lecture/Écriture', 'disk'),
this.renderTestButton('🔒', 'Test Firewall', 'Règles actives', 'firewall'),
this.renderTestButton('📶', 'Test WiFi', 'Signal et clients', 'wifi')
])
E('div', {
'id': 'quick-tests-grid',
'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;'
}, this.renderTestButtons(null))
])
]),
@ -111,19 +134,6 @@ return view.extend({
]);
},
renderTestButton: function(icon, label, desc, type) {
var self = this;
return E('button', {
'class': 'sh-btn',
'style': 'flex-direction: column; height: auto; padding: 16px;',
'click': function() { self.runTest(type); }
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 8px;' }, icon),
E('span', { 'style': 'font-weight: 600;' }, label),
E('span', { 'style': 'font-size: 10px; color: #707080;' }, desc)
]);
},
renderArchiveList: function(archives) {
if (!archives.length) {
return [
@ -135,7 +145,7 @@ return view.extend({
renderArchiveItem: function(archive) {
var name = archive.name || '';
var size = api.formatBytes(archive.size || 0);
var size = API.formatBytes(archive.size || 0);
var date = archive.created_at || '';
return E('div', { 'style': 'display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #1a1a24; border-radius: 8px; margin-bottom: 10px;' }, [
E('div', { 'style': 'display: flex; align-items: center; gap: 12px;' }, [
@ -171,10 +181,10 @@ return view.extend({
E('div', { 'class': 'spinning' })
]);
API.collectDiagnostics(includeLogs, includeConfig, includeNetwork, anonymize).then(L.bind(function(result) {
API.collectDiagnostics(includeLogs, includeConfig, includeNetwork, anonymize, this.selectedProfile || 'manual').then(L.bind(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', {}, '✅ Archive créée: ' + result.file + ' (' + api.formatBytes(result.size) + ')'), 'success');
ui.addNotification(null, E('p', {}, '✅ Archive créée: ' + result.file + ' (' + API.formatBytes(result.size) + ')'), 'success');
this.refreshArchives();
} else {
ui.addNotification(null, E('p', {}, '❌ Erreur lors de la collecte'), 'error');
@ -283,6 +293,135 @@ return view.extend({
}, this));
},
// Mapping des tests avec leurs métadonnées
testDefinitions: {
'connectivity': { icon: '🌐', label: 'Test Connectivité', desc: 'Ping WAN & DNS' },
'dns': { icon: '📡', label: 'Test DNS', desc: 'Résolution de noms' },
'latency': { icon: '⚡', label: 'Test Latence', desc: 'Ping vers Google' },
'disk': { icon: '💾', label: 'Test Disque', desc: 'Lecture/Écriture' },
'firewall': { icon: '🔒', label: 'Test Firewall', desc: 'Règles actives' },
'wifi': { icon: '📶', label: 'Test WiFi', desc: 'Signal et clients' }
},
renderTestButtons: function(profileTests) {
var self = this;
var testsToShow = [];
if (profileTests) {
// Filtrer selon les tests du profil
testsToShow = profileTests.split(',').map(function(t) { return t.trim(); });
} else {
// Afficher tous les tests par défaut
testsToShow = ['connectivity', 'dns', 'latency', 'disk', 'firewall', 'wifi'];
}
return testsToShow.map(function(testType) {
var testDef = self.testDefinitions[testType];
if (!testDef) return null;
return E('button', {
'class': 'sh-btn',
'style': 'flex-direction: column; height: auto; padding: 16px;',
'click': function() { self.runTest(testType); }
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 8px;' }, testDef.icon),
E('span', { 'style': 'font-weight: 600;' }, testDef.label),
E('span', { 'style': 'font-size: 10px; color: #707080;' }, testDef.desc)
]);
}).filter(function(btn) { return btn !== null; });
},
updateQuickTests: function(profile) {
var grid = document.getElementById('quick-tests-grid');
var profileInfo = document.getElementById('test-profile-info');
if (!grid) return;
// Vider la grille
grid.innerHTML = '';
// Rendre les nouveaux boutons
var buttons = this.renderTestButtons(profile ? profile.tests : null);
buttons.forEach(function(btn) {
grid.appendChild(btn);
});
// Mettre à jour l'info du profil
if (profileInfo) {
if (profile && profile.tests) {
var testCount = profile.tests.split(',').length;
profileInfo.textContent = '📋 ' + testCount + ' test(s) recommandé(s) pour ce profil';
profileInfo.style.display = 'block';
} else {
profileInfo.style.display = 'none';
}
}
},
renderProfileButtons: function() {
var self = this;
return (this.profiles || []).map(function(profile) {
return E('button', {
'class': 'sh-btn sh-btn-secondary profile-btn',
'data-profile': profile.name,
'style': 'display: flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1rem;',
'click': L.bind(self.selectProfile, self, profile.name)
}, [
E('span', { 'style': 'font-size: 1.5rem;' }, profile.icon || '📋'),
E('span', {}, profile.label || profile.name)
]);
});
},
selectProfile: function(profileName) {
var self = this;
// Highlight selected button
document.querySelectorAll('.profile-btn').forEach(function(btn) {
if (btn.dataset.profile === profileName) {
btn.classList.add('active');
btn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
btn.style.color = 'white';
} else {
btn.classList.remove('active');
btn.style.background = '';
btn.style.color = '';
}
});
// Load profile config and update toggles
API.getDiagnosticProfile(profileName).then(function(profile) {
self.selectedProfile = profileName;
// Update toggles to match profile
self.setToggle('diag_logs', profile.include_logs);
self.setToggle('diag_config', profile.include_config);
self.setToggle('diag_network', profile.include_network);
self.setToggle('diag_anonymize', profile.anonymize);
// Show description
var descBox = document.getElementById('profile-description');
if (descBox) {
descBox.style.display = 'block';
descBox.textContent = '📝 ' + (profile.description || '');
}
// Update quick tests to show only relevant tests
self.updateQuickTests(profile);
});
},
setToggle: function(id, value) {
var toggle = document.getElementById(id);
if (toggle) {
if (value == 1 || value === true) {
toggle.classList.add('active');
} else {
toggle.classList.remove('active');
}
}
},
handleSaveApply: null,
handleSave: null,
handleReset: null

View File

@ -604,16 +604,54 @@ collect_diagnostics() {
read input
[ -n "$input" ] && json_load "$input"
local include_logs include_config include_network anonymize
local include_logs include_config include_network anonymize profile
json_get_var profile profile 2>/dev/null
json_get_var include_logs include_logs 2>/dev/null
json_get_var include_config include_config 2>/dev/null
json_get_var include_network include_network 2>/dev/null
json_get_var anonymize anonymize 2>/dev/null
# If profile specified, load its config and override flags
if [ -n "$profile" ] && [ "$profile" != "manual" ]; then
case "$profile" in
network-issues)
include_logs=1
include_config=1
include_network=1
anonymize=0
;;
performance-problems)
include_logs=1
include_config=0
include_network=0
anonymize=0
;;
security-audit)
include_logs=1
include_config=1
include_network=1
anonymize=1
;;
wifi-problems)
include_logs=1
include_config=1
include_network=1
anonymize=0
;;
full-diagnostic)
include_logs=1
include_config=1
include_network=1
anonymize=1
;;
esac
fi
[ -z "$include_logs" ] && include_logs=1
[ -z "$include_config" ] && include_config=1
[ -z "$include_network" ] && include_network=1
[ -z "$anonymize" ] && anonymize=0
[ -z "$profile" ] && profile="manual"
local timestamp="$(date +%Y%m%d-%H%M%S)"
local workdir="$DIAG_DIR/work-$timestamp"
@ -622,6 +660,9 @@ collect_diagnostics() {
# System info
{
echo "=== System Information ==="
echo "Profile: $profile"
echo "Generated: $(date)"
echo
uname -a
echo
echo "--- CPU ---"
@ -681,7 +722,7 @@ collect_diagnostics() {
} >"$workdir/network.txt"
fi
local archive_name="diagnostics-$(hostname)-$timestamp.tar.gz"
local archive_name="diagnostics-$(hostname)-${profile}-$timestamp.tar.gz"
local archive_path="$DIAG_DIR/$archive_name"
tar -czf "$archive_path" -C "$workdir" . >/dev/null 2>&1 || {
@ -825,6 +866,169 @@ run_diagnostic_test() {
json_dump
}
# List all available diagnostic profiles
list_diagnostic_profiles() {
json_init
json_add_array "profiles"
# Profile 1: Network Issues
json_add_object ""
json_add_string "name" "network-issues"
json_add_string "label" "Problèmes Réseau"
json_add_string "icon" "🌐"
json_add_string "description" "Diagnostique les pannes de routage, DNS, perte de paquets et blocages firewall"
json_add_string "tests" "connectivity,dns,latency,firewall"
json_add_int "include_logs" 1
json_add_int "include_config" 1
json_add_int "include_network" 1
json_add_int "anonymize" 0
json_close_object
# Profile 2: Performance Problems
json_add_object ""
json_add_string "name" "performance-problems"
json_add_string "label" "Problèmes Performance"
json_add_string "icon" "⚡"
json_add_string "description" "Identifie les goulots d'étranglement CPU/mémoire, problèmes d'E/S disque"
json_add_string "tests" "disk,latency"
json_add_int "include_logs" 1
json_add_int "include_config" 0
json_add_int "include_network" 0
json_add_int "anonymize" 0
json_close_object
# Profile 3: Security Audit
json_add_object ""
json_add_string "name" "security-audit"
json_add_string "label" "Audit Sécurité"
json_add_string "icon" "🔐"
json_add_string "description" "Revue des règles firewall, logs d'authentification, exposition réseau"
json_add_string "tests" "firewall"
json_add_int "include_logs" 1
json_add_int "include_config" 1
json_add_int "include_network" 1
json_add_int "anonymize" 1
json_close_object
# Profile 4: WiFi Problems
json_add_object ""
json_add_string "name" "wifi-problems"
json_add_string "label" "Problèmes WiFi"
json_add_string "icon" "📶"
json_add_string "description" "Analyse la force du signal, interférences canaux, associations clients"
json_add_string "tests" "wifi,connectivity,latency"
json_add_int "include_logs" 1
json_add_int "include_config" 1
json_add_int "include_network" 1
json_add_int "anonymize" 0
json_close_object
# Profile 5: Full Diagnostic
json_add_object ""
json_add_string "name" "full-diagnostic"
json_add_string "label" "Diagnostic Complet"
json_add_string "icon" "📋"
json_add_string "description" "Diagnostic complet pour escalade support - collecte tout"
json_add_string "tests" "connectivity,dns,latency,disk,firewall,wifi"
json_add_int "include_logs" 1
json_add_int "include_config" 1
json_add_int "include_network" 1
json_add_int "anonymize" 1
json_close_object
# TODO: Add UCI custom profiles here
# config_load system-hub
# list_custom_profiles() { ... }
json_close_array
json_dump
}
# Get specific diagnostic profile
get_diagnostic_profile() {
read input
[ -n "$input" ] && json_load "$input"
local profile_name
json_get_var profile_name name
# Hardcoded profiles
case "$profile_name" in
network-issues)
json_init
json_add_string "name" "network-issues"
json_add_string "label" "Problèmes Réseau"
json_add_string "icon" "🌐"
json_add_string "description" "Diagnostique les pannes de routage, DNS, perte de paquets et blocages firewall"
json_add_string "tests" "connectivity,dns,latency,firewall"
json_add_int "include_logs" 1
json_add_int "include_config" 1
json_add_int "include_network" 1
json_add_int "anonymize" 0
json_dump
;;
performance-problems)
json_init
json_add_string "name" "performance-problems"
json_add_string "label" "Problèmes Performance"
json_add_string "icon" "⚡"
json_add_string "description" "Identifie les goulots d'étranglement CPU/mémoire, problèmes d'E/S disque"
json_add_string "tests" "disk,latency"
json_add_int "include_logs" 1
json_add_int "include_config" 0
json_add_int "include_network" 0
json_add_int "anonymize" 0
json_dump
;;
security-audit)
json_init
json_add_string "name" "security-audit"
json_add_string "label" "Audit Sécurité"
json_add_string "icon" "🔐"
json_add_string "description" "Revue des règles firewall, logs d'authentification, exposition réseau"
json_add_string "tests" "firewall"
json_add_int "include_logs" 1
json_add_int "include_config" 1
json_add_int "include_network" 1
json_add_int "anonymize" 1
json_dump
;;
wifi-problems)
json_init
json_add_string "name" "wifi-problems"
json_add_string "label" "Problèmes WiFi"
json_add_string "icon" "📶"
json_add_string "description" "Analyse la force du signal, interférences canaux, associations clients"
json_add_string "tests" "wifi,connectivity,latency"
json_add_int "include_logs" 1
json_add_int "include_config" 1
json_add_int "include_network" 1
json_add_int "anonymize" 0
json_dump
;;
full-diagnostic)
json_init
json_add_string "name" "full-diagnostic"
json_add_string "label" "Diagnostic Complet"
json_add_string "icon" "📋"
json_add_string "description" "Diagnostic complet pour escalade support - collecte tout"
json_add_string "tests" "connectivity,dns,latency,disk,firewall,wifi"
json_add_int "include_logs" 1
json_add_int "include_config" 1
json_add_int "include_network" 1
json_add_int "anonymize" 1
json_dump
;;
*)
# TODO: Check UCI for custom profile
json_init
json_add_boolean "success" 0
json_add_string "error" "Profile not found: $profile_name"
json_dump
;;
esac
}
remote_status() {
local section="system-hub.remote"
local enabled=$(uci -q get $section.rustdesk_enabled || echo 0)
@ -1278,9 +1482,12 @@ case "$1" in
"include_logs": 1,
"include_config": 1,
"include_network": 1,
"anonymize": 1
"anonymize": 1,
"profile": "string"
},
"list_diagnostics": {},
"list_diagnostic_profiles": {},
"get_diagnostic_profile": { "name": "string" },
"download_diagnostic": { "name": "string" },
"delete_diagnostic": { "name": "string" },
"run_diagnostic_test": { "test": "string" },
@ -1320,6 +1527,8 @@ EOF
save_settings) save_settings ;;
collect_diagnostics) collect_diagnostics ;;
list_diagnostics) list_diagnostics ;;
list_diagnostic_profiles) list_diagnostic_profiles ;;
get_diagnostic_profile) get_diagnostic_profile ;;
download_diagnostic) download_diagnostic ;;
delete_diagnostic) delete_diagnostic ;;
run_diagnostic_test) run_diagnostic_test ;;

View File

@ -14,6 +14,8 @@
"get_storage",
"get_settings",
"list_diagnostics",
"list_diagnostic_profiles",
"get_diagnostic_profile",
"download_diagnostic",
"run_diagnostic_test",
"remote_status",

View File

@ -0,0 +1,341 @@
# Manual Test Procedure - Diagnostic Profiles
## Test Environment Setup
- **Router**: OpenWrt with luci-app-system-hub installed
- **Access**: LuCI web interface
- **Browser**: Chrome, Firefox, or Edge (latest version)
- **Prerequisites**: Admin access to the router
---
## Test Case 1: Profile Selector Display
**Objective**: Verify that the profile selector card displays correctly with all 5 profiles
**Steps**:
1. Login to LuCI web interface
2. Navigate to **System → System Hub → Diagnostics**
3. Observe the "Profils de Diagnostic" card at the top
**Expected Results**:
- ✅ Profile selector card is visible
- ✅ 5 profile buttons are displayed in a grid layout
- ✅ Each button shows an icon and label:
- 🌐 Problèmes Réseau
- ⚡ Problèmes Performance
- 🔐 Audit Sécurité
- 📶 Problèmes WiFi
- 📋 Diagnostic Complet
- ✅ Buttons are clickable and styled properly
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Test Case 2: Network Issues Profile
**Objective**: Test the network-issues profile workflow
**Steps**:
1. Navigate to **System → System Hub → Diagnostics**
2. Click the "🌐 Problèmes Réseau" button
3. Observe the toggle switches below
4. Observe the description box
5. Click "📦 Générer Archive"
6. Wait for completion notification
7. Check the "Archives Récentes" section
**Expected Results**:
- ✅ Profile button is highlighted (gradient background)
- ✅ Description appears: "Diagnostique les pannes de routage..."
- ✅ Toggles are set to:
- Logs: ON
- Configuration: ON
- Network: ON
- Anonymize: OFF
- ✅ Success notification appears: "✅ Archive créée: ..."
- ✅ Archive filename contains "network-issues"
- ✅ Archive appears in recent archives list
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Test Case 3: Performance Problems Profile
**Objective**: Test the performance-problems profile with different toggles
**Steps**:
1. Navigate to **System → System Hub → Diagnostics**
2. Click the "⚡ Problèmes Performance" button
3. Observe toggle switches
4. Verify that Config toggle is OFF
5. Click "📦 Générer Archive"
6. Verify archive creation
**Expected Results**:
- ✅ Profile button is highlighted
- ✅ Description appears about CPU/memory bottlenecks
- ✅ Toggles are set to:
- Logs: ON
- Configuration: OFF (different from network profile!)
- Network: OFF
- Anonymize: OFF
- ✅ Archive filename contains "performance-problems"
- ✅ Archive is smaller than full diagnostic (no config/network)
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Test Case 4: Security Audit Profile with Anonymization
**Objective**: Verify anonymization works with security-audit profile
**Steps**:
1. Navigate to **System → System Hub → Diagnostics**
2. Click the "🔐 Audit Sécurité" button
3. Observe that Anonymize toggle is ON
4. Click "📦 Générer Archive"
5. Download the archive
6. Extract and inspect config files
**Expected Results**:
- ✅ Profile button is highlighted
- ✅ Anonymize toggle is ON
- ✅ Archive filename contains "security-audit"
- ✅ Config files in archive have sensitive data removed (passwords, keys, IPs, MACs)
- ✅ Logs still contain useful diagnostic information
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Test Case 5: WiFi Problems Profile
**Objective**: Test WiFi-specific profile
**Steps**:
1. Navigate to **System → System Hub → Diagnostics**
2. Click the "📶 Problèmes WiFi" button
3. Observe toggles
4. Click "📦 Générer Archive"
5. Verify archive creation
**Expected Results**:
- ✅ Profile button is highlighted
- ✅ Description mentions signal strength and interference
- ✅ All toggles ON except anonymize
- ✅ Archive filename contains "wifi-problems"
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Test Case 6: Full Diagnostic Profile
**Objective**: Test the complete diagnostic profile
**Steps**:
1. Navigate to **System → System Hub → Diagnostics**
2. Click the "📋 Diagnostic Complet" button
3. Observe toggles
4. Click "📦 Générer Archive"
5. Compare archive size with other profiles
**Expected Results**:
- ✅ Profile button is highlighted
- ✅ Description mentions "support escalation"
- ✅ All toggles ON including anonymize
- ✅ Archive filename contains "full-diagnostic"
- ✅ Archive is largest (contains everything)
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Test Case 7: Profile Override
**Objective**: Verify user can manually adjust toggles after selecting profile
**Steps**:
1. Navigate to **System → System Hub → Diagnostics**
2. Click the "🌐 Problèmes Réseau" profile
3. Manually toggle OFF the "Configuration" switch
4. Click "📦 Générer Archive"
5. Verify archive creation
**Expected Results**:
- ✅ Manual toggle change is respected
- ✅ Archive is created without config files
- ✅ Filename still contains "network-issues"
- ✅ User override works correctly
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Test Case 8: Profile Switching
**Objective**: Test switching between different profiles
**Steps**:
1. Navigate to **System → System Hub → Diagnostics**
2. Click "🌐 Problèmes Réseau" profile
3. Observe toggles state
4. Click "⚡ Problèmes Performance" profile
5. Observe toggles state changes
6. Switch to "🔐 Audit Sécurité"
7. Observe toggles state changes
**Expected Results**:
- ✅ Only one profile button is highlighted at a time
- ✅ Toggles update correctly for each profile
- ✅ Description updates for each profile
- ✅ No visual glitches or errors
- ✅ Profile highlighting transitions smoothly
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Test Case 9: Archive Download and Deletion
**Objective**: Test downloading and deleting profile-based archives
**Steps**:
1. Create archive with any profile
2. Click download button for the archive
3. Verify file downloads to browser
4. Click delete button
5. Confirm deletion
6. Verify archive removed from list
**Expected Results**:
- ✅ Download initiates correctly
- ✅ Filename matches what was shown
- ✅ File can be extracted (valid .tar.gz)
- ✅ Deletion confirmation dialog appears
- ✅ Archive is removed from list
- ✅ Archive file is deleted from router
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Test Case 10: Multiple Profiles in Sequence
**Objective**: Create archives with multiple different profiles in sequence
**Steps**:
1. Click "🌐 Problèmes Réseau" → Generate Archive
2. Wait for completion
3. Click "⚡ Problèmes Performance" → Generate Archive
4. Wait for completion
5. Click "🔐 Audit Sécurité" → Generate Archive
6. Check archives list
**Expected Results**:
- ✅ All 3 archives created successfully
- ✅ Each archive has correct profile name in filename
- ✅ Archives have different sizes (based on included data)
- ✅ All archives appear in recent archives list
- ✅ No errors or conflicts
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Regression Tests
### RT-1: Existing Diagnostics Still Work
**Objective**: Ensure non-profile diagnostics still function
**Steps**:
1. Don't select any profile
2. Manually set toggles (Logs: ON, Config: ON, Network: OFF, Anonymize: OFF)
3. Click "📦 Générer Archive"
**Expected Results**:
- ✅ Archive is created successfully
- ✅ Filename contains "manual" instead of profile name
- ✅ Only logs and config are included (no network info)
- ✅ Backward compatibility maintained
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
### RT-2: Quick Tests Unchanged
**Objective**: Verify quick test buttons still work
**Steps**:
1. Navigate to **System → System Hub → Diagnostics**
2. Scroll to "Tests Rapides" section
3. Click each test button:
- Connectivity
- DNS
- Latency
- Disk
- Firewall
- WiFi
**Expected Results**:
- ✅ All test buttons are visible
- ✅ Each test executes and shows results
- ✅ Tests are independent of profile selection
- ✅ No errors or regressions
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
### RT-3: Upload to Support
**Objective**: Verify upload functionality still works with profiles
**Steps**:
1. Create archive with any profile
2. Click "☁️ Envoyer au Support"
3. Select the profile-based archive
4. Confirm upload
**Expected Results**:
- ✅ Upload dialog appears
- ✅ Profile-based archives are listed
- ✅ Upload process works (or shows appropriate error if no endpoint configured)
- ✅ No regressions in upload functionality
**Result**: ☐ PASS ☐ FAIL
**Notes**: ___________________________________________
---
## Sign-off
**Tester Name**: _______________________________
**Date**: _______________________________
**Build Version**: _______________________________
**Browser**: _______________________________
**Overall Result**: ☐ ALL PASS ☐ SOME FAILURES
**Notes/Issues**:
___________________________________________
___________________________________________
___________________________________________
**Failures to Address**:
___________________________________________
___________________________________________
___________________________________________

View File

@ -0,0 +1,397 @@
# System Hub Testing Guide
Comprehensive testing suite for the diagnostic profiles feature in luci-app-system-hub.
## Test Structure
```
tests/
├── README.md # This file
├── test-profiles.sh # Unit tests (shell-based)
├── MANUAL_TESTS.md # Manual testing checklist
├── integration/
│ └── test-diagnostic-workflow.sh # End-to-end workflow test
└── frontend/
└── test-diagnostics-ui.js # Browser console tests
```
## Quick Start
### Run All Tests
```bash
# On the router (via SSH)
cd /usr/share/system-hub/tests # or wherever tests are installed
./test-profiles.sh
./integration/test-diagnostic-workflow.sh
```
### Run Individual Test Suites
See detailed instructions below for each test type.
---
## 1. Unit Tests
**File**: `test-profiles.sh`
**Type**: Shell script
**Execution**: On the router via SSH
**Duration**: ~30 seconds
### What It Tests
- Profile listing (list_diagnostic_profiles RPC)
- Profile retrieval (get_diagnostic_profile RPC)
- Profile-based diagnostic collection
- Archive filename formatting
- Profile flag overrides
- All 5 profiles individually
### Running Unit Tests
```bash
# SSH to the router
ssh root@192.168.8.205
# Navigate to tests directory
cd /usr/share/system-hub/tests # adjust path if needed
# Run unit tests
./test-profiles.sh
```
### Expected Output
```
========================================
System Hub Diagnostic Profiles Unit Tests
========================================
Starting test run at Mon Dec 30 16:00:00 2025
TEST 1: list_diagnostic_profiles returns valid JSON
==================================================
✅ PASS: list_diagnostic_profiles returns valid JSON
TEST 2: At least 5 profiles available
======================================
✅ PASS: At least 5 profiles returned (found 5)
...
========================================
Test Results Summary
========================================
Passed: 15
Failed: 0
Total: 15
✅ All tests passed!
```
### Exit Codes
- `0`: All tests passed
- `1`: Some tests failed
---
## 2. Integration Tests
**File**: `integration/test-diagnostic-workflow.sh`
**Type**: Shell script
**Execution**: On the router via SSH
**Duration**: ~60 seconds
### What It Tests
Complete end-to-end workflow:
1. List available profiles
2. Select a specific profile
3. Collect diagnostics with profile
4. Verify archive creation
5. Verify profile name in filename
6. List diagnostics
7. Download archive (RPC)
8. Delete archive
9. Verify deletion
### Running Integration Tests
```bash
# SSH to the router
ssh root@192.168.8.205
# Navigate to integration tests
cd /usr/share/system-hub/tests/integration
# Run workflow test
./test-diagnostic-workflow.sh
```
### Expected Output
```
========================================
Diagnostic Workflow Integration Test
========================================
Testing complete workflow with performance-problems profile
STEP 1: Listing available profiles
---------------------------------------
✅ SUCCESS: Profiles listed
- network-issues
- performance-problems
- security-audit
- wifi-problems
- full-diagnostic
STEP 2: Selecting performance-problems profile
---------------------------------------
✅ SUCCESS: Profile retrieved
Name: performance-problems
Label: Problèmes Performance
Tests: disk,latency
...
========================================
✅ ALL WORKFLOW STEPS PASSED
========================================
```
---
## 3. Frontend Validation Tests
**File**: `frontend/test-diagnostics-ui.js`
**Type**: JavaScript (browser console)
**Execution**: In browser developer tools
**Duration**: ~5 seconds
### What It Tests
- Profile grid rendering
- Profile button attributes
- Profile selection highlighting
- Toggle switch updates
- Description box display
- All 5 profiles present in UI
### Running Frontend Tests
1. **Open LuCI** in your browser
2. **Navigate** to System → System Hub → Diagnostics
3. **Open Developer Tools** (F12)
4. **Switch to Console tab**
5. **Copy and paste** the entire contents of `test-diagnostics-ui.js`
6. **Run** the tests:
```javascript
runAllTests()
```
### Expected Output
```
========================================
Diagnostic Profiles Frontend Tests
========================================
Starting tests at 4:00:00 PM
📋 TEST 1: Profile grid renders
================================
✅ PASS: Profile grid element exists
✅ PASS: At least 5 profile buttons rendered (found 5, expected at least 5)
📋 TEST 2: Profile buttons have correct attributes
===================================================
✅ PASS: Profile button has data-profile attribute
✅ PASS: Profile button contains icon span
...
========================================
Test Results Summary
========================================
Passed: 12
Failed: 0
Total: 12
✅ All tests passed!
```
---
## 4. Manual Tests
**File**: `MANUAL_TESTS.md`
**Type**: Documented checklist
**Execution**: Manual testing in LuCI
**Duration**: ~30-45 minutes
### What It Tests
- Comprehensive user workflows
- Visual appearance and styling
- Error handling
- Edge cases
- Regression testing
### Running Manual Tests
1. **Open** `MANUAL_TESTS.md`
2. **Follow** each test case step-by-step
3. **Record** results in the document
4. **Sign off** at the end
### Test Cases Included
- Test Case 1: Profile Selector Display
- Test Case 2: Network Issues Profile
- Test Case 3: Performance Problems Profile
- Test Case 4: Security Audit Profile with Anonymization
- Test Case 5: WiFi Problems Profile
- Test Case 6: Full Diagnostic Profile
- Test Case 7: Profile Override
- Test Case 8: Profile Switching
- Test Case 9: Archive Download and Deletion
- Test Case 10: Multiple Profiles in Sequence
- Regression Tests (3 additional tests)
---
## Testing Checklist
Use this checklist to ensure complete test coverage:
- [ ] Unit tests run successfully
- [ ] Integration tests run successfully
- [ ] Frontend tests run successfully
- [ ] Manual test cases completed
- [ ] All test cases passed
- [ ] Edge cases tested
- [ ] Regression tests passed
- [ ] Documentation reviewed
---
## Continuous Integration
### Automated Testing
To run tests automatically on each deployment:
```bash
# Example CI script
#!/bin/bash
echo "Running System Hub Profile Tests..."
# Deploy to test router
./deploy.sh test-router
# Run tests via SSH
ssh root@test-router "cd /usr/share/system-hub/tests && ./test-profiles.sh"
TEST_RESULT=$?
ssh root@test-router "cd /usr/share/system-hub/tests/integration && ./test-diagnostic-workflow.sh"
INTEGRATION_RESULT=$?
# Check results
if [ $TEST_RESULT -eq 0 ] && [ $INTEGRATION_RESULT -eq 0 ]; then
echo "✅ All automated tests passed"
exit 0
else
echo "❌ Tests failed"
exit 1
fi
```
---
## Troubleshooting
### Unit Tests Failing
**Problem**: `ubus call` commands fail
**Solutions**:
- Verify rpcd is running: `/etc/init.d/rpcd status`
- Restart rpcd: `/etc/init.d/rpcd restart`
- Check ACL permissions are deployed
- Verify user has luci-app-system-hub ACL
### Integration Tests Failing
**Problem**: Archive creation fails
**Solutions**:
- Check `/tmp/system-hub/diagnostics/` directory exists
- Verify disk space: `df -h`
- Check system logs: `logread | grep system-hub`
### Frontend Tests Failing
**Problem**: Profile buttons not found
**Solutions**:
- Clear browser cache (Ctrl+Shift+R)
- Verify diagnostics.js is deployed
- Check browser console for JavaScript errors
- Ensure LuCI cache is cleared: `rm -rf /tmp/luci-*`
### Manual Tests Failing
**Problem**: UI doesn't match expected results
**Solutions**:
- Logout and login again (ACL refresh)
- Clear browser cache completely
- Verify all files deployed correctly
- Check browser compatibility (use latest Chrome/Firefox)
---
## Test Maintenance
### Adding New Tests
1. **Unit Tests**: Add new test functions to `test-profiles.sh`
2. **Integration Tests**: Create new script in `integration/`
3. **Frontend Tests**: Add new test functions to `test-diagnostics-ui.js`
4. **Manual Tests**: Add new test case to `MANUAL_TESTS.md`
### Updating Tests
When adding new profiles or features:
1. Update test expectations (profile count, names, etc.)
2. Add tests for new functionality
3. Run full test suite to ensure no regressions
4. Update this README if test procedures change
---
## Contributing
When contributing tests:
- Follow existing patterns and conventions
- Add comments explaining what is being tested
- Include both positive and negative test cases
- Update documentation
- Test on actual hardware before submitting
---
## Support
For questions about testing:
- Check test output for specific error messages
- Review router logs: `logread`
- Check RPC backend: `ubus call luci.system-hub list`
- Verify deployment: `opkg list-installed | grep system-hub`
---
## License
Same license as luci-app-system-hub (MIT or GPL-2.0).

View File

@ -0,0 +1,270 @@
/**
* Frontend Validation Tests for Diagnostic Profiles
*
* USAGE:
* 1. Navigate to System Hub Diagnostics in LuCI
* 2. Open browser console (F12)
* 3. Copy and paste this entire file
* 4. Run: runAllTests()
*/
// Test counter
var testResults = {
passed: 0,
failed: 0,
tests: []
};
// Helper functions
function assert(condition, testName) {
if (condition) {
console.log('%c✅ PASS: ' + testName, 'color: green');
testResults.passed++;
testResults.tests.push({ name: testName, status: 'PASS' });
return true;
} else {
console.error('❌ FAIL: ' + testName);
testResults.failed++;
testResults.tests.push({ name: testName, status: 'FAIL' });
return false;
}
}
function assertExists(element, testName) {
return assert(element !== null && element !== undefined, testName);
}
function assertCount(actual, expected, testName) {
if (actual >= expected) {
return assert(true, testName + ' (found ' + actual + ', expected at least ' + expected + ')');
} else {
console.error('Expected at least ' + expected + ' but found ' + actual);
return assert(false, testName);
}
}
// Test 1: Profile grid renders
function testProfileGridRenders() {
console.log('\n📋 TEST 1: Profile grid renders');
console.log('================================');
var grid = document.querySelector('.profile-grid');
assertExists(grid, 'Profile grid element exists');
var buttons = grid ? grid.querySelectorAll('button.profile-btn') : [];
assertCount(buttons.length, 5, 'At least 5 profile buttons rendered');
}
// Test 2: Profile buttons have correct attributes
function testProfileButtonAttributes() {
console.log('\n📋 TEST 2: Profile buttons have correct attributes');
console.log('===================================================');
var buttons = document.querySelectorAll('.profile-btn');
if (buttons.length === 0) {
assert(false, 'No profile buttons found');
return;
}
var firstButton = buttons[0];
assertExists(firstButton.dataset.profile, 'Profile button has data-profile attribute');
var hasIcon = firstButton.querySelector('span') !== null;
assert(hasIcon, 'Profile button contains icon span');
}
// Test 3: Profile selection highlights button
function testProfileSelection() {
console.log('\n📋 TEST 3: Profile selection highlights button');
console.log('================================================');
var buttons = document.querySelectorAll('.profile-btn');
if (buttons.length === 0) {
assert(false, 'No profile buttons found');
return;
}
// Click first profile button
var firstButton = buttons[0];
var profileName = firstButton.dataset.profile;
console.log('Clicking profile:', profileName);
firstButton.click();
// Wait a bit for the async operation
setTimeout(function() {
var isActive = firstButton.classList.contains('active') ||
firstButton.style.background.includes('gradient');
assert(isActive, 'Clicked profile button is highlighted');
// Check other buttons are not active
var otherButtonsInactive = true;
for (var i = 1; i < buttons.length; i++) {
if (buttons[i].classList.contains('active')) {
otherButtonsInactive = false;
break;
}
}
assert(otherButtonsInactive, 'Other profile buttons are not highlighted');
}, 500);
}
// Test 4: Profile description appears
function testProfileDescriptionAppears() {
console.log('\n📋 TEST 4: Profile description appears');
console.log('=======================================');
var descBox = document.getElementById('profile-description');
assertExists(descBox, 'Profile description box exists');
// Click a profile button to trigger description
var buttons = document.querySelectorAll('.profile-btn');
if (buttons.length > 0) {
buttons[0].click();
setTimeout(function() {
var isVisible = descBox && descBox.style.display !== 'none';
assert(isVisible, 'Description box is visible after profile selection');
var hasContent = descBox && descBox.textContent.length > 0;
assert(hasContent, 'Description box contains text');
}, 500);
}
}
// Test 5: Toggle switches update when profile selected
function testToggleSwitchesUpdate() {
console.log('\n📋 TEST 5: Toggle switches update with profile');
console.log('================================================');
var logsToggle = document.getElementById('diag_logs');
var configToggle = document.getElementById('diag_config');
var networkToggle = document.getElementById('diag_network');
var anonymizeToggle = document.getElementById('diag_anonymize');
assertExists(logsToggle, 'Logs toggle exists');
assertExists(configToggle, 'Config toggle exists');
assertExists(networkToggle, 'Network toggle exists');
assertExists(anonymizeToggle, 'Anonymize toggle exists');
// Click network-issues profile (should have all logs/config/network enabled)
var buttons = document.querySelectorAll('.profile-btn');
var networkBtn = null;
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].dataset.profile === 'network-issues') {
networkBtn = buttons[i];
break;
}
}
if (networkBtn) {
networkBtn.click();
setTimeout(function() {
var logsActive = logsToggle && logsToggle.classList.contains('active');
var configActive = configToggle && configToggle.classList.contains('active');
var networkActive = networkToggle && networkToggle.classList.contains('active');
assert(logsActive, 'Logs toggle activated by network-issues profile');
assert(configActive, 'Config toggle activated by network-issues profile');
assert(networkActive, 'Network toggle activated by network-issues profile');
}, 500);
} else {
assert(false, 'Could not find network-issues profile button');
}
}
// Test 6: Diagnostic collection includes profile (mock test)
function testDiagnosticCollectionProfile() {
console.log('\n📋 TEST 6: Diagnostic collection includes profile');
console.log('==================================================');
// This is a simulated test - we check that the API call would include profile
// We can't actually trigger collection without mocking
console.log('Note: This would require mocking API.collectDiagnostics()');
console.log('Manual test: Click "Generate Archive" and verify archive filename includes profile name');
assert(true, 'Profile parameter mechanism in place (manual verification required)');
}
// Test 7: All 5 profiles are present
function testAllProfilesPresent() {
console.log('\n📋 TEST 7: All 5 expected profiles are present');
console.log('================================================');
var expectedProfiles = [
'network-issues',
'performance-problems',
'security-audit',
'wifi-problems',
'full-diagnostic'
];
var buttons = document.querySelectorAll('.profile-btn');
var foundProfiles = [];
for (var i = 0; i < buttons.length; i++) {
foundProfiles.push(buttons[i].dataset.profile);
}
expectedProfiles.forEach(function(profile) {
var found = foundProfiles.indexOf(profile) !== -1;
assert(found, 'Profile "' + profile + '" is present');
});
}
// Run all tests
function runAllTests() {
console.clear();
console.log('========================================');
console.log('Diagnostic Profiles Frontend Tests');
console.log('========================================');
console.log('Starting tests at ' + new Date().toLocaleTimeString());
console.log('');
testResults = { passed: 0, failed: 0, tests: [] };
// Run tests
testProfileGridRenders();
testProfileButtonAttributes();
testAllProfilesPresent();
// Tests with delays
setTimeout(function() {
testProfileSelection();
testProfileDescriptionAppears();
testToggleSwitchesUpdate();
testDiagnosticCollectionProfile();
// Summary after all tests complete
setTimeout(function() {
console.log('\n========================================');
console.log('Test Results Summary');
console.log('========================================');
console.log('%cPassed: ' + testResults.passed, 'color: green; font-weight: bold');
console.log('%cFailed: ' + testResults.failed, 'color: red; font-weight: bold');
console.log('Total: ' + (testResults.passed + testResults.failed));
console.log('');
if (testResults.failed === 0) {
console.log('%c✅ All tests passed!', 'color: green; font-weight: bold; font-size: 16px');
} else {
console.log('%c❌ Some tests failed', 'color: red; font-weight: bold; font-size: 16px');
console.log('');
console.log('Failed tests:');
testResults.tests.forEach(function(test) {
if (test.status === 'FAIL') {
console.log(' - ' + test.name);
}
});
}
}, 2000);
}, 500);
}
console.log('✅ Frontend test suite loaded');
console.log('Run tests with: runAllTests()');

View File

@ -0,0 +1,203 @@
#!/bin/sh
# Integration Test: Complete diagnostic workflow with profiles
# Tests end-to-end user workflow from profile selection to archive download
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "========================================"
echo "Diagnostic Workflow Integration Test"
echo "========================================"
echo "Testing complete workflow with performance-problems profile"
echo ""
# Step 1: List available profiles
echo -e "${YELLOW}STEP 1:${NC} Listing available profiles"
echo "---------------------------------------"
profiles=$(ubus call luci.system-hub list_diagnostic_profiles 2>&1)
if [ $? -ne 0 ]; then
echo -e "${RED}❌ FAILED${NC}: Could not list profiles"
echo "$profiles"
exit 1
fi
echo -e "${GREEN}✅ SUCCESS${NC}: Profiles listed"
echo "$profiles" | jsonfilter -e '@.profiles[*].name' | sed 's/^/ - /'
echo ""
# Step 2: Select performance-problems profile
echo -e "${YELLOW}STEP 2:${NC} Selecting performance-problems profile"
echo "---------------------------------------"
profile=$(ubus call luci.system-hub get_diagnostic_profile '{"name":"performance-problems"}' 2>&1)
if [ $? -ne 0 ]; then
echo -e "${RED}❌ FAILED${NC}: Could not get profile"
echo "$profile"
exit 1
fi
echo -e "${GREEN}✅ SUCCESS${NC}: Profile retrieved"
echo " Name: $(echo "$profile" | jsonfilter -e '@.name')"
echo " Label: $(echo "$profile" | jsonfilter -e '@.label')"
echo " Tests: $(echo "$profile" | jsonfilter -e '@.tests')"
echo ""
# Step 3: Collect diagnostics using profile
echo -e "${YELLOW}STEP 3:${NC} Collecting diagnostics with profile"
echo "---------------------------------------"
archive=$(ubus call luci.system-hub collect_diagnostics '{"profile":"performance-problems"}' 2>&1)
if [ $? -ne 0 ]; then
echo -e "${RED}❌ FAILED${NC}: Could not collect diagnostics"
echo "$archive"
exit 1
fi
success=$(echo "$archive" | jsonfilter -e '@.success')
if [ "$success" != "true" ]; then
echo -e "${RED}❌ FAILED${NC}: Collection reported failure"
echo "$archive"
exit 1
fi
filename=$(echo "$archive" | jsonfilter -e '@.file')
size=$(echo "$archive" | jsonfilter -e '@.size')
echo -e "${GREEN}✅ SUCCESS${NC}: Archive created"
echo " Filename: $filename"
echo " Size: $size bytes"
echo ""
# Step 4: Verify archive exists
echo -e "${YELLOW}STEP 4:${NC} Verifying archive exists on filesystem"
echo "---------------------------------------"
archive_path="/tmp/system-hub/diagnostics/$filename"
if [ ! -f "$archive_path" ]; then
echo -e "${RED}❌ FAILED${NC}: Archive file not found at $archive_path"
exit 1
fi
echo -e "${GREEN}✅ SUCCESS${NC}: Archive file exists"
ls -lh "$archive_path" | awk '{print " Size: " $5 " Modified: " $6 " " $7 " " $8}'
echo ""
# Step 5: Verify archive contains profile name
echo -e "${YELLOW}STEP 5:${NC} Verifying profile name in filename"
echo "---------------------------------------"
if echo "$filename" | grep -q "performance-problems"; then
echo -e "${GREEN}✅ SUCCESS${NC}: Filename contains profile name"
echo " $filename"
else
echo -e "${RED}❌ FAILED${NC}: Profile name not in filename"
echo " Expected: performance-problems"
echo " Found: $filename"
exit 1
fi
echo ""
# Step 6: List diagnostics to verify it appears
echo -e "${YELLOW}STEP 6:${NC} Listing diagnostics archives"
echo "---------------------------------------"
diagnostics=$(ubus call luci.system-hub list_diagnostics 2>&1)
if [ $? -ne 0 ]; then
echo -e "${RED}❌ FAILED${NC}: Could not list diagnostics"
echo "$diagnostics"
rm -f "$archive_path"
exit 1
fi
if echo "$diagnostics" | grep -q "$filename"; then
echo -e "${GREEN}✅ SUCCESS${NC}: Archive appears in diagnostics list"
echo "$diagnostics" | jsonfilter -e '@.archives[*].name' | grep "$filename" | sed 's/^/ - /'
else
echo -e "${RED}❌ FAILED${NC}: Archive not in diagnostics list"
rm -f "$archive_path"
exit 1
fi
echo ""
# Step 7: Download archive (simulate)
echo -e "${YELLOW}STEP 7:${NC} Downloading archive (verifying RPC)"
echo "---------------------------------------"
download=$(ubus call luci.system-hub download_diagnostic "{\"name\":\"$filename\"}" 2>&1)
if [ $? -ne 0 ]; then
echo -e "${RED}❌ FAILED${NC}: Could not download archive"
echo "$download"
rm -f "$archive_path"
exit 1
fi
download_success=$(echo "$download" | jsonfilter -e '@.success')
if [ "$download_success" != "true" ]; then
echo -e "${RED}❌ FAILED${NC}: Download reported failure"
rm -f "$archive_path"
exit 1
fi
echo -e "${GREEN}✅ SUCCESS${NC}: Download RPC successful"
echo " (Archive data would be base64 encoded for browser download)"
echo ""
# Step 8: Delete archive
echo -e "${YELLOW}STEP 8:${NC} Deleting archive"
echo "---------------------------------------"
delete=$(ubus call luci.system-hub delete_diagnostic "{\"name\":\"$filename\"}" 2>&1)
if [ $? -ne 0 ]; then
echo -e "${RED}❌ FAILED${NC}: Could not delete archive"
echo "$delete"
rm -f "$archive_path"
exit 1
fi
delete_success=$(echo "$delete" | jsonfilter -e '@.success')
if [ "$delete_success" != "true" ]; then
echo -e "${RED}❌ FAILED${NC}: Delete reported failure"
rm -f "$archive_path"
exit 1
fi
echo -e "${GREEN}✅ SUCCESS${NC}: Archive deleted"
echo ""
# Step 9: Verify deletion
echo -e "${YELLOW}STEP 9:${NC} Verifying archive was deleted"
echo "---------------------------------------"
if [ -f "$archive_path" ]; then
echo -e "${RED}❌ FAILED${NC}: Archive file still exists after deletion"
rm -f "$archive_path"
exit 1
fi
echo -e "${GREEN}✅ SUCCESS${NC}: Archive file removed from filesystem"
echo ""
# Final summary
echo "========================================"
echo -e "${GREEN}✅ ALL WORKFLOW STEPS PASSED${NC}"
echo "========================================"
echo "Successfully tested complete diagnostic workflow:"
echo " 1. List profiles"
echo " 2. Get specific profile"
echo " 3. Collect diagnostics with profile"
echo " 4. Verify archive creation"
echo " 5. Verify profile name in filename"
echo " 6. List diagnostics"
echo " 7. Download archive (RPC)"
echo " 8. Delete archive"
echo " 9. Verify deletion"
echo ""
exit 0

View File

@ -0,0 +1,273 @@
#!/bin/sh
# System Hub Diagnostic Profiles - Unit Tests
# Tests RPCD backend functions in isolation
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
# Test counters
TESTS_PASSED=0
TESTS_FAILED=0
TEST_NAME=""
# Color codes for output
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Assert functions
assert_equals() {
local expected="$1"
local actual="$2"
local test_name="$3"
if [ "$expected" = "$actual" ]; then
echo -e "${GREEN}✅ PASS${NC}: $test_name"
TESTS_PASSED=$((TESTS_PASSED + 1))
return 0
else
echo -e "${RED}❌ FAIL${NC}: $test_name"
echo " Expected: $expected"
echo " Actual: $actual"
TESTS_FAILED=$((TESTS_FAILED + 1))
return 1
fi
}
assert_contains() {
local haystack="$1"
local needle="$2"
local test_name="$3"
if echo "$haystack" | grep -q "$needle"; then
echo -e "${GREEN}✅ PASS${NC}: $test_name"
TESTS_PASSED=$((TESTS_PASSED + 1))
return 0
else
echo -e "${RED}❌ FAIL${NC}: $test_name"
echo " Expected to find: $needle"
echo " In: $haystack"
TESTS_FAILED=$((TESTS_FAILED + 1))
return 1
fi
}
assert_json_valid() {
local json="$1"
local test_name="$2"
if echo "$json" | jsonfilter -e '@' >/dev/null 2>&1; then
echo -e "${GREEN}✅ PASS${NC}: $test_name"
TESTS_PASSED=$((TESTS_PASSED + 1))
return 0
else
echo -e "${RED}❌ FAIL${NC}: $test_name"
echo " Invalid JSON: $json"
TESTS_FAILED=$((TESTS_FAILED + 1))
return 1
fi
}
# Test 1: Profile listing returns valid JSON
test_list_profiles_json() {
echo ""
echo "TEST 1: list_diagnostic_profiles returns valid JSON"
echo "=================================================="
local result=$(ubus call luci.system-hub list_diagnostic_profiles 2>&1)
assert_json_valid "$result" "list_diagnostic_profiles returns valid JSON"
}
# Test 2: At least 5 profiles returned
test_list_profiles_count() {
echo ""
echo "TEST 2: At least 5 profiles available"
echo "======================================"
local result=$(ubus call luci.system-hub list_diagnostic_profiles 2>&1)
local count=$(echo "$result" | jsonfilter -e '@.profiles[*]' 2>/dev/null | wc -l)
if [ "$count" -ge 5 ]; then
assert_equals "5" "$count" "At least 5 profiles returned (found $count)"
else
assert_equals "5" "$count" "At least 5 profiles returned"
fi
}
# Test 3: Profile names are correct
test_profile_names() {
echo ""
echo "TEST 3: Profile names are correct"
echo "=================================="
local result=$(ubus call luci.system-hub list_diagnostic_profiles 2>&1)
assert_contains "$result" "network-issues" "Contains network-issues profile"
assert_contains "$result" "performance-problems" "Contains performance-problems profile"
assert_contains "$result" "security-audit" "Contains security-audit profile"
assert_contains "$result" "wifi-problems" "Contains wifi-problems profile"
assert_contains "$result" "full-diagnostic" "Contains full-diagnostic profile"
}
# Test 4: Get specific profile - network-issues
test_get_profile_network() {
echo ""
echo "TEST 4: Get network-issues profile"
echo "==================================="
local result=$(ubus call luci.system-hub get_diagnostic_profile '{"name":"network-issues"}' 2>&1)
assert_json_valid "$result" "get_diagnostic_profile returns valid JSON"
local name=$(echo "$result" | jsonfilter -e '@.name' 2>/dev/null)
assert_equals "network-issues" "$name" "Profile name is network-issues"
local tests=$(echo "$result" | jsonfilter -e '@.tests' 2>/dev/null)
assert_contains "$tests" "connectivity" "Profile contains connectivity test"
assert_contains "$tests" "dns" "Profile contains dns test"
local include_logs=$(echo "$result" | jsonfilter -e '@.include_logs' 2>/dev/null)
assert_equals "1" "$include_logs" "Profile includes logs"
}
# Test 5: Get specific profile - performance-problems
test_get_profile_performance() {
echo ""
echo "TEST 5: Get performance-problems profile"
echo "========================================="
local result=$(ubus call luci.system-hub get_diagnostic_profile '{"name":"performance-problems"}' 2>&1)
local name=$(echo "$result" | jsonfilter -e '@.name' 2>/dev/null)
assert_equals "performance-problems" "$name" "Profile name is performance-problems"
local include_config=$(echo "$result" | jsonfilter -e '@.include_config' 2>/dev/null)
assert_equals "0" "$include_config" "Performance profile does not include config"
}
# Test 6: Get invalid profile
test_get_profile_invalid() {
echo ""
echo "TEST 6: Get invalid profile returns error"
echo "=========================================="
local result=$(ubus call luci.system-hub get_diagnostic_profile '{"name":"invalid-profile"}' 2>&1)
assert_json_valid "$result" "Invalid profile returns valid JSON error"
assert_contains "$result" "error" "Response contains error field"
}
# Test 7: Collect diagnostics with profile
test_collect_with_profile() {
echo ""
echo "TEST 7: Collect diagnostics with profile"
echo "========================================="
local result=$(ubus call luci.system-hub collect_diagnostics '{"profile":"network-issues"}' 2>&1)
assert_json_valid "$result" "collect_diagnostics with profile returns valid JSON"
local success=$(echo "$result" | jsonfilter -e '@.success' 2>/dev/null)
assert_equals "true" "$success" "Diagnostic collection succeeds"
local filename=$(echo "$result" | jsonfilter -e '@.file' 2>/dev/null)
assert_contains "$filename" "network-issues" "Archive filename contains profile name"
# Cleanup
if [ -n "$filename" ]; then
rm -f "/tmp/system-hub/diagnostics/$filename"
fi
}
# Test 8: Profile filename format
test_profile_filename_format() {
echo ""
echo "TEST 8: Profile filename format is correct"
echo "==========================================="
local result=$(ubus call luci.system-hub collect_diagnostics '{"profile":"security-audit"}' 2>&1)
local filename=$(echo "$result" | jsonfilter -e '@.file' 2>/dev/null)
# Format should be: diagnostics-{hostname}-{profile}-{timestamp}.tar.gz
assert_contains "$filename" "security-audit" "Filename contains profile name"
assert_contains "$filename" ".tar.gz" "Filename has .tar.gz extension"
assert_contains "$filename" "diagnostics-" "Filename starts with diagnostics-"
# Cleanup
if [ -n "$filename" ]; then
rm -f "/tmp/system-hub/diagnostics/$filename"
fi
}
# Test 9: Profile overrides flags
test_profile_overrides_flags() {
echo ""
echo "TEST 9: Profile overrides individual flags"
echo "==========================================="
# Performance profile has include_config=0
local result=$(ubus call luci.system-hub collect_diagnostics '{"profile":"performance-problems","include_config":1}' 2>&1)
local success=$(echo "$result" | jsonfilter -e '@.success' 2>/dev/null)
assert_equals "true" "$success" "Collection with profile override succeeds"
local filename=$(echo "$result" | jsonfilter -e '@.file' 2>/dev/null)
assert_contains "$filename" "performance-problems" "Filename contains profile name"
# Cleanup
if [ -n "$filename" ]; then
rm -f "/tmp/system-hub/diagnostics/$filename"
fi
}
# Test 10: All profiles can be retrieved
test_all_profiles_retrievable() {
echo ""
echo "TEST 10: All 5 profiles can be retrieved individually"
echo "======================================================"
local profiles="network-issues performance-problems security-audit wifi-problems full-diagnostic"
for profile in $profiles; do
local result=$(ubus call luci.system-hub get_diagnostic_profile "{\"name\":\"$profile\"}" 2>&1)
local name=$(echo "$result" | jsonfilter -e '@.name' 2>/dev/null)
assert_equals "$profile" "$name" "Profile $profile can be retrieved"
done
}
# Run all tests
echo "========================================"
echo "System Hub Diagnostic Profiles Unit Tests"
echo "========================================"
echo "Starting test run at $(date)"
echo ""
test_list_profiles_json
test_list_profiles_count
test_profile_names
test_get_profile_network
test_get_profile_performance
test_get_profile_invalid
test_collect_with_profile
test_profile_filename_format
test_profile_overrides_flags
test_all_profiles_retrievable
# Summary
echo ""
echo "========================================"
echo "Test Results Summary"
echo "========================================"
echo -e "${GREEN}Passed: $TESTS_PASSED${NC}"
echo -e "${RED}Failed: $TESTS_FAILED${NC}"
echo "Total: $((TESTS_PASSED + TESTS_FAILED))"
echo ""
if [ $TESTS_FAILED -eq 0 ]; then
echo -e "${GREEN}✅ All tests passed!${NC}"
exit 0
else
echo -e "${RED}❌ Some tests failed${NC}"
exit 1
fi