<?php
/**
 * Tests unitaires pour includes/security.php
 * (et les validateurs d'URL de includes/config.php)
 */

// --- validateVideoId --------------------------------------------------------

assertEquals(
    '9c5de94d-8e1a-4b3c-9d2e-1234567890ab',
    validateVideoId('9c5de94d-8e1a-4b3c-9d2e-1234567890ab'),
    'validateVideoId accepte un UUID valide'
);
assertEquals(
    '9C5DE94D-8E1A-4B3C-9D2E-1234567890AB',
    validateVideoId('9C5DE94D-8E1A-4B3C-9D2E-1234567890AB'),
    'validateVideoId accepte un UUID en majuscules'
);
assertEquals(
    '9c5de94d-8e1a-4b3c-9d2e-1234567890ab',
    validateVideoId('  9c5de94d-8e1a-4b3c-9d2e-1234567890ab  '),
    'validateVideoId nettoie les espaces autour de l\'UUID'
);
assertFalse(validateVideoId(''), 'validateVideoId refuse une chaîne vide');
assertFalse(validateVideoId('pas-un-uuid'), 'validateVideoId refuse une chaîne quelconque');
assertFalse(
    validateVideoId('9c5de94d-8e1a-4b3c-1234567890ab'),
    'validateVideoId refuse un UUID tronqué'
);

// --- validateSearchQuery ----------------------------------------------------

assertEquals('indépendance', validateSearchQuery('indépendance'), 'validateSearchQuery conserve les accents');
assertEquals(
    'script',
    validateSearchQuery('<script>'),
    'validateSearchQuery supprime les chevrons'
);
assertEquals(
    'recherche btest/b',
    validateSearchQuery('recherche <b>test</b>'),
    'validateSearchQuery supprime les caractères dangereux <>"\''
);
assertEquals('terme', validateSearchQuery('  terme  '), 'validateSearchQuery nettoie les espaces');
assertFalse(validateSearchQuery(''), 'validateSearchQuery refuse une chaîne vide');
assertFalse(
    validateSearchQuery(str_repeat('a', 201)),
    'validateSearchQuery refuse plus de 200 caractères'
);
assertEquals(
    str_repeat('a', 200),
    validateSearchQuery(str_repeat('a', 200)),
    'validateSearchQuery accepte exactement 200 caractères'
);

// --- validateCategoryId -----------------------------------------------------

assertEquals(1, validateCategoryId(1), 'validateCategoryId accepte la borne basse 1');
assertEquals(20, validateCategoryId(20), 'validateCategoryId accepte la borne haute 20');
assertEquals(5, validateCategoryId('5'), 'validateCategoryId convertit une chaîne numérique');
assertFalse(validateCategoryId(0), 'validateCategoryId refuse 0');
assertFalse(validateCategoryId(21), 'validateCategoryId refuse 21');
assertFalse(validateCategoryId(-3), 'validateCategoryId refuse un nombre négatif');
assertFalse(validateCategoryId('abc'), 'validateCategoryId refuse une chaîne non numérique');

// --- validatePageNumber -----------------------------------------------------

assertEquals(1, validatePageNumber(0), 'validatePageNumber remonte 0 à 1');
assertEquals(1, validatePageNumber(-5), 'validatePageNumber remonte un négatif à 1');
assertEquals(3, validatePageNumber(3), 'validatePageNumber conserve une page valide');
assertEquals(2, validatePageNumber('2'), 'validatePageNumber convertit une chaîne numérique');
assertEquals(1, validatePageNumber('abc'), 'validatePageNumber ramène une chaîne non numérique à 1');

// --- generateCSRFToken / validateCSRFToken ----------------------------------

$token = generateCSRFToken();
assertContains(':', $token, 'generateCSRFToken produit le format "timestamp:hash"');

[$timestamp, $hash] = explode(':', $token, 2);
assertTrue(ctype_digit($timestamp), 'generateCSRFToken : la partie timestamp est numérique');
assertEquals(
    hash_hmac('sha256', $timestamp, CSRF_SECRET),
    $hash,
    'generateCSRFToken : le hash est un HMAC-SHA256 du timestamp avec CSRF_SECRET'
);
assertTrue(validateCSRFToken($token), 'validateCSRFToken accepte un token fraîchement généré');

assertFalse(validateCSRFToken(''), 'validateCSRFToken refuse une chaîne vide');
assertFalse(validateCSRFToken('sans-deux-points'), 'validateCSRFToken refuse un token sans séparateur');
assertFalse(validateCSRFToken('abc:' . $hash), 'validateCSRFToken refuse un timestamp non numérique');
assertFalse(
    validateCSRFToken($timestamp . ':' . hash_hmac('sha256', $timestamp, 'autre-secret')),
    'validateCSRFToken refuse un hash signé avec un autre secret'
);
assertFalse(
    validateCSRFToken($timestamp . ':0' . $hash),
    'validateCSRFToken refuse un hash modifié'
);

// Token expiré : horodatage d'il y a 2 heures (limite : 1 heure)
$oldTimestamp = (string) (time() - 7200);
$oldToken = $oldTimestamp . ':' . hash_hmac('sha256', $oldTimestamp, CSRF_SECRET);
assertFalse(validateCSRFToken($oldToken), 'validateCSRFToken refuse un token expiré (> 1 heure)');

// Timestamp trop loin dans le futur (l'écart absolu est également plafonné)
$futureTimestamp = (string) (time() + 7200);
$futureToken = $futureTimestamp . ':' . hash_hmac('sha256', $futureTimestamp, CSRF_SECRET);
assertFalse(validateCSRFToken($futureToken), 'validateCSRFToken refuse un timestamp trop futur');

// --- isValidPeerTubeUrl (includes/config.php) -------------------------------

assertTrue(isValidPeerTubeUrl('https://peertube.example.com'), 'isValidPeerTubeUrl accepte une URL HTTPS publique');
assertTrue(isValidPeerTubeUrl('http://peertube.example.com'), 'isValidPeerTubeUrl accepte HTTP (développement)');
assertTrue(isValidPeerTubeUrl('https://8.8.8.8'), 'isValidPeerTubeUrl accepte une IP publique');
assertFalse(isValidPeerTubeUrl('ftp://example.com'), 'isValidPeerTubeUrl refuse un schéma non HTTP(S)');
assertFalse(isValidPeerTubeUrl('pas-une-url'), 'isValidPeerTubeUrl refuse une chaîne mal formée');
assertFalse(isValidPeerTubeUrl('https://localhost'), 'isValidPeerTubeUrl refuse localhost');
assertFalse(isValidPeerTubeUrl('http://127.0.0.1'), 'isValidPeerTubeUrl refuse 127.0.0.1');
assertFalse(isValidPeerTubeUrl('http://192.168.1.1'), 'isValidPeerTubeUrl refuse une IP privée');
assertFalse(isValidPeerTubeUrl('http://10.0.0.5'), 'isValidPeerTubeUrl refuse une IP privée (10.x)');
assertFalse(
    isValidPeerTubeUrl('https://metadata.google.internal'),
    'isValidPeerTubeUrl refuse metadata.google.internal'
);

// --- isValidApiEndpoint (includes/config.php) -------------------------------

assertTrue(isValidApiEndpoint('videos'), 'isValidApiEndpoint accepte "videos"');
assertTrue(isValidApiEndpoint('videos/categories'), 'isValidApiEndpoint accepte "videos/categories"');
assertTrue(isValidApiEndpoint('search/videos'), 'isValidApiEndpoint accepte "search/videos"');
assertTrue(isValidApiEndpoint('accounts'), 'isValidApiEndpoint accepte "accounts"');
assertTrue(
    isValidApiEndpoint('videos/9c5de94d-8e1a-4b3c-9d2e-1234567890ab'),
    'isValidApiEndpoint accepte un endpoint vidéo dynamique'
);
assertTrue(
    isValidApiEndpoint('videos/abc/comment-threads'),
    'isValidApiEndpoint accepte les fils de commentaires'
);
assertTrue(
    isValidApiEndpoint('accounts/annu_kute_ced/videos'),
    'isValidApiEndpoint accepte les vidéos d\'un compte'
);
assertFalse(isValidApiEndpoint('../config'), 'isValidApiEndpoint refuse le path traversal');
assertFalse(isValidApiEndpoint('videos/../x'), 'isValidApiEndpoint refuse ".." dans le chemin');
assertFalse(isValidApiEndpoint('videos//categories'), 'isValidApiEndpoint refuse un double slash');
assertFalse(isValidApiEndpoint('wp-admin'), 'isValidApiEndpoint refuse un endpoint hors liste blanche');
assertFalse(isValidApiEndpoint('videos.json'), 'isValidApiEndpoint refuse les caractères non autorisés (.)');
assertFalse(isValidApiEndpoint('videos?count=1'), 'isValidApiEndpoint refuse une query string');
assertFalse(isValidApiEndpoint(''), 'isValidApiEndpoint refuse une chaîne vide');

// --- getCspNonce ------------------------------------------------------------

$nonce = getCspNonce();
assertTrue(is_string($nonce) && $nonce !== '', 'getCspNonce retourne une chaîne non vide');
assertEquals(16, strlen(base64_decode($nonce, true)), 'getCspNonce est un base64 de 16 octets aléatoires');
assertEquals($nonce, getCspNonce(), 'getCspNonce retourne le même nonce durant toute la requête');
