GraphQL टेस्टिंग

GraphQL, APIs के लिए एक query language और type system है, जिसे GraphQL Specification परिभाषित करती है। API आम तौर पर एक ही endpoint देती है, जैसे POST /graphql। Client एक document भेजता है जिसमें ठीक वही fields होते हैं जो उसे चाहिए; server उसे typed schema के against validate करता है, execute करता है और उसी shape में JSON लौटाता है।

उदाहरण: { order(id: "1042") { status total } } सिर्फ़ {"data": {"order": {"status": "SHIPPED", "total": 249.5}}} लौटाती है, इससे ज़्यादा कुछ नहीं। Request अब भी HTTP पर जाती है, लेकिन tester जो जाँचता है उसका ज़्यादातर हिस्सा bodies में चला जाता है।

यह क्यों ज़रूरी है

GraphQL risk को उन जगहों पर ले जाता है जहाँ REST की आदतें नहीं देखतीं। Errors अक्सर 200 OK response के अंदर आते हैं, इसलिए status-only test pass हो जाता है जबकि operation fail हुआ। क्योंकि हर request client खुद बनाता है, एक ही data तक कई paths से पहुँचा जा सकता है, और किसी एक path पर भूला गया authorization data leak करता है।

एक request ऐसी nested lists भी माँग सकती है जिनकी cost किसी भी REST call से कहीं ज़्यादा हो। OWASP API Security Top 10 2023 अपने risks में unrestricted resource consumption और broken object property level authorization गिनाता है, और GraphQL में दोनों को गलत करना आसान है।

Schema, queries और mutations

Schema definition language (SDL) में लिखा schema ही contract है: हर type, अपने type के साथ हर field, और entry points। Query के fields data पढ़ते हैं और Mutation के fields उसे बदलते हैं; ! ऐसी value दिखाता है जो कभी null नहीं होती। Arguments और variables typed हैं: Int एक signed 32-bit integer है, इसलिए 2147483648 execution से पहले request error के साथ reject होता है।

REST की तुलना में आम तौर पर एक endpoint होता है, operation body में जाता है, और लौटने वाले fields endpoint नहीं, client चुनता है। REST API का OpenAPI document तभी होता है जब team उसे उपलब्ध कराए; GraphQL server के पास उसका schema हमेशा होता है और वह उसे enforce करता है, इसलिए हर field और argument एक तैयार test condition है।

REST, API को कई URIs में बाँटता है और नतीजा status code में बताता है; GraphQL एक endpoint इस्तेमाल करता है, fields client चुनता है, और errors body में आते हैं, आम तौर पर 200 OK के साथ।

REST

कई resources, हर एक का अपना URI; method बताता है कि क्या करना है

GET /v1/orders/1042

Endpoints:
हर resource का एक URI, कई methods
लौटने वाले fields:
Endpoint तय करता है
Errors:
एक status code पूरी request का नतीजा बताता है
Contract:
OpenAPI document, अगर team उसे उपलब्ध कराए
Access checks:
अक्सर हर endpoint और method पर
एक request की cost:
Endpoint के design से तय, अक्सर pages में

GraphQL

एक endpoint; request body operation और हर field का नाम देती है

POST /graphql

Endpoints:
आम तौर पर सब कुछ के लिए एक endpoint
लौटने वाले fields:
हर query में client चुनता है
Errors:
Body के errors array में, आम तौर पर 200 OK के साथ; बाकी fields का data फिर भी लौट सकता है
Contract:
Schema: हमेशा मौजूद, enforced, और introspection on हो तो पढ़ा जा सकता है
Access checks:
हर object और field पर, उस तक पहुँचने वाले हर path पर
एक request की cost:
Depth, list sizes और aliases के साथ बढ़ती है

आम व्यवहार: servers अलग-अलग होते हैं, ख़ासकर इस्तेमाल किए गए status codes में, इसलिए देखें कि आपकी API क्या document करती है।

  • Query: data selection जैसा ही होता है, हर माँगे गए field के साथ। null का मतलब असली "कोई value नहीं" भी हो सकता है और fail हुआ field भी; दोनों में फ़र्क सिर्फ़ errors की matching entry बताती है।
  • Mutation: data बदलता है और result में से आपके चुने fields लौटाता है; एक operation के कई mutation fields क्रम से चलते हैं।
  • Variables: variables object में भेजे जाते हैं और type-check होते हैं; इन्हें किसी भी input की तरह टेस्ट करें — missing, null, गलत type, range से बाहर।
# Schema (SDL), shortened
type Query {
  order(id: ID!): Order
  me: User!
}
type Mutation {
  cancelOrder(id: ID!, reason: String): Order
}
type Order {
  id: ID!
  status: OrderStatus!
  total: Float!
  trackingUrl: String
  internalNote: String
  customer: User!
  items: [OrderItem!]!
}
type User { id: ID! name: String! email: String! orders(first: Int): [Order!]! }
type OrderItem { sku: String! quantity: Int! }
enum OrderStatus { PLACED SHIPPED CANCELLED }

# An operation, sent over HTTP as a JSON body:
# {"query": "query OrderSummary($id: ID!) { ... }", "variables": {"id": "1042"}}
query OrderSummary($id: ID!) {
  order(id: $id) { status total items { sku quantity } }
}

आज़माएँ: GraphQL क्वेरी टेस्टर

HTTP 200 के साथ आने वाले errors

Specification response परिभाषित करती है, HTTP status नहीं: व्यवहार में एक JSON object जिसमें data, errors या दोनों होते हैं। REST में एक status code पूरी request का नतीजा बताता है; कई GraphQL servers हर उस चीज़ पर 200 OK देते हैं जिसे वे process कर पाए, और कुछ 4xx सिर्फ़ उन requests के लिए देते हैं जिन्हें वे parse या validate नहीं कर पाते।

Request error — गलत syntax, unknown field, गलत type का variable — execution रोक देता है: errors होता है, data नहीं। Execution error (specification के पिछले editions और कई tools में इसे field error कहा जाता है) सिर्फ़ उस field को null बनाता है, errors में एक entry जोड़ता है जो path में उसका नाम देती है, और बाकी data फिर भी लौटाता है: यह partial success है। अगर field non-null है, तो null सबसे नज़दीकी nullable parent तक ऊपर जाता है। नीचे के उदाहरण में trackingUrl के पीछे की carrier service time out हो गई।

  • Body पर assert करें: success पर errors नहीं होता और हर field का type सही होता है; expected failure पर errors, path और कोई भी documented error code जाँचें। extensions का content आपका server तय करता है, specification नहीं।
  • Partial success: missing tracking link "tracking उपलब्ध नहीं" की तरह नहीं दिखना चाहिए, और कोई भी message stack traces या SQL न दिखाए।
HTTP/1.1 200 OK
Content-Type: application/json

{
  "errors": [
    {
      "message": "Carrier service timed out",
      "locations": [{ "line": 4, "column": 5 }],
      "path": ["order", "trackingUrl"],
      "extensions": { "code": "UPSTREAM_TIMEOUT" }
    }
  ],
  "data": {
    "order": { "status": "SHIPPED", "trackingUrl": null }
  }
}

आज़माएँ: JSON Path टेस्टर आज़माएँ: API रिस्पॉन्स एनालाइज़र

Query depth, size और cost

User के orders हैं, हर Order का एक customer है, और उस customer के फिर orders हैं, इसलिए एक छोटी query client जितना चाहे उतनी गहरी nest हो सकती है। हर list level काम को गुणा करता है: 100 orders, जिनमें हर एक का customer 100 orders वाला हो, यानी 10,000 objects। REST endpoint वही लौटाता है जो उसके designers ने तय किया, अक्सर pages में; GraphQL request की cost उतनी होती है जितना client माँगे। Alias, जैसे किसी field से पहले o1:, response में उस field का नाम बदल देता है, इसलिए एक request एक ही field को कई बार दोहरा सकती है।

Maximum depth, first जैसे list arguments पर cap, cost budget, time-out और operations गिनने वाली rate limits की उम्मीद करें, और हर एक crash के बजाय errors में साफ़ entry के साथ fail हो; depth limit एक-एक level जोड़कर खोजें। सिर्फ़ उन systems को टेस्ट करें जो आपके हैं या जिनकी लिखित अनुमति आपके पास है, बेहतर हो कि test environment, और छोटे steps में।

  • first का cap 100 हो तो 100, 101, 0 और -1 भेजें: चुपचाप बड़ा page कभी नहीं।
  • एक ही request में कोई rate-limited mutation, जैसे coupon redeem करना, 10 aliases के साथ भेजें: limit को हर alias गिनना चाहिए, सिर्फ़ एक HTTP request नहीं।
# Nesting: every list level multiplies the work
query Deep {
  me {
    orders(first: 100) {
      customer {
        orders(first: 100) {
          customer { orders(first: 100) { id } }
        }
      }
    }
  }
}

# Aliases: the same field many times in one request
query Aliases {
  o1: order(id: "1001") { total }
  o2: order(id: "1002") { total }
  # ... and so on, up to o500
}

आज़माएँ: GraphQL क्वेरी टेस्टर

Worked example: हर field पर authorization

REST में access अक्सर हर endpoint और method पर check होता है। GraphQL के एक endpoint के साथ authorization हर बार लागू होना चाहिए जब कोई object या field resolve होता है, उस तक पहुँचने वाले हर path पर, चाहे check resolver में हो (server का वह function जो field की value बनाता है) या उस business logic में जिसे resolver call करता है। Requirement: "Customers सिर्फ़ अपने orders देखते हैं। internalNote सिर्फ़ support agents के लिए है। Order सिर्फ़ उसका owner cancel कर सकता है।" Customer A order 1042 (PLACED) का owner है, customer B के दूसरे orders हैं, और एक agent है; हर test उसी user का token इस्तेमाल करता है।

  • Tests 1 और 2: एक query, दो roles, दो expected results।
  • Tests 3 और 4 object-level authorization जाँचते हैं (OWASP API1:2023)। Test 4 के बाद agent के रूप में order दोबारा पढ़ें, और test 3 की तुलना unknown id से करें: अलग response B को बता देता है कि कौन-से orders मौजूद हैं।
  • Tests 5 और 6 internalNote तक nested list और mutation result से पहुँचते हैं; जो server यह rule सिर्फ़ order query में check करता है, वह इनमें fail होता है (OWASP API3:2023, object property level authorization)।
#  User        Operation, then expected result
1  Customer A  { order(id: "1042") { status internalNote } }
               -> status returned; internalNote null + error with its path
2  Agent       { order(id: "1042") { status internalNote } }
               -> both fields returned
3  Customer B  { order(id: "1042") { status } }
               -> order null; response identical to an unknown id
4  Customer B  mutation { cancelOrder(id: "1042") { status } }
               -> null + error; the agent still reads order 1042 as PLACED
5  Customer A  { me { orders(first: 5) { id internalNote } } }
               -> every internalNote null + error
6  Customer A  mutation { cancelOrder(id: "1042") { status internalNote } }
               -> status CANCELLED; internalNote null + error

आज़माएँ: API रिस्पॉन्स कम्पेरेटर

Introspection: schema खुद अपना वर्णन करता है

Introspection specification का हिस्सा है: __ से शुरू होने वाले system fields schema लौटाते हैं। __schema सारे types और root operation types list करता है, __type(name: "Order") एक type का वर्णन करता है, और __typename किसी भी object के type का नाम देता है। इससे हर query, mutation, field और argument की list बनाएँ जिसे cover करना है; fields से includeDeprecated: true के साथ माँगें, क्योंकि deprecated fields default रूप से छोड़ दिए जाते हैं पर फिर भी काम करते हैं।

क्योंकि यह API का नक्शा दे देता है, OWASP GraphQL Cheat Sheet सलाह देती है कि production और हर publicly accessible environment में introspection disable किया जाए। External clients के लिए बनी API में इसे खुला रखना जानबूझकर हो सकता है; तब भी cheat sheet सुझाव देती है कि unauthenticated या unauthorized users के लिए इसे disable करने पर विचार करें। Disable करने से schema secret नहीं होता: guess किए गए fields फिर भी जवाब देते हैं और कुछ servers errors में नाम suggest करते हैं ("Did you mean …?"), जिन्हें cheat sheet बंद करने की सलाह भी देती है, इसलिए authorization हर हाल में टिकना चाहिए।

  • हर release में result को snapshot के रूप में save करें और snapshots की तुलना करें: हटाया गया field या नया required argument clients को तोड़ देता है।
  • हर environment में __schema बिना token और एक सामान्य user के token के साथ भेजें, और नतीजे की तुलना तय की गई policy से करें।
query Inventory {
  __schema {
    queryType { name }
    mutationType { name }
    types {
      name
      kind
      fields(includeDeprecated: true) { name isDeprecated args { name } }
    }
  }
}

आज़माएँ: GraphQL क्वेरी टेस्टर आज़माएँ: JSON डिफ़

आम गलतियाँ

  • errors और data जाँचने के बजाय सिर्फ़ 200 OK assert करना।
  • data और errors दोनों वाले response को हर field की expected value तय किए बिना सीधे pass या fail कहना।
  • Authorization सिर्फ़ main query पर टेस्ट करना, nested objects, lists और mutation results पर नहीं।
  • यह मान लेना कि सिर्फ़ front end की queries मायने रखती हैं, जबकि कोई भी valid token schema में allowed कोई भी query भेज सकता है।
  • Depth या alias tests production या shared environment पर बिना अनुमति चलाना।

QA का नज़रिया

हर schema change को requirement की तरह review करें: कौन-से नए fields personal या internal data रखते हैं और उन्हें कौन पढ़ सकता है, कौन-सी lists paged हैं और उनका maximum क्या है, clients को किन errors की उम्मीद करनी चाहिए। पूछें कि depth, cost और rate limits कैसे configure हैं और introspection कहाँ on है।

Test cases और bug reports में operation, variables, user या role और पूरा response body लिखें, और error का path या वह exact query quote करें जो protected field तक पहुँची।

अभ्यास

नए delivery field के लिए tests design करें

आपका काम

आपकी team इस पाठ के Order type में नीचे दिए fields जोड़ती है। Requirement: "Customers अपने orders का delivery address और tracking events देखते हैं; support agents इन्हें किसी भी order के लिए देखते हैं। trackingEvents हर request में अधिकतम 50 events लौटाता है।"

Test cases लिखें, हर एक में user, operation, और data व errors में क्या दिखना चाहिए और क्या नहीं।

सामग्री
type Order {
  # ...existing fields from the lesson
  deliveryAddress: Address
  trackingEvents(first: Int): [TrackingEvent!]!
}
type Address { line1: String! city: String! postcode: String! }
type TrackingEvent { status: String! at: String! }
चरण
  1. ज़रूरी roles और test data की list बनाएँ।
  2. Positive cases लिखें।
  3. ऐसे authorization cases लिखें जो address तक दो paths से पहुँचें।
  4. first पर 2-value boundary value analysis लागू करें: हर boundary value और बगल वाले partition में उसकी सबसे नज़दीकी value टेस्ट करें।
मॉडल उत्तर दिखाएँ
मॉडल उत्तर

Data: customer A order 1042 का owner है जिसमें 60 tracking events हैं, customer B order 2077 का owner है, और एक support agent है; हर test उसी user का token इस्तेमाल करता है।

Positive: A और agent order(id: "1042") से deliveryAddress और trackingEvents(first: 10) माँगते हैं और दोनों बिना errors के पाते हैं।

Authorization: B order(id: "1042") से यही fields माँगता है और उसे order: null मिलता है, कहीं कोई address या event नहीं, और वही response जो unknown id पर मिलता है। B cancelOrder(id: "1042") { deliveryAddress } भेजता है: error के साथ null, और agent को order का status बिना बदलाव के दिखता है। me { orders } से B सिर्फ़ अपने addresses देखता है।

Limit: first: 50 50 events लौटाता है; first: 51 documented तरीके से साफ़ error या ठीक 50 देता है, 51 कभी नहीं। Lower boundary के लिए पूछें कि 0 valid है या नहीं: valid range 1 से 50 हो तो 0 (error) और 1 (एक event) टेस्ट करें; 0 से 50 हो तो -1 (error) और 0 (खाली list)। first के बिना पूछें कि default क्या है: अधिकतम 50, सारे 60 कभी नहीं।

अक्सर पूछे जाने वाले सवाल

कुछ fail होने पर भी GraphQL API 200 क्यों लौटाती है?

GraphQL specification response का वर्णन करती है, HTTP status का नहीं, और किसी एक field में execution error के बाद भी partial data वाला उपयोगी response बनता है। इसलिए कई servers हर उस request पर 200 देते हैं जिसे वे process कर पाए, और कुछ 4xx सिर्फ़ उन requests के लिए देते हैं जिन्हें वे parse या validate नहीं कर पाते। अपनी API का documentation देखें, और हर हाल में body पर assert करें।

क्या introspection बंद कर देना चाहिए?

Development और test environments में यह उपयोगी है। Production और दूसरे publicly accessible environments के लिए OWASP GraphQL Cheat Sheet इसे disable करने की सलाह देती है; external clients के explore करने के लिए बनी API में वह कम से कम unauthenticated या unauthorized users के लिए इसे disable करने पर विचार करने का सुझाव देती है। इस फ़ैसले को requirement मानें और हर environment में टेस्ट करें; इसे बंद करना exposure घटाता है, पर authorization की जगह कभी नहीं लेता।

क्या मैं GraphQL को अपने REST tools से टेस्ट कर सकता हूँ?

हाँ। HTTP पर GraphQL request आम तौर पर एक POST होती है जिसकी JSON body में query और variables होते हैं, इसलिए कोई भी HTTP client उसे भेज सकता है। GraphQL tools schema-aware editing और validation जोड़ते हैं। Mutqan का GraphQL Query Tester variables और headers के साथ queries और mutations किसी endpoint पर चलाता है और response दिखाता है।

संदर्भ

  • GraphQL SpecificationGraphQL FoundationGraphQL क्वेरी भाषा और टाइप सिस्टम, वैलिडेशन नियम और एक्ज़ीक्यूशन सेमांटिक्स।
  • OWASP Cheat Sheet Series: GraphQLOWASP FoundationGraphQL-विशिष्ट जोखिम: क्वेरी की गहराई और जटिलता की सीमा, प्रोडक्शन में इंट्रोस्पेक्शन, फ़ील्ड-स्तर पर ऑथराइज़ेशन।
  • OWASP API Security Top 10 (2023)OWASP Foundation · 2023API की दस सबसे आम कमज़ोरियाँ: ऑब्जेक्ट-लेवल ऑथराइज़ेशन का टूटना, ऑथेंटिकेशन का टूटना, अनियंत्रित रिसोर्स खपत और अन्य।

Mutqan tools से अभ्यास करें

किसी भी endpoint पर variables और हेडर के साथ GraphQL क्वेरी तथा mutation चलाएँ और रिस्पॉन्स जाँचें।

API टेस्टिंग

JSON पर JSONPath एक्सप्रेशन ($.store.book[*].author) चलाएँ और मैच लाइव देखें।

JSON और डेटा

दो API रिस्पॉन्स (जैसे staging बनाम production) की तुलना करें और हर संरचनात्मक तथा वैल्यू का अंतर देखें।

API टेस्टिंग

दो JSON डॉक्यूमेंट की सिमैंटिक तुलना करें और जोड़ी गई, हटाई गई तथा बदली गई वैल्यू हाइलाइट करें।

JSON और डेटा

किसी API रिस्पॉन्स का विश्लेषण करें: संरचना, फ़ील्ड टाइप, null, डुप्लिकेट, साइज़ और संभावित डेटा-क्वालिटी समस्याएँ।

API टेस्टिंग

और गहराई से

इस topic में आपकी प्रगति

QA रोडमैप का हिस्सा