# XML Assertions

This comprehensive guide explains how to write XML assertions for testing XML responses in Kusho using the new `xmlAssert` API.

_NOTE: This is available only on our Enterprise plan for select users. If you're interested in enabling this, please reach out to us at enterprise@kusho.ai or your RM._

## Overview

Kusho now supports **dual assertion systems**:
1. **Traditional Chai assertions** - For JSON responses (existing functionality, unchanged)
2. **New xmlAssert functions** - For XML responses with XPath support

The system automatically detects assertion type and routes to appropriate handlers, ensuring 100% backward compatibility.

## Assertion Processing Model

**Important**: Assertions are processed **line by line**. Each line is treated as a separate, independent assertion.

### Assertion Type Detection

- **xmlAssert assertions**: Lines starting with `xmlAssert.` - routed to XML-specific handler
- **Chai assertions**: All other lines - routed to traditional Chai handler (unchanged)

### Line-by-Line Processing

```javascript
// Each line below is processed as a separate assertion:
expect(response.status).to.equal(200)                           // Line 1: Chai assertion
xmlAssert.assertXPath(response.response, '//catalog', null, 'exists')  // Line 2: XML assertion
expect(response.headers['content-type']).to.include('xml')      // Line 3: Chai assertion
xmlAssert.assertXPath(response.response, '//product', 2, 'count')      // Line 4: XML assertion
```

## XML Assertion API

### Core Function

#### `xmlAssert.assertXPath(xmlString, xpathExpression, expectedValue, assertionType)`
**Purpose**: Validate XPath results with automatic error throwing
**Parameters**:
- `xmlString`: XML content (usually `response.response`)
- `xpathExpression`: XPath query string
- `expectedValue`: Expected result (or `null` for existence checks)
- `assertionType`: Type of assertion (`'exists'`, `'count'`, `'text'`, `'value'`)

## Assertion Types

### 1. Existence Assertions (`'exists'`)
**Purpose**: Verify that elements exist in XML

```javascript
// Check if catalog element exists
xmlAssert.assertXPath(response.response, '//catalog', null, 'exists')

// Check if products with specific attributes exist
xmlAssert.assertXPath(response.response, '//product[@category="electronics"]', null, 'exists')

// Check if nested elements exist
xmlAssert.assertXPath(response.response, '//product/specifications/weight', null, 'exists')
```

### 2. Count Assertions (`'count'`)
**Purpose**: Validate exact number of matching elements

```javascript
// Verify total product count
xmlAssert.assertXPath(response.response, '//product', 5, 'count')

// Count products in specific category
xmlAssert.assertXPath(response.response, '//product[@category="electronics"]', 3, 'count')

// Count elements with specific text content
xmlAssert.assertXPath(response.response, '//product[price > 1000]', 2, 'count')
```

### 3. Text Content Assertions (`'text'`)
**Purpose**: Validate text content of elements

```javascript
// Check product name
xmlAssert.assertXPath(response.response, '//product[@id="1"]/name', 'iPhone 14', 'text')

// Verify category text
xmlAssert.assertXPath(response.response, '//product[1]/@category', 'electronics', 'text')

// Check nested text content
xmlAssert.assertXPath(response.response, '//catalog/description', 'Electronics Catalog', 'text')
```

### 4. Value Assertions (`'value'`)
**Purpose**: Validate element values (supports both string and numeric comparisons)

```javascript
// String value assertions
xmlAssert.assertXPath(response.response, '//product/status', 'active', 'value')

// Numeric value assertions
xmlAssert.assertXPath(response.response, '//product/price', 999.99, 'value')
xmlAssert.assertXPath(response.response, '//product/stock', 25, 'value')

// ID value assertions
xmlAssert.assertXPath(response.response, '//product/@id', '12345', 'value')
```

## Advanced XPath Patterns

### Element Selection
```javascript
// Select by tag name
xmlAssert.assertXPath(response.response, '//product', null, 'exists')

// Select by attribute
xmlAssert.assertXPath(response.response, '//product[@id="123"]', null, 'exists')

// Select by position
xmlAssert.assertXPath(response.response, '//product[1]', null, 'exists')      // First product
xmlAssert.assertXPath(response.response, '//product[last()]', null, 'exists') // Last product

// Select by text content
xmlAssert.assertXPath(response.response, '//product[name="iPhone 14"]', null, 'exists')
```

### Attribute Queries
```javascript
// Check attribute existence
xmlAssert.assertXPath(response.response, '//product[@category]', null, 'exists')

// Check attribute values
xmlAssert.assertXPath(response.response, '//product/@category', 'electronics', 'text')
xmlAssert.assertXPath(response.response, '//product[@id="1"]/@category', 'electronics', 'value')

// Count elements with specific attributes
xmlAssert.assertXPath(response.response, '//product[@available="true"]', 3, 'count')
```

### Conditional Expressions
```javascript
// Elements with numeric conditions
xmlAssert.assertXPath(response.response, '//product[price > 500]', 4, 'count')
xmlAssert.assertXPath(response.response, '//product[stock < 10]', 2, 'count')

// Multiple conditions (AND)
xmlAssert.assertXPath(response.response, '//product[@category="electronics" and price > 1000]', 1, 'count')

// Text contains conditions
xmlAssert.assertXPath(response.response, '//product[contains(name, "iPhone")]', 3, 'count')

// Position-based conditions
xmlAssert.assertXPath(response.response, '//product[position() <= 3]', 3, 'count')
```

### Hierarchical Navigation
```javascript
// Parent-child relationships
xmlAssert.assertXPath(response.response, '//catalog/products/product', 5, 'count')

// Descendant selection
xmlAssert.assertXPath(response.response, '//catalog//product', 5, 'count')

// Following sibling
xmlAssert.assertXPath(response.response, '//product[1]/following-sibling::product', 4, 'count')

// Ancestor selection
xmlAssert.assertXPath(response.response, '//name/ancestor::product', null, 'exists')
```

## Complex Query Examples

### E-commerce XML Response
```xml
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
    <metadata>
        <total>5</total>
        <category>Electronics</category>
    </metadata>
    <products>
        <product id="1" category="electronics" available="true">
            <name>iPhone 14</name>
            <price currency="USD">999.99</price>
            <stock>25</stock>
            <specifications>
                <weight unit="g">172</weight>
                <color>Blue</color>
            </specifications>
        </product>
        <product id="2" category="electronics" available="true">
            <name>MacBook Pro</name>
            <price currency="USD">1999.99</price>
            <stock>10</stock>
            <specifications>
                <weight unit="kg">1.4</weight>
                <color>Silver</color>
            </specifications>
        </product>
    </products>
</catalog>
```

### Comprehensive Test Suite
**Note**: Each line below is processed as an independent assertion:

```javascript
// Basic structure validation - each line is a separate assertion
xmlAssert.assertXPath(response.response, '//catalog', null, 'exists')
xmlAssert.assertXPath(response.response, '//products', null, 'exists')
xmlAssert.assertXPath(response.response, '//metadata', null, 'exists')

// Count validations - independent line-by-line processing
xmlAssert.assertXPath(response.response, '//product', 2, 'count')
xmlAssert.assertXPath(response.response, '//product[@available="true"]', 2, 'count')

// Metadata assertions - each line evaluated separately
xmlAssert.assertXPath(response.response, '//metadata/total', '5', 'text')
xmlAssert.assertXPath(response.response, '//metadata/category', 'Electronics', 'text')

// Product-specific assertions - line-by-line evaluation
xmlAssert.assertXPath(response.response, '//product[@id="1"]/name', 'iPhone 14', 'text')
xmlAssert.assertXPath(response.response, '//product[@id="1"]/price', 999.99, 'value')
xmlAssert.assertXPath(response.response, '//product[@id="2"]/stock', 10, 'value')

// Attribute validations - independent assertions
xmlAssert.assertXPath(response.response, '//product[@id="1"]/@category', 'electronics', 'value')
xmlAssert.assertXPath(response.response, '//price/@currency', 'USD', 'text')

// Nested element assertions - each line is separate
xmlAssert.assertXPath(response.response, '//product[@id="1"]/specifications/weight', '172', 'text')
xmlAssert.assertXPath(response.response, '//specifications/color', 'Blue', 'text')

// Complex queries - single-line XPath expressions
xmlAssert.assertXPath(response.response, '//product[price > 1500]', 1, 'count')
xmlAssert.assertXPath(response.response, '//product[contains(name, "iPhone")]', 1, 'count')
xmlAssert.assertXPath(response.response, '//product[specifications/weight/@unit="kg"]', 1, 'count')
```

## Advanced Usage Patterns

### Single-Line Assertions Only

**Important**: Each assertion must be a complete statement on one line. Multi-line logic is not supported.

```javascript
// ✅ CORRECT - Each line is a complete assertion
xmlAssert.assertXPath(response.response, '//product', 2, 'count')
xmlAssert.assertXPath(response.response, '//product[@id="1"]/name', 'iPhone 14', 'text')
expect(response.status).to.equal(200)

// ❌ INCORRECT - Multi-line logic not supported
const result = xmlAssert.xpath(response.response, '//product/price')
if (result.exists) {
    // This won't work as expected
}

// ❌ INCORRECT - Variable assignments across lines not supported
const productCount = xmlAssert.xpath(response.response, '//product').count
xmlAssert.assertXPath(response.response, '//metadata/total', productCount, 'value')
```

### Working with Single Lines

```javascript
// ✅ CORRECT - Direct assertions on single lines
xmlAssert.assertXPath(response.response, '//product[price > 500]', 3, 'count')
xmlAssert.assertXPath(response.response, '//product[contains(name, "iPhone")]', 1, 'count')

// ✅ CORRECT - Use XPath expressions for complex logic instead of JavaScript
xmlAssert.assertXPath(response.response, '//product[specifications/weight/@unit="kg"]', 1, 'count')
xmlAssert.assertXPath(response.response, '//product[@category="electronics" and price > 1000]', 1, 'count')
```

### Error Handling

**Important**: Multi-line error handling logic is not supported due to line-by-line processing. Use single-line assertions only.

```javascript
// ✅ CORRECT - Each assertion is independent and self-contained
xmlAssert.assertXPath(response.response, '//product', 2, 'count')
xmlAssert.assertXPath(response.response, '//required-field', null, 'exists')

// ❌ INCORRECT - Try-catch blocks don't work across line processing
try {
    xmlAssert.assertXPath(response.response, '//product', 5, 'count')  // Won't work as expected
} catch (error) {
    // This logic won't execute properly
}

// ❌ INCORRECT - Conditional logic across lines not supported
const result = xmlAssert.xpath(response.response, '//optional-field')
if (result.exists) {
    // This won't work due to line-by-line processing
}

// ✅ CORRECT - Use XPath conditional expressions instead
xmlAssert.assertXPath(response.response, '//element[condition]', expectedValue, 'assertion_type')
```

## Backward Compatibility

### Existing Chai Assertions (Unchanged)
Your existing JSON assertions continue to work exactly as before:

```javascript
// These work unchanged - routed to traditional Chai handler
expect(response.status).to.equal(200)
expect(response.data).to.have.property('products')
expect(response.data.products).to.have.length(5)
expect(response.data.products[0]).to.have.property('name')
expect(response.data.products[0].name).to.equal('iPhone 14')
```

### Mixed Assertion Types
You can use both assertion types in the same test case. Each line is processed independently:

```javascript
// Each line below is processed as a separate assertion
expect(response.status).to.equal(200)                                    // Line 1: Chai
expect(response.headers['content-type']).to.include('xml')               // Line 2: Chai
xmlAssert.assertXPath(response.response, '//catalog', null, 'exists')    // Line 3: xmlAssert
xmlAssert.assertXPath(response.response, '//product', 2, 'count')        // Line 4: xmlAssert
expect(response.data).to.have.property('timestamp')                      // Line 5: Chai
```

## Best Practices

### 1. **Use Specific XPath Expressions**
```javascript
// Good - specific and targeted
xmlAssert.assertXPath(response.response, '//product[@id="1"]/name', 'iPhone 14', 'text')

// Avoid - too broad and potentially fragile
xmlAssert.assertXPath(response.response, '//*[text()="iPhone 14"]', null, 'exists')
```

### 2. **Test Structure Before Content**
```javascript
// First verify structure exists
xmlAssert.assertXPath(response.response, '//products', null, 'exists')
xmlAssert.assertXPath(response.response, '//product', 5, 'count')

// Then test specific content
xmlAssert.assertXPath(response.response, '//product[1]/name', 'iPhone 14', 'text')
```

### 3. **Handle Edge Cases**
```javascript
// Check for empty results
const result = xmlAssert.xpath(response.response, '//optional-element')
if (result.count === 0) {
    console.log('Optional element not present - this is acceptable')
} else {
    xmlAssert.assertXPath(response.response, '//optional-element', 'expected-value', 'text')
}
```

### 4. **Use Appropriate Assertion Types**
```javascript
// Use 'exists' for presence checks
xmlAssert.assertXPath(response.response, '//required-field', null, 'exists')

// Use 'count' for quantity validation
xmlAssert.assertXPath(response.response, '//item', 10, 'count')

// Use 'text' for exact text matching
xmlAssert.assertXPath(response.response, '//status', 'active', 'text')

// Use 'value' for numeric or flexible comparisons
xmlAssert.assertXPath(response.response, '//price', 99.99, 'value')
```

## Troubleshooting

### Common Issues

**1. XPath Expression Errors**
- Verify XPath syntax using browser dev tools
- Test expressions in XML validators
- Use browser's `document.evaluate()` to debug

**2. Namespace Issues**
- XML with namespaces requires namespace-aware XPath
- Consider using local-name() for namespace-agnostic queries

**3. Text vs Value Confusion**
- Use 'text' for element text content
- Use 'value' for attribute values and numeric comparisons

**4. Case Sensitivity**
- XML is case-sensitive
- Element names, attribute names, and values must match exactly

### Error Messages
The system provides descriptive error messages:
- `XPath '//product' should exist but no matching nodes found`
- `XPath '//product' should match 5 nodes but got 3`
- `XPath '//product/name' should have text 'iPhone 14' but got 'iPhone 13'`

## Limitations

1. **XPath 1.0 Only**: Browser limitations restrict to XPath 1.0 syntax
2. **Case Sensitive**: All XML elements and attributes are case-sensitive
3. **First Node Priority**: Text and value extractions use first matching node only
4. **No Namespace Prefixes**: Complex namespace handling not supported by default

## Migration Guide

To add XML assertions to existing test cases:

1. **Keep existing assertions unchanged** - they continue to work
2. **Add xmlAssert assertions** for XML-specific validations
3. **Start with existence checks**, then add specific validations
4. **Test thoroughly** to ensure both assertion types work together

This comprehensive system ensures robust XML testing capabilities while maintaining complete backward compatibility with existing Chai-based assertions.
