Ajoute token_hash + token_issued_at à enrolled_agents via ALTER TABLE idempotent (_init_db). Colonnes inertes : aucun branchement auth, runtime inchangé (tests WP-B verts). Base du token par poste (WP-C, cf DETTE-015). TDD: tests/unit/test_wpc_migration.py (présence, idempotence, préservation des données d'une base existante). 3 tests + non-régression WP-B = 9 passed. refs DETTE-015 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
93 lines
3.0 KiB
Python
93 lines
3.0 KiB
Python
"""WP-C Patch 1 — migration additive idempotente des colonnes « token par poste ».
|
|
|
|
Ajoute `token_hash` et `token_issued_at` à la table `enrolled_agents`, sans
|
|
casser les bases existantes ni perdre de données. Comportement runtime inchangé :
|
|
les colonnes restent inertes tant que l'auth par poste n'est pas branchée
|
|
(patchs WP-C ultérieurs). Voir DETTE-015 / PLAN-WPC-TDD-EXECUTABLE.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
|
|
from agent_v0.server_v1.agent_registry import AgentRegistry
|
|
|
|
|
|
def _columns(db_path) -> set[str]:
|
|
conn = sqlite3.connect(str(db_path))
|
|
try:
|
|
rows = conn.execute("PRAGMA table_info(enrolled_agents)").fetchall()
|
|
return {r[1] for r in rows}
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_token_columns_present_after_init(tmp_path):
|
|
"""Une base neuve doit contenir les colonnes token dès l'init."""
|
|
db = tmp_path / "fleet.db"
|
|
AgentRegistry(db_path=db)
|
|
cols = _columns(db)
|
|
assert "token_hash" in cols
|
|
assert "token_issued_at" in cols
|
|
|
|
|
|
def test_token_columns_idempotent(tmp_path):
|
|
"""Ré-initialiser plusieurs fois ne doit jamais lever (migration idempotente)."""
|
|
db = tmp_path / "fleet.db"
|
|
AgentRegistry(db_path=db)
|
|
AgentRegistry(db_path=db)
|
|
AgentRegistry(db_path=db)
|
|
cols = _columns(db)
|
|
assert "token_hash" in cols
|
|
assert "token_issued_at" in cols
|
|
|
|
|
|
def test_migration_preserves_existing_rows(tmp_path):
|
|
"""Une base ancienne (sans les colonnes) est migrée sans perte de données."""
|
|
db = tmp_path / "fleet.db"
|
|
# Ancien schéma, sans les colonnes token, avec une ligne existante.
|
|
conn = sqlite3.connect(str(db))
|
|
conn.execute(
|
|
"""
|
|
CREATE TABLE enrolled_agents (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
machine_id TEXT NOT NULL UNIQUE,
|
|
user_name TEXT,
|
|
user_email TEXT,
|
|
user_id TEXT,
|
|
hostname TEXT,
|
|
os_info TEXT,
|
|
version TEXT,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
enrolled_at TEXT NOT NULL,
|
|
last_seen_at TEXT,
|
|
uninstalled_at TEXT,
|
|
uninstall_reason TEXT
|
|
)
|
|
"""
|
|
)
|
|
conn.execute(
|
|
"INSERT INTO enrolled_agents (machine_id, status, enrolled_at) "
|
|
"VALUES ('PC-LEGACY', 'active', '2026-01-01T00:00:00+00:00')"
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
# Le démarrage du registry doit migrer la base existante.
|
|
AgentRegistry(db_path=db)
|
|
|
|
cols = _columns(db)
|
|
assert "token_hash" in cols
|
|
assert "token_issued_at" in cols
|
|
|
|
conn = sqlite3.connect(str(db))
|
|
try:
|
|
row = conn.execute(
|
|
"SELECT machine_id, token_hash FROM enrolled_agents "
|
|
"WHERE machine_id = 'PC-LEGACY'"
|
|
).fetchone()
|
|
finally:
|
|
conn.close()
|
|
assert row is not None
|
|
assert row[0] == "PC-LEGACY"
|
|
assert row[1] is None # colonne ajoutée, NULL par défaut
|