Toolvana: production-ready media tools platform (CodeIgniter 4)
- 134-tool registry with programmatic SEO (unique titles/H1/descriptions, JSON-LD graphs, sitemap index, canonical 301 enforcement via required filter) - DB-backed job queue (SKIP LOCKED) with drivers: Ffmpeg, Images (GD), Pdf (qpdf/gs/poppler), Youtube (thumbnails), Qr (server-side PNG) - Security: SSRF guard, MIME validation, rate limits, API-key auth, bcrypt admin login, security headers - Admin panel: dashboard, tools/categories/guides CRUD, SEO audit, analytics, job inspector with retry, system health, feature flags - Docker deployment (nginx + web/api FPM pools + scalable workers), PHPUnit suite (19 tests / 1139 assertions), PWA manifest + service worker
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<IfModule authz_core_module>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !authz_core_module>
|
||||
Deny from all
|
||||
</IfModule>
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
# Running Application Tests
|
||||
|
||||
This is the quick-start to CodeIgniter testing. Its intent is to describe what
|
||||
it takes to set up your application and get it ready to run unit tests.
|
||||
It is not intended to be a full description of the test features that you can
|
||||
use to test your application. Those details can be found in the documentation.
|
||||
|
||||
## Resources
|
||||
|
||||
* [CodeIgniter 4 User Guide on Testing](https://codeigniter.com/user_guide/testing/index.html)
|
||||
* [PHPUnit docs](https://phpunit.de/documentation.html)
|
||||
* [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830)
|
||||
|
||||
## Requirements
|
||||
|
||||
It is recommended to use the latest version of PHPUnit. At the time of this
|
||||
writing, we are running version 9.x. Support for this has been built into the
|
||||
**composer.json** file that ships with CodeIgniter and can easily be installed
|
||||
via [Composer](https://getcomposer.org/) if you don't already have it installed globally.
|
||||
|
||||
```console
|
||||
> composer install
|
||||
```
|
||||
|
||||
If running under macOS or Linux, you can create a symbolic link to make running tests a touch nicer.
|
||||
|
||||
```console
|
||||
> ln -s ./vendor/bin/phpunit ./phpunit
|
||||
```
|
||||
|
||||
You also need to install [XDebug](https://xdebug.org/docs/install) in order
|
||||
for code coverage to be calculated successfully. After installing `XDebug`, you must add `xdebug.mode=coverage` in the **php.ini** file to enable code coverage.
|
||||
|
||||
## Setting Up
|
||||
|
||||
A number of the tests use a running database.
|
||||
In order to set up the database edit the details for the `tests` group in
|
||||
**app/Config/Database.php** or **.env**.
|
||||
Make sure that you provide a database engine that is currently running on your machine.
|
||||
More details on a test database setup are in the
|
||||
[Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) section of the documentation.
|
||||
|
||||
## Running the tests
|
||||
|
||||
The entire test suite can be run by simply typing one command-line command from the main directory.
|
||||
|
||||
```console
|
||||
> ./phpunit
|
||||
```
|
||||
|
||||
If you are using Windows, use the following command.
|
||||
|
||||
```console
|
||||
> vendor\bin\phpunit
|
||||
```
|
||||
|
||||
You can limit tests to those within a single test directory by specifying the
|
||||
directory name after phpunit.
|
||||
|
||||
```console
|
||||
> ./phpunit app/Models
|
||||
```
|
||||
|
||||
## Generating Code Coverage
|
||||
|
||||
To generate coverage information, including HTML reports you can view in your browser,
|
||||
you can use the following command:
|
||||
|
||||
```console
|
||||
> ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m
|
||||
```
|
||||
|
||||
This runs all of the tests again collecting information about how many lines,
|
||||
functions, and files are tested. It also reports the percentage of the code that is covered by tests.
|
||||
It is collected in two formats: a simple text file that provides an overview as well
|
||||
as a comprehensive collection of HTML files that show the status of every line of code in the project.
|
||||
|
||||
The text file can be found at **tests/coverage.txt**.
|
||||
The HTML files can be viewed by opening **tests/coverage/index.html** in your favorite browser.
|
||||
|
||||
## PHPUnit XML Configuration
|
||||
|
||||
The repository has a ``phpunit.dist.xml`` file in the project root that's used for
|
||||
PHPUnit configuration. This is used to provide a default configuration if you
|
||||
do not have your own configuration file in the project root.
|
||||
|
||||
The normal practice would be to copy ``phpunit.dist.xml`` to ``phpunit.xml``
|
||||
(which is git ignored), and to tailor it as you see fit.
|
||||
For instance, you might wish to exclude database tests, or automatically generate
|
||||
HTML code coverage reports.
|
||||
|
||||
## Test Cases
|
||||
|
||||
Every test needs a *test case*, or class that your tests extend. CodeIgniter 4
|
||||
provides one class that you may use directly:
|
||||
* `CodeIgniter\Test\CIUnitTestCase`
|
||||
|
||||
Most of the time you will want to write your own test cases that extend `CIUnitTestCase`
|
||||
to hold functions and services common to your test suites.
|
||||
|
||||
## Creating Tests
|
||||
|
||||
All tests go in the **tests/** directory. Each test file is a class that extends a
|
||||
**Test Case** (see above) and contains methods for the individual tests. These method
|
||||
names must start with the word "test" and should have descriptive names for precisely what
|
||||
they are testing:
|
||||
`testUserCanModifyFile()` `testOutputColorMatchesInput()` `testIsLoggedInFailsWithInvalidUser()`
|
||||
|
||||
Writing tests is an art, and there are many resources available to help learn how.
|
||||
Review the links above and always pay attention to your code coverage.
|
||||
|
||||
### Database Tests
|
||||
|
||||
Tests can include migrating, seeding, and testing against a mock or live database.
|
||||
Be sure to modify the test case (or create your own) to point to your seed and migrations
|
||||
and include any additional steps to be run before tests in the `setUp()` method.
|
||||
See [Testing Your Database](https://codeigniter.com/user_guide/testing/database.html)
|
||||
for details.
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Support\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class ExampleMigration extends Migration
|
||||
{
|
||||
protected $DBGroup = 'tests';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField('id');
|
||||
$this->forge->addField([
|
||||
'name' => ['type' => 'varchar', 'constraint' => 31],
|
||||
'uid' => ['type' => 'varchar', 'constraint' => 31],
|
||||
'class' => ['type' => 'varchar', 'constraint' => 63],
|
||||
'icon' => ['type' => 'varchar', 'constraint' => 31],
|
||||
'summary' => ['type' => 'varchar', 'constraint' => 255],
|
||||
'created_at' => ['type' => 'datetime', 'null' => true],
|
||||
'updated_at' => ['type' => 'datetime', 'null' => true],
|
||||
'deleted_at' => ['type' => 'datetime', 'null' => true],
|
||||
]);
|
||||
|
||||
$this->forge->addKey('name');
|
||||
$this->forge->addKey('uid');
|
||||
$this->forge->addKey(['deleted_at', 'id']);
|
||||
$this->forge->addKey('created_at');
|
||||
|
||||
$this->forge->createTable('factories');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('factories');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Support\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class ExampleSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$factories = [
|
||||
[
|
||||
'name' => 'Test Factory',
|
||||
'uid' => 'test001',
|
||||
'class' => 'Factories\Tests\NewFactory',
|
||||
'icon' => 'fas fa-puzzle-piece',
|
||||
'summary' => 'Longer sample text for testing',
|
||||
],
|
||||
[
|
||||
'name' => 'Widget Factory',
|
||||
'uid' => 'widget',
|
||||
'class' => 'Factories\Tests\WidgetPlant',
|
||||
'icon' => 'fas fa-puzzle-piece',
|
||||
'summary' => 'Create widgets in your factory',
|
||||
],
|
||||
[
|
||||
'name' => 'Evil Factory',
|
||||
'uid' => 'evil-maker',
|
||||
'class' => 'Factories\Evil\MyFactory',
|
||||
'icon' => 'fas fa-book-dead',
|
||||
'summary' => 'Abandon all hope, ye who enter here',
|
||||
],
|
||||
];
|
||||
|
||||
$builder = $this->db->table('factories');
|
||||
|
||||
foreach ($factories as $factory) {
|
||||
$builder->insert($factory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Support\Libraries;
|
||||
|
||||
use Config\App;
|
||||
|
||||
/**
|
||||
* Class ConfigReader
|
||||
*
|
||||
* An extension of BaseConfig that prevents the constructor from
|
||||
* loading external values. Used to read actual local values from
|
||||
* a config file.
|
||||
*/
|
||||
class ConfigReader extends App
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Support\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class ExampleModel extends Model
|
||||
{
|
||||
protected $table = 'factories';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'object';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $allowedFields = [
|
||||
'name',
|
||||
'uid',
|
||||
'class',
|
||||
'icon',
|
||||
'summary',
|
||||
];
|
||||
protected $useTimestamps = true;
|
||||
protected $validationRules = [];
|
||||
protected $validationMessages = [];
|
||||
protected $skipValidation = false;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\Test\DatabaseTestTrait;
|
||||
use Tests\Support\Database\Seeds\ExampleSeeder;
|
||||
use Tests\Support\Models\ExampleModel;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ExampleDatabaseTest extends CIUnitTestCase
|
||||
{
|
||||
use DatabaseTestTrait;
|
||||
|
||||
protected $seed = ExampleSeeder::class;
|
||||
|
||||
public function testModelFindAll(): void
|
||||
{
|
||||
$model = new ExampleModel();
|
||||
|
||||
// Get every row created by ExampleSeeder
|
||||
$objects = $model->findAll();
|
||||
|
||||
// Make sure the count is as expected
|
||||
$this->assertCount(3, $objects);
|
||||
}
|
||||
|
||||
public function testSoftDeleteLeavesRow(): void
|
||||
{
|
||||
$model = new ExampleModel();
|
||||
$this->setPrivateProperty($model, 'useSoftDeletes', true);
|
||||
$this->setPrivateProperty($model, 'tempUseSoftDeletes', true);
|
||||
|
||||
/** @var stdClass $object */
|
||||
$object = $model->first();
|
||||
$model->delete($object->id);
|
||||
|
||||
// The model should no longer find it
|
||||
$this->assertNull($model->find($object->id));
|
||||
|
||||
// ... but it should still be in the database
|
||||
$result = $model->builder()->where('id', $object->id)->get()->getResult();
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\Test\FeatureTestTrait;
|
||||
|
||||
/**
|
||||
* SEO contract tests: every indexable page must render complete,
|
||||
* crawlable HTML on the FIRST response — no JS required.
|
||||
*
|
||||
* These run against the test database (migrated + seeded by setUp).
|
||||
*/
|
||||
final class SeoPagesTest extends CIUnitTestCase
|
||||
{
|
||||
use FeatureTestTrait;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
// migrate + seed the test database once (idempotent)
|
||||
try {
|
||||
command('migrate --all');
|
||||
command('db:seed InitialSeeder');
|
||||
} catch (\Throwable $e) {
|
||||
fwrite(STDERR, 'Test DB bootstrap: ' . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
private function get(string $page): \CodeIgniter\Test\TestResponse
|
||||
{
|
||||
return $this->call('get', $page);
|
||||
}
|
||||
|
||||
public function testToolPageRendersFullSeoContract(): void
|
||||
{
|
||||
$response = $this->get('/youtube-to-mp3');
|
||||
$response->assertStatus(200);
|
||||
|
||||
$html = (string) $response->getBody();
|
||||
|
||||
// canonical, title, description, OG
|
||||
$this->assertMatchesRegularExpression('#<link rel="canonical" href="https?://[^"]+/youtube-to-mp3">#', $html);
|
||||
$this->assertMatchesRegularExpression('/<title>[^<]+<\/title>/', $html);
|
||||
$this->assertStringContainsString('name="description"', $html);
|
||||
$this->assertStringContainsString('property="og:title"', $html);
|
||||
$this->assertStringContainsString('name="twitter:card" content="summary_large_image"', $html);
|
||||
|
||||
// structured data: FAQPage + BreadcrumbList + WebApplication
|
||||
$jsonLd = '';
|
||||
if (preg_match('/<script type="application\/ld\+json">(.*?)<\/script>/s', $html, $m)) {
|
||||
$jsonLd = $m[1];
|
||||
}
|
||||
$this->assertNotSame('', $jsonLd, 'JSON-LD block missing');
|
||||
foreach (['BreadcrumbList', 'WebApplication', 'FAQPage'] as $type) {
|
||||
$this->assertStringContainsString('"' . $type . '"', $jsonLd, "{$type} missing from JSON-LD");
|
||||
}
|
||||
|
||||
// content in first response: H1, how-to steps, FAQ questions visible
|
||||
$this->assertStringContainsString('<h1>', $html);
|
||||
$this->assertStringContainsString('Frequently asked questions', $html);
|
||||
$this->assertStringContainsString('Related tools', $html);
|
||||
$this->assertStringContainsString('breadcrumbs', $html);
|
||||
|
||||
// robots meta allows indexing
|
||||
$this->assertStringContainsString('content="index, follow', $html);
|
||||
}
|
||||
|
||||
public function testCategoryAndHomepageAreIndexable(): void
|
||||
{
|
||||
foreach (['/', '/youtube-tools', '/tools'] as $page) {
|
||||
$html = (string) $this->get($page)->getBody();
|
||||
$this->assertStringContainsString('rel="canonical"', $html, "canonical missing on {$page}");
|
||||
$this->assertStringContainsString('index, follow', $html, "robots meta wrong on {$page}");
|
||||
}
|
||||
}
|
||||
|
||||
public function testSearchIsNoindexed(): void
|
||||
{
|
||||
$html = (string) $this->get('/search?q=mp3')->getBody();
|
||||
|
||||
$this->assertStringContainsString('noindex', $html);
|
||||
}
|
||||
|
||||
public function testSitemapsContainOnlyCanonicalUrls(): void
|
||||
{
|
||||
$index = (string) $this->get('/sitemap.xml')->getBody();
|
||||
$this->assertStringContainsString('sitemapindex', $index);
|
||||
$this->assertStringContainsString('sitemap-tools-1.xml', $index);
|
||||
$this->assertStringContainsString('sitemap-guides.xml', $index);
|
||||
|
||||
$tools = (string) $this->get('/sitemap-tools-1.xml')->getBody();
|
||||
$this->assertStringContainsString('/youtube-to-mp3</loc>', $tools);
|
||||
$this->assertDoesNotMatchRegularExpression('#<loc>[^<]*/(api/|admin|download/|search\?)#', $tools, 'Non-indexable URL leaked into sitemap');
|
||||
|
||||
$categories = (string) $this->get('/sitemap-categories.xml')->getBody();
|
||||
$this->assertStringContainsString('/youtube-tools</loc>', $categories);
|
||||
}
|
||||
|
||||
public function testRobotsTxtBlocksProcessingEndpoints(): void
|
||||
{
|
||||
$body = (string) $this->get('/robots.txt')->getBody();
|
||||
|
||||
$this->assertStringContainsString('Disallow: /api/', $body);
|
||||
$this->assertStringContainsString('Disallow: /download/', $body);
|
||||
$this->assertStringContainsString('Disallow: /admin', $body);
|
||||
$this->assertStringContainsString('Sitemap:', $body);
|
||||
}
|
||||
|
||||
public function testUppercaseUrl301RedirectsToCanonical(): void
|
||||
{
|
||||
$this->get('/YouTube-To-MP3')->assertStatus(301);
|
||||
}
|
||||
|
||||
public function testUnknownSlugReturnsUseful404(): void
|
||||
{
|
||||
$result = $this->get('/this-tool-does-not-exist');
|
||||
$result->assertStatus(404);
|
||||
$html = (string) $result->getBody();
|
||||
$this->assertStringContainsString('Tool not found', $html);
|
||||
$this->assertStringContainsString('Popular tools', $html, '404 page must link popular tools');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ExampleSessionTest extends CIUnitTestCase
|
||||
{
|
||||
public function testSessionSimple(): void
|
||||
{
|
||||
$session = service('session');
|
||||
|
||||
$session->set('logged_in', 123);
|
||||
$this->assertSame(123, $session->get('logged_in'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Libraries\CatalogBuilder;
|
||||
use App\Libraries\FormatCatalog;
|
||||
use App\Libraries\Pipeline\Youtube as YoutubeLib;
|
||||
use App\Libraries\UrlGuard;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
/**
|
||||
* Pure unit tests — no database required.
|
||||
*/
|
||||
final class CatalogTest extends CIUnitTestCase
|
||||
{
|
||||
public function testCatalogBuildsUniqueSlugs(): void
|
||||
{
|
||||
$slugs = array_column(CatalogBuilder::build(), 'slug');
|
||||
|
||||
$this->assertSame($slugs, array_values(array_unique($slugs)), 'Tool slugs must be unique');
|
||||
$this->assertGreaterThan(100, count($slugs));
|
||||
}
|
||||
|
||||
public function testEveryToolHasCompleteSeoFields(): void
|
||||
{
|
||||
foreach (CatalogBuilder::build() as $tool) {
|
||||
$this->assertNotSame('', trim((string) $tool['seo_title']), "seo_title empty for {$tool['slug']}");
|
||||
$this->assertNotSame('', trim((string) $tool['h1']), "h1 empty for {$tool['slug']}");
|
||||
$this->assertGreaterThanOrEqual(20, mb_strlen((string) $tool['seo_description']), "short description for {$tool['slug']}");
|
||||
$this->assertNotEmpty($tool['how_to'], "how_to missing for {$tool['slug']}");
|
||||
$this->assertNotEmpty($tool['faqs'], "faqs missing for {$tool['slug']}");
|
||||
$this->assertLessThanOrEqual(65, mb_strlen((string) $tool['seo_title']) + 0, "title too long for {$tool['slug']}");
|
||||
}
|
||||
}
|
||||
|
||||
public function testConverterTitlesAreFormatSpecific(): void
|
||||
{
|
||||
$bySlug = [];
|
||||
foreach (CatalogBuilder::build() as $tool) {
|
||||
$bySlug[$tool['slug']] = $tool;
|
||||
}
|
||||
|
||||
// each converter page must mention its actual formats
|
||||
foreach (['jpg-to-png', 'mp4-to-webm', 'flac-to-mp3'] as $slug) {
|
||||
$this->assertArrayHasKey($slug, $bySlug);
|
||||
$title = (string) $bySlug[$slug]['seo_title'];
|
||||
[$from, $to] = explode('-to-', $slug);
|
||||
$this->assertStringContainsString(FormatCatalog::name($from), $title);
|
||||
$this->assertStringContainsString(FormatCatalog::name($to), $title);
|
||||
}
|
||||
}
|
||||
|
||||
public function testYoutubeIdExtraction(): void
|
||||
{
|
||||
$cases = [
|
||||
'https://www.youtube.com/watch?v=dQw4w9WgXcQ' => 'dQw4w9WgXcQ',
|
||||
'https://youtu.be/dQw4w9WgXcQ?t=42' => 'dQw4w9WgXcQ',
|
||||
'https://www.youtube.com/shorts/abcdefghijk' => 'abcdefghijk',
|
||||
'https://www.youtube.com/embed/abcdefghijk' => 'abcdefghijk',
|
||||
'dQw4w9WgXcQ' => 'dQw4w9WgXcQ',
|
||||
'https://evil.com/watch?v=dQw4w9WgXcQ' => null,
|
||||
'not a url' => null,
|
||||
];
|
||||
|
||||
foreach ($cases as $input => $expected) {
|
||||
$this->assertSame($expected, YoutubeLib::extractId($input), "Failed extracting from: {$input}");
|
||||
}
|
||||
}
|
||||
|
||||
public function testUrlGuardBlocksPrivateNetworks(): void
|
||||
{
|
||||
$guard = new UrlGuard();
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$guard->check('http://127.0.0.1/secret');
|
||||
}
|
||||
|
||||
public function testUrlGuardBlocksFileScheme(): void
|
||||
{
|
||||
$guard = new UrlGuard();
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$guard->check('file:///etc/passwd');
|
||||
}
|
||||
|
||||
public function testIsPublicIp(): void
|
||||
{
|
||||
$guard = new UrlGuard();
|
||||
|
||||
$this->assertFalse($guard->isPublicIp('10.0.0.5'));
|
||||
$this->assertFalse($guard->isPublicIp('192.168.1.1'));
|
||||
$this->assertFalse($guard->isPublicIp('169.254.169.254')); // cloud metadata
|
||||
$this->assertTrue($guard->isPublicIp('142.250.185.78'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Config\App;
|
||||
use Tests\Support\Libraries\ConfigReader;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HealthTest extends CIUnitTestCase
|
||||
{
|
||||
public function testIsDefinedAppPath(): void
|
||||
{
|
||||
$this->assertTrue(defined('APPPATH'));
|
||||
}
|
||||
|
||||
public function testBaseUrlHasBeenSet(): void
|
||||
{
|
||||
$validation = service('validation');
|
||||
|
||||
$env = false;
|
||||
|
||||
// Check the baseURL in .env
|
||||
if (is_file(HOMEPATH . '.env')) {
|
||||
$env = preg_grep('/^app\.baseURL = ./', file(HOMEPATH . '.env')) !== false;
|
||||
}
|
||||
|
||||
if ($env) {
|
||||
// BaseURL in .env is a valid URL?
|
||||
// phpunit.dist.xml sets app.baseURL in $_SERVER
|
||||
// So if you set app.baseURL in .env, it takes precedence
|
||||
$config = new App();
|
||||
$this->assertTrue(
|
||||
$validation->check($config->baseURL, 'valid_url'),
|
||||
'baseURL "' . $config->baseURL . '" in .env is not valid URL',
|
||||
);
|
||||
}
|
||||
|
||||
// Get the baseURL in app/Config/App.php
|
||||
// You can't use Config\App, because phpunit.dist.xml sets app.baseURL
|
||||
$reader = new ConfigReader();
|
||||
|
||||
// BaseURL in app/Config/App.php is a valid URL?
|
||||
$this->assertTrue(
|
||||
$validation->check($reader->baseURL, 'valid_url'),
|
||||
'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user