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); } /** * 用例 3:S3 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); } /** * 用例 4:WebDAV 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); } /** * 用例 5:UploadService 对 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); } } }