Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,54 @@ 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???


## 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


12 changes: 12 additions & 0 deletions database/tests/query-database_content.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
USE spanish_ex;
GO

SELECT *
FROM dbo.AspNetUsers

SELECT *
FROM dbo.VOCABULARY_T

SELECT *
FROM dbo.EXAMPLES_T
GO
247 changes: 247 additions & 0 deletions postman/SpanishByExampleApi.postman_collection.json
Original file line number Diff line number Diff line change
@@ -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}"
}
]
}
]
}
]
}
24 changes: 24 additions & 0 deletions postman/SpanishByExampleEnv.postman_environment.json
Original file line number Diff line number Diff line change
@@ -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"
}
Loading