xpeditis2.0/postman/Xpeditis_API.postman_collection.json
David-Henri ARNAUD dc1c881842 feature phase 2
2025-10-09 15:03:53 +02:00

504 lines
18 KiB
JSON

{
"info": {
"_postman_id": "xpeditis-api-collection-v2",
"name": "Xpeditis API - Maritime Freight Booking (Phase 2)",
"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- 🔐 Authentication JWT (register, login, refresh token)\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 2 - Authentication & User Management\n\n**Important:** \n1. Commencez par créer un compte (POST /auth/register)\n2. Ensuite connectez-vous (POST /auth/login) pour obtenir un token JWT\n3. Le token sera automatiquement ajouté aux autres requêtes\n4. Le token expire après 15 minutes (utilisez /auth/refresh pour en obtenir un nouveau)",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{accessToken}}",
"type": "string"
}
]
},
"item": [
{
"name": "Authentication",
"description": "Endpoints d'authentification JWT : register, login, refresh token, logout, profil utilisateur",
"item": [
{
"name": "Register New User",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 201 (Created)\", function () {",
" pm.response.to.have.status(201);",
"});",
"",
"pm.test(\"Response has access and refresh tokens\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('accessToken');",
" pm.expect(jsonData).to.have.property('refreshToken');",
" pm.expect(jsonData).to.have.property('user');",
"});",
"",
"pm.test(\"User object has correct properties\", function () {",
" var user = pm.response.json().user;",
" pm.expect(user).to.have.property('id');",
" pm.expect(user).to.have.property('email');",
" pm.expect(user).to.have.property('role');",
" pm.expect(user).to.have.property('organizationId');",
"});",
"",
"// Save tokens for subsequent requests",
"if (pm.response.code === 201) {",
" var jsonData = pm.response.json();",
" pm.environment.set(\"accessToken\", jsonData.accessToken);",
" pm.environment.set(\"refreshToken\", jsonData.refreshToken);",
" pm.environment.set(\"userId\", jsonData.user.id);",
" pm.environment.set(\"userEmail\", jsonData.user.email);",
" console.log(\"✅ Registration successful! Tokens saved.\");",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"email\": \"john.doe@acme.com\",\n \"password\": \"SecurePassword123!\",\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"organizationId\": \"550e8400-e29b-41d4-a716-446655440000\"\n}"
},
"url": {
"raw": "{{baseUrl}}/auth/register",
"host": ["{{baseUrl}}"],
"path": ["auth", "register"]
},
"description": "Créer un nouveau compte utilisateur\n\n**Validation:**\n- Email format valide\n- Password minimum 12 caractères\n- FirstName et LastName minimum 2 caractères\n- OrganizationId format UUID\n\n**Réponse:**\n- accessToken (expire après 15 min)\n- refreshToken (expire après 7 jours)\n- user object avec id, email, role, organizationId\n\n**Sécurité:**\n- Password hashé avec Argon2id (64MB memory, 3 iterations)\n- JWT signé avec HS256"
},
"response": []
},
{
"name": "Login",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response has tokens\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('accessToken');",
" pm.expect(jsonData).to.have.property('refreshToken');",
"});",
"",
"// Save tokens",
"if (pm.response.code === 200) {",
" var jsonData = pm.response.json();",
" pm.environment.set(\"accessToken\", jsonData.accessToken);",
" pm.environment.set(\"refreshToken\", jsonData.refreshToken);",
" pm.environment.set(\"userId\", jsonData.user.id);",
" console.log(\"✅ Login successful! Access token expires in 15 minutes.\");",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"email\": \"john.doe@acme.com\",\n \"password\": \"SecurePassword123!\"\n}"
},
"url": {
"raw": "{{baseUrl}}/auth/login",
"host": ["{{baseUrl}}"],
"path": ["auth", "login"]
},
"description": "Se connecter avec email et password\n\n**Réponse:**\n- accessToken (15 min)\n- refreshToken (7 jours)\n- user info\n\n**Erreurs possibles:**\n- 401: Email ou password incorrect\n- 401: Compte inactif"
},
"response": []
},
{
"name": "Refresh Access Token",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response has new access token\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('accessToken');",
"});",
"",
"// Update access token",
"if (pm.response.code === 200) {",
" pm.environment.set(\"accessToken\", pm.response.json().accessToken);",
" console.log(\"✅ Access token refreshed successfully!\");",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}"
},
"url": {
"raw": "{{baseUrl}}/auth/refresh",
"host": ["{{baseUrl}}"],
"path": ["auth", "refresh"]
},
"description": "Obtenir un nouveau access token avec le refresh token\n\n**Cas d'usage:**\n- Access token expiré (après 15 min)\n- Refresh token valide (< 7 jours)\n\n**Réponse:**\n- Nouveau accessToken valide pour 15 min\n\n**Note:** Le refresh token reste inchangé"
},
"response": []
},
{
"name": "Get Current User Profile",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response has user profile\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('id');",
" pm.expect(jsonData).to.have.property('email');",
" pm.expect(jsonData).to.have.property('role');",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/auth/me",
"host": ["{{baseUrl}}"],
"path": ["auth", "me"]
},
"description": "Récupérer le profil de l'utilisateur connecté\n\n**Authentification:** Requiert un access token valide\n\n**Réponse:**\n- id (UUID)\n- email\n- firstName\n- lastName\n- role (admin, manager, user, viewer)\n- organizationId"
},
"response": []
},
{
"name": "Logout",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"// Clear tokens from environment",
"pm.environment.unset(\"accessToken\");",
"pm.environment.unset(\"refreshToken\");",
"console.log(\"✅ Logged out successfully. Tokens cleared.\");"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"url": {
"raw": "{{baseUrl}}/auth/logout",
"host": ["{{baseUrl}}"],
"path": ["auth", "logout"]
},
"description": "Déconnecter l'utilisateur\n\n**Note:** Avec JWT, la déconnexion est principalement gérée côté client en supprimant les tokens. Pour plus de sécurité, une blacklist Redis peut être implémentée."
},
"response": []
}
]
},
{
"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": "🔐 **Authentification requise**\n\nRecherche 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": "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}$/);",
"});",
"",
"// Save booking ID and number",
"pm.environment.set(\"bookingId\", pm.response.json().id);",
"pm.environment.set(\"bookingNumber\", pm.response.json().bookingNumber);",
"console.log(\"Saved bookingId: \" + pm.response.json().id);"
],
"type": "text/javascript"
}
},
{
"listen": "prerequest",
"script": {
"exec": [
"// Ensure we have a rateQuoteId",
"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": "🔐 **Authentification requise**\n\nCréer une nouvelle réservation basée sur un tarif recherché\n\n**Note:** La réservation sera automatiquement liée à l'utilisateur et l'organisation connectés."
},
"response": []
},
{
"name": "Get Booking by ID",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/v1/bookings/{{bookingId}}",
"host": ["{{baseUrl}}"],
"path": ["api", "v1", "bookings", "{{bookingId}}"]
},
"description": "🔐 **Authentification requise**\n\nRécupérer les détails d'une réservation par ID\n\n**Sécurité:** Seules les réservations de votre organisation sont accessibles"
},
"response": []
},
{
"name": "List Bookings (Paginated)",
"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"
},
{
"key": "pageSize",
"value": "20"
},
{
"key": "status",
"value": "draft",
"disabled": true
}
]
},
"description": "🔐 **Authentification requise**\n\nLister toutes les réservations de votre organisation\n\n**Filtrage automatique:** Seules les réservations de votre organisation sont affichées"
},
"response": []
}
]
}
],
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
"// Check if access token exists and warn if missing (except for auth endpoints)",
"const url = pm.request.url.toString();",
"const isAuthEndpoint = url.includes('/auth/');",
"",
"if (!isAuthEndpoint && !pm.environment.get('accessToken')) {",
" console.warn('⚠️ No access token found. Please login first!');",
"}"
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"// Global test: check for 401 and suggest refresh",
"if (pm.response.code === 401) {",
" console.error('❌ Unauthorized (401). Your token may have expired.');",
" console.log('💡 Try refreshing your access token with POST /auth/refresh');",
"}"
]
}
}
],
"variable": [
{
"key": "baseUrl",
"value": "http://localhost:4000",
"type": "string"
},
{
"key": "accessToken",
"value": "",
"type": "string"
},
{
"key": "refreshToken",
"value": "",
"type": "string"
},
{
"key": "userId",
"value": "",
"type": "string"
},
{
"key": "userEmail",
"value": "",
"type": "string"
},
{
"key": "rateQuoteId",
"value": "",
"type": "string"
},
{
"key": "bookingId",
"value": "",
"type": "string"
},
{
"key": "bookingNumber",
"value": "",
"type": "string"
}
]
}