diff --git a/app/Models/Extensions/AbstractBaseConfigMigration.php b/app/Models/Extensions/AbstractBaseConfigMigration.php index f7a7c7529fe..e5cf50c6329 100644 --- a/app/Models/Extensions/AbstractBaseConfigMigration.php +++ b/app/Models/Extensions/AbstractBaseConfigMigration.php @@ -10,6 +10,9 @@ use Illuminate\Database\Migrations\Migration; +/** + * @codeCoverageIgnore still used and tested... but not in tests + */ abstract class AbstractBaseConfigMigration extends Migration { public const BOOL = '0|1'; diff --git a/app/Models/Extensions/BaseConfigMigration.php b/app/Models/Extensions/BaseConfigMigration.php index cb04f780ad9..8835f4f93fa 100644 --- a/app/Models/Extensions/BaseConfigMigration.php +++ b/app/Models/Extensions/BaseConfigMigration.php @@ -22,6 +22,8 @@ final public function up(): void /** * Reverse the migrations. + * + * @codeCoverageIgnore Tested but after CI run... */ final public function down(): void { diff --git a/app/Models/Extensions/BaseConfigMigrationReversed.php b/app/Models/Extensions/BaseConfigMigrationReversed.php index 980826cd927..9fea1fb3433 100644 --- a/app/Models/Extensions/BaseConfigMigrationReversed.php +++ b/app/Models/Extensions/BaseConfigMigrationReversed.php @@ -23,6 +23,8 @@ final public function up(): void /** * Reverse the migrations. + * + * @codeCoverageIgnore Tested but after CI run... */ final public function down(): void { diff --git a/app/Models/Extensions/ConfigsHas.php b/app/Models/Extensions/ConfigsHas.php index 0b54bbc0ae0..9809d2b128f 100644 --- a/app/Models/Extensions/ConfigsHas.php +++ b/app/Models/Extensions/ConfigsHas.php @@ -44,22 +44,28 @@ public static function hasExiftool(): bool if (Helpers::isExecAvailable()) { try { $cmd_output = exec('command -v exiftool'); + // @codeCoverageIgnoreStart } catch (\Exception $e) { $cmd_output = false; Handler::reportSafely(new ExternalComponentMissingException('could not find exiftool; `has_exiftool` will be set to 0', $e)); } + // @codeCoverageIgnoreEnd $path = $cmd_output === false ? '' : $cmd_output; $has_exiftool = $path === '' ? 0 : 1; } else { + // @codeCoverageIgnoreStart $has_exiftool = 0; + // @codeCoverageIgnoreEnd } try { self::set('has_exiftool', $has_exiftool); + // @codeCoverageIgnoreStart } catch (InvalidConfigOption|QueryBuilderException $e) { // If we could not save the detected setting, still proceed Handler::reportSafely($e); } + // @codeCoverageIgnoreEnd } return $has_exiftool === 1; @@ -82,22 +88,28 @@ public static function hasFFmpeg(): bool if (Helpers::isExecAvailable()) { try { $cmd_output = exec('command -v ffmpeg'); + // @codeCoverageIgnoreStart } catch (\Exception $e) { $cmd_output = false; Handler::reportSafely(new ExternalComponentMissingException('could not find ffmpeg; `has_ffmpeg` will be set to 0', $e)); } + // @codeCoverageIgnoreEnd $path = $cmd_output === false ? '' : $cmd_output; $has_ffmpeg = $path === '' ? 0 : 1; } else { + // @codeCoverageIgnoreStart $has_ffmpeg = 0; + // @codeCoverageIgnoreEnd } try { self::set('has_ffmpeg', $has_ffmpeg); + // @codeCoverageIgnoreStart } catch (InvalidConfigOption|QueryBuilderException $e) { // If we could not save the detected setting, still proceed Handler::reportSafely($e); } + // @codeCoverageIgnoreEnd } return $has_ffmpeg === 1; diff --git a/app/Models/Extensions/HasAttributesPatch.php b/app/Models/Extensions/HasAttributesPatch.php index 5df5cd12944..98941f3bff1 100644 --- a/app/Models/Extensions/HasAttributesPatch.php +++ b/app/Models/Extensions/HasAttributesPatch.php @@ -8,6 +8,7 @@ namespace App\Models\Extensions; +use App\Exceptions\Internal\LycheeLogicException; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\InvalidCastException; @@ -23,6 +24,7 @@ trait HasAttributesPatch */ protected function mutateAttributeForArray($key, $value) { + throw new LycheeLogicException('Unexpected array cast'); if ($this->hasGetMutator($key)) { $value = $this->mutateAttribute($key, $value); } diff --git a/app/Models/Extensions/HasRandomIDAndLegacyTimeBasedID.php b/app/Models/Extensions/HasRandomIDAndLegacyTimeBasedID.php index 3da6dfc0559..aa7a965c384 100644 --- a/app/Models/Extensions/HasRandomIDAndLegacyTimeBasedID.php +++ b/app/Models/Extensions/HasRandomIDAndLegacyTimeBasedID.php @@ -43,6 +43,8 @@ public function getIncrementing(): bool * @param bool $value * * @throws NotImplementedException + * + * @codeCoverageIgnore setter is should not be used */ public function setIncrementing($value) { @@ -90,7 +92,9 @@ public function setAttribute($key, $value): mixed protected function performInsert(Builder $query): bool { if ($this->fireModelEvent('creating') === false) { + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } // First we'll need to create a fresh query instance and touch the creation and @@ -111,6 +115,7 @@ protected function performInsert(Builder $query): bool $this->generateKey(); $attributes = $this->getAttributesForInsert(); $result = $query->insert($attributes); + // @codeCoverageIgnoreStart } catch (QueryException $e) { $lastException = $e; $errorCode = $e->getCode(); @@ -123,11 +128,14 @@ protected function performInsert(Builder $query): bool } else { throw $e; } + // @codeCoverageIgnoreEnd } } while ($retry && $retryCounter > 0); if ($retryCounter === 0) { + // @codeCoverageIgnoreStart throw new TimeBasedIdException('unable to persist model to DB after 5 unsuccessful attempts', $lastException); + // @codeCoverageIgnoreEnd } // We will go ahead and set the exists property to true, so that it is set when @@ -164,10 +172,11 @@ private function generateKey(): void if ($id[23] === '-') { $id[23] = '0'; } + // @codeCoverageIgnoreStart } catch (\Exception $e) { throw new InsufficientEntropyException($e); } - + // @codeCoverageIgnoreEnd if ( PHP_INT_MAX === 2147483647 || Configs::getValueAsBool('force_32bit_ids') @@ -176,7 +185,9 @@ private function generateKey(): void // full seconds in id. The calling code needs to be able to // handle duplicate ids. Note that this also exposes us to // the year 2038 problem. + // @codeCoverageIgnoreStart $legacyID = sprintf('%010d', microtime(true)); + // @codeCoverageIgnoreEnd } else { // Ensure 4 digits after the decimal point, 15 characters // total (including the decimal point), 0-padded on the diff --git a/app/Models/Extensions/SizeVariants.php b/app/Models/Extensions/SizeVariants.php index e6d99dc393f..5b40ea9e7f9 100644 --- a/app/Models/Extensions/SizeVariants.php +++ b/app/Models/Extensions/SizeVariants.php @@ -77,13 +77,17 @@ public function __construct(Photo $photo, ?Collection $sizeVariants = null) public function add(SizeVariant $sizeVariant): void { if ($sizeVariant->photo_id !== $this->photo->id) { + // @codeCoverageIgnoreStart throw new LycheeInvalidArgumentException('ID of owning photo does not match'); + // @codeCoverageIgnoreEnd } $sizeVariant->setRelation('photo', $this->photo); $candidate = $this->getSizeVariant($sizeVariant->type); if ($candidate !== null && $candidate->id !== $sizeVariant->id) { + // @codeCoverageIgnoreStart throw new LycheeInvalidArgumentException('Another size variant of the same type has already been added'); + // @codeCoverageIgnoreEnd } match ($sizeVariant->type) { @@ -105,6 +109,7 @@ public function add(SizeVariant $sizeVariant): void */ public function toArray(): array { + // AM I really used? return [ SizeVariantType::ORIGINAL->name() => $this->original?->toArray(), SizeVariantType::MEDIUM2X->name() => $this->medium2x?->toArray(), @@ -232,7 +237,9 @@ public function getPlaceholder(): ?SizeVariant public function create(SizeVariantType $sizeVariantType, string $shortPath, ImageDimension $dim, int $filesize): SizeVariant { if (!$this->photo->exists) { + // @codeCoverageIgnoreStart throw new IllegalOrderOfOperationException('Cannot create a size variant for a photo whose id is not yet persisted to DB'); + // @codeCoverageIgnoreEnd } try { $result = SizeVariant::create([ @@ -245,14 +252,17 @@ public function create(SizeVariantType $sizeVariantType, string $shortPath, Imag 'filesize' => $filesize, 'ratio' => $dim->getRatio(), ]); + /** @disregard P1006 */ $this->add($result); return $result; + // @codeCoverageIgnoreStart } catch (LycheeInvalidArgumentException $e) { // thrown by ::add(), if $result->photo_id !== $this->photo->id, // but we know that we assert that throw LycheeAssertionError::createFromUnexpectedException($e); } + // @codeCoverageIgnoreEnd } /** @@ -332,15 +342,4 @@ public function hasMedium(): bool { return $this->medium !== null || $this->medium2x !== null; } - - /** - * We don't need to check if small2x or medium2x exists. - * small2x implies small, and same for medium2x, but the opposite is not true! - * - * @return bool - */ - public function hasMediumOrSmall(): bool - { - return $this->small !== null || $this->medium !== null; - } } diff --git a/app/Models/Extensions/SortingDecorator.php b/app/Models/Extensions/SortingDecorator.php index 217e02a1d1a..34050f63d89 100644 --- a/app/Models/Extensions/SortingDecorator.php +++ b/app/Models/Extensions/SortingDecorator.php @@ -154,6 +154,7 @@ public function get(array $columns = ['*']): Collection for ($i = $this->pivotIdx + 1; $i < sizeof($this->orderBy); $i++) { $this->baseBuilder->orderBy($this->orderBy[$i]['column'], $this->orderBy[$i]['direction']); } + // @codeCoverageIgnoreStart } catch (\InvalidArgumentException) { // Sic! In theory, `\InvalidArgumentException` should be thrown // if the *type* of argument differs from the expected type @@ -164,6 +165,7 @@ public function get(array $columns = ['*']): Collection // direction does neither equal "asc" nor "desc". throw new InvalidOrderDirectionException(); } + // @codeCoverageIgnoreEnd /** @var Collection $result */ $result = $this->baseBuilder->get($columns); diff --git a/app/Models/Extensions/ThrowsConsistentExceptions.php b/app/Models/Extensions/ThrowsConsistentExceptions.php index d399ef73be3..f21aee99a70 100644 --- a/app/Models/Extensions/ThrowsConsistentExceptions.php +++ b/app/Models/Extensions/ThrowsConsistentExceptions.php @@ -8,6 +8,7 @@ namespace App\Models\Extensions; +use App\Exceptions\Internal\LycheeLogicException; use App\Exceptions\ModelDBException; use Illuminate\Database\Eloquent\JsonEncodingException; use Illuminate\Support\Str; @@ -138,6 +139,8 @@ public function jsonSerialize(): array */ public function toJson($options = 0): string { + // See if we can delete this. + throw new LycheeLogicException('Error encoding model [' . get_class($this) . '] to JSON'); try { // Note, we must not use the option `JSON_THROW_ON_ERROR` here, // because this does not clear `json_last_error()` from any diff --git a/app/Models/Extensions/Thumb.php b/app/Models/Extensions/Thumb.php index fbb4617a556..17fdb35f8f0 100644 --- a/app/Models/Extensions/Thumb.php +++ b/app/Models/Extensions/Thumb.php @@ -88,9 +88,11 @@ public static function createFromQueryable(Relation|Builder $photoQueryable, Sor ->first(); return self::createFromPhoto($cover); + // @codeCoverageIgnoreStart } catch (\InvalidArgumentException $e) { throw new InvalidPropertyException('Sorting order invalid', $e); } + // @codeCoverageIgnoreEnd } /** @@ -109,6 +111,9 @@ public static function createFromQueryable(Relation|Builder $photoQueryable, Sor * * @throws InvalidPropertyException thrown, if $sortingOrder neither * equals `desc` nor `asc` + * + * @codeCoverageIgnore We don't need to test that one. + * Note that the inRandomOrder maybe slower than fetching length + random int. */ public static function createFromRandomQueryable(Relation|Builder $photoQueryable): ?Thumb { @@ -128,8 +133,6 @@ public static function createFromRandomQueryable(Relation|Builder $photoQueryabl /** * Creates a thumbnail from the given photo. - * On Livewire it will use by default small and small2x if available, thumb and thumb2x if not. - * On Legacy it will use thumb and thumb2x. * * @param Photo|null $photo the photo * @@ -138,12 +141,16 @@ public static function createFromRandomQueryable(Relation|Builder $photoQueryabl public static function createFromPhoto(?Photo $photo): ?Thumb { if ($photo === null) { + // @codeCoverageIgnoreStart return null; + // @codeCoverageIgnoreEnd } $thumb = $photo->size_variants->getSmall() ?? $photo->size_variants->getThumb(); if ($thumb === null) { + // @codeCoverageIgnoreStart return null; + // @codeCoverageIgnoreEnd } $thumb2x = $photo->size_variants->getSmall() !== null @@ -152,7 +159,9 @@ public static function createFromPhoto(?Photo $photo): ?Thumb $placeholder = (Configs::getValueAsBool('low_quality_image_placeholder')) ? $photo->size_variants->getPlaceholder() + // @codeCoverageIgnoreStart : null; + // @codeCoverageIgnoreEnd return new self( $photo->id, diff --git a/app/Models/Extensions/ToArrayThrowsNotImplemented.php b/app/Models/Extensions/ToArrayThrowsNotImplemented.php index 8bca5a40ee9..2f7d8f9e6a6 100644 --- a/app/Models/Extensions/ToArrayThrowsNotImplemented.php +++ b/app/Models/Extensions/ToArrayThrowsNotImplemented.php @@ -16,9 +16,6 @@ * * Now that we use Resources toArray should no longer be used. * Throw an exception if we encounter this function in the code. - * - * Because Livewire uses toArray to serialize models when passing them to sub components, - * we still need to allow those cases. Those can be detected by the Route::is() call */ trait ToArrayThrowsNotImplemented { @@ -26,6 +23,8 @@ trait ToArrayThrowsNotImplemented * @return array * * @throws NotImplementedException + * + * @codeCoverageIgnore We should never reach this code */ final public function toArray(): array { diff --git a/app/Models/Extensions/UTCBasedTimes.php b/app/Models/Extensions/UTCBasedTimes.php index 6920ae49128..1ddb98e5b53 100644 --- a/app/Models/Extensions/UTCBasedTimes.php +++ b/app/Models/Extensions/UTCBasedTimes.php @@ -141,7 +141,9 @@ public function fromDateTime($value): ?string public function asDateTime($value): Carbon { if ($value === null || $value === '') { + // @codeCoverageIgnoreStart throw new LycheeLogicException('asDateTime called on null or empty string'); + // @codeCoverageIgnoreEnd } // If this value is already a Carbon instance, we shall just return it as is. @@ -155,10 +157,12 @@ public function asDateTime($value): Carbon // these checks since they will be a waste of time, and hinder performance // when checking the field. We will just return the DateTime right away. if ($value instanceof \DateTimeInterface) { + // @codeCoverageIgnoreStart return Date::parse( $value->format('Y-m-d H:i:s.u'), $value->getTimezone() ); + // @codeCoverageIgnoreEnd } // If this value is an integer, we will assume it is a UNIX timestamp's value @@ -166,10 +170,12 @@ public function asDateTime($value): Carbon // when defining your date fields as they might be UNIX timestamps here. // Applied patch: Set bare UTC timestamp to the application's default timezone if (is_numeric($value)) { + // @codeCoverageIgnoreStart $result = Date::createFromTimestamp($value); $result->setTimezone(date_default_timezone_get()); return $result; + // @codeCoverageIgnoreEnd } // If the value is in simply year, month, day format, we will instantiate the @@ -179,12 +185,14 @@ public function asDateTime($value): Carbon // is interpreted relative to UTC and _then_ set to the // application's default timezone. if (preg_match(self::$STANDARD_DATE_PATTERN, $value) === 1) { + // @codeCoverageIgnoreStart $date = Date::createFromFormat('Y-m-d', $value, self::$DB_TIMEZONE_NAME); $date = $date !== false ? $date : null; $result = $date?->startOfDay(); $result?->setTimezone(date_default_timezone_get()); return $result; + // @codeCoverageIgnoreEnd } // Finally, we will just assume this date is in the format used by default on @@ -225,19 +233,4 @@ public function asDateTime($value): Carbon return $result; } - - /** - * Prepares a date for array/JSON serialization. - * - * In contrast to the original implementation, this one serializes the - * timezone "as is" and includes fractions of seconds. - * - * @param \DateTimeInterface $date - * - * @return string - */ - protected function serializeDate(\DateTimeInterface $date): string - { - return $date->format('Y-m-d\TH:i:sP'); - } } diff --git a/app/Models/OauthCredential.php b/app/Models/OauthCredential.php index af35ffcb5cf..b036bfe4fb1 100644 --- a/app/Models/OauthCredential.php +++ b/app/Models/OauthCredential.php @@ -54,6 +54,8 @@ class OauthCredential extends Model * Return the relationship between a Photo and its Album. * * @return BelongsTo + * + * @codeCoverageIgnore Tested locally. */ public function user(): BelongsTo {