{ "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" } ] }