================================================================================
[OK] SONARQUBE EN ÉQUIPE DE 3 DÉVELOPPEURS - APPLICATION FLASK (GUIDE ULTRA-DÉTAILLÉ)
================================================================================

Ce guide couvre un cas réel et concret: une équipe de 3 développeurs Python
qui travaillent ensemble sur une application Flask et utilisent SonarQube
pour maintenir la qualité du code collectivement.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PRÉSENTATION DE L'ÉQUIPE ET DU PROJET
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

L'équipe:
  - Alice  -> Lead développeuse (aussi admin SonarQube)
  - Bob    -> Développeur backend
  - Claire -> Développeuse backend + tests

Le projet:
  - Nom: "TaskManager API"
  - Stack: Flask + SQLAlchemy + PostgreSQL
  - Versionning: Git + GitHub
  - Dépôt: github.com/equipe/taskmanager

Application Flask simple:
  - Route GET  /tasks          -> lister les tâches
  - Route POST /tasks          -> créer une tâche
  - Route GET  /tasks/<id>     -> récupérer une tâche
  - Route PUT  /tasks/<id>     -> modifier une tâche
  - Route DELETE /tasks/<id>  -> supprimer une tâche

Structure du projet:
  taskmanager/
  ├── app/
  │   ├── __init__.py
  │   ├── models.py
  │   ├── routes.py
  │   └── utils.py
  ├── tests/
  │   ├── __init__.py
  │   ├── conftest.py
  │   ├── test_routes.py
  │   └── test_models.py
  ├── requirements.txt
  ├── requirements-dev.txt
  ├── config.py
  ├── run.py
  ├── .gitignore
  └── sonar-project.properties


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 1 - MISE EN PLACE PAR ALICE (ADMIN / LEAD DEV)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Alice est chargée de mettre en place SonarQube pour toute l'équipe.
Elle fait cela une seule fois. Bob et Claire n'ont qu'à suivre la PARTIE 2.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.1 - Alice installe SonarQube sur son poste (ou serveur partagé)
────────────────────────────────────────────────────────────────────────────────

Alice ouvre son terminal et lance SonarQube avec Docker:

  COMMANDE:
  ─────────
  docker run -d \
    --name sonarqube \
    --restart unless-stopped \
    -p 9000:9000 \
    -v sonarqube_data:/opt/sonarqube/data \
    -v sonarqube_logs:/opt/sonarqube/logs \
    -v sonarqube_extensions:/opt/sonarqube/extensions \
    sonarqube:lts-community

  Explication:
  - --restart unless-stopped : SonarQube redémarre automatiquement si le PC redémarre
  - -v sonarqube_data:/...   : Les données sont sauvegardées même si Docker est arrêté
  - -p 9000:9000             : Accessible sur http://localhost:9000

Alice attend 2-3 minutes, puis vérifie:

  COMMANDE:
  ─────────
  curl http://localhost:9000/api/system/status

  RÉSULTAT ATTENDU:
  ─────────────────
  {"status":"UP"}

  Si le résultat est "STARTING", attendre encore 1 minute et réessayer.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.2 - Alice se connecte à SonarQube et change le mot de passe
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB - Étape par étape:

  1. Alice ouvre son navigateur et va sur:
     http://localhost:9000

  2. Elle voit la page de connexion:
     ┌─────────────────────────────────────┐
     │  [BLEU] SonarQube                       │
     │                                     │
     │  Username: [admin          ]        │
     │  Password: [admin          ]        │
     │                                     │
     │  [      Log In      ]               │
     └─────────────────────────────────────┘

  3. Elle entre:
     Username: admin
     Password: admin

  4. Elle clique sur "Log In"

  5. SonarQube lui demande immédiatement de changer le mot de passe:
     ┌─────────────────────────────────────────────────┐
     │  Update your password                           │
     │                                                 │
     │  Old password: [admin              ]            │
     │  New password: [TaskManager2024!   ]            │
     │  Confirm:      [TaskManager2024!   ]            │
     │                                                 │
     │  [         Update        ]                      │
     └─────────────────────────────────────────────────┘

  6. Elle entre son nouveau mot de passe et clique "Update"
     -> Elle est maintenant connectée en tant qu'admin

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.3 - Alice crée les comptes pour Bob et Claire
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB - Créer compte Bob:

  1. Alice clique sur l'icône Administration en haut à droite:
     Chemin: Administration > Security > Users

  2. Elle voit la liste des utilisateurs (seulement "admin" pour l'instant)

  3. Elle clique sur le bouton en haut à droite:
     [  + Create User  ]

  4. Formulaire qui apparaît:
     ┌─────────────────────────────────────────────────┐
     │  Create User                                    │
     │                                                 │
     │  Login*:        [bob                ]           │
     │  Name*:         [Bob Dupont         ]           │
     │  Email:         [bob@equipe.com     ]           │
     │  Password*:     [BobPass2024!       ]           │
     │                                                 │
     │  [   Create   ]   [Cancel]                      │
     └─────────────────────────────────────────────────┘

  5. Elle remplit et clique "Create"

  6. Elle répète la même opération pour Claire:
     Login:    claire
     Name:     Claire Martin
     Email:    claire@equipe.com
     Password: ClairePass2024!

  RÉSULTAT: La liste Users affiche maintenant:
  ┌────────────────────────────────────────────────────┐
  │  Users (3)                                         │
  ├─────────────┬────────────────┬────────────────────┤
  │  admin      │  Administrator │  Last login: now    │
  │  bob        │  Bob Dupont    │  Never logged in    │
  │  claire     │  Claire Martin │  Never logged in    │
  └─────────────┴────────────────┴────────────────────┘

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.4 - Alice crée un groupe "taskmanager-devs"
────────────────────────────────────────────────────────────────────────────────

Plutôt que de gérer les permissions utilisateur par utilisateur, Alice crée
un groupe qui regroupera les 3 développeurs.

INTERFACE WEB:

  1. Chemin: Administration > Security > Groups

  2. Cliquer sur: [  + Create Group  ]

  3. Formulaire:
     ┌─────────────────────────────────────────────────┐
     │  Create Group                                   │
     │                                                 │
     │  Name*:        [taskmanager-devs   ]            │
     │  Description:  [Equipe TaskManager ]            │
     │                                                 │
     │  [   Create   ]                                 │
     └─────────────────────────────────────────────────┘

  4. Cliquer sur "Create"

  5. Dans la liste des groupes, cliquer sur "Members" à droite de "taskmanager-devs":
     ┌──────────────────────────────────────────────────┐
     │  Members of taskmanager-devs (0 members)         │
     │                                                  │
     │  Search users: [                    ]            │
     │                                                  │
     │  Available users:                                │
     │  [WHITE_CIRCLE] admin                                         │
     │  [WHITE_CIRCLE] bob         <- Cocher                         │
     │  [WHITE_CIRCLE] claire      <- Cocher                         │
     └──────────────────────────────────────────────────┘

  6. Alice coche "bob" et "claire", puis clique "Add selected"
     (Elle n'ajoute PAS "admin" au groupe car admin a déjà tous les droits)

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.5 - Alice crée le projet SonarQube
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB:

  1. Alice clique sur "Projects" dans la barre de navigation en haut

  2. Elle voit la page vide avec un bouton:
     ┌─────────────────────────────────────────────────┐
     │  No projects yet                                │
     │                                                 │
     │  [  + Create project  ]                         │
     └─────────────────────────────────────────────────┘

  3. Cliquer sur "+ Create project"

  4. SonarQube demande d'où vient le projet:
     ┌─────────────────────────────────────────────────┐
     │  Create your first project                      │
     │                                                 │
     │  [WHITE_CIRCLE] From GitHub                                  │
     │  [WHITE_CIRCLE] From GitLab                                  │
     │  [WHITE_CIRCLE] From Bitbucket                               │
     │  [BLACK_CIRCLE] Manually      <- Sélectionner                │
     │                                                 │
     │  [       Next       ]                           │
     └─────────────────────────────────────────────────┘

  5. Sélectionner "Manually" et cliquer "Next"

  6. Remplir le formulaire:
     ┌─────────────────────────────────────────────────┐
     │  Create a project manually                      │
     │                                                 │
     │  Project display name*:                         │
     │  [TaskManager API                  ]            │
     │                                                 │
     │  Project key* (auto-rempli):                    │
     │  [taskmanager-api                  ]            │
     │                                                 │
     │  Main branch name:                              │
     │  [main                             ]            │
     │                                                 │
     │  [         Next         ]                       │
     └─────────────────────────────────────────────────┘

     - Project display name: TaskManager API
     - Project key: taskmanager-api (généré automatiquement, modifiable)
     - Main branch name: main

  7. Cliquer "Next"

  8. SonarQube demande la méthode d'analyse:
     ┌─────────────────────────────────────────────────┐
     │  How do you want to analyze your repository?    │
     │                                                 │
     │  [WHITE_CIRCLE] With GitHub Actions                          │
     │  [WHITE_CIRCLE] With GitLab CI                               │
     │  [WHITE_CIRCLE] With Jenkins                                 │
     │  [BLACK_CIRCLE] Locally      <- Sélectionner pour l'instant  │
     │                                                 │
     └─────────────────────────────────────────────────┘

  9. Sélectionner "Locally"

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.6 - Alice génère le token du projet
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB (suite de l'étape 1.5):

  10. SonarQube affiche:
      ┌──────────────────────────────────────────────────────┐
      │  Provide a token                                     │
      │                                                      │
      │  Generate a project token:                           │
      │                                                      │
      │  Token name*: [taskmanager-token            ]        │
      │  Token type:  [BLACK_CIRCLE] Project Analysis Token               │
      │  Expires in:  [No expiration          [BLACK_DOWN-POINTING_TRIANGLE]]            │
      │                                                      │
      │  [         Generate         ]                        │
      └──────────────────────────────────────────────────────┘

  11. Alice entre:
      Token name: taskmanager-token
      Token type: Project Analysis Token (déjà sélectionné)
      Expires in: No expiration (pour simplifier)

  12. Elle clique "Generate"

  13. SonarQube affiche le token:
      ┌──────────────────────────────────────────────────────┐
      │  [OK] Token generated successfully!                    │
      │                                                      │
      │  squ_a4f8e2c1d9b3f7a5e8c2d4f1b6e9a3c7d5f2b8e1     │
      │                                                      │
      │  [ATTENTION]  Save this token! It won't be shown again.      │
      │                                                      │
      │  [         Continue         ]                        │
      └──────────────────────────────────────────────────────┘

  14. TRÈS IMPORTANT: Alice copie ce token et le note quelque part:
      Token: squ_a4f8e2c1d9b3f7a5e8c2d4f1b6e9a3c7d5f2b8e1
      (dans un fichier .env, un gestionnaire de mots de passe, etc.)

  15. Elle clique "Continue"

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.7 - Alice configure les permissions du projet pour l'équipe
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB:

  1. Chemin: Projects > TaskManager API > Project Settings > Permissions

  2. Elle voit le tableau des permissions:
     ┌────────────────────────────────────────────────────────────────────┐
     │  Permissions for TaskManager API                                   │
     ├───────────────────┬────────┬──────────┬─────────┬─────────────────┤
     │  Group/User       │ Browse │ See Code │ Execute │ Administer      │
     │                   │        │          │ Analysis│                 │
     ├───────────────────┼────────┼──────────┼─────────┼─────────────────┤
     │  Anyone           │  [OK]     │          │         │                 │
     │  sonar-users      │  [OK]     │          │         │                 │
     └───────────────────┴────────┴──────────┴─────────┴─────────────────┘

  3. Alice veut donner accès au groupe "taskmanager-devs":
     En dessous du tableau, elle cherche le champ:
     "Grant permissions to: [Search for user or group...]"

  4. Elle tape "taskmanager-devs" dans ce champ et le sélectionne

  5. Elle coche les cases pour ce groupe:
     [OK] Browse (voir le projet)
     [OK] See Source Code (voir le code)
     [OK] Execute Analysis (lancer des analyses)

  6. Elle clique "Grant"

  RÉSULTAT final du tableau:
  ┌────────────────────────────────────────────────────────────────────┐
  │  Permissions for TaskManager API                                   │
  ├─────────────────────┬────────┬──────────┬─────────┬───────────────┤
  │  Group/User         │ Browse │ See Code │ Execute │ Administer    │
  ├─────────────────────┼────────┼──────────┼─────────┼───────────────┤
  │  Anyone             │  [OK]     │          │         │               │
  │  sonar-users        │  [OK]     │          │         │               │
  │  taskmanager-devs   │  [OK]     │    [OK]     │    [OK]    │               │
  └─────────────────────┴────────┴──────────┴─────────┴───────────────┘

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.8 - Alice crée et configure la Quality Gate de l'équipe
────────────────────────────────────────────────────────────────────────────────

Alice veut que l'équipe respecte des standards précis. Elle crée une Quality Gate
adaptée à leur projet.

INTERFACE WEB:

  1. Chemin: Administration > Quality Gates

  2. Elle voit la Quality Gate "Sonar way" (par défaut) dans la liste

  3. Elle clique sur "Create" (bouton en haut à droite):
     ┌─────────────────────────────────────────────────┐
     │  Create Quality Gate                            │
     │                                                 │
     │  Name*: [TaskManager-QualityGate    ]           │
     │                                                 │
     │  [         Create         ]                     │
     └─────────────────────────────────────────────────┘

  4. Elle entre: "TaskManager-QualityGate" et clique "Create"

  5. Elle est redirigée vers la page d'édition de la nouvelle Quality Gate.
     Elle voit "No conditions yet". Elle clique "Add Condition":

     Condition 1 - Coverage nouveau code:
     ┌─────────────────────────────────────────────────┐
     │  Add Condition                                  │
     │                                                 │
     │  Where: [BLACK_CIRCLE] On New Code                           │
     │  Metric: [Coverage                   [BLACK_DOWN-POINTING_TRIANGLE]]        │
     │  Operator: [is less than             [BLACK_DOWN-POINTING_TRIANGLE]]        │
     │  Value: [80                           ]         │
     │                                                 │
     │  [         Add Condition         ]              │
     └─────────────────────────────────────────────────┘
     -> Cliquer "Add Condition"

     Condition 2 - Bugs nouveaux:
     Metric: Bugs | On New Code | is greater than | 0
     -> Cliquer "Add Condition"

     Condition 3 - Vulnérabilités nouvelles:
     Metric: Vulnerabilities | On New Code | is greater than | 0
     -> Cliquer "Add Condition"

     Condition 4 - Duplications nouveau code:
     Metric: Duplicated Lines (%) | On New Code | is greater than | 3
     -> Cliquer "Add Condition"

  6. Résultat de la Quality Gate:
     ┌──────────────────────────────────────────────────────────────┐
     │  TaskManager-QualityGate                                     │
     ├──────────────────────────────────┬──────────────────────────┤
     │  Condition                       │  Seuil                   │
     ├──────────────────────────────────┼──────────────────────────┤
     │  New Coverage                    │  < 80%  -> FAIL           │
     │  New Bugs                        │  > 0    -> FAIL           │
     │  New Vulnerabilities             │  > 0    -> FAIL           │
     │  New Duplicated Lines (%)        │  > 3%   -> FAIL           │
     └──────────────────────────────────┴──────────────────────────┘

  7. Appliquer cette Quality Gate au projet:
     Chemin: Projects > TaskManager API > Project Settings > Quality Gate
     Choisir: "TaskManager-QualityGate" dans la liste déroulante
     Cliquer: "Save"

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.9 - Alice prépare le code source Flask (app de base)
────────────────────────────────────────────────────────────────────────────────

Alice crée la structure de base de l'application:

  COMMANDES TERMINAL:
  ───────────────────
  mkdir taskmanager && cd taskmanager
  python -m venv venv
  source venv/bin/activate          # Linux/Mac
  # venv\Scripts\activate           # Windows

  pip install flask flask-sqlalchemy psycopg2-binary
  pip install pytest pytest-cov

  mkdir app tests

  # requirements.txt
  pip freeze > requirements.txt

  # requirements-dev.txt
  echo "pytest==7.4.0" > requirements-dev.txt
  echo "pytest-cov==4.1.0" >> requirements-dev.txt
  echo "sonarqube-scanner==1.0.0" >> requirements-dev.txt

Contenu des fichiers principaux:

  ── app/__init__.py ──────────────────────────────────────────────────────────

  from flask import Flask
  from flask_sqlalchemy import SQLAlchemy

  db = SQLAlchemy()

  def create_app(config=None):
      app = Flask(__name__)
      app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
      app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

      if config:
          app.config.update(config)

      db.init_app(app)

      from app.routes import tasks_bp
      app.register_blueprint(tasks_bp)

      with app.app_context():
          db.create_all()

      return app

  ── app/models.py ────────────────────────────────────────────────────────────

  from app import db
  from datetime import datetime

  class Task(db.Model):
      id = db.Column(db.Integer, primary_key=True)
      title = db.Column(db.String(200), nullable=False)
      description = db.Column(db.Text, nullable=True)
      done = db.Column(db.Boolean, default=False)
      created_at = db.Column(db.DateTime, default=datetime.utcnow)

      def to_dict(self):
          return {
              'id': self.id,
              'title': self.title,
              'description': self.description,
              'done': self.done,
              'created_at': self.created_at.isoformat()
          }

  ── app/routes.py ────────────────────────────────────────────────────────────

  from flask import Blueprint, request, jsonify, abort
  from app import db
  from app.models import Task

  tasks_bp = Blueprint('tasks', __name__)

  @tasks_bp.route('/tasks', methods=['GET'])
  def get_tasks():
      tasks = Task.query.all()
      return jsonify([t.to_dict() for t in tasks])

  @tasks_bp.route('/tasks', methods=['POST'])
  def create_task():
      data = request.get_json()
      if not data or 'title' not in data:
          abort(400, description="Title is required")
      task = Task(title=data['title'], description=data.get('description'))
      db.session.add(task)
      db.session.commit()
      return jsonify(task.to_dict()), 201

  @tasks_bp.route('/tasks/<int:task_id>', methods=['GET'])
  def get_task(task_id):
      task = Task.query.get(task_id)
      if task is None:
          abort(404, description="Task not found")
      return jsonify(task.to_dict())

  @tasks_bp.route('/tasks/<int:task_id>', methods=['PUT'])
  def update_task(task_id):
      task = Task.query.get(task_id)
      if task is None:
          abort(404, description="Task not found")
      data = request.get_json()
      if not data:
          abort(400, description="No data provided")
      task.title = data.get('title', task.title)
      task.description = data.get('description', task.description)
      task.done = data.get('done', task.done)
      db.session.commit()
      return jsonify(task.to_dict())

  @tasks_bp.route('/tasks/<int:task_id>', methods=['DELETE'])
  def delete_task(task_id):
      task = Task.query.get(task_id)
      if task is None:
          abort(404, description="Task not found")
      db.session.delete(task)
      db.session.commit()
      return jsonify({'message': 'Task deleted'}), 200

  ── run.py ───────────────────────────────────────────────────────────────────

  from app import create_app

  app = create_app()

  if __name__ == '__main__':
      app.run(debug=True)

  ── tests/conftest.py ────────────────────────────────────────────────────────

  import pytest
  from app import create_app, db as _db

  @pytest.fixture
  def app():
      test_config = {
          'TESTING': True,
          'SQLALCHEMY_DATABASE_URI': 'sqlite:///:memory:'
      }
      app = create_app(test_config)
      with app.app_context():
          _db.create_all()
          yield app
          _db.drop_all()

  @pytest.fixture
  def client(app):
      return app.test_client()

  ── tests/test_routes.py (tests initiaux de Claire) ─────────────────────────

  import json

  def test_get_tasks_empty(client):
      response = client.get('/tasks')
      assert response.status_code == 200
      assert response.get_json() == []

  def test_create_task(client):
      data = {'title': 'Acheter du lait', 'description': 'Au supermarché'}
      response = client.post('/tasks', json=data)
      assert response.status_code == 201
      result = response.get_json()
      assert result['title'] == 'Acheter du lait'
      assert result['done'] == False

  def test_create_task_missing_title(client):
      response = client.post('/tasks', json={'description': 'test'})
      assert response.status_code == 400

  def test_get_task(client):
      client.post('/tasks', json={'title': 'Ma tâche'})
      response = client.get('/tasks/1')
      assert response.status_code == 200
      assert response.get_json()['title'] == 'Ma tâche'

  def test_get_task_not_found(client):
      response = client.get('/tasks/999')
      assert response.status_code == 404

  def test_update_task(client):
      client.post('/tasks', json={'title': 'Original'})
      response = client.put('/tasks/1', json={'title': 'Modifié', 'done': True})
      assert response.status_code == 200
      assert response.get_json()['title'] == 'Modifié'
      assert response.get_json()['done'] == True

  def test_delete_task(client):
      client.post('/tasks', json={'title': 'À supprimer'})
      response = client.delete('/tasks/1')
      assert response.status_code == 200
      assert client.get('/tasks/1').status_code == 404

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.10 - Alice crée le fichier sonar-project.properties
────────────────────────────────────────────────────────────────────────────────

Alice crée ce fichier à la racine du projet:

  COMMANDE TERMINAL:
  ──────────────────
  nano sonar-project.properties

  CONTENU DU FICHIER:
  ───────────────────
  # Identification du projet
  sonar.projectKey=taskmanager-api
  sonar.projectName=TaskManager API
  sonar.projectVersion=1.0.0

  # Chemins source et tests
  sonar.sources=app
  sonar.tests=tests

  # Encodage
  sonar.sourceEncoding=UTF-8

  # Version Python
  sonar.python.version=3.11

  # Rapport de couverture de tests
  sonar.python.coverage.reportPaths=coverage.xml

  # Fichiers à ignorer complètement
  sonar.exclusions=\
    **/venv/**,\
    **/.venv/**,\
    **/__pycache__/**,\
    **/migrations/**,\
    **/instance/**,\
    **/*.pyc

  # Fichiers tests exclus de l'analyse de code source
  sonar.test.exclusions=**/tests/**

  # Fichiers exclus du calcul de couverture
  sonar.coverage.exclusions=\
    **/tests/**,\
    **/run.py

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.11 - Alice installe sonar-scanner et fait la première analyse
────────────────────────────────────────────────────────────────────────────────

  COMMANDES TERMINAL (Mac/Linux):
  ────────────────────────────────

  # Mac avec Homebrew:
  brew install sonar-scanner

  # Linux (Ubuntu):
  wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip
  unzip sonar-scanner-cli-*.zip
  sudo mv sonar-scanner-* /opt/sonar-scanner
  echo 'export PATH=$PATH:/opt/sonar-scanner/bin' >> ~/.bashrc
  source ~/.bashrc

  # Vérifier l'installation:
  sonar-scanner --version
  # Attendu: SonarScanner 5.0.1...

  # Générer coverage d'abord:
  cd taskmanager/
  pytest --cov=app --cov-report=xml --cov-report=term
  # Attendu: coverage.xml créé dans le dossier courant

  # Lancer l'analyse SonarQube:
  sonar-scanner -Dsonar.token=squ_a4f8e2c1d9b3f7a5e8c2d4f1b6e9a3c7d5f2b8e1

  SORTIE ATTENDUE DANS LE TERMINAL:
  ───────────────────────────────────
  INFO: Scanner configuration file: .../sonar-scanner.properties
  INFO: Project root configuration file: sonar-project.properties
  INFO: SonarScanner 5.0.1
  INFO: Java 17
  INFO: User cache: /home/alice/.sonar/cache
  INFO: SonarQube server 10.x
  INFO: Default locale: "fr_FR"
  INFO: Work directory: ./.scannerwork
  INFO: -----------------------------------------------------------------------
  INFO: EXECUTION
  INFO: -----------------------------------------------------------------------
  INFO: Load project settings...
  INFO: Load quality profiles...
  INFO: Load active rules...
  INFO: Sensor Python Sensor [python]
  INFO:   5 source files to be analyzed
  INFO:   2 test source files to be analyzed
  INFO: Coverage report doesn't exist: ...  <- Si pas encore généré
  INFO: Sensor Python Coverage [python]
  INFO: -----------------------------------------------------------------------
  INFO: ANALYSIS SUCCESSFUL
  INFO: Note that you will be able to access the report at:
  INFO: http://localhost:9000/dashboard?id=taskmanager-api
  INFO: -----------------------------------------------------------------------
  INFO: Execution success
  INFO: Total time: 15.432s
  INFO: Final Memory: 28M/115M

  Si vous voyez "ANALYSIS SUCCESSFUL" -> Tout s'est bien passé!

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.12 - Alice vérifie les résultats dans l'interface
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB:

  1. Alice va sur: http://localhost:9000
  2. Elle clique sur le projet "TaskManager API"
  3. Elle voit le premier dashboard:

  ┌────────────────────────────────────────────────────────────────────┐
  │  TaskManager API                                                   │
  │  Last analysis: Today at 14:32  | main | 78 lines of code         │
  ├──────────────────────┬─────────────────────┬──────────────────────┤
  │  Reliability * A    │  Security * A      │  Maintainability * A │
  │  0 Bugs              │  0 Vulnerabilities  │  2 Code Smells        │
  ├──────────────────────┴─────────────────────┴──────────────────────┤
  │  Coverage: 82.5% [OK]  |  Duplications: 0.0% [OK]                     │
  ├────────────────────────────────────────────────────────────────────┤
  │  Quality Gate: [OK] PASSED                                           │
  └────────────────────────────────────────────────────────────────────┘

  Super! La base est propre. Alice partage les informations avec l'équipe.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.13 - Alice crée des tokens individuels pour Bob et Claire
────────────────────────────────────────────────────────────────────────────────

Chaque développeur doit avoir son propre token pour lancer les analyses.

Alice génère les tokens (elle peut aussi demander à Bob et Claire de le faire
eux-mêmes une fois connectés):

  INTERFACE WEB (pour générer token pour Bob):

  1. Chemin: Administration > Security > Users
  2. Cliquer sur "bob" dans la liste
  3. Onglet "Tokens"
  4. Cliquer "+ Generate Token":
     ┌─────────────────────────────────────────────────┐
     │  Generate Token for bob                         │
     │                                                 │
     │  Token name*: [bob-local-token      ]           │
     │  Token type:  [BLACK_CIRCLE] User Token                      │
     │  Expires in:  [No expiration  [BLACK_DOWN-POINTING_TRIANGLE]]               │
     │                                                 │
     │  [         Generate         ]                   │
     └─────────────────────────────────────────────────┘

  5. Token de Bob: squ_bob_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
     Token de Claire: squ_claire_xxxxxxxxxxxxxxxxxxxxxxxxx

  Alice note tous les tokens et les partage avec chacun par message privé.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 2 - CONFIGURATION POSTE DE BOB ET CLAIRE (DÉVELOPPEURS)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Bob et Claire reçoivent d'Alice:
  - L'URL de SonarQube: http://[IP_D_ALICE]:9000
    (ou http://localhost:9000 si SonarQube est en local)
  - Leur login et mot de passe (à changer à la première connexion)
  - Leur token personnel

NOTE: Si SonarQube est sur la machine d'Alice et que Bob/Claire veulent y accéder
depuis leur propre PC, Alice doit soit:
  a) Utiliser l'IP locale d'Alice sur le réseau (ex: http://192.168.1.10:9000)
  b) Ou pour ce guide, SonarQube est sur une machine partagée/serveur

Pour simplifier, supposons que SonarQube est accessible à:
http://sonar.equipe.local:9000 (ou l'IP d'Alice)

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.1 - Bob et Claire se connectent pour la première fois
────────────────────────────────────────────────────────────────────────────────

BOB fait ceci sur son poste:

  INTERFACE WEB:

  1. Ouvrir le navigateur -> http://sonar.equipe.local:9000

  2. Page de connexion:
     Username: bob
     Password: BobPass2024!   (donné par Alice)

  3. SonarQube demande de changer le mot de passe:
     Old password: BobPass2024!
     New password: MonNouveauMotDePasse!
     Confirm:      MonNouveauMotDePasse!
     -> Cliquer "Update"

  4. Bob voit maintenant le dashboard avec le projet "TaskManager API"

CLAIRE fait exactement pareil avec ses identifiants.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.2 - Bob et Claire récupèrent le projet Git et installent les dépendances
────────────────────────────────────────────────────────────────────────────────

BOB sur son terminal:

  COMMANDES:
  ──────────
  # Cloner le projet
  git clone https://github.com/equipe/taskmanager.git
  cd taskmanager

  # Créer et activer l'environnement virtuel
  python -m venv venv
  source venv/bin/activate    # Linux/Mac
  # venv\Scripts\activate     # Windows

  # Installer les dépendances
  pip install -r requirements.txt
  pip install -r requirements-dev.txt

  # Vérifier que les tests passent
  pytest
  # Attendu: 7 passed

CLAIRE fait exactement pareil sur son poste.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.3 - Bob et Claire installent sonar-scanner
────────────────────────────────────────────────────────────────────────────────

BOB:
  # Mac:
  brew install sonar-scanner

  # Linux:
  wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip
  unzip sonar-scanner-cli-*.zip
  sudo mv sonar-scanner-* /opt/sonar-scanner
  echo 'export PATH=$PATH:/opt/sonar-scanner/bin' >> ~/.bashrc
  source ~/.bashrc

  # Vérifier:
  sonar-scanner --version

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.4 - Bob et Claire créent leur .env local pour stocker le token
────────────────────────────────────────────────────────────────────────────────

Il ne faut JAMAIS mettre le token directement dans le code ou le fichier
sonar-project.properties (qui est dans Git).

BOB crée un fichier .env à la racine du projet:

  COMMANDE:
  ─────────
  nano .env

  CONTENU:
  ────────
  SONAR_TOKEN=squ_bob_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
  SONAR_HOST_URL=http://sonar.equipe.local:9000

  # S'assurer que .env est dans .gitignore:
  echo ".env" >> .gitignore
  echo ".scannerwork/" >> .gitignore
  echo "coverage.xml" >> .gitignore
  echo "htmlcov/" >> .gitignore
  echo ".coverage" >> .gitignore

Bob crée aussi un script pratique pour lancer l'analyse:

  COMMANDE:
  ─────────
  nano analyze.sh

  CONTENU DU SCRIPT:
  ──────────────────
  #!/bin/bash
  # analyze.sh - Script pour analyser avec SonarQube

  set -e  # Stopper si erreur

  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "  TaskManager - Analyse SonarQube"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

  # Charger les variables d'environnement
  if [ -f .env ]; then
      export $(grep -v '^#' .env | xargs)
  fi

  # Vérifier que le token est défini
  if [ -z "$SONAR_TOKEN" ]; then
      echo "[X] SONAR_TOKEN non défini dans .env"
      exit 1
  fi

  echo ""
  echo "[TEST] Étape 1/2: Lancement des tests avec couverture..."
  pytest --cov=app --cov-report=xml --cov-report=term-missing
  echo "[OK] Tests terminés"

  echo ""
  echo "[GRAPHIQUE] Étape 2/2: Envoi vers SonarQube..."
  sonar-scanner \
    -Dsonar.token=$SONAR_TOKEN \
    -Dsonar.host.url=$SONAR_HOST_URL

  echo ""
  echo "[OK] Analyse terminée!"
  echo "[HAUSSE] Voir résultats: $SONAR_HOST_URL/dashboard?id=taskmanager-api"

  COMMANDE pour rendre le script exécutable:
  ──────────────────────────────────────────
  chmod +x analyze.sh

CLAIRE fait exactement pareil avec son propre token dans son .env.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 3 - WORKFLOW QUOTIDIEN DE L'ÉQUIPE (CAS CONCRETS)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Voici comment l'équipe utilise SonarQube au quotidien à travers 3 scénarios réels.

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO A - Bob ajoute une fonctionnalité et introduit un bug
════════════════════════════════════════════════════════════════════════════════

Bob doit ajouter une route pour filtrer les tâches par statut (done/not done).

──────────────────────────────────────────────────────────────────────────────
A.1 - Bob crée une nouvelle branche et écrit son code
──────────────────────────────────────────────────────────────────────────────

  COMMANDES:
  ──────────
  git checkout -b feature/filter-tasks-by-status

Bob ajoute dans app/routes.py:

  ── NOUVEAU CODE DE BOB (app/routes.py - ajout) ──────────────────────────────

  @tasks_bp.route('/tasks/filter', methods=['GET'])
  def filter_tasks():
      status = request.args.get('done')    # "true" ou "false"

      # [X] BUG: Bob ne gère pas le cas où status est None
      # [X] BUG: Bob compare un string à un booléen
      if status == True:
          tasks = Task.query.filter_by(done=True).all()
      else:
          tasks = Task.query.filter_by(done=False).all()

      # [X] CODE SMELL: Variable intermédiaire inutile
      result = [t.to_dict() for t in tasks]
      result_final = result
      return jsonify(result_final)

──────────────────────────────────────────────────────────────────────────────
A.2 - Bob lance l'analyse SonarQube avant de push
──────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  ./analyze.sh

  SORTIE TERMINAL (extrait):
  ──────────────────────────
  [TEST] Étape 1/2: Lancement des tests avec couverture...

  PASSED tests/test_routes.py::test_get_tasks_empty
  PASSED tests/test_routes.py::test_create_task
  ...
  WARNING: no tests ran for filter_tasks (new function)
  Coverage: app/routes.py  72%  (la nouvelle route n'est pas testée)

  [GRAPHIQUE] Étape 2/2: Envoi vers SonarQube...
  INFO: ANALYSIS SUCCESSFUL

──────────────────────────────────────────────────────────────────────────────
A.3 - Bob regarde les résultats dans l'interface SonarQube
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB:

  1. Bob va sur http://sonar.equipe.local:9000
  2. Il clique sur "TaskManager API"
  3. Il voit:

  ┌─────────────────────────────────────────────────────────────────┐
  │  TaskManager API                                                │
  ├─────────────────────────────────────────────────────────────────┤
  │  Quality Gate: [X] FAILED                                        │
  │                                                                 │
  │  Reliability * A  (0 bugs)                                     │
  │  Security    * A  (0 vulnérabilités)                           │
  │  Coverage: 72%  [X] (inférieur à 80%)                            │
  └─────────────────────────────────────────────────────────────────┘

  4. Bob clique sur "Issues" pour voir les problèmes détectés:

  ┌──────────────────────────────────────────────────────────────────┐
  │  Issues (3 nouvelles)                                            │
  ├────────────────────────────────────────────────────────────────┤
  │  [BUG] Bug [MAJOR]                                                  │
  │  "status" may always evaluate to False here                     │
  │  app/routes.py:35                                               │
  │  Comparing a string variable to a boolean literal is always     │
  │  False (str != bool)                                            │
  ├──────────────────────────────────────────────────────────────────┤
  │  [NETTOYAGE] Code Smell [MINOR]                                           │
  │  Remove this useless assignment to local variable "result_final"│
  │  app/routes.py:42                                               │
  ├──────────────────────────────────────────────────────────────────┤
  │  [NETTOYAGE] Code Smell [MINOR]                                           │
  │  Refactor to reduce complexity                                   │
  │  app/routes.py:33                                               │
  └──────────────────────────────────────────────────────────────────┘

  5. Bob clique sur le Bug pour voir les détails:

  ┌──────────────────────────────────────────────────────────────────┐
  │  Bug: String comparison to boolean will always be False          │
  │  File: app/routes.py  Line: 35                                   │
  │                                                                  │
  │  33 | def filter_tasks():                                        │
  │  34 |     status = request.args.get('done')                      │
  │-> 35 |     if status == True:   <- PROBLÈME ICI                   │
  │  36 |         tasks = Task.query.filter_by(done=True).all()     │
  │  37 |     else:                                                  │
  │  38 |         tasks = Task.query.filter_by(done=False).all()    │
  │                                                                  │
  │  Why is this a bug?                                              │
  │  request.args.get() retourne toujours une string.               │
  │  "true" == True est toujours False en Python.                    │
  │                                                                  │
  │  Effort: 5 minutes                                               │
  └──────────────────────────────────────────────────────────────────┘

──────────────────────────────────────────────────────────────────────────────
A.4 - Bob corrige son code
──────────────────────────────────────────────────────────────────────────────

Bob retourne dans son éditeur et corrige app/routes.py:

  ── CODE CORRIGÉ ─────────────────────────────────────────────────────────────

  @tasks_bp.route('/tasks/filter', methods=['GET'])
  def filter_tasks():
      status = request.args.get('done')

      # [OK] Gestion du paramètre manquant
      if status is None:
          tasks = Task.query.all()
      # [OK] Comparaison correcte string à string
      elif status.lower() == 'true':
          tasks = Task.query.filter_by(done=True).all()
      else:
          tasks = Task.query.filter_by(done=False).all()

      # [OK] Plus de variable intermédiaire inutile
      return jsonify([t.to_dict() for t in tasks])

Bob ajoute aussi les tests dans tests/test_routes.py:

  def test_filter_tasks_done(client):
      client.post('/tasks', json={'title': 'Tâche 1'})
      client.post('/tasks', json={'title': 'Tâche 2'})
      client.put('/tasks/1', json={'done': True})
      response = client.get('/tasks/filter?done=true')
      assert response.status_code == 200
      tasks = response.get_json()
      assert len(tasks) == 1
      assert tasks[0]['done'] == True

  def test_filter_tasks_not_done(client):
      client.post('/tasks', json={'title': 'Tâche 1'})
      client.post('/tasks', json={'title': 'Tâche 2'})
      client.put('/tasks/1', json={'done': True})
      response = client.get('/tasks/filter?done=false')
      assert response.status_code == 200
      tasks = response.get_json()
      assert len(tasks) == 1
      assert tasks[0]['done'] == False

  def test_filter_tasks_no_param(client):
      client.post('/tasks', json={'title': 'Tâche'})
      response = client.get('/tasks/filter')
      assert response.status_code == 200
      assert len(response.get_json()) == 1

──────────────────────────────────────────────────────────────────────────────
A.5 - Bob re-lance l'analyse et vérifie que tout est vert
──────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  ./analyze.sh

  SORTIE TERMINAL:
  ────────────────
  [TEST] Étape 1/2: Lancement des tests avec couverture...
  10 passed in 0.43s
  Coverage app: 86%

  [GRAPHIQUE] Étape 2/2: Envoi vers SonarQube...
  INFO: ANALYSIS SUCCESSFUL

INTERFACE WEB (résultats):

  ┌─────────────────────────────────────────────────────────────────┐
  │  TaskManager API                                                │
  ├─────────────────────────────────────────────────────────────────┤
  │  Quality Gate: [OK] PASSED                                        │
  │                                                                 │
  │  Reliability * A  (0 bugs)                                     │
  │  Security    * A  (0 vulnérabilités)                           │
  │  Coverage: 86%  [OK]                                              │
  │  Duplications: 0%  [OK]                                           │
  └─────────────────────────────────────────────────────────────────┘

Bob peut maintenant faire son commit et push:

  COMMANDES:
  ──────────
  git add app/routes.py tests/test_routes.py
  git commit -m "feat: add filter tasks by status endpoint

  - Add GET /tasks/filter?done=true|false
  - Handle missing parameter (returns all tasks)
  - Add tests for all filter scenarios
  - SonarQube QualityGate: PASSED"

  git push origin feature/filter-tasks-by-status


════════════════════════════════════════════════════════════════════════════════
SCÉNARIO B - Claire détecte une vulnérabilité de sécurité et la résout
════════════════════════════════════════════════════════════════════════════════

Claire doit ajouter une recherche de tâches par titre.

──────────────────────────────────────────────────────────────────────────────
B.1 - Claire écrit le code (avec une vulnérabilité)
──────────────────────────────────────────────────────────────────────────────

  COMMANDES:
  ──────────
  git checkout main
  git pull
  git checkout -b feature/search-tasks

Claire ajoute dans app/routes.py:

  ── CODE DE CLAIRE (avec vulnérabilité) ──────────────────────────────────────

  from flask import request
  from app import db

  @tasks_bp.route('/tasks/search', methods=['GET'])
  def search_tasks():
      query = request.args.get('q', '')

      # [X] VULNÉRABILITÉ CRITIQUE: Injection SQL!
      # Ne JAMAIS faire cela dans un vrai projet
      raw_sql = f"SELECT * FROM task WHERE title LIKE '%{query}%'"
      results = db.engine.execute(raw_sql)

      tasks = [dict(row) for row in results]
      return jsonify(tasks)

──────────────────────────────────────────────────────────────────────────────
B.2 - Claire lance l'analyse
──────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  ./analyze.sh

──────────────────────────────────────────────────────────────────────────────
B.3 - Claire voit la vulnérabilité dans SonarQube
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB:

  1. Claire va sur http://sonar.equipe.local:9000 > TaskManager API

  2. Elle voit immédiatement:

  ┌─────────────────────────────────────────────────────────────────┐
  │  Quality Gate: [X] FAILED                                        │
  │                                                                 │
  │  Security * E  <- ROUGE  (avant: A)                            │
  │  1 Vulnerability [BLOCKER]                                      │
  └─────────────────────────────────────────────────────────────────┘

  3. Elle clique sur "1 Vulnerability":

  ┌──────────────────────────────────────────────────────────────────┐
  │  [VERROUILLE] Vulnerability [BLOCKER]                                      │
  │  Make sure using a dynamically formatted SQL query is safe here  │
  │  app/routes.py:51                                                │
  │                                                                  │
  │  49 | def search_tasks():                                        │
  │  50 |     query = request.args.get('q', '')                      │
  │-> 51 |     raw_sql = f"SELECT ... LIKE '%{query}%'"  <- DANGER!  │
  │  52 |     results = db.engine.execute(raw_sql)                   │
  │                                                                  │
  │  Why is this a vulnerability?                                    │
  │  A user can inject SQL code via the 'query' parameter.          │
  │  Example attack: ?q=' OR '1'='1 -> returns ALL tasks!           │
  │  Example attack: ?q='; DROP TABLE task; --  -> deletes data!    │
  │                                                                  │
  │  How to fix:                                                     │
  │  Use parameterized queries or ORM methods.                       │
  │                                                                  │
  │  Severity: BLOCKER                                               │
  │  Effort: 30 minutes                                              │
  │                                                                  │
  │  See also:                                                       │
  │  - CWE-89: Improper Neutralization of Special Elements           │
  │  - OWASP A03:2021 – Injection                                   │
  └──────────────────────────────────────────────────────────────────┘

──────────────────────────────────────────────────────────────────────────────
B.4 - Claire corrige la vulnérabilité
──────────────────────────────────────────────────────────────────────────────

  ── CODE CORRIGÉ DE CLAIRE ───────────────────────────────────────────────────

  @tasks_bp.route('/tasks/search', methods=['GET'])
  def search_tasks():
      query = request.args.get('q', '')

      if not query or len(query) < 2:
          return jsonify({'error': 'Query must be at least 2 characters'}), 400

      # [OK] ORM SQLAlchemy avec paramètre sécurisé (pas d'injection SQL possible)
      tasks = Task.query.filter(
          Task.title.ilike(f'%{query}%')    # ilike = insensible à la casse
      ).all()

      return jsonify([t.to_dict() for t in tasks])

Claire ajoute les tests:

  def test_search_tasks(client):
      client.post('/tasks', json={'title': 'Acheter du lait'})
      client.post('/tasks', json={'title': 'Aller à la gym'})
      response = client.get('/tasks/search?q=lait')
      assert response.status_code == 200
      tasks = response.get_json()
      assert len(tasks) == 1
      assert 'lait' in tasks[0]['title'].lower()

  def test_search_tasks_case_insensitive(client):
      client.post('/tasks', json={'title': 'Acheter du Lait'})
      response = client.get('/tasks/search?q=lait')
      assert response.status_code == 200
      assert len(response.get_json()) == 1

  def test_search_tasks_too_short(client):
      response = client.get('/tasks/search?q=a')
      assert response.status_code == 400

──────────────────────────────────────────────────────────────────────────────
B.5 - Claire re-lance et partage avec l'équipe via les commentaires SonarQube
──────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  ./analyze.sh

INTERFACE WEB - Claire laisse un commentaire sur l'issue résolue:

  1. Elle va dans Issues > Resolved (ou dans l'historique)
  2. Elle retrouve la vulnérabilité maintenant résolue
  3. Elle clique "Add Comment":
     ┌──────────────────────────────────────────────────────────────┐
     │  Add Comment                                                 │
     │                                                              │
     │  [Remplacé la requête SQL brute par l'ORM SQLAlchemy.       │
     │   L'injection SQL est maintenant impossible via ilike().     │
     │   Ajouté validation minimum 2 caractères.                   │
     │   Tests ajoutés: 3 cas couverts.                            │
     │                                                 ]           │
     │                                                              │
     │  [  Save  ]                                                  │
     └──────────────────────────────────────────────────────────────┘

  Alice et Bob pourront lire ce commentaire dans l'historique.


════════════════════════════════════════════════════════════════════════════════
SCÉNARIO C - Alice fait une revue de code hebdomadaire dans SonarQube
════════════════════════════════════════════════════════════════════════════════

Chaque lundi matin, Alice fait un tour dans SonarQube pour voir l'état global
du projet et assigner des tâches à l'équipe.

──────────────────────────────────────────────────────────────────────────────
C.1 - Alice regarde le dashboard global
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB:

  1. Aller sur: http://sonar.equipe.local:9000
  2. Cliquer sur "TaskManager API"
  3. État du projet après 2 semaines:

  ┌────────────────────────────────────────────────────────────────────┐
  │  TaskManager API                                                   │
  │  Last analysis: Today 09:00  |  main  |  287 lines of code        │
  ├────────────────┬──────────────┬────────────────┬───────────────────┤
  │  Reliability   │  Security    │ Maintainability│                   │
  │  * A          │  * A        │  * B          │                   │
  │  0 Bugs        │  0 Vuln.     │  8 Code Smells │                   │
  ├────────────────┴──────────────┴────────────────┤                   │
  │  Coverage: 79%  [ATTENTION]  (juste sous 80%)           │                   │
  │  Duplications: 2.1%  [OK]                        │                   │
  │                                                │                   │
  │  Quality Gate: [X] FAILED (Coverage < 80%)      │                   │
  └────────────────────────────────────────────────┴───────────────────┘

──────────────────────────────────────────────────────────────────────────────
C.2 - Alice examine les Code Smells et les assigne
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB:

  1. Alice clique sur "8 Code Smells"
  2. Elle voit la liste:

  ┌──────────────────────────────────────────────────────────────────────┐
  │  Issues - Code Smells (8)                                            │
  │  Filters: [Type: Code Smell [BLACK_DOWN-POINTING_TRIANGLE]]  [Status: Open [BLACK_DOWN-POINTING_TRIANGLE]]                    │
  ├────────────────────────────────────────────────────────────────────┤
  │  [NETTOYAGE] MINOR  Rename this variable from "d" to something readable      │
  │  app/utils.py:15  |  Unassigned  |  5min effort                    │
  ├────────────────────────────────────────────────────────────────────┤
  │  [NETTOYAGE] MAJOR  Function "process_all" has 45 lines (>30 recommended)   │
  │  app/routes.py:78  |  Unassigned  |  1h effort                     │
  ├────────────────────────────────────────────────────────────────────┤
  │  [NETTOYAGE] MINOR  Add a docstring to this function                        │
  │  app/models.py:12  |  Unassigned  |  5min effort                   │
  ├────────────────────────────────────────────────────────────────────┤
  │  ... (5 autres)                                                     │
  └──────────────────────────────────────────────────────────────────────┘

  3. Alice clique sur le premier Code Smell pour l'assigner à Bob:
     ┌──────────────────────────────────────────────────────────────┐
     │  [NETTOYAGE] Rename variable "d"                                      │
     │  app/utils.py:15                                             │
     │                                                              │
     │  [Assign to [BLACK_DOWN-POINTING_TRIANGLE]]  bob <- Alice sélectionne Bob                 │
     │                                                              │
     │  [Comment] Alice écrit:                                      │
     │  "Bob, renommer cette variable dans ta prochaine PR.        │
     │   Utilise 'date_created' ou 'deadline' selon contexte."     │
     └──────────────────────────────────────────────────────────────┘

  4. Alice assigne la fonction trop longue à elle-même (pour refactoriser):
     Issue "Function has 45 lines" -> Assign to: admin (Alice)

  5. Alice assigne les docstrings manquants à Claire:
     Issues "Add a docstring" -> Assign to: claire

──────────────────────────────────────────────────────────────────────────────
C.3 - Bob et Claire voient leurs tâches assignées
──────────────────────────────────────────────────────────────────────────────

BOB se connecte à SonarQube le lundi matin:

INTERFACE WEB:

  1. Bob va sur http://sonar.equipe.local:9000
  2. Dans le menu en haut à droite: clic sur son avatar > "My Issues"
     Chemin: [bob [BLACK_DOWN-POINTING_TRIANGLE]] > My Issues

  3. Il voit:
     ┌──────────────────────────────────────────────────────────────────┐
     │  My Issues (1)                                                   │
     │  Assigned to: bob  |  Status: Open                              │
     ├────────────────────────────────────────────────────────────────┤
     │  [NETTOYAGE] MINOR  Rename variable "d" to something readable            │
     │  TaskManager API  |  app/utils.py:15                           │
     │  Comment: "Bob, renommer cette variable..."                     │
     │                                                                  │
     │  [  Open in project  ]                                          │
     └──────────────────────────────────────────────────────────────────┘

  4. Bob clique sur l'issue, voit le code, et planifie la correction.

CLAIRE fait pareil et voit ses issues de docstrings assignées.

──────────────────────────────────────────────────────────────────────────────
C.4 - Alice vérifie l'historique d'évolution du projet
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB:

  1. Alice va sur la page du projet: TaskManager API
  2. Elle clique sur l'onglet "Activity" (ou "Historique"):
     Chemin: Project > Activity

  3. Elle voit une timeline:

  ┌──────────────────────────────────────────────────────────────────┐
  │  Activity                                                        │
  │                                                                  │
  │  [CALENDRIER] Lundi semaine 1:                                             │
  │    Bugs: 0 | Coverage: 82% | Code Smells: 2 | QG: [OK] PASSED    │
  │                                                                  │
  │  [CALENDRIER] Mercredi semaine 1:  (ajout de Bob - filtre)                │
  │    Bugs: 0 | Coverage: 86% | Code Smells: 2 | QG: [OK] PASSED    │
  │                                                                  │
  │  [CALENDRIER] Vendredi semaine 1:  (ajout de Claire - recherche)          │
  │    Bugs: 0 | Coverage: 83% | Code Smells: 4 | QG: [OK] PASSED    │
  │                                                                  │
  │  [CALENDRIER] Lundi semaine 2:  (ajout alice - refactoring)              │
  │    Bugs: 0 | Coverage: 79% | Code Smells: 8 | QG: [X] FAILED    │
  └──────────────────────────────────────────────────────────────────┘

  4. Alice voit que la coverage a baissé à 79% lors de son dernier ajout.
     Elle s'assigne l'issue et ajoute les tests manquants ce matin même.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 4 - INTÉGRATION CONTINUE AVEC GITHUB ACTIONS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

L'équipe veut automatiser les analyses SonarQube à chaque push sur GitHub.
Alice configure cela en 3 étapes.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.1 - Alice configure les secrets GitHub
────────────────────────────────────────────────────────────────────────────────

Pour que GitHub Actions puisse communiquer avec SonarQube, Alice doit ajouter
2 secrets dans le dépôt GitHub.

INTERFACE GITHUB (https://github.com/equipe/taskmanager):

  1. Aller dans: Settings > Secrets and variables > Actions

  2. Cliquer "New repository secret" pour le premier secret:
     ┌──────────────────────────────────────────────────────────────┐
     │  New secret                                                  │
     │                                                              │
     │  Name:   SONAR_TOKEN                                         │
     │  Value:  squ_a4f8e2c1d9b3f7a5e8c2d4f1b6e9a3c7d5f2b8e1     │
     │          (le token du projet créé à l'étape 1.6)            │
     │                                                              │
     │  [  Add secret  ]                                            │
     └──────────────────────────────────────────────────────────────┘

  3. Cliquer "New repository secret" pour le deuxième:
     ┌──────────────────────────────────────────────────────────────┐
     │  New secret                                                  │
     │                                                              │
     │  Name:   SONAR_HOST_URL                                      │
     │  Value:  http://sonar.equipe.local:9000                      │
     │          (ou l'adresse IP publique du serveur SonarQube)     │
     │                                                              │
     │  [  Add secret  ]                                            │
     └──────────────────────────────────────────────────────────────┘

  Résultat:
  ┌─────────────────────────────────────────────────────┐
  │  Actions secrets and variables                      │
  │                                                     │
  │  SONAR_HOST_URL  Updated just now                   │
  │  SONAR_TOKEN     Updated just now                   │
  └─────────────────────────────────────────────────────┘

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.2 - Alice crée le fichier GitHub Actions
────────────────────────────────────────────────────────────────────────────────

  COMMANDES:
  ──────────
  mkdir -p .github/workflows
  nano .github/workflows/sonarqube.yml

  CONTENU COMPLET DU FICHIER:
  ───────────────────────────

  name: SonarQube - Analyse Qualité

  # Déclenché sur:
  on:
    push:
      branches:
        - main
        - develop
        - 'feature/**'     # Toutes les branches feature/...
    pull_request:
      branches:
        - main

  jobs:
    sonarqube-analysis:
      name: Analyse SonarQube
      runs-on: ubuntu-latest

      steps:
        # Étape 1: Récupérer le code
        - name: Checkout code
          uses: actions/checkout@v4
          with:
            fetch-depth: 0    # IMPORTANT: Récupère tout l'historique Git
                              # (nécessaire pour l'analyse de branches SonarQube)

        # Étape 2: Configurer Python
        - name: Setup Python 3.11
          uses: actions/setup-python@v4
          with:
            python-version: '3.11'

        # Étape 3: Cache des dépendances pip (accélère les builds)
        - name: Cache pip packages
          uses: actions/cache@v3
          with:
            path: ~/.cache/pip
            key: ${{ runner.os }}-pip-${{ hashFiles('requirements*.txt') }}
            restore-keys: |
              ${{ runner.os }}-pip-

        # Étape 4: Installer les dépendances
        - name: Install dependencies
          run: |
            python -m pip install --upgrade pip
            pip install -r requirements.txt
            pip install -r requirements-dev.txt

        # Étape 5: Lancer les tests avec couverture
        - name: Run tests with coverage
          run: |
            pytest \
              --cov=app \
              --cov-report=xml \
              --cov-report=term-missing \
              -v
          env:
            TESTING: true

        # Étape 6: Cache SonarQube (accélère les analyses)
        - name: Cache SonarQube scanner
          uses: actions/cache@v3
          with:
            path: ~/.sonar/cache
            key: ${{ runner.os }}-sonar
            restore-keys: ${{ runner.os }}-sonar

        # Étape 7: Lancer l'analyse SonarQube
        - name: SonarQube Scan
          uses: sonarsource/sonarqube-scan-action@master
          env:
            SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
            SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

        # Étape 8: Vérifier la Quality Gate (bloque le build si FAILED)
        - name: Check SonarQube Quality Gate
          uses: sonarsource/sonarqube-quality-gate-action@master
          timeout-minutes: 5
          env:
            SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.3 - Alice commite et teste la pipeline
────────────────────────────────────────────────────────────────────────────────

  COMMANDES:
  ──────────
  git add .github/workflows/sonarqube.yml
  git commit -m "ci: add SonarQube GitHub Actions pipeline"
  git push origin main

INTERFACE GITHUB - Voir l'exécution:

  1. Sur GitHub: Onglet "Actions" du dépôt

  2. Elle voit la pipeline en cours:
     ┌──────────────────────────────────────────────────────────────┐
     │  Actions                                                     │
     │                                                              │
     │  [JAUNE] SonarQube - Analyse Qualité  (en cours...)             │
     │     ci: add SonarQube GitHub Actions pipeline               │
     │     main  ·  1 minute ago                                   │
     └──────────────────────────────────────────────────────────────┘

  3. Après ~3 minutes:
     ┌──────────────────────────────────────────────────────────────┐
     │  [OK] SonarQube - Analyse Qualité  (success)                  │
     │     ci: add SonarQube GitHub Actions pipeline               │
     │     main  ·  3 minutes ago  ·  2m 45s                       │
     └──────────────────────────────────────────────────────────────┘

  4. En cliquant dessus, elle voit le détail de chaque étape:
     [OK] Checkout code                (3s)
     [OK] Setup Python 3.11            (12s)
     [OK] Cache pip packages           (2s)
     [OK] Install dependencies         (25s)
     [OK] Run tests with coverage      (8s)  -> 10 passed, coverage: 86%
     [OK] Cache SonarQube scanner      (3s)
     [OK] SonarQube Scan               (45s) -> ANALYSIS SUCCESSFUL
     [OK] Check SonarQube Quality Gate (5s)  -> PASSED

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.4 - Workflow complet quand Bob pousse une PR
────────────────────────────────────────────────────────────────────────────────

Voici exactement ce qui se passe quand Bob pousse sa branche feature:

  COMMANDES BOB:
  ──────────────
  git push origin feature/filter-tasks-by-status
  # Puis créer une Pull Request sur GitHub

INTERFACE GITHUB - Création de la PR:

  1. GitHub affiche un message:
     "feature/filter-tasks-by-status had recent pushes"
     Bob clique "[Compare & pull request]"

  2. Formulaire de PR:
     ┌──────────────────────────────────────────────────────────────┐
     │  Open a pull request                                         │
     │                                                              │
     │  Title: feat: add filter tasks by status endpoint           │
     │                                                              │
     │  Description:                                               │
     │  ## Changements                                             │
     │  - Ajout route GET /tasks/filter?done=true|false           │
     │  - Gestion paramètre manquant (retourne toutes les tâches) │
     │  - 3 nouveaux tests                                         │
     │  - SonarQube: PASSED localement                            │
     │                                                              │
     │  [  Create pull request  ]                                   │
     └──────────────────────────────────────────────────────────────┘

  3. GitHub lance automatiquement la pipeline SonarQube.
     Au bout de ~3 minutes, dans la PR:

     ┌──────────────────────────────────────────────────────────────┐
     │  Checks                                                      │
     │                                                              │
     │  [OK] SonarQube - Analyse Qualité  — All checks have passed   │
     │                                                              │
     │  Details:                                                    │
     │  [OK] Run tests with coverage: 10 passed, coverage 86%        │
     │  [OK] SonarQube Scan: ANALYSIS SUCCESSFUL                     │
     │  [OK] Quality Gate: PASSED                                     │
     │                                                              │
     │  This branch has no conflicts with the base branch          │
     │                                                              │
     │  [  Merge pull request  ]  <- Alice peut maintenant merger  │
     └──────────────────────────────────────────────────────────────┘

  Si la Quality Gate avait ÉCHOUÉ:
     ┌──────────────────────────────────────────────────────────────┐
     │  Checks                                                      │
     │                                                              │
     │  [X] SonarQube - Analyse Qualité  — 1 check failed           │
     │                                                              │
     │  [X] Quality Gate: FAILED                                     │
     │     New Coverage 72% < 80% required                         │
     │                                                              │
     │  Required check has failed                                   │
     │  [ Merge pull request ] <- GRISÉ, impossible de merger!     │
     └──────────────────────────────────────────────────────────────┘

     Bob devrait retourner coder, ajouter des tests, et re-pousser.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 5 - BONNES PRATIQUES DE L'ÉQUIPE (RÈGLES D'OR)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Ces règles ont été définies par Alice en accord avec l'équipe.

────────────────────────────────────────────────────────────────────────────────
RÈGLE 1 - Analyser en local AVANT de pousser
────────────────────────────────────────────────────────────────────────────────

Chaque développeur doit toujours lancer ./analyze.sh avant son git push.
Cela évite de "polluer" la pipeline CI/CD avec des erreurs évitables.

  # [OK] Workflow correct:
  ./analyze.sh          # 1. Analyser en local
  git add .             # 2. Seulement si PASSED
  git commit -m "..."
  git push

  # [X] Workflow à éviter:
  git add .
  git commit -m "..."
  git push              # Et découvrir les erreurs dans GitHub Actions...

────────────────────────────────────────────────────────────────────────────────
RÈGLE 2 - Tester ce qu'on écrit (règle des 80%)
────────────────────────────────────────────────────────────────────────────────

Chaque nouvelle fonction DOIT avoir au moins un test.
La couverture de code ne doit jamais descendre sous 80%.

  # Pour voir quelles lignes ne sont pas couvertes:
  pytest --cov=app --cov-report=term-missing

  # Exemple de sortie:
  # Name           Stmts  Miss  Cover   Missing
  # app/routes.py    45     8    82%   15, 28, 52-57

  # Les lignes 15, 28, 52-57 ne sont pas couvertes -> Ajouter des tests!

────────────────────────────────────────────────────────────────────────────────
RÈGLE 3 - Les bugs et vulnérabilités BLOQUEUR/CRITIQUE = correction immédiate
────────────────────────────────────────────────────────────────────────────────

  BLOCKER    -> Correction avant fin de journée
  CRITICAL   -> Correction dans les 48h
  MAJOR      -> Correction dans la semaine
  MINOR      -> Planifier dans le backlog
  INFO       -> À traiter lors du prochain refactoring

────────────────────────────────────────────────────────────────────────────────
RÈGLE 4 - Ne jamais utiliser NOSONAR pour contourner une règle
────────────────────────────────────────────────────────────────────────────────

Le commentaire # NOSONAR désactive l'analyse sur une ligne.
Il ne doit être utilisé QUE pour de vrais faux positifs, avec justification.

  # [OK] Utilisation acceptable (vrai faux positif documenté):
  password = generate_token()  # NOSONAR: not a password, it's a CSRF token

  # [X] Utilisation interdite (contournement paresseux):
  result = division(a, b)  # NOSONAR

────────────────────────────────────────────────────────────────────────────────
RÈGLE 5 - Réunion qualité hebdomadaire (15 minutes chaque lundi)
────────────────────────────────────────────────────────────────────────────────

  Agenda de la réunion:
  1. Alice partage le dashboard SonarQube sur son écran
  2. Review des métriques de la semaine (5 min):
     - Coverage stable/en hausse?
     - Nouveaux bugs ou vulnérabilités?
     - Dette technique: diminue-t-elle?
  3. Attribution des issues ouvertes (5 min)
  4. Objectifs de la semaine (5 min):
     Exemple: "Cette semaine, on vise coverage 85%"


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 6 - RÉSUMÉ VISUEL - QUI FAIT QUOI ET QUAND
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ALICE (Lead / Admin)
  ────────────────────
  [OK] Configure SonarQube et le projet (une fois)
  [OK] Crée les comptes Bob et Claire (une fois)
  [OK] Définit la Quality Gate (une fois, révisée si besoin)
  [OK] Configure GitHub Actions (une fois)
  [OK] Fait la revue hebdomadaire
  [OK] Assigne les issues à l'équipe
  [OK] Valide et merge les PRs si Quality Gate PASSED

  BOB et CLAIRE (Développeurs)
  ────────────────────────────
  [OK] Lancent ./analyze.sh avant chaque git push
  [OK] Corrigent les issues qui leur sont assignées
  [OK] Ajoutent des tests pour tout nouveau code
  [OK] Laissent des commentaires dans SonarQube sur les corrections
  [OK] Ne pousent jamais du code avec Quality Gate FAILED


  WORKFLOW COMPLET D'UNE FEATURE:
  ────────────────────────────────

  Bob                    SonarQube              GitHub Actions       Alice
  ───                    ─────────              ──────────────       ─────
  git checkout -b feat
  [écrire code]
  [écrire tests]
  ./analyze.sh ──────-> analyse locale ──-> [voir dashboard]
  [corriger si FAILED]
  git push ────────────────────────────────────────────-> [pipeline démarre]
                                           [tests run]
                                           [sonar scan]
                                           [QG check] ────────────-> [voir PR]
                                                                     [merger si [OK]]
                                                                     [demander correction si [X]]


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 7 - COMMANDES DE RÉFÉRENCE RAPIDE (CHEATSHEET ÉQUIPE)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  COMMANDES DU QUOTIDIEN:
  ───────────────────────

  # Lancer tous les tests + couverture + SonarQube (la commande principale)
  ./analyze.sh

  # Lancer seulement les tests (sans SonarQube):
  pytest -v

  # Lancer tests avec rapport de couverture (sans SonarQube):
  pytest --cov=app --cov-report=term-missing

  # Voir quelle ligne spécifique n'est pas couverte:
  pytest --cov=app --cov-report=html
  open htmlcov/index.html     # Ouvre le rapport HTML dans le navigateur

  # Lancer un seul fichier de test:
  pytest tests/test_routes.py -v

  # Lancer un seul test spécifique:
  pytest tests/test_routes.py::test_create_task -v

  # Analyser manuellement (sans le script analyze.sh):
  source .env
  pytest --cov=app --cov-report=xml
  sonar-scanner -Dsonar.token=$SONAR_TOKEN -Dsonar.host.url=$SONAR_HOST_URL

  URLS UTILES:
  ────────────

  # Dashboard principal:
  http://sonar.equipe.local:9000/dashboard?id=taskmanager-api

  # Liste de toutes mes issues assignées:
  http://sonar.equipe.local:9000/issues?assignees=bob

  # Issues ouvertes du projet:
  http://sonar.equipe.local:9000/project/issues?id=taskmanager-api&resolved=false

  # Activité / historique:
  http://sonar.equipe.local:9000/project/activity?id=taskmanager-api

  # Règles (pour comprendre une issue):
  http://sonar.equipe.local:9000/coding_rules

  ALIAS PRATIQUES (à ajouter dans ~/.bashrc ou ~/.zshrc):
  ────────────────────────────────────────────────────────

  alias sq='./analyze.sh'
  alias sqtest='pytest --cov=app --cov-report=term-missing'
  alias sqopen='open http://sonar.equipe.local:9000/dashboard?id=taskmanager-api'

  # Utilisation:
  sq        # Analyse complète
  sqtest    # Tests seulement
  sqopen    # Ouvre le dashboard dans le navigateur

================================================================================
FIN DE LA SECTION - SONARQUBE EN ÉQUIPE DE 3 DÉVELOPPEURS (FLASK)
================================================================================