autocommit 17-07-2024-07-54
This commit is contained in:
parent
2c201e47cd
commit
242c5c08f0
|
|
@ -0,0 +1,534 @@
|
||||||
|
# POS System API Documentation
|
||||||
|
|
||||||
|
## Base URL
|
||||||
|
|
||||||
|
All URLs referenced in the documentation have the following base:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:8000/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Most endpoints require authentication. Use the following endpoint to obtain a JWT token:
|
||||||
|
|
||||||
|
### Login for Access Token
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /token
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "string",
|
||||||
|
"token_type": "bearer"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the received token in the Authorization header for subsequent requests:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Items
|
||||||
|
|
||||||
|
### Create a new item
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /items
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"price": 0,
|
||||||
|
"quantity": 0,
|
||||||
|
"unit": "string",
|
||||||
|
"related_items": ["string"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"name": "string",
|
||||||
|
"price": 0,
|
||||||
|
"quantity": 0,
|
||||||
|
"unit": "string",
|
||||||
|
"related_items": ["string"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get all items
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /items
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
- `skip` (optional): number of items to skip
|
||||||
|
- `limit` (optional): maximum number of items to return
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"name": "string",
|
||||||
|
"price": 0,
|
||||||
|
"quantity": 0,
|
||||||
|
"unit": "string",
|
||||||
|
"related_items": ["string"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get a specific item
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /items/{item_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"name": "string",
|
||||||
|
"price": 0,
|
||||||
|
"quantity": 0,
|
||||||
|
"unit": "string",
|
||||||
|
"related_items": ["string"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update an item
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /items/{item_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"price": 0,
|
||||||
|
"quantity": 0,
|
||||||
|
"unit": "string",
|
||||||
|
"related_items": ["string"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"name": "string",
|
||||||
|
"price": 0,
|
||||||
|
"quantity": 0,
|
||||||
|
"unit": "string",
|
||||||
|
"related_items": ["string"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Delete an item
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /items/{item_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Item successfully deleted"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Orders
|
||||||
|
|
||||||
|
### Create a new order
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /orders
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user_id": "string",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item_id": "string",
|
||||||
|
"quantity": 0,
|
||||||
|
"price_at_order": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_amount": 0,
|
||||||
|
"payment_method": "string",
|
||||||
|
"notes": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"user_id": "string",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item_id": "string",
|
||||||
|
"quantity": 0,
|
||||||
|
"price_at_order": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_amount": 0,
|
||||||
|
"payment_method": "string",
|
||||||
|
"payment_status": "string",
|
||||||
|
"order_status": "string",
|
||||||
|
"created_at": "string",
|
||||||
|
"updated_at": "string",
|
||||||
|
"discount_applied": 0,
|
||||||
|
"notes": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get all orders
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /orders
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
- `skip` (optional): number of orders to skip
|
||||||
|
- `limit` (optional): maximum number of orders to return
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"user_id": "string",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item_id": "string",
|
||||||
|
"quantity": 0,
|
||||||
|
"price_at_order": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_amount": 0,
|
||||||
|
"payment_method": "string",
|
||||||
|
"payment_status": "string",
|
||||||
|
"order_status": "string",
|
||||||
|
"created_at": "string",
|
||||||
|
"updated_at": "string",
|
||||||
|
"discount_applied": 0,
|
||||||
|
"notes": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get a specific order
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /orders/{order_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"user_id": "string",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item_id": "string",
|
||||||
|
"quantity": 0,
|
||||||
|
"price_at_order": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_amount": 0,
|
||||||
|
"payment_method": "string",
|
||||||
|
"payment_status": "string",
|
||||||
|
"order_status": "string",
|
||||||
|
"created_at": "string",
|
||||||
|
"updated_at": "string",
|
||||||
|
"discount_applied": 0,
|
||||||
|
"notes": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update an order
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /orders/{order_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item_id": "string",
|
||||||
|
"quantity": 0,
|
||||||
|
"price_at_order": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_amount": 0,
|
||||||
|
"payment_method": "string",
|
||||||
|
"payment_status": "string",
|
||||||
|
"order_status": "string",
|
||||||
|
"discount_applied": 0,
|
||||||
|
"notes": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"user_id": "string",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item_id": "string",
|
||||||
|
"quantity": 0,
|
||||||
|
"price_at_order": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_amount": 0,
|
||||||
|
"payment_method": "string",
|
||||||
|
"payment_status": "string",
|
||||||
|
"order_status": "string",
|
||||||
|
"created_at": "string",
|
||||||
|
"updated_at": "string",
|
||||||
|
"discount_applied": 0,
|
||||||
|
"notes": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Delete an order
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /orders/{order_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Order successfully deleted"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Process payment for an order
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /orders/{order_id}/process_payment
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"payment_method": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Payment processed successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Apply discount to an order
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /orders/{order_id}/apply_discount
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"discount_percentage": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"total_amount": 0,
|
||||||
|
"discount_applied": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Users
|
||||||
|
|
||||||
|
### Register a new user
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /users
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "string",
|
||||||
|
"email": "string",
|
||||||
|
"full_name": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"username": "string",
|
||||||
|
"email": "string",
|
||||||
|
"full_name": "string",
|
||||||
|
"is_active": true,
|
||||||
|
"is_superuser": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get current user
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /users/me
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"username": "string",
|
||||||
|
"email": "string",
|
||||||
|
"full_name": "string",
|
||||||
|
"is_active": true,
|
||||||
|
"is_superuser": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get all users
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /users
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
- `skip` (optional): number of users to skip
|
||||||
|
- `limit` (optional): maximum number of users to return
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"username": "string",
|
||||||
|
"email": "string",
|
||||||
|
"full_name": "string",
|
||||||
|
"is_active": true,
|
||||||
|
"is_superuser": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update a user
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /users/{user_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "string",
|
||||||
|
"full_name": "string",
|
||||||
|
"password": "string",
|
||||||
|
"is_active": true,
|
||||||
|
"is_superuser": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"username": "string",
|
||||||
|
"email": "string",
|
||||||
|
"full_name": "string",
|
||||||
|
"is_active": true,
|
||||||
|
"is_superuser": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Delete a user
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /users/{user_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "User successfully deleted"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Responses
|
||||||
|
|
||||||
|
All endpoints can return the following error responses:
|
||||||
|
|
||||||
|
- 400 Bad Request
|
||||||
|
- 401 Unauthorized
|
||||||
|
- 403 Forbidden
|
||||||
|
- 404 Not Found
|
||||||
|
- 422 Unprocessable Entity
|
||||||
|
- 500 Internal Server Error
|
||||||
|
|
||||||
|
Error response body:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "Error message"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -401,6 +401,20 @@ files = [
|
||||||
dnspython = ">=2.0.0"
|
dnspython = ">=2.0.0"
|
||||||
idna = ">=2.0.0"
|
idna = ">=2.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "execnet"
|
||||||
|
version = "2.1.1"
|
||||||
|
description = "execnet: rapid multi-Python deployment"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
||||||
|
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["hatch", "pre-commit", "pytest", "tox"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "executing"
|
name = "executing"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
|
@ -1511,6 +1525,26 @@ pluggy = ">=1.5,<2.0"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-xdist"
|
||||||
|
version = "3.6.1"
|
||||||
|
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
||||||
|
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
execnet = ">=2.1"
|
||||||
|
pytest = ">=7.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
psutil = ["psutil (>=3.0)"]
|
||||||
|
setproctitle = ["setproctitle"]
|
||||||
|
testing = ["filelock"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.9.0.post0"
|
version = "2.9.0.post0"
|
||||||
|
|
@ -2308,4 +2342,4 @@ files = [
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "e206ba057f9668e090364cc695646a317f0896de3d81b60e05450b7498324d84"
|
content-hash = "0d6489d261e19e3bffaafb61a05fdc436dae4801beefcaf640e9abc4b7bad8fd"
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
from pydantic import BaseModel, Field, EmailStr, field_validator, validator, ConfigDict
|
from pydantic import BaseModel, Field, EmailStr, field_validator, ConfigDict
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from datetime import datetime
|
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
def validate_object_id(value: str) -> ObjectId:
|
|
||||||
if not ObjectId.is_valid(value):
|
|
||||||
raise ValueError("Invalid ObjectId")
|
|
||||||
return ObjectId(value)
|
|
||||||
|
|
||||||
|
|
||||||
class MongoBaseModel(BaseModel):
|
class MongoBaseModel(BaseModel):
|
||||||
|
|
@ -20,11 +14,12 @@ class MongoBaseModel(BaseModel):
|
||||||
json_encoders={ObjectId: str}
|
json_encoders={ObjectId: str}
|
||||||
)
|
)
|
||||||
|
|
||||||
# @field_validator("id", pre=True)
|
@field_validator('id', mode='before')
|
||||||
# def validate_id(cls, v):
|
@classmethod
|
||||||
# if isinstance(v, ObjectId):
|
def convert_object_id_to_string(cls, v):
|
||||||
# return str(v)
|
if isinstance(v, ObjectId):
|
||||||
# return v
|
return str(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class Item(MongoBaseModel):
|
class Item(MongoBaseModel):
|
||||||
|
|
@ -39,10 +34,12 @@ class OrderItem(BaseModel):
|
||||||
item_id: str
|
item_id: str
|
||||||
quantity: int
|
quantity: int
|
||||||
price_at_order: float
|
price_at_order: float
|
||||||
|
|
||||||
# @field_validator("item_id")
|
@field_validator("item_id")
|
||||||
# def validate_item_id(cls, v):
|
def validate_id(cls, v):
|
||||||
# return validate_object_id(v)
|
if isinstance(v, ObjectId):
|
||||||
|
return str(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class Order(MongoBaseModel):
|
class Order(MongoBaseModel):
|
||||||
|
|
@ -57,7 +54,7 @@ class Order(MongoBaseModel):
|
||||||
discount_applied: Optional[float] = None
|
discount_applied: Optional[float] = None
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
|
|
||||||
# @validator("user_id")
|
# @field_validator("user_id")
|
||||||
# def validate_user_id(cls, v):
|
# def validate_user_id(cls, v):
|
||||||
# return validate_object_id(v)
|
# return validate_object_id(v)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@ async def create_item(item: Item):
|
||||||
item_dict = item.model_dump(exclude={"id"})
|
item_dict = item.model_dump(exclude={"id"})
|
||||||
result = db.items.insert_one(item_dict)
|
result = db.items.insert_one(item_dict)
|
||||||
created_item = db.items.find_one({"_id": result.inserted_id})
|
created_item = db.items.find_one({"_id": result.inserted_id})
|
||||||
return Item(**created_item)
|
return Item.model_validate(created_item)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=List[Item])
|
@router.get("/", response_model=List[Item])
|
||||||
async def read_items():
|
async def read_items():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
items = list(db.items.find())
|
items = list(db.items.find())
|
||||||
return [Item(**item) for item in items]
|
return [Item.model_validate(item) for item in items]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{item_id}", response_model=Item)
|
@router.get("/{item_id}", response_model=Item)
|
||||||
|
|
@ -32,7 +32,7 @@ async def read_item(item_id: str):
|
||||||
item = db.items.find_one({"_id": ObjectId(item_id)})
|
item = db.items.find_one({"_id": ObjectId(item_id)})
|
||||||
if item is None:
|
if item is None:
|
||||||
raise HTTPException(status_code=404, detail="Item not found")
|
raise HTTPException(status_code=404, detail="Item not found")
|
||||||
return Item(**item)
|
return Item.model_validate(item)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{item_id}", response_model=Item)
|
@router.put("/{item_id}", response_model=Item)
|
||||||
|
|
@ -44,7 +44,7 @@ async def update_item(item_id: str, item: Item):
|
||||||
if result.modified_count == 0:
|
if result.modified_count == 0:
|
||||||
raise HTTPException(status_code=404, detail="Item not found")
|
raise HTTPException(status_code=404, detail="Item not found")
|
||||||
updated_item = db.items.find_one({"_id": ObjectId(item_id)})
|
updated_item = db.items.find_one({"_id": ObjectId(item_id)})
|
||||||
return Item(**updated_item)
|
return Item.model_validate(updated_item)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{item_id}", response_model=dict)
|
@router.delete("/{item_id}", response_model=dict)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ python-jose = "^3.3.0"
|
||||||
passlib = "^1.7.4"
|
passlib = "^1.7.4"
|
||||||
|
|
||||||
pydantic = "^2.8.2"
|
pydantic = "^2.8.2"
|
||||||
|
pytest-xdist = "^3.6.1"
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ipykernel = "^6.29.5"
|
ipykernel = "^6.29.5"
|
||||||
|
|
||||||
|
|
@ -30,3 +31,4 @@ indent-size = 2
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
server = "pos_system.server:main"
|
server = "pos_system.server:main"
|
||||||
ui = "pos_system.ui:main"
|
ui = "pos_system.ui:main"
|
||||||
|
test-items = "tests.test_items_fastapi:test"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# File: pytest.ini (place this in your project root directory)
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
log_cli = true
|
||||||
|
log_cli_level = INFO
|
||||||
|
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
|
||||||
|
log_cli_date_format=%Y-%m-%d %H:%M:%S
|
||||||
|
log_file = tests/output/pytest.log
|
||||||
|
log_file_level = DEBUG
|
||||||
|
log_file_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
|
||||||
|
log_file_date_format=%Y-%m-%d %H:%M:%S
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
output/
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
import logging
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from hypothesis import given, strategies as st
|
from hypothesis import given, strategies as st, settings
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
from pos_system.server import app
|
from pos_system.server import app
|
||||||
from pos_system.database import get_db
|
from pos_system.database import get_db
|
||||||
|
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|
@ -17,15 +19,18 @@ def clear_db():
|
||||||
|
|
||||||
|
|
||||||
def test_create_item():
|
def test_create_item():
|
||||||
|
logger.info("Testing create item")
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/items/",
|
"/items/",
|
||||||
json={"name": "Test Item", "price": 10.99, "quantity": 5, "unit": "piece"}
|
json={"name": "Test Item", "price": 10.99, "quantity": 5, "unit": "piece"}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert "id" in response.json()
|
assert "_id" in response.json()
|
||||||
|
logger.info(f"Created item with ID: {response.json()['_id']}")
|
||||||
|
|
||||||
|
|
||||||
def test_read_items():
|
def test_read_items():
|
||||||
|
logger.info("Testing read items")
|
||||||
client.post("/items/", json={"name": "Test Item",
|
client.post("/items/", json={"name": "Test Item",
|
||||||
"price": 10.99, "quantity": 5, "unit": "piece"})
|
"price": 10.99, "quantity": 5, "unit": "piece"})
|
||||||
response = client.get("/items/")
|
response = client.get("/items/")
|
||||||
|
|
@ -33,54 +38,62 @@ def test_read_items():
|
||||||
items = response.json()
|
items = response.json()
|
||||||
assert len(items) == 1
|
assert len(items) == 1
|
||||||
assert items[0]["name"] == "Test Item"
|
assert items[0]["name"] == "Test Item"
|
||||||
|
logger.info(f"Retrieved {len(items)} items")
|
||||||
|
|
||||||
|
|
||||||
def test_read_item():
|
def test_read_item():
|
||||||
|
logger.info("Testing read single item")
|
||||||
create_response = client.post(
|
create_response = client.post(
|
||||||
"/items/", json={"name": "Test Item", "price": 10.99, "quantity": 5, "unit": "piece"})
|
"/items/", json={"name": "Test Item", "price": 10.99, "quantity": 5, "unit": "piece"})
|
||||||
item_id = create_response.json()["id"]
|
item_id = create_response.json()["_id"]
|
||||||
response = client.get(f"/items/{item_id}")
|
response = client.get(f"/items/{item_id}")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json()["name"] == "Test Item"
|
assert response.json()["name"] == "Test Item"
|
||||||
|
logger.info(f"Retrieved item with ID: {item_id}")
|
||||||
|
|
||||||
|
|
||||||
def test_update_item():
|
def test_update_item():
|
||||||
|
logger.info("Testing update item")
|
||||||
create_response = client.post(
|
create_response = client.post(
|
||||||
"/items/", json={"name": "Test Item", "price": 10.99, "quantity": 5, "unit": "piece"})
|
"/items/", json={"name": "Test Item", "price": 10.99, "quantity": 5, "unit": "piece"})
|
||||||
item_id = create_response.json()["id"]
|
item_id = create_response.json()["_id"]
|
||||||
update_data = {"name": "Updated Item",
|
update_data = {"name": "Updated Item",
|
||||||
"price": 15.99, "quantity": 10, "unit": "piece"}
|
"price": 15.99, "quantity": 10, "unit": "piece"}
|
||||||
response = client.put(f"/items/{item_id}", json=update_data)
|
response = client.put(f"/items/{item_id}", json=update_data)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json()["name"] == "Updated Item"
|
assert response.json()["name"] == "Updated Item"
|
||||||
|
logger.info(f"Updated item with ID: {item_id}")
|
||||||
|
|
||||||
|
|
||||||
def test_delete_item():
|
def test_delete_item():
|
||||||
|
logger.info("Testing delete item")
|
||||||
create_response = client.post(
|
create_response = client.post(
|
||||||
"/items/", json={"name": "Test Item", "price": 10.99, "quantity": 5, "unit": "piece"})
|
"/items/", json={"name": "Test Item", "price": 10.99, "quantity": 5, "unit": "piece"})
|
||||||
item_id = create_response.json()["id"]
|
item_id = create_response.json()["_id"]
|
||||||
response = client.delete(f"/items/{item_id}")
|
response = client.delete(f"/items/{item_id}")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json()["message"] == "Item deleted successfully"
|
assert response.json()["message"] == "Item deleted successfully"
|
||||||
get_response = client.get(f"/items/{item_id}")
|
get_response = client.get(f"/items/{item_id}")
|
||||||
assert get_response.status_code == 404
|
assert get_response.status_code == 404
|
||||||
|
logger.info(f"Deleted item with ID: {item_id}")
|
||||||
|
|
||||||
|
|
||||||
@given(
|
@given(
|
||||||
name=st.text(min_size=1, max_size=50),
|
name=st.text(min_size=1, max_size=20),
|
||||||
price=st.floats(min_value=0.01, max_value=1000000,
|
price=st.floats(min_value=0.01, max_value=100,
|
||||||
allow_nan=False, allow_infinity=False),
|
allow_nan=False, allow_infinity=False),
|
||||||
quantity=st.integers(min_value=0, max_value=1000000),
|
quantity=st.integers(min_value=0, max_value=100),
|
||||||
unit=st.text(min_size=1, max_size=10)
|
unit=st.text(min_size=1, max_size=10)
|
||||||
)
|
)
|
||||||
|
@settings(max_examples=50) # Limit the number of examples to 50
|
||||||
def test_create_item_property(name, price, quantity, unit):
|
def test_create_item_property(name, price, quantity, unit):
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/items/",
|
"/items/",
|
||||||
json={"name": name, "price": price, "quantity": quantity, "unit": unit}
|
json={"name": name, "price": price, "quantity": quantity, "unit": unit}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert "id" in response.json()
|
assert "_id" in response.json()
|
||||||
item_id = response.json()["id"]
|
item_id = response.json()["_id"]
|
||||||
get_response = client.get(f"/items/{item_id}")
|
get_response = client.get(f"/items/{item_id}")
|
||||||
assert get_response.status_code == 200
|
assert get_response.status_code == 200
|
||||||
item = get_response.json()
|
item = get_response.json()
|
||||||
|
|
@ -93,15 +106,16 @@ def test_create_item_property(name, price, quantity, unit):
|
||||||
@given(
|
@given(
|
||||||
st.lists(
|
st.lists(
|
||||||
st.fixed_dictionaries({
|
st.fixed_dictionaries({
|
||||||
"name": st.text(min_size=1, max_size=50),
|
"name": st.text(min_size=1, max_size=20),
|
||||||
"price": st.floats(min_value=0.01, max_value=1000000, allow_nan=False, allow_infinity=False),
|
"price": st.floats(min_value=0.01, max_value=100, allow_nan=False, allow_infinity=False),
|
||||||
"quantity": st.integers(min_value=0, max_value=1000000),
|
"quantity": st.integers(min_value=0, max_value=100),
|
||||||
"unit": st.text(min_size=1, max_size=10)
|
"unit": st.text(min_size=1, max_size=10)
|
||||||
}),
|
}),
|
||||||
min_size=1,
|
min_size=1,
|
||||||
max_size=10
|
max_size=10
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@settings(max_examples=20) # Limit the number of examples to 20
|
||||||
def test_read_items_property(items):
|
def test_read_items_property(items):
|
||||||
for item in items:
|
for item in items:
|
||||||
client.post("/items/", json=item)
|
client.post("/items/", json=item)
|
||||||
|
|
@ -115,6 +129,22 @@ def test_read_items_property(items):
|
||||||
assert "quantity" in retrieved_item
|
assert "quantity" in retrieved_item
|
||||||
assert "unit" in retrieved_item
|
assert "unit" in retrieved_item
|
||||||
|
|
||||||
|
# Add this function to log only failed property tests
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
|
def pytest_runtest_makereport(item, call):
|
||||||
|
outcome = yield
|
||||||
|
report = outcome.get_result()
|
||||||
|
if report.when == "call" and report.failed:
|
||||||
|
if "hypothesis" in item.keywords:
|
||||||
|
logger.error(f"Property test failed: {item.name}")
|
||||||
|
logger.error(f"Falsifying example: {call.excinfo.value}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
pytest.main([__file__])
|
pytest.main([__file__])
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
pytest.main([__file__])
|
||||||
Loading…
Reference in New Issue