diff --git a/README.md b/README.md index 15bd87e..2501fa7 100644 --- a/README.md +++ b/README.md @@ -407,6 +407,14 @@ private static void WebServerStatusChanged(object obj, WebServerStatusEventArgs } ``` +## E2E tests + +There is a collection of postman tests `nanoFramework WebServer E2E Tests.postman_collection.json` in WebServerE2ETests which should be used for testing WebServer in real world scenario. Usage is simple: +- Import json file into Postman +- Deploy WebServerE2ETests to your device - copy IP +- Set the `base_url` variable to match your device IP address +- Choose request you want to test or run whole collection and check tests results. + ## Feedback and documentation For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home). diff --git a/tests/WebServerE2ETests/nanoFramework WebServer E2E Tests.postman_collection.json b/tests/WebServerE2ETests/nanoFramework WebServer E2E Tests.postman_collection.json new file mode 100644 index 0000000..29da8c2 --- /dev/null +++ b/tests/WebServerE2ETests/nanoFramework WebServer E2E Tests.postman_collection.json @@ -0,0 +1,920 @@ +{ + "info": { + "_postman_id": "334bf606-3daa-4fd5-abbe-ad80afb49cce", + "name": "nanoFramework WebServer E2E Tests", + "description": "# ๐Ÿš€ Get started here\n\nThis template guides you through CRUD operations (GET, POST, PUT, DELETE), variables, and tests.\n\n## ๐Ÿ”– **How to use this template**\n\n#### **Step 1: Send requests**\n\nRESTful APIs allow you to perform CRUD operations using the POST, GET, PUT, and DELETE HTTP methods.\n\nThis collection contains each of these [request](https://learning.postman.com/docs/sending-requests/requests/) types. Open each request and click \"Send\" to see what happens.\n\n#### **Step 2: View responses**\n\nObserve the response tab for status code (200 OK), response time, and size.\n\n#### **Step 3: Send new Body data**\n\nUpdate or add new data in \"Body\" in the POST request. Typically, Body data is also used in PUT request.\n\n```\n{\n \"name\": \"Add your name in the body\"\n}\n\n ```\n\n#### **Step 4: Update the variable**\n\nVariables enable you to store and reuse values in Postman. We have created a [variable](https://learning.postman.com/docs/sending-requests/variables/) called `base_url` with the sample request [https://postman-api-learner.glitch.me](https://postman-api-learner.glitch.me). Replace it with your API endpoint to customize this collection.\n\n#### **Step 5: Add tests in the \"Tests\" tab**\n\nTests help you confirm that your API is working as expected. You can write test scripts in JavaScript and view the output in the \"Test Results\" tab.\n\n\n\n## ๐Ÿ’ช Pro tips\n\n- Use folders to group related requests and organize the collection.\n- Add more [scripts](https://learning.postman.com/docs/writing-scripts/intro-to-scripts/) in \"Tests\" to verify if the API works as expected and execute workflows.\n \n\n## ๐Ÿ’กRelated templates\n\n[API testing basics](https://go.postman.co/redirect/workspace?type=personal&collectionTemplateId=e9a37a28-055b-49cd-8c7e-97494a21eb54&sourceTemplateId=ddb19591-3097-41cf-82af-c84273e56719) \n[API documentation](https://go.postman.co/redirect/workspace?type=personal&collectionTemplateId=e9c28f47-1253-44af-a2f3-20dce4da1f18&sourceTemplateId=ddb19591-3097-41cf-82af-c84273e56719) \n[Authorization methods](https://go.postman.co/redirect/workspace?type=personal&collectionTemplateId=31a9a6ed-4cdf-4ced-984c-d12c9aec1c27&sourceTemplateId=ddb19591-3097-41cf-82af-c84273e56719)", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "3994606" + }, + "item": [ + { + "name": "ServerCommandReceived_EmptyPath", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Response text\", function () {", + " pm.expect(pm.response.text()).to.include(\"Hi from nanoFramework Server\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}", + "host": [ + "{{base_url}}" + ] + }, + "description": "This is a GET request and it is used to \"get\" data from an endpoint. There is no request body for a GET request, but you can use query parameters to help specify the resource you want data on (e.g., in this request, we have `id=1`).\n\nA successful GET response will have a `200 OK` status, and should include some kind of response body - for example, HTML web content or JSON data." + }, + "response": [] + }, + { + "name": "ServerCommandReceived_Params", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status OK\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Body contains parameters\", function () {\r", + " pm.expect(pm.response.text()).to.include(\"Here are the parameters of this URL:
Parameter name: testKey1, Value: 123
Parameter name: testKey2, Value: 321
\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/param.htm?testKey1=123&testKey2=321", + "host": [ + "{{base_url}}" + ], + "path": [ + "param.htm" + ], + "query": [ + { + "key": "testKey1", + "value": "123" + }, + { + "key": "testKey2", + "value": "321" + } + ] + } + }, + "response": [] + }, + { + "name": "ServerCommandReceived_WriteFile", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status OK\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/useinternal", + "host": [ + "{{base_url}}" + ], + "path": [ + "useinternal" + ] + } + }, + "response": [] + }, + { + "name": "ServerCommandReceived_ReadFile", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status OK\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.have.body(\"This is a test file for WebServer\");\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/Text.txt", + "host": [ + "{{base_url}}" + ], + "path": [ + "Text.txt" + ] + } + }, + "response": [] + }, + { + "name": "ServerCommandReceived_ReadFileMemory", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status OK\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.have.body(\"This is a test file for WebServer\");\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/Text2.txt", + "host": [ + "{{base_url}}" + ], + "path": [ + "Text2.txt" + ] + } + }, + "response": [] + }, + { + "name": "SimpleRouteController_OutputWithOKCode", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/okcode", + "host": [ + "{{base_url}}" + ], + "path": [ + "okcode" + ] + } + }, + "response": [] + }, + { + "name": "SimpleRouteController_OutputWithNotFoundCode", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 404\", function () {\r", + " pm.response.to.have.status(404);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/notfoundcode", + "host": [ + "{{base_url}}" + ], + "path": [ + "notfoundcode" + ] + } + }, + "response": [] + }, + { + "name": "SimpleRouteController_OutputWithOKText", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response body OK\", function () {\r", + " pm.response.to.have.body(\"OK\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/oktext", + "host": [ + "{{base_url}}" + ], + "path": [ + "oktext" + ] + } + }, + "response": [] + }, + { + "name": "SimpleRouteController_RouteGetTest_test", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response body OK\", function () {\r", + " pm.response.to.have.body(\"The route asked is test\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/test", + "host": [ + "{{base_url}}" + ], + "path": [ + "test" + ] + } + }, + "response": [] + }, + { + "name": "SimpleRouteController_RouteGetTest_Test2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response body OK\", function () {\r", + " pm.response.to.have.body(\"The route asked is Test2\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/Test2", + "host": [ + "{{base_url}}" + ], + "path": [ + "Test2" + ] + } + }, + "response": [] + }, + { + "name": "SimpleRouteController_RouteGetTest_tEst42", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response body OK\", function () {\r", + " pm.response.to.have.body(\"The route asked is tEst42\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/tEst42", + "host": [ + "{{base_url}}" + ], + "path": [ + "tEst42" + ] + } + }, + "response": [] + }, + { + "name": "SimpleRouteController_RouteGetTest_TEST", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response body OK\", function () {\r", + " pm.response.to.have.body(\"The route asked is TEST\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/TEST", + "host": [ + "{{base_url}}" + ], + "path": [ + "TEST" + ] + } + }, + "response": [] + }, + { + "name": "SimpleRouteController_RouteAnyTest", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/test/any", + "host": [ + "{{base_url}}" + ], + "path": [ + "test", + "any" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_Basic_NoCredentials", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 401\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authbasic", + "host": [ + "{{base_url}}" + ], + "path": [ + "authbasic" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_Basic_401", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 401\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "", + "type": "string" + }, + { + "key": "username", + "value": "", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authbasic", + "host": [ + "{{base_url}}" + ], + "path": [ + "authbasic" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_Special_NoCredentials", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 401\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authbasicspecial", + "host": [ + "{{base_url}}" + ], + "path": [ + "authbasicspecial" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_Special_InvalidCredentials", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 401\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "123", + "type": "string" + }, + { + "key": "username", + "value": "321", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authbasicspecial", + "host": [ + "{{base_url}}" + ], + "path": [ + "authbasicspecial" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_Special_OK", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "{{auth_basic_password}}", + "type": "string" + }, + { + "key": "username", + "value": "{{auth_basic_username}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authbasicspecial", + "host": [ + "{{base_url}}" + ], + "path": [ + "authbasicspecial" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_Key_NoHeader", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 401\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authapi", + "host": [ + "{{base_url}}" + ], + "path": [ + "authapi" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_Key_NoKey", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 401\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "key", + "value": "ApiKey", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authapi", + "host": [ + "{{base_url}}" + ], + "path": [ + "authapi" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_Key_OK", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{auth_apikey}}", + "type": "string" + }, + { + "key": "key", + "value": "ApiKey", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authapi", + "host": [ + "{{base_url}}" + ], + "path": [ + "authapi" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_None_OK", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authnone", + "host": [ + "{{base_url}}" + ], + "path": [ + "authnone" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_DefaultApi_NoHeader", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 401\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authdefaultapi", + "host": [ + "{{base_url}}" + ], + "path": [ + "authdefaultapi" + ] + } + }, + "response": [] + }, + { + "name": "AuthController_DefaultApi_401", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status 401\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "", + "type": "string" + }, + { + "key": "key", + "value": "ApiKey", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/authdefaultapi", + "host": [ + "{{base_url}}" + ], + "path": [ + "authdefaultapi" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "base_url", + "value": "http://192.168.1.149" + }, + { + "key": "auth_basic_username", + "value": "user2", + "type": "string" + }, + { + "key": "auth_basic_password", + "value": "password", + "type": "string" + }, + { + "key": "auth_apikey", + "value": "superKey1234", + "type": "string" + } + ] +} \ No newline at end of file diff --git a/tests/nanoFramework.WebServer.Tests/WebServerTests.cs b/tests/nanoFramework.WebServer.Tests/WebServerTests.cs index 1a803c2..acf232c 100644 --- a/tests/nanoFramework.WebServer.Tests/WebServerTests.cs +++ b/tests/nanoFramework.WebServer.Tests/WebServerTests.cs @@ -70,6 +70,29 @@ public void IsRouteMatch_Should_ReturnTrueForMatchingMethodAndRoute(string route Assert.IsTrue(result); } - // TODO: Case sensitive tests + [TestMethod] + public void IsRouteMatch_Should_ReturnTrueForMatchingMethodAndRouteCaseSensitive() + { + // Arrange + var routeMethod = "POST"; + var routeUrl = "/api/test"; + var invokedMethod = "POST"; + var invokedUrlMatch = "/api/test"; + var invokedUrlNotMatch = "/API/TEST"; + var route = new CallbackRoutes() + { + Method = routeMethod, + Route = routeUrl, + CaseSensitive = true + }; + + // Act + var resultMatch = WebServer.IsRouteMatch(route, invokedMethod, invokedUrlMatch); + var resultNotMatch = WebServer.IsRouteMatch(route, invokedMethod, invokedUrlNotMatch); + + // Assert + Assert.IsTrue(resultMatch); + Assert.IsFalse(resultNotMatch); + } } }