108 lines
4.7 KiB
Plaintext
108 lines
4.7 KiB
Plaintext
|
|
# =============================================================
|
||
|
|
# OxiCloud — vCard parameter value encoding (RFC 6868)
|
||
|
|
# =============================================================
|
||
|
|
# Contact `type` fields (EMAIL/TEL/ADR) are free text, not a fixed
|
||
|
|
# enum — a value containing a comma, semicolon, colon, or DQUOTE
|
||
|
|
# previously broke the generated vCard's grammar outright (no quoting
|
||
|
|
# or escaping was applied at all). See
|
||
|
|
# application/adapters/param_encoding.rs::render_param_value, wired
|
||
|
|
# into carddav_adapter.rs::contact_to_vcard and
|
||
|
|
# contact_service.rs::generate_vcard.
|
||
|
|
#
|
||
|
|
# Coverage:
|
||
|
|
# 1. A plain type value ("work") is emitted unquoted, unchanged.
|
||
|
|
# 2. A type value containing a comma and an embedded DQUOTE is
|
||
|
|
# emitted quoted and RFC 6868-encoded (embedded DQUOTE → `^'`),
|
||
|
|
# producing a well-formed content line instead of broken syntax.
|
||
|
|
# =============================================================
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# Step 1 — Login, capture JWT
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
POST {{base_url}}/api/auth/login
|
||
|
|
Content-Type: application/json
|
||
|
|
{ "username": "{{username}}", "password": "{{password}}" }
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
[Captures]
|
||
|
|
token: jsonpath "$.access_token"
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# Step 2 — Fresh address book.
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
POST {{base_url}}/api/address-books
|
||
|
|
Authorization: Bearer {{token}}
|
||
|
|
Content-Type: application/json
|
||
|
|
{ "name": "param-encoding-test", "description": "", "is_public": false }
|
||
|
|
|
||
|
|
HTTP 201
|
||
|
|
[Captures]
|
||
|
|
book_id: jsonpath "$.id"
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# Step 3 — Contact with an EMAIL type needing RFC 6868 encoding:
|
||
|
|
# contains a comma (grammar-triggers quoting) AND an
|
||
|
|
# embedded DQUOTE (needs caret-escaping even once quoted).
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
POST {{base_url}}/api/address-books/{{book_id}}/contacts
|
||
|
|
Authorization: Bearer {{token}}
|
||
|
|
Content-Type: application/json
|
||
|
|
{
|
||
|
|
"first_name": "Special",
|
||
|
|
"last_name": "Case",
|
||
|
|
"full_name": "Special Case",
|
||
|
|
"email": [
|
||
|
|
{ "email": "special@example.com", "type": "Work, \"Primary\"", "is_primary": true }
|
||
|
|
],
|
||
|
|
"phone": [
|
||
|
|
{ "number": "+1-555-0199", "type": "mobile", "is_primary": true }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
HTTP 201
|
||
|
|
[Captures]
|
||
|
|
contact_id: jsonpath "$.id"
|
||
|
|
contact_uid: jsonpath "$.uid"
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# Step 4 — Fetch the generated vCard via CardDAV GET.
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
GET {{base_url}}/carddav/{{book_id}}/{{contact_uid}}.vcf
|
||
|
|
Authorization: Bearer {{token}}
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
[Asserts]
|
||
|
|
body startsWith "BEGIN:VCARD"
|
||
|
|
# Plain type value ("mobile") stays bare, unquoted.
|
||
|
|
body contains "TEL;TYPE=MOBILE:+1-555-0199"
|
||
|
|
# Comma + embedded DQUOTE forces quoting and RFC 6868 caret-encoding.
|
||
|
|
body contains "EMAIL;TYPE=\"WORK, ^'PRIMARY^'\":special@example.com"
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# Cleanup
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
GET {{base_url}}/api/address-books/{{book_id}}/contacts/{{contact_id}}
|
||
|
|
Authorization: Bearer {{token}}
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
[Captures]
|
||
|
|
contact_etag: header "ETag"
|
||
|
|
|
||
|
|
|
||
|
|
DELETE {{base_url}}/api/address-books/{{book_id}}/contacts/{{contact_id}}
|
||
|
|
Authorization: Bearer {{token}}
|
||
|
|
If-Match: {{contact_etag}}
|
||
|
|
|
||
|
|
HTTP 204
|
||
|
|
|
||
|
|
|
||
|
|
DELETE {{base_url}}/api/address-books/{{book_id}}
|
||
|
|
Authorization: Bearer {{token}}
|
||
|
|
|
||
|
|
HTTP 204
|