diff --git a/Docktor.postman_collection.json b/Docktor.postman_collection.json new file mode 100644 index 00000000..0575e844 --- /dev/null +++ b/Docktor.postman_collection.json @@ -0,0 +1,513 @@ +{ + "info": { + "name": "Docktor", + "_postman_id": "38808964-571e-1153-9ed0-5b6b394fb1ab", + "description": "This collection will test the CRUD of catologServices from Docktor", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "catalogServices", + "description": "", + "item": [ + { + "name": "GET all catalogServices", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");", + " ", + "tests[\"Has Content-Type\"] = contentTypeHeaderExists;", + " ", + "if (contentTypeHeaderExists) {", + " tests[\"Content-Type is application/json\"] = ", + " responseHeaders[\"Content-Type\"].has(\"application/json\");", + "}", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Response time is acceptable\"] = responseTime < 200; // milliseconds", + "", + "tests[\"ResponseBody contains version field \"] =responseBody.has(\"versions\");", + "tests[\"ResponseBody contains tags field \"] = responseBody.has(\"tags\");", + "tests[\"ResponseBody contains created field \"] = responseBody.has(\"created\");", + "tests[\"ResponseBody contains updated field \"] = responseBody.has(\"updated\");", + "", + "// Load the JSON Schema", + "const catalogServiceSchema = JSON.parse(environment.catalogServiceSchema);", + "", + "// Test whether the response matches the schema ", + "tests[\"CatalogService is valid\"] = tv4.validate(data, catalogServiceSchema);" + ] + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFmb3VjYXVsdCIsImV4cCI6MTUwODc2MDQ5MiwiaXNzIjoiZG9ja3RvciJ9.Lrikl1hm323SUgqz3u9Z9XRVcc9MrXn7t7cUVWwWjvI" + }, + { + "key": "", + "value": "", + "disabled": true + }, + { + "key": "", + "value": "", + "disabled": true + } + ], + "body": {}, + "url": { + "raw": "{{url}}/api/catalogServices", + "host": [ + "{{url}}" + ], + "path": [ + "api", + "catalogServices" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "GET catalogService by ID", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");", + " ", + "tests[\"Has Content-Type\"] = contentTypeHeaderExists;", + " ", + "if (contentTypeHeaderExists) {", + " tests[\"Content-Type is application/json\"] = ", + " responseHeaders[\"Content-Type\"].has(\"application/json\");", + "}", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Response time is acceptable\"] = responseTime < 200; // milliseconds", + "", + "tests[\"Catalog 1 contains version field \"] =responseBody.has(\"versions\");", + "tests[\"Catalog 1 contains tags field \"] = responseBody.has(\"tags\");", + "tests[\"Catalog 1 contains created field \"] = responseBody.has(\"created\");", + "tests[\"Catalog 1 contains updated field \"] = responseBody.has(\"updated\");", + "", + "// Load the JSON Schema", + "const catalogServiceSchema = JSON.parse(environment.catalogServiceSchema);", + "", + "// Test whether the response matches the schema ", + "tests[\"CatalogService is valid\"] = tv4.validate(responseBody, catalogServiceSchema);" + ] + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFmb3VjYXVsdCIsImV4cCI6MTUwODc2MDQ5MiwiaXNzIjoiZG9ja3RvciJ9.Lrikl1hm323SUgqz3u9Z9XRVcc9MrXn7t7cUVWwWjvI" + }, + { + "key": "", + "value": "", + "disabled": true + } + ], + "body": {}, + "url": { + "raw": "{{url}}/api/catalogServices/559fba2391b933ac02dec738", + "host": [ + "{{url}}" + ], + "path": [ + "api", + "catalogServices", + "559fba2391b933ac02dec738" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "GET catalogService's tag by ID", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");", + " ", + "tests[\"Has Content-Type\"] = contentTypeHeaderExists;", + " ", + "if (contentTypeHeaderExists) {", + " tests[\"Content-Type is application/json\"] = ", + " responseHeaders[\"Content-Type\"].has(\"application/json\");", + "}", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Response time is acceptable\"] = responseTime < 200; // milliseconds" + ] + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFmb3VjYXVsdCIsImV4cCI6MTUwODc2MDQ5MiwiaXNzIjoiZG9ja3RvciJ9.Lrikl1hm323SUgqz3u9Z9XRVcc9MrXn7t7cUVWwWjvI" + }, + { + "key": "", + "value": "", + "disabled": true + } + ], + "body": {}, + "url": { + "raw": "{{url}}/api/catalogServices/559fba2391b933ac02dec738/tags", + "host": [ + "{{url}}" + ], + "path": [ + "api", + "catalogServices", + "559fba2391b933ac02dec738", + "tags" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "POST new catalogService", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");", + " ", + "tests[\"Has Content-Type\"] = contentTypeHeaderExists;", + " ", + "if (contentTypeHeaderExists) {", + " tests[\"Content-Type is application/json\"] = ", + " responseHeaders[\"Content-Type\"].has(\"application/json\");", + "}", + "", + "tests[\"Successful POST request\"] = responseCode.code === 201 || responseCode.code === 202;", + "tests[\"Response time is acceptable\"] = responseTime < 200; // milliseconds", + "tests[\"Content-Type header is set\"] = postman.getResponseHeader(\"Content-Type\");", + "", + "tests[\"ResponseBody contains version field \"] =responseBody.has(\"versions\");", + "tests[\"ResponseBody contains tags field \"] = responseBody.has(\"tags\");", + "tests[\"ResponseBody contains created field \"] = responseBody.has(\"created\");", + "tests[\"ResponseBody contains updated field \"] = responseBody.has(\"updated\");", + "", + "// Load the JSON Schema", + "const catalogServiceSchema = JSON.parse(environment.catalogServiceSchema);", + "", + "// Test whether the response matches the schema ", + "tests[\"CatalogService is valid\"] = tv4.validate(responseBody, catalogServiceSchema);", + "", + "// Extract this newly created catalogService and store it:", + "var catalogServiceLink = postman.getResponseHeader(\"location\");", + "postman.setEnvironmentVariable(\"catalogServiceLink\", catalogServiceLink);", + "", + "// Store the body of this new catalog service", + "var catalogServiceBody = responseBody;", + "postman.setEnvironmentVariable(\"catalogServiceBody\", catalogServiceBody);", + "", + "" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFmb3VjYXVsdCIsImV4cCI6MTUwODc2MDQ5MiwiaXNzIjoiZG9ja3RvciJ9.Lrikl1hm323SUgqz3u9Z9XRVcc9MrXn7t7cUVWwWjvI" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "", + "value": "", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": " {\n \"id\": \"\",\n \"name\": \"TEST-POST\",\n \"versions\": {},\n \"tags\": [],\n \"created\": \"\",\n \"updated\": \"\"\n }" + }, + "url": { + "raw": "{{url}}/api/catalogServices/new", + "host": [ + "{{url}}" + ], + "path": [ + "api", + "catalogServices", + "new" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "Validate that the post was created", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Response time is acceptable\"] = responseTime < 200; // milliseconds", + "", + "tests[\"ResponseBody contains version field \"] =responseBody.has(\"versions\");", + "tests[\"ResponseBody contains tags field \"] = responseBody.has(\"tags\");", + "tests[\"ResponseBody contains created field \"] = responseBody.has(\"created\");", + "tests[\"ResponseBody contains updated field \"] = responseBody.has(\"updated\");", + "", + "// Load the JSON Schema", + "const catalogServiceSchema = JSON.parse(environment.catalogServiceSchema);", + "", + "// Test whether the response matches the schema ", + "tests[\"CatalogService is valid\"] = tv4.validate(data, catalogServiceSchema);", + "", + "tests[\"Has correct catalog service posted\"] = responseBody === environment.catalogServiceBody;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFmb3VjYXVsdCIsImV4cCI6MTUwODc2MDQ5MiwiaXNzIjoiZG9ja3RvciJ9.Lrikl1hm323SUgqz3u9Z9XRVcc9MrXn7t7cUVWwWjvI" + } + ], + "body": {}, + "url": { + "raw": "{{catalogServiceLink}}", + "host": [ + "{{catalogServiceLink}}" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "PUT catalogService just created", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");", + " ", + "tests[\"Has Content-Type\"] = contentTypeHeaderExists;", + " ", + "if (contentTypeHeaderExists) {", + " tests[\"Content-Type is application/json\"] = ", + " responseHeaders[\"Content-Type\"].has(\"application/json\");", + "}", + "", + "tests[\"Successful PUT request\"] = responseCode.code === 200 || responseCode.code === 204;", + "tests[\"Response time is acceptable\"] = responseTime < 200; // milliseconds", + "tests[\"Content-Type header is set\"] = postman.getResponseHeader(\"Content-Type\");", + "", + "tests[\"ResponseBody contains version field \"] =responseBody.has(\"versions\");", + "tests[\"ResponseBody contains tags field \"] = responseBody.has(\"tags\");", + "tests[\"ResponseBody contains created field \"] = responseBody.has(\"created\");", + "tests[\"ResponseBody contains updated field \"] = responseBody.has(\"updated\");", + "", + "// Load the JSON Schema", + "const catalogServiceSchema = JSON.parse(environment.catalogServiceSchema);", + "", + "// Test whether the response matches the schema ", + "tests[\"CatalogService is valid\"] = tv4.validate(responseBody, catalogServiceSchema);", + "", + "// Store the body of this updated catalog service", + "var catalogServiceBody = responseBody;", + "postman.setEnvironmentVariable(\"catalogServiceBody\", catalogServiceBody);", + "" + ] + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFmb3VjYXVsdCIsImV4cCI6MTUwODc2MDQ5MiwiaXNzIjoiZG9ja3RvciJ9.Lrikl1hm323SUgqz3u9Z9XRVcc9MrXn7t7cUVWwWjvI" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "", + "value": "", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\t \n\t \"id\": \"\",\n \"name\": \"TEST_POST_UPDATED\",\n \"versions\": null,\n \"tags\": null,\n \"created\": null,\n \"updated\": null\n}\n " + }, + "url": { + "raw": "{{catalogServiceLink}}", + "host": [ + "{{catalogServiceLink}}" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": " Verify that the catalogService have been updated", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Response time is acceptable\"] = responseTime < 200; // milliseconds", + "", + "tests[\"Has correct updated text\"] = responseBody === environment.catalogServiceBody" + ] + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFmb3VjYXVsdCIsImV4cCI6MTUwODc2MDQ5MiwiaXNzIjoiZG9ja3RvciJ9.Lrikl1hm323SUgqz3u9Z9XRVcc9MrXn7t7cUVWwWjvI" + } + ], + "body": {}, + "url": { + "raw": "{{catalogServiceLink}}", + "host": [ + "{{catalogServiceLink}}" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "DELET catalogService by ID", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");", + " ", + "tests[\"Has Content-Type\"] = contentTypeHeaderExists;", + " ", + "if (contentTypeHeaderExists) {", + " tests[\"Content-Type is application/json\"] = ", + " responseHeaders[\"Content-Type\"].has(\"application/json\");", + "}", + "", + "tests[\"Successful DELETE request\"] = responseCode.code === 200 || responseCode.code === 204 || responseCode.code === 202;", + "tests[\"Response time is acceptable\"] = responseTime < 200; // milliseconds" + ] + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFmb3VjYXVsdCIsImV4cCI6MTUwODc2MDQ5MiwiaXNzIjoiZG9ja3RvciJ9.Lrikl1hm323SUgqz3u9Z9XRVcc9MrXn7t7cUVWwWjvI" + }, + { + "key": "", + "value": "", + "disabled": true + } + ], + "body": {}, + "url": { + "raw": "{{catalogServiceLink}}", + "host": [ + "{{catalogServiceLink}}" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "Verify that the catalogService post has been deleted", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"Status code is 404\"] = responseCode.code === 404;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFmb3VjYXVsdCIsImV4cCI6MTUwODc2MDQ5MiwiaXNzIjoiZG9ja3RvciJ9.Lrikl1hm323SUgqz3u9Z9XRVcc9MrXn7t7cUVWwWjvI" + } + ], + "body": {}, + "url": { + "raw": "{{catalogServiceLink}}", + "host": [ + "{{catalogServiceLink}}" + ] + }, + "description": "" + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/Postman Echo.postman_collection.json b/Postman Echo.postman_collection.json new file mode 100644 index 00000000..9379cb42 --- /dev/null +++ b/Postman Echo.postman_collection.json @@ -0,0 +1,4079 @@ +{ + "info": { + "name": "Postman Echo", + "_postman_id": "f695cab7-6878-eb55-7943-ad88e1ccfd65", + "description": "Postman Echo is service you can use to test your REST clients and make sample API calls. It provides endpoints for `GET`, `POST`, `PUT`, various auth mechanisms and other utility endpoints.\n\nThe documentation for the endpoints as well as example responses can be found at [https://postman-echo.com](https://postman-echo.com/?source=echo-collection-app-onboarding)", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Auth: Digest", + "description": "Digest authentication protects an endpoint with a username and password without actually transmitting the password over network.\nOne has to apply a hash function (like MD5, etc) to the username and password before sending them over the network.\n\n> Username: `postman`\n>\n> Password: `password`\n\nUnlike Basic-Auth, authentication happens using two consecutive requests where the first request returns `401 Unauthorised` along with `WWW-Authenticate` header containing information that needs to be used to authenticate subsequent calls.\n\nTo know more about digest authentication, refer to the [Digest Access Authentication](https://en.wikipedia.org/wiki/Digest_access_authentication) wikipedia article.\nThe article on [authentication helpers](https://www.getpostman.com/docs/helpers#digest-auth) elaborates how to use the same within the Postman app.", + "item": [ + { + "name": "DigestAuth Request", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"response code is 401\"] = responseCode.code === 401;", + "tests[\"response has WWW-Authenticate header\"] = (postman.getResponseHeader('WWW-Authenticate'));", + "", + "var authenticateHeader = postman.getResponseHeader('WWW-Authenticate'),", + " realmStart = authenticateHeader.indexOf('\"',authenticateHeader.indexOf(\"realm\")) + 1 ,", + " realmEnd = authenticateHeader.indexOf('\"',realmStart),", + " realm = authenticateHeader.slice(realmStart,realmEnd),", + " nonceStart = authenticateHeader.indexOf('\"',authenticateHeader.indexOf(\"nonce\")) + 1,", + " nonceEnd = authenticateHeader.indexOf('\"',nonceStart),", + " nonce = authenticateHeader.slice(nonceStart,nonceEnd);", + " ", + "postman.setGlobalVariable('echo_digest_realm', realm);", + "postman.setGlobalVariable('echo_digest_nonce', nonce);" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "code", + "value": "xWnkliVQJURqB2x1", + "type": "text" + }, + { + "key": "grant_type", + "value": "authorization_code", + "type": "text" + }, + { + "key": "redirect_uri", + "value": "https://www.getpostman.com/oauth2/callback", + "type": "text" + }, + { + "key": "client_id", + "value": "abc123", + "type": "text" + }, + { + "key": "client_secret", + "value": "ssh-secret", + "type": "text" + } + ] + }, + "url": { + "raw": "https://postman-echo.com/digest-auth", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "digest-auth" + ] + }, + "description": "Performing a simple `GET` request to this endpoint returns status code `401 Unauthorized` with `WWW-Authenticate` header containing information to successfully authenticate subsequent requests.\nThe `WWW-Authenticate` header must be processed to extract `realm` and `nonce` values to hash subsequent requests.\n\nWhen this request is executed within Postman, the script attached with this request does the hard work of extracting realm and nonce from the header and set it as [global variables](https://www.getpostman.com/docs/environments#global-variables?source=echo-collection-app-onboarding) named `echo_digest_nonce` and `echo_digest_realm`.\nThese variables are re-used in subsequent request for seamless integration of the two requests." + }, + "response": [] + }, + { + "name": "DigestAuth Success", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"response code is 200\"] = responseCode.code === 200;", + "tests[\"body contains authenticated\"] = responseBody.has(\"authenticated\");" + ] + } + } + ], + "request": { + "auth": { + "type": "digest", + "digest": [ + { + "key": "algorithm", + "value": "MD5", + "type": "string" + }, + { + "key": "username", + "value": "postman", + "type": "string" + }, + { + "key": "realm", + "value": "{{echo_digest_realm}}", + "type": "string" + }, + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "nonce", + "value": "{{echo_digest_nonce}}", + "type": "string" + }, + { + "key": "nonceCount", + "value": "", + "type": "string" + }, + { + "key": "clientNonce", + "value": "", + "type": "string" + }, + { + "key": "opaque", + "value": "", + "type": "string" + }, + { + "key": "qop", + "value": "", + "type": "string" + }, + { + "key": "disableRetryRequest", + "type": "any" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Digest username=\"postman\", realm=\"Users\", nonce=\"ni1LiL0O37PRRhofWdCLmwFsnEtH1lew\", uri=\"/digest-auth\", response=\"254679099562cf07df9b6f5d8d15db44\", opaque=\"\"" + } + ], + "body": {}, + "url": { + "raw": "https://postman-echo.com/digest-auth", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "digest-auth" + ] + }, + "description": "This endpoint sends a hashed Digest Authorization header to gain access to a valid `200 Ok` response code. In Postman, it uses the stored [global variables](https://www.getpostman.com/docs/environments#gloval-variables?source=echo-collection-app-onboarding), `echo_digest_realm` and `echo_digest_nonce`, to generate the hashed authorisation header.\n\nWithin Postman, for this request to successfully authenticate, running the previous request \"DigestAuth Request\" stores the relevant information within the global variables." + }, + "response": [ + { + "id": "08fe8cd3-660d-4795-b4e0-d01088562d81", + "name": "200", + "originalRequest": { + "header": [], + "body": { + "mode": "raw", + "raw": "" + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Encoding", + "key": "Content-Encoding", + "value": "gzip", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "42", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 29 Oct 2015 06:17:51 GMT", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.6.2", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + }, + { + "name": "X-Powered-By", + "key": "X-Powered-By", + "value": "Sails ", + "description": "" + } + ], + "cookie": [], + "responseTime": "9843", + "body": "{\"authenticated\":true}" + } + ] + } + ] + }, + { + "name": "Auth: Others", + "description": "", + "item": [ + { + "name": "Basic Auth", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"response code is 200\"] = responseCode.code === 200;", + "tests[\"Body contains authenticated\"] = responseBody.has(\"authenticated\");" + ] + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "username", + "value": "postman", + "type": "string" + }, + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "saveHelperData", + "value": true, + "type": "boolean" + }, + { + "key": "showPassword", + "value": false, + "type": "boolean" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Basic cG9zdG1hbjpwYXNzd29yZA==" + } + ], + "body": {}, + "url": { + "raw": "https://postman-echo.com/basic-auth", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "basic-auth" + ] + }, + "description": "This endpoint simulates a **basic-auth** protected endpoint. \nThe endpoint accepts a default username and password and returns a status code of `200 ok` only if the same is provided. \nOtherwise it will return a status code `401 unauthorized`.\n\n> Username: `postman`\n> \n> Password: `password`\n\nTo use this endpoint, send a request with the header `Authorization: Basic cG9zdG1hbjpwYXNzd29yZA==`. \nThe cryptic latter half of the header value is a base64 encoded concatenation of the default username and password. \nUsing Postman, to send this request, you can simply fill in the username and password in the \"Authorization\" tab and Postman will do the rest for you.\n\nTo know more about basic authentication, refer to the [Basic Access Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) wikipedia article.\nThe article on [authentication helpers](https://www.getpostman.com/docs/helpers#basic-auth?source=echo-collection-app-onboarding) elaborates how to use the same within the Postman app." + }, + "response": [ + { + "id": "b55547ac-4767-4284-8141-f223c0d43592", + "name": "200", + "originalRequest": { + "header": [], + "body": { + "mode": "raw", + "raw": "" + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Encoding", + "key": "Content-Encoding", + "value": "gzip", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "42", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Sat, 31 Oct 2015 06:38:25 GMT", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.6.2", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + }, + { + "name": "X-Powered-By", + "key": "X-Powered-By", + "value": "Sails ", + "description": "" + } + ], + "cookie": [], + "responseTime": "377", + "body": "{\"authenticated\":true}" + } + ] + }, + { + "name": "OAuth1.0 Verify Signature", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"response code is 200\"] = responseCode.code === 200;", + "var body = JSON.parse(responseBody);", + "tests[\"Body contains status pass\"] = body[\"status\"] == \"pass\"" + ] + } + } + ], + "request": { + "auth": { + "type": "oauth1", + "oauth1": [ + { + "key": "consumerKey", + "value": "RKCGzna7bv9YD57c", + "type": "string" + }, + { + "key": "consumerSecret", + "value": "D+EdQ-gs$-%@2Nu7", + "type": "string" + }, + { + "key": "token", + "value": "", + "type": "string" + }, + { + "key": "tokenSecret", + "value": "", + "type": "string" + }, + { + "key": "signatureMethod", + "value": "HMAC-SHA1", + "type": "string" + }, + { + "key": "timestamp", + "value": 1472121255, + "type": "number" + }, + { + "key": "nonce", + "value": "e5VR16", + "type": "string" + }, + { + "key": "version", + "value": "1.0", + "type": "string" + }, + { + "key": "realm", + "value": "", + "type": "string" + }, + { + "key": "addParamsToHeader", + "value": true, + "type": "boolean" + }, + { + "key": "autoAddParam", + "type": "any" + }, + { + "key": "addEmptyParamsToSign", + "value": false, + "type": "boolean" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "code", + "value": "xWnkliVQJURqB2x1", + "type": "text" + }, + { + "key": "grant_type", + "value": "authorization_code", + "type": "text" + }, + { + "key": "redirect_uri", + "value": "https://www.getpostman.com/oauth2/callback", + "type": "text" + }, + { + "key": "client_id", + "value": "abc123", + "type": "text" + }, + { + "key": "client_secret", + "value": "ssh-secret", + "type": "text" + } + ] + }, + "url": { + "raw": "https://postman-echo.com/oauth1", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "oauth1" + ] + }, + "description": "OAuth1.0a is a specification that defines a protocol that can be used by one\nservice to access \"protected\" resources (endpoints) on another service. A\nmajor part of OAuth1.0 is HTTP Request Signing. This endpoint allows you to \ncheck whether the request calculation works properly in the client. \n\nThe endpoint supports the HTTP ``Authorization`` header. In case the signature\nverification fails, the endpoint provides the four debug values,\n\n* ``base_uri``\n* ``normalized_param_string``\n* ``base_string``\n* ``signing_key``\n\nFor more details about these parameters, check the [OAuth1.0a Specification](http://oauth.net/core/1.0a/)\n\nIn order to use this endpoint, you can set the following values:\n\n> Consumer Key: ``RKCGzna7bv9YD57c``\n>\n> Consumer Secret: ``D+EdQ-gs$-%@2Nu7``\n\nIf you are using Postman, also check the \"Add params to header\" and \n\"Auto add parameters\" boxes." + }, + "response": [ + { + "id": "32c7b324-c173-465a-9ea3-acceef75d266", + "name": "200", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "name": "Authorization", + "value": "OAuth oauth_consumer_key=\"RKCGzna7bv9YD57c\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1472121261\",oauth_nonce=\"ki0RQW\",oauth_version=\"1.0\",oauth_signature=\"s0rK92Myxx7ceUBVzlMaxiiXU00%3D\"" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "code", + "value": "xWnkliVQJURqB2x1", + "type": "text" + }, + { + "key": "grant_type", + "value": "authorization_code", + "type": "text" + }, + { + "key": "redirect_uri", + "value": "https://www.getpostman.com/oauth2/callback", + "type": "text" + }, + { + "key": "client_id", + "value": "abc123", + "type": "text" + }, + { + "key": "client_secret", + "value": "ssh-secret", + "type": "text" + } + ] + }, + "url": { + "raw": "https://echo.getpostman.com/oauth1", + "protocol": "https", + "host": [ + "echo", + "getpostman", + "com" + ], + "path": [ + "oauth1" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Expose-Headers", + "key": "Access-Control-Expose-Headers", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Encoding", + "key": "Content-Encoding", + "value": "gzip", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "95", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 25 Aug 2016 10:34:23 GMT", + "description": "" + }, + { + "name": "ETag", + "key": "ETag", + "value": "W/\"4e-Cq3UhvpVSyl6R6204lPVIA\"", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.8.1", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + } + ], + "cookie": [], + "responseTime": "2057", + "body": "{\"status\":\"pass\",\"message\":\"OAuth-1.0a signature verification was successful\"}" + }, + { + "id": "a0b1d579-21ff-49fa-876d-da33d01a00a6", + "name": "401", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "name": "Authorization", + "value": "OAuth oauth_consumer_key=\"RKCGzna7bv9YD57c_wrong\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1472121295\",oauth_nonce=\"8LTsU2\",oauth_version=\"1.0\",oauth_signature=\"tSUexpY%2B7EhSY7cFXiFN5EMx2zw%3D\"" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "code", + "value": "xWnkliVQJURqB2x1", + "type": "text" + }, + { + "key": "grant_type", + "value": "authorization_code", + "type": "text" + }, + { + "key": "redirect_uri", + "value": "https://www.getpostman.com/oauth2/callback", + "type": "text" + }, + { + "key": "client_id", + "value": "abc123", + "type": "text" + }, + { + "key": "client_secret", + "value": "ssh-secret", + "type": "text" + } + ] + }, + "url": { + "raw": "https://echo.getpostman.com/oauth1", + "protocol": "https", + "host": [ + "echo", + "getpostman", + "com" + ], + "path": [ + "oauth1" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Expose-Headers", + "key": "Access-Control-Expose-Headers", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "536", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 25 Aug 2016 10:34:55 GMT", + "description": "" + }, + { + "name": "ETag", + "key": "ETag", + "value": "W/\"218-SGnurnTsu5qV5cCYWxsJlg\"", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.8.1", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + } + ], + "cookie": [], + "responseTime": "322", + "body": "{\"status\":\"fail\",\"message\":\"HMAC-SHA1 verification failed\",\"base_uri\":\"https://echo.getpostman.com/oauth1\",\"normalized_param_string\":\"oauth_consumer_key=RKCGzna7bv9YD57c_wrong&oauth_nonce=8LTsU2&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1472121295&oauth_version=1.0\",\"base_string\":\"GET&https%3A%2F%2Fecho.getpostman.com%2Foauth1&oauth_consumer_key%3DRKCGzna7bv9YD57c_wrong%26oauth_nonce%3D8LTsU2%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1472121295%26oauth_version%3D1.0\",\"signing_key\":\"D%2BEdQ-gs%24-%25%402Nu7&\"}" + } + ] + }, + { + "name": "Hawk Auth", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"Status code is 200\"] = responseCode.code === 200;" + ] + } + } + ], + "request": { + "auth": { + "type": "hawk", + "hawk": [ + { + "key": "authId", + "value": "dh37fgj492je", + "type": "string" + }, + { + "key": "authKey", + "value": "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn", + "type": "string" + }, + { + "key": "algorithm", + "value": "sha256", + "type": "string" + }, + { + "key": "user", + "value": "", + "type": "string" + }, + { + "key": "saveHelperData", + "value": true, + "type": "boolean" + }, + { + "key": "nonce", + "value": "RZKGNz", + "type": "string" + }, + { + "key": "extraData", + "type": "any" + }, + { + "key": "appId", + "type": "any" + }, + { + "key": "delegation", + "type": "any" + }, + { + "key": "timestamp", + "value": "", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "access_token", + "value": "xyz1", + "type": "text" + }, + { + "key": "id", + "value": "U1", + "type": "text" + }, + { + "key": "server_secret", + "value": "zeppelin", + "type": "text" + }, + { + "key": "admin", + "value": "true", + "type": "text" + } + ] + }, + "url": { + "raw": "https://postman-echo.com/auth/hawk", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "auth", + "hawk" + ] + }, + "description": "This endpoint is a Hawk Authentication protected endpoint. [Hawk authentication](https://github.com/hueniverse/hawk) is a widely used protocol for protecting API endpoints. One of Hawk's main goals is to enable HTTP authentication for services that do not use TLS (although it can be used in conjunction with TLS as well).\n\nIn order to use this endpoint, select the \"Hawk Auth\" helper inside Postman, and set the following values:\n\nHawk Auth ID: `dh37fgj492je`\n\nHawk Auth Key: `werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn`\n\nAlgorithm: `sha256`\n\nThe rest of the values are optional, and can be left blank. Hitting send should give you a response with a status code of 200 OK." + }, + "response": [ + { + "id": "b3808284-6b19-4db5-a0f5-83063a3c3c8a", + "name": "Success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "name": "Authorization", + "value": "Hawk id=\"dh37fgj492je\", ts=\"1459422734\", nonce=\"XiwiCU\", mac=\"KzMHk67BYCC9zZqRy5hRdWFEFLHX5bNlRWGdmOAWKp0=\"" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "access_token", + "value": "xyz1", + "type": "text" + }, + { + "key": "id", + "value": "U1", + "type": "text" + }, + { + "key": "server_secret", + "value": "zeppelin", + "type": "text" + }, + { + "key": "admin", + "value": "true", + "type": "text" + } + ] + }, + "url": { + "raw": "https://echo.getpostman.com/auth/hawk", + "protocol": "https", + "host": [ + "echo", + "getpostman", + "com" + ], + "path": [ + "auth", + "hawk" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Encoding", + "key": "Content-Encoding", + "value": "gzip", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 31 Mar 2016 11:12:16 GMT", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.6.2", + "description": "" + }, + { + "name": "Server-Authorization", + "key": "Server-Authorization", + "value": "Hawk mac=\"vRrUzDdcHu2NaNts/r4zg2xmXMdX8wPiTGTM398BDRg=\", hash=\"qmtflETMybaZiOQ2dLT17yiRunFT5OCIxZRZ0boQaiE=\"", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + }, + { + "name": "X-Powered-By", + "key": "X-Powered-By", + "value": "Sails ", + "description": "" + }, + { + "name": "transfer-encoding", + "key": "transfer-encoding", + "value": "chunked", + "description": "" + } + ], + "cookie": [ + { + "expires": "Fri Apr 15 2016 12:54:28 GMT+0200 (CEST)", + "hostOnly": false, + "httpOnly": false, + "domain": ".getpostman.com", + "path": "/", + "secure": false, + "session": false, + "value": "yes", + "key": "getpostmanlogin" + }, + { + "expires": "Fri Apr 15 2016 12:54:28 GMT+0200 (CEST)", + "hostOnly": false, + "httpOnly": false, + "domain": ".getpostman.com", + "path": "/", + "secure": false, + "session": false, + "value": "9f887f3b7f14b8c29ac4dc4109381b0b89a76e785c7b34251d6c8025b41b24013d2aa49f40e2deac19cbf0594dd984169455534d91ff98d4d1868d67ac857017629f137926e3a04a616bb83a2fb5ab9e6cbea9579ed5d5c1155d47545d72aad5be99f4abd0a7130805b3807d70cd507171dbe9d950d8e35a853f9ec075f5a767c95df4d57f7d521b66605b3bda3801700e26e651d1129c798b729ee3b91702d43ae64ab226c3f426893753def772c15442a7552dc84a3c773d6099a50b0a6af940b64c8176fedfcecd5fc31ccfc3bbc0124bfdaa0d62e4252d4aafb46a3c10963d12391e1fa97a1c0f19a636f57a3ac8cc35567d1cb6cb53b77f8adde3f6754a765596d7d00bdeb9acb5cc8d115e7c3f50ec3228e34d3e6c7464e9039b01868e03d10e9f87772397602453e9e91205de7b86021fad06eb26e69298e99ff1597a670faeb310f8c092041d544851de84f2bee89a92123da6eea286210524035c85361e2af42166a6", + "key": "postman.sid" + }, + { + "expires": "Invalid Date", + "hostOnly": true, + "httpOnly": true, + "domain": "echo.getpostman.com", + "path": "/", + "secure": false, + "session": true, + "value": "s%3AryJV7v-PE4PuTjBK6nH5XOynQ4atuATV.n17KcaLhVmV8TBHNLwdwXgGR7lmqs3i478WPlTbRgZ4", + "key": "sails.sid" + } + ], + "responseTime": "1855", + "body": "{\"status\":\"pass\",\"message\":\"Hawk Authentication successful\"}" + } + ] + } + ] + }, + { + "name": "Cookies", + "description": "The cookie related endpoints allow one to get, set and delete simple cookies.\n\nCookies are small snippets of information that is stored in the browser and sent back to the server with every subsequent requests in order to store useful information between requests.\nIf you want to know more about cookies, read the [HTTP Cookie](https://en.wikipedia.org/wiki/HTTP_cookie) article on wikipedia.", + "item": [ + { + "name": "Set Cookies", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "// handle case where it is 304", + "", + "if (responseCode.code === 200) {", + " tests[\"Status code is 302 or 200\"] = true;", + " tests[\"Body contains cookies\"] = responseBody.has(\"cookies\");", + " ", + " var body = JSON.parse(responseBody);", + " tests[\"Body contains cookie foo1\"] = 'foo1' in body.cookies;", + " tests[\"Body contains cookie foo2\"] = 'foo2' in body.cookies;", + "", + "}", + "else if (responseCode.code === 302) {", + " tests[\"Status code is 302 or 200\"] = true;", + " tests[\"Body has redirection message\"] = responseBody.has(\"Found. Redirecting to /cookies\")", + "}", + "else {", + " tests[\"Status code is 302 or 200\"] = false;", + "}", + "", + "tests[\"foo1 cookie is set\"] = _.get(postman.getResponseCookie('foo1'), 'value') === 'bar1';", + "", + "tests[\"foo2 cookie is set\"] = _.get(postman.getResponseCookie('foo2'), 'value') === 'bar2';" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/cookies/set?foo1=bar1&foo2=bar2", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "cookies", + "set" + ], + "query": [ + { + "key": "foo1", + "value": "bar1" + }, + { + "key": "foo2", + "value": "bar2" + } + ] + }, + "description": "The cookie setter endpoint accepts a list of cookies and their values as part of URL parameters of a `GET` request. These cookies are saved and can be subsequently retrieved or deleted. The response of this request returns a JSON with all cookies listed.\n\nTo set your own set of cookies, simply replace the URL parameters \"foo1=bar1&foo2=bar2\" with your own set of key-value pairs." + }, + "response": [ + { + "id": "cc4d957e-926b-44ec-8029-3915f9250061", + "name": "Cookies", + "originalRequest": { + "header": [], + "body": { + "mode": "raw", + "raw": "" + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Encoding", + "key": "Content-Encoding", + "value": "gzip", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "51", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 29 Oct 2015 06:15:28 GMT", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.6.2", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + }, + { + "name": "X-Powered-By", + "key": "X-Powered-By", + "value": "Sails ", + "description": "" + } + ], + "cookie": [], + "responseTime": "3063", + "body": "{\"cookies\":{\"foo1\":\"bar\",\"foo2\":\"bar\"}}" + } + ] + }, + { + "name": "Get Cookies", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "try {", + " tests[\"Body contains cookies\"] = responseBody.has(\"cookies\");", + " responseJSON = JSON.parse(responseBody);", + " tests[\"Cookies object is empty\"] = (Object.keys(responseJSON.cookies).length > 0)", + "}", + "catch (e) { }", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/cookies", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "cookies" + ] + }, + "description": "Use this endpoint to get a list of all cookies that are stored with respect to this domain. Whatever key-value pairs that has been previously set by calling the \"Set Cookies\" endpoint, will be returned as response JSON." + }, + "response": [ + { + "id": "f50b9e42-64f6-4477-9bab-c08c304ff24a", + "name": "Cookies", + "originalRequest": { + "header": [], + "body": { + "mode": "raw", + "raw": "" + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Encoding", + "key": "Content-Encoding", + "value": "gzip", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "46", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 29 Oct 2015 06:16:29 GMT", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.6.2", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + }, + { + "name": "X-Powered-By", + "key": "X-Powered-By", + "value": "Sails ", + "description": "" + } + ], + "cookie": [], + "responseTime": "462", + "body": "{\"cookies\":{\"foo2\":\"bar\"}}" + } + ] + }, + { + "name": "Delete Cookies", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "// handle case where it is 304", + "", + "if (responseCode.code === 200) {", + " tests[\"Status code is 302 or 200\"] = true;", + " tests[\"Body contains cookies\"] = responseBody.has(\"cookies\");", + " ", + " var body = JSON.parse(responseBody);", + " tests[\"Body contains cookie foo1\"] = 'foo1' in body.cookies;", + " tests[\"Body contains cookie foo2\"] = 'foo2' in body.cookies;", + "", + "}", + "else if (responseCode.code === 302) {", + " tests[\"Status code is 302 or 200\"] = true;", + " tests[\"Body has redirection message\"] = responseBody.has(\"Found. Redirecting to /cookies\")", + "}", + "else {", + " tests[\"Status code is 302 or 200\"] = false;", + "}", + "", + "tests[\"foo1 cookie is set\"] = _.get(postman.getResponseCookie('foo1'), 'value') === undefined;", + "", + "tests[\"foo2 cookie is set\"] = _.get(postman.getResponseCookie('foo2'), 'value') === undefined;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/cookies/delete?foo1&foo2", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "cookies", + "delete" + ], + "query": [ + { + "key": "foo1", + "value": "" + }, + { + "key": "foo2", + "value": "" + } + ] + }, + "description": "One or more cookies that has been set for this domain can be deleted by providing the cookie names as part of the URL parameter. The response of this request is a JSON containing the list of currently set cookies." + }, + "response": [ + { + "id": "8de907b5-5771-4ca1-9f3c-bfb16d3131ec", + "name": "Cookies Response", + "originalRequest": { + "header": [], + "body": { + "mode": "raw", + "raw": "" + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Encoding", + "key": "Content-Encoding", + "value": "gzip", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "46", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 29 Oct 2015 06:16:00 GMT", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.6.2", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + }, + { + "name": "X-Powered-By", + "key": "X-Powered-By", + "value": "Sails ", + "description": "" + } + ], + "cookie": [], + "responseTime": "1417", + "body": "{\"cookies\":{\"foo2\":\"bar\"}}" + } + ] + } + ] + }, + { + "name": "Headers", + "description": "The following set of endpoints allow one to see the headers being sent as part of a request and to get a custom set of headers as part of response.\n\nHTTP header fields provide required information about the request or response, or about the object sent in the message body. Both request headers and response headers can be controlled using these endpoints.", + "item": [ + { + "name": "Request Headers", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "try {", + " tests[\"Body contains headers\"] = responseBody.has(\"headers\");", + " responseJSON = JSON.parse(responseBody);", + " tests[\"Header contains host\"] = \"host\" in responseJSON.headers;", + " tests[\"Header contains test parameter sent as part of request header\"] = \"my-sample-header\" in responseJSON.headers;", + "}", + "catch (e) { }", + "", + "", + "", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "my-sample-header", + "value": "Lorem ipsum dolor sit amet" + } + ], + "body": {}, + "url": { + "raw": "https://postman-echo.com/headers", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "headers" + ] + }, + "description": "A `GET` request to this endpoint returns the list of all request headers as part of the response JSON.\nIn Postman, sending your own set of headers through the [Headers tab](https://www.getpostman.com/docs/requests#headers?source=echo-collection-app-onboarding) will reveal the headers as part of the response." + }, + "response": [ + { + "id": "8743cf03-a322-43b2-a6c8-915e45638ccc", + "name": "my-sample-header", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "my-sample-header", + "value": "Lorem ipsum dolor sit amet", + "enabled": true + } + ], + "body": {}, + "url": { + "raw": "https://echo.getpostman.com/headers", + "protocol": "https", + "host": [ + "echo", + "getpostman", + "com" + ], + "path": [ + "headers" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Encoding", + "key": "Content-Encoding", + "value": "gzip", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "342", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 31 Mar 2016 11:14:00 GMT", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.6.2", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + }, + { + "name": "X-Powered-By", + "key": "X-Powered-By", + "value": "Sails ", + "description": "" + } + ], + "cookie": [ + { + "expires": "Invalid Date", + "hostOnly": true, + "httpOnly": true, + "domain": "echo.getpostman.com", + "path": "/", + "secure": false, + "session": true, + "value": "s%3A9stja5zKmIILxq9Jvtha7Lp9LIz1VIdK.Vp8MHC%2BEUJe4ICZPXn2JAoXaV2bTgtoQd%2B3XJLNr51Y", + "key": "sails.sid" + } + ], + "responseTime": "460", + "body": "{\"headers\":{\"host\":\"echo.getpostman.com\",\"accept\":\"*/*\",\"accept-encoding\":\"gzip, deflate, sdch\",\"accept-language\":\"en-US,en;q=0.8\",\"cache-control\":\"no-cache\",\"my-sample-header\":\"Lorem ipsum dolor sit amet\",\"postman-token\":\"3c8ea80b-f599-fba6-e0b4-a0910440e7b6\",\"user-agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36\",\"x-forwarded-port\":\"443\",\"x-forwarded-proto\":\"https\"}}" + } + ] + }, + { + "name": "Response Headers", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"Body contains Content-Type\"] = responseBody.has(\"Content-Type\");", + "tests[\"response headers have key sent as part of request\"] = (postman.getResponseHeader('test') == 'response_headers')" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/response-headers?Content-Type=text/html&test=response_headers", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "response-headers" + ], + "query": [ + { + "key": "Content-Type", + "value": "text/html" + }, + { + "key": "test", + "value": "response_headers" + } + ] + }, + "description": "This endpoint causes the server to send custom set of response headers. Providing header values as part of the URL parameters of a `GET` request to this endpoint returns the same as part of response header.\n\nTo send your own set of headers, simply add or replace the the URL parameters with your own set." + }, + "response": [ + { + "id": "ad9e8863-9da8-4702-91cf-28d7f1144ef1", + "name": "Response headers", + "originalRequest": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://echo.getpostman.com/response-headers?Content-Type=text/html&test=response_headers", + "protocol": "https", + "host": [ + "echo", + "getpostman", + "com" + ], + "path": [ + "response-headers" + ], + "query": [ + { + "key": "Content-Type", + "value": "text/html" + }, + { + "key": "test", + "value": "response_headers" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "html", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Encoding", + "key": "Content-Encoding", + "value": "gzip", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "71", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "text/html; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 31 Mar 2016 11:14:18 GMT", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.6.2", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + }, + { + "name": "X-Powered-By", + "key": "X-Powered-By", + "value": "Sails ", + "description": "" + }, + { + "name": "test", + "key": "test", + "value": "response_headers", + "description": "" + } + ], + "cookie": [ + { + "expires": "Invalid Date", + "hostOnly": true, + "httpOnly": true, + "domain": "echo.getpostman.com", + "path": "/", + "secure": false, + "session": true, + "value": "s%3A9stja5zKmIILxq9Jvtha7Lp9LIz1VIdK.Vp8MHC%2BEUJe4ICZPXn2JAoXaV2bTgtoQd%2B3XJLNr51Y", + "key": "sails.sid" + } + ], + "responseTime": "568", + "body": "{\"Content-Type\":\"text/html\",\"test\":\"response_headers\"}" + } + ] + } + ] + }, + { + "name": "Request Methods", + "description": "HTTP has multiple request \"verbs\", such as `GET`, `PUT`, `POST`, `DELETE`,\n`PATCH`, `HEAD`, etc. \n\nAn HTTP Method (verb) defines how a request should be interpreted by a server. \nThe endpoints in this section demonstrate various HTTP Verbs. Postman supports \nall the HTTP Verbs, including some rarely used ones, such as `PROPFIND`, `UNLINK`, \netc.\n\nFor details about HTTP Verbs, refer to [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9)\n", + "item": [ + { + "name": "GET Request", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "tests['response json contains headers'] = _.has(responseJSON, 'headers');", + "tests['response json contains args'] = _.has(responseJSON, 'args');", + "tests['response json contains url'] = _.has(responseJSON, 'url');", + "", + "tests['args key contains argument passed as url parameter'] = ('test' in responseJSON.args);", + "tests['args passed via request url params has value \"123\"'] = (_.get(responseJSON, 'args.test') === \"123\");" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/get?test=123", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "get" + ], + "query": [ + { + "key": "test", + "value": "123" + } + ] + }, + "description": "The HTTP `GET` request method is meant to retrieve data from a server. The data\nis identified by a unique URI (Uniform Resource Identifier). \n\nA `GET` request can pass parameters to the server using \"Query String \nParameters\". For example, in the following request,\n\n> http://example.com/hi/there?hand=wave\n\nThe parameter \"hand\" has the value \"wave\".\n\nThis endpoint echoes the HTTP headers, request parameters and the complete\nURI requested." + }, + "response": [] + }, + { + "name": "POST Raw Text", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'data');", + "tests['response matches the data posted'] = (responseJSON.data && responseJSON.data.length === 256);", + "", + "tests[\"content-type equals text/plain\"] = responseJSON && responseJSON.headers && (responseJSON.headers[\"content-type\"] === 'text/plain');" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": { + "mode": "raw", + "raw": "Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium. Praesent neque quam, tincidunt nec leo eget, rutrum vehicula magna.\nMaecenas consequat elementum elit, id semper sem tristique et. Integer pulvinar enim quis consectetur interdum volutpat." + }, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested." + }, + "response": [] + }, + { + "name": "POST Form Data", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'form');", + "tests['response matches the data posted'] = (responseJSON.form && responseJSON.form.strange === 'boom');" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "strange", + "value": "boom", + "type": "text" + } + ] + }, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested when data is sent as a form parameter." + }, + "response": [] + }, + { + "name": "PUT Request", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has PUT data'] = _.has(responseJSON, 'data');", + "tests['response matches the data sent in request'] = (responseJSON.data && responseJSON.data.length === 256);" + ] + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "Etiam mi lacus, cursus vitae felis et, blandit pellentesque neque. Vestibulum eget nisi a tortor commodo dignissim.\nQuisque ipsum ligula, faucibus a felis a, commodo elementum nisl. Mauris vulputate sapien et tincidunt viverra. Donec vitae velit nec metus." + }, + "url": { + "raw": "https://postman-echo.com/put", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "put" + ] + }, + "description": "The HTTP `PUT` request method is similar to HTTP `POST`. It too is meant to \ntransfer data to a server (and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `PUT` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following \nraw HTTP request,\n\n> PUT /hi/there?hand=wave\n>\n> \n\n\n" + }, + "response": [] + }, + { + "name": "PATCH Request", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has PUT data'] = _.has(responseJSON, 'data');", + "tests['response matches the data sent in request'] = (responseJSON.data && responseJSON.data.length === 256);" + ] + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "Curabitur auctor, elit nec pulvinar porttitor, ex augue condimentum enim, eget suscipit urna felis quis neque.\nSuspendisse sit amet luctus massa, nec venenatis mi. Suspendisse tincidunt massa at nibh efficitur fringilla. Nam quis congue mi. Etiam volutpat." + }, + "url": { + "raw": "https://postman-echo.com/patch", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "patch" + ] + }, + "description": "The HTTP `PATCH` method is used to update resources on a server. The exact\nuse of `PATCH` requests depends on the server in question. There are a number\nof server implementations which handle `PATCH` differently. Technically, \n`PATCH` supports both Query String parameters and a Request Body.\n\nThis endpoint accepts an HTTP `PATCH` request and provides debug information\nsuch as the HTTP headers, Query String arguments, and the Request Body." + }, + "response": [] + }, + { + "name": "DELETE Request", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has PUT data'] = _.has(responseJSON, 'data');", + "tests['response matches the data sent in request'] = (responseJSON.data && responseJSON.data.length === 256);" + ] + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "Donec fermentum, nisi sed cursus eleifend, nulla tortor ultricies tellus, ut vehicula orci arcu ut velit. In volutpat egestas dapibus.\nMorbi condimentum vestibulum sapien. Etiam dignissim diam quis eros lobortis gravida vel lobortis est. Etiam gravida sed." + }, + "url": { + "raw": "https://postman-echo.com/delete", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "delete" + ] + }, + "description": "The HTTP `DELETE` method is used to delete resources on a server. The exact\nuse of `DELETE` requests depends on the server implementation. In general, \n`DELETE` requests support both, Query String parameters as well as a Request \nBody.\n\nThis endpoint accepts an HTTP `DELETE` request and provides debug information\nsuch as the HTTP headers, Query String arguments, and the Request Body." + }, + "response": [] + } + ] + }, + { + "name": "Utilities", + "description": "", + "item": [ + { + "name": "Response Status Code", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "try {", + " responseJSON = JSON.parse(responseBody); ", + " tests[\"Status equals 200\"] = responseJSON.status === 200;", + "}", + "catch (e) { }", + "tests[\"Body contains status\"] = responseBody.has(\"status\");", + "", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/status/200", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "status", + "200" + ] + }, + "description": "This endpoint allows one to instruct the server which status code to respond with.\n\nEvery response is accompanied by a status code. The status code provides a summary of the nature of response sent by the server. For example, a status code of `200` means everything is okay with the response and a code of `404` implies that the requested URL does not exist on server. \nA list of all valid HTTP status code can be found at the [List of Status Codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) wikipedia article. When using Postman, the response status code is described for easy reference.\n\nNote that if an invalid status code is requested to be sent, the server returns a status code of `400 Bad Request`." + }, + "response": [ + { + "id": "db6c3586-984d-4710-bb09-067b09ccd21c", + "name": "200", + "originalRequest": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://echo.getpostman.com/status/200", + "protocol": "https", + "host": [ + "echo", + "getpostman", + "com" + ], + "path": [ + "status", + "200" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "javascript", + "_postman_previewtype": "html", + "header": [ + { + "name": "Access-Control-Allow-Credentials", + "key": "Access-Control-Allow-Credentials", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Headers", + "key": "Access-Control-Allow-Headers", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Methods", + "key": "Access-Control-Allow-Methods", + "value": "", + "description": "" + }, + { + "name": "Access-Control-Allow-Origin", + "key": "Access-Control-Allow-Origin", + "value": "", + "description": "" + }, + { + "name": "Connection", + "key": "Connection", + "value": "keep-alive", + "description": "" + }, + { + "name": "Content-Length", + "key": "Content-Length", + "value": "14", + "description": "" + }, + { + "name": "Content-Type", + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "description": "" + }, + { + "name": "Date", + "key": "Date", + "value": "Thu, 31 Mar 2016 11:58:47 GMT", + "description": "" + }, + { + "name": "ETag", + "key": "ETag", + "value": "W/\"e-1056260003\"", + "description": "" + }, + { + "name": "Server", + "key": "Server", + "value": "nginx/1.6.2", + "description": "" + }, + { + "name": "Vary", + "key": "Vary", + "value": "Accept-Encoding", + "description": "" + }, + { + "name": "X-Powered-By", + "key": "X-Powered-By", + "value": "Sails ", + "description": "" + } + ], + "cookie": [ + { + "expires": "Fri Apr 15 2016 13:14:58 GMT+0200 (CEST)", + "hostOnly": false, + "httpOnly": false, + "domain": ".getpostman.com", + "path": "/", + "secure": false, + "session": false, + "value": "yes", + "key": "getpostmanlogin" + }, + { + "expires": "Fri Apr 15 2016 13:14:58 GMT+0200 (CEST)", + "hostOnly": false, + "httpOnly": false, + "domain": ".getpostman.com", + "path": "/", + "secure": false, + "session": false, + "value": "df0c0256028d7ec4d641f766104a9571a8e249685bbc667d7cee030bbf44d3209495c70c03248e31e678a93812591d5e12187a8e99bf6bc5e80c40903f6ff6226938f24e413c0ffa613a7372064ec44a8594e8d3ede6945e34394f369573feeebc4a73a3e24b8c9ac18a53704addb5fd3f71f1ede488ff551feb059e9c1fb208164814e45e0312c4df8ea6e83c26702f42ae634c6afbe82d57c857bbf5598b5527961c1c28688dc2580070a4389f0cf4ec0a179b5b9c11b2ecbaa5460d374065bf5c7a3add9505df0fa89acb9f227f05ed2d4c6b58c39d6d728bd49f6f323ae67d4a75882aa7682f5d6fc5b981ba411d94aa93970bfaefa1953a73e440d50d012b5f288975c888e2345ee7777e746fb5aed3a7b2dbc087c6456621aa78c24a3c17c5f96cf59844933249a352f631e2008cffac6faf06d0e253dcc01cf0067bf56c1fbc5ed61fec1861b60c5accf35ffc2e56154a113004fa1db9d7171c3af8fc063918554092f5", + "key": "postman.sid" + }, + { + "expires": "Sat Mar 31 2018 13:16:21 GMT+0200 (CEST)", + "hostOnly": false, + "httpOnly": false, + "domain": ".echo.getpostman.com", + "path": "/", + "secure": false, + "session": false, + "value": "GA1.3.1703443399.1459422978", + "key": "_ga" + }, + { + "expires": "Invalid Date", + "hostOnly": true, + "httpOnly": true, + "domain": "echo.getpostman.com", + "path": "/", + "secure": false, + "session": true, + "value": "s%3AvuHU0EKeDbyNjVrEc7U30dMPzVu8CRaD.GOV1H9olcVzXqrwqP%2BC%2B6MVj2UczXivcN00jgPoDYfs", + "key": "sails.sid" + } + ], + "responseTime": "251", + "body": "{\"status\":200}" + } + ] + }, + { + "name": "Streamed Response", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"response code is 200\"] = responseCode.code === 200;", + "tests[\"response is sent in chunks\"] = (postman.getResponseHeader('Transfer-Encoding') === 'chunked')", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/stream/10", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "stream", + "10" + ] + }, + "description": "This endpoint allows one to recieve streaming http response using [chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) of a configurable length.\n\nA streaming response does not wait for the entire response to be generated on server before flushing it out. This implies that for a fairly large response, parts of it can be streamed to the requestee as and when it is generated on server. The client can then take actions of processing this partially received data." + }, + "response": [] + }, + { + "name": "Delay Response", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests[\"response body has key delay\"] = 'delay' in responseJSON;", + "}", + "catch (e) { }", + "tests[\"response code is 200\"] = responseCode.code === 200;", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/delay/3", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "delay", + "3" + ] + }, + "description": "Using this endpoint one can configure how long it takes for the server to come back with a response. Appending a number to the URL defines the time (in seconds) the server will wait before responding.\n\nNote that a maximum delay of 10 seconds is accepted by the server." + }, + "response": [ + { + "id": "9ec42a57-9cd0-454c-b542-2fdc0bb7bb0c", + "name": "success-response", + "originalRequest": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://echo.getpostman.com/delay/3", + "protocol": "https", + "host": [ + "echo", + "getpostman", + "com" + ], + "path": [ + "delay", + "3" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "_postman_previewtype": "text", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "", + "name": "Access-Control-Allow-Credentials", + "description": "" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "", + "name": "Access-Control-Allow-Headers", + "description": "" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "", + "name": "Access-Control-Allow-Methods", + "description": "" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "", + "name": "Access-Control-Allow-Origin", + "description": "" + }, + { + "key": "Access-Control-Expose-Headers", + "value": "", + "name": "Access-Control-Expose-Headers", + "description": "" + }, + { + "key": "Connection", + "value": "keep-alive", + "name": "Connection", + "description": "" + }, + { + "key": "Content-Length", + "value": "13", + "name": "Content-Length", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "name": "Content-Type", + "description": "" + }, + { + "key": "Date", + "value": "Mon, 02 Jan 2017 09:19:03 GMT", + "name": "Date", + "description": "" + }, + { + "key": "ETag", + "value": "W/\"d-t/L/D5c0SDl+MoXtKdSVOg\"", + "name": "ETag", + "description": "" + }, + { + "key": "Server", + "value": "nginx/1.10.1", + "name": "Server", + "description": "" + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "name": "Vary", + "description": "" + } + ], + "cookie": [ + { + "expires": "Mon Jan 18 2038 22:44:07 GMT+0100 (CET)", + "httpOnly": true, + "domain": "echo.getpostman.com", + "path": "/", + "secure": false, + "value": "s%3AYjUiFBtGiJVL2a-qzZQZ1DFlAMhgXN9O.WaAjRUV0OteZxwmhbNibuB7VKse068JJIh6PwLQUKmQ", + "key": "sails.sid" + } + ], + "responseTime": "4769", + "body": "{\"delay\":\"3\"}" + } + ] + }, + { + "name": "Get UTF8 Encoded Response", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"response code is 200\"] = responseCode.code === 200;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/encoding/utf8", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "encoding", + "utf8" + ] + }, + "description": "If a response of an endpoint requires to send data beyond the basic English / ASCII character set, the `charset` parameter in the `Content-Type` response header defines the character encoding policy.\n\nThis endpoint returns an `UTF8` character encoded response body with text in various languages such as Greek, Latin, East Asian, etc. Postman can interpret the character encoding and use appropriate methods to display the character set in responses." + }, + "response": [] + }, + { + "name": "GZip Compressed Response", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "try {", + " var data = JSON.parse(responseBody);", + " tests[\"Body contains gzipped\"] = responseBody.has(\"gzipped\");", + " tests[\"Body contains headers\"] = responseBody.has(\"headers\");", + " tests[\"Body contains method\"] = responseBody.has(\"method\");", + "}", + "catch(e) {", + " console.log('Cannot parse response,probably not a JSON');", + "}", + "tests[\"response code is 200\"] = responseCode.code === 200;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/gzip", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "gzip" + ] + }, + "description": "This endpoint returns the response using [gzip compression algoritm](https://en.wikipedia.org/wiki/Gzip).\nThe uncompressed response is a JSON string containing the details of the request sent by the client. For this endpoint to work, one should request with `Accept-encoding` header containing `gzip` as part of its value. Postman supports gzip, deflate and SDCH decoding and automatically sends them as part of the request.\n\nHTTP Compression allows the server to send responses in a compressed format, which is uncompressed by the client before processing. This reduces network bandwidth consumption at the cost of increase in CPU usage.\nTo know more about this, refer the [HTTP Compression](https://en.wikipedia.org/wiki/HTTP_compression) wikipedia article." + }, + "response": [] + }, + { + "name": "Deflate Compressed Response", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"response code is 200\"] = responseCode.code === 200;", + "", + "try {", + " var data = JSON.parse(responseBody);", + " tests[\"Body contains deflated\"] = responseBody.has(\"deflated\");", + " tests[\"Body contains headers\"] = responseBody.has(\"headers\");", + " tests[\"Body contains method\"] = responseBody.has(\"method\");", + "}", + "catch(e) {", + " console.log('Cannot parse response,probably not a JSON');", + "}", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/deflate", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "deflate" + ] + }, + "description": "This endpoint returns the response using [deflate compression algoritm](https://en.wikipedia.org/wiki/DEFLATE). \nThe uncompressed response is a JSON string containing the details of the request sent by the client. For this endpoint to work, one should request with `Accept-encoding` header containing `deflate` as part of its value. Postman supports gzip, deflate and SDCH decoding and automatically sends them as part of the request.\n\nHTTP Compression allows the server to send responses in a compressed format, which is uncompressed by the client before processing. This reduces network bandwidth consumption at the cost of increase in CPU usage.\nTo know more about this, refer the [HTTP Compression](https://en.wikipedia.org/wiki/HTTP_compression) wikipedia article." + }, + "response": [] + }, + { + "name": "IP address in JSON format", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var body = JSON.parse(responseBody);", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Valid response structure\"] = /^[a-fA-F:\\.0-9]+$/.test(body.ip);" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/ip", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "ip" + ] + }, + "description": "A simple `GET` request to return the IP address of the source request in the following `JSON` format:\n\n```json\n{\n ip: \"request-ip-address\"\n}\n```" + }, + "response": [] + } + ] + }, + { + "name": "Utilities / Date and Time", + "description": "A set of `/time/*` mounted requests to perform date-time manipulations, among other operations.\n", + "item": [ + { + "name": "Current UTC time", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Time is in a valid format\"] = responseBody === postman.getResponseHeader(\"date\");", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/now", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "now" + ] + }, + "description": "A simple `GET` request to `/time/now` to return the current timestamp as a UTC string.\n\n```\nFri, 04 Nov 2016 09:00:46 GMT\n```" + }, + "response": [ + { + "id": "36e75ffa-b1f6-41ec-b14f-e5fcfa5933ff", + "name": "time as text", + "originalRequest": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/now", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "now" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "html", + "_postman_previewtype": "text", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "", + "name": "Access-Control-Allow-Credentials", + "description": "" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "", + "name": "Access-Control-Allow-Headers", + "description": "" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "", + "name": "Access-Control-Allow-Methods", + "description": "" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "", + "name": "Access-Control-Allow-Origin", + "description": "" + }, + { + "key": "Access-Control-Expose-Headers", + "value": "", + "name": "Access-Control-Expose-Headers", + "description": "" + }, + { + "key": "Connection", + "value": "keep-alive", + "name": "Connection", + "description": "" + }, + { + "key": "Content-Encoding", + "value": "gzip", + "name": "Content-Encoding", + "description": "" + }, + { + "key": "Content-Length", + "value": "49", + "name": "Content-Length", + "description": "" + }, + { + "key": "Content-Type", + "value": "text/html; charset=utf-8", + "name": "Content-Type", + "description": "" + }, + { + "key": "Date", + "value": "Wed, 11 Jan 2017 10:27:12 GMT", + "name": "Date", + "description": "" + }, + { + "key": "ETag", + "value": "W/\"1d-2jJhkzratfVX9VZ0+raHbw\"", + "name": "ETag", + "description": "" + }, + { + "key": "Server", + "value": "nginx/1.10.1", + "name": "Server", + "description": "" + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "name": "Vary", + "description": "" + }, + { + "key": "set-cookie", + "value": "sails.sid=s%3A2lT3TO7qS1tadeSAp4axl-NcXG9CV6Rf.HGqLY%2FlKEKY4fgCLePaAZs3tCHp%2Bglf7ZOJYlonGeig; Path=/; HttpOnly", + "name": "set-cookie", + "description": "" + } + ], + "cookie": [ + { + "expires": "Mon Jan 18 2038 22:44:07 GMT+0100 (CET)", + "httpOnly": true, + "domain": "postman-echo.com", + "path": "/", + "secure": false, + "value": "s%3A2lT3TO7qS1tadeSAp4axl-NcXG9CV6Rf.HGqLY%2FlKEKY4fgCLePaAZs3tCHp%2Bglf7ZOJYlonGeig", + "key": "sails.sid" + } + ], + "responseTime": "749", + "body": "Wed, 11 Jan 2017 10:27:12 GMT" + } + ] + }, + { + "name": "Timestamp validity", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var validity = JSON.parse(responseBody).valid;", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Timestamp is valid\"] = validity === true;", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/valid?timestamp=2016-10-10", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "valid" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + } + ] + }, + "description": "A simple `GET` request to `/time/valid` to determine the validity of the timestamp, (current by default).\nThis endpoint accepts `timestamp`, `locale`, `format`, and `strict` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a valid key to indicate the result. The response code is `200`.\n\n```\n{\n valid: true/false\n}\n```" + }, + "response": [ + { + "id": "0de5421b-f8d0-4dc5-abbf-4c0e642394d1", + "name": "Invalid Timestamp", + "originalRequest": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/valid?timestamp=2016-10-10", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "valid" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "_postman_previewtype": "text", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "", + "name": "Access-Control-Allow-Credentials", + "description": "" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "", + "name": "Access-Control-Allow-Headers", + "description": "" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "", + "name": "Access-Control-Allow-Methods", + "description": "" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "", + "name": "Access-Control-Allow-Origin", + "description": "" + }, + { + "key": "Access-Control-Expose-Headers", + "value": "", + "name": "Access-Control-Expose-Headers", + "description": "" + }, + { + "key": "Connection", + "value": "keep-alive", + "name": "Connection", + "description": "" + }, + { + "key": "Content-Length", + "value": "15", + "name": "Content-Length", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "name": "Content-Type", + "description": "" + }, + { + "key": "Date", + "value": "Wed, 11 Jan 2017 10:27:53 GMT", + "name": "Date", + "description": "" + }, + { + "key": "ETag", + "value": "W/\"f-/i9mO/upK91ZtL0BkKFGtw\"", + "name": "ETag", + "description": "" + }, + { + "key": "Server", + "value": "nginx/1.10.1", + "name": "Server", + "description": "" + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "name": "Vary", + "description": "" + }, + { + "key": "set-cookie", + "value": "sails.sid=s%3ATNJaNxi2QCv4RPBb64sIZxQGN1h6IP3g.9sQVAijlsLsh0r7LgffxXa9k2we6UumPEVv%2Bsk4woLI; Path=/; HttpOnly", + "name": "set-cookie", + "description": "" + } + ], + "cookie": [ + { + "expires": "Mon Jan 18 2038 22:44:07 GMT+0100 (CET)", + "httpOnly": true, + "domain": "postman-echo.com", + "path": "/", + "secure": false, + "value": "s%3ATNJaNxi2QCv4RPBb64sIZxQGN1h6IP3g.9sQVAijlsLsh0r7LgffxXa9k2we6UumPEVv%2Bsk4woLI", + "key": "sails.sid" + } + ], + "responseTime": "264", + "body": "{\"valid\":false}" + }, + { + "id": "6bdbe413-2cb8-4872-83dc-11317f106c05", + "name": "Valid Timestamp", + "originalRequest": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/valid?timestamp=2016-10-10", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "valid" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "_postman_previewtype": "text", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "", + "name": "Access-Control-Allow-Credentials", + "description": "" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "", + "name": "Access-Control-Allow-Headers", + "description": "" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "", + "name": "Access-Control-Allow-Methods", + "description": "" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "", + "name": "Access-Control-Allow-Origin", + "description": "" + }, + { + "key": "Access-Control-Expose-Headers", + "value": "", + "name": "Access-Control-Expose-Headers", + "description": "" + }, + { + "key": "Connection", + "value": "keep-alive", + "name": "Connection", + "description": "" + }, + { + "key": "Content-Length", + "value": "14", + "name": "Content-Length", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "name": "Content-Type", + "description": "" + }, + { + "key": "Date", + "value": "Wed, 11 Jan 2017 10:27:33 GMT", + "name": "Date", + "description": "" + }, + { + "key": "ETag", + "value": "W/\"e-OYN7L87J1Ba9oy5mJE2kcA\"", + "name": "ETag", + "description": "" + }, + { + "key": "Server", + "value": "nginx/1.10.1", + "name": "Server", + "description": "" + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "name": "Vary", + "description": "" + }, + { + "key": "set-cookie", + "value": "sails.sid=s%3AdDGZPe1CZw4mAxGVCHr6RfCADCAwquXa.F5MEm5LJad30JHrSwGGoyWLn2OAAGdvUM7kDtzNfdFI; Path=/; HttpOnly", + "name": "set-cookie", + "description": "" + } + ], + "cookie": [ + { + "expires": "Mon Jan 18 2038 22:44:07 GMT+0100 (CET)", + "httpOnly": true, + "domain": "postman-echo.com", + "path": "/", + "secure": false, + "value": "s%3AdDGZPe1CZw4mAxGVCHr6RfCADCAwquXa.F5MEm5LJad30JHrSwGGoyWLn2OAAGdvUM7kDtzNfdFI", + "key": "sails.sid" + } + ], + "responseTime": "493", + "body": "{\"valid\":true}" + } + ] + }, + { + "name": "Format timestamp", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var format = JSON.parse(responseBody).format;", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Formatted result is valid\"] = format === \"20\";", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/format?timestamp=2016-10-10&format=mm", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "format" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + }, + { + "key": "format", + "value": "mm" + } + ] + }, + "description": "A simple `GET` request to `/time/format` to convert the timestamp to any desired valid format.\n\nThis endpoint accepts `timestamp`, `locale`, `format`, and `strict` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a `format` key to indicate the result. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n format: \"formatted-timestamp\"\n}\n```" + }, + "response": [] + }, + { + "name": "Extract timestamp unit", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var unit = JSON.parse(responseBody).unit;", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Returned unit is valid\"] = unit === 1;", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/unit?timestamp=2016-10-10&unit=day", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "unit" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + }, + { + "key": "unit", + "value": "day" + } + ] + }, + "description": "A simple `GET` request to `/time/unit` to extract the specified timestamp unit (as provided in the `unit` query parameter). The default unit returned is the `year`.\n\nThis endpoint accepts `timestamp`, `locale`, `format`, and `strict` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a `unit` key to indicate the result. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n unit: \"extracted-timestamp-unit\"\n}\n```" + }, + "response": [] + }, + { + "name": "Time addition", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var sum = JSON.parse(responseBody).sum;", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/add?timestamp=2016-10-10&years=100", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "add" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + }, + { + "key": "years", + "value": "100" + } + ] + }, + "description": "A simple `GET` request to `/time/add` to add units of time to the specified / current timestamp (as provided in the `years`, `months`, `days`, `hours`, `minutes`, `seconds`, and `milliseconds` query parameters).\n\nThis endpoint accepts `timestamp`, `locale`, `format`, and `strict` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a `sum` key to indicate the result. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n sum: \"sum of (provided / current) and provided timestamps\"\n}\n```" + }, + "response": [] + }, + { + "name": "Time subtraction", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var difference = JSON.parse(responseBody).difference;", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/subtract?timestamp=2016-10-10&years=100", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "subtract" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + }, + { + "key": "years", + "value": "100" + } + ] + }, + "description": "A simple `GET` request to `/time/subtract` to subtract units of time from the specified / current timestamp (as provided in the `years`, `months`, `days`, `hours`, `minutes`, `seconds`, and `milliseconds` query parameters).\n\nThis endpoint accepts `timestamp`, `locale`, `format`, and `strict` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a `difference` key to indicate the result. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n difference: \"difference between (provided / current) and provided timestamps\"\n}\n```" + }, + "response": [] + }, + { + "name": "Start of time", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var start = JSON.parse(responseBody).start;", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/start?timestamp=2016-10-10&unit=month", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "start" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + }, + { + "key": "unit", + "value": "month" + } + ] + }, + "description": "A simple `GET` request to `/time/start` to return a relative timstamp in the past from the specified / current timestamp (as provided in the `unit` query parameter).\n\nFor instance, if the `unit` has been specified as `month`, the returned timestamp would indicate the beginning of the current month. Similar results are returned for other units of time, like: `years`, `months`, `days`, `hours`, `minutes`, `seconds`, and `milliseconds`\n\nThis endpoint accepts `timestamp`, `locale`, `format`, and `strict` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a `start` key to indicate the result. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n start: \"A timestamp from the past, depending on the `unit` specified\"\n}\n```" + }, + "response": [] + }, + { + "name": "Object representation", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var object = JSON.parse(responseBody);", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/start?timestamp=2016-10-10&unit=month", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "start" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + }, + { + "key": "unit", + "value": "month" + } + ] + }, + "description": "A simple `GET` request to `/time/object` to return the current / provided timestamp as a JSON object.\n\nFor instance, if the `unit` has been specified as `month`, the returned timestamp would indicate the beginning of the current month. Similar results are returned for other units of time, like: `years`, `months`, `days`, `hours`, `minutes`, `seconds`, and `milliseconds`\n\nThis endpoint accepts `timestamp`, `locale`, `format`, and `strict` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n years: 2016,\n months: 10,\n days: 10,\n hours: 23,\n minutes: 34,\n seconds: 20,\n milliseconds: 980\n}\n```" + }, + "response": [] + }, + { + "name": "Before comparisons", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var before = JSON.parse(responseBody).before;", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Comparsion was correct\"] = before === true;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/before?timestamp=2016-10-10&target=2017-10-10", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "before" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + }, + { + "key": "target", + "value": "2017-10-10" + } + ] + }, + "description": "A simple `GET` request to `/time/before` to check if the provided timestamps is before a comparison `target` (query parameter).\n\nThis endpoint accepts `timestamp`, `locale`, `format`, `strict`, and `target` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a `before` key to indicate the result. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n before: true/false\n}\n```" + }, + "response": [] + }, + { + "name": "After comparisons", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var after = JSON.parse(responseBody).after;", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Comparsion was correct\"] = after === false;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/after?timestamp=2016-10-10&target=2017-10-10", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "after" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + }, + { + "key": "target", + "value": "2017-10-10" + } + ] + }, + "description": "A simple `GET` request to `/time/after` to check if the provided timestamps is after a comparison `target` (query parameter).\n\nThis endpoint accepts `timestamp`, `locale`, `format`, `strict`, and `target` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a `after` key to indicate the result. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n after: true/false\n}\n```" + }, + "response": [] + }, + { + "name": "Between timestamps", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var after = JSON.parse(responseBody).after;", + "", + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Comparsion was correct\"] = after === false;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/between?timestamp=2016-10-10&start=2017-10-10&end=2019-10-10", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "between" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + }, + { + "key": "start", + "value": "2017-10-10" + }, + { + "key": "end", + "value": "2019-10-10" + } + ] + }, + "description": "A simple `GET` request to `/time/between` to check if the provided timestamp is between a range specified by the `start` and `end` query parameters. A resolution limit can also be specified by the `unit` query parameter.\n\nFor instance, for a resolution `unit` of `month`,\n`2016-10-05` does lie between `2016-11-02` and `2016-09-01`.\n\nThis endpoint also accepts `timestamp`, `locale`, `format`, `strict`, and `target` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a `between` key to indicate the result. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n between: true/false\n}\n```" + }, + "response": [] + }, + { + "name": "Leap year check", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Comparsion was correct\"] = JSON.parse(responseBody).leap === true;" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/time/leap?timestamp=2016-10-10", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "time", + "leap" + ], + "query": [ + { + "key": "timestamp", + "value": "2016-10-10" + } + ] + }, + "description": "A simple `GET` request to `/time/leap` to check if the provided/current timestamp belongs to a leap year.\n\nThis endpoint also accepts `timestamp`, `locale`, `format`, `strict`, and `target` query parameters to construct the date time instance to check against.\n\nResponses are provided in JSON format, with a `leap` key to indicate the result. The response code is `200` for valid query parameters, and `400` otherwise.\n\n```\n{\n leap: true/false\n}\n```" + }, + "response": [] + } + ] + }, + { + "name": "Utilities / Postman Collection", + "description": "", + "item": [ + { + "name": "Transform collection from format v1 to v2", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": \"7875be4b-917d-4aff-8cc4-5606c36bf418\",\n \"name\": \"Sample Postman Collection\",\n \"description\": \"A sample collection to demonstrate collections as a set of related requests\",\n \"order\": [\n \"4d9134be-e8bf-4693-9cd7-1c0fc66ae739\",\n \"141ba274-cc50-4377-a59c-e080066f375e\",\n \"4511ca8b-0bc7-430f-b894-a7ec1036f322\"\n ],\n \"folders\": [],\n \"requests\": [\n {\n \"id\": \"4d9134be-e8bf-4693-9cd7-1c0fc66ae739\",\n \"name\": \"A simple GET request\",\n \"collectionId\": \"877b9dae-a50e-4152-9b89-870c37216f78\",\n \"method\": \"GET\",\n \"headers\": \"\",\n \"data\": [],\n \"rawModeData\": \"\",\n \"tests\": \"tests['response code is 200'] = (responseCode.code === 200);\",\n \"preRequestScript\": \"\",\n \"url\": \"https://postman-echo.com/get?source=newman-sample-github-collection\"\n },\n {\n \"id\": \"141ba274-cc50-4377-a59c-e080066f375e\",\n \"name\": \"A simple POST request\",\n \"collectionId\": \"877b9dae-a50e-4152-9b89-870c37216f78\",\n \"method\": \"POST\",\n \"headers\": \"Content-Type: text/plain\",\n \"dataMode\": \"raw\",\n \"data\": [],\n \"rawModeData\": \"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\",\n \"url\": \"https://postman-echo.com/post\"\n },\n {\n \"id\": \"4511ca8b-0bc7-430f-b894-a7ec1036f322\",\n \"name\": \"A simple POST request with JSON body\",\n \"collectionId\": \"877b9dae-a50e-4152-9b89-870c37216f78\",\n \"method\": \"POST\",\n \"headers\": \"Content-Type: application/json\",\n \"dataMode\": \"raw\",\n \"data\": [],\n \"rawModeData\": \"{\\\"text\\\":\\\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\\\"}\",\n \"url\": \"https://postman-echo.com/post\"\n }\n ]\n}" + }, + "url": { + "raw": "https://postman-echo.com/transform/collection?from=1&to=2", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "transform", + "collection" + ], + "query": [ + { + "key": "from", + "value": "1" + }, + { + "key": "to", + "value": "2" + } + ] + }, + "description": "" + }, + "response": [ + { + "id": "486bbef6-400f-4b21-a1c6-e2f1d228471b", + "name": "Sample v2 Response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "enabled": true, + "description": "The mime type of this content", + "disabled": false + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": \"7875be4b-917d-4aff-8cc4-5606c36bf418\",\n \"name\": \"Sample Postman Collection\",\n \"description\": \"A sample collection to demonstrate collections as a set of related requests\",\n \"order\": [\n \"4d9134be-e8bf-4693-9cd7-1c0fc66ae739\",\n \"141ba274-cc50-4377-a59c-e080066f375e\",\n \"4511ca8b-0bc7-430f-b894-a7ec1036f322\"\n ],\n \"folders\": [],\n \"requests\": [\n {\n \"id\": \"4d9134be-e8bf-4693-9cd7-1c0fc66ae739\",\n \"name\": \"A simple GET request\",\n \"collectionId\": \"877b9dae-a50e-4152-9b89-870c37216f78\",\n \"method\": \"GET\",\n \"headers\": \"\",\n \"data\": [],\n \"rawModeData\": \"\",\n \"tests\": \"tests['response code is 200'] = (responseCode.code === 200);\",\n \"preRequestScript\": \"\",\n \"url\": \"https://postman-echo.com/get?source=newman-sample-github-collection\"\n },\n {\n \"id\": \"141ba274-cc50-4377-a59c-e080066f375e\",\n \"name\": \"A simple POST request\",\n \"collectionId\": \"877b9dae-a50e-4152-9b89-870c37216f78\",\n \"method\": \"POST\",\n \"headers\": \"Content-Type: text/plain\",\n \"dataMode\": \"raw\",\n \"data\": [],\n \"rawModeData\": \"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\",\n \"url\": \"https://postman-echo.com/post\"\n },\n {\n \"id\": \"4511ca8b-0bc7-430f-b894-a7ec1036f322\",\n \"name\": \"A simple POST request with JSON body\",\n \"collectionId\": \"877b9dae-a50e-4152-9b89-870c37216f78\",\n \"method\": \"POST\",\n \"headers\": \"Content-Type: application/json\",\n \"dataMode\": \"raw\",\n \"data\": [],\n \"rawModeData\": \"{\\\"text\\\":\\\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\\\"}\",\n \"url\": \"https://postman-echo.com/post\"\n }\n ]\n}" + }, + "url": { + "raw": "https://postman-echo.com/transform/collection?from=1&to=2", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "transform", + "collection" + ], + "query": [ + { + "key": "from", + "value": "1" + }, + { + "key": "to", + "value": "2" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "_postman_previewtype": "text", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "", + "name": "Access-Control-Allow-Credentials", + "description": "" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "", + "name": "Access-Control-Allow-Headers", + "description": "" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "", + "name": "Access-Control-Allow-Methods", + "description": "" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "", + "name": "Access-Control-Allow-Origin", + "description": "" + }, + { + "key": "Access-Control-Expose-Headers", + "value": "", + "name": "Access-Control-Expose-Headers", + "description": "" + }, + { + "key": "Connection", + "value": "keep-alive", + "name": "Connection", + "description": "" + }, + { + "key": "Content-Encoding", + "value": "gzip", + "name": "Content-Encoding", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "name": "Content-Type", + "description": "" + }, + { + "key": "Date", + "value": "Wed, 11 Jan 2017 10:41:32 GMT", + "name": "Date", + "description": "" + }, + { + "key": "ETag", + "value": "W/\"4cc-7P727Clhlrl9+b1/vneniw\"", + "name": "ETag", + "description": "" + }, + { + "key": "Server", + "value": "nginx/1.10.1", + "name": "Server", + "description": "" + }, + { + "key": "Vary", + "value": "X-HTTP-Method-Override, Accept-Encoding", + "name": "Vary", + "description": "" + }, + { + "key": "set-cookie", + "value": "sails.sid=s%3AHtnQ1hlPxoj7wZahoNkcjN-aw9nQL0fc.KSyfLbEKhv1Lt3LvH13Ogjv9ENZgsBBSM6V8Y7TqVOU; Path=/; HttpOnly", + "name": "set-cookie", + "description": "" + }, + { + "key": "transfer-encoding", + "value": "chunked", + "name": "transfer-encoding", + "description": "" + } + ], + "cookie": [ + { + "expires": "Mon Jan 18 2038 22:44:07 GMT+0100 (CET)", + "httpOnly": true, + "domain": "postman-echo.com", + "path": "/", + "secure": false, + "value": "s%3AHtnQ1hlPxoj7wZahoNkcjN-aw9nQL0fc.KSyfLbEKhv1Lt3LvH13Ogjv9ENZgsBBSM6V8Y7TqVOU", + "key": "sails.sid" + } + ], + "responseTime": "920", + "body": "{\"variables\":[],\"info\":{\"name\":\"Sample Postman Collection\",\"_postman_id\":\"7875be4b-917d-4aff-8cc4-5606c36bf418\",\"description\":\"A sample collection to demonstrate collections as a set of related requests\",\"schema\":\"https://schema.getpostman.com/json/collection/v2.0.0/collection.json\"},\"item\":[{\"name\":\"A simple GET request\",\"event\":[{\"listen\":\"test\",\"script\":{\"type\":\"text/javascript\",\"exec\":[\"tests['response code is 200'] = (responseCode.code === 200);\"]}}],\"request\":{\"url\":\"https://postman-echo.com/get?source=newman-sample-github-collection\",\"method\":\"GET\",\"header\":[],\"body\":{\"mode\":\"raw\",\"raw\":\"\"}},\"response\":[]},{\"name\":\"A simple POST request\",\"request\":{\"url\":\"https://postman-echo.com/post\",\"method\":\"POST\",\"header\":[{\"key\":\"Content-Type\",\"value\":\"text/plain\",\"description\":\"\"}],\"body\":{\"mode\":\"raw\",\"raw\":\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\"}},\"response\":[]},{\"name\":\"A simple POST request with JSON body\",\"request\":{\"url\":\"https://postman-echo.com/post\",\"method\":\"POST\",\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json\",\"description\":\"\"}],\"body\":{\"mode\":\"raw\",\"raw\":\"{\\\"text\\\":\\\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\\\"}\"}},\"response\":[]}]}" + } + ] + }, + { + "name": "Transform collection from format v2 to v1", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"info\": {\n \"name\": \"Sample Postman Collection\",\n \"schema\": \"https://schema.getpostman.com/json/collection/v2.0.0/collection.json\",\n \"description\": \"A sample collection to demonstrate collections as a set of related requests\"\n },\n\n \"item\": [{\n \"name\": \"A simple GET request\",\n \"event\": [{\n \"listen\": \"test\",\n \"script\": {\n \"type\": \"text/javascript\",\n \"exec\": [\"tests['response code is 200'] = (responseCode.code === 200);\"]\n }\n }],\n \"request\": {\n \"url\": \"https://postman-echo.com/get?source=newman-sample-github-collection\",\n \"method\": \"GET\"\n }\n }, {\n \"name\": \"A simple POST request\",\n \"request\": {\n \"url\": \"https://postman-echo.com/post\",\n \"method\": \"POST\",\n \"header\": [{\n \"key\": \"Content-Type\",\n \"value\": \"text/plain\"\n }],\n \"body\": {\n \"mode\": \"raw\",\n \"raw\": \"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\"\n }\n }\n }, {\n \"name\": \"A simple POST request with JSON body\",\n \"request\": {\n \"url\": \"https://postman-echo.com/post\",\n \"method\": \"POST\",\n \"header\": [{\n \"key\": \"Content-Type\",\n \"value\": \"application/json\"\n }],\n \"body\": {\n \"mode\": \"raw\",\n \"raw\": \"{\\\"text\\\":\\\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\\\"}\"\n }\n }\n }]\n}" + }, + "url": { + "raw": "https://postman-echo.com/transform/collection?from=2&to=1", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "transform", + "collection" + ], + "query": [ + { + "key": "from", + "value": "2" + }, + { + "key": "to", + "value": "1" + } + ] + }, + "description": "" + }, + "response": [ + { + "id": "6adadb21-0825-4740-a051-f316e0653b48", + "name": "Sample v1 Response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "enabled": true, + "description": "The mime type of this content", + "disabled": false + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"info\": {\n \"name\": \"Sample Postman Collection\",\n \"schema\": \"https://schema.getpostman.com/json/collection/v2.0.0/collection.json\",\n \"description\": \"A sample collection to demonstrate collections as a set of related requests\"\n },\n\n \"item\": [{\n \"name\": \"A simple GET request\",\n \"event\": [{\n \"listen\": \"test\",\n \"script\": {\n \"type\": \"text/javascript\",\n \"exec\": [\"tests['response code is 200'] = (responseCode.code === 200);\"]\n }\n }],\n \"request\": {\n \"url\": \"https://postman-echo.com/get?source=newman-sample-github-collection\",\n \"method\": \"GET\"\n }\n }, {\n \"name\": \"A simple POST request\",\n \"request\": {\n \"url\": \"https://postman-echo.com/post\",\n \"method\": \"POST\",\n \"header\": [{\n \"key\": \"Content-Type\",\n \"value\": \"text/plain\"\n }],\n \"body\": {\n \"mode\": \"raw\",\n \"raw\": \"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\"\n }\n }\n }, {\n \"name\": \"A simple POST request with JSON body\",\n \"request\": {\n \"url\": \"https://postman-echo.com/post\",\n \"method\": \"POST\",\n \"header\": [{\n \"key\": \"Content-Type\",\n \"value\": \"application/json\"\n }],\n \"body\": {\n \"mode\": \"raw\",\n \"raw\": \"{\\\"text\\\":\\\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\\\"}\"\n }\n }\n }]\n}" + }, + "url": { + "raw": "https://postman-echo.com/transform/collection?from=2&to=1", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "transform", + "collection" + ], + "query": [ + { + "key": "from", + "value": "2" + }, + { + "key": "to", + "value": "1" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "_postman_previewtype": "text", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "", + "name": "Access-Control-Allow-Credentials", + "description": "" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "", + "name": "Access-Control-Allow-Headers", + "description": "" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "", + "name": "Access-Control-Allow-Methods", + "description": "" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "", + "name": "Access-Control-Allow-Origin", + "description": "" + }, + { + "key": "Access-Control-Expose-Headers", + "value": "", + "name": "Access-Control-Expose-Headers", + "description": "" + }, + { + "key": "Connection", + "value": "keep-alive", + "name": "Connection", + "description": "" + }, + { + "key": "Content-Encoding", + "value": "gzip", + "name": "Content-Encoding", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "name": "Content-Type", + "description": "" + }, + { + "key": "Date", + "value": "Wed, 11 Jan 2017 10:38:42 GMT", + "name": "Date", + "description": "" + }, + { + "key": "ETag", + "value": "W/\"569-P9uLZEIyoPfMmQ+U0mTO1A\"", + "name": "ETag", + "description": "" + }, + { + "key": "Server", + "value": "nginx/1.10.1", + "name": "Server", + "description": "" + }, + { + "key": "Vary", + "value": "X-HTTP-Method-Override, Accept-Encoding", + "name": "Vary", + "description": "" + }, + { + "key": "set-cookie", + "value": "sails.sid=s%3A55y5Ll7HpTzt_hKuw6N54k4N04ilmMdn.uCPCHttP5DmI%2BdBw2I9NZL55lFFOzz4XxS4qAHv47gI; Path=/; HttpOnly", + "name": "set-cookie", + "description": "" + }, + { + "key": "transfer-encoding", + "value": "chunked", + "name": "transfer-encoding", + "description": "" + } + ], + "cookie": [ + { + "expires": "Mon Jan 18 2038 22:44:07 GMT+0100 (CET)", + "httpOnly": true, + "domain": "postman-echo.com", + "path": "/", + "secure": false, + "value": "s%3A55y5Ll7HpTzt_hKuw6N54k4N04ilmMdn.uCPCHttP5DmI%2BdBw2I9NZL55lFFOzz4XxS4qAHv47gI", + "key": "sails.sid" + } + ], + "responseTime": "276", + "body": "{\"id\":\"0c42230c-c8e4-4ca0-a4aa-d393971de8b8\",\"name\":\"Sample Postman Collection\",\"description\":\"A sample collection to demonstrate collections as a set of related requests\",\"order\":[\"3d04ed83-dc1e-40ec-923c-16aa92509e50\",\"e02f8160-fb41-4633-be80-cc7d701e85b4\",\"77bd6d4d-1060-4927-aa5c-dcdba7f750cf\"],\"folders\":[],\"requests\":[{\"id\":\"3d04ed83-dc1e-40ec-923c-16aa92509e50\",\"name\":\"A simple GET request\",\"collectionId\":\"1dd68aff-a3fa-4f52-904f-5b75053bc9d9\",\"method\":\"GET\",\"headers\":\"\",\"data\":[],\"rawModeData\":\"\",\"tests\":\"tests['response code is 200'] = (responseCode.code === 200);\",\"preRequestScript\":\"\",\"url\":\"https://postman-echo.com/get?source=newman-sample-github-collection\"},{\"id\":\"e02f8160-fb41-4633-be80-cc7d701e85b4\",\"name\":\"A simple POST request\",\"collectionId\":\"1dd68aff-a3fa-4f52-904f-5b75053bc9d9\",\"method\":\"POST\",\"headers\":\"Content-Type: text/plain\",\"dataMode\":\"raw\",\"data\":[],\"rawModeData\":\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\",\"url\":\"https://postman-echo.com/post\"},{\"id\":\"77bd6d4d-1060-4927-aa5c-dcdba7f750cf\",\"name\":\"A simple POST request with JSON body\",\"collectionId\":\"1dd68aff-a3fa-4f52-904f-5b75053bc9d9\",\"method\":\"POST\",\"headers\":\"Content-Type: application/json\",\"dataMode\":\"raw\",\"data\":[],\"rawModeData\":\"{\\\"text\\\":\\\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\\\"}\",\"url\":\"https://postman-echo.com/post\"}]}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index b1cec5e5..04773ec2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -32,7 +32,7 @@ var cfgFile string var RootCmd = &cobra.Command{ Use: "docktor", Short: "Administration & Monitoring Deployment with Docker", - Long: `Docktor is a web application which aims to make the deployment of docke services easier + Long: `Docktor is a web application which aims to make the deployment of docker services easier. With it, you can manage several daemons, services and group. Each service can be deployed on a daemon for a group. `, diff --git a/cmd/serve.go b/cmd/serve.go index ead8ecf4..0d65bc4e 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -31,7 +31,7 @@ func init() { serveCmd.Flags().StringP("jwt-secret", "j", "dev-docktor-secret", "Secret key used for JWT token authentication. Change it in your instance") serveCmd.Flags().StringP("reset-pwd-secret", "", "dev-docktor-reset-pwd-to-change", "Secret key used when resetting the password. Change it in your instance") serveCmd.Flags().StringP("bcrypt-pepper", "p", "dev-docktor-bcrypt", "Pepper used in password generation. Change it in your instance") - serveCmd.Flags().StringP("env", "e", "prod", "dev or prod") + serveCmd.Flags().StringP("env", "e", "dev", "dev or prod") serveCmd.Flags().String("encrypt-secret", "encrypt-secret-to-change", "Secret for sensible data encryption. Change it in your instance") serveCmd.Flags().String("ldap-address", "", "LDAP full address like : ldap.server:389. Optional") serveCmd.Flags().String("ldap-baseDN", "", "BaseDN. Optional") diff --git a/gometalinter.json b/gometalinter.json index 75a31168..079c6bf2 100644 --- a/gometalinter.json +++ b/gometalinter.json @@ -12,7 +12,7 @@ "vetshadow", "gas", "varcheck", - "aligncheck", + "maligned", "interfacer" ], "Cyclo": 15, diff --git a/newman-docker b/newman-docker new file mode 160000 index 00000000..b28f5a8b --- /dev/null +++ b/newman-docker @@ -0,0 +1 @@ +Subproject commit b28f5a8b74ae7cfbb568d001c7bd9be988042605 diff --git a/npm-27139-9e3ad190/registry.npmjs.org/cli-table2/-/cli-table2-0.2.0.tgz.489355601 b/npm-27139-9e3ad190/registry.npmjs.org/cli-table2/-/cli-table2-0.2.0.tgz.489355601 new file mode 100644 index 00000000..e69de29b diff --git a/package.json b/package.json index 0913cd55..845be15a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "François Samin - fsamin", "Gwendal Leclerc - gwleclerc", "Mathieu Cornic - macornic", - "Thibaut Rousseau - Thiht" + "Thibaut Rousseau - Thiht", + "Quentin Foucault - qfoucault" ], "repository": { "type": "git", @@ -73,8 +74,8 @@ "sass-loader": "^6.0.6", "source-map-loader": "^0.2.1", "style-loader": "^0.18.2", - "webpack": "^3.5.5", - "webpack-dev-server": "^2.7.1" + "webpack": "^3.7.1", + "webpack-dev-server": "^2.9.1" }, "dependencies": { "classnames": "^2.2.5", diff --git a/server/controllers/auth.go b/server/controllers/auth.go index efe917a8..79775e51 100644 --- a/server/controllers/auth.go +++ b/server/controllers/auth.go @@ -148,8 +148,8 @@ func (a *Auth) Login(c echo.Context) error { return c.JSON(http.StatusOK, Token{ID: token, User: user}) } -//ResetPassword handles resets the password of someone -//When user reset his password, it generates an email to this person with a link to change his. +// ResetPassword handles resets the password of someone +// When user reset his password, it generates an email to this person with a link to change his. // The password is not reset in database because someone with bad intentions could use this feature to prevent someone else to login. func (a *Auth) ResetPassword(c echo.Context) error { // Get input parameters @@ -189,8 +189,8 @@ func (a *Auth) ResetPassword(c echo.Context) error { return c.JSON(http.StatusOK, "OK") } -//ChangeResetPassword changes the password of someone that reset it before -//When user changes the password, he's automatically connected +// ChangeResetPassword changes the password of someone that reset it before +// When user changes the password, he's automatically connected func (a *Auth) ChangeResetPassword(c echo.Context) error { // Get input parameters diff --git a/server/controllers/catalogServices.go b/server/controllers/catalogServices.go new file mode 100644 index 00000000..32a95a26 --- /dev/null +++ b/server/controllers/catalogServices.go @@ -0,0 +1,135 @@ +package controllers + +import ( + "fmt" + "net/http" + "time" + + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" + "github.com/soprasteria/docktor/server/storage" + "github.com/soprasteria/docktor/server/types" + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// CatalogServices contains all services handlers +type CatalogServices struct { +} + +// GetAll catalogServices from docktor +func (s *CatalogServices) GetAll(c echo.Context) error { + docktorAPI := c.Get("api").(*storage.Docktor) + catalogServices, err := docktorAPI.CatalogServices().FindAll() + if err != nil { + log.WithError(err).Error("Unable to get all catalogServices") + return c.String(http.StatusInternalServerError, fmt.Sprintf("Unable to get all daemons because of technical error: %v. Retry later.", err)) + } + return c.JSON(http.StatusOK, catalogServices) +} + +// Save catalogService into docktor +func (s *CatalogServices) Save(c echo.Context) error { + docktorAPI := c.Get("api").(*storage.Docktor) + var catalogService types.CatalogService + if err := c.Bind(&catalogService); err != nil { + log.WithError(err).Error("Unable to bind catalogService to save") + return c.String(http.StatusBadRequest, "Unable to parse catalogService received from client") + } + + // If the ID is empty, it's a creation, so generate an object ID + id := c.Param("catalogServiceID") + if id == "" { + // New catalogService to create + catalogService.ID = bson.NewObjectId() + catalogService.Created = time.Now() + } else { + // Existing catalogService + catalogService.ID = bson.ObjectIdHex(id) + s, err := docktorAPI.CatalogServices().FindByIDBson(catalogService.ID) + if err != nil { + if err == mgo.ErrNotFound { + log.WithError(err).Warnf("Tried to save a catalogService that does not exist: %v", catalogService.ID) + return c.String(http.StatusBadRequest, "CatalogService does not exist") + } + log.WithError(err).Errorf("Unable to find catalogService because of unexpected error : %v", catalogService.ID) + return c.String(http.StatusInternalServerError, "Unable to find catalogService because of technical error. Retry later.") + } + catalogService.Created = s.Created + } + + // Validate fields from validator tags for common types + if err := c.Validate(catalogService); err != nil { + log.WithError(err).Errorf("Unable to save catalogService %v because some fields are not valid", catalogService.ID) + return c.String(http.StatusBadRequest, fmt.Sprintf("Some fields of catalogService are not valid: %v", err)) + } + + // Validate fields that cIannot be validated by validator engine + if err := catalogService.Validate(); err != nil { + log.WithError(err).Errorf("Unable to save catalogService %v because some fields are not valid", catalogService.ID) + return c.String(http.StatusBadRequest, fmt.Sprintf("Some fields of catalogService are not valid: %v", err)) + } + + // Keep only existing and remove duplicates of external collections + // Used to clean the service of old data when saving + catalogService.Tags = existingTags(docktorAPI, catalogService.Tags) + catalogService.Updated = time.Now() + + res, err := docktorAPI.CatalogServices().Save(catalogService) + if err != nil { + log.WithError(err).Errorf("Unexpected error when saving catalogService %v", catalogService.ID) + return c.String(http.StatusInternalServerError, fmt.Sprintf("Unable to save catalogService because of technical error: %v. Retry later.", err)) + } + return c.JSON(http.StatusOK, res) +} + +// existingCatalogServices return catalogServices filtered by existing ones +// Checks wether the Services actually exists in database +func existingCatalogServices(docktorAPI *storage.Docktor, catalogServicesIDs []bson.ObjectId) []bson.ObjectId { + + existingCatalogServiceIDs := []bson.ObjectId{} + + // Get all real groups + existingcatalogServices, _ := docktorAPI.CatalogServices().FindAllByIDs(catalogServicesIDs) + + // Get their ids only + for _, s := range existingcatalogServices { + existingCatalogServiceIDs = append(existingCatalogServiceIDs, s.ID) + } + + return existingCatalogServiceIDs +} + +// Delete catalogServices into docktor +func (s *CatalogServices) Delete(c echo.Context) error { + docktorAPI := c.Get("api").(*storage.Docktor) + id := c.Param("catalogServiceID") + + res, err := docktorAPI.CatalogServices().Delete(bson.ObjectIdHex(id)) + if err != nil { + log.WithError(err).Errorf("Unexpected error when deleting catalogService %v", id) + return c.String(http.StatusInternalServerError, fmt.Sprintf("Unable to delete catalogService because of technical error: %v. Retry later.", err)) + } + return c.String(http.StatusOK, res.Hex()) +} + +// Get get a catalogService from docktor by its ID +func (s *CatalogServices) Get(c echo.Context) error { + catalogService := c.Get("catalogService").(types.CatalogService) + return c.JSON(http.StatusOK, catalogService) +} + +// GetTags get all tags from a given catalogsService +// It is able to get tags from sub entities (like containers and services if needed) +func (s *CatalogServices) GetTags(c echo.Context) error { + catalogService := c.Get("catalogService").(types.CatalogService) + docktorAPI := c.Get("api").(*storage.Docktor) + tagIds := catalogService.Tags + + tags, err := docktorAPI.Tags().FindAllByIDs(tagIds) + if err != nil { + log.WithError(err).WithField("catalogService", catalogService.ID).Error("Can't get tags of catalogService") + return c.JSON(http.StatusInternalServerError, "Incorrect data. Contact your administrator") + } + return c.JSON(http.StatusOK, tags) +} diff --git a/server/controllers/catalogServices/constants.go b/server/controllers/catalogServices/constants.go new file mode 100644 index 00000000..6ba299a0 --- /dev/null +++ b/server/controllers/catalogServices/constants.go @@ -0,0 +1,9 @@ +package catalogServices + +const ( + // CatalogServiceNotFound : error while retreiving services + CatalogServiceNotFound string = "Cannot find catalog services %s" + + // CatalogServiceInvalidID : ID is not set in url or is invalid + CatalogServiceInvalidID string = "Invalid catalog services ID" +) diff --git a/server/controllers/catalogServices/middlewares.go b/server/controllers/catalogServices/middlewares.go new file mode 100644 index 00000000..d6bf1aa7 --- /dev/null +++ b/server/controllers/catalogServices/middlewares.go @@ -0,0 +1,28 @@ +package catalogServices + +import ( + "fmt" + "net/http" + + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" + "github.com/soprasteria/docktor/server/storage" +) + +// RetrieveCatalogService find catalogService using id param and put it in echo.Context +func RetrieveCatalogService(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + docktorAPI := c.Get("api").(*storage.Docktor) + catalogServiceID := c.Param("catalogServiceID") + if catalogServiceID == "" { + return c.String(http.StatusBadRequest, CatalogServiceInvalidID) + } + catalogService, err := docktorAPI.CatalogServices().FindByID(catalogServiceID) + if err != nil || catalogService.ID.Hex() == "" { + log.WithField("catalogService", catalogServiceID).Error("Unable to fetch catalogService") + return c.String(http.StatusNotFound, fmt.Sprintf(CatalogServiceNotFound, catalogServiceID)) + } + c.Set("catalogService", catalogService) + return next(c) + } +} diff --git a/server/controllers/daemons.go b/server/controllers/daemons.go index 740d857b..06641d9c 100644 --- a/server/controllers/daemons.go +++ b/server/controllers/daemons.go @@ -38,7 +38,7 @@ func (d *Daemons) GetAll(c echo.Context) error { return c.JSON(http.StatusOK, docktorDaemons) } -//Save daemon into docktor +// Save daemon into docktor func (d *Daemons) Save(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) var daemon types.Daemon @@ -125,7 +125,7 @@ func (d *Daemons) Save(c echo.Context) error { return c.JSON(http.StatusOK, res) } -//Delete daemon into docktor +// Delete daemon into docktor func (d *Daemons) Delete(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) id := c.Param("daemonID") @@ -149,7 +149,7 @@ func (d *Daemons) Delete(c echo.Context) error { return c.String(http.StatusOK, res.Hex()) } -//Get daemon from docktor +// Get daemon from docktor func (d *Daemons) Get(c echo.Context) error { daemon := c.Get("daemon").(types.Daemon) authenticatedUser, err := getUserFromToken(c) diff --git a/server/controllers/export.go b/server/controllers/export.go index 6224c4d5..f0085f40 100644 --- a/server/controllers/export.go +++ b/server/controllers/export.go @@ -16,7 +16,7 @@ import ( type Export struct { } -//ExportAll exports all the data as a file +// ExportAll exports all the data as a file func (a *Export) ExportAll(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) exporter := export.Export{Docktor: docktorAPI} diff --git a/server/controllers/groups.go b/server/controllers/groups.go index b856bf7f..b6c8eaaa 100644 --- a/server/controllers/groups.go +++ b/server/controllers/groups.go @@ -19,7 +19,7 @@ import ( type Groups struct { } -//GetAll groups from docktor +// GetAll groups from docktor func (g *Groups) GetAll(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) groups, err := docktorAPI.Groups().FindAll() @@ -30,7 +30,7 @@ func (g *Groups) GetAll(c echo.Context) error { return c.JSON(http.StatusOK, groups) } -//Save group into docktor +// Save group into docktor func (g *Groups) Save(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) var group types.Group @@ -151,7 +151,7 @@ func existingFileSystems(docktorAPI *storage.Docktor, fileSystems types.FileSyst return existingFileSystems } -//Delete group into docktor +// Delete group into docktor func (g *Groups) Delete(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) id := c.Param("groupID") @@ -166,7 +166,7 @@ func (g *Groups) Delete(c echo.Context) error { return c.String(http.StatusOK, res.Hex()) } -//Get group from docktor +// Get group from docktor func (g *Groups) Get(c echo.Context) error { group := c.Get("group").(types.Group) return c.JSON(http.StatusOK, group) @@ -175,36 +175,38 @@ func (g *Groups) Get(c echo.Context) error { // GetTags get all tags from a given group // It is able to get get tags from sub entities (like containers and services if needed) func (g *Groups) GetTags(c echo.Context) error { - // withServices, _ := strconv.ParseBool(c.QueryParam("services")) // Get all tags from a given daemon - // withcontainers, _ := strconv.ParseBool(c.QueryParam("containers")) // Get all tags from a given Users + //withServices, _ := strconv.ParseBool(c.QueryParam("services")) // Get all tags from a given daemon + //withcontainers, _ := strconv.ParseBool(c.QueryParam("containers")) // Get all tags from a given Users group := c.Get("group").(types.Group) docktorAPI := c.Get("api").(*storage.Docktor) tagIds := group.Tags - // TODO : enable it when containers and services are used again. - // Get also tags from container instances of group - // if withcontainers { - // for _, c := range group.Containers { - // tagIds = append(tagIds, c.Tags...) - // } - // } - // // Get also tags from the type of containers (= service) - // if withServices { - // var serviceIds []bson.ObjectId - // // Get services from containers - // for _, c := range group.Containers { - // serviceIds = append(serviceIds, c.ServiceID) - // } - // services, err := docktorAPI.Services().FindAllByIDs(serviceIds) - // if err != nil { - // log.WithError(err).WithField("group", group.ID).WithField("services.ids", serviceIds).Error("Can't get tags of service") - // return c.JSON(http.StatusInternalServerError, "Incorrect data. Contact your administrator") - // } - // // Get tags from services - // for _, s := range services { - // tagIds = append(tagIds, s.Tags...) - // } - // } + /* + // TODO : enable it when containers and services are used again. + // Get also tags from container instances of group + if withcontainers { + for _, c := range group.Containers { + tagIds = append(tagIds, c.Tags...) + } + } + //Get also tags from the type of containers (= service) + if withServices { + var serviceIds []bson.ObjectId + // Get services from containers + for _, c := range group.Containers { + serviceIds = append(serviceIds, c.ServiceID) + } + services, err := docktorAPI.CatalogServices().FindAllByIDs(serviceIds) + if err != nil { + log.WithError(err).WithField("group", group.ID).WithField("services.ids", serviceIds).Error("Can't get tags of service") + return c.JSON(http.StatusInternalServerError, "Incorrect data. Contact your administrator") + } + // Get tags from services + for _, s := range services { + tagIds = append(tagIds, s.Tags...) + } + } + */ tags, err := docktorAPI.Tags().FindAllByIDs(tagIds) if err != nil { @@ -243,10 +245,12 @@ func (g *Groups) GetDaemons(c echo.Context) error { daemonIds = append(daemonIds, fs.Daemon) } - // TODO : enable it when containers and services are used again. - // for _, c := range group.Containers { - // daemonIds = append(daemonIds, c.DaemonID) - // } + /* + // TODO : enable it when containers and services are used again. + for _, c := range group.Containers { + daemonIds = append(daemonIds, c.DaemonID) + } + */ ds, err := docktorAPI.Daemons().FindAllByIDs(daemonIds) if err != nil { diff --git a/server/controllers/sites.go b/server/controllers/sites.go index 6121c54f..3f7242e1 100644 --- a/server/controllers/sites.go +++ b/server/controllers/sites.go @@ -19,7 +19,7 @@ import ( type Sites struct { } -//GetAll sites from docktor +// GetAll sites from docktor func (s *Sites) GetAll(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) sites, err := docktorAPI.Sites().FindAll() @@ -30,7 +30,7 @@ func (s *Sites) GetAll(c echo.Context) error { return c.JSON(http.StatusOK, sites) } -//Save site into docktor +// Save site into docktor func (s *Sites) Save(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) var site types.Site @@ -78,7 +78,7 @@ func (s *Sites) Save(c echo.Context) error { } -//Delete site into docktor +// Delete site into docktor func (s *Sites) Delete(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) id := c.Param("siteID") diff --git a/server/controllers/tags.go b/server/controllers/tags.go index 99ff50b4..c0ea2b08 100644 --- a/server/controllers/tags.go +++ b/server/controllers/tags.go @@ -20,7 +20,7 @@ const TagAlreadyExistErrMessage string = "Tag %q already exists in category %q" type Tags struct { } -//GetAll tags from docktor +// GetAll tags from docktor func (s *Tags) GetAll(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) tags, err := docktorAPI.Tags().FindAll() @@ -31,7 +31,7 @@ func (s *Tags) GetAll(c echo.Context) error { return c.JSON(http.StatusOK, tags) } -//Save or update tag into docktor +// Save or update tag into docktor func (s *Tags) Save(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) @@ -94,7 +94,7 @@ func (s *Tags) Save(c echo.Context) error { return c.JSON(http.StatusOK, res) } -//Delete tag into docktor +// Delete tag into docktor func (s *Tags) Delete(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) @@ -104,7 +104,7 @@ func (s *Tags) Delete(c echo.Context) error { docktorAPI.Daemons(), docktorAPI.Users(), docktorAPI.Groups(), - // TODO : add others collections (groups, services ...) + docktorAPI.CatalogServices(), } // Remove tags from all collections containings tags diff --git a/server/controllers/users.go b/server/controllers/users.go index 0ef19e2a..57c6942b 100644 --- a/server/controllers/users.go +++ b/server/controllers/users.go @@ -18,7 +18,7 @@ import ( type Users struct { } -//GetAll users from docktor +// GetAll users from docktor func (u *Users) GetAll(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) webservice := users.Rest{Docktor: docktorAPI} @@ -106,7 +106,7 @@ func (u *Users) Update(c echo.Context) error { return c.JSON(http.StatusOK, res) } -//Delete user into docktor +// Delete user into docktor func (u *Users) Delete(c echo.Context) error { docktorAPI := c.Get("api").(*storage.Docktor) id := c.Param("userID") @@ -196,7 +196,7 @@ func (u *Users) Profile(c echo.Context) error { return c.JSON(http.StatusOK, user) } -//Get user from docktor +// Get user from docktor func (u *Users) Get(c echo.Context) error { // No access control on purpose user := c.Get("user").(users.UserRest) diff --git a/server/server.go b/server/server.go index aebafdf5..54450bed 100644 --- a/server/server.go +++ b/server/server.go @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/middleware" "github.com/soprasteria/docktor/server/controllers" "github.com/soprasteria/docktor/server/controllers/auth" + "github.com/soprasteria/docktor/server/controllers/catalogServices" "github.com/soprasteria/docktor/server/controllers/daemons" "github.com/soprasteria/docktor/server/controllers/groups" "github.com/soprasteria/docktor/server/controllers/users" @@ -39,6 +40,7 @@ func New() { usersC := controllers.Users{} authC := controllers.Auth{} exportC := controllers.Export{} + catalogServicesC := controllers.CatalogServices{} engine.Use(middleware.Logger()) engine.Use(middleware.Recover()) @@ -127,6 +129,20 @@ func New() { } } + catalogServicesAPI := api.Group("/catalogServices") + { + catalogServicesAPI.GET("", catalogServicesC.GetAll) + catalogServicesAPI.POST("/new", catalogServicesC.Save, hasRole(types.AdminRole)) + catalogServiceAPI := catalogServicesAPI.Group("/:catalogServiceID") + { + catalogServiceAPI.Use(isValidID("catalogServiceID")) + catalogServiceAPI.GET("", catalogServicesC.Get, catalogServices.RetrieveCatalogService) + catalogServiceAPI.GET("/tags", catalogServicesC.GetTags, catalogServices.RetrieveCatalogService) + catalogServiceAPI.DELETE("", catalogServicesC.Delete, hasRole(types.AdminRole)) + catalogServiceAPI.PUT("", catalogServicesC.Save) + } + } + usersAPI := api.Group("/users") { // No "isAdmin" middleware on users because users can delete/modify themselves diff --git a/server/storage/catalogServices.go b/server/storage/catalogServices.go new file mode 100644 index 00000000..dc0d407a --- /dev/null +++ b/server/storage/catalogServices.go @@ -0,0 +1,144 @@ +package storage + +import ( + "fmt" + + "github.com/soprasteria/docktor/server/types" + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// CatalogServicesRepo is the repo for CatalogService +type CatalogServicesRepo interface { + //=========== + // CatalogServices + //=========== + + // Drop drops the content of the collection + Drop() error + + // Save a CatalogService into database + Save(catalogService types.CatalogService) (types.CatalogService, error) + + // Delete a CatalogService in database + Delete(id bson.ObjectId) (bson.ObjectId, error) + + // FindByID get the CatalogService by its id + FindByID(id string) (types.CatalogService, error) + + // FindByIDBson get the CatalogService by its id + FindByIDBson(id bson.ObjectId) (types.CatalogService, error) + + // Find get the first CatalogService with a given name + Find(name string) (types.CatalogService, error) + + // FindAll get all CatalogServices + FindAll() ([]types.CatalogService, error) + + // FindAllByName get all CatalogServices by the give name + FindAllByName(name string) ([]types.CatalogService, error) + + // FindAllByIDs get all CatalogServices from their ids + FindAllByIDs(ids []bson.ObjectId) ([]types.CatalogService, error) + + // GetCollectionName returns the name of the collection + GetCollectionName() string + + // RemoveTag + RemoveTag(id bson.ObjectId) (*mgo.ChangeInfo, error) +} + +// DefaultCatalogServicesRepo is the repository for catalogServices +type DefaultCatalogServicesRepo struct { + coll *mgo.Collection +} + +// NewCatalogServicesRepo instantiate new CatalogServicesRepo +func NewCatalogServicesRepo(coll *mgo.Collection) CatalogServicesRepo { + return &DefaultCatalogServicesRepo{coll: coll} +} + +// GetCollectionName gets the name of the collection +func (r *DefaultCatalogServicesRepo) GetCollectionName() string { + return r.coll.FullName +} + +// CreateIndexes creates Index +func (r *DefaultCatalogServicesRepo) CreateIndexes() error { + return r.coll.EnsureIndex(mgo.Index{ + Key: []string{"title"}, + Unique: true, + Name: "catalogService_title_unique", + }) +} + +// Drop drops the content of the collection +func (r *DefaultCatalogServicesRepo) Drop() error { + return r.coll.DropCollection() +} + +// Save a catalogService into a database +func (r *DefaultCatalogServicesRepo) Save(catalogService types.CatalogService) (types.CatalogService, error) { + newCatalogService := types.NewCatalogService(catalogService) + _, err := r.coll.UpsertId(catalogService.ID, bson.M{"$set": types.NewCatalogService(catalogService)}) + if mgo.IsDup(err) { + return catalogService, fmt.Errorf("Another catalogService exists with name '%v'", catalogService.Name) + } + return newCatalogService, err +} + +// Delete a catalogService in database +func (r *DefaultCatalogServicesRepo) Delete(id bson.ObjectId) (bson.ObjectId, error) { + err := r.coll.RemoveId(id) + return id, err +} + +// Find get the first catalogService with a given name +func (r *DefaultCatalogServicesRepo) Find(name string) (types.CatalogService, error) { + result := types.CatalogService{} + err := r.coll.Find(bson.M{"title": name}).One(&result) + return result, err +} + +// FindByID get the catalogService by its id +func (r *DefaultCatalogServicesRepo) FindByID(id string) (types.CatalogService, error) { + result := types.CatalogService{} + err := r.coll.FindId(bson.ObjectIdHex(id)).One(&result) + return result, err +} + +// FindByIDBson get the catalogService by its id (as a bson object) +func (r *DefaultCatalogServicesRepo) FindByIDBson(id bson.ObjectId) (types.CatalogService, error) { + result := types.CatalogService{} + err := r.coll.FindId(id).One(&result) + return result, err +} + +// FindAll get all catalogServices +func (r *DefaultCatalogServicesRepo) FindAll() ([]types.CatalogService, error) { + results := []types.CatalogService{} + err := r.coll.Find(bson.M{}).All(&results) + return results, err +} + +// FindAllByIDs get all catalogServices from thei ids +func (r *DefaultCatalogServicesRepo) FindAllByIDs(ids []bson.ObjectId) ([]types.CatalogService, error) { + results := []types.CatalogService{} + err := r.coll.Find(bson.M{"_id": bson.M{"$in": ids}}).All(&results) + return results, err +} + +// FindAllByName get all catalogServices by the give name +func (r *DefaultCatalogServicesRepo) FindAllByName(name string) ([]types.CatalogService, error) { + results := []types.CatalogService{} + err := r.coll.Find(bson.M{"title": name}).All(&results) + return results, err +} + +// RemoveTag removes given tag from all users +func (r *DefaultCatalogServicesRepo) RemoveTag(id bson.ObjectId) (*mgo.ChangeInfo, error) { + return r.coll.UpdateAll( + bson.M{"tags": bson.M{"$in": []bson.ObjectId{id}}}, + bson.M{"$pull": bson.M{"tags": id}}, + ) +} diff --git a/server/storage/docktor.go b/server/storage/docktor.go index fb0279c3..4286303f 100644 --- a/server/storage/docktor.go +++ b/server/storage/docktor.go @@ -25,6 +25,7 @@ type Session interface { type Client interface { Collections() []IsCollection Groups() GroupsRepo + CatalogServices() CatalogServicesRepo Daemons() DaemonsRepo Users() UsersRepo Sites() SitesRepo @@ -46,12 +47,13 @@ type IsCollectionWithIndexes interface { // Docktor is the implementation structure to use the API // It contains API accessing to services, jobs, daemons, etc. + the open session type Docktor struct { - session Session - groups GroupsRepo - daemons DaemonsRepo - users UsersRepo - sites SitesRepo - tags TagsRepo + session Session + groups GroupsRepo + catalogServices CatalogServicesRepo + daemons DaemonsRepo + users UsersRepo + sites SitesRepo + tags TagsRepo } type appContext struct { @@ -92,12 +94,13 @@ func Get() (Client, error) { context := appContext{database} return &Docktor{ - groups: NewGroupsRepo(context.db.C("groups")), - daemons: NewDaemonsRepo(context.db.C("daemons")), - users: NewUsersRepo(context.db.C("users")), - sites: NewSitesRepo(context.db.C("sites")), - tags: NewTagsRepo(context.db.C("tags")), - session: s, + groups: NewGroupsRepo(context.db.C("groups")), + catalogServices: NewCatalogServicesRepo(context.db.C("catalogServices")), + daemons: NewDaemonsRepo(context.db.C("daemons")), + users: NewUsersRepo(context.db.C("users")), + sites: NewSitesRepo(context.db.C("sites")), + tags: NewTagsRepo(context.db.C("tags")), + session: s, }, nil } @@ -111,6 +114,11 @@ func (dock *Docktor) Groups() GroupsRepo { return dock.groups } +// CatalogServices is the entrypoint for CatalogServices API +func (dock *Docktor) CatalogServices() CatalogServicesRepo { + return dock.catalogServices +} + // Daemons is the entrypoint for Daemons API func (dock *Docktor) Daemons() DaemonsRepo { return dock.daemons @@ -136,6 +144,7 @@ func (dock *Docktor) Collections() []IsCollection { return []IsCollection{ dock.daemons, dock.groups, + dock.catalogServices, dock.users, dock.sites, dock.tags, diff --git a/server/storage/groups.go b/server/storage/groups.go index cbd3a187..81b40c00 100644 --- a/server/storage/groups.go +++ b/server/storage/groups.go @@ -30,7 +30,7 @@ type GroupsRepo interface { FindAll() ([]types.Group, error) // FindAllByName get all groups by the give name FindAllByName(name string) ([]types.Group, error) - // FindAllByIDs get all groups from thei ids + // FindAllByIDs get all groups from their ids FindAllByIDs(ids []bson.ObjectId) ([]types.Group, error) // FindAllByRegex get all groups by the regex name FindAllByRegex(nameRegex string) ([]types.Group, error) diff --git a/server/types/catalog.go b/server/types/catalog.go index aec229b6..e6ccf30b 100644 --- a/server/types/catalog.go +++ b/server/types/catalog.go @@ -1,6 +1,8 @@ package types import ( + "fmt" + "regexp" "time" "gopkg.in/mgo.v2/bson" @@ -11,6 +13,11 @@ const ( PrimaryContainerType ContainerType = "primary" // SidekickContainerType is the type of container considered as a secondary container of a service (e.g. database of main application) SidekickContainerType ContainerType = "sidekick" + + // catalogService name is used to override user-friendly name of service. + // Meaning it has to be a valid service name. + // Here, it's a alphanum + underscore authorized string with up to 200 characters + catalogServiceNamePattern = `^[a-zA-Z0-9_]{1,200}$` ) // CatatalogTemplate is an archetype to bootstrap a set of services @@ -39,6 +46,44 @@ type CatalogService struct { Updated time.Time `bson:"updated" json:"updated"` } +// NewCatalogService creates new catalogService for another one. +// It helps setting default values, and cleaning duplicates +func NewCatalogService(s CatalogService) CatalogService { + newCatalogService := s + // newCatalogService.Versions = removeDuplicatesVersion(s.Versions) + newCatalogService.Tags = removeDuplicatesTags(s.Tags) + return newCatalogService +} + +/* +// CatalogServiceVersions is a slice of multiple CatalogServiceVersions entities +type CatalogServiceVersions []CatalogServiceVersion + +// removeDuplicatesVersion from a member list +func removeDuplicatesVersion(catalogServiceVersions CatalogServiceVersions) CatalogServiceVersions { + result := CatalogServiceVersions{} + seen := map[string]bool{} + for _, catalogServiceVersion := range catalogServiceVersions { + if _, ok := seen[catalogServiceVersion.Name]; !ok { + result = append(result, catalogServiceVersion) + seen[catalogServiceVersion.Name] = true + } + } + return result +} +*/ + +var catalogServiceNameRegex = regexp.MustCompile(catalogServiceNamePattern) + +// Validate validates semantic of fields in a catalogService (like the name) +func (s CatalogService) Validate() error { + + if !catalogServiceNameRegex.MatchString(s.Name) { + return fmt.Errorf("Name %q does not match regex %q", s.Name, catalogServiceNamePattern) + } + return nil +} + // CatalogServiceVersion defines a version for a service from catalog. It contains the version number and all its metadata // A service version is composed of many containers type CatalogServiceVersion struct { diff --git a/server/types/group.go b/server/types/group.go index 64122b06..70c432e1 100644 --- a/server/types/group.go +++ b/server/types/group.go @@ -174,7 +174,7 @@ func (role MemberRole) IsValid() bool { return role == MemberModeratorRole || role == MemberUserRole } -// Member is user whois subscribed to the groupe. His role in this group defines what he is able to do. +// Member is user who is subscribed to the groupe. His role in this group defines what he is able to do. type Member struct { User bson.ObjectId `bson:"user" json:"user" validate:"required"` Role MemberRole `bson:"role" json:"role" validate:"required"`