# API Testing Reference

Comprehensive guide to API testing with Newman, WireMock, and integration patterns.

## Newman

Command-line collection runner for Postman.

**Container**: `postman/newman:latest`

### Running Collections

```bash
# Basic run
docker exec newman newman run /etc/newman/collection.json

# With environment
docker exec newman newman run /etc/newman/collection.json \
  -e /etc/newman/environments/dev.json

# With global variables
docker exec newman newman run /etc/newman/collection.json \
  -g /etc/newman/globals.json

# Override variables
docker exec newman newman run /etc/newman/collection.json \
  --env-var "baseUrl=http://api.example.com" \
  --env-var "apiKey=test123"

# Multiple iterations
docker exec newman newman run /etc/newman/collection.json -n 10

# With data file (data-driven testing)
docker exec newman newman run /etc/newman/collection.json \
  -d /etc/newman/data/test-data.json
```

### Reporters

```bash
# CLI reporter (default)
docker exec newman newman run collection.json -r cli

# JSON reporter
docker exec newman newman run collection.json \
  -r json \
  --reporter-json-export /results/newman.json

# JUnit XML (CI integration)
docker exec newman newman run collection.json \
  -r junit \
  --reporter-junit-export /results/newman.xml

# HTML Extra (detailed HTML report)
docker exec newman newman run collection.json \
  -r htmlextra \
  --reporter-htmlextra-export /results/newman.html

# Multiple reporters
docker exec newman newman run collection.json \
  -r cli,json,junit,htmlextra \
  --reporter-json-export /results/newman.json \
  --reporter-junit-export /results/newman.xml \
  --reporter-htmlextra-export /results/newman.html

# Allure reporter (for Allure dashboard)
docker exec newman newman run collection.json \
  -r allure \
  --reporter-allure-export /results/allure-results
```

### Collection Structure

```json
{
  "info": {
    "name": "API Test Collection",
    "description": "API testing for MyApp",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Authentication",
      "item": [
        {
          "name": "Login",
          "request": {
            "method": "POST",
            "url": "{{baseUrl}}/api/auth/login",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{email}}\",\n  \"password\": \"{{password}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Login successful', function() {",
                  "    pm.response.to.have.status(200);",
                  "});",
                  "",
                  "pm.test('Has auth token', function() {",
                  "    var jsonData = pm.response.json();",
                  "    pm.expect(jsonData.token).to.be.a('string');",
                  "    pm.environment.set('authToken', jsonData.token);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Users",
      "item": [
        {
          "name": "Get User Profile",
          "request": {
            "method": "GET",
            "url": "{{baseUrl}}/api/users/me",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{authToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status 200', function() {",
                  "    pm.response.to.have.status(200);",
                  "});",
                  "",
                  "pm.test('Response time < 500ms', function() {",
                  "    pm.expect(pm.response.responseTime).to.be.below(500);",
                  "});",
                  "",
                  "pm.test('Has required fields', function() {",
                  "    var jsonData = pm.response.json();",
                  "    pm.expect(jsonData).to.have.property('id');",
                  "    pm.expect(jsonData).to.have.property('email');",
                  "    pm.expect(jsonData).to.have.property('name');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}
```

### Environment Files

```json
{
  "id": "dev-environment",
  "name": "Development",
  "values": [
    {
      "key": "baseUrl",
      "value": "http://localhost:8080",
      "enabled": true
    },
    {
      "key": "email",
      "value": "test@example.com",
      "enabled": true
    },
    {
      "key": "password",
      "value": "testpass123",
      "enabled": true
    },
    {
      "key": "authToken",
      "value": "",
      "enabled": true
    }
  ]
}
```

### Test Scripts

#### Pre-request Scripts

```javascript
// Generate timestamp
pm.environment.set("timestamp", Date.now());

// Generate random ID
pm.environment.set("randomId", Math.random().toString(36).substring(7));

// Generate UUID
pm.environment.set("uuid", require('uuid').v4());

// Sign request (HMAC)
const crypto = require('crypto-js');
const secret = pm.environment.get('apiSecret');
const payload = pm.request.body.raw;
const signature = crypto.HmacSHA256(payload, secret).toString();
pm.request.headers.add({key: 'X-Signature', value: signature});
```

#### Test Scripts

```javascript
// Status code checks
pm.test("Status code is 200", function() {
    pm.response.to.have.status(200);
});

pm.test("Status code is 2xx", function() {
    pm.response.to.be.success;
});

// Response time
pm.test("Response time < 500ms", function() {
    pm.expect(pm.response.responseTime).to.be.below(500);
});

// Header checks
pm.test("Content-Type is JSON", function() {
    pm.response.to.have.header("Content-Type");
    pm.expect(pm.response.headers.get("Content-Type")).to.include("application/json");
});

// Body structure
pm.test("Response has required fields", function() {
    const json = pm.response.json();
    pm.expect(json).to.have.property('id');
    pm.expect(json).to.have.property('name');
    pm.expect(json.items).to.be.an('array');
});

// JSON Schema validation
const schema = {
    "type": "object",
    "required": ["id", "name", "email"],
    "properties": {
        "id": {"type": "integer"},
        "name": {"type": "string"},
        "email": {"type": "string", "format": "email"}
    }
};

pm.test("Response matches schema", function() {
    pm.response.to.have.jsonSchema(schema);
});

// Chaining requests
const jsonData = pm.response.json();
pm.environment.set("userId", jsonData.id);
pm.collectionVariables.set("lastCreatedUser", jsonData.id);
```

## WireMock

API mocking and service virtualization.

**Container**: `wiremock/wiremock:latest`
**Port**: 8081
**Admin API**: http://localhost:8081/__admin

### Static Mappings

```json
// wiremock/mappings/health.json
{
  "request": {
    "method": "GET",
    "url": "/api/health"
  },
  "response": {
    "status": 200,
    "headers": {
      "Content-Type": "application/json"
    },
    "jsonBody": {
      "status": "healthy",
      "timestamp": "{{now}}"
    },
    "transformers": ["response-template"]
  }
}
```

### Dynamic Stubs via API

```bash
# Create stub
curl -X POST http://localhost:8081/__admin/mappings \
  -H "Content-Type: application/json" \
  -d '{
    "request": {
      "method": "GET",
      "urlPattern": "/api/users/[0-9]+"
    },
    "response": {
      "status": 200,
      "jsonBody": {
        "id": 1,
        "name": "Test User"
      }
    }
  }'

# List all mappings
curl http://localhost:8081/__admin/mappings

# Delete all mappings
curl -X POST http://localhost:8081/__admin/mappings/reset

# Verify requests
curl http://localhost:8081/__admin/requests

# Get mapping by ID
curl http://localhost:8081/__admin/mappings/{id}
```

### Request Matching

```json
{
  "request": {
    "method": "POST",
    "urlPath": "/api/users",
    "headers": {
      "Content-Type": {
        "equalTo": "application/json"
      },
      "Authorization": {
        "matches": "Bearer .*"
      }
    },
    "queryParameters": {
      "page": {
        "equalTo": "1"
      }
    },
    "bodyPatterns": [
      {
        "matchesJsonPath": "$.email"
      },
      {
        "matchesJsonPath": {
          "expression": "$.name",
          "contains": "test"
        }
      }
    ]
  },
  "response": {
    "status": 201,
    "jsonBody": {
      "id": 123,
      "created": true
    }
  }
}
```

### Response Templating

```json
{
  "request": {
    "method": "GET",
    "urlPath": "/api/users/{{request.pathSegments.[2]}}"
  },
  "response": {
    "status": 200,
    "headers": {
      "Content-Type": "application/json"
    },
    "jsonBody": {
      "id": "{{request.pathSegments.[2]}}",
      "requestedAt": "{{now}}",
      "requestedBy": "{{request.headers.X-User-ID}}",
      "params": "{{request.query.sort}}"
    },
    "transformers": ["response-template"]
  }
}
```

### Fault Simulation

```json
// Delay response
{
  "request": {
    "method": "GET",
    "url": "/api/slow"
  },
  "response": {
    "status": 200,
    "fixedDelayMilliseconds": 5000
  }
}

// Random delay
{
  "request": {
    "method": "GET",
    "url": "/api/variable"
  },
  "response": {
    "status": 200,
    "delayDistribution": {
      "type": "uniform",
      "lower": 500,
      "upper": 2000
    }
  }
}

// Connection reset
{
  "request": {
    "method": "GET",
    "url": "/api/broken"
  },
  "response": {
    "fault": "CONNECTION_RESET_BY_PEER"
  }
}

// Empty response
{
  "request": {
    "method": "GET",
    "url": "/api/empty"
  },
  "response": {
    "fault": "EMPTY_RESPONSE"
  }
}
```

### Stateful Behavior

```json
// Scenario: Order lifecycle
[
  {
    "scenarioName": "Order Lifecycle",
    "requiredScenarioState": "Started",
    "newScenarioState": "Order Created",
    "request": {
      "method": "POST",
      "url": "/api/orders"
    },
    "response": {
      "status": 201,
      "jsonBody": {"id": 1, "status": "created"}
    }
  },
  {
    "scenarioName": "Order Lifecycle",
    "requiredScenarioState": "Order Created",
    "newScenarioState": "Order Confirmed",
    "request": {
      "method": "PUT",
      "url": "/api/orders/1/confirm"
    },
    "response": {
      "status": 200,
      "jsonBody": {"id": 1, "status": "confirmed"}
    }
  }
]
```

## API Testing Patterns

### Pattern 1: Contract Testing

```javascript
// Collection: Contract Tests

// Test: Verify API contract
pm.test("Response matches contract", function() {
    const schema = {
        "type": "object",
        "required": ["id", "name", "email", "created_at"],
        "properties": {
            "id": {"type": "integer", "minimum": 1},
            "name": {"type": "string", "minLength": 1},
            "email": {"type": "string", "format": "email"},
            "created_at": {"type": "string", "format": "date-time"}
        },
        "additionalProperties": false
    };

    pm.response.to.have.jsonSchema(schema);
});
```

### Pattern 2: Data-Driven Testing

```json
// data/users.json
[
    {"email": "user1@test.com", "password": "pass1", "expectedStatus": 200},
    {"email": "user2@test.com", "password": "pass2", "expectedStatus": 200},
    {"email": "invalid", "password": "short", "expectedStatus": 400},
    {"email": "", "password": "", "expectedStatus": 400}
]
```

```javascript
// Test script
pm.test(`Status is ${data.expectedStatus}`, function() {
    pm.response.to.have.status(data.expectedStatus);
});
```

### Pattern 3: Chained Requests

```javascript
// Request 1: Create User
pm.test("User created", function() {
    pm.response.to.have.status(201);
    pm.environment.set("userId", pm.response.json().id);
});

// Request 2: Get User (uses userId from Request 1)
// URL: {{baseUrl}}/users/{{userId}}

pm.test("User retrieved", function() {
    pm.response.to.have.status(200);
    pm.expect(pm.response.json().id).to.eql(parseInt(pm.environment.get("userId")));
});

// Request 3: Delete User
// URL: {{baseUrl}}/users/{{userId}}

pm.test("User deleted", function() {
    pm.response.to.have.status(204);
});
```

### Pattern 4: Authentication Flow

```javascript
// Pre-request script for authenticated requests
const tokenExpiry = pm.environment.get("tokenExpiry");
const now = Date.now();

if (!tokenExpiry || now > tokenExpiry) {
    // Token expired or missing, need to re-authenticate
    pm.sendRequest({
        url: pm.environment.get("baseUrl") + "/api/auth/login",
        method: "POST",
        header: {
            "Content-Type": "application/json"
        },
        body: {
            mode: "raw",
            raw: JSON.stringify({
                email: pm.environment.get("email"),
                password: pm.environment.get("password")
            })
        }
    }, function(err, res) {
        if (!err && res.code === 200) {
            const json = res.json();
            pm.environment.set("authToken", json.token);
            pm.environment.set("tokenExpiry", now + (json.expires_in * 1000));
        }
    });
}
```

### Pattern 5: Error Handling Tests

```javascript
// Test 400 Bad Request
pm.test("Returns 400 for invalid data", function() {
    pm.response.to.have.status(400);
    const json = pm.response.json();
    pm.expect(json).to.have.property('error');
    pm.expect(json.error).to.have.property('code');
    pm.expect(json.error).to.have.property('message');
});

// Test 401 Unauthorized
pm.test("Returns 401 without auth", function() {
    pm.response.to.have.status(401);
});

// Test 404 Not Found
pm.test("Returns 404 for missing resource", function() {
    pm.response.to.have.status(404);
});

// Test 429 Rate Limit
pm.test("Returns 429 on rate limit", function() {
    pm.response.to.have.status(429);
    pm.response.to.have.header("Retry-After");
});
```

## Collection Organization

```
newman-collections/
├── collections/
│   ├── smoke-tests.json          # Quick health checks
│   ├── contract-tests.json       # API contract validation
│   ├── integration-tests.json    # End-to-end flows
│   ├── security-tests.json       # Auth, injection tests
│   └── performance-tests.json    # Response time validation
├── environments/
│   ├── local.json
│   ├── dev.json
│   ├── staging.json
│   └── production.json
├── data/
│   ├── users.json
│   ├── products.json
│   └── test-cases.csv
└── globals.json
```

## CI/CD Integration

### GitHub Actions

```yaml
name: API Tests
on: [push, pull_request]

jobs:
  api-tests:
    runs-on: ubuntu-latest
    services:
      wiremock:
        image: wiremock/wiremock:latest
        ports:
          - 8081:8080

    steps:
      - uses: actions/checkout@v3

      - name: Run Newman Tests
        run: |
          docker run --rm --network host \
            -v ${PWD}/collections:/etc/newman \
            postman/newman run /etc/newman/collection.json \
            -e /etc/newman/environments/ci.json \
            -r cli,junit \
            --reporter-junit-export results.xml

      - name: Publish Results
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: API Tests
          path: results.xml
          reporter: java-junit
```

## Best Practices

1. **Organize by feature** - Group related endpoints together
2. **Use environments** - Separate configs for each stage
3. **Document tests** - Clear names and descriptions
4. **Include negative tests** - Test error handling
5. **Validate contracts** - Use JSON Schema validation
6. **Chain requests logically** - Setup → Test → Cleanup
7. **Mock external services** - Use WireMock for dependencies
8. **Test performance** - Include response time assertions
9. **Automate in CI** - Run on every commit
10. **Report clearly** - Generate readable reports
