From 2776b50c0627de743482262559798747d362c5a9 Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Fri, 29 May 2026 18:47:55 +0000 Subject: [PATCH 01/14] =?UTF-8?q?Edit=20des=20fichiers=20mod=C3=A8les?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/tg/cyberlabmanager/model/AuditEntry.java | 5 +++++ src/main/java/tg/cyberlabmanager/model/Lab.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java index 4c6731f..fa7fa65 100644 --- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java +++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java @@ -8,6 +8,7 @@ public class AuditEntry { private Integer vmId; private String action; private String details; + private String labName; public int getId() { return id; @@ -48,4 +49,8 @@ public String getDetails() { public void setDetails(String details) { this.details = details; } + + public String getLog() { + return ""; + } } diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java index 9e02ad8..9b0522d 100644 --- a/src/main/java/tg/cyberlabmanager/model/Lab.java +++ b/src/main/java/tg/cyberlabmanager/model/Lab.java @@ -10,6 +10,7 @@ public class Lab { private String description; private String category; private final List virtualMachines = new ArrayList<>(); + private String notes; public Lab() { } @@ -53,6 +54,10 @@ public void setCategory(String category) { this.category = category; } + public String getNotes() { return notes; } + + public void setNotes(String notes) { this.notes = notes; } + public List getVirtualMachines() { return Collections.unmodifiableList(virtualMachines); } From 989654a78e80bf711f6271296f7b371eaa9d770c Mon Sep 17 00:00:00 2001 From: akpmarcelin Date: Fri, 29 May 2026 19:20:06 +0000 Subject: [PATCH 02/14] feat(data): add DatabaseManager, PdfExporter and temporary model stubs --- .../cyberlabmanager/data/DatabaseManager.java | 532 +++++++++++++++++- .../tg/cyberlabmanager/model/AppConfig.java | 26 +- .../tg/cyberlabmanager/model/AuditEntry.java | 63 +-- .../cyberlabmanager/model/JournalEntry.java | 43 +- .../java/tg/cyberlabmanager/model/Lab.java | 78 +-- .../tg/cyberlabmanager/model/Snapshot.java | 53 +- .../tg/cyberlabmanager/model/VMStatus.java | 3 +- .../cyberlabmanager/model/VirtualMachine.java | 92 +-- .../tg/cyberlabmanager/pdf/PdfExporter.java | 297 +++++++++- 9 files changed, 882 insertions(+), 305 deletions(-) diff --git a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java index 9cd04a9..56ac366 100644 --- a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java +++ b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java @@ -1,76 +1,562 @@ package tg.cyberlabmanager.data; -import tg.cyberlabmanager.model.AppConfig; -import tg.cyberlabmanager.model.AuditEntry; -import tg.cyberlabmanager.model.JournalEntry; -import tg.cyberlabmanager.model.Lab; -import tg.cyberlabmanager.model.Snapshot; -import tg.cyberlabmanager.model.VirtualMachine; +import tg.cyberlabmanager.model.*; +import java.sql.*; +import java.util.ArrayList; import java.util.List; +/** + * Gestionnaire de la base de données SQLite de Cyber Lab Manager. + * Fournit toutes les opérations CRUD pour les labos, VMs, snapshots, + * journal de bord, audit et configuration. + * + * Utilisation normale : new DatabaseManager("jdbc:sqlite:/home/user/cyberlab.db") + * Utilisation en test : new DatabaseManager("jdbc:sqlite::memory:") + */ public class DatabaseManager { + + private final String jdbcUrl; + + // ========================================================================= + // Constructeur + initialisation + // ========================================================================= + + /** + * Crée le DatabaseManager et initialise les tables si elles n'existent pas. + * + * @param jdbcUrl URL JDBC SQLite, ex: "jdbc:sqlite:/home/user/cyberlab.db" + * ou "jdbc:sqlite::memory:" pour les tests + */ + public DatabaseManager(String jdbcUrl) { + this.jdbcUrl = jdbcUrl; + initDatabase(); + } + + /** + * Ouvre et retourne une connexion SQLite. + * Toujours utilisée dans un try-with-resources pour garantir la fermeture. + */ + private Connection getConnection() throws SQLException { + return DriverManager.getConnection(jdbcUrl); + } + + /** + * Crée toutes les tables si elles n'existent pas encore. + * Appelé automatiquement au démarrage. + */ + private void initDatabase() { + String[] tables = { + // Table des laboratoires + "CREATE TABLE IF NOT EXISTS labo (" + + " id_labo INTEGER PRIMARY KEY AUTOINCREMENT," + + " titre TEXT NOT NULL," + + " description TEXT," + + " categorie TEXT" + + ");", + + // Table des machines virtuelles + "CREATE TABLE IF NOT EXISTS vm (" + + " id_vm INTEGER PRIMARY KEY AUTOINCREMENT," + + " nom_vm TEXT NOT NULL," + + " uuid TEXT UNIQUE," + + " id_labo INTEGER REFERENCES labo(id_labo) ON DELETE SET NULL," + + " lien_doc TEXT," + + " chemin_vboxmanage TEXT" + + ");", + + // Table des snapshots + "CREATE TABLE IF NOT EXISTS snapshot (" + + " id_snap INTEGER PRIMARY KEY AUTOINCREMENT," + + " id_vm INTEGER NOT NULL REFERENCES vm(id_vm) ON DELETE CASCADE," + + " nom_snap TEXT," + + " date_creation TEXT," + + " description TEXT" + + ");", + + // Table du journal de bord + "CREATE TABLE IF NOT EXISTS journal_entry (" + + " id_entry INTEGER PRIMARY KEY AUTOINCREMENT," + + " id_vm INTEGER NOT NULL REFERENCES vm(id_vm) ON DELETE CASCADE," + + " horodatage TEXT NOT NULL," + + " contenu TEXT NOT NULL" + + ");", + + // Table d'audit automatique + "CREATE TABLE IF NOT EXISTS audit_log (" + + " id_log INTEGER PRIMARY KEY AUTOINCREMENT," + + " horodatage TEXT NOT NULL," + + " id_vm INTEGER REFERENCES vm(id_vm) ON DELETE SET NULL," + + " action TEXT NOT NULL," + + " details TEXT" + + ");", + + // Table de configuration (clé/valeur) + "CREATE TABLE IF NOT EXISTS app_config (" + + " cle TEXT PRIMARY KEY," + + " valeur TEXT" + + ");" + }; + + try (Connection conn = getConnection(); + Statement stmt = conn.createStatement()) { + for (String sql : tables) { + stmt.execute(sql); + } + } catch (SQLException e) { + throw new RuntimeException("Impossible d'initialiser la base de données : " + e.getMessage(), e); + } + } + + // ========================================================================= + // LABORATOIRES + // ========================================================================= + + /** + * Sauvegarde un laboratoire. Fait un INSERT si l'id est 0, un UPDATE sinon. + * Après INSERT, l'id généré est assigné à l'objet lab. + * + * @param lab le laboratoire à sauvegarder + */ public void saveLab(Lab lab) { - throw new UnsupportedOperationException("Not implemented yet"); + if (lab.getId() == 0) { + // INSERT + String sql = "INSERT INTO labo (titre, description, categorie) VALUES (?, ?, ?)"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + ps.setString(1, lab.getTitre()); + ps.setString(2, lab.getDescription()); + ps.setString(3, lab.getCategorie()); + ps.executeUpdate(); + ResultSet keys = ps.getGeneratedKeys(); + if (keys.next()) { + lab.setId(keys.getInt(1)); + } + } catch (SQLException e) { + throw new RuntimeException("Erreur saveLab (INSERT) : " + e.getMessage(), e); + } + } else { + // UPDATE + String sql = "UPDATE labo SET titre=?, description=?, categorie=? WHERE id_labo=?"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, lab.getTitre()); + ps.setString(2, lab.getDescription()); + ps.setString(3, lab.getCategorie()); + ps.setInt(4, lab.getId()); + ps.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException("Erreur saveLab (UPDATE) : " + e.getMessage(), e); + } + } } + /** + * Récupère un laboratoire par son identifiant. + * + * @param id l'identifiant du laboratoire + * @return le Lab correspondant, ou null si introuvable + */ public Lab getLab(int id) { - throw new UnsupportedOperationException("Not implemented yet"); + String sql = "SELECT id_labo, titre, description, categorie FROM labo WHERE id_labo=?"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, id); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + return mapLab(rs); + } + } catch (SQLException e) { + throw new RuntimeException("Erreur getLab : " + e.getMessage(), e); + } + return null; } + /** + * Retourne tous les laboratoires enregistrés. + * + * @return liste de tous les Lab, vide si aucun + */ public List getAllLabs() { - throw new UnsupportedOperationException("Not implemented yet"); + List labs = new ArrayList<>(); + String sql = "SELECT id_labo, titre, description, categorie FROM labo ORDER BY titre"; + try (Connection conn = getConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql)) { + while (rs.next()) { + labs.add(mapLab(rs)); + } + } catch (SQLException e) { + throw new RuntimeException("Erreur getAllLabs : " + e.getMessage(), e); + } + return labs; } + /** + * Supprime un laboratoire. Les VMs associées deviennent orphelines (id_labo → NULL). + * + * @param lab le laboratoire à supprimer + */ public void deleteLab(Lab lab) { - throw new UnsupportedOperationException("Not implemented yet"); + String sql = "DELETE FROM labo WHERE id_labo=?"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, lab.getId()); + ps.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException("Erreur deleteLab : " + e.getMessage(), e); + } + } + + /** Construit un objet Lab depuis un ResultSet. */ + private Lab mapLab(ResultSet rs) throws SQLException { + Lab lab = new Lab(); + lab.setId(rs.getInt("id_labo")); + lab.setTitre(rs.getString("titre")); + lab.setDescription(rs.getString("description")); + lab.setCategorie(rs.getString("categorie")); + return lab; } + // ========================================================================= + // MACHINES VIRTUELLES + // ========================================================================= + + /** + * Sauvegarde une VM. INSERT si id==0, UPDATE sinon. + * Si labId est null, la VM est orpheline (non rattachée à un labo). + * + * @param vm la machine virtuelle à sauvegarder + * @param labId l'identifiant du labo associé, ou null pour une VM orpheline + */ public void saveVirtualMachine(VirtualMachine vm, Integer labId) { - throw new UnsupportedOperationException("Not implemented yet"); + if (vm.getId() == 0) { + String sql = "INSERT INTO vm (nom_vm, uuid, id_labo, lien_doc, chemin_vboxmanage) VALUES (?,?,?,?,?)"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + ps.setString(1, vm.getNomVm()); + ps.setString(2, vm.getUuid()); + if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER); + ps.setString(4, vm.getLienDoc()); + ps.setString(5, vm.getCheminVboxmanage()); + ps.executeUpdate(); + ResultSet keys = ps.getGeneratedKeys(); + if (keys.next()) vm.setId(keys.getInt(1)); + } catch (SQLException e) { + throw new RuntimeException("Erreur saveVirtualMachine (INSERT) : " + e.getMessage(), e); + } + } else { + String sql = "UPDATE vm SET nom_vm=?, uuid=?, id_labo=?, lien_doc=?, chemin_vboxmanage=? WHERE id_vm=?"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, vm.getNomVm()); + ps.setString(2, vm.getUuid()); + if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER); + ps.setString(4, vm.getLienDoc()); + ps.setString(5, vm.getCheminVboxmanage()); + ps.setInt(6, vm.getId()); + ps.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException("Erreur saveVirtualMachine (UPDATE) : " + e.getMessage(), e); + } + } } + /** + * Retourne toutes les VMs rattachées à un laboratoire. + * + * @param labId l'identifiant du laboratoire + * @return liste des VMs du labo + */ public List getVMsForLab(int labId) { - throw new UnsupportedOperationException("Not implemented yet"); + List vms = new ArrayList<>(); + String sql = "SELECT * FROM vm WHERE id_labo=?"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, labId); + ResultSet rs = ps.executeQuery(); + while (rs.next()) vms.add(mapVM(rs)); + } catch (SQLException e) { + throw new RuntimeException("Erreur getVMsForLab : " + e.getMessage(), e); + } + return vms; } + /** + * Retourne les VMs sans laboratoire associé. + * + * @return liste des VMs orphelines + */ public List getOrphanVMs() { - throw new UnsupportedOperationException("Not implemented yet"); + List vms = new ArrayList<>(); + String sql = "SELECT * FROM vm WHERE id_labo IS NULL"; + try (Connection conn = getConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql)) { + while (rs.next()) vms.add(mapVM(rs)); + } catch (SQLException e) { + throw new RuntimeException("Erreur getOrphanVMs : " + e.getMessage(), e); + } + return vms; } + /** + * Détache une VM de son laboratoire (id_labo devient NULL). + * Ne supprime pas la VM de l'application ni de VirtualBox. + * + * @param vm la VM à détacher + */ public void removeVMFromLab(VirtualMachine vm) { - throw new UnsupportedOperationException("Not implemented yet"); + String sql = "UPDATE vm SET id_labo=NULL WHERE id_vm=?"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, vm.getId()); + ps.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException("Erreur removeVMFromLab : " + e.getMessage(), e); + } + } + + /** Construit un objet VirtualMachine depuis un ResultSet. */ + private VirtualMachine mapVM(ResultSet rs) throws SQLException { + VirtualMachine vm = new VirtualMachine(); + vm.setId(rs.getInt("id_vm")); + vm.setNomVm(rs.getString("nom_vm")); + vm.setUuid(rs.getString("uuid")); + vm.setLienDoc(rs.getString("lien_doc")); + vm.setCheminVboxmanage(rs.getString("chemin_vboxmanage")); + return vm; } + // ========================================================================= + // SNAPSHOTS + // ========================================================================= + + /** + * Enregistre un snapshot associé à une VM. + * + * @param snap le snapshot à persister + * @param vmId l'identifiant de la VM concernée + */ public void saveSnapshot(Snapshot snap, int vmId) { - throw new UnsupportedOperationException("Not implemented yet"); + String sql = "INSERT INTO snapshot (id_vm, nom_snap, date_creation, description) VALUES (?,?,?,?)"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + ps.setInt(1, vmId); + ps.setString(2, snap.getNomSnap()); + ps.setString(3, snap.getDateCreation()); + ps.setString(4, snap.getDescription()); + ps.executeUpdate(); + ResultSet keys = ps.getGeneratedKeys(); + if (keys.next()) snap.setId(keys.getInt(1)); + } catch (SQLException e) { + throw new RuntimeException("Erreur saveSnapshot : " + e.getMessage(), e); + } } + /** + * Retourne tous les snapshots d'une VM, du plus récent au plus ancien. + * + * @param vmId l'identifiant de la VM + * @return liste des snapshots + */ public List getSnapshotsForVM(int vmId) { - throw new UnsupportedOperationException("Not implemented yet"); + List snaps = new ArrayList<>(); + String sql = "SELECT * FROM snapshot WHERE id_vm=? ORDER BY date_creation DESC"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, vmId); + ResultSet rs = ps.executeQuery(); + while (rs.next()) snaps.add(mapSnapshot(rs)); + } catch (SQLException e) { + throw new RuntimeException("Erreur getSnapshotsForVM : " + e.getMessage(), e); + } + return snaps; } + /** Construit un objet Snapshot depuis un ResultSet. */ + private Snapshot mapSnapshot(ResultSet rs) throws SQLException { + Snapshot s = new Snapshot(); + s.setId(rs.getInt("id_snap")); + s.setNomSnap(rs.getString("nom_snap")); + s.setDateCreation(rs.getString("date_creation")); + s.setDescription(rs.getString("description")); + return s; + } + + // ========================================================================= + // JOURNAL DE BORD + // ========================================================================= + + /** + * Ajoute une entrée dans le journal de bord d'une VM. + * + * @param entry l'entrée de journal à persister + * @param vmId l'identifiant de la VM concernée + */ public void addJournalEntry(JournalEntry entry, int vmId) { - throw new UnsupportedOperationException("Not implemented yet"); + String sql = "INSERT INTO journal_entry (id_vm, horodatage, contenu) VALUES (?,?,?)"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + ps.setInt(1, vmId); + ps.setString(2, entry.getHorodatage()); + ps.setString(3, entry.getContenu()); + ps.executeUpdate(); + ResultSet keys = ps.getGeneratedKeys(); + if (keys.next()) entry.setId(keys.getInt(1)); + } catch (SQLException e) { + throw new RuntimeException("Erreur addJournalEntry : " + e.getMessage(), e); + } } + /** + * Retourne toutes les entrées du journal d'une VM, de la plus récente à la plus ancienne. + * + * @param vmId l'identifiant de la VM + * @return liste des entrées de journal + */ public List getJournalEntriesForVM(int vmId) { - throw new UnsupportedOperationException("Not implemented yet"); + List entries = new ArrayList<>(); + String sql = "SELECT * FROM journal_entry WHERE id_vm=? ORDER BY horodatage DESC"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, vmId); + ResultSet rs = ps.executeQuery(); + while (rs.next()) entries.add(mapJournalEntry(rs)); + } catch (SQLException e) { + throw new RuntimeException("Erreur getJournalEntriesForVM : " + e.getMessage(), e); + } + return entries; + } + + /** Construit un objet JournalEntry depuis un ResultSet. */ + private JournalEntry mapJournalEntry(ResultSet rs) throws SQLException { + JournalEntry e = new JournalEntry(); + e.setId(rs.getInt("id_entry")); + e.setHorodatage(rs.getString("horodatage")); + e.setContenu(rs.getString("contenu")); + return e; } + // ========================================================================= + // AUDIT + // ========================================================================= + + /** + * Enregistre une entrée d'audit (appelé automatiquement après chaque action sur une VM). + * + * @param entry l'entrée d'audit à persister + */ public void addAuditEntry(AuditEntry entry) { - throw new UnsupportedOperationException("Not implemented yet"); + String sql = "INSERT INTO audit_log (horodatage, id_vm, action, details) VALUES (?,?,?,?)"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + ps.setString(1, entry.getHorodatage()); + if (entry.getVmId() > 0) ps.setInt(2, entry.getVmId()); else ps.setNull(2, Types.INTEGER); + ps.setString(3, entry.getAction()); + ps.setString(4, entry.getDetails()); + ps.executeUpdate(); + ResultSet keys = ps.getGeneratedKeys(); + if (keys.next()) entry.setId(keys.getInt(1)); + } catch (SQLException e) { + throw new RuntimeException("Erreur addAuditEntry : " + e.getMessage(), e); + } } + /** + * Retourne l'historique d'audit d'une VM. + * + * @param vmId l'identifiant de la VM + * @return liste des entrées d'audit, de la plus récente à la plus ancienne + */ public List getAuditLogsForVM(int vmId) { - throw new UnsupportedOperationException("Not implemented yet"); + List logs = new ArrayList<>(); + String sql = "SELECT * FROM audit_log WHERE id_vm=? ORDER BY horodatage DESC"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, vmId); + ResultSet rs = ps.executeQuery(); + while (rs.next()) logs.add(mapAuditEntry(rs)); + } catch (SQLException e) { + throw new RuntimeException("Erreur getAuditLogsForVM : " + e.getMessage(), e); + } + return logs; + } + + /** Construit un objet AuditEntry depuis un ResultSet. */ + private AuditEntry mapAuditEntry(ResultSet rs) throws SQLException { + AuditEntry a = new AuditEntry(); + a.setId(rs.getInt("id_log")); + a.setHorodatage(rs.getString("horodatage")); + a.setVmId(rs.getInt("id_vm")); + a.setAction(rs.getString("action")); + a.setDetails(rs.getString("details")); + return a; } + // ========================================================================= + // CONFIGURATION + // ========================================================================= + + /** + * Charge la configuration applicative depuis la base. + * Retourne une configuration avec des valeurs par défaut si rien n'est enregistré. + * + * @return la configuration courante + */ public AppConfig getConfig() { - throw new UnsupportedOperationException("Not implemented yet"); + AppConfig config = new AppConfig(); + config.setVboxManagePath(getConfigValue("vboxManagePath", "VBoxManage")); + config.setDefaultExportDir(getConfigValue("defaultExportDir", + System.getProperty("user.home"))); + return config; } + /** + * Sauvegarde la configuration applicative. + * + * @param config la configuration à persister + */ public void saveConfig(AppConfig config) { - throw new UnsupportedOperationException("Not implemented yet"); + upsertConfig("vboxManagePath", config.getVboxManagePath()); + upsertConfig("defaultExportDir", config.getDefaultExportDir()); + } + + /** + * Lit une valeur de configuration par sa clé. + * + * @param key la clé de configuration + * @param defaultValue valeur retournée si la clé est absente + * @return la valeur stockée ou la valeur par défaut + */ + private String getConfigValue(String key, String defaultValue) { + String sql = "SELECT valeur FROM app_config WHERE cle=?"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, key); + ResultSet rs = ps.executeQuery(); + if (rs.next()) return rs.getString("valeur"); + } catch (SQLException e) { + throw new RuntimeException("Erreur getConfigValue : " + e.getMessage(), e); + } + return defaultValue; + } + + /** + * Insère ou met à jour une valeur de configuration (UPSERT). + * + * @param key la clé + * @param value la valeur + */ + private void upsertConfig(String key, String value) { + String sql = "INSERT OR REPLACE INTO app_config (cle, valeur) VALUES (?,?)"; + try (Connection conn = getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, key); + ps.setString(2, value); + ps.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException("Erreur upsertConfig : " + e.getMessage(), e); + } } -} +} \ No newline at end of file diff --git a/src/main/java/tg/cyberlabmanager/model/AppConfig.java b/src/main/java/tg/cyberlabmanager/model/AppConfig.java index 6d6c891..6ecdfc9 100644 --- a/src/main/java/tg/cyberlabmanager/model/AppConfig.java +++ b/src/main/java/tg/cyberlabmanager/model/AppConfig.java @@ -1,22 +1,12 @@ package tg.cyberlabmanager.model; - + public class AppConfig { private String vboxManagePath; - private String pdfExportDirectory; - - public String getVboxManagePath() { - return vboxManagePath; - } - - public void setVboxManagePath(String vboxManagePath) { - this.vboxManagePath = vboxManagePath; - } - - public String getPdfExportDirectory() { - return pdfExportDirectory; - } - - public void setPdfExportDirectory(String pdfExportDirectory) { - this.pdfExportDirectory = pdfExportDirectory; - } + private String defaultExportDir; + + public String getVboxManagePath() { return vboxManagePath; } + public void setVboxManagePath(String p) { this.vboxManagePath = p; } + public String getDefaultExportDir() { return defaultExportDir; } + public void setDefaultExportDir(String d) { this.defaultExportDir = d; } } + \ No newline at end of file diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java index 4c6731f..27759e1 100644 --- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java +++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java @@ -1,51 +1,20 @@ package tg.cyberlabmanager.model; - -import java.time.LocalDateTime; - + public class AuditEntry { - private int id; - private LocalDateTime timestamp; - private Integer vmId; + private int id; + private String horodatage; + private int vmId; private String action; private String details; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public LocalDateTime getTimestamp() { - return timestamp; - } - - public void setTimestamp(LocalDateTime timestamp) { - this.timestamp = timestamp; - } - - public Integer getVmId() { - return vmId; - } - - public void setVmId(Integer vmId) { - this.vmId = vmId; - } - - public String getAction() { - return action; - } - - public void setAction(String action) { - this.action = action; - } - - public String getDetails() { - return details; - } - - public void setDetails(String details) { - this.details = details; - } -} + + public int getId() { return id; } + public void setId(int id) { this.id = id; } + public String getHorodatage() { return horodatage; } + public void setHorodatage(String h){ this.horodatage = h; } + public int getVmId() { return vmId; } + public void setVmId(int v) { this.vmId = v; } + public String getAction() { return action; } + public void setAction(String a) { this.action = a; } + public String getDetails() { return details; } + public void setDetails(String d) { this.details = d; } +} \ No newline at end of file diff --git a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java index ec7031f..4b1b025 100644 --- a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java +++ b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java @@ -1,33 +1,14 @@ package tg.cyberlabmanager.model; - -import java.time.LocalDateTime; - + public class JournalEntry { - private int id; - private LocalDateTime timestamp; - private String content; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public LocalDateTime getTimestamp() { - return timestamp; - } - - public void setTimestamp(LocalDateTime timestamp) { - this.timestamp = timestamp; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } -} + private int id; + private String horodatage; + private String contenu; + + public int getId() { return id; } + public void setId(int id) { this.id = id; } + public String getHorodatage() { return horodatage; } + public void setHorodatage(String h){ this.horodatage = h; } + public String getContenu() { return contenu; } + public void setContenu(String c) { this.contenu = c; } +} \ No newline at end of file diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java index 9e02ad8..64ab47d 100644 --- a/src/main/java/tg/cyberlabmanager/model/Lab.java +++ b/src/main/java/tg/cyberlabmanager/model/Lab.java @@ -1,67 +1,17 @@ package tg.cyberlabmanager.model; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - + public class Lab { - private int id; - private String title; + private int id; + private String titre; private String description; - private String category; - private final List virtualMachines = new ArrayList<>(); - - public Lab() { - } - - public Lab(int id, String title, String description, String category) { - this.id = id; - this.title = title; - this.description = description; - this.category = category; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public List getVirtualMachines() { - return Collections.unmodifiableList(virtualMachines); - } - - public void addVirtualMachine(VirtualMachine virtualMachine) { - virtualMachines.add(virtualMachine); - } - - public void removeVirtualMachine(VirtualMachine virtualMachine) { - virtualMachines.remove(virtualMachine); - } -} + private String categorie; + + public int getId() { return id; } + public void setId(int id) { this.id = id; } + public String getTitre() { return titre; } + public void setTitre(String t) { this.titre = t; } + public String getDescription() { return description; } + public void setDescription(String d) { this.description = d; } + public String getCategorie() { return categorie; } + public void setCategorie(String c) { this.categorie = c; } +} \ No newline at end of file diff --git a/src/main/java/tg/cyberlabmanager/model/Snapshot.java b/src/main/java/tg/cyberlabmanager/model/Snapshot.java index bd5e879..a4ed027 100644 --- a/src/main/java/tg/cyberlabmanager/model/Snapshot.java +++ b/src/main/java/tg/cyberlabmanager/model/Snapshot.java @@ -1,42 +1,17 @@ package tg.cyberlabmanager.model; - -import java.time.LocalDateTime; - + public class Snapshot { - private int id; - private String name; - private LocalDateTime createdAt; + private int id; + private String nomSnap; + private String dateCreation; private String description; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } -} + + public int getId() { return id; } + public void setId(int id) { this.id = id; } + public String getNomSnap() { return nomSnap; } + public void setNomSnap(String n){ this.nomSnap = n; } + public String getDateCreation() { return dateCreation; } + public void setDateCreation(String d){ this.dateCreation = d; } + public String getDescription() { return description; } + public void setDescription(String d){ this.description = d; } +} \ No newline at end of file diff --git a/src/main/java/tg/cyberlabmanager/model/VMStatus.java b/src/main/java/tg/cyberlabmanager/model/VMStatus.java index b6a8744..fa252ef 100644 --- a/src/main/java/tg/cyberlabmanager/model/VMStatus.java +++ b/src/main/java/tg/cyberlabmanager/model/VMStatus.java @@ -5,5 +5,6 @@ public enum VMStatus { POWERED_OFF, SAVED, PAUSED, - UNKNOWN + UNKNOWN, + ABORTED } diff --git a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java index f031a89..632a857 100644 --- a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java +++ b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java @@ -1,71 +1,27 @@ package tg.cyberlabmanager.model; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - + public class VirtualMachine { - private int id; - private String name; + private int id; + private String nomVm; private String uuid; - private String documentationUrl; - private VMStatus status = VMStatus.UNKNOWN; - private final List snapshots = new ArrayList<>(); - private final List journalEntries = new ArrayList<>(); - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getUuid() { - return uuid; - } - - public void setUuid(String uuid) { - this.uuid = uuid; - } - - public String getDocumentationUrl() { - return documentationUrl; - } - - public void setDocumentationUrl(String documentationUrl) { - this.documentationUrl = documentationUrl; - } - - public VMStatus getStatus() { - return status; - } - - public void setStatus(VMStatus status) { - this.status = status; - } - - public List getSnapshots() { - return Collections.unmodifiableList(snapshots); - } - - public void addSnapshot(Snapshot snapshot) { - snapshots.add(snapshot); - } - - public List getJournalEntries() { - return Collections.unmodifiableList(journalEntries); - } - - public void addJournalEntry(JournalEntry journalEntry) { - journalEntries.add(journalEntry); - } -} + private String lienDoc; + private String cheminVboxmanage; + private VMStatus status; + + public int getId() { return id; } + public void setId(int id) { this.id = id; } + public String getNomVm() { return nomVm; } + public void setNomVm(String n) { this.nomVm = n; } + public String getUuid() { return uuid; } + public void setUuid(String u) { this.uuid = u; } + public String getLienDoc() { return lienDoc; } + public void setLienDoc(String l) { this.lienDoc = l; } + public String getCheminVboxmanage() { return cheminVboxmanage; } + public void setCheminVboxmanage(String c){ this.cheminVboxmanage = c; } + public VMStatus getStatus() { return status; } + public void setStatus(VMStatus s) { this.status = s; } + // Label lisible pour le PDF + public String getStatusLabel() { + return status != null ? status.name() : "UNKNOWN"; + } +} \ No newline at end of file diff --git a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java index 5c670d7..18490ef 100644 --- a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java +++ b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java @@ -1,21 +1,290 @@ package tg.cyberlabmanager.pdf; -import tg.cyberlabmanager.model.JournalEntry; -import tg.cyberlabmanager.model.Lab; -import tg.cyberlabmanager.model.Snapshot; -import tg.cyberlabmanager.model.VirtualMachine; +import tg.cyberlabmanager.model.*; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.font.Standard14Fonts; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; +/** + * Génère un rapport PDF récapitulatif d'un laboratoire Cyber Lab Manager. + * Le rapport contient : en-tête du labo, liste des VMs, snapshots et journal de bord. + * + * Utilise Apache PDFBox 3.x. + */ public class PdfExporter { - public void exportLabToPdf( - Lab lab, - List vms, - List snapshots, - List journals, - String filePath - ) throws IOException { - throw new UnsupportedOperationException("Not implemented yet"); - } -} + + // ── Mise en page ────────────────────────────────────────────────────────── + private static final float MARGIN = 50f; + private static final float PAGE_HEIGHT = PDRectangle.A4.getHeight(); // ~842 + private static final float PAGE_WIDTH = PDRectangle.A4.getWidth(); // ~595 + private static final float CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN; + + // ── Tailles de police ───────────────────────────────────────────────────── + private static final float FONT_TITLE = 18f; + private static final float FONT_HEADING = 13f; + private static final float FONT_SUBHEAD = 11f; + private static final float FONT_BODY = 10f; + + // ── Polices PDFBox 3.x ──────────────────────────────────────────────────── + private static final PDType1Font FONT_BOLD = new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD); + private static final PDType1Font FONT_REGULAR = new PDType1Font(Standard14Fonts.FontName.HELVETICA); + private static final PDType1Font FONT_OBLIQUE = new PDType1Font(Standard14Fonts.FontName.HELVETICA_OBLIQUE); + + // ── État interne de pagination ───────────────────────────────────────────── + private PDDocument document; + private PDPageContentStream content; + private float cursorY; // position verticale courante (descend vers 0) + + // ========================================================================= + // Méthode publique principale + // ========================================================================= + + /** + * Génère et sauvegarde le rapport PDF d'un laboratoire. + * + * @param lab le laboratoire exporté + * @param vms liste des VMs du labo (déjà filtrée par le contrôleur) + * @param snapshots liste des snapshots (déjà filtrée par le contrôleur) + * @param journals liste des entrées de journal (déjà filtrée par le contrôleur) + * @param filePath chemin complet du fichier PDF à créer + * @throws IOException si l'écriture du fichier échoue + */ + public void exportLabToPdf(Lab lab, + List vms, + List snapshots, + List journals, + String filePath) throws IOException { + + document = new PDDocument(); + newPage(); // crée la première page et initialise cursorY + + // ── En-tête du rapport ──────────────────────────────────────────────── + writeLine("Rapport – " + lab.getTitre(), FONT_BOLD, FONT_TITLE, true); + writeLine("Catégorie : " + nvl(lab.getCategorie(), "Non définie"), FONT_REGULAR, FONT_BODY, false); + writeLine("Généré le : " + now(), FONT_REGULAR, FONT_BODY, false); + if (lab.getDescription() != null && !lab.getDescription().isBlank()) { + writeLine("Description : " + lab.getDescription(), FONT_OBLIQUE, FONT_BODY, false); + } + spacer(12f); + separator(); + spacer(8f); + + // ── Section 1 : Machines virtuelles ─────────────────────────────────── + writeLine("1. Machines virtuelles (" + vms.size() + ")", FONT_BOLD, FONT_HEADING, false); + spacer(6f); + + if (vms.isEmpty()) { + writeLine(" Aucune machine virtuelle dans ce laboratoire.", FONT_OBLIQUE, FONT_BODY, false); + } else { + for (VirtualMachine vm : vms) { + checkPageBreak(60f); + writeLine(" ▸ " + vm.getNomVm(), FONT_BOLD, FONT_SUBHEAD, false); + writeLine(" UUID : " + nvl(vm.getUuid(), "—"), FONT_REGULAR, FONT_BODY, false); + writeLine(" Statut : " + nvl(vm.getStatusLabel(), "Inconnu"), FONT_REGULAR, FONT_BODY, false); + if (vm.getLienDoc() != null && !vm.getLienDoc().isBlank()) { + writeLine(" Doc : " + vm.getLienDoc(), FONT_REGULAR, FONT_BODY, false); + } + spacer(5f); + } + } + + spacer(8f); + separator(); + spacer(8f); + + // ── Section 2 : Snapshots ───────────────────────────────────────────── + writeLine("2. Snapshots (" + snapshots.size() + ")", FONT_BOLD, FONT_HEADING, false); + spacer(6f); + + if (snapshots.isEmpty()) { + writeLine(" Aucun snapshot enregistré.", FONT_OBLIQUE, FONT_BODY, false); + } else { + for (Snapshot snap : snapshots) { + checkPageBreak(40f); + writeLine(" ▸ " + nvl(snap.getNomSnap(), "Sans nom"), FONT_BOLD, FONT_SUBHEAD, false); + writeLine(" Date : " + nvl(snap.getDateCreation(), "—"), FONT_REGULAR, FONT_BODY, false); + if (snap.getDescription() != null && !snap.getDescription().isBlank()) { + writeLine(" Note : " + snap.getDescription(), FONT_OBLIQUE, FONT_BODY, false); + } + spacer(4f); + } + } + + spacer(8f); + separator(); + spacer(8f); + + // ── Section 3 : Journal de bord ─────────────────────────────────────── + writeLine("3. Journal de bord (" + journals.size() + " entrée(s))", FONT_BOLD, FONT_HEADING, false); + spacer(6f); + + if (journals.isEmpty()) { + writeLine(" Aucune note dans le journal.", FONT_OBLIQUE, FONT_BODY, false); + } else { + for (JournalEntry entry : journals) { + checkPageBreak(40f); + writeLine(" [" + nvl(entry.getHorodatage(), "—") + "]", FONT_BOLD, FONT_BODY, false); + // Le contenu peut être long : on le découpe en lignes + writeWrappedText(" " + nvl(entry.getContenu(), ""), FONT_REGULAR, FONT_BODY); + spacer(5f); + } + } + + // ── Pied de page de la dernière page ────────────────────────────────── + writeFooter(); + + content.close(); + document.save(filePath); + document.close(); + } + + // ========================================================================= + // Gestion des pages + // ========================================================================= + + /** + * Crée une nouvelle page A4 et réinitialise le curseur en haut. + */ + private void newPage() throws IOException { + if (content != null) { + content.close(); + } + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + content = new PDPageContentStream(document, page); + cursorY = PAGE_HEIGHT - MARGIN; + } + + /** + * Si l'espace restant est insuffisant pour écrire {@code needed} points, + * crée une nouvelle page. + * + * @param needed espace vertical nécessaire en points + */ + private void checkPageBreak(float needed) throws IOException { + if (cursorY - needed < MARGIN + 20f) { + newPage(); + } + } + + // ========================================================================= + // Écriture de contenu + // ========================================================================= + + /** + * Écrit une ligne de texte simple et fait descendre le curseur. + * + * @param text le texte à écrire + * @param font la police PDFBox + * @param size la taille en points + * @param underline true pour ajouter une ligne sous le texte (simulée par un trait) + */ + private void writeLine(String text, PDType1Font font, float size, boolean underline) + throws IOException { + checkPageBreak(size + 6f); + cursorY -= size + 2f; + + content.beginText(); + content.setFont(font, size); + content.newLineAtOffset(MARGIN, cursorY); + content.showText(sanitize(text)); + content.endText(); + + if (underline) { + float textWidth = font.getStringWidth(sanitize(text)) / 1000 * size; + content.moveTo(MARGIN, cursorY - 2f); + content.lineTo(MARGIN + textWidth, cursorY - 2f); + content.stroke(); + cursorY -= 4f; + } + } + + /** + * Écrit un texte long en le découpant sur plusieurs lignes si nécessaire. + * + * @param text le texte (potentiellement long) + * @param font la police + * @param size la taille + */ + private void writeWrappedText(String text, PDType1Font font, float size) throws IOException { + // Largeur max en points, convertie en "unités police" + float maxWidth = CONTENT_WIDTH; + + String[] words = sanitize(text).split(" "); + StringBuilder line = new StringBuilder(); + + for (String word : words) { + String candidate = line.isEmpty() ? word : line + " " + word; + float w = font.getStringWidth(candidate) / 1000 * size; + if (w > maxWidth && !line.isEmpty()) { + writeLine(line.toString(), font, size, false); + line = new StringBuilder(word); + } else { + line = new StringBuilder(candidate); + } + } + if (!line.isEmpty()) { + writeLine(line.toString(), font, size, false); + } + } + + /** Ajoute un espace vertical. */ + private void spacer(float points) { + cursorY -= points; + } + + /** Dessine un trait de séparation horizontal. */ + private void separator() throws IOException { + content.setLineWidth(0.5f); + content.moveTo(MARGIN, cursorY); + content.lineTo(PAGE_WIDTH - MARGIN, cursorY); + content.stroke(); + cursorY -= 2f; + } + + /** Écrit un pied de page discret en bas de la page courante. */ + private void writeFooter() throws IOException { + float footerY = MARGIN - 10f; + content.beginText(); + content.setFont(FONT_OBLIQUE, 8f); + content.newLineAtOffset(MARGIN, footerY); + content.showText("Cyber Lab Manager – Rapport généré le " + now()); + content.endText(); + } + + // ========================================================================= + // Utilitaires + // ========================================================================= + + /** Retourne la valeur ou le fallback si null/blank. */ + private String nvl(String value, String fallback) { + return (value != null && !value.isBlank()) ? value : fallback; + } + + /** Date/heure courante formatée pour affichage. */ + private String now() { + return LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); + } + + /** + * Nettoie les caractères non supportés par les polices standard PDF (Latin-1 uniquement). + * Remplace les caractères hors plage par '?'. + */ + private String sanitize(String text) { + if (text == null) return ""; + StringBuilder sb = new StringBuilder(); + for (char c : text.toCharArray()) { + sb.append(c <= 255 ? c : '?'); + } + return sb.toString(); + } +} \ No newline at end of file From c204060251366340a6385c78d12d0eebb1c1d6e9 Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Fri, 29 May 2026 19:51:31 +0000 Subject: [PATCH 03/14] Add lab name to audit schema --- src/main/resources/tg/cyberlabmanager/data/schema.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/tg/cyberlabmanager/data/schema.sql b/src/main/resources/tg/cyberlabmanager/data/schema.sql index fde532e..96160eb 100644 --- a/src/main/resources/tg/cyberlabmanager/data/schema.sql +++ b/src/main/resources/tg/cyberlabmanager/data/schema.sql @@ -35,6 +35,7 @@ CREATE TABLE IF NOT EXISTS audit_entry ( id_audit INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, id_vm INTEGER, + lab_name TEXT, action TEXT NOT NULL, details TEXT, FOREIGN KEY (id_vm) REFERENCES virtual_machine(id_vm) ON DELETE SET NULL From e6b6e6d15f35cc0ec37125f38dd3ad019c9281d2 Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Fri, 29 May 2026 19:51:55 +0000 Subject: [PATCH 04/14] Align model entities with domain logic --- .../tg/cyberlabmanager/model/AuditEntry.java | 27 ++++++++++++++++++- .../java/tg/cyberlabmanager/model/Lab.java | 5 ---- .../tg/cyberlabmanager/model/Snapshot.java | 18 +++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java index fa7fa65..b4fee56 100644 --- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java +++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java @@ -1,8 +1,11 @@ package tg.cyberlabmanager.model; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; public class AuditEntry { + private static final DateTimeFormatter LOG_TIMESTAMP_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + private int id; private LocalDateTime timestamp; private Integer vmId; @@ -50,7 +53,29 @@ public void setDetails(String details) { this.details = details; } + public String getLabName() { + return labName; + } + + public void setLabName(String labName) { + this.labName = labName; + } + public String getLog() { - return ""; + StringBuilder log = new StringBuilder(); + log.append(timestamp == null ? "unknown-time" : timestamp.format(LOG_TIMESTAMP_FORMATTER)); + log.append(" | action=").append(action == null ? "UNKNOWN" : action); + + if (vmId != null) { + log.append(" | vmId=").append(vmId); + } + if (labName != null && !labName.isBlank()) { + log.append(" | lab=").append(labName); + } + if (details != null && !details.isBlank()) { + log.append(" | details=").append(details); + } + + return log.toString(); } } diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java index 9b0522d..9e02ad8 100644 --- a/src/main/java/tg/cyberlabmanager/model/Lab.java +++ b/src/main/java/tg/cyberlabmanager/model/Lab.java @@ -10,7 +10,6 @@ public class Lab { private String description; private String category; private final List virtualMachines = new ArrayList<>(); - private String notes; public Lab() { } @@ -54,10 +53,6 @@ public void setCategory(String category) { this.category = category; } - public String getNotes() { return notes; } - - public void setNotes(String notes) { this.notes = notes; } - public List getVirtualMachines() { return Collections.unmodifiableList(virtualMachines); } diff --git a/src/main/java/tg/cyberlabmanager/model/Snapshot.java b/src/main/java/tg/cyberlabmanager/model/Snapshot.java index bd5e879..03eed48 100644 --- a/src/main/java/tg/cyberlabmanager/model/Snapshot.java +++ b/src/main/java/tg/cyberlabmanager/model/Snapshot.java @@ -4,9 +4,11 @@ public class Snapshot { private int id; + private String uuid; private String name; private LocalDateTime createdAt; private String description; + private boolean online; public int getId() { return id; @@ -16,6 +18,14 @@ public void setId(int id) { this.id = id; } + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + public String getName() { return name; } @@ -39,4 +49,12 @@ public String getDescription() { public void setDescription(String description) { this.description = description; } + + public boolean isOnline() { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } } From f284e9555088bef15d236bbba73450dd8e9d0386 Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Fri, 29 May 2026 20:11:26 +0000 Subject: [PATCH 05/14] Document public Java APIs with Javadoc --- src/main/java/module-info.java | 5 +- .../tg/cyberlabmanager/app/CyberLabApp.java | 15 +++ .../java/tg/cyberlabmanager/app/Launcher.java | 14 +++ .../controller/LabController.java | 79 ++++++++++++++ .../cyberlabmanager/data/DatabaseManager.java | 100 ++++++++++++++++++ .../hypervisor/HypervisorException.java | 14 +++ .../hypervisor/IHypervisor.java | 49 +++++++++ .../hypervisor/VBoxException.java | 22 ++++ .../hypervisor/VBoxOrchestrator.java | 16 +++ .../tg/cyberlabmanager/model/AppConfig.java | 32 ++++++ .../tg/cyberlabmanager/model/AuditEntry.java | 77 ++++++++++++++ .../cyberlabmanager/model/JournalEntry.java | 42 ++++++++ .../java/tg/cyberlabmanager/model/Lab.java | 73 +++++++++++++ .../tg/cyberlabmanager/model/Snapshot.java | 73 +++++++++++++ .../tg/cyberlabmanager/model/VMStatus.java | 8 ++ .../cyberlabmanager/model/VirtualMachine.java | 85 ++++++++++++++- .../tg/cyberlabmanager/pdf/PdfExporter.java | 19 ++++ .../tg/cyberlabmanager/ui/LabListView.java | 8 ++ .../java/tg/cyberlabmanager/ui/MainView.java | 8 ++ .../tg/cyberlabmanager/ui/VmControlPanel.java | 8 ++ 20 files changed, 745 insertions(+), 2 deletions(-) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 6bcfcc9..930b3ae 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,3 +1,6 @@ +/** + * Module principal de l'application Cyber Lab Manager. + */ module tg.cyberlabmanager { // ── JavaFX ────────────────────────────────────────────────────────── @@ -31,4 +34,4 @@ exports tg.cyberlabmanager.hypervisor; exports tg.cyberlabmanager.pdf; exports tg.cyberlabmanager.ui; -} \ No newline at end of file +} diff --git a/src/main/java/tg/cyberlabmanager/app/CyberLabApp.java b/src/main/java/tg/cyberlabmanager/app/CyberLabApp.java index 70b6866..d8770e1 100644 --- a/src/main/java/tg/cyberlabmanager/app/CyberLabApp.java +++ b/src/main/java/tg/cyberlabmanager/app/CyberLabApp.java @@ -7,7 +7,22 @@ import java.io.IOException; +/** + * Application JavaFX principale de Cyber Lab Manager. + */ public class CyberLabApp extends Application { + /** + * Cree l'application JavaFX. + */ + public CyberLabApp() { + } + + /** + * Initialise et affiche la fenetre principale JavaFX. + * + * @param stage scene principale fournie par JavaFX + * @throws IOException si le chargement du fichier FXML echoue + */ @Override public void start(Stage stage) throws IOException { FXMLLoader loader = new FXMLLoader(CyberLabApp.class.getResource("/tg/cyberlabmanager/ui/main-view.fxml")); diff --git a/src/main/java/tg/cyberlabmanager/app/Launcher.java b/src/main/java/tg/cyberlabmanager/app/Launcher.java index 7e25325..113aaca 100644 --- a/src/main/java/tg/cyberlabmanager/app/Launcher.java +++ b/src/main/java/tg/cyberlabmanager/app/Launcher.java @@ -2,7 +2,21 @@ import javafx.application.Application; +/** + * Point d'entree deleguant le lancement a l'application JavaFX. + */ public class Launcher { + /** + * Cree le lanceur de l'application. + */ + public Launcher() { + } + + /** + * Lance l'application JavaFX. + * + * @param args arguments de ligne de commande + */ public static void main(String[] args) { Application.launch(CyberLabApp.class, args); } diff --git a/src/main/java/tg/cyberlabmanager/controller/LabController.java b/src/main/java/tg/cyberlabmanager/controller/LabController.java index 732d284..7228bab 100644 --- a/src/main/java/tg/cyberlabmanager/controller/LabController.java +++ b/src/main/java/tg/cyberlabmanager/controller/LabController.java @@ -11,6 +11,9 @@ import java.util.List; +/** + * Controleur principal coordonnant l'interface, la persistance et l'hyperviseur. + */ public class LabController { private IHypervisor hypervisor; private DatabaseManager databaseManager; @@ -19,9 +22,19 @@ public class LabController { @FXML private Label statusLabel; + /** + * Cree un controleur vide utilise par JavaFX lors du chargement FXML. + */ public LabController() { } + /** + * Cree un controleur avec ses dependances injectees. + * + * @param hypervisor service de pilotage de l'hyperviseur + * @param databaseManager service de persistance + * @param pdfExporter service d'export PDF + */ public LabController(IHypervisor hypervisor, DatabaseManager databaseManager, PdfExporter pdfExporter) { this.hypervisor = hypervisor; this.databaseManager = databaseManager; @@ -35,54 +48,120 @@ private void initialize() { } } + /** + * Declenche le demarrage d'une machine virtuelle. + * + * @param vm machine virtuelle cible + */ public void onStartVMClicked(VirtualMachine vm) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Declenche l'arret d'une machine virtuelle. + * + * @param vm machine virtuelle cible + */ public void onStopVMClicked(VirtualMachine vm) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Declenche la sauvegarde d'etat d'une machine virtuelle. + * + * @param vm machine virtuelle cible + */ public void onSaveStateClicked(VirtualMachine vm) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Declenche la creation d'un snapshot pour une machine virtuelle. + * + * @param vm machine virtuelle cible + * @param name nom du snapshot + * @param description description du snapshot + */ public void onTakeSnapshotClicked(VirtualMachine vm, String name, String description) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Declenche la restauration d'un snapshot pour une machine virtuelle. + * + * @param vm machine virtuelle cible + * @param snapshotName nom ou identifiant du snapshot + */ public void onRestoreSnapshotClicked(VirtualMachine vm, String snapshotName) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Ajoute une note d'analyse a une machine virtuelle. + * + * @param vm machine virtuelle cible + * @param text contenu de la note + */ public void onAddJournalEntry(VirtualMachine vm, String text) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Declenche l'import des machines virtuelles disponibles. + */ public void onImportVMsClicked() { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Confirme l'import de machines virtuelles selectionnees. + * + * @param selectedVMs machines virtuelles selectionnees + * @param labId identifiant du laboratoire cible, ou {@code null} + */ public void onImportConfirm(List selectedVMs, Integer labId) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Declenche l'export PDF d'un laboratoire. + * + * @param lab laboratoire a exporter + */ public void onExportLabClicked(Lab lab) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Declenche l'export des machines virtuelles sans laboratoire. + */ public void onExportOrphansClicked() { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Retourne tous les laboratoires connus. + * + * @return liste des laboratoires + */ public List getAllLabs() { return databaseManager.getAllLabs(); } + /** + * Retourne les machines virtuelles non rattachees a un laboratoire. + * + * @return liste des machines virtuelles orphelines + */ public List getOrphanVMs() { return databaseManager.getOrphanVMs(); } + /** + * Persiste la configuration mise a jour. + * + * @param newConfig nouvelle configuration applicative + */ public void onConfigUpdated(AppConfig newConfig) { databaseManager.saveConfig(newConfig); } diff --git a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java index 9cd04a9..833413f 100644 --- a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java +++ b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java @@ -9,67 +9,167 @@ import java.util.List; +/** + * Service d'acces aux donnees de l'application. + * + *

Il masque la persistance SQLite derriere des operations de haut niveau sur + * les entites metier.

+ */ public class DatabaseManager { + /** + * Cree un gestionnaire de base de donnees. + */ + public DatabaseManager() { + } + + /** + * Enregistre ou met a jour un laboratoire. + * + * @param lab laboratoire a persister + */ public void saveLab(Lab lab) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Recupere un laboratoire par son identifiant local. + * + * @param id identifiant local du laboratoire + * @return laboratoire correspondant + */ public Lab getLab(int id) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Recupere tous les laboratoires. + * + * @return liste des laboratoires + */ public List getAllLabs() { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Supprime un laboratoire sans supprimer les VM physiques. + * + * @param lab laboratoire a supprimer + */ public void deleteLab(Lab lab) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Enregistre ou met a jour une machine virtuelle. + * + * @param vm machine virtuelle a persister + * @param labId identifiant du laboratoire rattache, ou {@code null} + */ public void saveVirtualMachine(VirtualMachine vm, Integer labId) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Recupere les machines virtuelles rattachees a un laboratoire. + * + * @param labId identifiant local du laboratoire + * @return liste des machines virtuelles rattachees + */ public List getVMsForLab(int labId) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Recupere les machines virtuelles sans laboratoire. + * + * @return liste des machines virtuelles orphelines + */ public List getOrphanVMs() { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Detache une machine virtuelle de son laboratoire. + * + * @param vm machine virtuelle a detacher + */ public void removeVMFromLab(VirtualMachine vm) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Enregistre un snapshot pour une machine virtuelle. + * + * @param snap snapshot a persister + * @param vmId identifiant local de la machine virtuelle + */ public void saveSnapshot(Snapshot snap, int vmId) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Recupere les snapshots d'une machine virtuelle. + * + * @param vmId identifiant local de la machine virtuelle + * @return liste des snapshots rattaches + */ public List getSnapshotsForVM(int vmId) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Ajoute une note d'analyse pour une machine virtuelle. + * + * @param entry note d'analyse a persister + * @param vmId identifiant local de la machine virtuelle + */ public void addJournalEntry(JournalEntry entry, int vmId) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Recupere les notes d'analyse d'une machine virtuelle. + * + * @param vmId identifiant local de la machine virtuelle + * @return liste des notes d'analyse + */ public List getJournalEntriesForVM(int vmId) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Ajoute une entree d'audit. + * + * @param entry entree d'audit a persister + */ public void addAuditEntry(AuditEntry entry) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Recupere les entrees d'audit d'une machine virtuelle. + * + * @param vmId identifiant local de la machine virtuelle + * @return liste des entrees d'audit + */ public List getAuditLogsForVM(int vmId) { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Recupere la configuration applicative. + * + * @return configuration applicative + */ public AppConfig getConfig() { throw new UnsupportedOperationException("Not implemented yet"); } + /** + * Enregistre la configuration applicative. + * + * @param config configuration a persister + */ public void saveConfig(AppConfig config) { throw new UnsupportedOperationException("Not implemented yet"); } diff --git a/src/main/java/tg/cyberlabmanager/hypervisor/HypervisorException.java b/src/main/java/tg/cyberlabmanager/hypervisor/HypervisorException.java index e4c0b3c..d59bf62 100644 --- a/src/main/java/tg/cyberlabmanager/hypervisor/HypervisorException.java +++ b/src/main/java/tg/cyberlabmanager/hypervisor/HypervisorException.java @@ -1,10 +1,24 @@ package tg.cyberlabmanager.hypervisor; +/** + * Exception commune aux erreurs d'orchestration d'hyperviseur. + */ public class HypervisorException extends Exception { + /** + * Cree une exception d'hyperviseur avec un message. + * + * @param message message decrivant l'erreur + */ public HypervisorException(String message) { super(message); } + /** + * Cree une exception d'hyperviseur avec un message et une cause. + * + * @param message message decrivant l'erreur + * @param cause cause initiale de l'erreur + */ public HypervisorException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/tg/cyberlabmanager/hypervisor/IHypervisor.java b/src/main/java/tg/cyberlabmanager/hypervisor/IHypervisor.java index c83757b..99ad3cf 100644 --- a/src/main/java/tg/cyberlabmanager/hypervisor/IHypervisor.java +++ b/src/main/java/tg/cyberlabmanager/hypervisor/IHypervisor.java @@ -4,18 +4,67 @@ import java.util.List; +/** + * Contrat minimal pour piloter un hyperviseur depuis l'application. + */ public interface IHypervisor { + /** + * Liste les machines virtuelles disponibles dans l'hyperviseur. + * + * @return liste de couples ou tableaux de donnees representant les VM + * @throws HypervisorException si la recuperation echoue + */ List listVMs() throws HypervisorException; + /** + * Retourne le statut courant d'une machine virtuelle. + * + * @param uuid UUID de la machine virtuelle + * @return statut courant de la VM + * @throws HypervisorException si le statut ne peut pas etre recupere + */ VMStatus getStatus(String uuid) throws HypervisorException; + /** + * Demarre une machine virtuelle. + * + * @param uuid UUID de la machine virtuelle + * @throws HypervisorException si le demarrage echoue + */ void startVM(String uuid) throws HypervisorException; + /** + * Arrete une machine virtuelle. + * + * @param uuid UUID de la machine virtuelle + * @throws HypervisorException si l'arret echoue + */ void stopVM(String uuid) throws HypervisorException; + /** + * Sauvegarde l'etat d'une machine virtuelle. + * + * @param uuid UUID de la machine virtuelle + * @throws HypervisorException si la sauvegarde d'etat echoue + */ void saveState(String uuid) throws HypervisorException; + /** + * Cree un snapshot pour une machine virtuelle. + * + * @param uuid UUID de la machine virtuelle + * @param name nom du snapshot + * @param description description du snapshot + * @throws HypervisorException si la creation du snapshot echoue + */ void takeSnapshot(String uuid, String name, String description) throws HypervisorException; + /** + * Restaure un snapshot pour une machine virtuelle. + * + * @param uuid UUID de la machine virtuelle + * @param snapshotName nom ou identifiant du snapshot + * @throws HypervisorException si la restauration echoue + */ void restoreSnapshot(String uuid, String snapshotName) throws HypervisorException; } diff --git a/src/main/java/tg/cyberlabmanager/hypervisor/VBoxException.java b/src/main/java/tg/cyberlabmanager/hypervisor/VBoxException.java index 7a513e0..9851744 100644 --- a/src/main/java/tg/cyberlabmanager/hypervisor/VBoxException.java +++ b/src/main/java/tg/cyberlabmanager/hypervisor/VBoxException.java @@ -1,19 +1,41 @@ package tg.cyberlabmanager.hypervisor; +/** + * Exception specifique aux commandes VirtualBox executees via VBoxManage. + */ public class VBoxException extends HypervisorException { + /** Code de sortie retourne par VBoxManage. */ private final int exitCode; + /** Sortie standard ou erreur retournee par VBoxManage. */ private final String output; + /** + * Cree une exception VirtualBox avec le code de sortie et la sortie texte. + * + * @param message message decrivant l'erreur + * @param exitCode code de sortie de VBoxManage + * @param output sortie standard ou erreur produite par VBoxManage + */ public VBoxException(String message, int exitCode, String output) { super(message); this.exitCode = exitCode; this.output = output; } + /** + * Retourne le code de sortie de VBoxManage. + * + * @return code de sortie + */ public int getExitCode() { return exitCode; } + /** + * Retourne la sortie produite par VBoxManage. + * + * @return sortie standard ou erreur + */ public String getOutput() { return output; } diff --git a/src/main/java/tg/cyberlabmanager/hypervisor/VBoxOrchestrator.java b/src/main/java/tg/cyberlabmanager/hypervisor/VBoxOrchestrator.java index 4228933..b8f432d 100644 --- a/src/main/java/tg/cyberlabmanager/hypervisor/VBoxOrchestrator.java +++ b/src/main/java/tg/cyberlabmanager/hypervisor/VBoxOrchestrator.java @@ -4,37 +4,53 @@ import java.util.List; +/** + * Implementation de {@link IHypervisor} pour VirtualBox via VBoxManage. + */ public class VBoxOrchestrator implements IHypervisor { + /** + * Cree un orchestrateur VirtualBox. + */ + public VBoxOrchestrator() { + } + + /** {@inheritDoc} */ @Override public List listVMs() throws HypervisorException { throw new UnsupportedOperationException("Not implemented yet"); } + /** {@inheritDoc} */ @Override public VMStatus getStatus(String uuid) throws HypervisorException { throw new UnsupportedOperationException("Not implemented yet"); } + /** {@inheritDoc} */ @Override public void startVM(String uuid) throws HypervisorException { throw new UnsupportedOperationException("Not implemented yet"); } + /** {@inheritDoc} */ @Override public void stopVM(String uuid) throws HypervisorException { throw new UnsupportedOperationException("Not implemented yet"); } + /** {@inheritDoc} */ @Override public void saveState(String uuid) throws HypervisorException { throw new UnsupportedOperationException("Not implemented yet"); } + /** {@inheritDoc} */ @Override public void takeSnapshot(String uuid, String name, String description) throws HypervisorException { throw new UnsupportedOperationException("Not implemented yet"); } + /** {@inheritDoc} */ @Override public void restoreSnapshot(String uuid, String snapshotName) throws HypervisorException { throw new UnsupportedOperationException("Not implemented yet"); diff --git a/src/main/java/tg/cyberlabmanager/model/AppConfig.java b/src/main/java/tg/cyberlabmanager/model/AppConfig.java index 6d6c891..04d3f60 100644 --- a/src/main/java/tg/cyberlabmanager/model/AppConfig.java +++ b/src/main/java/tg/cyberlabmanager/model/AppConfig.java @@ -1,21 +1,53 @@ package tg.cyberlabmanager.model; +/** + * Configuration persistante de l'application. + * + *

Elle contient les chemins utilises par les services techniques, notamment + * VBoxManage et le repertoire d'export PDF.

+ */ public class AppConfig { private String vboxManagePath; private String pdfExportDirectory; + /** + * Cree une configuration vide. + */ + public AppConfig() { + } + + /** + * Retourne le chemin de l'executable VBoxManage. + * + * @return chemin configure vers VBoxManage + */ public String getVboxManagePath() { return vboxManagePath; } + /** + * Definit le chemin de l'executable VBoxManage. + * + * @param vboxManagePath chemin vers VBoxManage + */ public void setVboxManagePath(String vboxManagePath) { this.vboxManagePath = vboxManagePath; } + /** + * Retourne le repertoire d'export des rapports PDF. + * + * @return repertoire d'export PDF + */ public String getPdfExportDirectory() { return pdfExportDirectory; } + /** + * Definit le repertoire d'export des rapports PDF. + * + * @param pdfExportDirectory repertoire d'export PDF + */ public void setPdfExportDirectory(String pdfExportDirectory) { this.pdfExportDirectory = pdfExportDirectory; } diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java index b4fee56..03a8ec2 100644 --- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java +++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java @@ -3,6 +3,12 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +/** + * Trace automatique d'une action de controle effectuee sur une machine virtuelle. + * + *

Cette entite est separee de {@link VirtualMachine} afin de conserver un + * historique potentiellement volumineux sans alourdir le modele de la VM.

+ */ public class AuditEntry { private static final DateTimeFormatter LOG_TIMESTAMP_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME; @@ -13,54 +19,125 @@ public class AuditEntry { private String details; private String labName; + /** + * Cree une entree d'audit vide. + */ + public AuditEntry() { + } + + /** + * Retourne l'identifiant local de l'entree d'audit. + * + * @return identifiant local + */ public int getId() { return id; } + /** + * Definit l'identifiant local de l'entree d'audit. + * + * @param id identifiant local + */ public void setId(int id) { this.id = id; } + /** + * Retourne l'horodatage de l'action. + * + * @return date et heure de l'action + */ public LocalDateTime getTimestamp() { return timestamp; } + /** + * Definit l'horodatage de l'action. + * + * @param timestamp date et heure de l'action + */ public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } + /** + * Retourne l'identifiant local de la VM concernee. + * + * @return identifiant de la VM, ou {@code null} si aucune VM n'est liee + */ public Integer getVmId() { return vmId; } + /** + * Definit l'identifiant local de la VM concernee. + * + * @param vmId identifiant de la VM, ou {@code null} + */ public void setVmId(Integer vmId) { this.vmId = vmId; } + /** + * Retourne l'action auditee. + * + * @return action auditee + */ public String getAction() { return action; } + /** + * Definit l'action auditee. + * + * @param action action auditee + */ public void setAction(String action) { this.action = action; } + /** + * Retourne les details de l'action. + * + * @return details de l'action + */ public String getDetails() { return details; } + /** + * Definit les details de l'action. + * + * @param details details de l'action + */ public void setDetails(String details) { this.details = details; } + /** + * Retourne le nom du laboratoire concerne. + * + * @return nom du laboratoire + */ public String getLabName() { return labName; } + /** + * Definit le nom du laboratoire concerne. + * + * @param labName nom du laboratoire + */ public void setLabName(String labName) { this.labName = labName; } + /** + * Construit une representation textuelle de l'entree d'audit. + * + * @return ligne de log lisible + */ public String getLog() { StringBuilder log = new StringBuilder(); log.append(timestamp == null ? "unknown-time" : timestamp.format(LOG_TIMESTAMP_FORMATTER)); diff --git a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java index ec7031f..76fca92 100644 --- a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java +++ b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java @@ -2,31 +2,73 @@ import java.time.LocalDateTime; +/** + * Note d'analyse horodatee saisie par l'analyste pour une machine virtuelle. + * + *

Une entree de journal est rattachee au cycle de vie logique d'une + * {@link VirtualMachine}.

+ */ public class JournalEntry { private int id; private LocalDateTime timestamp; private String content; + /** + * Cree une entree de journal vide. + */ + public JournalEntry() { + } + + /** + * Retourne l'identifiant local de l'entree de journal. + * + * @return identifiant local + */ public int getId() { return id; } + /** + * Definit l'identifiant local de l'entree de journal. + * + * @param id identifiant local + */ public void setId(int id) { this.id = id; } + /** + * Retourne l'horodatage de la note. + * + * @return date et heure de la note + */ public LocalDateTime getTimestamp() { return timestamp; } + /** + * Definit l'horodatage de la note. + * + * @param timestamp date et heure de la note + */ public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } + /** + * Retourne le contenu de la note. + * + * @return contenu saisi par l'analyste + */ public String getContent() { return content; } + /** + * Definit le contenu de la note. + * + * @param content contenu saisi par l'analyste + */ public void setContent(String content) { this.content = content; } diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java index 9e02ad8..2aac0df 100644 --- a/src/main/java/tg/cyberlabmanager/model/Lab.java +++ b/src/main/java/tg/cyberlabmanager/model/Lab.java @@ -4,6 +4,13 @@ import java.util.Collections; import java.util.List; +/** + * Entite racine representant un laboratoire thematique. + * + *

Ses attributs correspondent aux colonnes de la table {@code lab}. Les VM + * rattachees sont exposees en lecture seule et modifiees via les methodes + * d'agregation.

+ */ public class Lab { private int id; private String title; @@ -11,9 +18,20 @@ public class Lab { private String category; private final List virtualMachines = new ArrayList<>(); + /** + * Cree un laboratoire vide. + */ public Lab() { } + /** + * Cree un laboratoire avec ses champs principaux. + * + * @param id identifiant local du laboratoire + * @param title titre du laboratoire + * @param description description du laboratoire + * @param category categorie du laboratoire + */ public Lab(int id, String title, String description, String category) { this.id = id; this.title = title; @@ -21,46 +39,101 @@ public Lab(int id, String title, String description, String category) { this.category = category; } + /** + * Retourne l'identifiant local du laboratoire. + * + * @return identifiant local + */ public int getId() { return id; } + /** + * Definit l'identifiant local du laboratoire. + * + * @param id identifiant local + */ public void setId(int id) { this.id = id; } + /** + * Retourne le titre du laboratoire. + * + * @return titre du laboratoire + */ public String getTitle() { return title; } + /** + * Definit le titre du laboratoire. + * + * @param title titre du laboratoire + */ public void setTitle(String title) { this.title = title; } + /** + * Retourne la description du laboratoire. + * + * @return description du laboratoire + */ public String getDescription() { return description; } + /** + * Definit la description du laboratoire. + * + * @param description description du laboratoire + */ public void setDescription(String description) { this.description = description; } + /** + * Retourne la categorie du laboratoire. + * + * @return categorie du laboratoire + */ public String getCategory() { return category; } + /** + * Definit la categorie du laboratoire. + * + * @param category categorie du laboratoire + */ public void setCategory(String category) { this.category = category; } + /** + * Retourne les machines virtuelles rattachees au laboratoire. + * + * @return liste non modifiable des machines virtuelles + */ public List getVirtualMachines() { return Collections.unmodifiableList(virtualMachines); } + /** + * Rattache une machine virtuelle au laboratoire. + * + * @param virtualMachine machine virtuelle a ajouter + */ public void addVirtualMachine(VirtualMachine virtualMachine) { virtualMachines.add(virtualMachine); } + /** + * Retire une machine virtuelle du laboratoire. + * + * @param virtualMachine machine virtuelle a retirer + */ public void removeVirtualMachine(VirtualMachine virtualMachine) { virtualMachines.remove(virtualMachine); } diff --git a/src/main/java/tg/cyberlabmanager/model/Snapshot.java b/src/main/java/tg/cyberlabmanager/model/Snapshot.java index 03eed48..8b8c396 100644 --- a/src/main/java/tg/cyberlabmanager/model/Snapshot.java +++ b/src/main/java/tg/cyberlabmanager/model/Snapshot.java @@ -2,6 +2,13 @@ import java.time.LocalDateTime; +/** + * Instantane VirtualBox rattache a une machine virtuelle. + * + *

Le modele conserve l'identite locale en base, l'UUID VirtualBox, les + * metadonnees d'affichage et l'information indiquant si l'instantane a ete pris + * pendant que la VM etait en execution.

+ */ public class Snapshot { private int id; private String uuid; @@ -10,50 +17,116 @@ public class Snapshot { private String description; private boolean online; + /** + * Cree un snapshot vide. + */ + public Snapshot() { + } + + /** + * Retourne l'identifiant local du snapshot. + * + * @return identifiant local + */ public int getId() { return id; } + /** + * Definit l'identifiant local du snapshot. + * + * @param id identifiant local + */ public void setId(int id) { this.id = id; } + /** + * Retourne l'UUID VirtualBox du snapshot. + * + * @return UUID VirtualBox + */ public String getUuid() { return uuid; } + /** + * Definit l'UUID VirtualBox du snapshot. + * + * @param uuid UUID VirtualBox + */ public void setUuid(String uuid) { this.uuid = uuid; } + /** + * Retourne le nom du snapshot. + * + * @return nom du snapshot + */ public String getName() { return name; } + /** + * Definit le nom du snapshot. + * + * @param name nom du snapshot + */ public void setName(String name) { this.name = name; } + /** + * Retourne la date de creation du snapshot. + * + * @return date de creation + */ public LocalDateTime getCreatedAt() { return createdAt; } + /** + * Definit la date de creation du snapshot. + * + * @param createdAt date de creation + */ public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + /** + * Retourne la description du snapshot. + * + * @return description du snapshot + */ public String getDescription() { return description; } + /** + * Definit la description du snapshot. + * + * @param description description du snapshot + */ public void setDescription(String description) { this.description = description; } + /** + * Indique si le snapshot a ete pris VM allumee. + * + * @return {@code true} si le snapshot inclut l'etat en execution + */ public boolean isOnline() { return online; } + /** + * Definit si le snapshot a ete pris VM allumee. + * + * @param online {@code true} si le snapshot inclut l'etat en execution + */ public void setOnline(boolean online) { this.online = online; } diff --git a/src/main/java/tg/cyberlabmanager/model/VMStatus.java b/src/main/java/tg/cyberlabmanager/model/VMStatus.java index b6a8744..b014192 100644 --- a/src/main/java/tg/cyberlabmanager/model/VMStatus.java +++ b/src/main/java/tg/cyberlabmanager/model/VMStatus.java @@ -1,9 +1,17 @@ package tg.cyberlabmanager.model; +/** + * Etats stables d'une machine virtuelle tels qu'exposes par l'hyperviseur. + */ public enum VMStatus { + /** La VM est en cours d'execution. */ RUNNING, + /** La VM est eteinte. */ POWERED_OFF, + /** La VM est dans un etat sauvegarde. */ SAVED, + /** La VM est en pause. */ PAUSED, + /** Le statut de la VM n'a pas pu etre determine. */ UNKNOWN } diff --git a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java index f031a89..18d8f75 100644 --- a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java +++ b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java @@ -4,6 +4,13 @@ import java.util.Collections; import java.util.List; +/** + * Machine virtuelle importee depuis VirtualBox. + * + *

L'UUID VirtualBox fournit une reference stable. Les snapshots et notes + * d'analyse sont encapsules pour eviter l'exposition directe des listes + * internes.

+ */ public class VirtualMachine { private int id; private String name; @@ -13,59 +20,135 @@ public class VirtualMachine { private final List snapshots = new ArrayList<>(); private final List journalEntries = new ArrayList<>(); + /** + * Cree une machine virtuelle vide. + */ + public VirtualMachine() { + } + + /** + * Retourne l'identifiant local de la VM. + * + * @return identifiant local + */ public int getId() { return id; } + /** + * Definit l'identifiant local de la VM. + * + * @param id identifiant local + */ public void setId(int id) { this.id = id; } + /** + * Retourne le nom de la VM. + * + * @return nom de la VM + */ public String getName() { return name; } + /** + * Definit le nom de la VM. + * + * @param name nom de la VM + */ public void setName(String name) { this.name = name; } + /** + * Retourne l'UUID VirtualBox de la VM. + * + * @return UUID VirtualBox + */ public String getUuid() { return uuid; } + /** + * Definit l'UUID VirtualBox de la VM. + * + * @param uuid UUID VirtualBox + */ public void setUuid(String uuid) { this.uuid = uuid; } + /** + * Retourne l'URL de documentation associee a la VM. + * + * @return URL de documentation + */ public String getDocumentationUrl() { return documentationUrl; } + /** + * Definit l'URL de documentation associee a la VM. + * + * @param documentationUrl URL de documentation + */ public void setDocumentationUrl(String documentationUrl) { this.documentationUrl = documentationUrl; } + /** + * Retourne le statut courant de la VM. + * + * @return statut de la VM + */ public VMStatus getStatus() { return status; } + /** + * Definit le statut courant de la VM. + * + * @param status statut de la VM + */ public void setStatus(VMStatus status) { this.status = status; } + /** + * Retourne les snapshots rattaches a la VM. + * + * @return liste non modifiable des snapshots + */ public List getSnapshots() { return Collections.unmodifiableList(snapshots); } + /** + * Ajoute un snapshot a la VM. + * + * @param snapshot snapshot a ajouter + */ public void addSnapshot(Snapshot snapshot) { snapshots.add(snapshot); } + /** + * Retourne les notes d'analyse rattachees a la VM. + * + * @return liste non modifiable des notes d'analyse + */ public List getJournalEntries() { return Collections.unmodifiableList(journalEntries); } - public void addJournalEntry(JournalEntry journalEntry) { + /** + * Ajoute une note d'analyse a la VM. + * + * @param journalEntry note d'analyse a ajouter + */ + public void addJournalEntry(JournalEntry journalEntry) { journalEntries.add(journalEntry); } } diff --git a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java index 5c670d7..2ffbcb6 100644 --- a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java +++ b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java @@ -8,7 +8,26 @@ import java.io.IOException; import java.util.List; +/** + * Service responsable de l'export d'un laboratoire au format PDF. + */ public class PdfExporter { + /** + * Cree un exporteur PDF. + */ + public PdfExporter() { + } + + /** + * Exporte les donnees d'un laboratoire dans un fichier PDF. + * + * @param lab laboratoire exporte + * @param vms machines virtuelles du laboratoire + * @param snapshots snapshots a inclure + * @param journals notes d'analyse a inclure + * @param filePath chemin du fichier PDF cible + * @throws IOException si l'ecriture du PDF echoue + */ public void exportLabToPdf( Lab lab, List vms, diff --git a/src/main/java/tg/cyberlabmanager/ui/LabListView.java b/src/main/java/tg/cyberlabmanager/ui/LabListView.java index 7a10e42..8e06fb6 100644 --- a/src/main/java/tg/cyberlabmanager/ui/LabListView.java +++ b/src/main/java/tg/cyberlabmanager/ui/LabListView.java @@ -1,4 +1,12 @@ package tg.cyberlabmanager.ui; +/** + * Vue chargee d'afficher la liste des laboratoires. + */ public class LabListView { + /** + * Cree la vue de liste des laboratoires. + */ + public LabListView() { + } } diff --git a/src/main/java/tg/cyberlabmanager/ui/MainView.java b/src/main/java/tg/cyberlabmanager/ui/MainView.java index 0e763a1..b38c414 100644 --- a/src/main/java/tg/cyberlabmanager/ui/MainView.java +++ b/src/main/java/tg/cyberlabmanager/ui/MainView.java @@ -1,4 +1,12 @@ package tg.cyberlabmanager.ui; +/** + * Vue principale de l'application. + */ public class MainView { + /** + * Cree la vue principale. + */ + public MainView() { + } } diff --git a/src/main/java/tg/cyberlabmanager/ui/VmControlPanel.java b/src/main/java/tg/cyberlabmanager/ui/VmControlPanel.java index 49b4eea..e0692bc 100644 --- a/src/main/java/tg/cyberlabmanager/ui/VmControlPanel.java +++ b/src/main/java/tg/cyberlabmanager/ui/VmControlPanel.java @@ -1,4 +1,12 @@ package tg.cyberlabmanager.ui; +/** + * Vue chargee des controles utilisateur pour les machines virtuelles. + */ public class VmControlPanel { + /** + * Cree le panneau de controle des machines virtuelles. + */ + public VmControlPanel() { + } } From daaddcb8aecf0b69ab795427a5e2b2b69315746f Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Fri, 29 May 2026 20:25:53 +0000 Subject: [PATCH 06/14] Add unit tests for model classes --- .../java/tg/cyberlabmanager/model/.gitkeep | 1 - .../cyberlabmanager/model/AppConfigTest.java | 18 ++++++ .../cyberlabmanager/model/AuditEntryTest.java | 51 +++++++++++++++ .../model/JournalEntryTest.java | 23 +++++++ .../tg/cyberlabmanager/model/LabTest.java | 64 +++++++++++++++++++ .../cyberlabmanager/model/SnapshotTest.java | 30 +++++++++ .../cyberlabmanager/model/VMStatusTest.java | 18 ++++++ .../model/VirtualMachineTest.java | 61 ++++++++++++++++++ 8 files changed, 265 insertions(+), 1 deletion(-) delete mode 100644 src/test/java/tg/cyberlabmanager/model/.gitkeep create mode 100644 src/test/java/tg/cyberlabmanager/model/AppConfigTest.java create mode 100644 src/test/java/tg/cyberlabmanager/model/AuditEntryTest.java create mode 100644 src/test/java/tg/cyberlabmanager/model/JournalEntryTest.java create mode 100644 src/test/java/tg/cyberlabmanager/model/LabTest.java create mode 100644 src/test/java/tg/cyberlabmanager/model/SnapshotTest.java create mode 100644 src/test/java/tg/cyberlabmanager/model/VMStatusTest.java create mode 100644 src/test/java/tg/cyberlabmanager/model/VirtualMachineTest.java diff --git a/src/test/java/tg/cyberlabmanager/model/.gitkeep b/src/test/java/tg/cyberlabmanager/model/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/src/test/java/tg/cyberlabmanager/model/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/test/java/tg/cyberlabmanager/model/AppConfigTest.java b/src/test/java/tg/cyberlabmanager/model/AppConfigTest.java new file mode 100644 index 0000000..5b24c55 --- /dev/null +++ b/src/test/java/tg/cyberlabmanager/model/AppConfigTest.java @@ -0,0 +1,18 @@ +package tg.cyberlabmanager.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AppConfigTest { + @Test + void shouldStoreConfigurationPaths() { + AppConfig config = new AppConfig(); + + config.setVboxManagePath("/usr/bin/VBoxManage"); + config.setPdfExportDirectory("/tmp/reports"); + + assertEquals("/usr/bin/VBoxManage", config.getVboxManagePath()); + assertEquals("/tmp/reports", config.getPdfExportDirectory()); + } +} diff --git a/src/test/java/tg/cyberlabmanager/model/AuditEntryTest.java b/src/test/java/tg/cyberlabmanager/model/AuditEntryTest.java new file mode 100644 index 0000000..493d86a --- /dev/null +++ b/src/test/java/tg/cyberlabmanager/model/AuditEntryTest.java @@ -0,0 +1,51 @@ +package tg.cyberlabmanager.model; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AuditEntryTest { + @Test + void shouldStoreAuditFields() { + LocalDateTime timestamp = LocalDateTime.of(2026, 5, 29, 20, 30); + AuditEntry entry = new AuditEntry(); + + entry.setId(7); + entry.setTimestamp(timestamp); + entry.setVmId(12); + entry.setAction("START"); + entry.setDetails("VM started from UI"); + entry.setLabName("Malware Lab"); + + assertEquals(7, entry.getId()); + assertEquals(timestamp, entry.getTimestamp()); + assertEquals(12, entry.getVmId()); + assertEquals("START", entry.getAction()); + assertEquals("VM started from UI", entry.getDetails()); + assertEquals("Malware Lab", entry.getLabName()); + } + + @Test + void shouldBuildReadableLogLine() { + AuditEntry entry = new AuditEntry(); + entry.setTimestamp(LocalDateTime.of(2026, 5, 29, 20, 30)); + entry.setVmId(12); + entry.setAction("SNAPSHOT_TAKEN"); + entry.setLabName("Pentest Lab"); + entry.setDetails("Snapshot baseline created"); + + assertEquals( + "2026-05-29T20:30:00 | action=SNAPSHOT_TAKEN | vmId=12 | lab=Pentest Lab | details=Snapshot baseline created", + entry.getLog() + ); + } + + @Test + void shouldBuildFallbackLogWhenOptionalFieldsAreMissing() { + AuditEntry entry = new AuditEntry(); + + assertEquals("unknown-time | action=UNKNOWN", entry.getLog()); + } +} diff --git a/src/test/java/tg/cyberlabmanager/model/JournalEntryTest.java b/src/test/java/tg/cyberlabmanager/model/JournalEntryTest.java new file mode 100644 index 0000000..1abd563 --- /dev/null +++ b/src/test/java/tg/cyberlabmanager/model/JournalEntryTest.java @@ -0,0 +1,23 @@ +package tg.cyberlabmanager.model; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class JournalEntryTest { + @Test + void shouldStoreJournalEntryFields() { + LocalDateTime timestamp = LocalDateTime.of(2026, 5, 29, 21, 15); + JournalEntry entry = new JournalEntry(); + + entry.setId(3); + entry.setTimestamp(timestamp); + entry.setContent("Observed suspicious network traffic."); + + assertEquals(3, entry.getId()); + assertEquals(timestamp, entry.getTimestamp()); + assertEquals("Observed suspicious network traffic.", entry.getContent()); + } +} diff --git a/src/test/java/tg/cyberlabmanager/model/LabTest.java b/src/test/java/tg/cyberlabmanager/model/LabTest.java new file mode 100644 index 0000000..e8d5bc8 --- /dev/null +++ b/src/test/java/tg/cyberlabmanager/model/LabTest.java @@ -0,0 +1,64 @@ +package tg.cyberlabmanager.model; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class LabTest { + @Test + void shouldCreateLabWithConstructorValues() { + Lab lab = new Lab(1, "Malware Analysis", "Static and dynamic malware lab", "malware"); + + assertEquals(1, lab.getId()); + assertEquals("Malware Analysis", lab.getTitle()); + assertEquals("Static and dynamic malware lab", lab.getDescription()); + assertEquals("malware", lab.getCategory()); + } + + @Test + void shouldUpdateLabFieldsWithSetters() { + Lab lab = new Lab(); + + lab.setId(2); + lab.setTitle("Pentest"); + lab.setDescription("Web attack lab"); + lab.setCategory("pentest"); + + assertEquals(2, lab.getId()); + assertEquals("Pentest", lab.getTitle()); + assertEquals("Web attack lab", lab.getDescription()); + assertEquals("pentest", lab.getCategory()); + } + + @Test + void shouldAddAndRemoveVirtualMachines() { + Lab lab = new Lab(); + VirtualMachine vm = new VirtualMachine(); + vm.setName("Kali"); + + lab.addVirtualMachine(vm); + + List virtualMachines = lab.getVirtualMachines(); + assertEquals(1, virtualMachines.size()); + assertSame(vm, virtualMachines.get(0)); + + lab.removeVirtualMachine(vm); + + assertTrue(lab.getVirtualMachines().isEmpty()); + } + + @Test + void shouldExposeUnmodifiableVirtualMachineList() { + Lab lab = new Lab(); + + assertThrows( + UnsupportedOperationException.class, + () -> lab.getVirtualMachines().add(new VirtualMachine()) + ); + } +} diff --git a/src/test/java/tg/cyberlabmanager/model/SnapshotTest.java b/src/test/java/tg/cyberlabmanager/model/SnapshotTest.java new file mode 100644 index 0000000..9501f63 --- /dev/null +++ b/src/test/java/tg/cyberlabmanager/model/SnapshotTest.java @@ -0,0 +1,30 @@ +package tg.cyberlabmanager.model; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SnapshotTest { + @Test + void shouldStoreSnapshotFields() { + LocalDateTime createdAt = LocalDateTime.of(2026, 5, 29, 22, 0); + Snapshot snapshot = new Snapshot(); + + snapshot.setId(4); + snapshot.setUuid("snap-uuid-123"); + snapshot.setName("baseline"); + snapshot.setCreatedAt(createdAt); + snapshot.setDescription("Clean VM state"); + snapshot.setOnline(true); + + assertEquals(4, snapshot.getId()); + assertEquals("snap-uuid-123", snapshot.getUuid()); + assertEquals("baseline", snapshot.getName()); + assertEquals(createdAt, snapshot.getCreatedAt()); + assertEquals("Clean VM state", snapshot.getDescription()); + assertTrue(snapshot.isOnline()); + } +} diff --git a/src/test/java/tg/cyberlabmanager/model/VMStatusTest.java b/src/test/java/tg/cyberlabmanager/model/VMStatusTest.java new file mode 100644 index 0000000..5f86ffe --- /dev/null +++ b/src/test/java/tg/cyberlabmanager/model/VMStatusTest.java @@ -0,0 +1,18 @@ +package tg.cyberlabmanager.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +class VMStatusTest { + @Test + void shouldExposeExpectedStableStatuses() { + assertEquals(5, VMStatus.values().length); + assertSame(VMStatus.RUNNING, VMStatus.valueOf("RUNNING")); + assertSame(VMStatus.POWERED_OFF, VMStatus.valueOf("POWERED_OFF")); + assertSame(VMStatus.SAVED, VMStatus.valueOf("SAVED")); + assertSame(VMStatus.PAUSED, VMStatus.valueOf("PAUSED")); + assertSame(VMStatus.UNKNOWN, VMStatus.valueOf("UNKNOWN")); + } +} diff --git a/src/test/java/tg/cyberlabmanager/model/VirtualMachineTest.java b/src/test/java/tg/cyberlabmanager/model/VirtualMachineTest.java new file mode 100644 index 0000000..c986258 --- /dev/null +++ b/src/test/java/tg/cyberlabmanager/model/VirtualMachineTest.java @@ -0,0 +1,61 @@ +package tg.cyberlabmanager.model; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class VirtualMachineTest { + @Test + void shouldUseUnknownStatusByDefault() { + VirtualMachine vm = new VirtualMachine(); + + assertSame(VMStatus.UNKNOWN, vm.getStatus()); + } + + @Test + void shouldStoreVirtualMachineFields() { + VirtualMachine vm = new VirtualMachine(); + + vm.setId(5); + vm.setName("Ubuntu Lab"); + vm.setUuid("vm-uuid-456"); + vm.setDocumentationUrl("https://example.test/doc"); + vm.setStatus(VMStatus.RUNNING); + + assertEquals(5, vm.getId()); + assertEquals("Ubuntu Lab", vm.getName()); + assertEquals("vm-uuid-456", vm.getUuid()); + assertEquals("https://example.test/doc", vm.getDocumentationUrl()); + assertSame(VMStatus.RUNNING, vm.getStatus()); + } + + @Test + void shouldAddSnapshotsAndJournalEntries() { + VirtualMachine vm = new VirtualMachine(); + Snapshot snapshot = new Snapshot(); + JournalEntry journalEntry = new JournalEntry(); + + vm.addSnapshot(snapshot); + vm.addJournalEntry(journalEntry); + + List snapshots = vm.getSnapshots(); + List journalEntries = vm.getJournalEntries(); + + assertEquals(1, snapshots.size()); + assertSame(snapshot, snapshots.get(0)); + assertEquals(1, journalEntries.size()); + assertSame(journalEntry, journalEntries.get(0)); + } + + @Test + void shouldExposeUnmodifiableCollections() { + VirtualMachine vm = new VirtualMachine(); + + assertThrows(UnsupportedOperationException.class, () -> vm.getSnapshots().add(new Snapshot())); + assertThrows(UnsupportedOperationException.class, () -> vm.getJournalEntries().add(new JournalEntry())); + } +} From 9d130aa38bcbf5a3366a59f2e92759b9bc479657 Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Sat, 30 May 2026 02:09:29 +0000 Subject: [PATCH 07/14] Corrections mineures orthographe du JavaDoc --- .../tg/cyberlabmanager/model/AppConfig.java | 20 ++++----- .../tg/cyberlabmanager/model/AuditEntry.java | 42 +++++++++---------- .../cyberlabmanager/model/JournalEntry.java | 14 +++---- .../java/tg/cyberlabmanager/model/Lab.java | 32 +++++++------- .../tg/cyberlabmanager/model/Snapshot.java | 34 +++++++-------- .../tg/cyberlabmanager/model/VMStatus.java | 10 ++--- .../cyberlabmanager/model/VirtualMachine.java | 34 +++++++-------- 7 files changed, 93 insertions(+), 93 deletions(-) diff --git a/src/main/java/tg/cyberlabmanager/model/AppConfig.java b/src/main/java/tg/cyberlabmanager/model/AppConfig.java index 04d3f60..2a3fa9a 100644 --- a/src/main/java/tg/cyberlabmanager/model/AppConfig.java +++ b/src/main/java/tg/cyberlabmanager/model/AppConfig.java @@ -3,30 +3,30 @@ /** * Configuration persistante de l'application. * - *

Elle contient les chemins utilises par les services techniques, notamment - * VBoxManage et le repertoire d'export PDF.

+ *

Elle contient les chemins utilisés par les services techniques, notamment + * VBoxManage et le répertoire d'export PDF.

*/ public class AppConfig { private String vboxManagePath; private String pdfExportDirectory; /** - * Cree une configuration vide. + * Crée une configuration vide. */ public AppConfig() { } /** - * Retourne le chemin de l'executable VBoxManage. + * Retourne le chemin de l'exécutable VBoxManage. * - * @return chemin configure vers VBoxManage + * @return chemin configuré vers VBoxManage */ public String getVboxManagePath() { return vboxManagePath; } /** - * Definit le chemin de l'executable VBoxManage. + * Définit le chemin de l'exécutable VBoxManage. * * @param vboxManagePath chemin vers VBoxManage */ @@ -35,18 +35,18 @@ public void setVboxManagePath(String vboxManagePath) { } /** - * Retourne le repertoire d'export des rapports PDF. + * Retourne le répertoire d'export des rapports PDF. * - * @return repertoire d'export PDF + * @return répertoire d'export PDF */ public String getPdfExportDirectory() { return pdfExportDirectory; } /** - * Definit le repertoire d'export des rapports PDF. + * Définit le répertoire d'export des rapports PDF. * - * @param pdfExportDirectory repertoire d'export PDF + * @param pdfExportDirectory répertoire d'export PDF */ public void setPdfExportDirectory(String pdfExportDirectory) { this.pdfExportDirectory = pdfExportDirectory; diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java index 03a8ec2..7c87fea 100644 --- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java +++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java @@ -4,10 +4,10 @@ import java.time.format.DateTimeFormatter; /** - * Trace automatique d'une action de controle effectuee sur une machine virtuelle. + * Trace automatique d'une action de contrôle effectuée sur une machine virtuelle. * - *

Cette entite est separee de {@link VirtualMachine} afin de conserver un - * historique potentiellement volumineux sans alourdir le modele de la VM.

+ *

Cette entité est séparée de {@link VirtualMachine} afin de conserver un + * historique potentiellement volumineux sans alourdir le modèle de la VM.

*/ public class AuditEntry { private static final DateTimeFormatter LOG_TIMESTAMP_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME; @@ -20,13 +20,13 @@ public class AuditEntry { private String labName; /** - * Cree une entree d'audit vide. + * Crée une entrée d'audit vide. */ public AuditEntry() { } /** - * Retourne l'identifiant local de l'entree d'audit. + * Retourne l'identifiant local de l'entrée d'audit. * * @return identifiant local */ @@ -35,7 +35,7 @@ public int getId() { } /** - * Definit l'identifiant local de l'entree d'audit. + * Définit l'identifiant local de l'entrée d'audit. * * @param id identifiant local */ @@ -53,7 +53,7 @@ public LocalDateTime getTimestamp() { } /** - * Definit l'horodatage de l'action. + * Définit l'horodatage de l'action. * * @param timestamp date et heure de l'action */ @@ -62,16 +62,16 @@ public void setTimestamp(LocalDateTime timestamp) { } /** - * Retourne l'identifiant local de la VM concernee. + * Retourne l'identifiant local de la VM concernée. * - * @return identifiant de la VM, ou {@code null} si aucune VM n'est liee + * @return identifiant de la VM, ou {@code null} si aucune VM n'est liée */ public Integer getVmId() { return vmId; } /** - * Definit l'identifiant local de la VM concernee. + * Définit l'identifiant local de la VM concernée. * * @param vmId identifiant de la VM, ou {@code null} */ @@ -80,43 +80,43 @@ public void setVmId(Integer vmId) { } /** - * Retourne l'action auditee. + * Retourne l'action auditée. * - * @return action auditee + * @return action auditée */ public String getAction() { return action; } /** - * Definit l'action auditee. + * Définit l'action auditée. * - * @param action action auditee + * @param action action auditée */ public void setAction(String action) { this.action = action; } /** - * Retourne les details de l'action. + * Retourne les détails de l'action. * - * @return details de l'action + * @return détails de l'action */ public String getDetails() { return details; } /** - * Definit les details de l'action. + * Définit les détails de l'action. * - * @param details details de l'action + * @param details détails de l'action */ public void setDetails(String details) { this.details = details; } /** - * Retourne le nom du laboratoire concerne. + * Retourne le nom du laboratoire concerné. * * @return nom du laboratoire */ @@ -125,7 +125,7 @@ public String getLabName() { } /** - * Definit le nom du laboratoire concerne. + * Définit le nom du laboratoire concerné. * * @param labName nom du laboratoire */ @@ -134,7 +134,7 @@ public void setLabName(String labName) { } /** - * Construit une representation textuelle de l'entree d'audit. + * Construit une représentation textuelle de l'entrée d'audit. * * @return ligne de log lisible */ diff --git a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java index 76fca92..d2287ad 100644 --- a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java +++ b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java @@ -3,9 +3,9 @@ import java.time.LocalDateTime; /** - * Note d'analyse horodatee saisie par l'analyste pour une machine virtuelle. + * Note d'analyse horodatée saisie par l'analyste pour une machine virtuelle. * - *

Une entree de journal est rattachee au cycle de vie logique d'une + *

Une entrée de journal est rattachée au cycle de vie logique d'une * {@link VirtualMachine}.

*/ public class JournalEntry { @@ -14,13 +14,13 @@ public class JournalEntry { private String content; /** - * Cree une entree de journal vide. + * Crée une entrée de journal vide. */ public JournalEntry() { } /** - * Retourne l'identifiant local de l'entree de journal. + * Retourne l'identifiant local de l'entrée de journal. * * @return identifiant local */ @@ -29,7 +29,7 @@ public int getId() { } /** - * Definit l'identifiant local de l'entree de journal. + * Définit l'identifiant local de l'entrée de journal. * * @param id identifiant local */ @@ -47,7 +47,7 @@ public LocalDateTime getTimestamp() { } /** - * Definit l'horodatage de la note. + * Définit l'horodatage de la note. * * @param timestamp date et heure de la note */ @@ -65,7 +65,7 @@ public String getContent() { } /** - * Definit le contenu de la note. + * Définit le contenu de la note. * * @param content contenu saisi par l'analyste */ diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java index 2aac0df..8f82900 100644 --- a/src/main/java/tg/cyberlabmanager/model/Lab.java +++ b/src/main/java/tg/cyberlabmanager/model/Lab.java @@ -5,11 +5,11 @@ import java.util.List; /** - * Entite racine representant un laboratoire thematique. + * Entité racine représentant un laboratoire thématique. * *

Ses attributs correspondent aux colonnes de la table {@code lab}. Les VM - * rattachees sont exposees en lecture seule et modifiees via les methodes - * d'agregation.

+ * rattachées sont exposées en lecture seule et modifiées via les méthodes + * d'agrégation.

*/ public class Lab { private int id; @@ -19,18 +19,18 @@ public class Lab { private final List virtualMachines = new ArrayList<>(); /** - * Cree un laboratoire vide. + * Crée un laboratoire vide. */ public Lab() { } /** - * Cree un laboratoire avec ses champs principaux. + * Crée un laboratoire avec ses champs principaux. * * @param id identifiant local du laboratoire * @param title titre du laboratoire * @param description description du laboratoire - * @param category categorie du laboratoire + * @param category catégorie du laboratoire */ public Lab(int id, String title, String description, String category) { this.id = id; @@ -49,7 +49,7 @@ public int getId() { } /** - * Definit l'identifiant local du laboratoire. + * Définit l'identifiant local du laboratoire. * * @param id identifiant local */ @@ -67,7 +67,7 @@ public String getTitle() { } /** - * Definit le titre du laboratoire. + * Définit le titre du laboratoire. * * @param title titre du laboratoire */ @@ -85,7 +85,7 @@ public String getDescription() { } /** - * Definit la description du laboratoire. + * Définit la description du laboratoire. * * @param description description du laboratoire */ @@ -94,25 +94,25 @@ public void setDescription(String description) { } /** - * Retourne la categorie du laboratoire. + * Retourne la catégorie du laboratoire. * - * @return categorie du laboratoire + * @return catégorie du laboratoire */ public String getCategory() { return category; } /** - * Definit la categorie du laboratoire. + * Définit la catégorie du laboratoire. * - * @param category categorie du laboratoire + * @param category catégorie du laboratoire */ public void setCategory(String category) { this.category = category; } /** - * Retourne les machines virtuelles rattachees au laboratoire. + * Retourne les machines virtuelles rattachées au laboratoire. * * @return liste non modifiable des machines virtuelles */ @@ -123,7 +123,7 @@ public List getVirtualMachines() { /** * Rattache une machine virtuelle au laboratoire. * - * @param virtualMachine machine virtuelle a ajouter + * @param virtualMachine machine virtuelle à ajouter */ public void addVirtualMachine(VirtualMachine virtualMachine) { virtualMachines.add(virtualMachine); @@ -132,7 +132,7 @@ public void addVirtualMachine(VirtualMachine virtualMachine) { /** * Retire une machine virtuelle du laboratoire. * - * @param virtualMachine machine virtuelle a retirer + * @param virtualMachine machine virtuelle à retirer */ public void removeVirtualMachine(VirtualMachine virtualMachine) { virtualMachines.remove(virtualMachine); diff --git a/src/main/java/tg/cyberlabmanager/model/Snapshot.java b/src/main/java/tg/cyberlabmanager/model/Snapshot.java index 8b8c396..cf69b33 100644 --- a/src/main/java/tg/cyberlabmanager/model/Snapshot.java +++ b/src/main/java/tg/cyberlabmanager/model/Snapshot.java @@ -3,11 +3,11 @@ import java.time.LocalDateTime; /** - * Instantane VirtualBox rattache a une machine virtuelle. + * Instantané VirtualBox rattaché à une machine virtuelle. * - *

Le modele conserve l'identite locale en base, l'UUID VirtualBox, les - * metadonnees d'affichage et l'information indiquant si l'instantane a ete pris - * pendant que la VM etait en execution.

+ *

Le modèle conserve l'identité locale en base, l'UUID VirtualBox, les + * métadonnées d'affichage et l'information indiquant si l'instantané a été pris + * pendant que la VM était en exécution.

*/ public class Snapshot { private int id; @@ -18,7 +18,7 @@ public class Snapshot { private boolean online; /** - * Cree un snapshot vide. + * Crée un snapshot vide. */ public Snapshot() { } @@ -33,7 +33,7 @@ public int getId() { } /** - * Definit l'identifiant local du snapshot. + * Définit l'identifiant local du snapshot. * * @param id identifiant local */ @@ -51,7 +51,7 @@ public String getUuid() { } /** - * Definit l'UUID VirtualBox du snapshot. + * Définit l'UUID VirtualBox du snapshot. * * @param uuid UUID VirtualBox */ @@ -69,7 +69,7 @@ public String getName() { } /** - * Definit le nom du snapshot. + * Définit le nom du snapshot. * * @param name nom du snapshot */ @@ -78,18 +78,18 @@ public void setName(String name) { } /** - * Retourne la date de creation du snapshot. + * Retourne la date de création du snapshot. * - * @return date de creation + * @return date de création */ public LocalDateTime getCreatedAt() { return createdAt; } /** - * Definit la date de creation du snapshot. + * Définit la date de création du snapshot. * - * @param createdAt date de creation + * @param createdAt date de création */ public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; @@ -105,7 +105,7 @@ public String getDescription() { } /** - * Definit la description du snapshot. + * Définit la description du snapshot. * * @param description description du snapshot */ @@ -114,18 +114,18 @@ public void setDescription(String description) { } /** - * Indique si le snapshot a ete pris VM allumee. + * Indique si le snapshot a été pris VM allumée. * - * @return {@code true} si le snapshot inclut l'etat en execution + * @return {@code true} si le snapshot inclut l'état en exécution */ public boolean isOnline() { return online; } /** - * Definit si le snapshot a ete pris VM allumee. + * Définit si le snapshot a été pris VM allumée. * - * @param online {@code true} si le snapshot inclut l'etat en execution + * @param online {@code true} si le snapshot inclut l'état en exécution */ public void setOnline(boolean online) { this.online = online; diff --git a/src/main/java/tg/cyberlabmanager/model/VMStatus.java b/src/main/java/tg/cyberlabmanager/model/VMStatus.java index b014192..3b0130a 100644 --- a/src/main/java/tg/cyberlabmanager/model/VMStatus.java +++ b/src/main/java/tg/cyberlabmanager/model/VMStatus.java @@ -1,17 +1,17 @@ package tg.cyberlabmanager.model; /** - * Etats stables d'une machine virtuelle tels qu'exposes par l'hyperviseur. + * États stables d'une machine virtuelle tels qu'exposés par l'hyperviseur. */ public enum VMStatus { - /** La VM est en cours d'execution. */ + /** La VM est en cours d'exécution. */ RUNNING, - /** La VM est eteinte. */ + /** La VM est éteinte. */ POWERED_OFF, - /** La VM est dans un etat sauvegarde. */ + /** La VM est dans un état sauvegardé. */ SAVED, /** La VM est en pause. */ PAUSED, - /** Le statut de la VM n'a pas pu etre determine. */ + /** Le statut de la VM n'a pas pu être déterminé. */ UNKNOWN } diff --git a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java index 18d8f75..e8442cd 100644 --- a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java +++ b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java @@ -5,11 +5,11 @@ import java.util.List; /** - * Machine virtuelle importee depuis VirtualBox. + * Machine virtuelle importée depuis VirtualBox. * - *

L'UUID VirtualBox fournit une reference stable. Les snapshots et notes - * d'analyse sont encapsules pour eviter l'exposition directe des listes - * internes.

+ *

L'UUID VirtualBox fournit une référence stable. Les snapshots et notes + * d'analyse sont encapsulés pour éviter l'exposition directe des listes + * internes.

*/ public class VirtualMachine { private int id; @@ -21,7 +21,7 @@ public class VirtualMachine { private final List journalEntries = new ArrayList<>(); /** - * Cree une machine virtuelle vide. + * Crée une machine virtuelle vide. */ public VirtualMachine() { } @@ -36,7 +36,7 @@ public int getId() { } /** - * Definit l'identifiant local de la VM. + * Définit l'identifiant local de la VM. * * @param id identifiant local */ @@ -54,7 +54,7 @@ public String getName() { } /** - * Definit le nom de la VM. + * Définit le nom de la VM. * * @param name nom de la VM */ @@ -72,7 +72,7 @@ public String getUuid() { } /** - * Definit l'UUID VirtualBox de la VM. + * Définit l'UUID VirtualBox de la VM. * * @param uuid UUID VirtualBox */ @@ -81,7 +81,7 @@ public void setUuid(String uuid) { } /** - * Retourne l'URL de documentation associee a la VM. + * Retourne l'URL de documentation associée à la VM. * * @return URL de documentation */ @@ -90,7 +90,7 @@ public String getDocumentationUrl() { } /** - * Definit l'URL de documentation associee a la VM. + * Définit l'URL de documentation associée à la VM. * * @param documentationUrl URL de documentation */ @@ -108,7 +108,7 @@ public VMStatus getStatus() { } /** - * Definit le statut courant de la VM. + * Définit le statut courant de la VM. * * @param status statut de la VM */ @@ -117,7 +117,7 @@ public void setStatus(VMStatus status) { } /** - * Retourne les snapshots rattaches a la VM. + * Retourne les snapshots rattachés à la VM. * * @return liste non modifiable des snapshots */ @@ -126,16 +126,16 @@ public List getSnapshots() { } /** - * Ajoute un snapshot a la VM. + * Ajoute un snapshot à la VM. * - * @param snapshot snapshot a ajouter + * @param snapshot snapshot à ajouter */ public void addSnapshot(Snapshot snapshot) { snapshots.add(snapshot); } /** - * Retourne les notes d'analyse rattachees a la VM. + * Retourne les notes d'analyse rattachées à la VM. * * @return liste non modifiable des notes d'analyse */ @@ -144,9 +144,9 @@ public List getJournalEntries() { } /** - * Ajoute une note d'analyse a la VM. + * Ajoute une note d'analyse à la VM. * - * @param journalEntry note d'analyse a ajouter + * @param journalEntry note d'analyse à ajouter */ public void addJournalEntry(JournalEntry journalEntry) { journalEntries.add(journalEntry); From 51ac4bdb50b709442dcc4b3d718d1d3d0023fc06 Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Sat, 30 May 2026 15:00:12 +0000 Subject: [PATCH 08/14] chore: add CI workflow and PR template --- .github/workflows/ci.yml | 34 ++++++++++++++++++++++ .github/workflows/pull_request_template.md | 25 ++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pull_request_template.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d07d6d8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + branches: [ "main", "develop", "feature/**", "fix/**" ] + pull_request: + branches: [ "develop", "main" ] + +jobs: + build-and-test: + name: Build & Test (Java 17) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Java 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven # met le dépôt Maven en cache → builds 2x plus rapides + + - name: Build & run tests + run: ./mvnw test --no-transfer-progress + + - name: Upload Surefire reports on failure + if: failure() # s'exécute UNIQUEMENT si les tests échouent + uses: actions/upload-artifact@v4 + with: + name: surefire-reports + path: target/surefire-reports/ + retention-days: 7 diff --git a/.github/workflows/pull_request_template.md b/.github/workflows/pull_request_template.md new file mode 100644 index 0000000..b7bbc66 --- /dev/null +++ b/.github/workflows/pull_request_template.md @@ -0,0 +1,25 @@ +## Ce que fait ce MR + + + +## Couche concernée + +- [ ] `model` — entités et POJOs +- [ ] `hypervisor` — IHypervisor / VBoxOrchestrator +- [ ] `data` — DatabaseManager / schema SQL +- [ ] `pdf` — PdfExporter +- [ ] `controller` — LabController +- [ ] `ui` — vues JavaFX +- [ ] Config / build (pom.xml, module-info, CI) + +## Checklist avant review + +- [ ] `./mvnw test` passe en local (0 failure, 0 error) +- [ ] Javadoc présent sur toutes les classes et méthodes publiques ajoutées +- [ ] Pas de `System.out.println` de debug oublié +- [ ] Pas de TODO non intentionnel +- [ ] Les imports inutilisés ont été supprimés (IntelliJ : Ctrl+Alt+O) + +## Pour le reviewer + + From cac0d7bc6dbd80422abd6da80962958fda03668d Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Sat, 30 May 2026 15:16:44 +0000 Subject: [PATCH 09/14] fix: move PR template to correct location --- .github/{workflows => }/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => }/pull_request_template.md (100%) diff --git a/.github/workflows/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/workflows/pull_request_template.md rename to .github/pull_request_template.md From 4ab5d25f976a50a84cb9c652fcd1c931d1184515 Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Sat, 30 May 2026 15:28:41 +0000 Subject: [PATCH 10/14] Add: Adding CONTRIBUTING to the repo --- CONTRIBUTING.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..baae740 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,74 @@ +# Guide de contribution — Cyber Lab Manager + +## Branches + +| Préfixe | Usage | Exemple | +|---|---|---| +| `feature/` | Nouvelle fonctionnalité | `feature/vbox-orchestrator` | +| `fix/` | Correction de bug | `fix/snapshot-uuid-null` | +| `chore/` | Config, CI, docs | `chore/github-config` | +| `test/` | Ajout ou correction de tests seuls | `test/hypervisor-coverage` | + +**Règle absolue :** on ne pousse jamais directement sur `develop` ou `main`. +Toujours passer par un MR depuis une branche `feature/` ou `fix/`. + +## Format des commits + +On suit [Conventional Commits](https://www.conventionalcommits.org/fr) : + +``` +(): +``` + +| Type | Quand l'utiliser | +|---|---| +| `feat` | Ajout d'une fonctionnalité | +| `fix` | Correction d'un bug | +| `test` | Ajout ou modification de tests | +| `docs` | Documentation (Javadoc, Markdown) | +| `refactor` | Refactoring sans changement de comportement | +| `chore` | Config build, CI, dépendances | + +**Exemples :** +``` +feat(model): add Snapshot uuid and online fields +fix(hypervisor): handle VBoxManage non-zero exit code +test(model): add LabTest unmodifiable list assertion +docs(hypervisor): complete Javadoc on IHypervisor +chore: add GitHub Actions CI workflow +``` + +## Ouvrir un MR + +1. Ta branche est à jour avec `develop` (`git rebase develop` ou `git merge develop`) +2. `./mvnw test` passe en local — **0 failure, 0 error** +3. Javadoc présent sur toutes les classes et méthodes publiques ajoutées +4. Ouvre le MR vers `develop` sur GitHub +5. Remplis le template de MR (pré-rempli automatiquement) +6. Assigne au moins un reviewer + +## Revue de code + +- Le reviewer a **48h** pour donner son retour +- Un commentaire `[bloquant]` doit être résolu avant le merge +- Un commentaire `[suggestion]` est optionnel +- Approuver = confirmer que les tests passent ET que le code est lisible + +## Standards de code + +- **Java 17**, style standard IntelliJ (Ctrl+Alt+L pour formatter) +- Javadoc obligatoire sur toutes les classes et méthodes `public` +- Pas de `System.out.println` → utiliser `java.util.logging.Logger` +- Imports inutilisés supprimés (Ctrl+Alt+O dans IntelliJ) +- Pas de TODO non documenté dans un MR + +## Responsabilités par package + +| Package | Responsable | +|---|---| +| `tg.cyberlabmanager.model` | Rôle 3 | +| `tg.cyberlabmanager.hypervisor` | Rôle 3 | +| `tg.cyberlabmanager.data` | Rôle 2 | +| `tg.cyberlabmanager.pdf` | Rôle 2 | +| `tg.cyberlabmanager.ui` | Rôle 1 | +| `tg.cyberlabmanager.controller` | Rôle 1 | From 68d90c3da3a6f6d5e3b70e6e6125c86363bfa31c Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Sat, 30 May 2026 15:30:35 +0000 Subject: [PATCH 11/14] chore: untrack target directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index eef09ab..a6268c3 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ build/ ### Mac OS ### .DS_Store +target/ From 6d2a438f8f940286d1d084222675fe80f4627455 Mon Sep 17 00:00:00 2001 From: TheSOCAnalyst Date: Sat, 30 May 2026 15:54:59 +0000 Subject: [PATCH 12/14] docs: add SETUP.md environment guide --- docs/Markdowns/SETUP.md | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/Markdowns/SETUP.md diff --git a/docs/Markdowns/SETUP.md b/docs/Markdowns/SETUP.md new file mode 100644 index 0000000..738100d --- /dev/null +++ b/docs/Markdowns/SETUP.md @@ -0,0 +1,89 @@ +# SETUP — Environnement de développement + +## Prérequis + +| Outil | Version minimale | Vérification | +|---|---|---| +| JDK | 17 (LTS) | `java -version` | +| Maven | 3.8+ | `./mvnw -version` | +| VirtualBox | 7.0+ | Pour les tests d'intégration | +| Git | 2.x | `git --version` | + +> Le projet utilise le Maven Wrapper (`./mvnw`) — pas besoin d'installer Maven globalement. + +--- + +## Configuration IDE — point critique à lire + +Le projet compile en **Java 17** même si tu as un JDK plus récent (21, 22…) sur ta machine. +C'est contrôlé par cette ligne dans `pom.xml` : + +```xml +17 +``` + +Cette propriété fixe simultanément la syntaxe acceptée, le bytecode cible (class file version 61) +et les APIs accessibles — le compilateur de ton JDK s'adapte automatiquement. + +### IntelliJ IDEA + +**File → Project Structure → Project** : +- SDK : ton JDK installé (17, 21, peu importe) +- Language level : **17** + +**File → Settings → Build → Compiler → Java Compiler** : +- Target bytecode version : **17** + +### Pourquoi c'est important + +Si quelqu'un compile avec bytecode Java 21 et pousse du code, les autres obtiendront : +``` +UnsupportedClassVersionError: Unsupported major.minor version 65.0 +``` +La version 65 = Java 21. La version 61 = Java 17 (notre cible). +Garder tout le monde sur la même cible évite ce type de conflit au merge. + +--- + +## Lancer le projet + +```bash +# Compiler et lancer tous les tests +./mvnw test + +# Lancer l'application JavaFX +./mvnw javafx:run + +# Générer la Javadoc dans target/reports/apidocs/ +./mvnw javadoc:javadoc +``` + +> Si tu utilises un JDK non standard, préfixe avec JAVA_HOME : +> `JAVA_HOME=/usr/lib/jvm/jdk-21.0.11-oracle-x64 ./mvnw test` + +--- + +## Structure des branches + +``` +main ← releases stables uniquement +develop ← intégration continue (branche de référence) +feature/x ← développement d'une fonctionnalité +fix/x ← correction de bug +chore/x ← config, CI, documentation +``` + +Voir [CONTRIBUTING.md](../CONTRIBUTING.md) pour les conventions complètes. + +--- + +## Responsabilités par package + +| Package | Rôle | Contenu | +|---|---|---| +| `tg.cyberlabmanager.model` | Rôle 3 | Entités métier (Lab, VM, Snapshot…) | +| `tg.cyberlabmanager.hypervisor` | Rôle 3 | IHypervisor, VBoxOrchestrator | +| `tg.cyberlabmanager.data` | Rôle 2 | DatabaseManager, SQLite | +| `tg.cyberlabmanager.pdf` | Rôle 2 | PdfExporter | +| `tg.cyberlabmanager.ui` | Rôle 1 | Vues JavaFX | +| `tg.cyberlabmanager.controller` | Rôle 1 | LabController | From 2e605d2c249f2cda58d0ac525bb1558406e31cbf Mon Sep 17 00:00:00 2001 From: akpmarcelin Date: Sat, 20 Jun 2026 19:34:29 +0000 Subject: [PATCH 13/14] fix(data,pdf): adapt to Role 3 real model classes --- .../cyberlabmanager/data/DatabaseManager.java | 270 ++++++++---------- .../tg/cyberlabmanager/pdf/PdfExporter.java | 104 +++---- 2 files changed, 162 insertions(+), 212 deletions(-) diff --git a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java index 56ac366..ee485b0 100644 --- a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java +++ b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java @@ -1,53 +1,58 @@ package tg.cyberlabmanager.data; -import tg.cyberlabmanager.model.*; +import tg.cyberlabmanager.model.AppConfig; +import tg.cyberlabmanager.model.AuditEntry; +import tg.cyberlabmanager.model.JournalEntry; +import tg.cyberlabmanager.model.Lab; +import tg.cyberlabmanager.model.Snapshot; +import tg.cyberlabmanager.model.VMStatus; +import tg.cyberlabmanager.model.VirtualMachine; import java.sql.*; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; /** - * Gestionnaire de la base de données SQLite de Cyber Lab Manager. - * Fournit toutes les opérations CRUD pour les labos, VMs, snapshots, - * journal de bord, audit et configuration. + * Service d'accès aux données de l'application. * - * Utilisation normale : new DatabaseManager("jdbc:sqlite:/home/user/cyberlab.db") - * Utilisation en test : new DatabaseManager("jdbc:sqlite::memory:") + * Il masque la persistance SQLite derrière des opérations de haut niveau sur + * les entités métier. */ public class DatabaseManager { private final String jdbcUrl; - - // ========================================================================= + // Constructeur + initialisation - // ========================================================================= /** - * Crée le DatabaseManager et initialise les tables si elles n'existent pas. + * Crée un gestionnaire de base de données utilisant le fichier SQLite + * par défaut situé dans le répertoire utilisateur. + */ + public DatabaseManager() { + this("jdbc:sqlite:" + System.getProperty("user.home") + "/cyberlab.db"); + } + + /** + * Crée un gestionnaire de base de données avec une URL JDBC explicite. + * Utile pour les tests (ex: "jdbc:sqlite::memory:"). * - * @param jdbcUrl URL JDBC SQLite, ex: "jdbc:sqlite:/home/user/cyberlab.db" - * ou "jdbc:sqlite::memory:" pour les tests + * @param jdbcUrl URL JDBC SQLite */ public DatabaseManager(String jdbcUrl) { this.jdbcUrl = jdbcUrl; initDatabase(); } - /** - * Ouvre et retourne une connexion SQLite. - * Toujours utilisée dans un try-with-resources pour garantir la fermeture. - */ private Connection getConnection() throws SQLException { return DriverManager.getConnection(jdbcUrl); } /** * Crée toutes les tables si elles n'existent pas encore. - * Appelé automatiquement au démarrage. */ private void initDatabase() { String[] tables = { - // Table des laboratoires "CREATE TABLE IF NOT EXISTS labo (" + " id_labo INTEGER PRIMARY KEY AUTOINCREMENT," + " titre TEXT NOT NULL," + @@ -55,26 +60,25 @@ private void initDatabase() { " categorie TEXT" + ");", - // Table des machines virtuelles "CREATE TABLE IF NOT EXISTS vm (" + - " id_vm INTEGER PRIMARY KEY AUTOINCREMENT," + - " nom_vm TEXT NOT NULL," + - " uuid TEXT UNIQUE," + - " id_labo INTEGER REFERENCES labo(id_labo) ON DELETE SET NULL," + - " lien_doc TEXT," + - " chemin_vboxmanage TEXT" + + " id_vm INTEGER PRIMARY KEY AUTOINCREMENT," + + " nom_vm TEXT NOT NULL," + + " uuid TEXT UNIQUE," + + " id_labo INTEGER REFERENCES labo(id_labo) ON DELETE SET NULL," + + " lien_doc TEXT," + + " statut TEXT" + ");", - // Table des snapshots "CREATE TABLE IF NOT EXISTS snapshot (" + " id_snap INTEGER PRIMARY KEY AUTOINCREMENT," + " id_vm INTEGER NOT NULL REFERENCES vm(id_vm) ON DELETE CASCADE," + + " uuid TEXT," + " nom_snap TEXT," + " date_creation TEXT," + - " description TEXT" + + " description TEXT," + + " online INTEGER DEFAULT 0" + ");", - // Table du journal de bord "CREATE TABLE IF NOT EXISTS journal_entry (" + " id_entry INTEGER PRIMARY KEY AUTOINCREMENT," + " id_vm INTEGER NOT NULL REFERENCES vm(id_vm) ON DELETE CASCADE," + @@ -82,16 +86,15 @@ private void initDatabase() { " contenu TEXT NOT NULL" + ");", - // Table d'audit automatique "CREATE TABLE IF NOT EXISTS audit_log (" + " id_log INTEGER PRIMARY KEY AUTOINCREMENT," + " horodatage TEXT NOT NULL," + " id_vm INTEGER REFERENCES vm(id_vm) ON DELETE SET NULL," + " action TEXT NOT NULL," + - " details TEXT" + + " details TEXT," + + " lab_name TEXT" + ");", - // Table de configuration (clé/valeur) "CREATE TABLE IF NOT EXISTS app_config (" + " cle TEXT PRIMARY KEY," + " valeur TEXT" + @@ -108,41 +111,34 @@ private void initDatabase() { } } - // ========================================================================= // LABORATOIRES - // ========================================================================= /** - * Sauvegarde un laboratoire. Fait un INSERT si l'id est 0, un UPDATE sinon. - * Après INSERT, l'id généré est assigné à l'objet lab. + * Enregistre ou met à jour un laboratoire. * - * @param lab le laboratoire à sauvegarder + * @param lab laboratoire à persister */ public void saveLab(Lab lab) { if (lab.getId() == 0) { - // INSERT String sql = "INSERT INTO labo (titre, description, categorie) VALUES (?, ?, ?)"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { - ps.setString(1, lab.getTitre()); + ps.setString(1, lab.getTitle()); ps.setString(2, lab.getDescription()); - ps.setString(3, lab.getCategorie()); + ps.setString(3, lab.getCategory()); ps.executeUpdate(); ResultSet keys = ps.getGeneratedKeys(); - if (keys.next()) { - lab.setId(keys.getInt(1)); - } + if (keys.next()) lab.setId(keys.getInt(1)); } catch (SQLException e) { throw new RuntimeException("Erreur saveLab (INSERT) : " + e.getMessage(), e); } } else { - // UPDATE String sql = "UPDATE labo SET titre=?, description=?, categorie=? WHERE id_labo=?"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { - ps.setString(1, lab.getTitre()); + ps.setString(1, lab.getTitle()); ps.setString(2, lab.getDescription()); - ps.setString(3, lab.getCategorie()); + ps.setString(3, lab.getCategory()); ps.setInt(4, lab.getId()); ps.executeUpdate(); } catch (SQLException e) { @@ -152,10 +148,10 @@ public void saveLab(Lab lab) { } /** - * Récupère un laboratoire par son identifiant. + * Récupère un laboratoire par son identifiant local. * - * @param id l'identifiant du laboratoire - * @return le Lab correspondant, ou null si introuvable + * @param id identifiant local du laboratoire + * @return laboratoire correspondant, ou null si introuvable */ public Lab getLab(int id) { String sql = "SELECT id_labo, titre, description, categorie FROM labo WHERE id_labo=?"; @@ -173,9 +169,9 @@ public Lab getLab(int id) { } /** - * Retourne tous les laboratoires enregistrés. + * Récupère tous les laboratoires. * - * @return liste de tous les Lab, vide si aucun + * @return liste des laboratoires */ public List getAllLabs() { List labs = new ArrayList<>(); @@ -193,9 +189,9 @@ public List getAllLabs() { } /** - * Supprime un laboratoire. Les VMs associées deviennent orphelines (id_labo → NULL). + * Supprime un laboratoire sans supprimer les VM physiques. * - * @param lab le laboratoire à supprimer + * @param lab laboratoire à supprimer */ public void deleteLab(Lab lab) { String sql = "DELETE FROM labo WHERE id_labo=?"; @@ -208,37 +204,35 @@ public void deleteLab(Lab lab) { } } - /** Construit un objet Lab depuis un ResultSet. */ private Lab mapLab(ResultSet rs) throws SQLException { Lab lab = new Lab(); lab.setId(rs.getInt("id_labo")); - lab.setTitre(rs.getString("titre")); + lab.setTitle(rs.getString("titre")); lab.setDescription(rs.getString("description")); - lab.setCategorie(rs.getString("categorie")); + lab.setCategory(rs.getString("categorie")); return lab; } - // ========================================================================= // MACHINES VIRTUELLES - // ========================================================================= /** - * Sauvegarde une VM. INSERT si id==0, UPDATE sinon. - * Si labId est null, la VM est orpheline (non rattachée à un labo). + * Enregistre ou met à jour une machine virtuelle. * - * @param vm la machine virtuelle à sauvegarder - * @param labId l'identifiant du labo associé, ou null pour une VM orpheline + * @param vm machine virtuelle à persister + * @param labId identifiant du laboratoire rattaché, ou {@code null} */ public void saveVirtualMachine(VirtualMachine vm, Integer labId) { + String statut = vm.getStatus() != null ? vm.getStatus().name() : VMStatus.UNKNOWN.name(); + if (vm.getId() == 0) { - String sql = "INSERT INTO vm (nom_vm, uuid, id_labo, lien_doc, chemin_vboxmanage) VALUES (?,?,?,?,?)"; + String sql = "INSERT INTO vm (nom_vm, uuid, id_labo, lien_doc, statut) VALUES (?,?,?,?,?)"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { - ps.setString(1, vm.getNomVm()); + ps.setString(1, vm.getName()); ps.setString(2, vm.getUuid()); if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER); - ps.setString(4, vm.getLienDoc()); - ps.setString(5, vm.getCheminVboxmanage()); + ps.setString(4, vm.getDocumentationUrl()); + ps.setString(5, statut); ps.executeUpdate(); ResultSet keys = ps.getGeneratedKeys(); if (keys.next()) vm.setId(keys.getInt(1)); @@ -246,14 +240,14 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) { throw new RuntimeException("Erreur saveVirtualMachine (INSERT) : " + e.getMessage(), e); } } else { - String sql = "UPDATE vm SET nom_vm=?, uuid=?, id_labo=?, lien_doc=?, chemin_vboxmanage=? WHERE id_vm=?"; + String sql = "UPDATE vm SET nom_vm=?, uuid=?, id_labo=?, lien_doc=?, statut=? WHERE id_vm=?"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { - ps.setString(1, vm.getNomVm()); + ps.setString(1, vm.getName()); ps.setString(2, vm.getUuid()); if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER); - ps.setString(4, vm.getLienDoc()); - ps.setString(5, vm.getCheminVboxmanage()); + ps.setString(4, vm.getDocumentationUrl()); + ps.setString(5, statut); ps.setInt(6, vm.getId()); ps.executeUpdate(); } catch (SQLException e) { @@ -263,10 +257,10 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) { } /** - * Retourne toutes les VMs rattachées à un laboratoire. + * Récupère les machines virtuelles rattachées à un laboratoire. * - * @param labId l'identifiant du laboratoire - * @return liste des VMs du labo + * @param labId identifiant local du laboratoire + * @return liste des machines virtuelles rattachées */ public List getVMsForLab(int labId) { List vms = new ArrayList<>(); @@ -283,9 +277,9 @@ public List getVMsForLab(int labId) { } /** - * Retourne les VMs sans laboratoire associé. + * Récupère les machines virtuelles sans laboratoire. * - * @return liste des VMs orphelines + * @return liste des machines virtuelles orphelines */ public List getOrphanVMs() { List vms = new ArrayList<>(); @@ -301,10 +295,9 @@ public List getOrphanVMs() { } /** - * Détache une VM de son laboratoire (id_labo devient NULL). - * Ne supprime pas la VM de l'application ni de VirtualBox. + * Détache une machine virtuelle de son laboratoire. * - * @param vm la VM à détacher + * @param vm machine virtuelle à détacher */ public void removeVMFromLab(VirtualMachine vm) { String sql = "UPDATE vm SET id_labo=NULL WHERE id_vm=?"; @@ -317,35 +310,39 @@ public void removeVMFromLab(VirtualMachine vm) { } } - /** Construit un objet VirtualMachine depuis un ResultSet. */ private VirtualMachine mapVM(ResultSet rs) throws SQLException { VirtualMachine vm = new VirtualMachine(); vm.setId(rs.getInt("id_vm")); - vm.setNomVm(rs.getString("nom_vm")); + vm.setName(rs.getString("nom_vm")); vm.setUuid(rs.getString("uuid")); - vm.setLienDoc(rs.getString("lien_doc")); - vm.setCheminVboxmanage(rs.getString("chemin_vboxmanage")); + vm.setDocumentationUrl(rs.getString("lien_doc")); + String statut = rs.getString("statut"); + try { + vm.setStatus(statut != null ? VMStatus.valueOf(statut) : VMStatus.UNKNOWN); + } catch (IllegalArgumentException e) { + vm.setStatus(VMStatus.UNKNOWN); + } return vm; } - // ========================================================================= // SNAPSHOTS - // ========================================================================= /** - * Enregistre un snapshot associé à une VM. + * Enregistre un snapshot pour une machine virtuelle. * - * @param snap le snapshot à persister - * @param vmId l'identifiant de la VM concernée + * @param snap snapshot à persister + * @param vmId identifiant local de la machine virtuelle */ public void saveSnapshot(Snapshot snap, int vmId) { - String sql = "INSERT INTO snapshot (id_vm, nom_snap, date_creation, description) VALUES (?,?,?,?)"; + String sql = "INSERT INTO snapshot (id_vm, uuid, nom_snap, date_creation, description, online) VALUES (?,?,?,?,?,?)"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, vmId); - ps.setString(2, snap.getNomSnap()); - ps.setString(3, snap.getDateCreation()); - ps.setString(4, snap.getDescription()); + ps.setString(2, snap.getUuid()); + ps.setString(3, snap.getName()); + ps.setString(4, snap.getCreatedAt() != null ? snap.getCreatedAt().toString() : null); + ps.setString(5, snap.getDescription()); + ps.setInt(6, snap.isOnline() ? 1 : 0); ps.executeUpdate(); ResultSet keys = ps.getGeneratedKeys(); if (keys.next()) snap.setId(keys.getInt(1)); @@ -355,10 +352,10 @@ public void saveSnapshot(Snapshot snap, int vmId) { } /** - * Retourne tous les snapshots d'une VM, du plus récent au plus ancien. + * Récupère les snapshots d'une machine virtuelle. * - * @param vmId l'identifiant de la VM - * @return liste des snapshots + * @param vmId identifiant local de la machine virtuelle + * @return liste des snapshots rattachés */ public List getSnapshotsForVM(int vmId) { List snaps = new ArrayList<>(); @@ -374,33 +371,33 @@ public List getSnapshotsForVM(int vmId) { return snaps; } - /** Construit un objet Snapshot depuis un ResultSet. */ private Snapshot mapSnapshot(ResultSet rs) throws SQLException { Snapshot s = new Snapshot(); s.setId(rs.getInt("id_snap")); - s.setNomSnap(rs.getString("nom_snap")); - s.setDateCreation(rs.getString("date_creation")); + s.setUuid(rs.getString("uuid")); + s.setName(rs.getString("nom_snap")); + String dateStr = rs.getString("date_creation"); + s.setCreatedAt(dateStr != null ? LocalDateTime.parse(dateStr) : null); s.setDescription(rs.getString("description")); + s.setOnline(rs.getInt("online") == 1); return s; } - // ========================================================================= // JOURNAL DE BORD - // ========================================================================= /** - * Ajoute une entrée dans le journal de bord d'une VM. + * Ajoute une note d'analyse pour une machine virtuelle. * - * @param entry l'entrée de journal à persister - * @param vmId l'identifiant de la VM concernée + * @param entry note d'analyse à persister + * @param vmId identifiant local de la machine virtuelle */ public void addJournalEntry(JournalEntry entry, int vmId) { String sql = "INSERT INTO journal_entry (id_vm, horodatage, contenu) VALUES (?,?,?)"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, vmId); - ps.setString(2, entry.getHorodatage()); - ps.setString(3, entry.getContenu()); + ps.setString(2, entry.getTimestamp() != null ? entry.getTimestamp().toString() : LocalDateTime.now().toString()); + ps.setString(3, entry.getContent()); ps.executeUpdate(); ResultSet keys = ps.getGeneratedKeys(); if (keys.next()) entry.setId(keys.getInt(1)); @@ -410,10 +407,10 @@ public void addJournalEntry(JournalEntry entry, int vmId) { } /** - * Retourne toutes les entrées du journal d'une VM, de la plus récente à la plus ancienne. + * Récupère les notes d'analyse d'une machine virtuelle. * - * @param vmId l'identifiant de la VM - * @return liste des entrées de journal + * @param vmId identifiant local de la machine virtuelle + * @return liste des notes d'analyse */ public List getJournalEntriesForVM(int vmId) { List entries = new ArrayList<>(); @@ -429,32 +426,31 @@ public List getJournalEntriesForVM(int vmId) { return entries; } - /** Construit un objet JournalEntry depuis un ResultSet. */ private JournalEntry mapJournalEntry(ResultSet rs) throws SQLException { JournalEntry e = new JournalEntry(); e.setId(rs.getInt("id_entry")); - e.setHorodatage(rs.getString("horodatage")); - e.setContenu(rs.getString("contenu")); + String dateStr = rs.getString("horodatage"); + e.setTimestamp(dateStr != null ? LocalDateTime.parse(dateStr) : null); + e.setContent(rs.getString("contenu")); return e; } - // ========================================================================= // AUDIT - // ========================================================================= /** - * Enregistre une entrée d'audit (appelé automatiquement après chaque action sur une VM). + * Ajoute une entrée d'audit. * - * @param entry l'entrée d'audit à persister + * @param entry entrée d'audit à persister */ public void addAuditEntry(AuditEntry entry) { - String sql = "INSERT INTO audit_log (horodatage, id_vm, action, details) VALUES (?,?,?,?)"; + String sql = "INSERT INTO audit_log (horodatage, id_vm, action, details, lab_name) VALUES (?,?,?,?,?)"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { - ps.setString(1, entry.getHorodatage()); - if (entry.getVmId() > 0) ps.setInt(2, entry.getVmId()); else ps.setNull(2, Types.INTEGER); + ps.setString(1, entry.getTimestamp() != null ? entry.getTimestamp().toString() : LocalDateTime.now().toString()); + if (entry.getVmId() != null) ps.setInt(2, entry.getVmId()); else ps.setNull(2, Types.INTEGER); ps.setString(3, entry.getAction()); ps.setString(4, entry.getDetails()); + ps.setString(5, entry.getLabName()); ps.executeUpdate(); ResultSet keys = ps.getGeneratedKeys(); if (keys.next()) entry.setId(keys.getInt(1)); @@ -464,10 +460,10 @@ public void addAuditEntry(AuditEntry entry) { } /** - * Retourne l'historique d'audit d'une VM. + * Récupère les entrées d'audit d'une machine virtuelle. * - * @param vmId l'identifiant de la VM - * @return liste des entrées d'audit, de la plus récente à la plus ancienne + * @param vmId identifiant local de la machine virtuelle + * @return liste des entrées d'audit */ public List getAuditLogsForVM(int vmId) { List logs = new ArrayList<>(); @@ -483,52 +479,44 @@ public List getAuditLogsForVM(int vmId) { return logs; } - /** Construit un objet AuditEntry depuis un ResultSet. */ private AuditEntry mapAuditEntry(ResultSet rs) throws SQLException { AuditEntry a = new AuditEntry(); a.setId(rs.getInt("id_log")); - a.setHorodatage(rs.getString("horodatage")); - a.setVmId(rs.getInt("id_vm")); + String dateStr = rs.getString("horodatage"); + a.setTimestamp(dateStr != null ? LocalDateTime.parse(dateStr) : null); + int vmIdValue = rs.getInt("id_vm"); + a.setVmId(rs.wasNull() ? null : vmIdValue); a.setAction(rs.getString("action")); a.setDetails(rs.getString("details")); + a.setLabName(rs.getString("lab_name")); return a; } - // ========================================================================= // CONFIGURATION - // ========================================================================= /** - * Charge la configuration applicative depuis la base. - * Retourne une configuration avec des valeurs par défaut si rien n'est enregistré. + * Récupère la configuration applicative. * - * @return la configuration courante + * @return configuration applicative */ public AppConfig getConfig() { AppConfig config = new AppConfig(); config.setVboxManagePath(getConfigValue("vboxManagePath", "VBoxManage")); - config.setDefaultExportDir(getConfigValue("defaultExportDir", + config.setPdfExportDirectory(getConfigValue("pdfExportDirectory", System.getProperty("user.home"))); return config; } /** - * Sauvegarde la configuration applicative. + * Enregistre la configuration applicative. * - * @param config la configuration à persister + * @param config configuration à persister */ public void saveConfig(AppConfig config) { upsertConfig("vboxManagePath", config.getVboxManagePath()); - upsertConfig("defaultExportDir", config.getDefaultExportDir()); + upsertConfig("pdfExportDirectory", config.getPdfExportDirectory()); } - /** - * Lit une valeur de configuration par sa clé. - * - * @param key la clé de configuration - * @param defaultValue valeur retournée si la clé est absente - * @return la valeur stockée ou la valeur par défaut - */ private String getConfigValue(String key, String defaultValue) { String sql = "SELECT valeur FROM app_config WHERE cle=?"; try (Connection conn = getConnection(); @@ -542,12 +530,6 @@ private String getConfigValue(String key, String defaultValue) { return defaultValue; } - /** - * Insère ou met à jour une valeur de configuration (UPSERT). - * - * @param key la clé - * @param value la valeur - */ private void upsertConfig(String key, String value) { String sql = "INSERT OR REPLACE INTO app_config (cle, valeur) VALUES (?,?)"; try (Connection conn = getConnection(); diff --git a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java index 18490ef..7a6679f 100644 --- a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java +++ b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java @@ -1,6 +1,9 @@ package tg.cyberlabmanager.pdf; -import tg.cyberlabmanager.model.*; +import tg.cyberlabmanager.model.JournalEntry; +import tg.cyberlabmanager.model.Lab; +import tg.cyberlabmanager.model.Snapshot; +import tg.cyberlabmanager.model.VirtualMachine; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; @@ -22,31 +25,25 @@ */ public class PdfExporter { - // ── Mise en page ────────────────────────────────────────────────────────── private static final float MARGIN = 50f; - private static final float PAGE_HEIGHT = PDRectangle.A4.getHeight(); // ~842 - private static final float PAGE_WIDTH = PDRectangle.A4.getWidth(); // ~595 + private static final float PAGE_HEIGHT = PDRectangle.A4.getHeight(); + private static final float PAGE_WIDTH = PDRectangle.A4.getWidth(); private static final float CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN; - // ── Tailles de police ───────────────────────────────────────────────────── private static final float FONT_TITLE = 18f; private static final float FONT_HEADING = 13f; private static final float FONT_SUBHEAD = 11f; private static final float FONT_BODY = 10f; - // ── Polices PDFBox 3.x ──────────────────────────────────────────────────── private static final PDType1Font FONT_BOLD = new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD); private static final PDType1Font FONT_REGULAR = new PDType1Font(Standard14Fonts.FontName.HELVETICA); private static final PDType1Font FONT_OBLIQUE = new PDType1Font(Standard14Fonts.FontName.HELVETICA_OBLIQUE); - // ── État interne de pagination ───────────────────────────────────────────── + private static final DateTimeFormatter DISPLAY_FORMAT = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"); + private PDDocument document; private PDPageContentStream content; - private float cursorY; // position verticale courante (descend vers 0) - - // ========================================================================= - // Méthode publique principale - // ========================================================================= + private float cursorY; /** * Génère et sauvegarde le rapport PDF d'un laboratoire. @@ -65,12 +62,12 @@ public void exportLabToPdf(Lab lab, String filePath) throws IOException { document = new PDDocument(); - newPage(); // crée la première page et initialise cursorY + newPage(); - // ── En-tête du rapport ──────────────────────────────────────────────── - writeLine("Rapport – " + lab.getTitre(), FONT_BOLD, FONT_TITLE, true); - writeLine("Catégorie : " + nvl(lab.getCategorie(), "Non définie"), FONT_REGULAR, FONT_BODY, false); - writeLine("Généré le : " + now(), FONT_REGULAR, FONT_BODY, false); + // En-tête + writeLine("Rapport – " + nvl(lab.getTitle(), "Sans titre"), FONT_BOLD, FONT_TITLE, true); + writeLine("Catégorie : " + nvl(lab.getCategory(), "Non définie"), FONT_REGULAR, FONT_BODY, false); + writeLine("Généré le : " + now(), FONT_REGULAR, FONT_BODY, false); if (lab.getDescription() != null && !lab.getDescription().isBlank()) { writeLine("Description : " + lab.getDescription(), FONT_OBLIQUE, FONT_BODY, false); } @@ -78,7 +75,7 @@ public void exportLabToPdf(Lab lab, separator(); spacer(8f); - // ── Section 1 : Machines virtuelles ─────────────────────────────────── + // Section 1 : Machines virtuelles writeLine("1. Machines virtuelles (" + vms.size() + ")", FONT_BOLD, FONT_HEADING, false); spacer(6f); @@ -87,11 +84,12 @@ public void exportLabToPdf(Lab lab, } else { for (VirtualMachine vm : vms) { checkPageBreak(60f); - writeLine(" ▸ " + vm.getNomVm(), FONT_BOLD, FONT_SUBHEAD, false); + writeLine(" ▸ " + nvl(vm.getName(), "Sans nom"), FONT_BOLD, FONT_SUBHEAD, false); writeLine(" UUID : " + nvl(vm.getUuid(), "—"), FONT_REGULAR, FONT_BODY, false); - writeLine(" Statut : " + nvl(vm.getStatusLabel(), "Inconnu"), FONT_REGULAR, FONT_BODY, false); - if (vm.getLienDoc() != null && !vm.getLienDoc().isBlank()) { - writeLine(" Doc : " + vm.getLienDoc(), FONT_REGULAR, FONT_BODY, false); + writeLine(" Statut : " + (vm.getStatus() != null ? vm.getStatus().name() : "UNKNOWN"), + FONT_REGULAR, FONT_BODY, false); + if (vm.getDocumentationUrl() != null && !vm.getDocumentationUrl().isBlank()) { + writeLine(" Doc : " + vm.getDocumentationUrl(), FONT_REGULAR, FONT_BODY, false); } spacer(5f); } @@ -101,7 +99,7 @@ public void exportLabToPdf(Lab lab, separator(); spacer(8f); - // ── Section 2 : Snapshots ───────────────────────────────────────────── + // Section 2 : Snapshots writeLine("2. Snapshots (" + snapshots.size() + ")", FONT_BOLD, FONT_HEADING, false); spacer(6f); @@ -110,8 +108,10 @@ public void exportLabToPdf(Lab lab, } else { for (Snapshot snap : snapshots) { checkPageBreak(40f); - writeLine(" ▸ " + nvl(snap.getNomSnap(), "Sans nom"), FONT_BOLD, FONT_SUBHEAD, false); - writeLine(" Date : " + nvl(snap.getDateCreation(), "—"), FONT_REGULAR, FONT_BODY, false); + writeLine(" ▸ " + nvl(snap.getName(), "Sans nom"), FONT_BOLD, FONT_SUBHEAD, false); + writeLine(" Date : " + formatDate(snap.getCreatedAt()), FONT_REGULAR, FONT_BODY, false); + writeLine(" Type : " + (snap.isOnline() ? "À chaud (VM allumée)" : "À froid (VM éteinte)"), + FONT_REGULAR, FONT_BODY, false); if (snap.getDescription() != null && !snap.getDescription().isBlank()) { writeLine(" Note : " + snap.getDescription(), FONT_OBLIQUE, FONT_BODY, false); } @@ -123,7 +123,7 @@ public void exportLabToPdf(Lab lab, separator(); spacer(8f); - // ── Section 3 : Journal de bord ─────────────────────────────────────── + // Section 3 : Journal de bord writeLine("3. Journal de bord (" + journals.size() + " entrée(s))", FONT_BOLD, FONT_HEADING, false); spacer(6f); @@ -132,14 +132,12 @@ public void exportLabToPdf(Lab lab, } else { for (JournalEntry entry : journals) { checkPageBreak(40f); - writeLine(" [" + nvl(entry.getHorodatage(), "—") + "]", FONT_BOLD, FONT_BODY, false); - // Le contenu peut être long : on le découpe en lignes - writeWrappedText(" " + nvl(entry.getContenu(), ""), FONT_REGULAR, FONT_BODY); + writeLine(" [" + formatDate(entry.getTimestamp()) + "]", FONT_BOLD, FONT_BODY, false); + writeWrappedText(" " + nvl(entry.getContent(), ""), FONT_REGULAR, FONT_BODY); spacer(5f); } } - // ── Pied de page de la dernière page ────────────────────────────────── writeFooter(); content.close(); @@ -147,13 +145,10 @@ public void exportLabToPdf(Lab lab, document.close(); } - // ========================================================================= + // Gestion des pages - // ========================================================================= - /** - * Crée une nouvelle page A4 et réinitialise le curseur en haut. - */ + private void newPage() throws IOException { if (content != null) { content.close(); @@ -164,30 +159,14 @@ private void newPage() throws IOException { cursorY = PAGE_HEIGHT - MARGIN; } - /** - * Si l'espace restant est insuffisant pour écrire {@code needed} points, - * crée une nouvelle page. - * - * @param needed espace vertical nécessaire en points - */ private void checkPageBreak(float needed) throws IOException { if (cursorY - needed < MARGIN + 20f) { newPage(); } } - // ========================================================================= // Écriture de contenu - // ========================================================================= - /** - * Écrit une ligne de texte simple et fait descendre le curseur. - * - * @param text le texte à écrire - * @param font la police PDFBox - * @param size la taille en points - * @param underline true pour ajouter une ligne sous le texte (simulée par un trait) - */ private void writeLine(String text, PDType1Font font, float size, boolean underline) throws IOException { checkPageBreak(size + 6f); @@ -208,17 +187,8 @@ private void writeLine(String text, PDType1Font font, float size, boolean underl } } - /** - * Écrit un texte long en le découpant sur plusieurs lignes si nécessaire. - * - * @param text le texte (potentiellement long) - * @param font la police - * @param size la taille - */ private void writeWrappedText(String text, PDType1Font font, float size) throws IOException { - // Largeur max en points, convertie en "unités police" float maxWidth = CONTENT_WIDTH; - String[] words = sanitize(text).split(" "); StringBuilder line = new StringBuilder(); @@ -237,12 +207,10 @@ private void writeWrappedText(String text, PDType1Font font, float size) throws } } - /** Ajoute un espace vertical. */ private void spacer(float points) { cursorY -= points; } - /** Dessine un trait de séparation horizontal. */ private void separator() throws IOException { content.setLineWidth(0.5f); content.moveTo(MARGIN, cursorY); @@ -251,7 +219,6 @@ private void separator() throws IOException { cursorY -= 2f; } - /** Écrit un pied de page discret en bas de la page courante. */ private void writeFooter() throws IOException { float footerY = MARGIN - 10f; content.beginText(); @@ -261,18 +228,19 @@ private void writeFooter() throws IOException { content.endText(); } - // ========================================================================= + // Utilitaires - // ========================================================================= - /** Retourne la valeur ou le fallback si null/blank. */ private String nvl(String value, String fallback) { return (value != null && !value.isBlank()) ? value : fallback; } - /** Date/heure courante formatée pour affichage. */ private String now() { - return LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); + return LocalDateTime.now().format(DISPLAY_FORMAT); + } + + private String formatDate(LocalDateTime dt) { + return dt != null ? dt.format(DISPLAY_FORMAT) : "—"; } /** From 12d8a64639f0a5973a8166813f673ac003e0f6ca Mon Sep 17 00:00:00 2001 From: akpmarcelin Date: Sat, 4 Jul 2026 07:21:50 +0000 Subject: [PATCH 14/14] feat(data): implement DatabaseManager with SQLite - 30 tests passing --- .../cyberlabmanager/data/DatabaseManager.java | 117 ++-- .../data/DatabaseManagerTest.java | 510 ++++++++++++++++++ 2 files changed, 571 insertions(+), 56 deletions(-) create mode 100644 src/test/java/tg/cyberlabmanager/data/DatabaseManagerTest.java diff --git a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java index ee485b0..ae19aed 100644 --- a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java +++ b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java @@ -16,42 +16,58 @@ /** * Service d'accès aux données de l'application. * - * Il masque la persistance SQLite derrière des opérations de haut niveau sur - * les entités métier. + *

Maintient une connexion SQLite unique ouverte pendant toute la durée de + * vie de l'instance. Compatible avec SQLite en mémoire (:memory:) pour les + * tests.

*/ public class DatabaseManager { - private final String jdbcUrl; - - // Constructeur + initialisation + private final Connection connection; + + // ========================================================================= + // Constructeurs + // ========================================================================= /** - * Crée un gestionnaire de base de données utilisant le fichier SQLite - * par défaut situé dans le répertoire utilisateur. + * Crée un gestionnaire utilisant le fichier cyberlab.db dans le répertoire + * utilisateur. */ public DatabaseManager() { this("jdbc:sqlite:" + System.getProperty("user.home") + "/cyberlab.db"); } /** - * Crée un gestionnaire de base de données avec une URL JDBC explicite. - * Utile pour les tests (ex: "jdbc:sqlite::memory:"). + * Crée un gestionnaire avec une URL JDBC explicite. + * Utiliser "jdbc:sqlite::memory:" pour les tests unitaires. * * @param jdbcUrl URL JDBC SQLite */ public DatabaseManager(String jdbcUrl) { - this.jdbcUrl = jdbcUrl; + try { + Class.forName("org.sqlite.JDBC"); + this.connection = DriverManager.getConnection(jdbcUrl); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Driver SQLite introuvable", e); + } catch (SQLException e) { + throw new RuntimeException("Impossible d'ouvrir la base de données : " + e.getMessage(), e); + } initDatabase(); } - private Connection getConnection() throws SQLException { - return DriverManager.getConnection(jdbcUrl); + private Connection getConnection() { + return this.connection; } /** * Crée toutes les tables si elles n'existent pas encore. */ private void initDatabase() { + // Activer les clés étrangères SQLite (désactivées par défaut) + try (Statement stmt = getConnection().createStatement()) { + stmt.execute("PRAGMA foreign_keys = ON"); + } catch (SQLException e) { + throw new RuntimeException("Impossible d'activer les clés étrangères : " + e.getMessage(), e); + } String[] tables = { "CREATE TABLE IF NOT EXISTS labo (" + " id_labo INTEGER PRIMARY KEY AUTOINCREMENT," + @@ -101,8 +117,7 @@ private void initDatabase() { ");" }; - try (Connection conn = getConnection(); - Statement stmt = conn.createStatement()) { + try (Statement stmt = getConnection().createStatement()) { for (String sql : tables) { stmt.execute(sql); } @@ -111,7 +126,9 @@ private void initDatabase() { } } + // ========================================================================= // LABORATOIRES + // ========================================================================= /** * Enregistre ou met à jour un laboratoire. @@ -121,8 +138,7 @@ private void initDatabase() { public void saveLab(Lab lab) { if (lab.getId() == 0) { String sql = "INSERT INTO labo (titre, description, categorie) VALUES (?, ?, ?)"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, lab.getTitle()); ps.setString(2, lab.getDescription()); ps.setString(3, lab.getCategory()); @@ -134,8 +150,7 @@ public void saveLab(Lab lab) { } } else { String sql = "UPDATE labo SET titre=?, description=?, categorie=? WHERE id_labo=?"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setString(1, lab.getTitle()); ps.setString(2, lab.getDescription()); ps.setString(3, lab.getCategory()); @@ -155,13 +170,10 @@ public void saveLab(Lab lab) { */ public Lab getLab(int id) { String sql = "SELECT id_labo, titre, description, categorie FROM labo WHERE id_labo=?"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setInt(1, id); ResultSet rs = ps.executeQuery(); - if (rs.next()) { - return mapLab(rs); - } + if (rs.next()) return mapLab(rs); } catch (SQLException e) { throw new RuntimeException("Erreur getLab : " + e.getMessage(), e); } @@ -176,12 +188,9 @@ public Lab getLab(int id) { public List getAllLabs() { List labs = new ArrayList<>(); String sql = "SELECT id_labo, titre, description, categorie FROM labo ORDER BY titre"; - try (Connection conn = getConnection(); - Statement stmt = conn.createStatement(); + try (Statement stmt = getConnection().createStatement(); ResultSet rs = stmt.executeQuery(sql)) { - while (rs.next()) { - labs.add(mapLab(rs)); - } + while (rs.next()) labs.add(mapLab(rs)); } catch (SQLException e) { throw new RuntimeException("Erreur getAllLabs : " + e.getMessage(), e); } @@ -195,8 +204,7 @@ public List getAllLabs() { */ public void deleteLab(Lab lab) { String sql = "DELETE FROM labo WHERE id_labo=?"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setInt(1, lab.getId()); ps.executeUpdate(); } catch (SQLException e) { @@ -213,7 +221,9 @@ private Lab mapLab(ResultSet rs) throws SQLException { return lab; } + // ========================================================================= // MACHINES VIRTUELLES + // ========================================================================= /** * Enregistre ou met à jour une machine virtuelle. @@ -226,8 +236,7 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) { if (vm.getId() == 0) { String sql = "INSERT INTO vm (nom_vm, uuid, id_labo, lien_doc, statut) VALUES (?,?,?,?,?)"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, vm.getName()); ps.setString(2, vm.getUuid()); if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER); @@ -241,8 +250,7 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) { } } else { String sql = "UPDATE vm SET nom_vm=?, uuid=?, id_labo=?, lien_doc=?, statut=? WHERE id_vm=?"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setString(1, vm.getName()); ps.setString(2, vm.getUuid()); if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER); @@ -265,8 +273,7 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) { public List getVMsForLab(int labId) { List vms = new ArrayList<>(); String sql = "SELECT * FROM vm WHERE id_labo=?"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setInt(1, labId); ResultSet rs = ps.executeQuery(); while (rs.next()) vms.add(mapVM(rs)); @@ -284,8 +291,7 @@ public List getVMsForLab(int labId) { public List getOrphanVMs() { List vms = new ArrayList<>(); String sql = "SELECT * FROM vm WHERE id_labo IS NULL"; - try (Connection conn = getConnection(); - Statement stmt = conn.createStatement(); + try (Statement stmt = getConnection().createStatement(); ResultSet rs = stmt.executeQuery(sql)) { while (rs.next()) vms.add(mapVM(rs)); } catch (SQLException e) { @@ -301,8 +307,7 @@ public List getOrphanVMs() { */ public void removeVMFromLab(VirtualMachine vm) { String sql = "UPDATE vm SET id_labo=NULL WHERE id_vm=?"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setInt(1, vm.getId()); ps.executeUpdate(); } catch (SQLException e) { @@ -325,7 +330,9 @@ private VirtualMachine mapVM(ResultSet rs) throws SQLException { return vm; } + // ========================================================================= // SNAPSHOTS + // ========================================================================= /** * Enregistre un snapshot pour une machine virtuelle. @@ -335,8 +342,7 @@ private VirtualMachine mapVM(ResultSet rs) throws SQLException { */ public void saveSnapshot(Snapshot snap, int vmId) { String sql = "INSERT INTO snapshot (id_vm, uuid, nom_snap, date_creation, description, online) VALUES (?,?,?,?,?,?)"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, vmId); ps.setString(2, snap.getUuid()); ps.setString(3, snap.getName()); @@ -360,8 +366,7 @@ public void saveSnapshot(Snapshot snap, int vmId) { public List getSnapshotsForVM(int vmId) { List snaps = new ArrayList<>(); String sql = "SELECT * FROM snapshot WHERE id_vm=? ORDER BY date_creation DESC"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setInt(1, vmId); ResultSet rs = ps.executeQuery(); while (rs.next()) snaps.add(mapSnapshot(rs)); @@ -383,7 +388,9 @@ private Snapshot mapSnapshot(ResultSet rs) throws SQLException { return s; } + // ========================================================================= // JOURNAL DE BORD + // ========================================================================= /** * Ajoute une note d'analyse pour une machine virtuelle. @@ -393,8 +400,7 @@ private Snapshot mapSnapshot(ResultSet rs) throws SQLException { */ public void addJournalEntry(JournalEntry entry, int vmId) { String sql = "INSERT INTO journal_entry (id_vm, horodatage, contenu) VALUES (?,?,?)"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, vmId); ps.setString(2, entry.getTimestamp() != null ? entry.getTimestamp().toString() : LocalDateTime.now().toString()); ps.setString(3, entry.getContent()); @@ -415,8 +421,7 @@ public void addJournalEntry(JournalEntry entry, int vmId) { public List getJournalEntriesForVM(int vmId) { List entries = new ArrayList<>(); String sql = "SELECT * FROM journal_entry WHERE id_vm=? ORDER BY horodatage DESC"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setInt(1, vmId); ResultSet rs = ps.executeQuery(); while (rs.next()) entries.add(mapJournalEntry(rs)); @@ -435,7 +440,9 @@ private JournalEntry mapJournalEntry(ResultSet rs) throws SQLException { return e; } + // ========================================================================= // AUDIT + // ========================================================================= /** * Ajoute une entrée d'audit. @@ -444,8 +451,7 @@ private JournalEntry mapJournalEntry(ResultSet rs) throws SQLException { */ public void addAuditEntry(AuditEntry entry) { String sql = "INSERT INTO audit_log (horodatage, id_vm, action, details, lab_name) VALUES (?,?,?,?,?)"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, entry.getTimestamp() != null ? entry.getTimestamp().toString() : LocalDateTime.now().toString()); if (entry.getVmId() != null) ps.setInt(2, entry.getVmId()); else ps.setNull(2, Types.INTEGER); ps.setString(3, entry.getAction()); @@ -468,8 +474,7 @@ public void addAuditEntry(AuditEntry entry) { public List getAuditLogsForVM(int vmId) { List logs = new ArrayList<>(); String sql = "SELECT * FROM audit_log WHERE id_vm=? ORDER BY horodatage DESC"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setInt(1, vmId); ResultSet rs = ps.executeQuery(); while (rs.next()) logs.add(mapAuditEntry(rs)); @@ -492,7 +497,9 @@ private AuditEntry mapAuditEntry(ResultSet rs) throws SQLException { return a; } + // ========================================================================= // CONFIGURATION + // ========================================================================= /** * Récupère la configuration applicative. @@ -519,8 +526,7 @@ public void saveConfig(AppConfig config) { private String getConfigValue(String key, String defaultValue) { String sql = "SELECT valeur FROM app_config WHERE cle=?"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setString(1, key); ResultSet rs = ps.executeQuery(); if (rs.next()) return rs.getString("valeur"); @@ -532,8 +538,7 @@ private String getConfigValue(String key, String defaultValue) { private void upsertConfig(String key, String value) { String sql = "INSERT OR REPLACE INTO app_config (cle, valeur) VALUES (?,?)"; - try (Connection conn = getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { + try (PreparedStatement ps = getConnection().prepareStatement(sql)) { ps.setString(1, key); ps.setString(2, value); ps.executeUpdate(); diff --git a/src/test/java/tg/cyberlabmanager/data/DatabaseManagerTest.java b/src/test/java/tg/cyberlabmanager/data/DatabaseManagerTest.java new file mode 100644 index 0000000..9c34f1d --- /dev/null +++ b/src/test/java/tg/cyberlabmanager/data/DatabaseManagerTest.java @@ -0,0 +1,510 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package tg.cyberlabmanager.data; + +/** + * + * @author agokoli + */ +import tg.cyberlabmanager.model.*; + +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * Tests unitaires de DatabaseManager. + * Utilise SQLite en mémoire (:memory:) — aucun fichier créé sur le disque. + * Chaque test repart d'une base vide grâce au @BeforeEach. + */ +class DatabaseManagerTest { + + private DatabaseManager db; + + @BeforeEach + void setUp() { + // Base en mémoire : rapide, isolée, détruite après chaque test + db = new DatabaseManager("jdbc:sqlite::memory:"); + } + + // ========================================================================= + // LABORATOIRES + // ========================================================================= + + @Test + @DisplayName("saveLab : un nouveau labo reçoit un id généré") + void testSaveLabInsert() { + Lab lab = new Lab(); + lab.setTitle("Labo Malware"); + lab.setDescription("Analyse de malware"); + lab.setCategory("malware"); + + db.saveLab(lab); + + assertNotEquals(0, lab.getId(), "L'id doit être assigné après l'INSERT"); + } + + @Test + @DisplayName("getLab : retrouve un labo par son id") + void testGetLab() { + Lab lab = new Lab(); + lab.setTitle("Labo Pentest"); + lab.setCategory("pentest"); + db.saveLab(lab); + + Lab retrieved = db.getLab(lab.getId()); + + assertNotNull(retrieved); + assertEquals("Labo Pentest", retrieved.getTitle()); + assertEquals("pentest", retrieved.getCategory()); + } + + @Test + @DisplayName("getLab : retourne null si l'id n'existe pas") + void testGetLabNotFound() { + Lab result = db.getLab(9999); + assertNull(result); + } + + @Test + @DisplayName("saveLab : met à jour un labo existant") + void testSaveLabUpdate() { + Lab lab = new Lab(); + lab.setTitle("Ancien titre"); + lab.setCategory("reseau"); + db.saveLab(lab); + + lab.setTitle("Nouveau titre"); + db.saveLab(lab); // doit faire un UPDATE + + Lab updated = db.getLab(lab.getId()); + assertEquals("Nouveau titre", updated.getTitle()); + } + + @Test + @DisplayName("getAllLabs : retourne tous les labos") + void testGetAllLabs() { + Lab lab1 = new Lab(); lab1.setTitle("Labo A"); lab1.setCategory("malware"); + Lab lab2 = new Lab(); lab2.setTitle("Labo B"); lab2.setCategory("pentest"); + Lab lab3 = new Lab(); lab3.setTitle("Labo C"); lab3.setCategory("reseau"); + + db.saveLab(lab1); + db.saveLab(lab2); + db.saveLab(lab3); + + List labs = db.getAllLabs(); + assertEquals(3, labs.size()); + } + + @Test + @DisplayName("getAllLabs : retourne une liste vide si aucun labo") + void testGetAllLabsEmpty() { + List labs = db.getAllLabs(); + assertNotNull(labs); + assertTrue(labs.isEmpty()); + } + + @Test + @DisplayName("deleteLab : supprime un labo existant") + void testDeleteLab() { + Lab lab = new Lab(); + lab.setTitle("Labo à supprimer"); + db.saveLab(lab); + + db.deleteLab(lab); + + assertNull(db.getLab(lab.getId())); + assertEquals(0, db.getAllLabs().size()); + } + + @Test + @DisplayName("deleteLab : les VMs rattachées deviennent orphelines") + void testDeleteLabOrphansVMs() { + Lab lab = new Lab(); + lab.setTitle("Labo avec VMs"); + db.saveLab(lab); + + VirtualMachine vm = new VirtualMachine(); + vm.setName("Kali"); + vm.setUuid("uuid-kali"); + db.saveVirtualMachine(vm, lab.getId()); + + db.deleteLab(lab); // les VMs doivent passer en orphelines + + List orphans = db.getOrphanVMs(); + assertEquals(1, orphans.size()); + assertEquals("Kali", orphans.get(0).getName()); + } + + // ========================================================================= + // MACHINES VIRTUELLES + // ========================================================================= + + @Test + @DisplayName("saveVirtualMachine : une nouvelle VM reçoit un id généré") + void testSaveVMInsert() { + Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab); + + VirtualMachine vm = new VirtualMachine(); + vm.setName("Ubuntu-22"); + vm.setUuid("uuid-ubuntu"); + vm.setStatus(VMStatus.POWERED_OFF); + + db.saveVirtualMachine(vm, lab.getId()); + + assertNotEquals(0, vm.getId(), "L'id doit être assigné après l'INSERT"); + } + + @Test + @DisplayName("saveVirtualMachine : labId null crée une VM orpheline") + void testSaveVMOrpheline() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM-Orpheline"); + vm.setUuid("uuid-orphan"); + + db.saveVirtualMachine(vm, null); + + List orphans = db.getOrphanVMs(); + assertEquals(1, orphans.size()); + assertEquals("VM-Orpheline", orphans.get(0).getName()); + } + + @Test + @DisplayName("getVMsForLab : retourne les VMs d'un labo") + void testGetVMsForLab() { + Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab); + + VirtualMachine vm1 = new VirtualMachine(); vm1.setName("VM1"); vm1.setUuid("uuid-1"); + VirtualMachine vm2 = new VirtualMachine(); vm2.setName("VM2"); vm2.setUuid("uuid-2"); + + db.saveVirtualMachine(vm1, lab.getId()); + db.saveVirtualMachine(vm2, lab.getId()); + + List vms = db.getVMsForLab(lab.getId()); + assertEquals(2, vms.size()); + } + + @Test + @DisplayName("getVMsForLab : ne retourne pas les VMs d'un autre labo") + void testGetVMsForLabIsolation() { + Lab lab1 = new Lab(); lab1.setTitle("Labo 1"); db.saveLab(lab1); + Lab lab2 = new Lab(); lab2.setTitle("Labo 2"); db.saveLab(lab2); + + VirtualMachine vm = new VirtualMachine(); vm.setName("VM du labo 1"); vm.setUuid("uuid-x"); + db.saveVirtualMachine(vm, lab1.getId()); + + List vmsLab2 = db.getVMsForLab(lab2.getId()); + assertTrue(vmsLab2.isEmpty()); + } + + @Test + @DisplayName("getOrphanVMs : retourne uniquement les VMs sans labo") + void testGetOrphanVMs() { + Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab); + + VirtualMachine vmRattachee = new VirtualMachine(); + vmRattachee.setName("VM rattachée"); vmRattachee.setUuid("uuid-r"); + db.saveVirtualMachine(vmRattachee, lab.getId()); + + VirtualMachine vmOrpheline = new VirtualMachine(); + vmOrpheline.setName("VM orpheline"); vmOrpheline.setUuid("uuid-o"); + db.saveVirtualMachine(vmOrpheline, null); + + List orphans = db.getOrphanVMs(); + assertEquals(1, orphans.size()); + assertEquals("VM orpheline", orphans.get(0).getName()); + } + + @Test + @DisplayName("removeVMFromLab : détache une VM sans la supprimer") + void testRemoveVMFromLab() { + Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab); + + VirtualMachine vm = new VirtualMachine(); + vm.setName("Kali"); vm.setUuid("uuid-kali"); + db.saveVirtualMachine(vm, lab.getId()); + + db.removeVMFromLab(vm); + + // La VM ne doit plus être dans le labo + assertTrue(db.getVMsForLab(lab.getId()).isEmpty()); + // Mais elle doit être orpheline + assertEquals(1, db.getOrphanVMs().size()); + } + + @Test + @DisplayName("saveVirtualMachine : le statut est bien persisté") + void testSaveVMStatus() { + Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab); + + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM Running"); vm.setUuid("uuid-run"); + vm.setStatus(VMStatus.RUNNING); + db.saveVirtualMachine(vm, lab.getId()); + + List vms = db.getVMsForLab(lab.getId()); + assertEquals(VMStatus.RUNNING, vms.get(0).getStatus()); + } + + @Test + @DisplayName("saveVirtualMachine : documentationUrl est bien persistée") + void testSaveVMDocUrl() { + Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab); + + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM Doc"); vm.setUuid("uuid-doc"); + vm.setDocumentationUrl("https://doc.exemple.com"); + db.saveVirtualMachine(vm, lab.getId()); + + List vms = db.getVMsForLab(lab.getId()); + assertEquals("https://doc.exemple.com", vms.get(0).getDocumentationUrl()); + } + + // ========================================================================= + // SNAPSHOTS + // ========================================================================= + + @Test + @DisplayName("saveSnapshot : un nouveau snapshot reçoit un id généré") + void testSaveSnapshot() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-snap"); + db.saveVirtualMachine(vm, null); + + Snapshot snap = new Snapshot(); + snap.setName("Snapshot initial"); + snap.setDescription("Etat propre"); + snap.setCreatedAt(LocalDateTime.of(2026, 6, 1, 10, 0)); + snap.setOnline(false); + + db.saveSnapshot(snap, vm.getId()); + + assertNotEquals(0, snap.getId()); + } + + @Test + @DisplayName("getSnapshotsForVM : retourne les snapshots dans l'ordre décroissant") + void testGetSnapshotsForVM() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-snaps"); + db.saveVirtualMachine(vm, null); + + Snapshot s1 = new Snapshot(); s1.setName("Snap 1"); + s1.setCreatedAt(LocalDateTime.of(2026, 1, 1, 0, 0)); + Snapshot s2 = new Snapshot(); s2.setName("Snap 2"); + s2.setCreatedAt(LocalDateTime.of(2026, 6, 1, 0, 0)); + + db.saveSnapshot(s1, vm.getId()); + db.saveSnapshot(s2, vm.getId()); + + List snaps = db.getSnapshotsForVM(vm.getId()); + assertEquals(2, snaps.size()); + // Le plus récent en premier + assertEquals("Snap 2", snaps.get(0).getName()); + } + + @Test + @DisplayName("getSnapshotsForVM : retourne liste vide si aucun snapshot") + void testGetSnapshotsEmpty() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-no-snap"); + db.saveVirtualMachine(vm, null); + + assertTrue(db.getSnapshotsForVM(vm.getId()).isEmpty()); + } + + @Test + @DisplayName("saveSnapshot : le champ online est bien persisté") + void testSnapshotOnline() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-online"); + db.saveVirtualMachine(vm, null); + + Snapshot snap = new Snapshot(); + snap.setName("Snap chaud"); + snap.setCreatedAt(LocalDateTime.now()); + snap.setOnline(true); + db.saveSnapshot(snap, vm.getId()); + + List snaps = db.getSnapshotsForVM(vm.getId()); + assertTrue(snaps.get(0).isOnline()); + } + + // ========================================================================= + // JOURNAL DE BORD + // ========================================================================= + + @Test + @DisplayName("addJournalEntry : une nouvelle entrée reçoit un id généré") + void testAddJournalEntry() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-journal"); + db.saveVirtualMachine(vm, null); + + JournalEntry entry = new JournalEntry(); + entry.setTimestamp(LocalDateTime.now()); + entry.setContent("Analyse en cours — comportement suspect détecté"); + + db.addJournalEntry(entry, vm.getId()); + + assertNotEquals(0, entry.getId()); + } + + @Test + @DisplayName("getJournalEntriesForVM : retourne les entrées de la plus récente à la plus ancienne") + void testGetJournalEntries() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-j2"); + db.saveVirtualMachine(vm, null); + + JournalEntry e1 = new JournalEntry(); + e1.setTimestamp(LocalDateTime.of(2026, 1, 1, 8, 0)); + e1.setContent("Première note"); + + JournalEntry e2 = new JournalEntry(); + e2.setTimestamp(LocalDateTime.of(2026, 6, 1, 9, 0)); + e2.setContent("Note récente"); + + db.addJournalEntry(e1, vm.getId()); + db.addJournalEntry(e2, vm.getId()); + + List entries = db.getJournalEntriesForVM(vm.getId()); + assertEquals(2, entries.size()); + assertEquals("Note récente", entries.get(0).getContent()); + } + + @Test + @DisplayName("getJournalEntriesForVM : retourne liste vide si aucune note") + void testGetJournalEntriesEmpty() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-j-empty"); + db.saveVirtualMachine(vm, null); + + assertTrue(db.getJournalEntriesForVM(vm.getId()).isEmpty()); + } + + // ========================================================================= + // AUDIT + // ========================================================================= + + @Test + @DisplayName("addAuditEntry : une entrée d'audit reçoit un id généré") + void testAddAuditEntry() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-audit"); + db.saveVirtualMachine(vm, null); + + AuditEntry entry = new AuditEntry(); + entry.setTimestamp(LocalDateTime.now()); + entry.setVmId(vm.getId()); + entry.setAction("START"); + entry.setDetails("Démarrage manuel"); + + db.addAuditEntry(entry); + + assertNotEquals(0, entry.getId()); + } + + @Test + @DisplayName("getAuditLogsForVM : retourne les logs d'une VM") + void testGetAuditLogs() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-audit2"); + db.saveVirtualMachine(vm, null); + + AuditEntry e1 = new AuditEntry(); + e1.setTimestamp(LocalDateTime.now()); + e1.setVmId(vm.getId()); + e1.setAction("START"); + + AuditEntry e2 = new AuditEntry(); + e2.setTimestamp(LocalDateTime.now()); + e2.setVmId(vm.getId()); + e2.setAction("STOP"); + + db.addAuditEntry(e1); + db.addAuditEntry(e2); + + List logs = db.getAuditLogsForVM(vm.getId()); + assertEquals(2, logs.size()); + } + + @Test + @DisplayName("addAuditEntry : vmId null est accepté") + void testAddAuditEntryNullVmId() { + AuditEntry entry = new AuditEntry(); + entry.setTimestamp(LocalDateTime.now()); + entry.setVmId(null); // pas de VM liée + entry.setAction("APP_START"); + entry.setDetails("Démarrage de l'application"); + + assertDoesNotThrow(() -> db.addAuditEntry(entry)); + assertNotEquals(0, entry.getId()); + } + + @Test + @DisplayName("addAuditEntry : labName est bien persisté") + void testAuditEntryLabName() { + VirtualMachine vm = new VirtualMachine(); + vm.setName("VM"); vm.setUuid("uuid-labname"); + db.saveVirtualMachine(vm, null); + + AuditEntry entry = new AuditEntry(); + entry.setTimestamp(LocalDateTime.now()); + entry.setVmId(vm.getId()); + entry.setAction("SNAPSHOT_TAKEN"); + entry.setLabName("Labo Malware"); + + db.addAuditEntry(entry); + + List logs = db.getAuditLogsForVM(vm.getId()); + assertEquals("Labo Malware", logs.get(0).getLabName()); + } + + // ========================================================================= + // CONFIGURATION + // ========================================================================= + + @Test + @DisplayName("getConfig : retourne des valeurs par défaut si rien n'est sauvegardé") + void testGetConfigDefaults() { + AppConfig config = db.getConfig(); + + assertNotNull(config); + assertEquals("VBoxManage", config.getVboxManagePath()); + assertNotNull(config.getPdfExportDirectory()); + } + + @Test + @DisplayName("saveConfig et getConfig : les valeurs sont bien persistées") + void testSaveAndGetConfig() { + AppConfig config = new AppConfig(); + config.setVboxManagePath("/usr/bin/VBoxManage"); + config.setPdfExportDirectory("/home/agokoli/rapports"); + + db.saveConfig(config); + + AppConfig loaded = db.getConfig(); + assertEquals("/usr/bin/VBoxManage", loaded.getVboxManagePath()); + assertEquals("/home/agokoli/rapports", loaded.getPdfExportDirectory()); + } + + @Test + @DisplayName("saveConfig : une deuxième sauvegarde écrase la première") + void testSaveConfigOverwrite() { + AppConfig config1 = new AppConfig(); + config1.setVboxManagePath("/chemin/ancien"); + db.saveConfig(config1); + + AppConfig config2 = new AppConfig(); + config2.setVboxManagePath("/chemin/nouveau"); + db.saveConfig(config2); + + AppConfig loaded = db.getConfig(); + assertEquals("/chemin/nouveau", loaded.getVboxManagePath()); + } +} \ No newline at end of file