mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-11-09 11:10:08 +01:00
test: add characterization tests for file-access token lifecycle
This commit is contained in:
1 parent
abb735253e
commit
3f1dcf5d43
2 files changed
+152
-1
No files matched your search
@@ -1 +1 @@
|
||||
{"version":2,"defects":{"CryptoTest::testEncryptDecryptEmptyString":7,"CryptoTest::testDecryptWithTamperedCiphertextReturnsEmpty":7,"EmailObfuscatorTest::testEmailTextReplacesBareEmail":7,"SystemControllerHelpersTest::testHumanBytesOneMB":7,"SystemControllerHelpersTest::testHumanBytesOneGB":7,"CryptoTest::testEncryptEmptyStringProducesCiphertext":7,"DatabaseExtendedTest::testFindOrCreateAuthorCreatesNew":7,"DatabaseExtendedTest::testFindOrCreateAuthorIdempotent":7,"DatabaseExtendedTest::testFindOrCreateAuthorWithEmail":7,"DatabaseExtendedTest::testFindOrCreateAuthorRejectsCSVArtefacts":7,"DatabaseExtendedTest::testDeduplicateLanguagesMergesCaseInsensitiveDupes":7,"RateLimitExtendedTest::testGetRemainingDecrements":7,"RateLimitExtendedTest::testGetRemainingAtLimit":7,"RateLimitExtendedTest::testGetRemainingReturnsZeroAfterExhaustion":7,"RateLimitExtendedTest::testGetResetTimePositiveAfterHits":7,"ThesisCreateValidationTest::testDuplicateTagsAreDeduplicated":8,"ThesisEditValidationTest::testLoadReturnsDataForKnownId":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForHexId":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForPeertubeVideo":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForPeertubeAudio":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForMixedIds":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForHexInCoverQueue":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForPeertubeInCoverQueue":8,"PureLogicTest::testHasFilePondQueueDataReturnsFalseForNumericIdsOnly":8,"PureLogicTest::testHasFilePondQueueDataReturnsFalseForEmptyInput":8,"PureLogicTest::testHasFilePondQueueDataReturnsFalseForEmptyStrings":8,"PureLogicTest::testHasFilePondQueueDataHandlesScalarNotArray":8},"times":{"CryptoTest::testEncryptDecryptRoundTrip":0,"CryptoTest::testEncryptDecryptWithUnicode":0,"CryptoTest::testEncryptDecryptMultiline":0,"CryptoTest::testDifferentPlaintextsProduceDifferentCiphertexts":0,"CryptoTest::testSamePlaintextProducesDifferentCiphertexts":0,"CryptoTest::testIsEncryptedRecognizesEncryptedValue":0,"CryptoTest::testIsEncryptedRejectsPlaintext":0,"CryptoTest::testIsEncryptedReturnsFalseForEmptyString":0,"CryptoTest::testIsEncryptedRejectsInvalidBase64":0,"CryptoTest::testEncryptDecryptEmptyString":0.008,"CryptoTest::testDecryptEmptyStringReturnsEmpty":0,"CryptoTest::testDecryptInvalidBase64ReturnsInputGracefully":0,"CryptoTest::testDecryptTooShortBlobReturnsInputGracefully":0,"CryptoTest::testDecryptWithTamperedCiphertextReturnsEmpty":0,"CryptoTest::testDecryptValidBlobTamperedTagReturnsEmpty":0,"EmailObfuscatorTest::testEncodeContainsNoAtSign":0,"EmailObfuscatorTest::testEncodeOutputIsNumericEntities":0,"EmailObfuscatorTest::testEmailReturnsObfuscatedAddress":0,"EmailObfuscatorTest::testMailtoBuildsCorrectHrefStructure":0,"EmailObfuscatorTest::testEmailTextReplacesBareEmail":0,"EmailObfuscatorTest::testEmailTextReplacesMultipleEmails":0,"EmailObfuscatorTest::testMailtoInTextReplacesMailtoLinks":0,"EmailObfuscatorTest::testObfuscateHtmlReplacesAnchorTag":0,"EmailObfuscatorTest::testObfuscateHtmlKeepsNonMailtoLinksUnchanged":0,"EmailObfuscatorTest::testObfuscateHtmlPreservesCustomLinkText":0,"EmailObfuscatorTest::testEmptyStringReturnsEmpty":0,"EmailObfuscatorTest::testStringWithNoEmailsIsUnchanged":0,"EmailObfuscatorTest::testAlreadyObfuscatedContentIsNotDoubleEncoded":0,"EmailObfuscatorTest::testMultipleEmailsInOneString":0,"EmailObfuscatorTest::testEmailWithPlusSign":0,"StudentEmailTest::testBuildHtmlReturnsNonEmptyString":0,"StudentEmailTest::testBuildHtmlContainsKeyFields":0,"StudentEmailTest::testBuildHtmlEscapesSpecialCharacters":0,"StudentEmailTest::testBuildHtmlHandlesMissingOptionalFields":0,"StudentEmailTest::testBuildHtmlHandlesNullFieldsGracefully":0,"StudentEmailTest::testBuildHtmlHandlesEmptyArray":0,"StudentEmailTest::testBuildHtmlContainsLabelFields":0,"SystemControllerHelpersTest::testHumanBytesZero":0,"SystemControllerHelpersTest::testHumanBytesBelowOneKB":0,"SystemControllerHelpersTest::testHumanBytesOneKB":0,"SystemControllerHelpersTest::testHumanBytesOneMB":0,"SystemControllerHelpersTest::testHumanBytesOneGB":0,"SystemControllerHelpersTest::testHumanBytes1523MB":0,"SystemControllerHelpersTest::testHumanBytes2500GB":0,"SystemControllerHelpersTest::testDiskColorBelowWarning":0,"SystemControllerHelpersTest::testDiskColorWarning":0,"SystemControllerHelpersTest::testDiskColorCritical":0,"SystemControllerHelpersTest::testLogLineClassCrit":0,"SystemControllerHelpersTest::testLogLineClassError":0,"SystemControllerHelpersTest::testLogLineClassWarn":0,"SystemControllerHelpersTest::testLogLineClassNotice":0,"SystemControllerHelpersTest::testLogLineClassHttp500":0,"SystemControllerHelpersTest::testLogLineClassHttp300":0,"SystemControllerHelpersTest::testLogLineClassDefault":0,"SystemControllerHelpersTest::testNginxLineClassComment":0.003,"SystemControllerHelpersTest::testNginxLineClassBlock":0.004,"SystemControllerHelpersTest::testNginxLineClassDirective":0.003,"SystemCoLine truncated
|
||||
{"version":2,"defects":{"CryptoTest::testEncryptDecryptEmptyString":7,"CryptoTest::testDecryptWithTamperedCiphertextReturnsEmpty":7,"EmailObfuscatorTest::testEmailTextReplacesBareEmail":7,"SystemControllerHelpersTest::testHumanBytesOneMB":7,"SystemControllerHelpersTest::testHumanBytesOneGB":7,"CryptoTest::testEncryptEmptyStringProducesCiphertext":7,"DatabaseExtendedTest::testFindOrCreateAuthorCreatesNew":7,"DatabaseExtendedTest::testFindOrCreateAuthorIdempotent":7,"DatabaseExtendedTest::testFindOrCreateAuthorWithEmail":7,"DatabaseExtendedTest::testFindOrCreateAuthorRejectsCSVArtefacts":7,"DatabaseExtendedTest::testDeduplicateLanguagesMergesCaseInsensitiveDupes":7,"RateLimitExtendedTest::testGetRemainingDecrements":7,"RateLimitExtendedTest::testGetRemainingAtLimit":7,"RateLimitExtendedTest::testGetRemainingReturnsZeroAfterExhaustion":7,"RateLimitExtendedTest::testGetResetTimePositiveAfterHits":7,"ThesisCreateValidationTest::testDuplicateTagsAreDeduplicated":8,"ThesisEditValidationTest::testLoadReturnsDataForKnownId":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForHexId":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForPeertubeVideo":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForPeertubeAudio":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForMixedIds":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForHexInCoverQueue":8,"PureLogicTest::testHasFilePondQueueDataReturnsTrueForPeertubeInCoverQueue":8,"PureLogicTest::testHasFilePondQueueDataReturnsFalseForNumericIdsOnly":8,"PureLogicTest::testHasFilePondQueueDataReturnsFalseForEmptyInput":8,"PureLogicTest::testHasFilePondQueueDataReturnsFalseForEmptyStrings":8,"PureLogicTest::testHasFilePondQueueDataHandlesScalarNotArray":8},"times":{"CryptoTest::testEncryptDecryptRoundTrip":0,"CryptoTest::testEncryptDecryptWithUnicode":0,"CryptoTest::testEncryptDecryptMultiline":0,"CryptoTest::testDifferentPlaintextsProduceDifferentCiphertexts":0,"CryptoTest::testSamePlaintextProducesDifferentCiphertexts":0,"CryptoTest::testIsEncryptedRecognizesEncryptedValue":0,"CryptoTest::testIsEncryptedRejectsPlaintext":0,"CryptoTest::testIsEncryptedReturnsFalseForEmptyString":0,"CryptoTest::testIsEncryptedRejectsInvalidBase64":0,"CryptoTest::testEncryptDecryptEmptyString":0.008,"CryptoTest::testDecryptEmptyStringReturnsEmpty":0,"CryptoTest::testDecryptInvalidBase64ReturnsInputGracefully":0,"CryptoTest::testDecryptTooShortBlobReturnsInputGracefully":0,"CryptoTest::testDecryptWithTamperedCiphertextReturnsEmpty":0,"CryptoTest::testDecryptValidBlobTamperedTagReturnsEmpty":0,"EmailObfuscatorTest::testEncodeContainsNoAtSign":0,"EmailObfuscatorTest::testEncodeOutputIsNumericEntities":0,"EmailObfuscatorTest::testEmailReturnsObfuscatedAddress":0,"EmailObfuscatorTest::testMailtoBuildsCorrectHrefStructure":0,"EmailObfuscatorTest::testEmailTextReplacesBareEmail":0,"EmailObfuscatorTest::testEmailTextReplacesMultipleEmails":0,"EmailObfuscatorTest::testMailtoInTextReplacesMailtoLinks":0,"EmailObfuscatorTest::testObfuscateHtmlReplacesAnchorTag":0,"EmailObfuscatorTest::testObfuscateHtmlKeepsNonMailtoLinksUnchanged":0,"EmailObfuscatorTest::testObfuscateHtmlPreservesCustomLinkText":0,"EmailObfuscatorTest::testEmptyStringReturnsEmpty":0,"EmailObfuscatorTest::testStringWithNoEmailsIsUnchanged":0,"EmailObfuscatorTest::testAlreadyObfuscatedContentIsNotDoubleEncoded":0,"EmailObfuscatorTest::testMultipleEmailsInOneString":0,"EmailObfuscatorTest::testEmailWithPlusSign":0,"StudentEmailTest::testBuildHtmlReturnsNonEmptyString":0,"StudentEmailTest::testBuildHtmlContainsKeyFields":0,"StudentEmailTest::testBuildHtmlEscapesSpecialCharacters":0,"StudentEmailTest::testBuildHtmlHandlesMissingOptionalFields":0,"StudentEmailTest::testBuildHtmlHandlesNullFieldsGracefully":0,"StudentEmailTest::testBuildHtmlHandlesEmptyArray":0,"StudentEmailTest::testBuildHtmlContainsLabelFields":0,"SystemControllerHelpersTest::testHumanBytesZero":0,"SystemControllerHelpersTest::testHumanBytesBelowOneKB":0,"SystemControllerHelpersTest::testHumanBytesOneKB":0,"SystemControllerHelpersTest::testHumanBytesOneMB":0,"SystemControllerHelpersTest::testHumanBytesOneGB":0,"SystemControllerHelpersTest::testHumanBytes1523MB":0,"SystemControllerHelpersTest::testHumanBytes2500GB":0,"SystemControllerHelpersTest::testDiskColorBelowWarning":0,"SystemControllerHelpersTest::testDiskColorWarning":0,"SystemControllerHelpersTest::testDiskColorCritical":0,"SystemControllerHelpersTest::testLogLineClassCrit":0,"SystemControllerHelpersTest::testLogLineClassError":0,"SystemControllerHelpersTest::testLogLineClassWarn":0,"SystemControllerHelpersTest::testLogLineClassNotice":0,"SystemControllerHelpersTest::testLogLineClassHttp500":0,"SystemControllerHelpersTest::testLogLineClassHttp300":0,"SystemControllerHelpersTest::testLogLineClassDefault":0,"SystemControllerHelpersTest::testNginxLineClassComment":0.003,"SystemControllerHelpersTest::testNginxLineClassBlock":0.004,"SystemControllerHelpersTest::testNginxLineClassDirective":0.003,"SystemCoLine truncated
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* FileAccessTokenTest — characterization tests for the file-access one-time
|
||||
* token lifecycle (Database::generateAccessToken / redeemAccessToken).
|
||||
*
|
||||
* These pin the CURRENT contract so we can migrate the implementation onto
|
||||
* the shared OneTimeToken model without changing behavior (behaviour-
|
||||
* preserving refactor). Run before and after the migration; results must
|
||||
* stay green.
|
||||
*/
|
||||
class FileAccessTokenTest extends TestCase
|
||||
{
|
||||
private Database $db;
|
||||
private PDO $pdo;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
TestDatabase::resetData();
|
||||
$this->db = TestDatabase::getInstance();
|
||||
$this->pdo = TestDatabase::getPDO();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed a thesis + an approved file-access request, returning
|
||||
* [thesisId, requestId].
|
||||
*/
|
||||
private function seedApprovedRequest(): array
|
||||
{
|
||||
[$authorId, $thesisId] = TestDatabase::seedBasicThesis();
|
||||
$requestId = $this->db->createFileAccessRequest($thesisId, 'xamxam@erg.be', null);
|
||||
// Approve it (status must be 'approved' for redemption to succeed).
|
||||
$this->pdo->prepare(
|
||||
"UPDATE file_access_requests SET status = 'approved', approved_at = CURRENT_TIMESTAMP WHERE id = ?"
|
||||
)->execute([$requestId]);
|
||||
return [$thesisId, $requestId];
|
||||
}
|
||||
|
||||
// ── generateAccessToken ──────────────────────────────────────────────────
|
||||
|
||||
public function testGenerateAccessTokenReturns256BitHex(): void
|
||||
{
|
||||
[$thesisId, $requestId] = $this->seedApprovedRequest();
|
||||
|
||||
$token = $this->db->generateAccessToken($requestId, 24);
|
||||
|
||||
$this->assertMatchesRegularExpression('/^[0-9a-f]{64}$/', $token);
|
||||
}
|
||||
|
||||
public function testGenerateAccessTokenPersistsRowWithExpectedState(): void
|
||||
{
|
||||
[$thesisId, $requestId] = $this->seedApprovedRequest();
|
||||
|
||||
$token = $this->db->generateAccessToken($requestId, 24);
|
||||
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM file_access_tokens WHERE request_id = ?');
|
||||
$stmt->execute([$requestId]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
$this->assertNotFalse($row);
|
||||
$this->assertSame($token, $row['token'], 'Current impl stores the plaintext token');
|
||||
$this->assertSame(1, (int) $row['is_valid']);
|
||||
$this->assertNull($row['used_at']);
|
||||
$this->assertNotNull($row['expires_at']);
|
||||
}
|
||||
|
||||
// ── redeemAccessToken ────────────────────────────────────────────────────
|
||||
|
||||
public function testRedeemValidTokenReturnsThesisAndRequestIds(): void
|
||||
{
|
||||
[$thesisId, $requestId] = $this->seedApprovedRequest();
|
||||
$token = $this->db->generateAccessToken($requestId, 24);
|
||||
|
||||
$result = $this->db->redeemAccessToken($token, '1.2.3.4', 'ua');
|
||||
|
||||
$this->assertNotNull($result);
|
||||
$this->assertSame($thesisId, $result['thesis_id']);
|
||||
$this->assertSame($requestId, $result['request_id']);
|
||||
}
|
||||
|
||||
public function testRedeemMarksTokenUsed(): void
|
||||
{
|
||||
[$thesisId, $requestId] = $this->seedApprovedRequest();
|
||||
$token = $this->db->generateAccessToken($requestId, 24);
|
||||
|
||||
$this->db->redeemAccessToken($token, '1.2.3.4', 'ua');
|
||||
|
||||
$stmt = $this->pdo->prepare('SELECT used_at, is_valid FROM file_access_tokens WHERE request_id = ?');
|
||||
$stmt->execute([$requestId]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
$this->assertNotNull($row['used_at']);
|
||||
$this->assertSame(0, (int) $row['is_valid']);
|
||||
}
|
||||
|
||||
public function testRedeemIsOneTimeUse(): void
|
||||
{
|
||||
[$thesisId, $requestId] = $this->seedApprovedRequest();
|
||||
$token = $this->db->generateAccessToken($requestId, 24);
|
||||
|
||||
$first = $this->db->redeemAccessToken($token, '1.2.3.4', 'ua');
|
||||
$second = $this->db->redeemAccessToken($token, '1.2.3.4', 'ua');
|
||||
|
||||
$this->assertNotNull($first);
|
||||
$this->assertNull($second);
|
||||
}
|
||||
|
||||
public function testRedeemUnknownTokenReturnsNull(): void
|
||||
{
|
||||
$result = $this->db->redeemAccessToken(str_repeat('ab', 32), '1.2.3.4', 'ua');
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testRedeemExpiredTokenReturnsNull(): void
|
||||
{
|
||||
[$thesisId, $requestId] = $this->seedApprovedRequest();
|
||||
// Negative hours → already expired
|
||||
$token = $this->db->generateAccessToken($requestId, -1);
|
||||
|
||||
$result = $this->db->redeemAccessToken($token, '1.2.3.4', 'ua');
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testRedeemLogsAuditOnSuccess(): void
|
||||
{
|
||||
[$thesisId, $requestId] = $this->seedApprovedRequest();
|
||||
$token = $this->db->generateAccessToken($requestId, 24);
|
||||
|
||||
$this->db->redeemAccessToken($token, '1.2.3.4', 'ua');
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
"SELECT COUNT(*) FROM file_access_audit WHERE request_id = ? AND event = 'redeemed'"
|
||||
);
|
||||
$stmt->execute([$requestId]);
|
||||
$this->assertSame(1, (int) $stmt->fetchColumn());
|
||||
}
|
||||
|
||||
public function testRedeemDoesNotRequireThesisIdArgument(): void
|
||||
{
|
||||
// The thesis is resolved from the request JOIN, not passed in.
|
||||
[$thesisId, $requestId] = $this->seedApprovedRequest();
|
||||
$token = $this->db->generateAccessToken($requestId, 24);
|
||||
|
||||
// Approving a different request must not affect this one's scoping —
|
||||
// the returned thesis_id is derived from file_access_requests.
|
||||
$result = $this->db->redeemAccessToken($token, '1.2.3.4', 'ua');
|
||||
$this->assertSame($thesisId, $result['thesis_id']);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user