NEW: Add Dolibarr Hurl Test Skill (#39366)

# NEW: Add Dolibarr Hurl Test Skill

Added comprehensive documentation for creating and maintaining Hurl tests for Dolibarr ERP/CRM. The documentation covers file naming conventions, authentication requirements, test structure, and best practices for Dolibarr-specific Hurl testing.
This commit is contained in:
MDW 2026-08-04 18:26:12 +02:00 committed by GitHub
parent 2172989699
commit 2be65df31f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -0,0 +1,463 @@
---
name: dolibarr-hurl-test
description: Create and maintain Hurl tests for Dolibarr ERP/CRM. Use when asked to create, write, or fix hurl tests for Dolibarr, or when working with test/hurl directory. Covers Dolibarr-specific conventions for hurl testing including authentication, test file naming, and test structure.
---
# Dolibarr Hurl Test Skill
## Description
Create and maintain Hurl tests for Dolibarr ERP/CRM. Use when asked to create, write, or fix hurl tests for Dolibarr, or when working with test/hurl directory. This skill covers the Dolibarr-specific conventions for hurl testing including authentication, test file naming, and test structure.
**IMPORTANT**: Add `body not contains "xdebug-error"` assertions to Hurl tests (and also for each {Error:,Warning:,notice,warning}).
**NOTE**: Hurl uses Rust regex syntax, which does not support negative lookaheads (`(?!...)`). Use character class exclusions like `[^X]` instead.
## When to Use
- User asks to create hurl tests for Dolibarr
- User asks to fix failing hurl tests
- Working with test/hurl directory
- Need to test Dolibarr GUI or API endpoints
- User mentions "hurl test" or "hurl tests"
## Dolibarr Hurl Test Conventions
### File Naming Pattern
Hurl test files must follow a specific naming pattern based on the number prefix:
| Prefix | Description | Authentication Required |
|--------|-------------|------------------------|
| 00 | No authentication required | No |
| 10 | Authentication required (GUI tests) | Yes |
| 20 | Reserved for future use | - |
| 30 | POST data for later tests | Yes |
| 40 | GET data that was POST'ed in 30_ files | Yes |
| 50 | PUT data that was POST'ed in 30_ files | Yes |
| 60 | GET data that was PUT'ed in 50_ files | Yes |
| 70 | DELETE data | Yes |
| 80 | GET data that was just DELETE'd | Yes |
| 90 | Reserved for future use | - |
**Important**: For Dolibarr GUI tests that require authentication, use the `10` prefix (not 30, 40, etc.). The run.sh script only executes GUI tests matching `*/10*.hurl`.
### File Structure
```
test/hurl/
├── api/ # API endpoint tests
│ ├── 00_*.hurl # No auth required
│ └── 10_*.hurl # Auth required
├── gui/ # GUI/browser tests
│ ├── 00_*.hurl # No auth required
│ ├── 10_*.hurl # Auth required (most GUI tests)
│ └── save_login_cookie.hurl # Special file for login
└── public/ # Public endpoint tests
└── 00_*.hurl
```
### Directory Structure
Place tests in directories matching the Dolibarr module/path structure:
- `test/hurl/gui/fourn/commande/` - Supplier order tests
- `test/hurl/gui/societe/` - Third party/company tests
- `test/hurl/api/commande/` - Customer order API tests
- `test/hurl/api/fourn/` - Supplier API tests
### Authentication
Dolibarr requires authentication for most GUI operations. The hurl framework handles this through:
1. **save_login_cookie.hurl** - Special file that logs in and saves cookies
2. **Cookie jar** - Passed via `--cookiefile` parameter
3. **CSRF token** - Captured from login page and passed with requests
#### Capturing CSRF Token
```hurl
# Get the login page to capture CSRF token
GET http://{{hostnport}}/
HTTP 200
[Captures]
token: xpath "normalize-space(//meta[@name='anti-csrf-newtoken']/@content)"
# Use the token in subsequent requests
POST http://{{hostnport}}/some/page.php
[FormParams]
action: some_action
token: {{token}}
```
### Creating Test Data
#### Supplier Creation
Suppliers (fournisseurs) are created via `societe/card.php` with `type=f`:
```hurl
POST http://{{hostnport}}/societe/card.php?leftmenu=suppliers&action=create&type=f
HTTP 200
[FormParams]
name: Test Supplier {{timestamp}}
code_fournisseur: TEST_{{timestamp}}
fournisseur: 1
client: 0
email: test-{{timestamp}}@example.com
address:
zip:
town:
country_id: 1
token: {{token}}
# Capture the supplier ID
GET http://{{hostnport}}/societe/card.php?socid={{socid}}&leftmenu=suppliers
HTTP 200
[Captures]
socid: queryParam "socid"
```
#### Supplier Order Creation
```hurl
POST http://{{hostnport}}/fourn/commande/card.php?action=create
HTTP 200
[FormParams]
socid: {{socid}}
date: {{now}}
date_livraison: {{now_plus_7_days}}
ref_supplier: ORDER-{{timestamp}}
statut: 0
token: {{token}}
# Capture the order ID
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 200
[Captures]
orderid: queryParam "id"
```
#### Customer Order Creation
```hurl
POST http://{{hostnport}}/commande/card.php?action=create
HTTP 200
[FormParams]
socid: {{socid}}
date: {{now}}
date_livraison: {{now_plus_7_days}}
ref_customer: ORDER-{{timestamp}}
statut: 0
token: {{token}}
```
### Assertions
Use Dolibarr-specific assertions:
```hurl
# Check for tracking number display
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 200
[Asserts]
body contains "FX123456789012"
body contains "FedEx"
body contains "TrackingNumber"
body contains "https://www.fedex.com/tracking/FX123456789012"
```
#### Handling Expected Warnings
When testing for expected warning messages while catching unexpected ones:
```hurl
# Expect a specific warning but catch others
GET http://{{hostnport}}/some/page.php
HTTP 200
[Asserts]
body contains "Warning: Invoice.*has different tracking"
body not contains "Warning: [^I]"
```
This allows the expected "Invoice..." warning while catching any other warnings that don't start with "I".
#### Regex Patterns
Hurl uses Rust regex syntax. Important differences from PCRE:
- No negative lookahead support: `(?!pattern)` is NOT supported
- Use character class exclusions: `[^X]` to match any character except X
- To match a literal `?` in URLs, use `[?]` not `\?`
- Start anchors: Use `^` for start of string
```hurl
# Match redirect Location header with query parameter
header "Location" matches "^/fourn/facture/card.php[?]id=[0-9]+$"
# Match any warning except those starting with "I"
body not contains "Warning: [^I]"
```
### Running Tests
#### Using run_with_server.sh
The recommended way to run hurl tests locally:
```bash
# Run specific test path
test/hurl/run_with_server.sh gui/fourn/commande
# Run with custom port
test/hurl/run_with_server.sh --port=9000 gui/fourn/commande
```
#### Using run.sh Directly
```bash
# Set environment variables
export DOLIHOST="127.0.50.1"
export DOLIPORT="9999"
export DOLIUSERNAME="admin"
export DOLIPASSWORD="admin"
export COOKIEJAR="/tmp/hurl_cookie.jar"
# Run tests
test/hurl/run.sh --cookiefile="$COOKIEJAR" --port="$DOLIPORT" --host="$DOLIHOST" --user="$DOLIUSERNAME" --pass="$DOLIPASSWORD" gui/fourn/commande
```
#### Running Specific Tests
```bash
# Run all tests containing "tracking"
test/hurl/run_with_server.sh tracking
# Run tests in a specific directory
test/hurl/run_with_server.sh gui/fourn
# Run specific test file
test/hurl/run_with_server.sh 10_supplier_order_tracking_POST
```
### Test File Template
#### GUI Test with Authentication (10 prefix)
```hurl
# Test description
POST http://{{hostnport}}/module/page.php?action=create
HTTP 200
[FormParams]
field1: value1
field2: value2
token: {{token}}
# Capture created ID
GET http://{{hostnport}}/module/page.php?id={{created_id}}
HTTP 200
[Captures]
created_id: queryParam "id"
# Verify the creation
GET http://{{hostnport}}/module/page.php?id={{created_id}}
HTTP 200
[Asserts]
body contains "value1"
body contains "value2"
body not contains "warning"
body not contains "error"
body not contains "notice"
body not contains "Warning:"
body not contains "Error:"
body not contains "xdebug-error"
```
#### API Test with Authentication (10 prefix)
```hurl
# API tests use the DOLAPIKEY header
GET http://{{hostnport}}/api/index.php/orders
HTTP 200
DOLAPIKEY: DOLAPIKEY: your_api_key_here
[Asserts]
jsonpath "$.data" exists
jsonpath "$.pagination.total" >= 0
body not contains "warning"
body not contains "error"
body not contains "notice"
body not contains "Warning:"
body not contains "Error:"
```
### Built-in Variables
Hurl provides useful built-in variables:
| Variable | Description | Example |
|----------|-------------|---------|
| {{timestamp}} | Current timestamp | 1712345678 |
| {{now}} | Current date/time | 2024-04-05T12:34:56 |
| {{now_plus_7_days}} | Date 7 days from now | 2024-04-12T12:34:56 |
| {{hostnport}} | Host and port | 127.0.50.1:9999 |
| {{token}} | CSRF token (captured) | abc123... |
| {{username}} | Username | admin |
| {{password}} | Password | admin |
### Best Practices
1. **Always include token in POST requests**
```hurl
[FormParams]
action: create
token: {{token}}
```
2. **Use proper URL patterns**
- Supplier creation: `societe/card.php?leftmenu=suppliers&action=create&type=f`
- Customer creation: `societe/card.php?leftmenu=customers&action=create&type=c`
- Supplier order: `fourn/commande/card.php`
- Customer order: `commande/card.php`
3. **Capture IDs for cleanup**
Always capture created IDs so they can be used in subsequent tests and cleaned up:
```hurl
[Captures]
socid: queryParam "socid"
orderid: queryParam "id"
```
4. **Clean up test data**
Create DELETE tests (70_ prefix) to remove test data:
```hurl
POST http://{{hostnport}}/fourn/commande/card.php?action=delete
HTTP 200
[FormParams]
id: {{orderid}}
token: {{token}}
```
5. **Use proper HTTP status codes**
- 200 for successful operations
- 404 for deleted/not found resources
- 400 for bad requests
6. **Test both positive and negative cases**
- Test valid inputs
- Test invalid inputs
- Test edge cases
7. **Keep tests small and focused**
Each test file should test one specific functionality.
8. **Handle expected messages properly**
- Use `body contains "Expected message"` for messages you expect to see
- Use `body not contains "Error:"` and `body not contains "Warning:"` for unexpected errors
- For expected warnings, check for the specific warning text and use exclusion patterns for unexpected ones
### Common Issues and Fixes
#### Issue: Tests not being found
**Cause**: Wrong file name prefix
**Fix**: Use `10_` prefix for GUI tests that require authentication
#### Issue: Authentication failing
**Cause**: Missing token or cookie
**Fix**: Ensure `save_login_cookie.hurl` runs first and token is captured
#### Issue: CSRF protection blocking requests
**Cause**: Missing token parameter
**Fix**: Add `token: {{token}}` to all POST requests with actions
#### Issue: Redirects not being followed
**Cause**: Hurl doesn't follow redirects by default
**Fix**: Use separate GET requests to follow redirects, capture IDs from URLs
#### Issue: Regex patterns not working
**Cause**: Using PCRE syntax (like `(?!...)` negative lookahead) which Rust regex doesn't support
**Fix**: Use Rust-compatible regex syntax. For excluding patterns, use character class exclusions like `[^X]` instead of negative lookaheads
#### Issue: URL query parameter matching
**Cause**: Using `\?` to match literal `?` in URLs
**Fix**: Use `[?]` to match literal question marks in regex patterns
#### Issue: Expected warnings being flagged as failures
**Cause**: Using `body not contains "Warning:"` when some warnings are expected
**Fix**: Use `body contains "Warning: ExpectedMessage"` for expected warnings and `body not contains "Warning: [^E]"` to catch unexpected warnings starting with other characters
#### Issue: Tests running against wrong environment
**Cause**: Hardcoded URLs
**Fix**: Use `{{hostnport}}` variable and proper environment setup
### Example: Complete Test Suite for Supplier Order Tracking
**File: test/hurl/gui/fourn/commande/10_supplier_order_tracking_POST.hurl**
```hurl
# Create a supplier for tracking test
POST http://{{hostnport}}/societe/card.php?leftmenu=suppliers&action=create&type=f
HTTP 200
[FormParams]
name: Test Supplier for Tracking {{timestamp}}
code_fournisseur: TRACKTEST_{{timestamp}}
fournisseur: 1
client: 0
email: test-supplier-{{timestamp}}@example.com
country_id: 1
token: {{token}}
# Capture the supplier ID
GET http://{{hostnport}}/societe/card.php?socid={{socid}}&leftmenu=suppliers
HTTP 200
[Captures]
socid: queryParam "socid"
# Create a supplier order
POST http://{{hostnport}}/fourn/commande/card.php?action=create
HTTP 200
[FormParams]
socid: {{socid}}
date: {{now}}
date_livraison: {{now_plus_7_days}}
ref_supplier: ORDER-{{timestamp}}
statut: 0
token: {{token}}
# Capture the order ID
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 200
[Captures]
orderid: queryParam "id"
# Set tracking information
POST http://{{hostnport}}/fourn/commande/card.php?action=set_tracking
HTTP 200
[FormParams]
id: {{orderid}}
tracking_awb: FX123456789012
carrier_code: FX
token: {{token}}
```
**File: test/hurl/gui/fourn/commande/10_supplier_order_tracking_GET.hurl**
```hurl
# Verify tracking information is displayed
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 200
[Asserts]
body contains "FX123456789012"
body contains "FedEx"
body contains "TrackingNumber"
body contains "https://www.fedex.com/tracking/FX123456789012"
```
**File: test/hurl/gui/fourn/commande/10_supplier_order_tracking_DELETE.hurl**
```hurl
# Delete the supplier order
POST http://{{hostnport}}/fourn/commande/card.php?action=delete
HTTP 200
[FormParams]
id: {{orderid}}
token: {{token}}
# Verify order is deleted
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 404
# Delete the supplier
POST http://{{hostnport}}/societe/card.php?leftmenu=suppliers&action=delete
HTTP 200
[FormParams]
socid: {{socid}}
token: {{token}}
# Verify supplier is deleted
GET http://{{hostnport}}/societe/card.php?socid={{socid}}&leftmenu=suppliers
HTTP 404
```
### Validation
Before committing hurl tests:
1. Run the tests locally with `test/hurl/run_with_server.sh <test_path>`
2. Verify all tests pass
3. Check that cleanup works (DELETE tests)
4. Ensure no test data is left behind
### References
- [Hurl Documentation](https://hurl.dev/docs)
- [Dolibarr Hurl Test README](test/hurl/README.md)
- [Existing Dolibarr Hurl Tests](test/hurl/)