Files
ulthon_admin/tests/Feature/StorageDriverTest.php

216 lines
7.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace tests\Feature;
use app\admin\model\SystemConfig;
use app\common\service\UploadService;
use app\common\test\TestCase;
use think\exception\ValidateException;
use think\facade\Cache;
use think\facade\Filesystem;
use think\filesystem\driver\S3;
use think\filesystem\driver\Webdav;
use think\File;
/**
* 存储驱动测试S3 与 WebDAV 磁盘解析、URL 拼接、上传白名单放行.
*
* 覆盖 storage-webdav-s3 任务的运行时行为:
* 1. Filesystem::disk('s3') / disk('webdav') 能按 sysconfig 正确解析驱动实例
* 2. 两个驱动的 url() 拼接domain + path
* 3. UploadService 对新类型 s3 / webdav 真正通过 in:{upload_allow_type} 白名单闸门
* UploadServiceBase.php:40 的校验规则,非仅构造)
*
* 隔离策略:
* - 测试自播种 sysconfig不依赖库中既有种子值写入发生在 setUp 事务内,
* tearDown 的 Db::rollback 自动撤销
* - sysconfig 有 3600s 文件缓存extend/base/helper.php sysconfig()
* 播种后必须 Cache::tag('sysconfig')->clear() 才能读到新值;
* tearDown 里回滚后同样要清一次,防止测试期缓存值泄漏给同进程后续套件
*/
class StorageDriverTest extends TestCase
{
/**
* S3 七项配置(与后台表单 / T5 种子键名一致).
*/
private const S3_KV = [
's3_access_key' => 'testkey',
's3_secret_key' => 'testsecret',
's3_region' => 'us-east-1',
's3_bucket' => 'test-bucket',
's3_domain' => 'https://s3-test.example.com',
's3_endpoint' => '',
's3_path_style' => '0',
];
/**
* WebDAV 五项配置.
*/
private const WEBDAV_KV = [
'webdav_url' => 'https://webdav-test.example.com/dav/',
'webdav_username' => 'u',
'webdav_password' => 'p',
'webdav_prefix' => '',
'webdav_domain' => 'https://webdav-test.example.com',
];
private const ALLOW_TYPE_ALL = 'local_public,alioss,qnoss,txcos,s3,webdav';
protected function tearDown(): void
{
// 先回滚事务parent 内含 Db::rollback不要手动重复
parent::tearDown();
// 回滚后文件缓存仍持有测试期值,不清会泄漏给同进程后续套件与下次运行
Cache::tag('sysconfig')->clear();
}
/**
* 播种 upload 组 sysconfig 并清缓存.
*
* 注think-orm Model 没有 updateOrCreate已验证 method_exists 为 false
* 用等价的 find → save / create 实现同样的 upsert 语义。
*/
private function seedUploadConfigs(array $kv): void
{
foreach ($kv as $name => $value) {
$row = SystemConfig::where('group', 'upload')->where('name', $name)->find();
if ($row) {
$row->save(['value' => (string) $value, 'remark' => 'test', 'sort' => 0]);
} else {
SystemConfig::create([
'group' => 'upload',
'name' => $name,
'value' => (string) $value,
'remark' => 'test',
'sort' => 0,
]);
}
}
// sysconfig 有 3600s 缓存,不清会读到旧值
Cache::tag('sysconfig')->clear();
}
/**
* 用例 1播种 s3_* 配置后 disk('s3') 解析为 S3 驱动实例.
*/
public function test_s3_disk_resolves(): void
{
$this->seedUploadConfigs(self::S3_KV + ['upload_allow_type' => self::ALLOW_TYPE_ALL]);
$disk = Filesystem::disk('s3');
$this->assertInstanceOf(S3::class, $disk);
}
/**
* 用例 2播种 webdav_* 配置后 disk('webdav') 解析为 Webdav 驱动实例.
*/
public function test_webdav_disk_resolves(): void
{
$this->seedUploadConfigs(self::WEBDAV_KV);
$disk = Filesystem::disk('webdav');
$this->assertInstanceOf(Webdav::class, $disk);
}
/**
* 用例 3S3 url() 拼接 = s3_domain + '/' + path.
*
* 本用例内重新播种全部七项url() 实时读 sysconfig且前一用例的种子
* 已随其 tearDown 回滚;--filter 单独跑本用例时磁盘首次构造也需要
* region / bucket 等配置。
*/
public function test_s3_url_concat(): void
{
$this->seedUploadConfigs(self::S3_KV);
$url = Filesystem::disk('s3')->url('upload/20260817/abc.png');
$this->assertSame('https://s3-test.example.com/upload/20260817/abc.png', $url);
}
/**
* 用例 4WebDAV url() 拼接 = webdav_domain + '/' + path.
*
* 同理本用例内重新播种全部五项url() 实时读 sysconfig + 单独运行时
* 构造 Client 需要 url/username/password
*/
public function test_webdav_url_concat(): void
{
$this->seedUploadConfigs(self::WEBDAV_KV);
$url = Filesystem::disk('webdav')->url('upload/20260817/abc.png');
$this->assertSame('https://webdav-test.example.com/upload/20260817/abc.png', $url);
}
/**
* 用例 5UploadService 对 s3 / webdav 真正通过 in:{upload_allow_type} 白名单闸门.
*
* 走 validateException 全链(白名单 + fileExt + fileSize + PHP 注入检查),
* 而非仅构造 UploadService只构造不校验是恒真断言不可取
* webdav 构造只读 uploadType 属性不连磁盘,安全。
*/
public function test_upload_service_accepts_new_types(): void
{
$this->seedUploadConfigs([
'upload_type' => 's3',
'upload_allow_type' => self::ALLOW_TYPE_ALL,
'upload_allow_ext' => 'jpg,png',
'upload_allow_size' => '1024000',
]);
$tmpPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'ulthon_t8_test.png';
file_put_contents($tmpPath, 'pngdata');
try {
$file = new File($tmpPath);
$this->assertTrue(
(new UploadService('s3'))->validateException($file),
'upload_type=s3 应通过 in:{upload_allow_type} 白名单与文件校验'
);
$this->assertTrue(
(new UploadService('webdav'))->validateException($file),
'upload_type=webdav 应通过 in:{upload_allow_type} 白名单与文件校验'
);
} finally {
@unlink($tmpPath);
}
}
/**
* 附加用例(任务允许的可选白名单拒绝场景):白名单不含 s3 时闸门应拒绝.
*
* 锁死 in:{upload_allow_type} 的拒绝方向:防止有人把白名单校验改坏后
* 用例 5 仍绿。expectException 场景下 tearDown回滚 + 清缓存)照常执行。
*/
public function test_upload_service_rejects_type_not_in_whitelist(): void
{
$this->seedUploadConfigs([
'upload_allow_type' => 'local_public,alioss,qnoss,txcos',
'upload_allow_ext' => 'jpg,png',
'upload_allow_size' => '1024000',
]);
$tmpPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'ulthon_t8_reject.png';
file_put_contents($tmpPath, 'pngdata');
try {
$file = new File($tmpPath);
$this->expectException(ValidateException::class);
$this->expectExceptionMessage('指定上传类型有误');
(new UploadService('s3'))->validateException($file);
} finally {
@unlink($tmpPath);
}
}
}