- Introduced SitemapStorage class for reading, writing, and checking existence of sitemap files. - Updated GenerateSitemapCommand to utilize SitemapStorage for file operations. - Refactored tests to accommodate changes in sitemap file handling. - Enhanced configuration to support output paths and storage disks.
591 lines
22 KiB
PHP
591 lines
22 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Uwil\SitemapGenerator\Tests\Integration;
|
|
|
|
use Illuminate\Config\Repository as ConfigRepository;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Spatie\Sitemap\Sitemap;
|
|
use Spatie\Sitemap\Tags\Url;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Uwil\SitemapGenerator\Config\SitemapConfig;
|
|
use Uwil\SitemapGenerator\Exceptions\SitemapException;
|
|
use Uwil\SitemapGenerator\Localizers\DefaultRouteLocalizer;
|
|
use Uwil\SitemapGenerator\SitemapGenerator;
|
|
use Uwil\SitemapGenerator\SitemapHelper;
|
|
use Uwil\SitemapGenerator\Tests\Fixtures\Article;
|
|
use Uwil\SitemapGenerator\Tests\Fixtures\Product;
|
|
use Uwil\SitemapGenerator\Tests\Fixtures\Range;
|
|
use Uwil\SitemapGenerator\Tests\Unit\Config\ModelClass;
|
|
use Webmozart\Assert\Assert;
|
|
|
|
covers([SitemapGenerator::class, SitemapHelper::class, DefaultRouteLocalizer::class]);
|
|
|
|
describe('SitemapGenerator with DefaultRouteLocalizer', function () {
|
|
afterEach(function () {
|
|
Schema::dropIfExists('model_classes');
|
|
Schema::dropIfExists('products');
|
|
Schema::dropIfExists('ranges');
|
|
Schema::dropIfExists('articles');
|
|
});
|
|
|
|
it('can be instantiated and resolved from container', function () {
|
|
// GIVEN
|
|
$abstract = SitemapGenerator::class;
|
|
|
|
// WHEN
|
|
$generator = app($abstract);
|
|
|
|
// THEN
|
|
expect($generator)->toBeInstanceOf(SitemapGenerator::class);
|
|
});
|
|
|
|
it('generates an empty sitemap when no pages or cruds are configured', function () {
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
// GIVEN
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => [],
|
|
'supported_locales' => ['fr'],
|
|
'cruds' => [],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator($config, new SitemapHelper($config, new DefaultRouteLocalizer()));
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap)->toBeInstanceOf(Sitemap::class)
|
|
->and($sitemap->getTags())->toBeEmpty();
|
|
});
|
|
|
|
it('generates a sitemap with single pages in a single locale', function () {
|
|
// GIVEN
|
|
Route::name('about')->get('{locale}/about', fn () => 'About');
|
|
Route::name('contact')->get('{locale}/contact', fn () => 'Contact');
|
|
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => ['about', 'contact'],
|
|
'supported_locales' => ['fr'],
|
|
'cruds' => [],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator($config, new SitemapHelper($config, new DefaultRouteLocalizer()));
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap)->toBeInstanceOf(Sitemap::class)
|
|
->and($sitemap->hasUrl('http://localhost/fr/about'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/fr/contact'))->toBeTrue();
|
|
});
|
|
|
|
it('generates a sitemap with single pages and alternates across multiple locales', function () {
|
|
// GIVEN
|
|
Route::name('about')->get('{locale}/about', fn () => 'About');
|
|
Route::name('contact')->get('{locale}/contact', fn () => 'Contact');
|
|
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => ['about', 'contact'],
|
|
'supported_locales' => ['fr', 'en'],
|
|
'cruds' => [],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator($config, new SitemapHelper($config, new DefaultRouteLocalizer()));
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap)->toBeInstanceOf(Sitemap::class)
|
|
->and($sitemap->hasUrl('http://localhost/fr/about'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/en/about'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/fr/contact'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/en/contact'))->toBeTrue();
|
|
|
|
$aboutTagFr = collect($sitemap->getTags())->first(fn (mixed $tag): bool => $tag instanceof Url && $tag->url === 'http://localhost/fr/about');
|
|
Assert::isInstanceOf($aboutTagFr, Url::class);
|
|
expect($aboutTagFr->alternates)->toHaveCount(1)
|
|
->and($aboutTagFr->alternates[0]->url)->toBe('http://localhost/en/about')
|
|
->and($aboutTagFr->alternates[0]->locale)->toBe('en');
|
|
});
|
|
|
|
it('ignores non-existing routes gracefully', function () {
|
|
// GIVEN
|
|
Route::name('about')->get('{locale}/about', fn () => 'About');
|
|
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => ['about', 'inexisting-route'],
|
|
'supported_locales' => ['fr'],
|
|
'cruds' => [],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator(
|
|
$config,
|
|
new SitemapHelper(
|
|
$config,
|
|
new DefaultRouteLocalizer()
|
|
)
|
|
);
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap->hasUrl('http://localhost/fr/about'))->toBeTrue()
|
|
->and($sitemap->getTags())->toHaveCount(1);
|
|
});
|
|
|
|
it('generates a sitemap with cruds models and alternates', function () {
|
|
// GIVEN
|
|
Schema::create('model_classes', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->timestamps();
|
|
});
|
|
|
|
ModelClass::create();
|
|
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => [],
|
|
'supported_locales' => ['fr', 'en'],
|
|
'cruds' => [
|
|
[
|
|
'model' => ModelClass::class,
|
|
],
|
|
],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator(
|
|
$config,
|
|
new SitemapHelper(
|
|
$config,
|
|
new DefaultRouteLocalizer()
|
|
)
|
|
);
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap)->toBeInstanceOf(Sitemap::class)
|
|
->and($sitemap->hasUrl('http://localhost/fr/posts'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/en/posts'))->toBeTrue();
|
|
|
|
$crudTagFr = collect($sitemap->getTags())
|
|
->first(fn (mixed $tag): bool => $tag instanceof Url && $tag->url === 'http://localhost/fr/posts');
|
|
Assert::isInstanceOf($crudTagFr, Url::class);
|
|
expect($crudTagFr->alternates)->toHaveCount(1)
|
|
->and($crudTagFr->alternates[0]->url)->toBe('http://localhost/en/posts')
|
|
->and($crudTagFr->alternates[0]->locale)->toBe('en');
|
|
});
|
|
|
|
it('generates a combined sitemap with single pages and cruds', function () {
|
|
// GIVEN
|
|
Route::name('home')->get('{locale}/home', fn () => 'Home');
|
|
|
|
Schema::create('model_classes', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->timestamps();
|
|
});
|
|
|
|
ModelClass::create();
|
|
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => ['home'],
|
|
'supported_locales' => ['fr'],
|
|
'cruds' => [
|
|
[
|
|
'model' => ModelClass::class,
|
|
],
|
|
],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator(
|
|
$config,
|
|
new SitemapHelper(
|
|
$config,
|
|
new DefaultRouteLocalizer()
|
|
)
|
|
);
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap->hasUrl('http://localhost/fr/home'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/fr/posts'))->toBeTrue()
|
|
->and($sitemap->getTags())->toHaveCount(2);
|
|
});
|
|
|
|
it('generates sitemap with cruds matching documentation config example (model_filters, closure relation_filters, allow_null_relation, and multi-crud)', function () {
|
|
// GIVEN
|
|
Schema::create('ranges', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->boolean('status')->default(true);
|
|
$table->timestamps();
|
|
});
|
|
|
|
Schema::create('products', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('slug');
|
|
$table->boolean('published')->default(true);
|
|
$table->foreignId('range_id')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
|
|
Schema::create('articles', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('slug');
|
|
$table->timestamps();
|
|
});
|
|
|
|
$activeRange = Range::create(['status' => true]);
|
|
$inactiveRange = Range::create(['status' => false]);
|
|
|
|
Product::create(['slug' => 'p-active-range', 'published' => true, 'range_id' => $activeRange->id]);
|
|
Product::create(['slug' => 'p-no-range', 'published' => true, 'range_id' => null]);
|
|
Product::create(['slug' => 'p-inactive-range', 'published' => true, 'range_id' => $inactiveRange->id]);
|
|
Product::create(['slug' => 'p-draft', 'published' => false, 'range_id' => $activeRange->id]);
|
|
|
|
Article::create(['slug' => 'first-article']);
|
|
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => [],
|
|
'supported_locales' => ['fr'],
|
|
'cruds' => [
|
|
[
|
|
'model' => Product::class,
|
|
'model_filters' => [
|
|
['published', '=', true],
|
|
],
|
|
'relation_filters' => [
|
|
'range' => fn (Builder $q) => $q->where('status', true),
|
|
],
|
|
'allow_null_relation' => ['range'],
|
|
],
|
|
[
|
|
'model' => Article::class,
|
|
],
|
|
],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator(
|
|
$config,
|
|
new SitemapHelper(
|
|
$config,
|
|
new DefaultRouteLocalizer()
|
|
)
|
|
);
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap->hasUrl('http://localhost/fr/products/p-active-range'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/fr/products/p-no-range'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/fr/products/p-inactive-range'))->toBeFalse()
|
|
->and($sitemap->hasUrl('http://localhost/fr/products/p-draft'))->toBeFalse()
|
|
->and($sitemap->hasUrl('http://localhost/fr/articles/first-article'))->toBeTrue()
|
|
->and($sitemap->getTags())->toHaveCount(3);
|
|
});
|
|
|
|
it('filters crud models requiring existing relation when relation is not in allow_null_relation', function () {
|
|
// GIVEN
|
|
Schema::create('ranges', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->boolean('status')->default(true);
|
|
$table->timestamps();
|
|
});
|
|
|
|
Schema::create('products', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('slug');
|
|
$table->boolean('published')->default(true);
|
|
$table->foreignId('range_id')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
|
|
$activeRange = Range::create(['status' => true]);
|
|
|
|
Product::create(['slug' => 'with-range', 'published' => true, 'range_id' => $activeRange->id]);
|
|
Product::create(['slug' => 'without-range', 'published' => true, 'range_id' => null]);
|
|
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => [],
|
|
'supported_locales' => ['fr'],
|
|
'cruds' => [
|
|
[
|
|
'model' => Product::class,
|
|
'relation_filters' => [
|
|
'range' => fn (Builder $q) => $q->where('status', true),
|
|
],
|
|
'allow_null_relation' => [],
|
|
],
|
|
],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator(
|
|
$config,
|
|
new SitemapHelper(
|
|
$config,
|
|
new DefaultRouteLocalizer()
|
|
)
|
|
);
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap->hasUrl('http://localhost/fr/products/with-range'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/fr/products/without-range'))->toBeFalse()
|
|
->and($sitemap->getTags())->toHaveCount(1);
|
|
});
|
|
|
|
it('filters crud models using relation_filters with array constraint', function () {
|
|
// GIVEN
|
|
Schema::create('ranges', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->boolean('status')->default(true);
|
|
$table->timestamps();
|
|
});
|
|
|
|
Schema::create('products', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('slug');
|
|
$table->boolean('published')->default(true);
|
|
$table->foreignId('range_id')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
|
|
$activeRange = Range::create(['status' => true]);
|
|
$inactiveRange = Range::create(['status' => false]);
|
|
|
|
Product::create(['slug' => 'in-active', 'published' => true, 'range_id' => $activeRange->id]);
|
|
Product::create(['slug' => 'in-inactive', 'published' => true, 'range_id' => $inactiveRange->id]);
|
|
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => [],
|
|
'supported_locales' => ['fr'],
|
|
'cruds' => [
|
|
[
|
|
'model' => Product::class,
|
|
'relation_filters' => [
|
|
'range' => ['status' => true],
|
|
],
|
|
],
|
|
],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator(
|
|
$config,
|
|
new SitemapHelper(
|
|
$config,
|
|
new DefaultRouteLocalizer()
|
|
)
|
|
);
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap->hasUrl('http://localhost/fr/products/in-active'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/fr/products/in-inactive'))->toBeFalse()
|
|
->and($sitemap->getTags())->toHaveCount(1);
|
|
});
|
|
|
|
it('filters crud models using relation_filters with default string boolean constraint', function () {
|
|
// GIVEN
|
|
Schema::create('ranges', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->boolean('status')->default(true);
|
|
$table->timestamps();
|
|
});
|
|
|
|
Schema::create('products', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('slug');
|
|
$table->boolean('published')->default(true);
|
|
$table->foreignId('range_id')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
|
|
$activeRange = Range::create(['status' => true]);
|
|
$inactiveRange = Range::create(['status' => false]);
|
|
|
|
Product::create(['slug' => 'in-active', 'published' => true, 'range_id' => $activeRange->id]);
|
|
Product::create(['slug' => 'in-inactive', 'published' => true, 'range_id' => $inactiveRange->id]);
|
|
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => [],
|
|
'supported_locales' => ['fr'],
|
|
'cruds' => [
|
|
[
|
|
'model' => Product::class,
|
|
'relation_filters' => [
|
|
'range' => 'status',
|
|
],
|
|
],
|
|
],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator(
|
|
$config,
|
|
new SitemapHelper(
|
|
$config,
|
|
new DefaultRouteLocalizer()
|
|
)
|
|
);
|
|
|
|
// WHEN
|
|
$sitemap = $generator->generate();
|
|
|
|
// THEN
|
|
expect($sitemap->hasUrl('http://localhost/fr/products/in-active'))->toBeTrue()
|
|
->and($sitemap->hasUrl('http://localhost/fr/products/in-inactive'))->toBeFalse()
|
|
->and($sitemap->getTags())->toHaveCount(1);
|
|
});
|
|
|
|
it('throws SitemapException when an error occurs during sitemap generation', function () {
|
|
// GIVEN
|
|
/** @var \Uwil\SitemapGenerator\Tests\TestCase $this */
|
|
$config = new SitemapConfig(new ConfigRepository([
|
|
SitemapConfig::CONFIG_KEY => [
|
|
'single_pages' => [],
|
|
'supported_locales' => ['fr'],
|
|
'cruds' => [
|
|
[
|
|
// NOTE: This will cause an error because the 'products' table is not created
|
|
'model' => Product::class,
|
|
],
|
|
],
|
|
'schedule' => false,
|
|
'cron' => '* * * * *',
|
|
'localizer' => 'default',
|
|
'output' => [
|
|
'storage_disk' => $this->outputDisk,
|
|
'path' => $this->outputPath,
|
|
],
|
|
],
|
|
]));
|
|
|
|
$generator = new SitemapGenerator(
|
|
$config,
|
|
new SitemapHelper(
|
|
$config,
|
|
new DefaultRouteLocalizer()
|
|
)
|
|
);
|
|
|
|
// WHEN
|
|
$action = fn () => $generator->generate();
|
|
|
|
// THEN
|
|
expect($action)->toThrow(
|
|
SitemapException::class,
|
|
'An error occurred while generating the sitemap'
|
|
);
|
|
});
|
|
});
|