You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

107 lines
3.4 KiB

2 days ago
<?php
namespace Tests\Feature;
use App\Models\User;
use App\Models\WechatUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
final class AuthenticatableWechatUserStub extends WechatUser implements Authenticatable
{
use \Illuminate\Auth\Authenticatable;
}
class SecureUploadApiTest extends TestCase
{
public function test_admin_upload_accepts_whitelist_image_and_returns_storage_url(): void
{
Storage::fake('public');
Sanctum::actingAs($this->adminUser());
$file = UploadedFile::fake()->image('cover.png', 16, 16);
$response = $this->post('/api/upload', ['file' => $file], ['Accept' => 'application/json']);
$response->assertOk();
$url = (string) $response->json('url');
$path = (string) $response->json('path');
$this->assertMatchesRegularExpression('#^uploads/[0-9a-f-]{36}\.png$#', $path);
$this->assertStringEndsWith('/storage/'.$path, $url);
Storage::disk('public')->assertExists($path);
}
/**
* @dataProvider rejectedUploadNamesProvider
*/
public function test_admin_upload_rejects_unsafe_names_without_leaving_files(string $clientName): void
{
Storage::fake('public');
Sanctum::actingAs($this->adminUser());
$file = UploadedFile::fake()->create($clientName, 8, 'text/plain');
$response = $this->post('/api/upload', ['file' => $file], ['Accept' => 'application/json']);
$response->assertStatus(422);
$this->assertSame([], Storage::disk('public')->allFiles('uploads'));
}
public function test_h5_upload_uses_same_constraints(): void
{
Storage::fake('public');
Sanctum::actingAs($this->wechatUser());
$ok = UploadedFile::fake()->image('avatar.png', 16, 16);
$okResponse = $this->post('/api/h5/upload', ['file' => $ok], ['Accept' => 'application/json']);
$okResponse->assertOk();
$path = (string) $okResponse->json('path');
$this->assertMatchesRegularExpression('#^uploads/h5/[0-9a-f-]{36}\.png$#', $path);
Storage::disk('public')->assertExists($path);
$denied = UploadedFile::fake()->create('avatar.php', 8, 'text/plain');
$deniedResponse = $this->post('/api/h5/upload', ['file' => $denied], ['Accept' => 'application/json']);
$deniedResponse->assertStatus(422);
$this->assertCount(1, Storage::disk('public')->allFiles('uploads/h5'));
}
/**
* @return array<string, array{0: string}>
*/
public static function rejectedUploadNamesProvider(): array
{
return [
'php' => ['note.php'],
'phtml' => ['note.phtml'],
'phar' => ['note.phar'],
'php_jpg' => ['1.php.jpg'],
'php_png' => ['xxx.php.png'],
];
}
private function adminUser(): User
{
$user = new User([
'username' => 'upload_admin',
'name' => '上传测试',
'role' => 'super_admin',
'is_active' => true,
]);
$user->id = 1;
return $user;
}
private function wechatUser(): WechatUser
{
$user = new AuthenticatableWechatUserStub([
'openid' => 'otest_upload_user',
'nickname' => 'tester',
]);
$user->id = 1;
return $user;
}
}