| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- <?php
- namespace Webkul\BagistoApi\Tests\Feature\GraphQL;
- use Webkul\BagistoApi\Tests\GraphQLTestCase;
- use Webkul\Core\Models\Channel;
- use Webkul\Customer\Models\Customer;
- use Webkul\Product\Models\Product;
- use Webkul\Sales\Models\Invoice;
- use Webkul\Sales\Models\InvoiceItem;
- use Webkul\Sales\Models\Order;
- use Webkul\Sales\Models\OrderItem;
- use Webkul\Sales\Models\OrderPayment;
- class CustomerInvoiceTest extends GraphQLTestCase
- {
- /**
- * Create test data — customer with order and invoices
- */
- private function createTestData(): array
- {
- $this->seedRequiredData();
- $customer = $this->createCustomer();
- $channel = Channel::first();
- $product = Product::factory()->create();
- $order = Order::factory()->create([
- 'customer_id' => $customer->id,
- 'customer_email' => $customer->email,
- 'customer_first_name' => $customer->first_name,
- 'customer_last_name' => $customer->last_name,
- 'channel_id' => $channel->id,
- 'status' => 'completed',
- ]);
- $orderItem = OrderItem::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'sku' => 'TEST-INV-SKU-001',
- 'type' => 'simple',
- 'name' => 'Test Invoice Product',
- ]);
- OrderPayment::factory()->create([
- 'order_id' => $order->id,
- ]);
- /** Create first invoice (paid) */
- $invoice1 = Invoice::factory()->create([
- 'order_id' => $order->id,
- 'state' => 'paid',
- 'total_qty' => 2,
- 'sub_total' => 100.00,
- 'base_sub_total' => 100.00,
- 'grand_total' => 110.00,
- 'base_grand_total' => 110.00,
- 'shipping_amount' => 5.00,
- 'base_shipping_amount' => 5.00,
- 'tax_amount' => 5.00,
- 'base_tax_amount' => 5.00,
- 'discount_amount' => 0.00,
- 'base_discount_amount' => 0.00,
- 'base_currency_code' => 'USD',
- 'order_currency_code' => 'USD',
- 'increment_id' => 'INV-001',
- ]);
- InvoiceItem::factory()->create([
- 'invoice_id' => $invoice1->id,
- 'order_item_id' => $orderItem->id,
- 'name' => 'Test Invoice Product',
- 'sku' => 'TEST-INV-SKU-001',
- 'qty' => 2,
- 'price' => 50.00,
- 'base_price' => 50.00,
- 'total' => 100.00,
- 'base_total' => 100.00,
- ]);
- /** Create second invoice (pending) */
- $invoice2 = Invoice::factory()->create([
- 'order_id' => $order->id,
- 'state' => 'pending',
- 'total_qty' => 1,
- 'sub_total' => 50.00,
- 'base_sub_total' => 50.00,
- 'grand_total' => 55.00,
- 'base_grand_total' => 55.00,
- 'shipping_amount' => 3.00,
- 'base_shipping_amount' => 3.00,
- 'tax_amount' => 2.00,
- 'base_tax_amount' => 2.00,
- 'discount_amount' => 0.00,
- 'base_discount_amount' => 0.00,
- 'base_currency_code' => 'USD',
- 'order_currency_code' => 'USD',
- 'increment_id' => 'INV-002',
- ]);
- InvoiceItem::factory()->create([
- 'invoice_id' => $invoice2->id,
- 'order_item_id' => $orderItem->id,
- 'name' => 'Test Invoice Product',
- 'sku' => 'TEST-INV-SKU-001',
- 'qty' => 1,
- 'price' => 50.00,
- 'base_price' => 50.00,
- 'total' => 50.00,
- 'base_total' => 50.00,
- ]);
- return compact('customer', 'channel', 'product', 'order', 'orderItem', 'invoice1', 'invoice2');
- }
- // ── Collection Queries ────────────────────────────────────
- /**
- * Test: Query all customer invoices collection
- */
- public function test_get_customer_invoices_collection(): void
- {
- $testData = $this->createTestData();
- $query = <<<'GQL'
- query getCustomerInvoices {
- customerInvoices(first: 10) {
- edges {
- cursor
- node {
- _id
- incrementId
- state
- totalQty
- grandTotal
- baseGrandTotal
- subTotal
- baseSubTotal
- shippingAmount
- taxAmount
- discountAmount
- baseCurrencyCode
- orderCurrencyCode
- createdAt
- }
- }
- pageInfo {
- endCursor
- startCursor
- hasNextPage
- hasPreviousPage
- }
- totalCount
- }
- }
- GQL;
- $response = $this->authenticatedGraphQL($testData['customer'], $query);
- $response->assertOk();
- $data = $response->json('data.customerInvoices');
- expect($data['totalCount'])->toBeGreaterThanOrEqual(2);
- expect($data['edges'])->not()->toBeEmpty();
- }
- /**
- * Test: Unauthenticated request returns error
- */
- public function test_get_customer_invoices_requires_authentication(): void
- {
- $query = <<<'GQL'
- query getCustomerInvoices {
- customerInvoices(first: 5) {
- edges {
- node {
- _id
- }
- }
- }
- }
- GQL;
- $response = $this->graphQL($query);
- $response->assertOk();
- $errors = $response->json('errors');
- expect($errors)->not()->toBeEmpty();
- }
- /**
- * Test: Customer only sees invoices from their own orders
- */
- public function test_customer_only_sees_own_invoices(): void
- {
- $testData = $this->createTestData();
- /** Create another customer with their own order and invoice */
- $otherCustomer = $this->createCustomer();
- $channel = Channel::first();
- $otherOrder = Order::factory()->create([
- 'customer_id' => $otherCustomer->id,
- 'customer_email' => $otherCustomer->email,
- 'customer_first_name' => $otherCustomer->first_name,
- 'customer_last_name' => $otherCustomer->last_name,
- 'channel_id' => $channel->id,
- 'status' => 'completed',
- ]);
- Invoice::factory()->create([
- 'order_id' => $otherOrder->id,
- 'state' => 'paid',
- 'grand_total' => 200.00,
- 'base_grand_total' => 200.00,
- ]);
- $query = <<<'GQL'
- query getCustomerInvoices {
- customerInvoices(first: 50) {
- edges {
- node {
- _id
- }
- }
- totalCount
- }
- }
- GQL;
- $response = $this->authenticatedGraphQL($testData['customer'], $query);
- $response->assertOk();
- $data = $response->json('data.customerInvoices');
- /** Should only see the 2 invoices belonging to testData customer */
- expect($data['totalCount'])->toBe(2);
- }
- /**
- * Test: Filter invoices by orderId
- */
- public function test_filter_invoices_by_order_id(): void
- {
- $testData = $this->createTestData();
- $query = <<<'GQL'
- query getCustomerInvoices($orderId: Int) {
- customerInvoices(first: 10, orderId: $orderId) {
- edges {
- node {
- _id
- }
- }
- totalCount
- }
- }
- GQL;
- $response = $this->authenticatedGraphQL($testData['customer'], $query, [
- 'orderId' => $testData['order']->id,
- ]);
- $response->assertOk();
- $data = $response->json('data.customerInvoices');
- expect($data['totalCount'])->toBe(2);
- }
- /**
- * Test: Filter invoices by state
- */
- public function test_filter_invoices_by_state(): void
- {
- $testData = $this->createTestData();
- $query = <<<'GQL'
- query getCustomerInvoices($state: String) {
- customerInvoices(first: 10, state: $state) {
- edges {
- node {
- _id
- state
- }
- }
- totalCount
- }
- }
- GQL;
- $response = $this->authenticatedGraphQL($testData['customer'], $query, [
- 'state' => 'paid',
- ]);
- $response->assertOk();
- $data = $response->json('data.customerInvoices');
- expect($data['totalCount'])->toBe(1);
- $node = $data['edges'][0]['node'];
- expect($node['state'])->toBe('paid');
- }
- /**
- * Test: Filter by pending state
- */
- public function test_filter_invoices_by_pending_state(): void
- {
- $testData = $this->createTestData();
- $query = <<<'GQL'
- query getCustomerInvoices($state: String) {
- customerInvoices(first: 10, state: $state) {
- edges {
- node {
- _id
- state
- }
- }
- totalCount
- }
- }
- GQL;
- $response = $this->authenticatedGraphQL($testData['customer'], $query, [
- 'state' => 'pending',
- ]);
- $response->assertOk();
- $data = $response->json('data.customerInvoices');
- expect($data['totalCount'])->toBe(1);
- $node = $data['edges'][0]['node'];
- expect($node['state'])->toBe('pending');
- }
- // ── Single Item Query ─────────────────────────────────────
- /**
- * Test: Query single customer invoice by ID
- */
- public function test_get_customer_invoice_by_id(): void
- {
- $testData = $this->createTestData();
- $invoiceId = "/api/shop/customer-invoices/{$testData['invoice1']->id}";
- $query = <<<GQL
- query getCustomerInvoice {
- customerInvoice(id: "{$invoiceId}") {
- _id
- incrementId
- state
- totalQty
- grandTotal
- baseGrandTotal
- subTotal
- baseSubTotal
- shippingAmount
- baseShippingAmount
- taxAmount
- baseTaxAmount
- discountAmount
- baseDiscountAmount
- baseCurrencyCode
- orderCurrencyCode
- createdAt
- }
- }
- GQL;
- $response = $this->graphQL($query);
- $response->assertOk();
- $data = $response->json('data.customerInvoice');
- expect($data['_id'])->toBe($testData['invoice1']->id);
- expect($data['state'])->toBe('paid');
- expect($data['incrementId'])->toBe('INV-001');
- }
- /**
- * Test: Query invoice returns correct financial data
- */
- public function test_invoice_returns_financial_data(): void
- {
- $testData = $this->createTestData();
- $invoiceId = "/api/shop/customer-invoices/{$testData['invoice1']->id}";
- $query = <<<GQL
- query getCustomerInvoice {
- customerInvoice(id: "{$invoiceId}") {
- _id
- grandTotal
- baseGrandTotal
- subTotal
- baseSubTotal
- taxAmount
- shippingAmount
- discountAmount
- }
- }
- GQL;
- $response = $this->graphQL($query);
- $response->assertOk();
- $data = $response->json('data.customerInvoice');
- expect($data)->toHaveKeys([
- '_id',
- 'grandTotal',
- 'baseGrandTotal',
- 'subTotal',
- 'baseSubTotal',
- 'taxAmount',
- 'shippingAmount',
- 'discountAmount',
- ]);
- }
- /**
- * Test: Query non-existent invoice returns error
- */
- public function test_get_nonexistent_invoice_returns_error(): void
- {
- $this->seedRequiredData();
- $query = <<<'GQL'
- query getCustomerInvoice {
- customerInvoice(id: "/api/shop/customer-invoices/99999") {
- _id
- state
- }
- }
- GQL;
- $response = $this->graphQL($query);
- $response->assertOk();
- $errors = $response->json('errors');
- expect($errors)->not()->toBeEmpty();
- }
- // ── Pagination ────────────────────────────────────────────
- /**
- * Test: Pagination with first parameter
- */
- public function test_pagination_with_first_parameter(): void
- {
- $testData = $this->createTestData();
- $query = <<<'GQL'
- query getCustomerInvoices {
- customerInvoices(first: 1) {
- edges {
- cursor
- node {
- _id
- }
- }
- pageInfo {
- endCursor
- hasNextPage
- }
- totalCount
- }
- }
- GQL;
- $response = $this->authenticatedGraphQL($testData['customer'], $query);
- $response->assertOk();
- $data = $response->json('data.customerInvoices');
- expect(count($data['edges']))->toBe(1);
- expect($data['totalCount'])->toBeGreaterThanOrEqual(2);
- }
- /**
- * Test: Forward pagination with after cursor
- */
- public function test_pagination_with_after_cursor(): void
- {
- $testData = $this->createTestData();
- /** First page */
- $query = <<<'GQL'
- query getCustomerInvoices {
- customerInvoices(first: 1) {
- edges {
- cursor
- node {
- _id
- }
- }
- pageInfo {
- endCursor
- hasNextPage
- }
- }
- }
- GQL;
- $response = $this->authenticatedGraphQL($testData['customer'], $query);
- $response->assertOk();
- $firstPageData = $response->json('data.customerInvoices');
- $endCursor = $firstPageData['pageInfo']['endCursor'];
- $firstInvoiceId = $firstPageData['edges'][0]['node']['_id'];
- /** Second page */
- $query2 = <<<GQL
- query getCustomerInvoices {
- customerInvoices(first: 1, after: "{$endCursor}") {
- edges {
- node {
- _id
- }
- }
- }
- }
- GQL;
- $response2 = $this->authenticatedGraphQL($testData['customer'], $query2);
- $response2->assertOk();
- $secondPageData = $response2->json('data.customerInvoices');
- $secondInvoiceId = $secondPageData['edges'][0]['node']['_id'] ?? null;
- /** Second page should have a different invoice */
- expect($secondInvoiceId)->not()->toBe($firstInvoiceId);
- }
- // ── Schema Introspection ──────────────────────────────────
- /**
- * Test: CustomerInvoice type has expected fields in schema
- */
- public function test_customer_invoice_schema_has_expected_fields(): void
- {
- $query = <<<'GQL'
- {
- __type(name: "CustomerInvoice") {
- name
- fields {
- name
- }
- }
- }
- GQL;
- $response = $this->graphQL($query);
- $response->assertSuccessful();
- $type = $response->json('data.__type');
- expect($type)->not->toBeNull()
- ->and($type['name'])->toBe('CustomerInvoice');
- $fieldNames = array_column($type['fields'], 'name');
- expect($fieldNames)
- ->toContain('_id')
- ->toContain('incrementId')
- ->toContain('state')
- ->toContain('totalQty')
- ->toContain('grandTotal')
- ->toContain('subTotal')
- ->toContain('shippingAmount')
- ->toContain('taxAmount')
- ->toContain('discountAmount')
- ->toContain('baseCurrencyCode')
- ->toContain('orderCurrencyCode')
- ->toContain('createdAt');
- }
- }
|