CustomerOrderTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. <?php
  2. namespace Webkul\BagistoApi\Tests\Feature\GraphQL;
  3. use Webkul\BagistoApi\Tests\GraphQLTestCase;
  4. use Webkul\Core\Models\Channel;
  5. use Webkul\Customer\Models\Customer;
  6. use Webkul\Product\Models\Product;
  7. use Webkul\Sales\Models\Order;
  8. use Webkul\Sales\Models\OrderItem;
  9. use Webkul\Sales\Models\OrderPayment;
  10. class CustomerOrderTest extends GraphQLTestCase
  11. {
  12. /**
  13. * Create test data — customer with orders
  14. */
  15. private function createTestData(): array
  16. {
  17. $this->seedRequiredData();
  18. $customer = $this->createCustomer();
  19. $channel = Channel::first();
  20. $product = Product::factory()->create();
  21. $order1 = Order::factory()->create([
  22. 'customer_id' => $customer->id,
  23. 'customer_email' => $customer->email,
  24. 'customer_first_name' => $customer->first_name,
  25. 'customer_last_name' => $customer->last_name,
  26. 'channel_id' => $channel->id,
  27. 'status' => 'pending',
  28. ]);
  29. OrderItem::factory()->create([
  30. 'order_id' => $order1->id,
  31. 'product_id' => $product->id,
  32. 'sku' => 'TEST-SKU-001',
  33. 'type' => 'simple',
  34. 'name' => 'Test Product One',
  35. ]);
  36. OrderPayment::factory()->create([
  37. 'order_id' => $order1->id,
  38. ]);
  39. $order2 = Order::factory()->create([
  40. 'customer_id' => $customer->id,
  41. 'customer_email' => $customer->email,
  42. 'customer_first_name' => $customer->first_name,
  43. 'customer_last_name' => $customer->last_name,
  44. 'channel_id' => $channel->id,
  45. 'status' => 'completed',
  46. ]);
  47. OrderItem::factory()->create([
  48. 'order_id' => $order2->id,
  49. 'product_id' => $product->id,
  50. 'sku' => 'TEST-SKU-002',
  51. 'type' => 'simple',
  52. 'name' => 'Test Product Two',
  53. ]);
  54. OrderPayment::factory()->create([
  55. 'order_id' => $order2->id,
  56. ]);
  57. return compact('customer', 'channel', 'product', 'order1', 'order2');
  58. }
  59. // ── Collection Queries ────────────────────────────────────
  60. /**
  61. * Test: Query all customer orders collection
  62. */
  63. public function test_get_customer_orders_collection(): void
  64. {
  65. $testData = $this->createTestData();
  66. $query = <<<'GQL'
  67. query getCustomerOrders {
  68. customerOrders(first: 10) {
  69. edges {
  70. cursor
  71. node {
  72. _id
  73. incrementId
  74. status
  75. customerEmail
  76. customerFirstName
  77. customerLastName
  78. grandTotal
  79. subTotal
  80. shippingMethod
  81. shippingTitle
  82. totalItemCount
  83. totalQtyOrdered
  84. baseCurrencyCode
  85. orderCurrencyCode
  86. createdAt
  87. }
  88. }
  89. pageInfo {
  90. endCursor
  91. startCursor
  92. hasNextPage
  93. hasPreviousPage
  94. }
  95. totalCount
  96. }
  97. }
  98. GQL;
  99. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  100. $response->assertOk();
  101. $data = $response->json('data.customerOrders');
  102. expect($data['totalCount'])->toBeGreaterThanOrEqual(2);
  103. expect($data['edges'])->not()->toBeEmpty();
  104. }
  105. /**
  106. * Test: Order item baseImage is a JSON string, not a GraphQL object
  107. */
  108. public function test_customer_orders_item_base_image_is_json_string(): void
  109. {
  110. $testData = $this->createTestData();
  111. \Webkul\Product\Models\ProductImage::query()->create([
  112. 'product_id' => $testData['product']->id,
  113. 'path' => 'product/15/test-image.webp',
  114. 'position' => 1,
  115. ]);
  116. $query = <<<'GQL'
  117. query getCustomerOrders {
  118. customerOrders(first: 10) {
  119. edges {
  120. node {
  121. _id
  122. items {
  123. edges {
  124. node {
  125. sku
  126. name
  127. baseImage
  128. }
  129. }
  130. }
  131. }
  132. }
  133. }
  134. }
  135. GQL;
  136. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  137. if ($response->json('errors.0.message') && str_contains((string) $response->json('errors.0.message'), 'edges')) {
  138. $query = <<<'GQL'
  139. query getCustomerOrders {
  140. customerOrders(first: 10) {
  141. edges {
  142. node {
  143. _id
  144. items {
  145. sku
  146. name
  147. baseImage
  148. }
  149. }
  150. }
  151. }
  152. }
  153. GQL;
  154. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  155. }
  156. $response->assertOk();
  157. expect($response->json('errors'))->toBeNull();
  158. $item = $response->json('data.customerOrders.edges.0.node.items.edges.0.node')
  159. ?? $response->json('data.customerOrders.edges.0.node.items.0');
  160. expect($item)->not()->toBeNull();
  161. expect($item['baseImage'])->toBeString();
  162. $decoded = json_decode($item['baseImage'], true);
  163. expect($decoded)->toBeArray()
  164. ->and($decoded)->toHaveKeys([
  165. 'small_image_url',
  166. 'medium_image_url',
  167. 'large_image_url',
  168. 'original_image_url',
  169. ]);
  170. }
  171. /**
  172. * Test: Unauthenticated request returns error
  173. */
  174. public function test_get_customer_orders_requires_authentication(): void
  175. {
  176. $query = <<<'GQL'
  177. query getCustomerOrders {
  178. customerOrders(first: 5) {
  179. edges {
  180. node {
  181. _id
  182. }
  183. }
  184. }
  185. }
  186. GQL;
  187. $response = $this->graphQL($query);
  188. $response->assertOk();
  189. $errors = $response->json('errors');
  190. expect($errors)->not()->toBeEmpty();
  191. }
  192. /**
  193. * Test: Customer only sees their own orders
  194. */
  195. public function test_customer_only_sees_own_orders(): void
  196. {
  197. $testData = $this->createTestData();
  198. /** Create another customer with their own order */
  199. $otherCustomer = $this->createCustomer();
  200. $channel = Channel::first();
  201. Order::factory()->create([
  202. 'customer_id' => $otherCustomer->id,
  203. 'customer_email' => $otherCustomer->email,
  204. 'customer_first_name' => $otherCustomer->first_name,
  205. 'customer_last_name' => $otherCustomer->last_name,
  206. 'channel_id' => $channel->id,
  207. 'status' => 'pending',
  208. ]);
  209. $query = <<<'GQL'
  210. query getCustomerOrders {
  211. customerOrders(first: 50) {
  212. edges {
  213. node {
  214. _id
  215. }
  216. }
  217. totalCount
  218. }
  219. }
  220. GQL;
  221. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  222. $response->assertOk();
  223. $data = $response->json('data.customerOrders');
  224. /** Should only see the 2 orders belonging to testData customer */
  225. expect($data['totalCount'])->toBe(2);
  226. }
  227. /**
  228. * Test: Filter orders by status
  229. */
  230. public function test_filter_orders_by_status(): void
  231. {
  232. $testData = $this->createTestData();
  233. $query = <<<'GQL'
  234. query getCustomerOrders($status: String) {
  235. customerOrders(first: 10, status: $status) {
  236. edges {
  237. node {
  238. _id
  239. status
  240. }
  241. }
  242. totalCount
  243. }
  244. }
  245. GQL;
  246. $response = $this->authenticatedGraphQL($testData['customer'], $query, [
  247. 'status' => 'pending',
  248. ]);
  249. $response->assertOk();
  250. $data = $response->json('data.customerOrders');
  251. expect($data['totalCount'])->toBe(1);
  252. $node = $data['edges'][0]['node'];
  253. expect($node['status'])->toBe('pending');
  254. }
  255. /**
  256. * Test: Filter by completed status
  257. */
  258. public function test_filter_orders_by_completed_status(): void
  259. {
  260. $testData = $this->createTestData();
  261. $query = <<<'GQL'
  262. query getCustomerOrders($status: String) {
  263. customerOrders(first: 10, status: $status) {
  264. edges {
  265. node {
  266. _id
  267. status
  268. }
  269. }
  270. totalCount
  271. }
  272. }
  273. GQL;
  274. $response = $this->authenticatedGraphQL($testData['customer'], $query, [
  275. 'status' => 'completed',
  276. ]);
  277. $response->assertOk();
  278. $data = $response->json('data.customerOrders');
  279. expect($data['totalCount'])->toBe(1);
  280. $node = $data['edges'][0]['node'];
  281. expect($node['status'])->toBe('completed');
  282. }
  283. // ── Single Item Query ─────────────────────────────────────
  284. /**
  285. * Test: Query single customer order by ID
  286. */
  287. public function test_get_customer_order_by_id(): void
  288. {
  289. $testData = $this->createTestData();
  290. $orderId = "/api/shop/customer-orders/{$testData['order1']->id}";
  291. $query = <<<GQL
  292. query getCustomerOrder {
  293. customerOrder(id: "{$orderId}") {
  294. _id
  295. incrementId
  296. status
  297. customerEmail
  298. customerFirstName
  299. customerLastName
  300. grandTotal
  301. subTotal
  302. shippingMethod
  303. shippingTitle
  304. baseCurrencyCode
  305. orderCurrencyCode
  306. totalItemCount
  307. totalQtyOrdered
  308. createdAt
  309. }
  310. }
  311. GQL;
  312. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  313. $response->assertOk();
  314. $data = $response->json('data.customerOrder');
  315. expect($data['_id'])->toBe($testData['order1']->id);
  316. expect($data['status'])->toBe('pending');
  317. expect($data['customerEmail'])->toBe($testData['customer']->email);
  318. expect($data['customerFirstName'])->toBe($testData['customer']->first_name);
  319. expect($data['customerLastName'])->toBe($testData['customer']->last_name);
  320. }
  321. /**
  322. * Test: Query single customer order by numeric ID
  323. */
  324. public function test_get_customer_order_by_numeric_id(): void
  325. {
  326. $testData = $this->createTestData();
  327. $orderId = (string) $testData['order1']->id;
  328. $query = <<<GQL
  329. query getCustomerOrder {
  330. customerOrder(id: "{$orderId}") {
  331. _id
  332. status
  333. customerEmail
  334. }
  335. }
  336. GQL;
  337. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  338. $response->assertOk();
  339. $data = $response->json('data.customerOrder');
  340. expect($data['_id'])->toBe($testData['order1']->id);
  341. expect($data['status'])->toBe('pending');
  342. expect($data['customerEmail'])->toBe($testData['customer']->email);
  343. }
  344. /**
  345. * Test: Invalid customer order ID format returns validation error
  346. */
  347. public function test_get_customer_order_with_invalid_id_format_returns_error(): void
  348. {
  349. $testData = $this->createTestData();
  350. $query = <<<'GQL'
  351. query getCustomerOrder {
  352. customerOrder(id: "invalid-id") {
  353. _id
  354. }
  355. }
  356. GQL;
  357. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  358. $response->assertOk();
  359. $errors = $response->json('errors');
  360. $message = $errors[0]['message'] ?? '';
  361. expect($errors)->not()->toBeEmpty();
  362. expect($message)->toContain('Invalid ID format');
  363. }
  364. /**
  365. * Test: Query order returns correct financial data
  366. */
  367. public function test_order_returns_financial_data(): void
  368. {
  369. $testData = $this->createTestData();
  370. $orderId = "/api/shop/customer-orders/{$testData['order1']->id}";
  371. $query = <<<GQL
  372. query getCustomerOrder {
  373. customerOrder(id: "{$orderId}") {
  374. _id
  375. grandTotal
  376. baseGrandTotal
  377. subTotal
  378. baseSubTotal
  379. taxAmount
  380. shippingAmount
  381. discountAmount
  382. }
  383. }
  384. GQL;
  385. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  386. $response->assertOk();
  387. $data = $response->json('data.customerOrder');
  388. expect($data)->toHaveKeys([
  389. '_id',
  390. 'grandTotal',
  391. 'baseGrandTotal',
  392. 'subTotal',
  393. 'baseSubTotal',
  394. 'taxAmount',
  395. 'shippingAmount',
  396. 'discountAmount',
  397. ]);
  398. }
  399. /**
  400. * Test: Query non-existent order returns error
  401. */
  402. public function test_get_nonexistent_order_returns_error(): void
  403. {
  404. $this->seedRequiredData();
  405. $query = <<<'GQL'
  406. query getCustomerOrder {
  407. customerOrder(id: "/api/shop/customer-orders/99999") {
  408. _id
  409. status
  410. }
  411. }
  412. GQL;
  413. $response = $this->graphQL($query);
  414. $response->assertOk();
  415. $errors = $response->json('errors');
  416. expect($errors)->not()->toBeEmpty();
  417. }
  418. // ── Pagination ────────────────────────────────────────────
  419. /**
  420. * Test: Pagination with first parameter
  421. */
  422. public function test_pagination_with_first_parameter(): void
  423. {
  424. $testData = $this->createTestData();
  425. $query = <<<'GQL'
  426. query getCustomerOrders {
  427. customerOrders(first: 1) {
  428. edges {
  429. cursor
  430. node {
  431. _id
  432. }
  433. }
  434. pageInfo {
  435. endCursor
  436. hasNextPage
  437. }
  438. totalCount
  439. }
  440. }
  441. GQL;
  442. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  443. $response->assertOk();
  444. $data = $response->json('data.customerOrders');
  445. expect(count($data['edges']))->toBe(1);
  446. expect($data['totalCount'])->toBeGreaterThanOrEqual(2);
  447. }
  448. /**
  449. * Test: Forward pagination with after cursor
  450. */
  451. public function test_pagination_with_after_cursor(): void
  452. {
  453. $testData = $this->createTestData();
  454. /** First page */
  455. $query = <<<'GQL'
  456. query getCustomerOrders {
  457. customerOrders(first: 1) {
  458. edges {
  459. cursor
  460. node {
  461. _id
  462. }
  463. }
  464. pageInfo {
  465. endCursor
  466. hasNextPage
  467. }
  468. }
  469. }
  470. GQL;
  471. $response = $this->authenticatedGraphQL($testData['customer'], $query);
  472. $response->assertOk();
  473. $firstPageData = $response->json('data.customerOrders');
  474. $endCursor = $firstPageData['pageInfo']['endCursor'];
  475. $firstOrderId = $firstPageData['edges'][0]['node']['_id'];
  476. /** Second page */
  477. $query2 = <<<GQL
  478. query getCustomerOrders {
  479. customerOrders(first: 1, after: "{$endCursor}") {
  480. edges {
  481. node {
  482. _id
  483. }
  484. }
  485. }
  486. }
  487. GQL;
  488. $response2 = $this->authenticatedGraphQL($testData['customer'], $query2);
  489. $response2->assertOk();
  490. $secondPageData = $response2->json('data.customerOrders');
  491. $secondOrderId = $secondPageData['edges'][0]['node']['_id'] ?? null;
  492. /** Second page should have a different order */
  493. expect($secondOrderId)->not()->toBe($firstOrderId);
  494. }
  495. // ── Schema Introspection ──────────────────────────────────
  496. /**
  497. * Test: CustomerOrder type has expected fields in schema
  498. */
  499. public function test_customer_order_schema_has_expected_fields(): void
  500. {
  501. $query = <<<'GQL'
  502. {
  503. __type(name: "CustomerOrder") {
  504. name
  505. fields {
  506. name
  507. }
  508. }
  509. }
  510. GQL;
  511. $response = $this->graphQL($query);
  512. $response->assertSuccessful();
  513. $type = $response->json('data.__type');
  514. expect($type)->not->toBeNull()
  515. ->and($type['name'])->toBe('CustomerOrder');
  516. $fieldNames = array_column($type['fields'], 'name');
  517. expect($fieldNames)
  518. ->toContain('_id')
  519. ->toContain('incrementId')
  520. ->toContain('status')
  521. ->toContain('customerEmail')
  522. ->toContain('customerFirstName')
  523. ->toContain('customerLastName')
  524. ->toContain('grandTotal')
  525. ->toContain('subTotal')
  526. ->toContain('shippingMethod')
  527. ->toContain('shippingTitle')
  528. ->toContain('totalItemCount')
  529. ->toContain('totalQtyOrdered')
  530. ->toContain('baseCurrencyCode')
  531. ->toContain('orderCurrencyCode')
  532. ->toContain('createdAt');
  533. }
  534. /**
  535. * Test: CustomerOrderItem.baseImage is a GraphQL String
  536. */
  537. public function test_customer_order_item_base_image_schema_is_string(): void
  538. {
  539. $query = <<<'GQL'
  540. {
  541. __type(name: "CustomerOrderItem") {
  542. name
  543. fields {
  544. name
  545. type {
  546. kind
  547. name
  548. ofType {
  549. kind
  550. name
  551. }
  552. }
  553. }
  554. }
  555. }
  556. GQL;
  557. $response = $this->graphQL($query);
  558. $response->assertSuccessful();
  559. $fields = $response->json('data.__type.fields');
  560. expect($fields)->not()->toBeNull();
  561. $baseImage = collect($fields)->firstWhere('name', 'baseImage');
  562. expect($baseImage)->not()->toBeNull();
  563. $typeName = $baseImage['type']['name'] ?? $baseImage['type']['ofType']['name'] ?? null;
  564. expect($typeName)->toBe('String');
  565. }
  566. }