Files
xamxam/app/tests/Unit/ShareLinkTest.php

235 lines
10 KiB
PHP

<?php
/**
* ShareLink Unit Test
*
* Tests pure-logic methods that require no HTTP context:
* - generateSlug() — format, uniqueness, entropy
* - validateLink() — all branches: not_found, archived, disabled, expired, needs_password, valid
* - verifyPassword() — correct / wrong / no-password links
*/
putenv('DB_ENV=test');
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 2));
}
if (!defined('STORAGE_ROOT')) {
define('STORAGE_ROOT', APP_ROOT . '/storage');
}
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/ShareLink.php';
// ── Helpers ───────────────────────────────────────────────────────────────────
function slAssert(bool $cond, string $label): void
{
if ($cond) {
echo "$label\n";
} else {
throw new RuntimeException("FAIL: $label");
}
}
function slAssertEq(mixed $expected, mixed $actual, string $label): void
{
if ($expected === $actual) {
echo "$label\n";
} else {
$e = var_export($expected, true);
$a = var_export($actual, true);
throw new RuntimeException("FAIL $label\n expected: $e\n actual: $a");
}
}
// ── Setup ─────────────────────────────────────────────────────────────────────
echo "ShareLink Unit Test\n";
echo "===================\n\n";
$db = Database::getInstance();
$model = new ShareLink($db);
// We need a dummy admin user id — just use 1 (or any int; share_links.created_by is not FK-checked)
$adminId = 1;
$createdIds = [];
try {
// =========================================================================
// TEST 1: generateSlug — format YYYYMMDD-XXXXXXXX
// =========================================================================
echo "Test 1: generateSlug — format\n";
$slug = ShareLink::generateSlug();
slAssert(
(bool) preg_match('/^\d{8}-[A-Z2-7]{8}$/', $slug),
"slug matches YYYYMMDD-[BASE32]{8}: $slug"
);
$year = (int) substr($slug, 0, 4);
$month = (int) substr($slug, 4, 2);
$day = (int) substr($slug, 6, 2);
slAssert($year >= 2020 && $year <= 2100, 'year in plausible range');
slAssert($month >= 1 && $month <= 12, 'month in range');
slAssert($day >= 1 && $day <= 31, 'day in range');
echo "\n";
// =========================================================================
// TEST 2: generateSlug — two calls produce different slugs
// =========================================================================
echo "Test 2: generateSlug — uniqueness\n";
$slugs = [];
for ($i = 0; $i < 20; $i++) {
$slugs[] = ShareLink::generateSlug();
}
slAssertEq(count($slugs), count(array_unique($slugs)), '20 consecutive slugs are all unique');
echo "\n";
// =========================================================================
// TEST 3: validateLink — not_found on missing slug
// =========================================================================
echo "Test 3: validateLink — not_found on missing slug\n";
$result = $model->validateLink('NONEXISTENT-SLUG');
slAssertEq(false, $result['valid'], 'valid=false');
slAssertEq('not_found', $result['reason'], 'reason=not_found');
$result = $model->validateLink(null);
slAssertEq(false, $result['valid'], 'null slug: valid=false');
slAssertEq('not_found', $result['reason'], 'null slug: reason=not_found');
$result = $model->validateLink('');
slAssertEq(false, $result['valid'], 'empty slug: valid=false');
slAssertEq('not_found', $result['reason'], 'empty slug: reason=not_found');
echo "\n";
// =========================================================================
// TEST 4: validateLink — valid active link with no password
// =========================================================================
echo "Test 4: validateLink — valid active link\n";
$link = $model->create($adminId, null, null);
$createdIds[] = $link['id'];
$result = $model->validateLink($link['slug']);
slAssertEq(true, $result['valid'], 'valid=true');
slAssert(isset($result['link']), 'link row returned');
echo "\n";
// =========================================================================
// TEST 5: validateLink — disabled link
// =========================================================================
echo "Test 5: validateLink — disabled link\n";
$model->toggleActive($link['id']); // deactivate
$result = $model->validateLink($link['slug']);
slAssertEq(false, $result['valid'], 'valid=false after disable');
slAssertEq('disabled', $result['reason'], 'reason=disabled');
$model->toggleActive($link['id']); // restore
echo "\n";
// =========================================================================
// TEST 6: validateLink — archived link
// =========================================================================
echo "Test 6: validateLink — archived link\n";
$archivedLink = $model->create($adminId, null, null);
$createdIds[] = $archivedLink['id'];
$model->archive($archivedLink['id']);
$result = $model->validateLink($archivedLink['slug']);
slAssertEq(false, $result['valid'], 'valid=false for archived');
slAssertEq('archived', $result['reason'], 'reason=archived');
echo "\n";
// =========================================================================
// TEST 7: validateLink — expired link
// =========================================================================
echo "Test 7: validateLink — expired link\n";
$pastDate = date('Y-m-d H:i:s', strtotime('-1 day'));
$expiredLink = $model->create($adminId, null, $pastDate);
$createdIds[] = $expiredLink['id'];
$result = $model->validateLink($expiredLink['slug']);
slAssertEq(false, $result['valid'], 'valid=false for expired');
slAssertEq('expired', $result['reason'], 'reason=expired');
echo "\n";
// =========================================================================
// TEST 8: validateLink — not expired (future date)
// =========================================================================
echo "Test 8: validateLink — future expiry is still valid\n";
$futureDate = date('Y-m-d H:i:s', strtotime('+30 days'));
$futureLink = $model->create($adminId, null, $futureDate);
$createdIds[] = $futureLink['id'];
$result = $model->validateLink($futureLink['slug']);
slAssertEq(true, $result['valid'], 'valid=true for future expiry');
echo "\n";
// =========================================================================
// TEST 9: validateLink — needs_password when password is set
// =========================================================================
echo "Test 9: validateLink — needs_password\n";
$pwLink = $model->create($adminId, 'secret123', null);
$createdIds[] = $pwLink['id'];
$result = $model->validateLink($pwLink['slug']);
slAssertEq(false, $result['valid'], 'valid=false (needs password)');
slAssertEq('needs_password', $result['reason'], 'reason=needs_password');
slAssert(isset($result['link']), 'link row returned even when password needed');
echo "\n";
// =========================================================================
// TEST 10: verifyPassword — correct password
// =========================================================================
echo "Test 10: verifyPassword — correct password\n";
$pwLinkRow = $model->findBySlug($pwLink['slug']);
slAssertEq(true, $model->verifyPassword($pwLinkRow, 'secret123'), 'correct password accepted');
slAssertEq(false, $model->verifyPassword($pwLinkRow, 'wrongpass'), 'wrong password rejected');
slAssertEq(false, $model->verifyPassword($pwLinkRow, ''), 'empty password rejected');
echo "\n";
// =========================================================================
// TEST 11: verifyPassword — link with no password always passes
// =========================================================================
echo "Test 11: verifyPassword — no password set always returns true\n";
$noPwRow = $model->findBySlug($link['slug']);
slAssertEq(true, $model->verifyPassword($noPwRow, ''), 'no-pw link: empty string passes');
slAssertEq(true, $model->verifyPassword($noPwRow, 'anything'), 'no-pw link: any string passes');
echo "\n";
// =========================================================================
// TEST 12: incrementUsage — counter goes up
// =========================================================================
echo "Test 12: incrementUsage — counter increments\n";
$fresh = $model->findById($link['id']);
$before = (int)$fresh['usage_count'];
$model->incrementUsage($link['id']);
$model->incrementUsage($link['id']);
$after = (int)($model->findById($link['id'])['usage_count'] ?? 0);
slAssertEq($before + 2, $after, 'usage_count incremented by 2');
echo "\n";
// =========================================================================
// TEST 13: objet_restriction is stored and returned
// =========================================================================
echo "Test 13: objet_restriction stored correctly\n";
$restrictedLink = $model->create($adminId, null, null, 'tfe');
$createdIds[] = $restrictedLink['id'];
slAssertEq('tfe', $restrictedLink['objet_restriction'], 'objet_restriction=tfe stored');
$anyLink = $model->create($adminId, null, null, 'invalid_value');
$createdIds[] = $anyLink['id'];
slAssertEq(null, $anyLink['objet_restriction'], 'invalid objet_restriction stored as null');
echo "\n";
echo "✅ All ShareLink tests passed!\n";
$result = true;
} catch (Exception $e) {
echo '❌ FAIL: ' . $e->getMessage() . "\n";
$result = false;
} finally {
$pdo = $db->getConnection();
foreach ($createdIds as $id) {
try {
$pdo->prepare('DELETE FROM share_links WHERE id = ?')->execute([$id]);
} catch (Exception $e) { /* ignore */ }
}
}
return $result ?? false;