RouteTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. <?php
  2. namespace think\tests;
  3. use Closure;
  4. use Mockery as m;
  5. use Mockery\MockInterface;
  6. use PHPUnit\Framework\TestCase;
  7. use think\helper\Str;
  8. use think\Request;
  9. use think\response\Redirect;
  10. use think\response\View;
  11. use think\Route;
  12. class RouteTest extends TestCase
  13. {
  14. use InteractsWithApp;
  15. /** @var Route|MockInterface */
  16. protected $route;
  17. protected function tearDown(): void
  18. {
  19. m::close();
  20. }
  21. protected function setUp(): void
  22. {
  23. $this->prepareApp();
  24. $this->config->shouldReceive('get')->with('route')->andReturn(['url_route_must' => true]);
  25. $this->route = new Route($this->app);
  26. }
  27. /**
  28. * @param $path
  29. * @param string $method
  30. * @param string $host
  31. * @return m\Mock|Request
  32. */
  33. protected function makeRequest($path, $method = 'GET', $host = 'localhost')
  34. {
  35. $request = m::mock(Request::class)->makePartial();
  36. $request->shouldReceive('host')->andReturn($host);
  37. $request->shouldReceive('pathinfo')->andReturn($path);
  38. $request->shouldReceive('url')->andReturn('/' . $path);
  39. $request->shouldReceive('method')->andReturn(strtoupper($method));
  40. return $request;
  41. }
  42. public function testSimpleRequest()
  43. {
  44. $this->route->get('foo', function () {
  45. return 'get-foo';
  46. });
  47. $this->route->put('foo', function () {
  48. return 'put-foo';
  49. });
  50. $this->route->group(function () {
  51. $this->route->post('foo', function () {
  52. return 'post-foo';
  53. });
  54. });
  55. $request = $this->makeRequest('foo', 'post');
  56. $response = $this->route->dispatch($request);
  57. $this->assertEquals(200, $response->getCode());
  58. $this->assertEquals('post-foo', $response->getContent());
  59. $request = $this->makeRequest('foo', 'get');
  60. $response = $this->route->dispatch($request);
  61. $this->assertEquals(200, $response->getCode());
  62. $this->assertEquals('get-foo', $response->getContent());
  63. }
  64. public function testGroup()
  65. {
  66. $this->route->group(function () {
  67. $this->route->group('foo', function () {
  68. $this->route->post('bar', function () {
  69. return 'hello,world!';
  70. });
  71. });
  72. });
  73. $request = $this->makeRequest('foo/bar', 'post');
  74. $response = $this->route->dispatch($request);
  75. $this->assertEquals(200, $response->getCode());
  76. $this->assertEquals('hello,world!', $response->getContent());
  77. }
  78. public function testAllowCrossDomain()
  79. {
  80. $this->route->get('foo', function () {
  81. return 'get-foo';
  82. })->allowCrossDomain(['some' => 'bar']);
  83. $request = $this->makeRequest('foo', 'get');
  84. $response = $this->route->dispatch($request);
  85. $this->assertEquals('bar', $response->getHeader('some'));
  86. $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader());
  87. //$this->expectException(RouteNotFoundException::class);
  88. $request = $this->makeRequest('foo2', 'options');
  89. $this->route->dispatch($request);
  90. }
  91. public function testControllerDispatch()
  92. {
  93. $this->route->get('foo', 'foo/bar');
  94. $controller = m::Mock(\stdClass::class);
  95. $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName());
  96. $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
  97. $controller->shouldReceive('bar')->andReturn('bar');
  98. $request = $this->makeRequest('foo');
  99. $response = $this->route->dispatch($request);
  100. $this->assertEquals('bar', $response->getContent());
  101. }
  102. public function testEmptyControllerDispatch()
  103. {
  104. $this->route->get('foo', 'foo/bar');
  105. $controller = m::Mock(\stdClass::class);
  106. $this->app->shouldReceive('parseClass')->with('controller', 'Error')->andReturn($controller->mockery_getName());
  107. $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
  108. $controller->shouldReceive('bar')->andReturn('bar');
  109. $request = $this->makeRequest('foo');
  110. $response = $this->route->dispatch($request);
  111. $this->assertEquals('bar', $response->getContent());
  112. }
  113. protected function createMiddleware($times = 1)
  114. {
  115. $middleware = m::mock(Str::random(5));
  116. $middleware->shouldReceive('handle')->times($times)->andReturnUsing(function ($request, Closure $next) {
  117. return $next($request);
  118. });
  119. $this->app->shouldReceive('make')->with($middleware->mockery_getName())->andReturn($middleware);
  120. return $middleware;
  121. }
  122. public function testControllerWithMiddleware()
  123. {
  124. $this->route->get('foo', 'foo/bar');
  125. $controller = m::mock(FooClass::class);
  126. $controller->middleware = [
  127. $this->createMiddleware()->mockery_getName() . ":params1:params2",
  128. $this->createMiddleware(0)->mockery_getName() => ['except' => 'bar'],
  129. $this->createMiddleware()->mockery_getName() => ['only' => 'bar'],
  130. [
  131. 'middleware' => [$this->createMiddleware()->mockery_getName(), [new \stdClass()]],
  132. 'options' => ['only' => 'bar'],
  133. ],
  134. ];
  135. $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName());
  136. $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
  137. $controller->shouldReceive('bar')->once()->andReturn('bar');
  138. $request = $this->makeRequest('foo');
  139. $response = $this->route->dispatch($request);
  140. $this->assertEquals('bar', $response->getContent());
  141. }
  142. public function testRedirectDispatch()
  143. {
  144. $this->route->redirect('foo', 'http://localhost', 302);
  145. $request = $this->makeRequest('foo');
  146. $this->app->shouldReceive('make')->with(Request::class)->andReturn($request);
  147. $response = $this->route->dispatch($request);
  148. $this->assertInstanceOf(Redirect::class, $response);
  149. $this->assertEquals(302, $response->getCode());
  150. $this->assertEquals('http://localhost', $response->getData());
  151. }
  152. public function testViewDispatch()
  153. {
  154. $this->route->view('foo', 'index/hello', ['city' => 'shanghai']);
  155. $request = $this->makeRequest('foo');
  156. $response = $this->route->dispatch($request);
  157. $this->assertInstanceOf(View::class, $response);
  158. $this->assertEquals(['city' => 'shanghai'], $response->getVars());
  159. $this->assertEquals('index/hello', $response->getData());
  160. }
  161. public function testDomainBindResponse()
  162. {
  163. $this->route->domain('test', function () {
  164. $this->route->get('/', function () {
  165. return 'Hello,ThinkPHP';
  166. });
  167. });
  168. $request = $this->makeRequest('', 'get', 'test.domain.com');
  169. $response = $this->route->dispatch($request);
  170. $this->assertEquals('Hello,ThinkPHP', $response->getContent());
  171. $this->assertEquals(200, $response->getCode());
  172. }
  173. public function testResourceRouting()
  174. {
  175. // Test basic resource registration (returns ResourceRegister when not lazy)
  176. $resource = $this->route->resource('users', 'Users');
  177. $this->assertTrue($resource instanceof \think\route\Resource || $resource instanceof \think\route\ResourceRegister);
  178. // Test REST methods configuration
  179. $restMethods = $this->route->getRest();
  180. $this->assertIsArray($restMethods);
  181. $this->assertArrayHasKey('index', $restMethods);
  182. $this->assertArrayHasKey('create', $restMethods);
  183. $this->assertArrayHasKey('save', $restMethods);
  184. $this->assertArrayHasKey('read', $restMethods);
  185. $this->assertArrayHasKey('edit', $restMethods);
  186. $this->assertArrayHasKey('update', $restMethods);
  187. $this->assertArrayHasKey('delete', $restMethods);
  188. // Test custom REST method modification
  189. $this->route->rest('custom', ['get', '/custom', 'customAction']);
  190. $customMethod = $this->route->getRest('custom');
  191. $this->assertEquals(['get', '/custom', 'customAction'], $customMethod);
  192. }
  193. public function testUrlGeneration()
  194. {
  195. $this->route->get('user/<id>', 'User/detail')->name('user.detail');
  196. $this->route->post('user', 'User/save')->name('user.save');
  197. $urlBuild = $this->route->buildUrl('user.detail', ['id' => 123]);
  198. $this->assertInstanceOf(\think\route\Url::class, $urlBuild);
  199. $urlBuild = $this->route->buildUrl('user.save');
  200. $this->assertInstanceOf(\think\route\Url::class, $urlBuild);
  201. }
  202. public function testRouteParameterBinding()
  203. {
  204. $this->route->get('user/<id>', function ($id) {
  205. return "User ID: $id";
  206. });
  207. $request = $this->makeRequest('user/123', 'get');
  208. $response = $this->route->dispatch($request);
  209. $this->assertEquals('User ID: 123', $response->getContent());
  210. // Test multiple parameters
  211. $this->route->get('post/<year>/<month>', function ($year, $month) {
  212. return "Year: $year, Month: $month";
  213. });
  214. $request = $this->makeRequest('post/2024/12', 'get');
  215. $response = $this->route->dispatch($request);
  216. $this->assertEquals('Year: 2024, Month: 12', $response->getContent());
  217. }
  218. public function testRoutePatternValidation()
  219. {
  220. $this->route->get('user/<id>', function ($id) {
  221. return "User ID: $id";
  222. })->pattern(['id' => '\d+']);
  223. // Valid numeric ID
  224. $request = $this->makeRequest('user/123', 'get');
  225. $response = $this->route->dispatch($request);
  226. $this->assertEquals('User ID: 123', $response->getContent());
  227. // Test pattern with name validation
  228. $this->route->get('profile/<name>', function ($name) {
  229. return "Profile: $name";
  230. })->pattern(['name' => '[a-zA-Z]+']);
  231. $request = $this->makeRequest('profile/john', 'get');
  232. $response = $this->route->dispatch($request);
  233. $this->assertEquals('Profile: john', $response->getContent());
  234. }
  235. public function testMissRoute()
  236. {
  237. $this->route->get('home', function () {
  238. return 'home page';
  239. });
  240. $this->route->miss(function () {
  241. return 'Page not found';
  242. });
  243. // Test existing route
  244. $request = $this->makeRequest('home', 'get');
  245. $response = $this->route->dispatch($request);
  246. $this->assertEquals('home page', $response->getContent());
  247. // Test miss route
  248. $request = $this->makeRequest('nonexistent', 'get');
  249. $response = $this->route->dispatch($request);
  250. $this->assertEquals('Page not found', $response->getContent());
  251. }
  252. public function testRouteMiddleware()
  253. {
  254. $middleware = $this->createMiddleware();
  255. $this->route->get('protected', function () {
  256. return 'protected content';
  257. })->middleware($middleware->mockery_getName());
  258. $request = $this->makeRequest('protected', 'get');
  259. $response = $this->route->dispatch($request);
  260. $this->assertEquals('protected content', $response->getContent());
  261. }
  262. public function testRouteOptions()
  263. {
  264. $this->route->get('api/<version>/users', function ($version) {
  265. return "API Version: $version";
  266. })->option(['version' => '1.0']);
  267. $request = $this->makeRequest('api/v2/users', 'get');
  268. $response = $this->route->dispatch($request);
  269. $this->assertEquals('API Version: v2', $response->getContent());
  270. }
  271. public function testRouteCache()
  272. {
  273. // Test route configuration
  274. $config = $this->route->config();
  275. $this->assertIsArray($config);
  276. $caseConfig = $this->route->config('url_case_sensitive');
  277. $this->assertIsBool($caseConfig);
  278. // Test route name management
  279. $this->route->get('test', function () {
  280. return 'test';
  281. })->name('test.route');
  282. $names = $this->route->getName('test.route');
  283. $this->assertIsArray($names);
  284. }
  285. }
  286. class FooClass
  287. {
  288. public $middleware = [];
  289. public function bar()
  290. {
  291. }
  292. }