From 396988604cdbaecdf1f953307e138a49e1c6aaa2 Mon Sep 17 00:00:00 2001 From: dp Date: Tue, 10 Mar 2026 08:10:52 +0100 Subject: [PATCH 1/2] Issue #24: Preliminary documentation of System Test. --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index b9a5580..0058b63 100644 --- a/README.md +++ b/README.md @@ -77,3 +77,38 @@ The projects implement the following layers: - The middle layer is the `Business` layer which implements the business logic and shields the Api layer from the database communication. - And finally, the bottom layer is the `DataAccess` layer which handles the communication with the database. + +## Testing + + +### Unit Tests + +Business logic and controllers tested. + +Tools used: +- xUnit +- Moq +- FluentAssertions + + +### System Tests + +Manual end-to-end tests using Postman: + +- User registration +- User authentication +- Vocabulary search +- Creation of vocabulary entries +- Error handling + + +### Test Environment + +The Test Environment consists of: + +- ASP.NET Core Web API +- JWT authentication +- SQL Server +- Deployment using dotnet publish??? + + From 73dd519d4ced141eb45168f6dabcd0a1afa7bdff Mon Sep 17 00:00:00 2001 From: dp Date: Tue, 10 Mar 2026 09:38:28 +0100 Subject: [PATCH 2/2] Issue #24: API Testing with Postman. --- README.md | 16 ++ database/tests/query-database_content.sql | 12 + ...panishByExampleApi.postman_collection.json | 247 ++++++++++++++++++ ...anishByExampleEnv.postman_environment.json | 24 ++ 4 files changed, 299 insertions(+) create mode 100644 database/tests/query-database_content.sql create mode 100644 postman/SpanishByExampleApi.postman_collection.json create mode 100644 postman/SpanishByExampleEnv.postman_environment.json diff --git a/README.md b/README.md index 0058b63..d255349 100644 --- a/README.md +++ b/README.md @@ -112,3 +112,19 @@ The Test Environment consists of: - Deployment using dotnet publish??? +## API Testing + + +A Postman collection is included in the `postman/` directory. + +**Steps**: + +1. Start the API +2. Import Environment into Postman and fill out missing values +3. Import the Collection into Postman +4. Run the "Register" and "Login" requests to create and authenticate a user +5. Store the JWT Token in the Environment +6. Run the "POST" request of the Vocabulary folder to create an entry in the database +7. Run the "GET" request of the Examples folder to read the entry from the database + + diff --git a/database/tests/query-database_content.sql b/database/tests/query-database_content.sql new file mode 100644 index 0000000..76c79d7 --- /dev/null +++ b/database/tests/query-database_content.sql @@ -0,0 +1,12 @@ +USE spanish_ex; +GO + +SELECT * +FROM dbo.AspNetUsers + +SELECT * +FROM dbo.VOCABULARY_T + +SELECT * +FROM dbo.EXAMPLES_T +GO \ No newline at end of file diff --git a/postman/SpanishByExampleApi.postman_collection.json b/postman/SpanishByExampleApi.postman_collection.json new file mode 100644 index 0000000..f50124e --- /dev/null +++ b/postman/SpanishByExampleApi.postman_collection.json @@ -0,0 +1,247 @@ +{ + "info": { + "_postman_id": "c860ce28-1668-4204-b348-66e812e587f0", + "name": "SpanishByExampleApi", + "description": "Tests the SpanishByExample API.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "24268386" + }, + "item": [ + { + "name": "Examples", + "item": [ + { + "name": "GET", + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/api/examples?q=hablar", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "api", + "examples" + ], + "query": [ + { + "key": "q", + "value": "hablar" + } + ] + }, + "description": "Retrieves usage examples associated with a vocabulary entry.\n\n**Query parameters**:\n\n`q` - queried word eg `hablar`\n\n**Responses:**\n\n200 Ok - vocabulary entry found\n\n404 NotFound - vocabulary entry not found\n\n400 BadRequest - invalid input" + }, + "response": [ + { + "name": "200", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/api/examples?q=hablar", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "api", + "examples" + ], + "query": [ + { + "key": "q", + "value": "hablar" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": null, + "header": [ + { + "key": ":status", + "value": 200 + }, + { + "key": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "key": "date", + "value": "Tue, 10 Mar 2026 08:04:52 GMT" + }, + { + "key": "server", + "value": "Kestrel" + } + ], + "cookie": [], + "body": "{\n \"vocabularyId\": 1,\n \"wordNorm\": \"hablar\",\n \"englishTranslation\": \"to speak\",\n \"examples\": [\n {\n \"exampleId\": 1,\n \"exampleText\": \"Hablo español.\",\n \"englishTranslation\": \"I speak Spanish.\"\n }\n ]\n}" + } + ] + } + ] + }, + { + "name": "Auth", + "item": [ + { + "name": "register", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\": \"maxmustermann\",\r\n \"email\": \"max.mustermann@example.com\",\r\n \"password\": \"M_m12345678\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/api/Auth/register", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "api", + "Auth", + "register" + ] + }, + "description": "Registers a new user.\n\n**Request body**:\n\n```\n{\n \"username\": \"maxmustermann\",\n \"email\": \"max.mustermann@example.com\",\n \"password\": \"M_m12345678\"\n}\n\n ```\n\n**Responses**:\n\n200 Ok – user successfully created \n400 BadRequest – duplicate user or invalid input" + }, + "response": [] + }, + { + "name": "login", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\": \"maxmustermann\",\r\n \"password\": \"M_m12345678\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/api/Auth/login", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "api", + "Auth", + "login" + ] + }, + "description": "Authenticates an already registered user.\n\n**Request body**:\n\n```\n{\n \"username\": \"maxmustermann\",\n \"password\": \"M_m12345678\"\n}\n\n ```\n\n**Responses:**\n\n200 Ok – user successfully authenticated \n401 Unauthorized – user is not registered in the system" + }, + "response": [] + } + ] + }, + { + "name": "Vocabulary", + "item": [ + { + "name": "POST", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{JWT_TOKEN}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"rawWord\": \"hablar\",\r\n \"englishTranslation\": \"to speak\",\r\n \"examples\": [\r\n {\r\n \"exampleText\": \"Hablo español.\",\r\n \"englishTranslation\": \"I speak Spanish.\"\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/api/Vocabulary", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "api", + "Vocabulary" + ] + }, + "description": "Creates a new vocabulary entry together with one or more usage examples.\n\n**Authentication**: \nRequires a valid JWT bearer token.\n\n**Request body**:\n\n```\n{\n \"rawWord\": \"hablar\",\n \"englishTranslation\": \"to speak\",\n \"examples\": [\n {\n \"exampleText\": \"Hablo español.\",\n \"englishTranslation\": \"I speak Spanish.\"\n }\n ]\n}\n\n ```\n\n**Responses**: \n200 Ok – entry successfully created \n400 BadRequest – missing examples or invalid input \n409 Conflict – vocabulary entry already exists \n401 Unauthorized – missing or invalid token" + }, + "response": [ + { + "name": "200", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"rawWord\": \"hablar\",\r\n \"englishTranslation\": \"to speak\",\r\n \"examples\": [\r\n {\r\n \"exampleText\": \"Hablo español.\",\r\n \"englishTranslation\": \"I speak Spanish.\"\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/api/Vocabulary", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "api", + "Vocabulary" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": null, + "header": [ + { + "key": ":status", + "value": 200 + }, + { + "key": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "key": "date", + "value": "Tue, 10 Mar 2026 08:04:07 GMT" + }, + { + "key": "server", + "value": "Kestrel" + } + ], + "cookie": [], + "body": "{\n \"vocabularyId\": 1,\n \"wordNorm\": \"hablar\",\n \"englishTranslation\": \"to speak\",\n \"examples\": [\n {\n \"exampleId\": 1,\n \"exampleText\": \"Hablo español.\",\n \"englishTranslation\": \"I speak Spanish.\"\n }\n ]\n}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/postman/SpanishByExampleEnv.postman_environment.json b/postman/SpanishByExampleEnv.postman_environment.json new file mode 100644 index 0000000..0530497 --- /dev/null +++ b/postman/SpanishByExampleEnv.postman_environment.json @@ -0,0 +1,24 @@ +{ + "id": "bc0be333-e00a-4ffb-93c6-a2739044531e", + "name": "SpanishByExampleEnv", + "values": [ + { + "key": "BASE_URL", + "value": "", + "type": "default", + "description": "Base URL of Web API.", + "enabled": true + }, + { + "key": "JWT_TOKEN", + "value": "", + "type": "secret", + "description": "Token that gets returned by login and is required by protected endpoints.", + "enabled": true + } + ], + "color": null, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-03-10T08:33:43.071Z", + "_postman_exported_using": "Postman/12.0.5" +} \ No newline at end of file