# Postman Testing Guide - GraphRAG Knowledge Graph AI API ## Complete Step-by-Step API Testing Guide for Django REST API with JWT Authentication --- ## Table of Contents 1. [Postman Installation & Setup](#1-postman-installation--setup) 2. [Creating a Collection](#2-creating-a-collection) 3. [Setting Up Environment Variables](#3-setting-up-environment-variables) 4. [Endpoint-by-Endpoint Testing Guide](#4-endpoint-by-endpoint-testing-guide) 5. [Chaining Requests with JWT Tokens](#5-chaining-requests-with-jwt-tokens) 6. [Writing Post-Response Tests](#6-writing-post-response-tests) 7. [Common Errors & Troubleshooting](#7-common-errors--troubleshooting) 8. [Using Postman Runner for Batch Testing](#8-using-postman-runner-for-batch-testing) --- ## 1. Postman Installation & Setup ### Step 1.1: Download Postman ``` 1. Go to https://www.postman.com/downloads/ 2. Choose your OS (Windows / Mac / Linux) 3. Download and run the installer 4. Sign up for a free account (optional but recommended for sync) ``` ### Step 1.2: Verify Installation ``` Open Postman -> You should see the main workspace with: - Left sidebar (Collections, History) - Center panel (Request builder) - Right panel (Response viewer) ``` ### Step 1.3: Disable SSL Certificate Verification (for localhost) ``` 1. Click the gear icon (Settings) in the top-right 2. Go to "General" tab 3. Turn OFF "SSL certificate verification" 4. This is needed because localhost often uses self-signed certs ``` --- ## 2. Creating a Collection ### Step 2.1: Create the Collection ``` 1. Click "Collections" in the left sidebar 2. Click the "+" button or "Create Collection" 3. Name it: "GraphRAG Knowledge Graph AI API" 4. Click the "..." next to the collection name -> "Edit" 5. Go to "Authorization" tab 6. Set Type to: "Bearer Token" 7. In the Token field, enter: {{access_token}} 8. Click "Save" ``` ### Step 2.2: Organize with Folders Right-click the collection and create these folders: ``` GraphRAG Knowledge Graph AI API/ ├── 01 - Health Check ├── 02 - Authentication ├── 03 - Document Management ├── 04 - Query Endpoints ├── 05 - Graph Endpoints └── 06 - Evaluation ``` To create a folder: ``` 1. Right-click on the collection name 2. Select "Add Folder" 3. Name the folder 4. Drag requests into the appropriate folder ``` --- ## 3. Setting Up Environment Variables ### Step 3.1: Create an Environment ``` 1. Click the "Environments" tab in the left sidebar 2. Click "Create Environment" 3. Name it: "GraphRAG Local Dev" 4. Click "Add" to create variables ``` ### Step 3.2: Add Variables Add each of these variables with their initial values: | Variable | Initial Value | Description | |--------------------|--------------------------|---------------------------------| | `base_url` | `http://localhost:8000` | API base URL | | `access_token` | | JWT access token (auto-filled) | | `refresh_token` | | JWT refresh token (auto-filled) | | `test_username` | `testuser_01` | Test user username | | `test_email` | `testuser01@example.com` | Test user email | | `test_password` | `SecureP@ss1` | Test user password | | `uploaded_doc_id` | | Document ID (auto-filled) | ### Step 3.3: Activate the Environment ``` 1. Top-right corner: click the Environment dropdown 2. Select "GraphRAG Local Dev" 3. The eye icon shows current variable values ``` --- ## 4. Endpoint-by-Endpoint Testing Guide --- ### ENDPOINT 1: POST /api/auth/register/ **Folder**: `02 - Authentication` **Setup**: ``` Name: Register User Method: POST URL: {{base_url}}/api/auth/register/ ``` **Headers**: ``` Content-Type: application/json ``` **Body** (raw JSON): ```json { "username": "{{test_username}}", "email": "{{test_email}}", "password": "{{test_password}}", "confirm_password": "{{test_password}}" } ``` **Expected Response**: - Status: `201 Created` - Body: ```json { "message": "User registered successfully.", "user": { "id": 1, "username": "testuser_01", "email": "testuser01@example.com" } } ``` **What to Check**: - `message` field confirms success - `user` object contains `id`, `username`, `email` - No `password` field is returned (security!) **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 201", function () { pm.response.to.have.status(201); }); pm.test("Response has success message", function () { var jsonData = pm.response.json(); pm.expect(jsonData.message).to.eql("User registered successfully."); }); pm.test("User object exists without password", function () { var jsonData = pm.response.json(); pm.expect(jsonData.user).to.have.property("id"); pm.expect(jsonData.user).to.have.property("username"); pm.expect(jsonData.user).to.not.have.property("password"); }); ``` --- ### ENDPOINT 2: POST /api/auth/login/ **Folder**: `02 - Authentication` **Setup**: ``` Name: Login (Get JWT Tokens) Method: POST URL: {{base_url}}/api/auth/login/ ``` **Headers**: ``` Content-Type: application/json ``` **Body** (raw JSON): ```json { "username": "{{test_username}}", "password": "{{test_password}}" } ``` **Expected Response**: - Status: `200 OK` - Body: ```json { "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` **What to Check**: - `access` field contains a long JWT string - `refresh` field contains a long JWT string - Both tokens start with `eyJ` (base64-encoded JWT) **Post-Response Script** (Tests tab) - **CRITICAL - Saves tokens for all other requests**: ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Response contains access token", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("access"); pm.expect(jsonData.access).to.be.a("string"); // Save to environment variable for other requests pm.environment.set("access_token", jsonData.access); }); pm.test("Response contains refresh token", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("refresh"); pm.expect(jsonData.refresh).to.be.a("string"); pm.environment.set("refresh_token", jsonData.refresh); }); ``` --- ### ENDPOINT 3: POST /api/documents/upload/ **Folder**: `03 - Document Management` **Setup**: ``` Name: Upload Document Method: POST URL: {{base_url}}/api/documents/upload/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} Content-Type: multipart/form-data ``` **Body** (form-data): ``` Key: file Type: File Value: [Select a PDF, TXT, MD, CSV, JSON, HTML, XML, DOCX, or DOC file from your computer] ``` > **Important**: In the Body tab, select "form-data", then: > 1. In the Key column, type `file` > 2. Hover over the Key field - a dropdown appears on the right > 3. Change from "Text" to "File" > 4. Click the "Select Files" button that appears in the Value column > 5. Choose your test file **Allowed file types**: `.pdf`, `.txt`, `.md`, `.docx`, `.doc`, `.csv`, `.json`, `.html`, `.xml` **Max file size**: 10 MB **Expected Response**: - Status: `202 Accepted` - Body: ```json { "message": "File upload accepted. Ingestion running in background.", "document": { "id": 1, "user": { "id": 1, "username": "testuser_01", "email": "testuser01@example.com" }, "name": "test-document.pdf", "file_url": "http://localhost:8000/media/documents/test-document.pdf", "status": "PENDING", "entity_count": 0, "relationship_count": 0, "error_message": null, "processing_progress": 0, "processing_step": null, "created_at": "2026-07-09T10:00:00Z", "updated_at": "2026-07-09T10:00:00Z" } } ``` **What to Check**: - Status is `202 Accepted` (not 200 - this is async!) - `document.status` is `"PENDING"` initially - `document.id` is returned (save this for later requests) - No `password` or sensitive info leaked **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 202", function () { pm.response.to.have.status(202); }); pm.test("Upload accepted for background processing", function () { var jsonData = pm.response.json(); pm.expect(jsonData.message).to.include("accepted"); }); pm.test("Document ID is returned", function () { var jsonData = pm.response.json(); pm.expect(jsonData.document).to.have.property("id"); // Save document ID for other requests pm.environment.set("uploaded_doc_id", jsonData.document.id); }); pm.test("Document status is PENDING", function () { var jsonData = pm.response.json(); pm.expect(jsonData.document.status).to.eql("PENDING"); }); ``` --- ### ENDPOINT 4: GET /api/documents/ **Folder**: `03 - Document Management` **Setup**: ``` Name: List Documents Method: GET URL: {{base_url}}/api/documents/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None (GET request) **Expected Response**: - Status: `200 OK` - Body: ```json [ { "id": 1, "user": { "id": 1, "username": "testuser_01", "email": "testuser01@example.com" }, "name": "test-document.pdf", "file_url": "http://localhost:8000/media/documents/test-document.pdf", "status": "COMPLETED", "entity_count": 42, "relationship_count": 18, "error_message": null, "processing_progress": 100, "processing_step": "completed", "created_at": "2026-07-09T10:00:00Z", "updated_at": "2026-07-09T10:05:00Z" } ] ``` **What to Check**: - Response is an array (even if empty: `[]`) - Each document has `id`, `name`, `status`, `file_url` - `user` object matches logged-in user - No other user's documents are visible (multi-tenancy) **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Response is an array", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.be.an("array"); }); pm.test("Documents belong to current user only", function () { var jsonData = pm.response.json(); jsonData.forEach(function(doc) { pm.expect(doc.user.username).to.eql(pm.environment.get("test_username")); }); }); ``` --- ### ENDPOINT 5: GET /api/documents/{id}/ **Folder**: `03 - Document Management` **Setup**: ``` Name: Get Document Detail Method: GET URL: {{base_url}}/api/documents/{{uploaded_doc_id}}/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None **Expected Response**: - Status: `200 OK` - Body: Single document object (same structure as list item) **What to Check**: - Returned document `id` matches `{{uploaded_doc_id}}` - All fields are present and populated - `file_url` is a valid URL **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Returned document matches requested ID", function () { var jsonData = pm.response.json(); pm.expect(jsonData.id).to.eql(parseInt(pm.environment.get("uploaded_doc_id"))); }); pm.test("Document has all required fields", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.all.keys( "id", "user", "name", "file_url", "status", "entity_count", "relationship_count", "error_message", "processing_progress", "processing_step", "created_at", "updated_at" ); }); ``` --- ### ENDPOINT 6: DELETE /api/documents/{id}/ **Folder**: `03 - Document Management` **Setup**: ``` Name: Delete Document Method: DELETE URL: {{base_url}}/api/documents/{{uploaded_doc_id}}/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None **Expected Response**: - Status: `200 OK` - Body: ```json { "message": "Document and all extracted nodes/vectors deleted successfully." } ``` **What to Check**: - Confirmation message is returned - Document is actually removed (call List Documents to verify) **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Deletion confirmation message", function () { var jsonData = pm.response.json(); pm.expect(jsonData.message).to.include("deleted successfully"); }); ``` --- ### ENDPOINT 7: POST /api/query/ **Folder**: `04 - Query Endpoints` **Setup**: ``` Name: Hybrid Query (Default) Method: POST URL: {{base_url}}/api/query/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} Content-Type: application/json ``` **Body** (raw JSON): ```json { "query": "What are the main topics discussed in the document?", "mode": "hybrid" } ``` > `mode` can be: `"hybrid"` (default), `"graph"`, or `"vector"` **Expected Response**: - Status: `200 OK` - Body: ```json { "answer": "The document discusses several main topics including...", "sources": [ { "content": "excerpt from document...", "metadata": {"page": 1, "source": "test-document.pdf"} } ], "strategy": "HYBRID", "success": true, "response_time": 2.345 } ``` **What to Check**: - `answer` field contains a non-empty string - `success` is `true` - `strategy` matches the requested mode - `sources` array contains relevant excerpts **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Query returned a valid answer", function () { var jsonData = pm.response.json(); pm.expect(jsonData.success).to.be.true; pm.expect(jsonData.answer).to.be.a("string"); pm.expect(jsonData.answer.length).to.be.greaterThan(0); }); pm.test("Response includes sources", function () { var jsonData = pm.response.json(); pm.expect(jsonData.sources).to.be.an("array"); }); ``` --- ### ENDPOINT 8: POST /api/query/graph-only/ **Folder**: `04 - Query Endpoints` **Setup**: ``` Name: Graph-Only Query Method: POST URL: {{base_url}}/api/query/graph-only/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} Content-Type: application/json ``` **Body** (raw JSON): ```json { "query": "What entities are connected to the main concept?" } ``` **Expected Response**: - Status: `200 OK` - Body: Similar to hybrid query but `strategy` will be `"GRAPH"` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Graph-only strategy is used", function () { var jsonData = pm.response.json(); pm.expect(jsonData.strategy).to.eql("GRAPH"); }); ``` --- ### ENDPOINT 9: POST /api/query/vector-only/ **Folder**: `04 - Query Endpoints` **Setup**: ``` Name: Vector-Only Query Method: POST URL: {{base_url}}/api/query/vector-only/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} Content-Type: application/json ``` **Body** (raw JSON): ```json { "query": "Find similar content about machine learning" } ``` **Expected Response**: - Status: `200 OK` - Body: Similar structure but `strategy` will be `"VECTOR"` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Vector-only strategy is used", function () { var jsonData = pm.response.json(); pm.expect(jsonData.strategy).to.eql("VECTOR"); }); ``` --- ### ENDPOINT 10: POST /api/query/compare/ **Folder**: `04 - Query Endpoints` **Setup**: ``` Name: Compare All Query Modes Method: POST URL: {{base_url}}/api/query/compare/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} Content-Type: application/json ``` **Body** (raw JSON): ```json { "query": "Explain the relationships between key concepts" } ``` **Expected Response**: - Status: `200 OK` - Body: ```json { "query": "Explain the relationships between key concepts", "comparisons": { "graph": { "answer": "...", "sources": [], "strategy": "GRAPH", "response_time": 1.234, "success": true }, "vector": { "answer": "...", "sources": [], "strategy": "VECTOR", "response_time": 0.987, "success": true }, "hybrid": { "answer": "...", "sources": [], "strategy": "HYBRID", "response_time": 1.567, "success": true } }, "success": true } ``` **What to Check**: - All 3 modes (`graph`, `vector`, `hybrid`) are present in `comparisons` - Each has `answer`, `sources`, `strategy`, `response_time`, `success` - `success` is `true` for the overall response **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("All three modes are compared", function () { var jsonData = pm.response.json(); pm.expect(jsonData.comparisons).to.have.all.keys("graph", "vector", "hybrid"); }); pm.test("Each mode has required fields", function () { var jsonData = pm.response.json(); ["graph", "vector", "hybrid"].forEach(function(mode) { pm.expect(jsonData.comparisons[mode]).to.have.all.keys( "answer", "sources", "strategy", "response_time", "success" ); }); }); ``` --- ### ENDPOINT 11: GET /api/graph/ **Folder**: `05 - Graph Endpoints` **Setup**: ``` Name: Get Full Graph Data Method: GET URL: {{base_url}}/api/graph/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None **Expected Response**: - Status: `200 OK` - Body: ```json { "nodes": [ { "id": "entity_1", "label": "Machine Learning", "type": "CONCEPT", "properties": {} } ], "edges": [ { "source": "entity_1", "target": "entity_2", "relationship": "RELATED_TO", "properties": {} } ] } ``` **What to Check**: - `nodes` is an array - `edges` is an array - Each node has `id`, `label`, `type` - Each edge has `source`, `target`, `relationship` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Response contains nodes and edges", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("nodes"); pm.expect(jsonData).to.have.property("edges"); pm.expect(jsonData.nodes).to.be.an("array"); pm.expect(jsonData.edges).to.be.an("array"); }); ``` --- ### ENDPOINT 12: GET /api/graph/entity/{name}/ **Folder**: `05 - Graph Endpoints` **Setup**: ``` Name: Get Entity Details Method: GET URL: {{base_url}}/api/graph/entity/Machine Learning/ ``` > Replace `Machine Learning` with an actual entity name from your graph. **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None **Expected Response**: - Status: `200 OK` - Body: ```json { "name": "Machine Learning", "type": "CONCEPT", "properties": {}, "relationships": [ { "target": "Deep Learning", "type": "SUBSET_OF" } ], "subgraph": { "nodes": [], "edges": [] } } ``` **What to Check**: - Entity `name` matches the requested name - `relationships` array shows connections - `subgraph` contains the local neighborhood **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Entity name matches request", function () { var jsonData = pm.response.json(); pm.expect(jsonData.name).to.eql("Machine Learning"); }); ``` --- ### ENDPOINT 13: GET /api/graph/path/ **Folder**: `05 - Graph Endpoints` **Setup**: ``` Name: Find Path Between Entities Method: GET URL: {{base_url}}/api/graph/path/?entity_a=Entity1&entity_b=Entity2 ``` > Replace `Entity1` and `Entity2` with actual entity names. **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None (query parameters in URL) **Query Parameters** (can also set in "Params" tab): | Key | Value | |------------|-----------| | `entity_a` | `Entity1` | | `entity_b` | `Entity2` | **Expected Response**: - Status: `200 OK` - Body: ```json { "entity_a": "Entity1", "entity_b": "Entity2", "path": ["Entity1", "Intermediate", "Entity2"], "explanation": "Entity1 is connected to Entity2 through...", "path_length": 2, "success": true } ``` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Path response has required fields", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("path"); pm.expect(jsonData.path).to.be.an("array"); pm.expect(jsonData.path.length).to.be.greaterThan(0); }); ``` --- ### ENDPOINT 14: POST /api/graph/cypher/ **Folder**: `05 - Graph Endpoints` **Setup**: ``` Name: Execute Cypher Query Method: POST URL: {{base_url}}/api/graph/cypher/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} Content-Type: application/json ``` **Body** (raw JSON): ```json { "query": "MATCH (n) RETURN labels(n) AS label, count(n) AS count ORDER BY count DESC LIMIT 10" } ``` > This translates natural language or raw Cypher and executes it. **Expected Response**: - Status: `200 OK` - Body: ```json { "cypher": "MATCH (n) RETURN labels(n) AS label, count(n) AS count ORDER BY count DESC LIMIT 10", "results": [ {"label": ["CONCEPT"], "count": 42}, {"label": ["PERSON"], "count": 15} ], "success": true } ``` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Cypher query executed successfully", function () { var jsonData = pm.response.json(); pm.expect(jsonData.success).to.be.true; pm.expect(jsonData.results).to.be.an("array"); }); ``` --- ### ENDPOINT 15: GET /api/graph/stats/ **Folder**: `05 - Graph Endpoints` **Setup**: ``` Name: Get Graph Statistics Method: GET URL: {{base_url}}/api/graph/stats/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None **Expected Response**: - Status: `200 OK` - Body: ```json { "total_nodes": 150, "total_edges": 320, "node_types": { "CONCEPT": 42, "PERSON": 15, "ORGANIZATION": 8, "DOCUMENT": 5 }, "edge_types": { "RELATED_TO": 120, "AUTHORED_BY": 30, "PART_OF": 45 } } ``` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Stats contain node and edge counts", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("total_nodes"); pm.expect(jsonData).to.have.property("total_edges"); pm.expect(jsonData.total_nodes).to.be.a("number"); pm.expect(jsonData.total_edges).to.be.a("number"); }); ``` --- ### ENDPOINT 16: GET /api/graph/communities/ **Folder**: `05 - Graph Endpoints` **Setup**: ``` Name: List Communities Method: GET URL: {{base_url}}/api/graph/communities/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None **Expected Response**: - Status: `200 OK` - Body: ```json { "communities": [ { "id": 1, "label": "Community 1", "summary": "This community covers...", "member_count": 12, "members": ["Entity1", "Entity2", "Entity3"] } ], "count": 1 } ``` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Communities response structure", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("communities"); pm.expect(jsonData).to.have.property("count"); pm.expect(jsonData.communities).to.be.an("array"); }); ``` --- ### ENDPOINT 17: GET /api/graph/communities/{id}/ **Folder**: `05 - Graph Endpoints` **Setup**: ``` Name: Get Community Detail Method: GET URL: {{base_url}}/api/graph/communities/1/ ``` > Replace `1` with an actual community ID from the list endpoint. **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None **Expected Response**: - Status: `200 OK` - Body: ```json { "id": 1, "label": "Community 1", "summary": "This community covers...", "member_count": 12, "members": ["Entity1", "Entity2"], "member_details": [ { "name": "Entity1", "type": "CONCEPT", "properties": {} } ] } ``` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Community ID matches request", function () { var jsonData = pm.response.json(); pm.expect(jsonData.id).to.eql(1); }); ``` --- ### ENDPOINT 18: POST /api/graph/search/ **Folder**: `05 - Graph Endpoints` **Setup**: ``` Name: Search Entities Method: POST URL: {{base_url}}/api/graph/search/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} Content-Type: application/json ``` **Body** (raw JSON): ```json { "query": "machine learning" } ``` **Expected Response**: - Status: `200 OK` - Body: ```json { "query": "machine learning", "results": [ { "name": "Machine Learning", "type": "CONCEPT", "properties": {"description": "..."} }, { "name": "Deep Learning", "type": "CONCEPT", "properties": {"description": "..."} } ], "count": 2 } ``` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Search returns results array", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("results"); pm.expect(jsonData.results).to.be.an("array"); pm.expect(jsonData).to.have.property("count"); }); ``` --- ### ENDPOINT 19: GET /api/evaluation/ **Folder**: `06 - Evaluation` **Setup**: ``` Name: Run Evaluation Method: GET URL: {{base_url}}/api/evaluation/ ``` **Headers**: ``` Authorization: Bearer {{access_token}} ``` **Body**: None **Expected Response**: - Status: `200 OK` - Body: ```json { "evaluations": [ { "question": "What is X?", "expected_answer": "X is...", "results": { "graph": { "answer": "...", "response_time": 1.234, "success": true }, "vector": { "answer": "...", "response_time": 0.987, "success": true }, "hybrid": { "answer": "...", "response_time": 1.567, "success": true } } } ], "summary": { "total_pairs": 1, "avg_response_times": { "graph": 1.234, "vector": 0.987, "hybrid": 1.567 } } } ``` **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Evaluation has correct structure", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("evaluations"); pm.expect(jsonData).to.have.property("summary"); pm.expect(jsonData.evaluations).to.be.an("array"); }); ``` --- ### ENDPOINT 20: GET /api/health/ **Folder**: `01 - Health Check` **Setup**: ``` Name: Health Check Method: GET URL: {{base_url}}/api/health/ ``` **Headers**: None needed (no auth required) **Body**: None **Expected Response**: - Status: `200 OK` - Body: ```json { "status": "healthy", "services": { "django": "healthy", "neo4j": "healthy", "timestamp": 1688888000.0 } } ``` **What to Check**: - `status` is `"healthy"` or `"degraded"` - `services.django` is `"healthy"` - `services.neo4j` is `"healthy"` (if Neo4j is running) **Post-Response Script** (Tests tab): ```javascript pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Django service is healthy", function () { var jsonData = pm.response.json(); pm.expect(jsonData.services.django).to.eql("healthy"); }); pm.test("Health status is valid", function () { var jsonData = pm.response.json(); pm.expect(jsonData.status).to.be.oneOf(["healthy", "degraded"]); }); ``` --- ## 5. Chaining Requests with JWT Tokens ### How Token Chaining Works The key mechanism is the **Post-Response Script** in the Login request that saves tokens to environment variables: ``` Login Request ↓ (Post-Response Script saves tokens) pm.environment.set("access_token", jsonData.access); pm.environment.set("refresh_token", jsonData.refresh); ↓ All other requests use: Authorization: Bearer {{access_token}} ``` ### Complete Chaining Flow ``` Step 1: Health Check (no auth needed) ↓ Step 2: Register User (no auth needed) ↓ Step 3: Login -> saves access_token & refresh_token to environment ↓ Step 4-19: All other requests use {{access_token}} in Authorization header ↓ Step 20 (optional): Refresh Token -> saves new access_token ``` ### Refreshing an Expired Token When your access token expires (after 30 minutes), create a new request: **Setup**: ``` Name: Refresh Token Method: POST URL: {{base_url}}/api/auth/token/refresh/ ``` **Headers**: ``` Content-Type: application/json ``` **Body** (raw JSON): ```json { "refresh": "{{refresh_token}}" } ``` **Post-Response Script** (Tests tab): ```javascript pm.test("Token refreshed successfully", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("access"); pm.environment.set("access_token", jsonData.access); // If refresh token is also rotated if (jsonData.refresh) { pm.environment.set("refresh_token", jsonData.refresh); } }); ``` --- ## 6. Writing Post-Response Tests ### Test Syntax Reference ```javascript // Status code tests pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); // Response time tests pm.test("Response time is under 2 seconds", function () { pm.expect(pm.response.responseTime).to.be.below(2000); }); // Header tests pm.test("Content-Type is JSON", function () { pm.response.to.have.header("Content-Type", "application/json"); }); // Body field existence pm.test("Response has 'answer' field", function () { var jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("answer"); }); // Body field value pm.test("Answer is not empty", function () { var jsonData = pm.response.json(); pm.expect(jsonData.answer.length).to.be.greaterThan(0); }); // Array tests pm.test("Results is an array with items", function () { var jsonData = pm.response.json(); pm.expect(jsonData.results).to.be.an("array"); pm.expect(jsonData.results.length).to.be.greaterThan(0); }); // Type checks pm.test("ID is a number", function () { var jsonData = pm.response.json(); pm.expect(jsonData.id).to.be.a("number"); }); // Nested object checks pm.test("User object has username", function () { var jsonData = pm.response.json(); pm.expect(jsonData.user.username).to.be.a("string"); }); // Conditional tests pm.test("If success is true, answer exists", function () { var jsonData = pm.response.json(); if (jsonData.success) { pm.expect(jsonData.answer).to.exist; } }); // Environment variable assertions pm.test("Token was saved", function () { pm.expect(pm.environment.get("access_token")).to.exist; pm.expect(pm.environment.get("access_token").length).to.be.greaterThan(10); }); // Chai assertions (more expressive) pm.test("Response matches expected schema", function () { var schema = { type: "object", required: ["answer", "sources", "success"], properties: { answer: { type: "string" }, sources: { type: "array" }, success: { type: "boolean" } } }; var jsonData = pm.response.json(); pm.expect(tv4.validate(jsonData, schema)).to.be.true; }); ``` ### Setting Variables in Tests ```javascript // Set environment variable pm.environment.set("variable_name", value); // Set collection variable (shared across environments) pm.collectionVariables.set("variable_name", value); // Set global variable pm.globals.set("variable_name", value); // Get a variable var val = pm.environment.get("variable_name"); // Clear a variable pm.environment.unset("variable_name"); ``` --- ## 7. Common Errors & Troubleshooting ### Error 1: `401 Unauthorized` ``` Cause: Missing or invalid JWT token Fix: 1. Ensure you ran the Login request first 2. Check that access_token environment variable is set 3. Verify the Authorization header format: "Bearer {{access_token}}" 4. Token may have expired (30 min lifetime) - run Refresh Token request ``` ### Error 2: `400 Bad Request - "Passwords do not match"` ``` Cause: password and confirm_password fields differ Fix: 1. Ensure both fields have identical values 2. Check for trailing spaces in either field 3. Password must contain: uppercase, lowercase, number, special char (@$!%*?&) ``` ### Error 3: `400 Bad Request - "Disposable or temporary email accounts are not permitted"` ``` Cause: Email domain is in the blocklist Fix: Use a legitimate email domain like: - @gmail.com - @outlook.com - @company.com Do NOT use: mailinator.com, yopmail.com, tempmail.com, etc. ``` ### Error 4: `400 Bad Request - "No file was uploaded"` ``` Cause: Missing file in multipart form-data Fix: 1. Body type must be "form-data" (not raw JSON) 2. Key must be exactly "file" 3. Change key type from "Text" to "File" (hover over key field) 4. Select a valid file using the file picker ``` ### Error 5: `413 Request Entity Too Large` ``` Cause: File exceeds 10 MB limit Fix: Compress or split your file to under 10 MB ``` ### Error 6: `400 Bad Request - "File type '.exe' is not allowed"` ``` Cause: Uploaded file extension not in allowed list Allowed: .pdf, .txt, .md, .docx, .doc, .csv, .json, .html, .xml Fix: Convert file to an allowed format ``` ### Error 7: `404 Not Found - "Entity 'X' not found"` ``` Cause: Requested entity name doesn't exist in the graph Fix: 1. First call GET /api/graph/ to see available entities 2. Or call POST /api/graph/search/ to find entity names 3. Entity names are case-sensitive ``` ### Error 8: `429 Too Many Requests` ``` Cause: Rate limit exceeded (100 requests/minute for authenticated users) Fix: 1. Wait 60 seconds before retrying 2. Reduce request frequency 3. In Postman Runner, add a delay between iterations ``` ### Error 9: `500 Internal Server Error` ``` Cause: Server-side error (Neo4j down, LLM API key missing, etc.) Fix: 1. Check Django server logs 2. Verify Neo4j is running: GET /api/health/ 3. Check LLM API key is configured in .env 4. Ensure ChromaDB is initialized ``` ### Error 10: `Connection Refused` ``` Cause: Django server not running Fix: cd backend python manage.py runserver # Should see: Starting development server at http://localhost:8000/ ``` --- ## 8. Using Postman Runner for Batch Testing ### Step 8.1: Set Up a Test Suite Order Create a file called `test-order.json` or manually arrange requests in this order: ``` Test Execution Order: 1. GET /api/health/ (verify server is up) 2. POST /api/auth/register/ (create test user) 3. POST /api/auth/login/ (get JWT tokens) 4. POST /api/documents/upload/ (upload test document) 5. GET /api/documents/ (list documents) 6. GET /api/documents/{id}/ (get document detail) 7. POST /api/query/ (hybrid query) 8. POST /api/query/graph-only/ (graph query) 9. POST /api/query/vector-only/ (vector query) 10. POST /api/query/compare/ (compare all modes) 11. GET /api/graph/ (full graph) 12. GET /api/graph/entity/{name}/ (entity detail) 13. GET /api/graph/path/ (find path) 14. POST /api/graph/cypher/ (execute cypher) 15. GET /api/graph/stats/ (graph stats) 16. GET /api/graph/communities/ (list communities) 17. GET /api/graph/communities/{id}/ (community detail) 18. POST /api/graph/search/ (search entities) 19. GET /api/evaluation/ (run evaluation) 20. DELETE /api/documents/{id}/ (cleanup - delete document) ``` ### Step 8.2: Open Postman Runner ``` 1. Click "Runner" button in the top toolbar (or press Ctrl+Shift+R) 2. A new tab opens with the Collection Runner interface ``` ### Step 8.3: Configure the Runner ``` 1. Select Collection: "GraphRAG Knowledge Graph AI API" 2. Select Environment: "GraphRAG Local Dev" 3.Iterations: Set number of times to run (e.g., 5 for stress testing) 4. Delay: Set delay between requests in ms (e.g., 500ms) 5. Data File: (Optional) Upload a CSV/JSON with test data 6. Save responses: Toggle ON to save all responses 7. Keep variable values: Select "Save to Environment" ``` ### Step 8.4: Data-Driven Testing with CSV Create a file called `test-queries.csv`: ```csv query,mode,expected_strategy What is machine learning?,hybrid,HYBRID Explain neural networks,graph,GRAPH Find similar research papers,vector,VECTOR Compare all approaches for NLP,hybrid,HYBRID ``` In Runner: ``` 1. Click "Select File" next to "Data" 2. Upload test-queries.csv 3. In your requests, use: {{query}}, {{mode}}, {{expected_strategy}} 4. Postman will iterate through each row ``` ### Step 8.5: Run and Analyze Results ``` 1. Click "Run GraphRAG Knowledge Graph AI API" 2. Watch the execution in real-time 3. After completion, you'll see: - Total tests passed/failed - Response times for each request - Assertion results - Any errors encountered ``` ### Step 8.6: Export Results ``` 1. After run completes, click "Export Results" 2. Save as JSON for further analysis 3. Or use "Download as JSON" for CI/CD integration ``` ### Step 8.7: Newman (Command Line Runner) For CI/CD pipelines, use Newman: ```bash # Install Newman npm install -g newman # Export collection from Postman # (In Postman: Collection -> ... -> Export -> Collection v2.1) # Run collection newman run "GraphRAG_Knowledge_Graph_AI_API.postman_collection.json" \ --environment "GraphRAG_Local_Dev.postman_environment.json" \ --iteration-count 5 \ --delay-request 500 \ --reporters cli,html # Run with data file newman run "GraphRAG_Knowledge_Graph_AI_API.postman_collection.json" \ --environment "GraphRAG_Local_Dev.postman_environment.json" \ --iteration-data "test-queries.csv" \ --reporters cli,html ``` --- ## Quick Reference Card ### Authentication Headers ``` All authenticated requests need: Authorization: Bearer {{access_token}} Content-Type: application/json (for JSON bodies) Content-Type: multipart/form-data (for file uploads - set in body tab, not header) ``` ### Request Body Formats ``` JSON Body: { "key": "value" } Form-Data Body (for file uploads): Key: file | Type: File | Value: [select file] Query Parameters: ?entity_a=X&entity_b=Y ``` ### Expected Status Codes | Code | Meaning | When | |------|----------------------------------|-------------------------------------| | 200 | OK | Successful GET/DELETE | | 201 | Created | Successful POST (register) | | 202 | Accepted | Upload accepted (async processing) | | 400 | Bad Request | Invalid input / validation error | | 401 | Unauthorized | Missing/invalid/expired JWT token | | 404 | Not Found | Entity/document doesn't exist | | 413 | Request Entity Too Large | File exceeds 10MB limit | | 429 | Too Many Requests | Rate limit exceeded | | 500 | Internal Server Error | Server-side failure | ### Password Requirements ``` Must contain: - At least 1 uppercase letter (A-Z) - At least 1 lowercase letter (a-z) - At least 1 number (0-9) - At least 1 special character (@$!%*?&) - Minimum 8 characters - Must match confirm_password field ``` --- **Guide Version**: 1.0 **API Base URL**: http://localhost:8000 **Total Endpoints Documented**: 20 **Authentication**: JWT (SimpleJWT - 30min access, 7-day refresh)