UserControllerTest.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. /**
  3. * @group database
  4. * @group feature
  5. * @group api
  6. */
  7. use App\Models\User;
  8. use App\Models\Node;
  9. use Tests\SlimTestCase;
  10. beforeEach(function () {
  11. // Initialize database before any operations
  12. $this->useDatabase = true;
  13. $this->setUp();
  14. // Clear previous test data
  15. Node::where('name', 'Test Node')->delete();
  16. User::where('email', 'LIKE', 'test%@example.com')->delete();
  17. // Create test node
  18. $this->node = new Node();
  19. $this->node->name = 'Test Node';
  20. $this->node->server = 'test.example.com';
  21. $this->node->password = bin2hex(random_bytes(32));
  22. $this->node->type = 1; // shadowsocks
  23. $this->node->sort = 14;
  24. $this->node->node_class = 0; // Allow class 0 users
  25. $this->node->node_group = 0; // No group restriction
  26. $this->node->save();
  27. });
  28. afterEach(function () {
  29. if (isset($this->node)) {
  30. $this->node->delete();
  31. }
  32. User::where('email', 'LIKE', 'test%@example.com')->delete();
  33. });
  34. describe('UserController API - Shadowsocks Node', function () {
  35. it('returns user list for shadowsocks node', function () {
  36. // Create test users
  37. $users = createUsers(3);
  38. // Send request
  39. $response = $this->get('/mod_mu/users?node_id=' . $this->node->id . '&key=' . $_ENV['muKey']);
  40. // Verify response
  41. assertResponseStatus(200, $response);
  42. assertJsonResponse($response);
  43. $data = getJsonData($response);
  44. expect($data['ret'])->toBe(1)
  45. ->and($data['data'])->toHaveCount(3);
  46. // Verify returned fields
  47. // Note: Controller's $keys_unset are fields to be deleted
  48. // For sort 14, removes: u, d, transfer_enable, method, port, passwd, node_iplimit
  49. $userData = $data['data'][0];
  50. // These fields should be removed
  51. expect($userData)->not->toHaveKeys(['u', 'd', 'transfer_enable', 'method', 'port', 'passwd', 'node_iplimit']);
  52. // These fields should exist
  53. expect($userData)->toHaveKeys(['id', 'uuid', 'node_speedlimit']);
  54. });
  55. });
  56. describe('UserController API - V2Ray Node', function () {
  57. it('returns user list for v2ray node', function () {
  58. // Update node type
  59. $this->node->sort = 1;
  60. $this->node->save();
  61. // Create test users
  62. $users = createUsers(2);
  63. // Send request
  64. $response = $this->get('/mod_mu/users?node_id=' . $this->node->id . '&key=' . $_ENV['muKey']);
  65. // Verify response
  66. assertResponseStatus(200, $response);
  67. assertJsonResponse($response);
  68. $data = getJsonData($response);
  69. expect($data['ret'])->toBe(1);
  70. // Verify V2Ray node returns uuid
  71. $userData = $data['data'][0];
  72. expect($userData)
  73. ->toHaveKey('uuid')
  74. ->not->toHaveKey('passwd');
  75. });
  76. });
  77. describe('UserController API Authentication', function () {
  78. it('rejects invalid node key', function () {
  79. // Use wrong key
  80. $response = $this->get('/mod_mu/users?node_id=' . $this->node->id . '&key=invalid_key');
  81. // Verify returns 401
  82. assertResponseStatus(401, $response);
  83. });
  84. });
  85. describe('UserController API Traffic Reporting', function () {
  86. it('updates user traffic', function () {
  87. // Create test user
  88. $user = createUsers(1)[0];
  89. $initialU = $user->u;
  90. $initialD = $user->d;
  91. // Report traffic
  92. $trafficData = [
  93. [
  94. 'user_id' => $user->id,
  95. 'u' => 1024 * 1024, // 1MB
  96. 'd' => 2048 * 1024, // 2MB
  97. ]
  98. ];
  99. $response = $this->post('/mod_mu/users/traffic?node_id=' . $this->node->id . '&key=' . $_ENV['muKey'], [
  100. 'data' => json_encode($trafficData),
  101. ]);
  102. // Verify response
  103. assertResponseStatus(200, $response);
  104. assertJsonResponse($response);
  105. $data = getJsonData($response);
  106. expect($data['data'])->toBe('ok');
  107. // Verify traffic update
  108. $user->refresh();
  109. expect($user->u)->toBe($initialU + 1024 * 1024)
  110. ->and($user->d)->toBe($initialD + 2048 * 1024);
  111. });
  112. });
  113. describe('UserController API Node Status', function () {
  114. it('updates node online status', function () {
  115. // Send heartbeat
  116. $response = $this->post('/mod_mu/nodes/alive?node_id=' . $this->node->id . '&key=' . $_ENV['muKey'], [
  117. 'load' => '0.5',
  118. 'uptime' => '86400',
  119. ]);
  120. // Verify response
  121. assertResponseStatus(200, $response);
  122. // Verify node status update
  123. $this->node->refresh();
  124. expect($this->node->status)->toBe('online')
  125. ->and(strtotime($this->node->last_check_at))->toBeGreaterThan(time() - 60);
  126. })->skip('Node alive endpoint not implemented yet');
  127. });
  128. describe('UserController API Caching', function () {
  129. it('supports etag caching', function () {
  130. // First request
  131. $response1 = $this->get('/mod_mu/users?node_id=' . $this->node->id . '&key=' . $_ENV['muKey']);
  132. assertResponseStatus(200, $response1);
  133. $etag = $response1->getHeaderLine('ETag');
  134. expect($etag)->not->toBeEmpty();
  135. // Second request with ETag
  136. $response2 = $this->get('/mod_mu/users?node_id=' . $this->node->id . '&key=' . $_ENV['muKey'], [
  137. 'If-None-Match' => $etag,
  138. ]);
  139. // Should return 304
  140. assertResponseStatus(304, $response2);
  141. })->skip('ETag caching not implemented in current API');
  142. });
  143. // Helper functions specific to this test file
  144. /**
  145. * Assert response status code
  146. */
  147. if (!function_exists('assertResponseStatus')) {
  148. function assertResponseStatus(int $expected, $response): void
  149. {
  150. expect($response->getStatusCode())->toBe($expected);
  151. }
  152. }
  153. /**
  154. * Get JSON data from response
  155. */
  156. if (!function_exists('getJsonData')) {
  157. function getJsonData($response): array
  158. {
  159. return json_decode((string) $response->getBody(), true);
  160. }
  161. }
  162. /**
  163. * Create test users for API tests
  164. */
  165. function createUsers(int $count): array
  166. {
  167. $users = [];
  168. for ($i = 0; $i < $count; $i++) {
  169. $user = new User();
  170. $user->email = "test{$i}@example.com";
  171. $user->user_name = "testuser{$i}";
  172. $user->pass = password_hash('password', PASSWORD_DEFAULT);
  173. $user->api_token = bin2hex(random_bytes(32)); // Add unique API token
  174. $user->port = 10000 + $i;
  175. $user->passwd = bin2hex(random_bytes(16));
  176. $user->uuid = \Ramsey\Uuid\Uuid::uuid4()->toString();
  177. $user->method = 'aes-256-gcm';
  178. $user->transfer_enable = 1099511627776; // 1TB
  179. $user->u = rand(0, 1000000);
  180. $user->d = rand(0, 1000000);
  181. $user->node_iplimit = 0;
  182. $user->node_speedlimit = 0;
  183. $user->node_group = 0;
  184. $user->class = 0;
  185. $user->is_banned = 0;
  186. $user->class_expire = date('Y-m-d H:i:s', strtotime('+1 year'));
  187. $user->save();
  188. $users[] = $user;
  189. }
  190. return $users;
  191. }