Fix phpstan discovered issues

This commit is contained in:
DariusIII
2026-05-30 23:31:00 +02:00
parent 51aaf2b130
commit a0e78e7385
37 changed files with 200 additions and 208 deletions
@@ -40,9 +40,9 @@ class NntmuxPopulateSteamApps extends Command
$this->info(sprintf( $this->info(sprintf(
'Added %d new steam app(s), %d skipped, %d errors', 'Added %d new steam app(s), %d skipped, %d errors',
$stats['inserted'], // @phpstan-ignore offsetAccess.notFound $stats['inserted'],
$stats['skipped'], // @phpstan-ignore offsetAccess.notFound $stats['skipped'],
$stats['errors'] // @phpstan-ignore offsetAccess.notFound $stats['errors']
)); ));
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error($e->getMessage()); $this->error($e->getMessage());
+1 -1
View File
@@ -125,7 +125,7 @@ class NntmuxSearchDiag extends Command
// ['data' => [...]] shape is retained as a fallback. // ['data' => [...]] shape is retained as a fallback.
$rows = []; $rows = [];
foreach ($response as $key => $value) { foreach ($response as $key => $value) {
if (\is_int($key) || (\is_string($key) && ctype_digit($key))) { if (\is_int($key) || ctype_digit($key)) {
if (\is_array($value)) { if (\is_array($value)) {
$row = $value; $row = $value;
if (! isset($row['id'])) { if (! isset($row['id'])) {
@@ -172,7 +172,7 @@ class NntmuxSearchReconcile extends Command
// The old `$response['data']` shape only exists for non-raw mode or pre-4.x clients. // The old `$response['data']` shape only exists for non-raw mode or pre-4.x clients.
foreach ($response as $key => $value) { foreach ($response as $key => $value) {
// Top-level numeric keys mirror row ids (single-column id projection). // Top-level numeric keys mirror row ids (single-column id projection).
if (\is_int($key) || (\is_string($key) && ctype_digit($key))) { if (\is_int($key) || ctype_digit($key)) {
$id = (int) $key; $id = (int) $key;
if (isset($requested[$id])) { if (isset($requested[$id])) {
$out[$id] = $id; $out[$id] = $id;
@@ -68,8 +68,8 @@ class AdminPaymentController extends BasePageController
$summaryTotals = [ $summaryTotals = [
'tx_count' => (int) $summary->sum('tx_count'), 'tx_count' => (int) $summary->sum('tx_count'),
'invoice_total' => (float) $summary->sum(fn ($r) => (float) $r->invoice_total), 'invoice_total' => (float) $summary->sum(fn (Payment $payment) => (float) $payment->getAttribute('invoice_total')),
'value_total' => (float) $summary->sum(fn ($r) => (float) $r->value_total), 'value_total' => (float) $summary->sum(fn (Payment $payment) => (float) $payment->getAttribute('value_total')),
]; ];
$paymentStatuses = BtcPaymentController::paymentStatusesForAdminFilter(); $paymentStatuses = BtcPaymentController::paymentStatusesForAdminFilter();
@@ -101,7 +101,7 @@ class RegisterController extends Controller
'email', 'email',
'max:255', 'max:255',
Rule::unique('users', 'email')->where(fn ($query) => $query->whereNull('deleted_at')), Rule::unique('users', 'email')->where(fn ($query) => $query->whereNull('deleted_at')),
new ValidEmailDomain, app(ValidEmailDomain::class),
], ],
'password' => ['required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols()->uncompromised()], 'password' => ['required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols()->uncompromised()],
], [ ], [
+1 -1
View File
@@ -20,7 +20,7 @@ class BasePageController extends Controller
/** /**
* @var Collection<int, mixed> * @var Collection<int, mixed>
*/ */
public Collection $settings; // @phpstan-ignore property.phpDocType, class.notFound, missingType.generics public Collection $settings;
public string $title = ''; public string $title = '';
+6 -6
View File
@@ -87,9 +87,9 @@ class ContentController extends BasePageController
* *
* @return Collection<int, mixed> * @return Collection<int, mixed>
*/ */
protected function getActiveContent(): Collection // @phpstan-ignore class.notFound, missingType.generics, return.phpDocType protected function getActiveContent(): Collection
{ {
return Content::active()->ordered()->get(); // @phpstan-ignore method.notFound return Content::active()->ordered()->get();
} }
/** /**
@@ -97,11 +97,11 @@ class ContentController extends BasePageController
* *
* @return Collection<int, mixed> * @return Collection<int, mixed>
*/ */
protected function getAllButFront(): Collection // @phpstan-ignore class.notFound, missingType.generics, return.phpDocType protected function getAllButFront(): Collection
{ {
return Content::query() return Content::query()
->where('id', '<>', 1) ->where('id', '<>', 1)
->ordered() // @phpstan-ignore method.notFound ->ordered()
->get(); ->get();
} }
@@ -121,7 +121,7 @@ class ContentController extends BasePageController
* *
* @return Collection<int, mixed> * @return Collection<int, mixed>
*/ */
protected function getFrontPageContent(): Collection // @phpstan-ignore class.notFound, missingType.generics, return.phpDocType protected function getFrontPageContent(): Collection
{ {
return Content::frontPage()->get(); return Content::frontPage()->get();
} }
@@ -133,7 +133,7 @@ class ContentController extends BasePageController
{ {
return Content::active() return Content::active()
->ofType(Content::TYPE_INDEX) ->ofType(Content::TYPE_INDEX)
->ordered() // @phpstan-ignore method.notFound ->ordered()
->first(); ->first();
} }
} }
+42
View File
@@ -142,6 +142,18 @@ class AnidbInfo extends Model
return null; return null;
} }
/**
* Get AniDB URL if anidbid exists.
*/
public function getAnidbUrl(): ?string
{
if (empty($this->anidbid)) {
return null;
}
return 'https://anidb.net/anime/'.$this->anidbid;
}
/** /**
* Get AniList URL if anilist_id exists. * Get AniList URL if anilist_id exists.
*/ */
@@ -165,4 +177,34 @@ class AnidbInfo extends Model
return 'https://myanimelist.net/anime/'.$this->mal_id; return 'https://myanimelist.net/anime/'.$this->mal_id;
} }
/**
* Get MyAnimeList URL if mal_id exists.
*/
public function getMyAnimeListUrl(): ?string
{
return $this->getMalUrl();
}
/**
* Get available external links keyed by provider.
*
* @return array<string, string>
*/
public function getExternalLinks(): array
{
return array_filter([
'anidb' => $this->getAnidbUrl(),
'anilist' => $this->getAnilistUrl(),
'myanimelist' => $this->getMyAnimeListUrl(),
]);
}
/**
* Check whether at least one external link exists.
*/
public function hasExternalLinks(): bool
{
return $this->getExternalLinks() !== [];
}
} }
+1 -1
View File
@@ -143,7 +143,7 @@ class Content extends Model
{ {
return $query->active() // @phpstan-ignore method.notFound return $query->active() // @phpstan-ignore method.notFound
->ofType(self::TYPE_INDEX) ->ofType(self::TYPE_INDEX)
->ordered(); // @phpstan-ignore method.notFound ->ordered();
} }
/** /**
+3 -3
View File
@@ -65,9 +65,9 @@ class ReleaseFile extends Model
* @param Builder<self> $query * @param Builder<self> $query
* @return Builder<self> * @return Builder<self>
*/ */
public function scopeNfoFiles(Builder $query): Builder // @phpstan-ignore missingType.generics, return.phpDocType public function scopeNfoFiles(Builder $query): Builder
{ {
return $query->where(function (Builder $q) { // @phpstan-ignore missingType.generics return $query->where(function (Builder $q) {
$q->where('name', 'like', '%.nfo') $q->where('name', 'like', '%.nfo')
->orWhere('name', 'like', '%.diz') ->orWhere('name', 'like', '%.diz')
->orWhere('name', 'like', '%.inf') ->orWhere('name', 'like', '%.inf')
@@ -82,7 +82,7 @@ class ReleaseFile extends Model
* @param Builder<self> $query * @param Builder<self> $query
* @return Builder<self> * @return Builder<self>
*/ */
public function scopeNfoFilesWithContent(Builder $query): Builder // @phpstan-ignore missingType.generics, return.phpDocType public function scopeNfoFilesWithContent(Builder $query): Builder
{ {
return $query->nfoFiles()->where('size', '>', 0); return $query->nfoFiles()->where('size', '>', 0);
} }
+1 -1
View File
@@ -182,7 +182,7 @@ class ValidEmailDomain implements ValidationRule
/** /**
* Validate that the domain has proper DNS records * Validate that the domain has proper DNS records
*/ */
private function validateDnsRecords(string $domain): bool protected function validateDnsRecords(string $domain): bool
{ {
// Check for MX records (primary email validation) // Check for MX records (primary email validation)
if (@checkdnsrr($domain, 'MX')) { if (@checkdnsrr($domain, 'MX')) {
+11 -11
View File
@@ -63,7 +63,7 @@ class BinariesService
private int $headersBlackListed = 0; private int $headersBlackListed = 0;
/** /**
* @var array<string, mixed> * @var array<int, int|string>
*/ */
private array $headersReceived = []; private array $headersReceived = [];
@@ -257,7 +257,7 @@ class BinariesService
* @param int $first The oldest wanted header. * @param int $first The oldest wanted header.
* @param int $last The newest wanted header. * @param int $last The newest wanted header.
* @param string $type Is this part repair or update or backfill? * @param string $type Is this part repair or update or backfill?
* @param array<string, mixed>|null $missingParts If we are running in part repair, the list of missing article numbers. * @param array<int, mixed>|null $missingParts If we are running in part repair, the list of missing article numbers.
* @return array<string, mixed> Empty on failure. * @return array<string, mixed> Empty on failure.
* *
* @throws \Exception * @throws \Exception
@@ -381,7 +381,7 @@ class BinariesService
} }
// Calculate parts repaired // Calculate parts repaired
$lastPartNumber = $missingParts[$missingCount - 1]->numberid; // @phpstan-ignore offsetAccess.notFound $lastPartNumber = $missingParts[$missingCount - 1]->numberid;
$remainingCount = $this->missedPartHandler->getCount($groupArr['id'], $lastPartNumber); $remainingCount = $this->missedPartHandler->getCount($groupArr['id'], $lastPartNumber);
$partsRepaired = $missingCount - $remainingCount; $partsRepaired = $missingCount - $remainingCount;
@@ -693,7 +693,7 @@ class BinariesService
} }
/** /**
* @return array<string, mixed> * @return array<int, array<string, mixed>>
*/ */
private function downloadHeaders(bool $partRepair): ?array private function downloadHeaders(bool $partRepair): ?array
{ {
@@ -736,8 +736,8 @@ class BinariesService
} }
/** /**
* @param array<string, mixed> $headersNotInserted * @param array<int, int|string> $headersNotInserted
* @param array<string, mixed> $parsedHeaders * @param array<int, array<string, mixed>> $parsedHeaders
*/ */
private function handlePartRepairTracking(array $headersNotInserted, array $parsedHeaders): void private function handlePartRepairTracking(array $headersNotInserted, array $parsedHeaders): void
{ {
@@ -750,11 +750,11 @@ class BinariesService
// Check for missing headers in range // Check for missing headers in range
$expectedCount = $this->last - $this->first - $this->notYEnc - $this->headersBlackListed + 1; $expectedCount = $this->last - $this->first - $this->notYEnc - $this->headersBlackListed + 1;
if ($expectedCount > \count($this->headersReceived)) { if ($expectedCount > \count($this->headersReceived)) {
$rangeNotReceived = array_diff(range($this->first, $this->last), $this->headersReceived); $rangeNotReceived = array_values(array_diff(range($this->first, $this->last), $this->headersReceived));
$notReceivedCount = \count($rangeNotReceived); $notReceivedCount = \count($rangeNotReceived);
if ($notReceivedCount > 0) { if ($notReceivedCount > 0) {
$this->missedPartHandler->addMissingParts($rangeNotReceived, $this->groupMySQL['id']); // @phpstan-ignore argument.type $this->missedPartHandler->addMissingParts($rangeNotReceived, $this->groupMySQL['id']);
if ($this->config->echoCli) { if ($this->config->echoCli) {
cli()->alternate( cli()->alternate(
@@ -766,14 +766,14 @@ class BinariesService
} }
/** /**
* @param array<string, mixed> $missingParts * @param array<int, \stdClass> $missingParts
* @return array<string, mixed> * @return list<array{partfrom: mixed, partto: mixed, partlist: list<mixed>}>
*/ */
private function groupMissingPartsIntoRanges(array $missingParts): array private function groupMissingPartsIntoRanges(array $missingParts): array
{ {
$ranges = []; $ranges = [];
$partList = []; $partList = [];
$firstPart = $lastNum = $missingParts[0]->numberid; // @phpstan-ignore offsetAccess.notFound $firstPart = $lastNum = $missingParts[0]->numberid;
foreach ($missingParts as $part) { foreach ($missingParts as $part) {
if (($part->numberid - $firstPart) > ($this->config->messageBuffer / 4)) { if (($part->numberid - $firstPart) > ($this->config->messageBuffer / 4)) {
@@ -140,6 +140,7 @@ final class CollectionHandler
* Resolve collections for a chunk of headers with one bulk insert and one id lookup. * Resolve collections for a chunk of headers with one bulk insert and one id lookup.
* *
* @param array<int, array<string, mixed>> $headers * @param array<int, array<string, mixed>> $headers
* @param array<int, int> $totalFilesByIndex
* @return array<int, int> Collection ids keyed by header index * @return array<int, int> Collection ids keyed by header index
*/ */
public function getOrCreateCollections( public function getOrCreateCollections(
+3 -3
View File
@@ -34,10 +34,10 @@ final class HeaderParser
/** /**
* Parse and filter raw headers from NNTP. * Parse and filter raw headers from NNTP.
* *
* @param array<string, mixed> $headers Raw headers from NNTP * @param array<int, array<string, mixed>> $headers Raw headers from NNTP
* @param string $groupName The newsgroup name * @param string $groupName The newsgroup name
* @param bool $partRepair Whether this is a part repair scan * @param bool $partRepair Whether this is a part repair scan
* @param array<string, mixed>|null $missingParts Missing part numbers if part repair * @param array<int, mixed>|null $missingParts Missing part numbers if part repair
* @return array<string, mixed> Filtered and parsed headers with article info * @return array<string, mixed> Filtered and parsed headers with article info
*/ */
public function parse( public function parse(
@@ -137,7 +137,7 @@ final class HeaderParser
/** /**
* Extract highest and lowest article info from headers. * Extract highest and lowest article info from headers.
* *
* @param array<string, mixed> $headers * @param array<int, array<string, mixed>> $headers
* @return array<string, mixed> * @return array<string, mixed>
*/ */
public function getArticleRange(array $headers): array public function getArticleRange(array $headers): array
+12 -65
View File
@@ -20,7 +20,7 @@ final class HeaderStorageService
private BinariesConfig $config; private BinariesConfig $config;
/** @var array<int> Article numbers that failed to insert */ /** @var array<int, int|string> Article numbers that failed to insert */
private array $failedInserts = []; private array $failedInserts = [];
public function __construct( public function __construct(
@@ -41,10 +41,10 @@ final class HeaderStorageService
/** /**
* Store parsed headers to the database. * Store parsed headers to the database.
* *
* @param array<string, mixed> $headers Parsed headers with 'matches' already populated * @param array<int, array<string, mixed>> $headers Parsed headers with 'matches' already populated
* @param array<string, mixed> $groupMySQL Group info from database * @param array<string, mixed> $groupMySQL Group info from database
* @param bool $addToPartRepair Whether to track failed inserts * @param bool $addToPartRepair Whether to track failed inserts
* @return array<string, mixed> Article numbers that failed to insert * @return array<int, int|string> Article numbers that failed to insert
*/ */
public function store(array $headers, array $groupMySQL, bool $addToPartRepair = true): array public function store(array $headers, array $groupMySQL, bool $addToPartRepair = true): array
{ {
@@ -77,7 +77,7 @@ final class HeaderStorageService
/** /**
* Store one bounded header chunk inside its own transaction. * Store one bounded header chunk inside its own transaction.
* *
* @param array<string, mixed> $headers * @param array<int, array<string, mixed>> $headers
* @param array<string, mixed> $groupMySQL * @param array<string, mixed> $groupMySQL
*/ */
private function storeChunk(array $headers, array $groupMySQL, bool $addToPartRepair): void private function storeChunk(array $headers, array $groupMySQL, bool $addToPartRepair): void
@@ -87,16 +87,17 @@ final class HeaderStorageService
$this->partHandler->reset(); $this->partHandler->reset();
$this->partHandler->setAddToPartRepair($addToPartRepair); $this->partHandler->setAddToPartRepair($addToPartRepair);
$chunkNumbers = array_values(array_filter(array_map( $chunkNumbers = [];
static fn (array $header): mixed => $header['Number'] ?? null, foreach ($headers as $header) {
$headers if (isset($header['Number']) && (\is_int($header['Number']) || \is_string($header['Number']))) {
))); $chunkNumbers[] = $header['Number'];
}
}
// Create transaction // Create transaction
$transaction = new HeaderStorageTransaction( $transaction = new HeaderStorageTransaction(
$this->collectionHandler, $this->collectionHandler,
$this->binaryHandler, $this->binaryHandler
$this->partHandler
); );
$transaction->begin(); $transaction->begin();
@@ -208,65 +209,11 @@ final class HeaderStorageService
private function markHeaderFailed(array $header, HeaderStorageTransaction $transaction, bool $addToPartRepair): void private function markHeaderFailed(array $header, HeaderStorageTransaction $transaction, bool $addToPartRepair): void
{ {
$transaction->markError(); $transaction->markError();
if ($addToPartRepair && isset($header['Number'])) { if ($addToPartRepair && isset($header['Number']) && (\is_int($header['Number']) || \is_string($header['Number']))) {
$this->failedInserts[] = $header['Number']; $this->failedInserts[] = $header['Number'];
} }
} }
/**
* @param array<string, mixed> $groupMySQL
* @param array<string, mixed> $header
*/
private function processHeader(array $header, array $groupMySQL, HeaderStorageTransaction $transaction): bool
{
// Get file count from subject
$fileCount = $this->getFileCount($header['matches'][1]);
if ($fileCount[1] === 0 && $fileCount[3] === 0) {
$fileCount = $this->getFileCount($header['matches'][0]);
}
$totalFiles = (int) $fileCount[3];
$fileNumber = (int) $fileCount[1];
// Get or create collection
$collectionId = $this->collectionHandler->getOrCreateCollection(
$header,
$groupMySQL['id'],
$groupMySQL['name'],
$totalFiles,
$transaction->getBatchNoise()
);
if ($collectionId === null) {
$transaction->markError();
return false;
}
// Get or create binary
$binaryId = $this->binaryHandler->getOrCreateBinary(
$header,
$collectionId,
$groupMySQL['id'],
$fileNumber
);
if ($binaryId === null) {
$transaction->markError();
return false;
}
// Add part
if (! $this->partHandler->addPart($binaryId, $header)) {
$transaction->markError();
return false;
}
return true;
}
/** /**
* @return array<int, int|string> * @return array<int, int|string>
*/ */
@@ -24,8 +24,7 @@ final class HeaderStorageTransaction
public function __construct( public function __construct(
CollectionHandler $collectionHandler, CollectionHandler $collectionHandler,
BinaryHandler $binaryHandler, BinaryHandler $binaryHandler
PartHandler $partHandler
) { ) {
$this->collectionHandler = $collectionHandler; $this->collectionHandler = $collectionHandler;
$this->binaryHandler = $binaryHandler; $this->binaryHandler = $binaryHandler;
+5 -5
View File
@@ -29,7 +29,7 @@ final class MissedPartHandler
/** /**
* Add missing article numbers to the repair queue. * Add missing article numbers to the repair queue.
* *
* @param array<string, mixed> $numbers * @param array<int, int|string> $numbers
*/ */
public function addMissingParts(array $numbers, int $groupId): void public function addMissingParts(array $numbers, int $groupId): void
{ {
@@ -49,7 +49,7 @@ final class MissedPartHandler
} }
/** /**
* @param array<string, mixed> $numbers * @param array<int, int|string> $numbers
*/ */
private function addMissingPartsSqlite(array $numbers, int $groupId): void private function addMissingPartsSqlite(array $numbers, int $groupId): void
{ {
@@ -71,7 +71,7 @@ final class MissedPartHandler
} }
/** /**
* @param array<string, mixed> $numbers * @param array<int, int|string> $numbers
*/ */
private function addMissingPartsMysql(array $numbers, int $groupId): void private function addMissingPartsMysql(array $numbers, int $groupId): void
{ {
@@ -95,7 +95,7 @@ final class MissedPartHandler
/** /**
* Remove successfully repaired parts from the queue. * Remove successfully repaired parts from the queue.
* *
* @param array<string, mixed> $numbers * @param array<int, int|string> $numbers
*/ */
public function removeRepairedParts(array $numbers, int $groupId): void public function removeRepairedParts(array $numbers, int $groupId): void
{ {
@@ -120,7 +120,7 @@ final class MissedPartHandler
/** /**
* Get parts that need repair for a group. * Get parts that need repair for a group.
* *
* @return array<string, mixed> Array of missed parts * @return array<int, \stdClass> Array of missed parts
*/ */
public function getMissingParts(int $groupId): array public function getMissingParts(int $groupId): array
{ {
@@ -45,7 +45,6 @@ class CategorizationPipeline
?NzbSplitUnwrapper $nzbSplitUnwrapper = null, ?NzbSplitUnwrapper $nzbSplitUnwrapper = null,
?ObfuscatedSubjectExtractor $obfuscatedSubjectExtractor = null ?ObfuscatedSubjectExtractor $obfuscatedSubjectExtractor = null
) { ) {
/** @phpstan-ignore argument.templateType */
$this->pipes = collect($pipes) $this->pipes = collect($pipes)
->sortBy(fn (AbstractCategorizationPipe $p) => $p->getPriority()); ->sortBy(fn (AbstractCategorizationPipe $p) => $p->getPriority());
+2 -2
View File
@@ -442,13 +442,13 @@ class IGDBService
if ($isPublisher === true && $companyId) { if ($isPublisher === true && $companyId) {
$companyData = Company::find($companyId); $companyData = Company::find($companyId);
if ($companyData) { if ($companyData) {
$publishers[] = $companyData->name; // @phpstan-ignore property.notFound $publishers[] = $companyData->name;
} }
} }
if ($isDeveloper === true && $companyId) { if ($isDeveloper === true && $companyId) {
$companyData = Company::find($companyId); $companyData = Company::find($companyId);
if ($companyData) { if ($companyData) {
$developers[] = $companyData->name; // @phpstan-ignore property.notFound $developers[] = $companyData->name;
} }
} }
} }
@@ -119,7 +119,7 @@ class ReleaseBrowseService
'groups_id' => $groupId, 'groups_id' => $groupId,
'password_allow_rar' => $this->passwordAllowRar(), 'password_allow_rar' => $this->passwordAllowRar(),
'sort_field' => $indexSort, 'sort_field' => $indexSort,
'sort_dir' => $orderBy[1] ?? 'desc', // @phpstan-ignore offsetAccess.notFound 'sort_dir' => $orderBy[1] ?? 'desc',
'try_fuzzy' => true, 'try_fuzzy' => true,
], (int) $num, (int) $start); ], (int) $num, (int) $start);
$searchIndexIds = $filtered['ids']; $searchIndexIds = $filtered['ids'];
+3 -3
View File
@@ -312,7 +312,7 @@ class SteamService
/** /**
* Populate the steam_apps table with the full app list from Steam. * Populate the steam_apps table with the full app list from Steam.
* *
* @return list<array<string, mixed>> * @return array{inserted: int<0, max>, updated: int<0, max>, skipped: int<0, max>, errors: int<0, max>}
*/ */
public function populateSteamAppsTable(?callable $progressCallback = null): array public function populateSteamAppsTable(?callable $progressCallback = null): array
{ {
@@ -397,13 +397,13 @@ class SteamService
/** /**
* Get the full list of Steam apps. * Get the full list of Steam apps.
* *
* @return array{inserted: int<0, max>, updated: 0, skipped: int<0, max>, errors: int<0, max>} * @return list<array<string, mixed>>
*/ */
public function getFullAppList(): array public function getFullAppList(): array
{ {
$cacheKey = 'steam_full_app_list'; $cacheKey = 'steam_full_app_list';
$cached = Cache::get($cacheKey); $cached = Cache::get($cacheKey);
if ($cached !== null) { if (is_array($cached)) {
return $cached; return $cached;
} }
+1 -1
View File
@@ -275,7 +275,7 @@ class TmdbClient
* Get TV show details by ID * Get TV show details by ID
* *
* @param int|string $id The TMDB TV show ID * @param int|string $id The TMDB TV show ID
* @param array<string, mixed> $appendToResponse Additional data to append * @param list<string> $appendToResponse Additional data to append
* @return array<string, mixed>|null TV show data or null on failure * @return array<string, mixed>|null TV show data or null on failure
*/ */
public function getTvShow(int|string $id, array $appendToResponse = []): ?array public function getTvShow(int|string $id, array $appendToResponse = []): ?array
+1 -1
View File
@@ -77,7 +77,7 @@ class TmdbPipe extends AbstractTvProviderPipe
if ($videoId !== 0) { if ($videoId !== 0) {
$siteId = $tmdb->getSiteByID('tmdb', (int) $videoId); $siteId = $tmdb->getSiteByID('tmdb', (int) $videoId);
// If show exists in local DB with a TMDB ID, use it directly // If show exists in local DB with a TMDB ID, use it directly
if ($siteId !== false && $siteId !== 0) { // @phpstan-ignore notIdentical.alwaysTrue if ($siteId !== false && $siteId !== 0) {
$this->outputFoundInDb($cleanName); $this->outputFoundInDb($cleanName);
} else { } else {
// Show exists in local DB but without TMDB ID (from another source) // Show exists in local DB but without TMDB ID (from another source)
@@ -78,7 +78,7 @@ class TvMazePipe extends AbstractTvProviderPipe
$siteId = $tvmaze->getSiteByID('tvmaze', (int) $videoId); $siteId = $tvmaze->getSiteByID('tvmaze', (int) $videoId);
// If show exists in local DB but doesn't have a TVMaze ID, use the existing video // If show exists in local DB but doesn't have a TVMaze ID, use the existing video
// and process episode matching without trying to search TVMaze API // and process episode matching without trying to search TVMaze API
if ($siteId === false || $siteId === 0) { // @phpstan-ignore identical.alwaysFalse if ($siteId === false || $siteId === 0) {
// Show exists in our DB (likely from another source like TMDB) // Show exists in our DB (likely from another source like TMDB)
// Skip TVMaze API search and proceed to episode matching // Skip TVMaze API search and proceed to episode matching
$this->outputFoundInDb($cleanName); $this->outputFoundInDb($cleanName);
+1 -1
View File
@@ -80,7 +80,7 @@ class TvdbPipe extends AbstractTvProviderPipe
$siteId = $tvdb->getSiteByID('tvdb', (int) $videoId); $siteId = $tvdb->getSiteByID('tvdb', (int) $videoId);
// If show exists in local DB but doesn't have a TVDB ID, use the existing video // If show exists in local DB but doesn't have a TVDB ID, use the existing video
// and process episode matching without trying to search TVDB API // and process episode matching without trying to search TVDB API
if ($siteId === false || $siteId === 0) { // @phpstan-ignore identical.alwaysFalse if ($siteId === false || $siteId === 0) {
// Show exists in our DB (likely from another source like TMDB) // Show exists in our DB (likely from another source like TMDB)
// Skip TVDB API search and proceed to episode matching // Skip TVDB API search and proceed to episode matching
$this->outputFoundInDb($cleanName); $this->outputFoundInDb($cleanName);
@@ -51,7 +51,6 @@ class TvProcessingPipeline
*/ */
public function __construct(iterable $pipes = [], bool $echoOutput = true) public function __construct(iterable $pipes = [], bool $echoOutput = true)
{ {
/** @phpstan-ignore argument.templateType */
$this->pipes = collect($pipes) $this->pipes = collect($pipes)
->sortBy(fn (AbstractTvProviderPipe $p) => $p->getPriority()); ->sortBy(fn (AbstractTvProviderPipe $p) => $p->getPriority());
+30 -14
View File
@@ -7,6 +7,7 @@ namespace App\Services\TvProcessing;
use App\Models\Video; use App\Models\Video;
use App\Services\TmdbClient; use App\Services\TmdbClient;
use App\Services\TraktService; use App\Services\TraktService;
use App\Services\TvProcessing\Providers\AbstractTvProvider;
use App\Services\TvProcessing\Providers\TmdbProvider; use App\Services\TvProcessing\Providers\TmdbProvider;
use App\Services\TvProcessing\Providers\TraktProvider; use App\Services\TvProcessing\Providers\TraktProvider;
use App\Services\TvProcessing\Providers\TvdbProvider; use App\Services\TvProcessing\Providers\TvdbProvider;
@@ -139,10 +140,6 @@ class TvShowAdder
return null; return null;
} }
if (! is_object($extended)) {
return null;
}
// TvdbProvider::formatShowInfo() expects fields shaped like SearchResult // TvdbProvider::formatShowInfo() expects fields shaped like SearchResult
// (tvdb_id, name, overview, first_air_time, aliases). The extended // (tvdb_id, name, overview, first_air_time, aliases). The extended
// endpoint returns SeriesExtendedRecord (camelCase). Adapt it. // endpoint returns SeriesExtendedRecord (camelCase). Adapt it.
@@ -166,6 +163,9 @@ class TvShowAdder
]; ];
} }
/**
* @return array{videoId: int, existed: bool, source: string, externalId: string, title: ?string}
*/
private function addViaTvdb(int $tvdbId, int $type): array private function addViaTvdb(int $tvdbId, int $type): array
{ {
$provider = new TvdbProvider; $provider = new TvdbProvider;
@@ -206,6 +206,9 @@ class TvShowAdder
return is_array($show) ? $show : null; return is_array($show) ? $show : null;
} }
/**
* @return array{videoId: int, existed: bool, source: string, externalId: string, title: ?string}
*/
private function addViaTvMaze(int $tvmazeId, int $type): array private function addViaTvMaze(int $tvmazeId, int $type): array
{ {
$provider = new TvMazeProvider; $provider = new TvMazeProvider;
@@ -251,12 +254,16 @@ class TvShowAdder
} }
$show['alternative_titles'] = $alternativeTitles; $show['alternative_titles'] = $alternativeTitles;
$networks = TmdbClient::getArray($show, 'networks'); $networks = array_values(TmdbClient::getArray($show, 'networks'));
$show['network'] = ! empty($networks[0]['name']) ? (string) $networks[0]['name'] : ''; $firstNetwork = $networks[0] ?? null;
$show['network'] = is_array($firstNetwork) && ! empty($firstNetwork['name']) ? (string) $firstNetwork['name'] : '';
return $show; return $show;
} }
/**
* @return array{videoId: int, existed: bool, source: string, externalId: string, title: ?string}
*/
private function addViaTmdb(int $tmdbId, int $type): array private function addViaTmdb(int $tmdbId, int $type): array
{ {
$provider = new TmdbProvider; $provider = new TmdbProvider;
@@ -290,6 +297,9 @@ class TvShowAdder
return is_array($show) && ! empty($show['ids']) ? $show : null; return is_array($show) && ! empty($show['ids']) ? $show : null;
} }
/**
* @return array{videoId: int, existed: bool, source: string, externalId: string, title: ?string}
*/
private function addViaTrakt(int|string $traktId, int $type): array private function addViaTrakt(int|string $traktId, int $type): array
{ {
$provider = new TraktProvider; $provider = new TraktProvider;
@@ -338,8 +348,10 @@ class TvShowAdder
$trakt = app(TraktService::class); $trakt = app(TraktService::class);
if ($trakt->isConfigured()) { if ($trakt->isConfigured()) {
$results = $trakt->searchById($imdbId, 'imdb', 'show'); $results = $trakt->searchById($imdbId, 'imdb', 'show');
if (is_array($results) && ! empty($results[0]['show']['ids']['trakt'])) { $resultRows = is_array($results) ? array_values($results) : [];
$traktId = (int) $results[0]['show']['ids']['trakt']; $firstResult = $resultRows[0] ?? null;
if (is_array($firstResult) && ! empty($firstResult['show']['ids']['trakt'])) {
$traktId = (int) $firstResult['show']['ids']['trakt'];
$provider = new TraktProvider; $provider = new TraktProvider;
$show = $this->fetchTrakt($traktId); $show = $this->fetchTrakt($traktId);
if ($show !== null) { if ($show !== null) {
@@ -365,6 +377,9 @@ class TvShowAdder
return null; return null;
} }
/**
* @return array{videoId: int, existed: bool, source: string, externalId: string, title: ?string}
*/
private function addViaImdb(string $imdbId, int $type): array private function addViaImdb(string $imdbId, int $type): array
{ {
// TMDB // TMDB
@@ -388,8 +403,10 @@ class TvShowAdder
$trakt = app(TraktService::class); $trakt = app(TraktService::class);
if ($trakt->isConfigured()) { if ($trakt->isConfigured()) {
$results = $trakt->searchById($imdbId, 'imdb', 'show'); $results = $trakt->searchById($imdbId, 'imdb', 'show');
if (is_array($results) && ! empty($results[0]['show']['ids']['trakt'])) { $resultRows = is_array($results) ? array_values($results) : [];
return $this->addViaTrakt((int) $results[0]['show']['ids']['trakt'], $type); $firstResult = $resultRows[0] ?? null;
if (is_array($firstResult) && ! empty($firstResult['show']['ids']['trakt'])) {
return $this->addViaTrakt((int) $firstResult['show']['ids']['trakt'], $type);
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -417,7 +434,7 @@ class TvShowAdder
* @param array<string, mixed> $data * @param array<string, mixed> $data
* @return array{videoId: int, existed: bool, source: string, externalId: string, title: ?string} * @return array{videoId: int, existed: bool, source: string, externalId: string, title: ?string}
*/ */
private function persist(object $provider, array $data, string $source, string $externalId): array private function persist(AbstractTvProvider $provider, array $data, string $source, string $externalId): array
{ {
// Ensure required keys formatShowInfo callers might omit // Ensure required keys formatShowInfo callers might omit
$data += [ $data += [
@@ -442,10 +459,9 @@ class TvShowAdder
throw new RuntimeException('Provider returned a show without a title.'); throw new RuntimeException('Provider returned a show without a title.');
} }
/** @var int $videoId */ $videoId = $provider->add($data);
$videoId = $provider->add($data); // @phpstan-ignore-line - AbstractTvProvider::add()
if ($videoId > 0 && method_exists($provider, 'getPoster')) { if ($videoId > 0) {
try { try {
$provider->getPoster($videoId); $provider->getPoster($videoId);
} catch (\Throwable $e) { } catch (\Throwable $e) {
+1 -1
View File
@@ -65,7 +65,7 @@ class GlobalDataComposer
$viewData['usefulLinks'] = $this->rememberWithCacheFallback('content_useful_links', self::CACHE_TTL, function () { $viewData['usefulLinks'] = $this->rememberWithCacheFallback('content_useful_links', self::CACHE_TTL, function () {
return Content::active() return Content::active()
->ofType(Content::TYPE_USEFUL) ->ofType(Content::TYPE_USEFUL)
->ordered() // @phpstan-ignore method.notFound ->ordered()
->get(); ->get();
}); });
-42
View File
@@ -756,12 +756,6 @@ parameters:
count: 1 count: 1
path: app/Services/AdditionalProcessing/ReleaseProcessor.php path: app/Services/AdditionalProcessing/ReleaseProcessor.php
-
message: '#^Method App\\Services\\Binaries\\BinariesService\:\:groupMissingPartsIntoRanges\(\) should return array\<string, mixed\> but returns list\<array\<string, mixed\>\>\.$#'
identifier: return.type
count: 1
path: app/Services/Binaries/BinariesService.php
- -
message: '#^PHPDoc tag @return with type list\<array\<string, mixed\>\> is incompatible with native type int\|null\.$#' message: '#^PHPDoc tag @return with type list\<array\<string, mixed\>\> is incompatible with native type int\|null\.$#'
identifier: return.phpDocType identifier: return.phpDocType
@@ -1196,24 +1190,6 @@ parameters:
count: 1 count: 1
path: app/Services/SteamService.php path: app/Services/SteamService.php
-
message: '#^Method App\\Services\\SteamService\:\:getFullAppList\(\) should return array\{inserted\: int\<0, max\>, updated\: 0, skipped\: int\<0, max\>, errors\: int\<0, max\>\} but returns array\{\}\.$#'
identifier: return.type
count: 1
path: app/Services/SteamService.php
-
message: '#^Method App\\Services\\SteamService\:\:populateSteamAppsTable\(\) should return list\<array\<string, mixed\>\> but returns array\{inserted\: 0, updated\: 0, skipped\: 0, errors\: 0\}\.$#'
identifier: return.type
count: 1
path: app/Services/SteamService.php
-
message: '#^Method App\\Services\\SteamService\:\:populateSteamAppsTable\(\) should return list\<array\<string, mixed\>\> but returns array\{inserted\: int\<0, max\>, updated\: 0, skipped\: int\<0, max\>, errors\: int\<0, max\>\}\.$#'
identifier: return.type
count: 1
path: app/Services/SteamService.php
- -
message: '#^Method App\\Services\\SteamService\:\:tokenize\(\) should return array\<string, mixed\> but returns list\<string\>\.$#' message: '#^Method App\\Services\\SteamService\:\:tokenize\(\) should return array\<string, mixed\> but returns list\<string\>\.$#'
identifier: return.type identifier: return.type
@@ -1250,12 +1226,6 @@ parameters:
count: 1 count: 1
path: app/Services/UserStatsService.php path: app/Services/UserStatsService.php
-
message: '#^Method App\\Services\\UserStatsService\:\:getDownloadsPerDay\(\) should return array\<string, mixed\> but returns list\<array\<string, int\|string\|null\>\>\.$#'
identifier: return.type
count: 1
path: app/Services/UserStatsService.php
- -
message: '#^Method App\\Services\\UserStatsService\:\:getDownloadsPerHour\(\) should return list\<array\<string, int\|string\|null\>\> but returns array\<string, mixed\>\.$#' message: '#^Method App\\Services\\UserStatsService\:\:getDownloadsPerHour\(\) should return list\<array\<string, int\|string\|null\>\> but returns array\<string, mixed\>\.$#'
identifier: return.type identifier: return.type
@@ -1268,12 +1238,6 @@ parameters:
count: 1 count: 1
path: app/Services/UserStatsService.php path: app/Services/UserStatsService.php
-
message: '#^Method App\\Services\\UserStatsService\:\:getSummaryStats\(\) should return list\<array\<string, int\|string\|null\>\> but returns array\{total_users\: int\<0, max\>, downloads_today\: int, downloads_week\: int, api_hits_today\: int, api_hits_week\: int\}\.$#'
identifier: return.type
count: 1
path: app/Services/UserStatsService.php
- -
message: '#^Method App\\Services\\XrefService\:\:extractTokens\(\) should return array\<string, mixed\> but returns list\<string\>\.$#' message: '#^Method App\\Services\\XrefService\:\:extractTokens\(\) should return array\<string, mixed\> but returns list\<string\>\.$#'
identifier: return.type identifier: return.type
@@ -1352,12 +1316,6 @@ parameters:
count: 1 count: 1
path: app/Support/SecondaryIndexDocuments.php path: app/Support/SecondaryIndexDocuments.php
-
message: '#^Method App\\Support\\UpdatePerformanceHelper\:\:checkSystemResources\(\) should return array\<string, mixed\> but returns list\<string\>\.$#'
identifier: return.type
count: 1
path: app/Support/UpdatePerformanceHelper.php
- -
message: '#^Method App\\Support\\UpdatePerformanceHelper\:\:getSystemMemoryInfo\(\) should return list\<string\>\|null but returns array\{total\: int, available\: int, used\: int, usage_percent\: \(float\|int\)\}\.$#' message: '#^Method App\\Support\\UpdatePerformanceHelper\:\:getSystemMemoryInfo\(\) should return list\<string\>\|null but returns array\{total\: int, available\: int, used\: int, usage_percent\: \(float\|int\)\}\.$#'
identifier: return.type identifier: return.type
+8
View File
@@ -6,6 +6,7 @@ namespace Tests\Feature;
use App\Http\Controllers\Auth\RegisterController; use App\Http\Controllers\Auth\RegisterController;
use App\Models\User; use App\Models\User;
use App\Rules\ValidEmailDomain;
use App\Services\RegistrationStatusService; use App\Services\RegistrationStatusService;
use Illuminate\Contracts\Validation\UncompromisedVerifier; use Illuminate\Contracts\Validation\UncompromisedVerifier;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
@@ -38,6 +39,13 @@ class RegisterControllerTest extends TestCase
return true; return true;
} }
}); });
$this->app->bind(ValidEmailDomain::class, fn () => new class extends ValidEmailDomain
{
protected function validateDnsRecords(string $domain): bool
{
return true;
}
});
Schema::dropIfExists('users'); Schema::dropIfExists('users');
Schema::dropIfExists('roles'); Schema::dropIfExists('roles');
@@ -69,6 +69,7 @@ class ReleaseProcessorTest extends TestCase
]); ]);
$releaseManager = Mockery::mock(ReleaseFileManager::class); $releaseManager = Mockery::mock(ReleaseFileManager::class);
$releaseManager->shouldReceive('processReleaseNameFromNzbContents')->once()->andReturnFalse();
$releaseManager->shouldReceive('finalizeRelease')->once()->andReturnNull(); $releaseManager->shouldReceive('finalizeRelease')->once()->andReturnNull();
$tempWorkspace = Mockery::mock(TempWorkspaceService::class); $tempWorkspace = Mockery::mock(TempWorkspaceService::class);
@@ -62,16 +62,16 @@ class DeletedUsersByColumnTest extends TestCase
*/ */
public function test_dashboard_activity_log_shows_deleted_by(): void public function test_dashboard_activity_log_shows_deleted_by(): void
{ {
$bladePath = __DIR__.'/../../../resources/views/admin/dashboard.blade.php'; $scriptPath = __DIR__.'/../../../resources/js/alpine/components/admin/dashboard.js';
$this->assertFileExists($bladePath); $this->assertFileExists($scriptPath);
$content = file_get_contents($bladePath); $content = file_get_contents($scriptPath);
// Check that the dashboard shows deleted_by for deleted activity // Check that the dashboard shows deleted_by for deleted activity
$this->assertStringContainsString("activity->type === 'deleted'", $content); $this->assertStringContainsString("activity.type === 'deleted'", $content);
$this->assertStringContainsString("metadata['deleted_by']", $content); $this->assertStringContainsString('activity.metadata.deleted_by', $content);
$this->assertStringContainsString("metadata['permanent']", $content); $this->assertStringContainsString('activity.metadata.permanent', $content);
} }
/** /**
+10 -5
View File
@@ -27,14 +27,19 @@ final class ElasticSearchQueryTest extends TestCase
$driverSource = file_get_contents(__DIR__.'/../../app/Services/Search/Drivers/ElasticSearchDriver.php'); $driverSource = file_get_contents(__DIR__.'/../../app/Services/Search/Drivers/ElasticSearchDriver.php');
$this->assertIsString($driverSource); $this->assertIsString($driverSource);
$this->assertStringContainsString("'fields' => self::RELEASE_TEXT_FIELDS", $driverSource); $methodSource = strstr($driverSource, 'public function searchReleasesFiltered');
$this->assertStringContainsString("'type' => 'cross_fields'", $driverSource); $this->assertIsString($methodSource);
$this->assertStringContainsString("'operator' => 'and'", $driverSource); $methodSource = strstr($methodSource, 'private function buildReleaseFieldSpecificMustClauses', true);
$this->assertIsString($methodSource);
$this->assertStringContainsString("'fields' => self::RELEASE_TEXT_FIELDS", $methodSource);
$this->assertStringContainsString("'type' => 'cross_fields'", $methodSource);
$this->assertStringContainsString("'operator' => 'and'", $methodSource);
$this->assertStringNotContainsString( $this->assertStringNotContainsString(
"'fields' => ['searchname^3', 'name^2', 'filename', 'plainsearchname']", "'fields' => ['searchname^3', 'name^2', 'filename', 'plainsearchname']",
$driverSource $methodSource
); );
$this->assertStringNotContainsString("'type' => 'best_fields'", $driverSource); $this->assertStringNotContainsString("'type' => 'best_fields'", $methodSource);
} }
#[Test] #[Test]
+13 -15
View File
@@ -3,17 +3,15 @@
namespace Tests\Unit\Models; namespace Tests\Unit\Models;
use App\Models\AnidbInfo; use App\Models\AnidbInfo;
use Illuminate\Foundation\Testing\RefreshDatabase; use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class AnidbInfoTest extends TestCase class AnidbInfoTest extends TestCase
{ {
use RefreshDatabase; #[Test]
/** @test */
public function it_returns_anidb_url_when_anidbid_exists(): void public function it_returns_anidb_url_when_anidbid_exists(): void
{ {
$animeInfo = AnidbInfo::create([ $animeInfo = new AnidbInfo([
'anidbid' => 12345, 'anidbid' => 12345,
'type' => 'TV Series', 'type' => 'TV Series',
]); ]);
@@ -23,10 +21,10 @@ class AnidbInfoTest extends TestCase
$this->assertEquals('https://anidb.net/anime/12345', $url); $this->assertEquals('https://anidb.net/anime/12345', $url);
} }
/** @test */ #[Test]
public function it_returns_anilist_url_when_anilist_id_exists(): void public function it_returns_anilist_url_when_anilist_id_exists(): void
{ {
$animeInfo = AnidbInfo::create([ $animeInfo = new AnidbInfo([
'anidbid' => 1, 'anidbid' => 1,
'anilist_id' => 9253, 'anilist_id' => 9253,
'type' => 'TV Series', 'type' => 'TV Series',
@@ -37,12 +35,12 @@ class AnidbInfoTest extends TestCase
$this->assertEquals('https://anilist.co/anime/9253', $url); $this->assertEquals('https://anilist.co/anime/9253', $url);
} }
/** @test */ #[Test]
public function it_returns_myanimelist_url_when_myanimelist_id_exists(): void public function it_returns_myanimelist_url_when_myanimelist_id_exists(): void
{ {
$animeInfo = AnidbInfo::create([ $animeInfo = new AnidbInfo([
'anidbid' => 1, 'anidbid' => 1,
'myanimelist_id' => 9253, 'mal_id' => 9253,
'type' => 'TV Series', 'type' => 'TV Series',
]); ]);
@@ -51,13 +49,13 @@ class AnidbInfoTest extends TestCase
$this->assertEquals('https://myanimelist.net/anime/9253', $url); $this->assertEquals('https://myanimelist.net/anime/9253', $url);
} }
/** @test */ #[Test]
public function it_returns_all_external_links(): void public function it_returns_all_external_links(): void
{ {
$animeInfo = AnidbInfo::create([ $animeInfo = new AnidbInfo([
'anidbid' => 12345, 'anidbid' => 12345,
'anilist_id' => 9253, 'anilist_id' => 9253,
'myanimelist_id' => 9253, 'mal_id' => 9253,
'type' => 'TV Series', 'type' => 'TV Series',
]); ]);
@@ -69,10 +67,10 @@ class AnidbInfoTest extends TestCase
$this->assertArrayHasKey('myanimelist', $links); $this->assertArrayHasKey('myanimelist', $links);
} }
/** @test */ #[Test]
public function it_checks_if_external_links_exist(): void public function it_checks_if_external_links_exist(): void
{ {
$animeInfo = AnidbInfo::create([ $animeInfo = new AnidbInfo([
'anidbid' => 1, 'anidbid' => 1,
'anilist_id' => 123, 'anilist_id' => 123,
'type' => 'TV Series', 'type' => 'TV Series',
+3 -1
View File
@@ -137,7 +137,9 @@ final class NzbServicePathResolutionTest extends TestCase
); );
foreach ($iterator as $item) { foreach ($iterator as $item) {
if ($item->isDir()) { if ($item->isLink()) {
unlink($item->getPathname());
} elseif ($item->isDir()) {
rmdir($item->getPathname()); rmdir($item->getPathname());
} else { } else {
unlink($item->getPathname()); unlink($item->getPathname());
+21 -5
View File
@@ -12,7 +12,7 @@ class ValidEmailDomainTest extends TestCase
*/ */
public function test_rejects_disposable_email_domains(): void public function test_rejects_disposable_email_domains(): void
{ {
$rule = new ValidEmailDomain; $rule = $this->makeRule();
$disposableEmails = [ $disposableEmails = [
'test@guerrillamail.com', 'test@guerrillamail.com',
@@ -38,7 +38,7 @@ class ValidEmailDomainTest extends TestCase
*/ */
public function test_accepts_legitimate_email_domains(): void public function test_accepts_legitimate_email_domains(): void
{ {
$rule = new ValidEmailDomain; $rule = $this->makeRule();
$legitimateEmails = [ $legitimateEmails = [
'test@gmail.com', 'test@gmail.com',
@@ -62,7 +62,7 @@ class ValidEmailDomainTest extends TestCase
*/ */
public function test_rejects_emails_with_suspicious_patterns(): void public function test_rejects_emails_with_suspicious_patterns(): void
{ {
$rule = new ValidEmailDomain; $rule = $this->makeRule();
$suspiciousEmails = [ $suspiciousEmails = [
'test@tempdomainexample.com', 'test@tempdomainexample.com',
@@ -85,7 +85,7 @@ class ValidEmailDomainTest extends TestCase
*/ */
public function test_rejects_emails_with_invalid_domains(): void public function test_rejects_emails_with_invalid_domains(): void
{ {
$rule = new ValidEmailDomain; $rule = $this->makeRule();
$invalidEmails = [ $invalidEmails = [
'test@nonexistentdomain12345xyz.com', 'test@nonexistentdomain12345xyz.com',
@@ -109,7 +109,7 @@ class ValidEmailDomainTest extends TestCase
*/ */
public function test_rejects_malformed_emails(): void public function test_rejects_malformed_emails(): void
{ {
$rule = new ValidEmailDomain; $rule = $this->makeRule();
$malformedEmails = [ $malformedEmails = [
'notanemail', 'notanemail',
@@ -126,4 +126,20 @@ class ValidEmailDomainTest extends TestCase
$this->assertTrue($failed, "Expected {$email} to be rejected as malformed"); $this->assertTrue($failed, "Expected {$email} to be rejected as malformed");
} }
} }
private function makeRule(): ValidEmailDomain
{
return new class extends ValidEmailDomain
{
protected function validateDnsRecords(string $domain): bool
{
return in_array($domain, [
'gmail.com',
'yahoo.com',
'outlook.com',
'protonmail.com',
], true);
}
};
}
} }
+2 -1
View File
@@ -116,11 +116,12 @@ class XxxCategorizationTest extends TestCase
} }
#[DataProvider('vrReleasesProvider')] #[DataProvider('vrReleasesProvider')]
public function test_vr_releases_through_full_pipeline(string $releaseName, int $expectedCategoryId): void public function test_vr_releases_through_full_pipeline(string $releaseName, int $expectedCategoryId, string $expectedMatchedBy): void
{ {
$passable = $this->runPipeline($releaseName); $passable = $this->runPipeline($releaseName);
$this->assertSame($expectedCategoryId, $passable->bestResult->categoryId, "Pipeline wrong category for: {$releaseName}"); $this->assertSame($expectedCategoryId, $passable->bestResult->categoryId, "Pipeline wrong category for: {$releaseName}");
$this->assertSame($expectedMatchedBy, $passable->bestResult->matchedBy, "Pipeline wrong matched_by for: {$releaseName}");
} }
#[DataProvider('nonVrReleasesProvider')] #[DataProvider('nonVrReleasesProvider')]