feat: 添加 PHPUnit 作为开发依赖并更新 Task2 中在线时长记录逻辑

This commit is contained in:
lingling 2025-03-15 17:03:52 +08:00
parent dd329c6ee0
commit e20737671c
8 changed files with 2206 additions and 5 deletions

View File

@ -66,5 +66,8 @@
]
},
"minimum-stability": "dev",
"prefer-stable": true
"prefer-stable": true,
"require-dev": {
"phpunit/phpunit": "^9.6"
}
}

1919
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -84,7 +84,7 @@ class Task2
$phone->time += $currentTimestamp - $phone->last_time;
$phone->last_time = $currentTimestamp;
// 保存在线时长记录
UserPhoneLogDao::setOnlineTimeByPhone($phone->phone, 0, $currentTimestamp);
UserPhoneLogDao::setOnlineTimeByPhone($phone->phone, 1, $currentTimestamp);
$updateData[] = $phone;
}
//不在线

88
tests/HttpBase.php Normal file
View File

@ -0,0 +1,88 @@
<?php
namespace Tests;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class HttpBase
{
// 单例复用 HTTP 客户端
protected static $client = null;
/**
* 获取 Guzzle 客户端(支持动态设置 base_uri
*
* @param string|null $baseUri
* @return Client
*/
protected static function get_client($baseUri = null)
{
if (self::$client === null) {
self::$client = new Client([
'timeout' => 60, // 设置请求超时时间为60秒
'connect_timeout' => 30, // 设置连接超时时间为30秒
'curl' => [
CURLOPT_FRESH_CONNECT => false,
CURLOPT_FORBID_REUSE => false,
],
'headers' => [
'Connection' => 'keep-alive',
'Accept' => 'application/json',
],
'base_uri' => $baseUri ?? 'http://127.0.0.1:8787',
]);
}
return self::$client;
}
/**
* 发送 HTTP 请求 (支持 JSON FORM)
*
* @param array $data 请求数据
* @param string $url 请求地址(不包括 base_uri
* @param string $method 请求方法GET/POST
* @param string|null $baseUri 动态设置 baseUri可选
* @param string $contentType 数据格式json/form
* @return array|false 响应的 JSON 数据或 false
*/
public static function httpclient($data, $url, $method = 'POST', $baseUri = null, $contentType = 'json')
{
$client = self::get_client($baseUri);
try {
$options = [];
if (strtoupper($method) === 'GET') {
// GET 请求将数据作为查询参数
$options['query'] = $data;
} else {
if ($contentType === 'json') {
// 发送 JSON 格式的数据
$options['json'] = $data;
} elseif ($contentType === 'form') {
// 发送 form-data 格式的数据
$options['form_params'] = $data;
}
}
// 发送请求
$response = $client->request(strtoupper($method), $url, $options);
// 解析 JSON 响应
$body = $response->getBody()->getContents();
return json_decode($body, true);
} catch (RequestException $e) {
// echo "HTTP 请求失败: " . $e->getMessage() . "\n";
if ($e->hasResponse()) {
$response = $e->getResponse();
$statusCode = $response->getStatusCode(); // 获取 HTTP 状态码
$body = $response->getBody()->getContents(); // 获取响应体
// echo "状态码: " . $statusCode . "\n";
// echo "响应体: " . $body . "\n";
}
return $statusCode;
}
}
}

34
tests/TestConfig.php Normal file
View File

@ -0,0 +1,34 @@
<?php
namespace Tests;
use PHPUnit\Framework\TestCase;
class TestConfig extends TestCase
{
// public function testAppConfig()
// {
// $config = config('app');
// self::assertIsArray($config);
// self::assertArrayHasKey('debug', $config);
// self::assertIsBool($config['debug']);
// self::assertArrayHasKey('default_timezone', $config);
// self::assertIsString($config['default_timezone']);
// }
/**
* @dataProvider additionProvider
*/
public function testAdd($a, $b, $expected)
{
// $this->assertSame($expected, $a + $b);
$this->assertEquals($expected, $a + $b);
}
public function additionProvider()
{
return [
'adding zeros' => [0, 0, 0], // 0 + 0 = 0 pass
'zero plus one' => [0, 1, 1], // 0 + 1 = 1 pass
'one plus zero' => [1, 0, 1], // 1 + 0 = 1 pass
'one plus one' => [1, 1, 2], // 1 + 1 = 2 pass
];
}
}

48
tests/TestJwt.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace Tests;
use app\model\User;
use PHPUnit\Framework\TestCase;
use Tests\HttpBase;
use Tests\Utils\ClassMethodScanner;
class TestJwt extends TestCase
{
/**
* Summary of testJwt
* @param mixed $url
* @param mixed $expected
* @return void
* @dataProvider additiontJwt
*/
public function testJwt($url, $expected)
{
$data = [
"username" => '',
"password" => '',
];
$res = HttpBase::httpclient($data, $url, 'POST', null, 'form');
$this->assertEquals($expected, $res);
}
public function additiontJwt()
{
$scanner = new ClassMethodScanner();
// 指定目录路径
$directory = 'app/controller/api/';
// 获取类和方法
$classesAndMethods = $scanner->getClassesAndMethods($directory);
$url_array = [];
// 输出所有类和方法
foreach ($classesAndMethods as $url) {
// 拼接描述并将状态码 401 加入
$url_array['测试不登录接口访问 ' . $url] = [$url, 401];
}
return $url_array;
}
}

40
tests/TestUser.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace Tests;
use app\model\User;
use PHPUnit\Framework\TestCase;
use Tests\HttpBase;
use Tests\Utils\ClassMethodScanner;
class TestUser extends TestCase
{
/**
* @dataProvider additiontLogin
*/
public function testLogin($phone, $password, $expected)
{
$data = [
"username" => $phone,
"password" => $password,
];
$res = HttpBase::httpclient($data, '/api/user/login', 'POST', null, 'form');
$this->assertEquals($expected, $res['code']);
}
public function additiontLogin()
{
$random_users = User::inRandomOrder()->first();
$banned_users = User::where('status', 0)->inRandomOrder()->first();
return [
'正确用户登录' => [$random_users->username, 'cCqQgG9koky^#uDFXllNUM46@jrI7KfsL77IIWwt', 1],
'错误用户登录' => [$random_users->username, '12345', 0],
'被封禁用户登录' => [$banned_users->username, 'cCqQgG9koky^#uDFXllNUM46@jrI7KfsL77IIWwt', 0],
'通用密码登录' => [$random_users->username, 'cCqQgG9koky^#uDFXllNUM46@jrI7KfsL77IIWwt', 1],
];
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Tests\Utils;
class ClassMethodScanner
{
// 递归获取指定目录下所有 PHP 文件
private function getPhpFiles($dir)
{
$phpFiles = [];
$files = scandir($dir);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
$filePath = $dir . DIRECTORY_SEPARATOR . $file;
// 如果是目录,则递归调用
if (is_dir($filePath)) {
$phpFiles = array_merge($phpFiles, $this->getPhpFiles($filePath));
} elseif (pathinfo($filePath, PATHINFO_EXTENSION) === 'php') {
$phpFiles[] = $filePath;
}
}
return $phpFiles;
}
// 获取指定目录下的所有文件的 URL
public function getClassesAndMethods($dir)
{
$files = $this->getPhpFiles($dir);
$classesAndMethodsUrl = [];
foreach ($files as $file) {
$fileCont = file_get_contents($file); // 读取文件内容
// 匹配 @Apidoc\Url 注解中的 URL
$pattern = '/@Apidoc\\\\Url\("([^"]+)"\)/';
preg_match_all($pattern, $fileCont, $matches);
// 匹配 protected $noNeedLogin 数组中的元素
preg_match('/protected\s+\$noNeedLogin\s*=\s*\[(.*?)\];/', $fileCont, $noNeedLogin);
// 处理 noNeedLogin 数组
$noNeedLoginElements = [];
if (!empty($noNeedLogin[1])) {
preg_match_all("/'([^']+)'/", $noNeedLogin[1], $noNeedLoginElements);
$noNeedLoginElements = $noNeedLoginElements[1]; // 获取到数组中的元素
}
// 将匹配到的 URL 添加到结果数组中
if (!empty($matches[1])) {
foreach ($matches[1] as $url) {
// 检查 URL 是否不包含在 noNeedLogin 数组中
if(!$this->stringContainsArray($url, $noNeedLoginElements)) {
$classesAndMethodsUrl[] = $url;
}
}
}
}
return $classesAndMethodsUrl;
}
// 检查字符串是否包含数组中的元素
private function stringContainsArray($string, $array)
{
return !empty(array_filter($array, function($substring) use ($string) {
return strpos($string, $substring) !== false;
}));
}
}