580 lines
19 KiB
JSON
580 lines
19 KiB
JSON
{
|
|
"info": {
|
|
"_postman_id": "xpeditis-api-collection",
|
|
"name": "Xpeditis API - Maritime Freight Booking",
|
|
"description": "Collection complète pour tester l'API Xpeditis - Plateforme de réservation de fret maritime B2B\n\n**Base URL:** http://localhost:4000\n\n**Fonctionnalités:**\n- Recherche de tarifs maritimes multi-transporteurs\n- Création et gestion de réservations\n- Validation automatique des données\n- Cache Redis (15 min)\n\n**Phase actuelle:** MVP Phase 1\n**Authentication:** À implémenter en Phase 2",
|
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
},
|
|
"item": [
|
|
{
|
|
"name": "Rates API",
|
|
"description": "Recherche de tarifs maritimes auprès de plusieurs transporteurs (Maersk, MSC, CMA CGM, etc.)",
|
|
"item": [
|
|
{
|
|
"name": "Search Rates - Rotterdam to Shanghai",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"exec": [
|
|
"pm.test(\"Status code is 200\", function () {",
|
|
" pm.response.to.have.status(200);",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Response has quotes array\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData).to.have.property('quotes');",
|
|
" pm.expect(jsonData.quotes).to.be.an('array');",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Response time is acceptable\", function () {",
|
|
" pm.expect(pm.response.responseTime).to.be.below(3000);",
|
|
"});",
|
|
"",
|
|
"// Save first quote ID for booking tests",
|
|
"if (pm.response.json().quotes.length > 0) {",
|
|
" pm.environment.set(\"rateQuoteId\", pm.response.json().quotes[0].id);",
|
|
" console.log(\"Saved rateQuoteId: \" + pm.response.json().quotes[0].id);",
|
|
"}"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"origin\": \"NLRTM\",\n \"destination\": \"CNSHA\",\n \"containerType\": \"40HC\",\n \"mode\": \"FCL\",\n \"departureDate\": \"2025-02-15\",\n \"quantity\": 2,\n \"weight\": 20000,\n \"isHazmat\": false\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/rates/search",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"rates",
|
|
"search"
|
|
]
|
|
},
|
|
"description": "Recherche de tarifs maritimes pour Rotterdam → Shanghai\n\n**Paramètres:**\n- `origin`: Code UN/LOCODE (5 caractères) - NLRTM = Rotterdam\n- `destination`: Code UN/LOCODE - CNSHA = Shanghai\n- `containerType`: 40HC (40ft High Cube)\n- `mode`: FCL (Full Container Load)\n- `departureDate`: Date de départ souhaitée\n- `quantity`: Nombre de conteneurs\n- `weight`: Poids total en kg\n\n**Cache:** Résultats mis en cache pendant 15 minutes"
|
|
},
|
|
"response": []
|
|
},
|
|
{
|
|
"name": "Search Rates - Hamburg to Los Angeles",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"exec": [
|
|
"pm.test(\"Status code is 200\", function () {",
|
|
" pm.response.to.have.status(200);",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Count matches quotes array length\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData.count).to.equal(jsonData.quotes.length);",
|
|
"});"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"origin\": \"DEHAM\",\n \"destination\": \"USLAX\",\n \"containerType\": \"40DRY\",\n \"mode\": \"FCL\",\n \"departureDate\": \"2025-03-01\",\n \"quantity\": 1,\n \"weight\": 15000,\n \"isHazmat\": false\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/rates/search",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"rates",
|
|
"search"
|
|
]
|
|
},
|
|
"description": "Recherche Hamburg → Los Angeles avec conteneur 40DRY"
|
|
},
|
|
"response": []
|
|
},
|
|
{
|
|
"name": "Search Rates - With Hazmat",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"origin\": \"NLRTM\",\n \"destination\": \"SGSIN\",\n \"containerType\": \"20DRY\",\n \"mode\": \"FCL\",\n \"departureDate\": \"2025-02-20\",\n \"quantity\": 1,\n \"weight\": 10000,\n \"isHazmat\": true,\n \"imoClass\": \"3\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/rates/search",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"rates",
|
|
"search"
|
|
]
|
|
},
|
|
"description": "Recherche avec marchandises dangereuses (Hazmat)\n\n**IMO Classes:**\n- 1: Explosifs\n- 2: Gaz\n- 3: Liquides inflammables\n- 4: Solides inflammables\n- 5: Substances comburantes\n- 6: Substances toxiques\n- 7: Matières radioactives\n- 8: Substances corrosives\n- 9: Matières dangereuses diverses"
|
|
},
|
|
"response": []
|
|
},
|
|
{
|
|
"name": "Search Rates - Invalid Port Code (Error)",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"exec": [
|
|
"pm.test(\"Status code is 400 (Validation Error)\", function () {",
|
|
" pm.response.to.have.status(400);",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Error message mentions validation\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData).to.have.property('message');",
|
|
"});"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"origin\": \"INVALID\",\n \"destination\": \"CNSHA\",\n \"containerType\": \"40HC\",\n \"mode\": \"FCL\",\n \"departureDate\": \"2025-02-15\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/rates/search",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"rates",
|
|
"search"
|
|
]
|
|
},
|
|
"description": "Test de validation : code port invalide\n\nDevrait retourner une erreur 400 avec message de validation"
|
|
},
|
|
"response": []
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Bookings API",
|
|
"description": "Gestion complète des réservations : création, consultation, listing",
|
|
"item": [
|
|
{
|
|
"name": "Create Booking",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"exec": [
|
|
"pm.test(\"Status code is 201 (Created)\", function () {",
|
|
" pm.response.to.have.status(201);",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Response has booking ID\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData).to.have.property('id');",
|
|
" pm.expect(jsonData).to.have.property('bookingNumber');",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Booking number has correct format\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData.bookingNumber).to.match(/^WCM-\\d{4}-[A-Z0-9]{6}$/);",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Initial status is draft\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData.status).to.equal('draft');",
|
|
"});",
|
|
"",
|
|
"// Save booking ID and number for later tests",
|
|
"pm.environment.set(\"bookingId\", pm.response.json().id);",
|
|
"pm.environment.set(\"bookingNumber\", pm.response.json().bookingNumber);",
|
|
"console.log(\"Saved bookingId: \" + pm.response.json().id);",
|
|
"console.log(\"Saved bookingNumber: \" + pm.response.json().bookingNumber);"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
},
|
|
{
|
|
"listen": "prerequest",
|
|
"script": {
|
|
"exec": [
|
|
"// Ensure we have a rateQuoteId from previous search",
|
|
"if (!pm.environment.get(\"rateQuoteId\")) {",
|
|
" console.warn(\"No rateQuoteId found. Run 'Search Rates' first!\");",
|
|
"}"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"rateQuoteId\": \"{{rateQuoteId}}\",\n \"shipper\": {\n \"name\": \"Acme Corporation\",\n \"address\": {\n \"street\": \"123 Main Street\",\n \"city\": \"Rotterdam\",\n \"postalCode\": \"3000 AB\",\n \"country\": \"NL\"\n },\n \"contactName\": \"John Doe\",\n \"contactEmail\": \"john.doe@acme.com\",\n \"contactPhone\": \"+31612345678\"\n },\n \"consignee\": {\n \"name\": \"Shanghai Imports Ltd\",\n \"address\": {\n \"street\": \"456 Trade Avenue\",\n \"city\": \"Shanghai\",\n \"postalCode\": \"200000\",\n \"country\": \"CN\"\n },\n \"contactName\": \"Jane Smith\",\n \"contactEmail\": \"jane.smith@shanghai-imports.cn\",\n \"contactPhone\": \"+8613812345678\"\n },\n \"cargoDescription\": \"Electronics and consumer goods for retail distribution\",\n \"containers\": [\n {\n \"type\": \"40HC\",\n \"containerNumber\": \"ABCU1234567\",\n \"vgm\": 22000,\n \"sealNumber\": \"SEAL123456\"\n }\n ],\n \"specialInstructions\": \"Please handle with care. Delivery before 5 PM.\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/bookings",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"bookings"
|
|
]
|
|
},
|
|
"description": "Créer une nouvelle réservation basée sur un tarif recherché\n\n**Note:** Exécutez d'abord une recherche de tarifs pour obtenir un `rateQuoteId` valide.\n\n**Validation:**\n- Email format E.164\n- Téléphone international format\n- Country code ISO 3166-1 alpha-2 (2 lettres)\n- Container number: 4 lettres + 7 chiffres\n- Cargo description: min 10 caractères\n\n**Statuts possibles:**\n- `draft`: Initial (modifiable)\n- `pending_confirmation`: Soumis au transporteur\n- `confirmed`: Confirmé\n- `in_transit`: En transit\n- `delivered`: Livré (final)\n- `cancelled`: Annulé (final)"
|
|
},
|
|
"response": []
|
|
},
|
|
{
|
|
"name": "Get Booking by ID",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"exec": [
|
|
"pm.test(\"Status code is 200\", function () {",
|
|
" pm.response.to.have.status(200);",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Response has complete booking details\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData).to.have.property('id');",
|
|
" pm.expect(jsonData).to.have.property('bookingNumber');",
|
|
" pm.expect(jsonData).to.have.property('shipper');",
|
|
" pm.expect(jsonData).to.have.property('consignee');",
|
|
" pm.expect(jsonData).to.have.property('rateQuote');",
|
|
"});"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/bookings/{{bookingId}}",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"bookings",
|
|
"{{bookingId}}"
|
|
]
|
|
},
|
|
"description": "Récupérer les détails complets d'une réservation par son ID UUID\n\n**Note:** Créez d'abord une réservation pour obtenir un `bookingId` valide."
|
|
},
|
|
"response": []
|
|
},
|
|
{
|
|
"name": "Get Booking by Booking Number",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"exec": [
|
|
"pm.test(\"Status code is 200\", function () {",
|
|
" pm.response.to.have.status(200);",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Booking number matches request\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData.bookingNumber).to.equal(pm.environment.get(\"bookingNumber\"));",
|
|
"});"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/bookings/number/{{bookingNumber}}",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"bookings",
|
|
"number",
|
|
"{{bookingNumber}}"
|
|
]
|
|
},
|
|
"description": "Récupérer une réservation par son numéro (format: WCM-2025-ABC123)\n\n**Avantage:** Format plus convivial que l'UUID pour les utilisateurs"
|
|
},
|
|
"response": []
|
|
},
|
|
{
|
|
"name": "List Bookings (Paginated)",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"exec": [
|
|
"pm.test(\"Status code is 200\", function () {",
|
|
" pm.response.to.have.status(200);",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Response has pagination metadata\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData).to.have.property('bookings');",
|
|
" pm.expect(jsonData).to.have.property('total');",
|
|
" pm.expect(jsonData).to.have.property('page');",
|
|
" pm.expect(jsonData).to.have.property('pageSize');",
|
|
" pm.expect(jsonData).to.have.property('totalPages');",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Bookings is an array\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData.bookings).to.be.an('array');",
|
|
"});"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/bookings?page=1&pageSize=20",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"bookings"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "page",
|
|
"value": "1",
|
|
"description": "Numéro de page (commence à 1)"
|
|
},
|
|
{
|
|
"key": "pageSize",
|
|
"value": "20",
|
|
"description": "Nombre d'éléments par page (max: 100)"
|
|
},
|
|
{
|
|
"key": "status",
|
|
"value": "draft",
|
|
"description": "Filtrer par statut (optionnel)",
|
|
"disabled": true
|
|
}
|
|
]
|
|
},
|
|
"description": "Lister toutes les réservations avec pagination\n\n**Paramètres de requête:**\n- `page`: Numéro de page (défaut: 1)\n- `pageSize`: Éléments par page (défaut: 20, max: 100)\n- `status`: Filtrer par statut (optionnel)\n\n**Statuts disponibles:**\n- draft\n- pending_confirmation\n- confirmed\n- in_transit\n- delivered\n- cancelled"
|
|
},
|
|
"response": []
|
|
},
|
|
{
|
|
"name": "List Bookings - Filter by Status (Draft)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/bookings?page=1&pageSize=10&status=draft",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"bookings"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "page",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "pageSize",
|
|
"value": "10"
|
|
},
|
|
{
|
|
"key": "status",
|
|
"value": "draft"
|
|
}
|
|
]
|
|
},
|
|
"description": "Lister uniquement les réservations en statut 'draft'"
|
|
},
|
|
"response": []
|
|
},
|
|
{
|
|
"name": "Create Booking - Validation Error",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"exec": [
|
|
"pm.test(\"Status code is 400 (Validation Error)\", function () {",
|
|
" pm.response.to.have.status(400);",
|
|
"});",
|
|
"",
|
|
"pm.test(\"Error contains validation messages\", function () {",
|
|
" var jsonData = pm.response.json();",
|
|
" pm.expect(jsonData).to.have.property('message');",
|
|
" pm.expect(jsonData.message).to.be.an('array');",
|
|
"});"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"rateQuoteId\": \"invalid-uuid\",\n \"shipper\": {\n \"name\": \"A\",\n \"address\": {\n \"street\": \"123\",\n \"city\": \"R\",\n \"postalCode\": \"3000\",\n \"country\": \"INVALID\"\n },\n \"contactName\": \"J\",\n \"contactEmail\": \"invalid-email\",\n \"contactPhone\": \"123\"\n },\n \"consignee\": {\n \"name\": \"Test\",\n \"address\": {\n \"street\": \"123 Street\",\n \"city\": \"City\",\n \"postalCode\": \"12345\",\n \"country\": \"CN\"\n },\n \"contactName\": \"Contact\",\n \"contactEmail\": \"contact@test.com\",\n \"contactPhone\": \"+8612345678\"\n },\n \"cargoDescription\": \"Short\",\n \"containers\": []\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/api/v1/bookings",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"api",
|
|
"v1",
|
|
"bookings"
|
|
]
|
|
},
|
|
"description": "Test de validation : données invalides\n\n**Erreurs attendues:**\n- UUID invalide\n- Nom trop court\n- Email invalide\n- Téléphone invalide\n- Code pays invalide\n- Description cargo trop courte"
|
|
},
|
|
"response": []
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Health & Status",
|
|
"description": "Endpoints de santé et statut du système (à implémenter)",
|
|
"item": [
|
|
{
|
|
"name": "Health Check",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/health",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"health"
|
|
]
|
|
},
|
|
"description": "Vérifier l'état de santé de l'API\n\n**Status:** À implémenter en Phase 2"
|
|
},
|
|
"response": []
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"event": [
|
|
{
|
|
"listen": "prerequest",
|
|
"script": {
|
|
"type": "text/javascript",
|
|
"exec": [
|
|
""
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"type": "text/javascript",
|
|
"exec": [
|
|
""
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"variable": [
|
|
{
|
|
"key": "baseUrl",
|
|
"value": "http://localhost:4000",
|
|
"type": "string"
|
|
},
|
|
{
|
|
"key": "rateQuoteId",
|
|
"value": "",
|
|
"type": "string"
|
|
},
|
|
{
|
|
"key": "bookingId",
|
|
"value": "",
|
|
"type": "string"
|
|
},
|
|
{
|
|
"key": "bookingNumber",
|
|
"value": "",
|
|
"type": "string"
|
|
}
|
|
]
|
|
}
|