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:
parent
2172989699
commit
2be65df31f
1 changed files with 463 additions and 0 deletions
463
.agents/skills/dolibarr-hurl-test/SKILL.md
Normal file
463
.agents/skills/dolibarr-hurl-test/SKILL.md
Normal 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/)
|
||||
Loading…
Reference in a new issue