diff --git a/tests/Feature/StorageDriverTest.php b/tests/Feature/StorageDriverTest.php new file mode 100644 index 0000000..198e449 --- /dev/null +++ b/tests/Feature/StorageDriverTest.php @@ -0,0 +1,215 @@ +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); + } + } +}