Stuff
This commit is contained in:
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
BIN
database.sqlite3
BIN
database.sqlite3
Binary file not shown.
@@ -1,5 +1,7 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from src.resources.routes import resources_router
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
from src.users.router import users_router
|
from src.users.router import users_router
|
||||||
@@ -7,3 +9,4 @@ from src.maps.router import maps_router
|
|||||||
|
|
||||||
app.include_router(users_router, prefix="/users")
|
app.include_router(users_router, prefix="/users")
|
||||||
app.include_router(maps_router, prefix="/maps")
|
app.include_router(maps_router, prefix="/maps")
|
||||||
|
app.include_router(resources_router, prefix="/resources")
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
"""empty message
|
"""empty message
|
||||||
|
|
||||||
Revision ID: 2607b8f9586f
|
Revision ID: 24a18ed1c824
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2025-10-04 15:15:16.698698
|
Create Date: 2025-10-04 18:51:07.316957
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
|
|
||||||
import sqlmodel
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = '2607b8f9586f'
|
revision: str = '24a18ed1c824'
|
||||||
down_revision: Union[str, Sequence[str], None] = None
|
down_revision: Union[str, Sequence[str], None] = None
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
@@ -30,10 +30,10 @@ def upgrade() -> None:
|
|||||||
)
|
)
|
||||||
op.create_index(op.f('ix_user_name'), 'user', ['name'], unique=True)
|
op.create_index(op.f('ix_user_name'), 'user', ['name'], unique=True)
|
||||||
op.create_table('waypoint',
|
op.create_table('waypoint',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('x', sa.Float(), nullable=False),
|
sa.Column('x', sa.Float(), nullable=False),
|
||||||
sa.Column('y', sa.Float(), nullable=False),
|
sa.Column('y', sa.Float(), nullable=False),
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from sqlmodel import select
|
from sqlmodel import select
|
||||||
|
|
||||||
from src.db import get_db
|
from src.db import get_db
|
||||||
from src.maps.models import Waypoint
|
from src.maps.models import Waypoint
|
||||||
from src.maps.schemas import WaypointCreate, WaypointResponse
|
from src.maps.schemas import WaypointCreate, WaypointResponse, WaypointPatch
|
||||||
from src.utils.decorators import auth_required
|
from src.utils.decorators import auth_required
|
||||||
|
|
||||||
maps_router = APIRouter()
|
maps_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@auth_required()
|
@auth_required()
|
||||||
@maps_router.post("/waypoints", response_model=WaypointResponse)
|
@maps_router.post("/waypoints", response_model=WaypointResponse)
|
||||||
def create_waypoint(waypoint: WaypointCreate, db: Session = Depends(get_db)):
|
def create_waypoint(waypoint: WaypointCreate, db: Session = Depends(get_db)):
|
||||||
@@ -35,3 +35,30 @@ def get_waypoint(waypoint_id: int, db: Session = Depends(get_db)):
|
|||||||
if not wp:
|
if not wp:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Waypoint not found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Waypoint not found")
|
||||||
return wp
|
return wp
|
||||||
|
|
||||||
|
|
||||||
|
@auth_required()
|
||||||
|
@maps_router.patch("/waypoints/{waypoint_id}", response_model=WaypointResponse)
|
||||||
|
def update_waypoint(waypoint_id: int, waypoint_data: WaypointPatch, db: Session = Depends(get_db)):
|
||||||
|
wp = db.get(Waypoint, waypoint_id)
|
||||||
|
if not wp:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Waypoint not found")
|
||||||
|
|
||||||
|
for key, value in waypoint_data.model_dump(exclude_unset=True).items():
|
||||||
|
setattr(wp, key, value)
|
||||||
|
|
||||||
|
db.add(wp)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(wp)
|
||||||
|
return wp
|
||||||
|
|
||||||
|
|
||||||
|
@auth_required()
|
||||||
|
@maps_router.delete("/waypoints/{waypoint_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
def delete_waypoint(waypoint_id: int, db: Session = Depends(get_db)):
|
||||||
|
wp = db.get(Waypoint, waypoint_id)
|
||||||
|
if not wp:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Waypoint not found")
|
||||||
|
db.delete(wp)
|
||||||
|
db.commit()
|
||||||
|
return
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
|
|
||||||
@@ -6,6 +8,12 @@ class WaypointCreate(BaseModel):
|
|||||||
x: float
|
x: float
|
||||||
y: float
|
y: float
|
||||||
|
|
||||||
|
|
||||||
|
class WaypointPatch(BaseModel):
|
||||||
|
name: Optional[str] = None
|
||||||
|
x: Optional[float] = None
|
||||||
|
y: Optional[float] = None
|
||||||
|
|
||||||
class WaypointResponse(WaypointCreate):
|
class WaypointResponse(WaypointCreate):
|
||||||
id: int
|
id: int
|
||||||
|
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
from sqlalchemy import StaticPool
|
|
||||||
from sqlmodel import SQLModel, create_engine, Session
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
|
|
||||||
from src import app, maps_router
|
|
||||||
from src.maps.models import Waypoint
|
|
||||||
from src.db import get_db
|
|
||||||
|
|
||||||
# --- Setup in-memory SQLite ---
|
|
||||||
TEST_DATABASE_URL = "sqlite:///:memory:"
|
|
||||||
engine = create_engine(
|
|
||||||
TEST_DATABASE_URL,
|
|
||||||
connect_args={"check_same_thread": False},
|
|
||||||
poolclass=StaticPool # <-- crucial for in-memory DB
|
|
||||||
)
|
|
||||||
TestingSessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
|
||||||
|
|
||||||
# --- Override dependency ---
|
|
||||||
def override_get_db():
|
|
||||||
db = TestingSessionLocal()
|
|
||||||
try:
|
|
||||||
yield db
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
app.dependency_overrides[get_db] = override_get_db
|
|
||||||
|
|
||||||
# Include router (no need to check prefix)
|
|
||||||
app.include_router(maps_router, prefix="/maps")
|
|
||||||
|
|
||||||
client = TestClient(app)
|
|
||||||
|
|
||||||
# --- Fixture to create tables ---
|
|
||||||
@pytest.fixture(scope="module", autouse=True)
|
|
||||||
def setup_db():
|
|
||||||
SQLModel.metadata.create_all(engine)
|
|
||||||
yield
|
|
||||||
SQLModel.metadata.drop_all(engine)
|
|
||||||
|
|
||||||
# --- Bypass auth decorator for tests ---
|
|
||||||
def fake_auth_dependency():
|
|
||||||
return lambda: True
|
|
||||||
|
|
||||||
# Monkeypatch your auth_required to do nothing in tests
|
|
||||||
from src.utils.decorators import auth_required
|
|
||||||
auth_required = lambda *args, **kwargs: (lambda x: x)
|
|
||||||
|
|
||||||
# --- Tests ---
|
|
||||||
def test_create_waypoint():
|
|
||||||
payload = {"name": "TestPoint", "x": 10.5, "y": 20.5}
|
|
||||||
response = client.post("/maps/waypoints", json=payload)
|
|
||||||
assert response.status_code == 200
|
|
||||||
data = response.json()
|
|
||||||
assert data["name"] == payload["name"]
|
|
||||||
assert data["x"] == payload["x"]
|
|
||||||
assert data["y"] == payload["y"]
|
|
||||||
assert "id" in data
|
|
||||||
|
|
||||||
def test_get_waypoints():
|
|
||||||
response = client.get("/maps/waypoints")
|
|
||||||
assert response.status_code == 200
|
|
||||||
data = response.json()
|
|
||||||
assert isinstance(data, list)
|
|
||||||
assert len(data) >= 1
|
|
||||||
|
|
||||||
def test_get_waypoint_by_id():
|
|
||||||
# Create waypoint first
|
|
||||||
payload = {"name": "Point1", "x": 1.0, "y": 2.0}
|
|
||||||
create_resp = client.post("/maps/waypoints", json=payload)
|
|
||||||
wp_id = create_resp.json()["id"]
|
|
||||||
|
|
||||||
# Fetch by ID
|
|
||||||
response = client.get(f"/maps/waypoints/{wp_id}")
|
|
||||||
assert response.status_code == 200
|
|
||||||
data = response.json()
|
|
||||||
assert data["id"] == wp_id
|
|
||||||
assert data["name"] == payload["name"]
|
|
||||||
|
|
||||||
def test_get_waypoint_not_found():
|
|
||||||
response = client.get("/maps/waypoints/9999")
|
|
||||||
assert response.status_code == 404
|
|
||||||
data = response.json()
|
|
||||||
assert data["detail"] == "Waypoint not found"
|
|
||||||
0
src/resources/__init__.py
Normal file
0
src/resources/__init__.py
Normal file
21
src/resources/models.py
Normal file
21
src/resources/models.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from sqlmodel import SQLModel, Field, Relationship
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
class ResourceType(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
name: str
|
||||||
|
changes: List["ResourceChange"] = Relationship(back_populates="type")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def balance(self) -> float:
|
||||||
|
return sum(change.change for change in self.changes)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceChange(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
change: float
|
||||||
|
timestamp: datetime = Field(default_factory=datetime.now)
|
||||||
|
|
||||||
|
type_id: Optional[int] = Field(default=None, foreign_key="resourcetype.id")
|
||||||
|
type: Optional[ResourceType] = Relationship(back_populates="changes")
|
||||||
76
src/resources/routes.py
Normal file
76
src/resources/routes.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy import func
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import List
|
||||||
|
from sqlmodel import select
|
||||||
|
|
||||||
|
from src.db import get_db
|
||||||
|
from src.utils.decorators import auth_required
|
||||||
|
from src.resources.models import ResourceType, ResourceChange
|
||||||
|
from src.resources.schemas import (
|
||||||
|
ResourceTypeCreate, ResourceTypeResponse,
|
||||||
|
ResourceChangeCreate, ResourceChangeResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
resources_router = APIRouter()
|
||||||
|
|
||||||
|
@auth_required()
|
||||||
|
@resources_router.post("/types", response_model=ResourceTypeResponse)
|
||||||
|
def create_resource_type(rt: ResourceTypeCreate, db: Session = Depends(get_db)):
|
||||||
|
db_rt = ResourceType.model_validate(rt)
|
||||||
|
db.add(db_rt)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_rt)
|
||||||
|
return db_rt
|
||||||
|
|
||||||
|
|
||||||
|
@auth_required()
|
||||||
|
@resources_router.get("/types", response_model=List[ResourceTypeResponse])
|
||||||
|
def list_resource_types(db: Session = Depends(get_db)):
|
||||||
|
rts = db.execute(select(ResourceType)).scalars().all()
|
||||||
|
return rts
|
||||||
|
|
||||||
|
|
||||||
|
@auth_required()
|
||||||
|
@resources_router.get("/types/{type_id}", response_model=ResourceTypeResponse)
|
||||||
|
def get_resource_type(type_id: int, db: Session = Depends(get_db)):
|
||||||
|
rt = db.get(ResourceType, type_id)
|
||||||
|
if not rt:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Resource type not found")
|
||||||
|
|
||||||
|
return rt
|
||||||
|
|
||||||
|
|
||||||
|
@auth_required()
|
||||||
|
@resources_router.delete("/types/{type_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
def delete_resource_type(type_id: int, db: Session = Depends(get_db)):
|
||||||
|
rt = db.get(ResourceType, type_id)
|
||||||
|
if not rt:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Resource type not found")
|
||||||
|
db.delete(rt)
|
||||||
|
db.commit()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@auth_required()
|
||||||
|
@resources_router.post("/types/{type_id}/changes", response_model=ResourceChangeResponse)
|
||||||
|
def create_resource_change(type_id: int, change: ResourceChangeCreate, db: Session = Depends(get_db)):
|
||||||
|
rt = db.get(ResourceType, type_id)
|
||||||
|
if not rt:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Resource type not found")
|
||||||
|
|
||||||
|
db_change = ResourceChange.model_validate(change)
|
||||||
|
db_change.type = rt
|
||||||
|
db.add(db_change)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_change)
|
||||||
|
return db_change
|
||||||
|
|
||||||
|
|
||||||
|
@auth_required()
|
||||||
|
@resources_router.get("/types/{type_id}/changes", response_model=List[ResourceChangeResponse])
|
||||||
|
def list_resource_changes(type_id: int, db: Session = Depends(get_db)):
|
||||||
|
rt = db.get(ResourceType, type_id)
|
||||||
|
if not rt:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Resource type not found")
|
||||||
|
return rt.changes
|
||||||
19
src/resources/schemas.py
Normal file
19
src/resources/schemas.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
|
class ResourceTypeCreate(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
class ResourceTypeResponse(ResourceTypeCreate):
|
||||||
|
id: int
|
||||||
|
balance: float
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
class ResourceChangeCreate(BaseModel):
|
||||||
|
change: float
|
||||||
|
|
||||||
|
class ResourceChangeResponse(ResourceChangeCreate):
|
||||||
|
id: int
|
||||||
|
timestamp: datetime
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
0
src/tests/__init__.py
Normal file
0
src/tests/__init__.py
Normal file
42
src/tests/conftest.py
Normal file
42
src/tests/conftest.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
from sqlalchemy.pool import StaticPool
|
||||||
|
from src import app
|
||||||
|
from src.db import get_db
|
||||||
|
|
||||||
|
TEST_DATABASE_URL = "sqlite:///:memory:"
|
||||||
|
|
||||||
|
engine = create_engine(
|
||||||
|
TEST_DATABASE_URL,
|
||||||
|
connect_args={"check_same_thread": False},
|
||||||
|
poolclass=StaticPool
|
||||||
|
)
|
||||||
|
|
||||||
|
TestingSessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def setup_db():
|
||||||
|
SQLModel.metadata.create_all(engine)
|
||||||
|
yield
|
||||||
|
SQLModel.metadata.drop_all(engine)
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def db_session():
|
||||||
|
db = TestingSessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def client(db_session):
|
||||||
|
def override_get_db():
|
||||||
|
try:
|
||||||
|
yield db_session
|
||||||
|
finally:
|
||||||
|
pass
|
||||||
|
app.dependency_overrides[get_db] = override_get_db
|
||||||
|
return TestClient(app)
|
||||||
75
src/tests/test_maps.py
Normal file
75
src/tests/test_maps.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
def fake_auth_dependency():
|
||||||
|
return lambda: True
|
||||||
|
|
||||||
|
auth_required = lambda *args, **kwargs: (lambda x: x)
|
||||||
|
|
||||||
|
def test_create_waypoint(client):
|
||||||
|
payload = {"name": "TestPoint", "x": 10.5, "y": 20.5}
|
||||||
|
response = client.post("/maps/waypoints", json=payload)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["name"] == payload["name"]
|
||||||
|
assert data["x"] == payload["x"]
|
||||||
|
assert data["y"] == payload["y"]
|
||||||
|
assert "id" in data
|
||||||
|
|
||||||
|
def test_get_waypoints(client):
|
||||||
|
response = client.get("/maps/waypoints")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data, list)
|
||||||
|
assert len(data) >= 1
|
||||||
|
|
||||||
|
def test_get_waypoint_by_id(client):
|
||||||
|
payload = {"name": "Point1", "x": 1.0, "y": 2.0}
|
||||||
|
create_resp = client.post("/maps/waypoints", json=payload)
|
||||||
|
wp_id = create_resp.json()["id"]
|
||||||
|
|
||||||
|
response = client.get(f"/maps/waypoints/{wp_id}")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["id"] == wp_id
|
||||||
|
assert data["name"] == payload["name"]
|
||||||
|
|
||||||
|
def test_get_waypoint_not_found(client):
|
||||||
|
response = client.get("/maps/waypoints/9999")
|
||||||
|
assert response.status_code == 404
|
||||||
|
data = response.json()
|
||||||
|
assert data["detail"] == "Waypoint not found"
|
||||||
|
|
||||||
|
def test_delete_waypoint(client):
|
||||||
|
payload = {"name": "DeletePoint", "x": 5.0, "y": 5.0}
|
||||||
|
create_resp = client.post("/maps/waypoints", json=payload)
|
||||||
|
wp_id = create_resp.json()["id"]
|
||||||
|
|
||||||
|
del_resp = client.delete(f"/maps/waypoints/{wp_id}")
|
||||||
|
assert del_resp.status_code == 204
|
||||||
|
|
||||||
|
get_resp = client.get(f"/maps/waypoints/{wp_id}")
|
||||||
|
assert get_resp.status_code == 404
|
||||||
|
assert get_resp.json()["detail"] == "Waypoint not found"
|
||||||
|
|
||||||
|
def test_delete_waypoint_not_found(client):
|
||||||
|
response = client.delete("/maps/waypoints/9999")
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert response.json()["detail"] == "Waypoint not found"
|
||||||
|
|
||||||
|
def test_patch_waypoint(client):
|
||||||
|
payload = {"name": "PatchPoint", "x": 0.0, "y": 0.0}
|
||||||
|
create_resp = client.post("/maps/waypoints", json=payload)
|
||||||
|
wp_id = create_resp.json()["id"]
|
||||||
|
|
||||||
|
patch_payload = {"name": "UpdatedPoint", "x": 1.1, "y": 2.2}
|
||||||
|
patch_resp = client.patch(f"/maps/waypoints/{wp_id}", json=patch_payload)
|
||||||
|
assert patch_resp.status_code == 200
|
||||||
|
data = patch_resp.json()
|
||||||
|
assert data["id"] == wp_id
|
||||||
|
assert data["name"] == patch_payload["name"]
|
||||||
|
assert data["x"] == patch_payload["x"]
|
||||||
|
assert data["y"] == patch_payload["y"]
|
||||||
|
|
||||||
|
def test_patch_waypoint_not_found(client):
|
||||||
|
patch_payload = {"name": "NonExistent", "x": 1.0, "y": 2.0}
|
||||||
|
response = client.patch("/maps/waypoints/9999", json=patch_payload)
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert response.json()["detail"] == "Waypoint not found"
|
||||||
126
src/tests/test_resources.py
Normal file
126
src/tests/test_resources.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
def fake_auth_dependency():
|
||||||
|
return lambda: True
|
||||||
|
|
||||||
|
auth_required = lambda *args, **kwargs: (lambda x: x)
|
||||||
|
|
||||||
|
def test_create_resource_type(client):
|
||||||
|
payload = {"name": "Food"}
|
||||||
|
response = client.post("/resources/types", json=payload)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["name"] == "Food"
|
||||||
|
assert "id" in data
|
||||||
|
|
||||||
|
def test_list_resource_types(client):
|
||||||
|
response = client.get("/resources/types")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data, list)
|
||||||
|
assert len(data) >= 1
|
||||||
|
|
||||||
|
def test_get_resource_type_by_id(client):
|
||||||
|
payload = {"name": "Water"}
|
||||||
|
create_resp = client.post("/resources/types", json=payload)
|
||||||
|
type_id = create_resp.json()["id"]
|
||||||
|
|
||||||
|
response = client.get(f"/resources/types/{type_id}")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["id"] == type_id
|
||||||
|
assert data["name"] == "Water"
|
||||||
|
|
||||||
|
def test_get_resource_type_not_found(client):
|
||||||
|
response = client.get("/resources/types/9999")
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert response.json()["detail"] == "Resource type not found"
|
||||||
|
|
||||||
|
def test_delete_resource_type(client):
|
||||||
|
payload = {"name": "Ammo"}
|
||||||
|
create_resp = client.post("/resources/types", json=payload)
|
||||||
|
type_id = create_resp.json()["id"]
|
||||||
|
|
||||||
|
del_resp = client.delete(f"/resources/types/{type_id}")
|
||||||
|
assert del_resp.status_code == 204
|
||||||
|
|
||||||
|
get_resp = client.get(f"/resources/types/{type_id}")
|
||||||
|
assert get_resp.status_code == 404
|
||||||
|
assert get_resp.json()["detail"] == "Resource type not found"
|
||||||
|
|
||||||
|
def test_delete_resource_type_not_found(client):
|
||||||
|
response = client.delete("/resources/types/9999")
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert response.json()["detail"] == "Resource type not found"
|
||||||
|
|
||||||
|
# -------- Resource Change Tests --------
|
||||||
|
def test_create_resource_change(client):
|
||||||
|
# First create a resource type
|
||||||
|
type_resp = client.post("/resources/types", json={"name": "Medicine"})
|
||||||
|
type_id = type_resp.json()["id"]
|
||||||
|
|
||||||
|
payload = {"change": 10.5}
|
||||||
|
response = client.post(f"/resources/types/{type_id}/changes", json=payload)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["change"] == 10.5
|
||||||
|
assert "timestamp" in data
|
||||||
|
assert "id" in data
|
||||||
|
|
||||||
|
def test_create_resource_change_type_not_found(client):
|
||||||
|
payload = {"change": -5}
|
||||||
|
response = client.post("/resources/types/9999/changes", json=payload)
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert response.json()["detail"] == "Resource type not found"
|
||||||
|
|
||||||
|
def test_list_resource_changes(client):
|
||||||
|
# Create resource type with changes
|
||||||
|
type_resp = client.post("/resources/types", json={"name": "Fuel"})
|
||||||
|
type_id = type_resp.json()["id"]
|
||||||
|
|
||||||
|
client.post(f"/resources/types/{type_id}/changes", json={"change": 20})
|
||||||
|
client.post(f"/resources/types/{type_id}/changes", json={"change": -5})
|
||||||
|
|
||||||
|
response = client.get(f"/resources/types/{type_id}/changes")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data, list)
|
||||||
|
assert len(data) == 2
|
||||||
|
assert data[0]["change"] == 20
|
||||||
|
assert data[1]["change"] == -5
|
||||||
|
|
||||||
|
def test_list_resource_changes_type_not_found(client):
|
||||||
|
response = client.get("/resources/types/9999/changes")
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert response.json()["detail"] == "Resource type not found"
|
||||||
|
|
||||||
|
def test_balance_of_resource_type(client):
|
||||||
|
# Create type
|
||||||
|
type_resp = client.post("/resources/types", json={"name": "Supplies"})
|
||||||
|
type_id = type_resp.json()["id"]
|
||||||
|
|
||||||
|
# Add changes
|
||||||
|
client.post(f"/resources/types/{type_id}/changes", json={"change": 50})
|
||||||
|
client.post(f"/resources/types/{type_id}/changes", json={"change": -20})
|
||||||
|
client.post(f"/resources/types/{type_id}/changes", json={"change": 5})
|
||||||
|
|
||||||
|
# Get by ID with balance
|
||||||
|
resp = client.get(f"/resources/types/{type_id}")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.json()
|
||||||
|
assert data["balance"] == 35 # 50 - 20 + 5
|
||||||
|
|
||||||
|
def test_list_resource_types_with_balance(client):
|
||||||
|
# Create type
|
||||||
|
type_resp = client.post("/resources/types", json={"name": "Tools"})
|
||||||
|
type_id = type_resp.json()["id"]
|
||||||
|
|
||||||
|
# Add changes
|
||||||
|
client.post(f"/resources/types/{type_id}/changes", json={"change": 10})
|
||||||
|
client.post(f"/resources/types/{type_id}/changes", json={"change": 15})
|
||||||
|
|
||||||
|
# List all
|
||||||
|
resp = client.get("/resources/types")
|
||||||
|
print(resp.json())
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.json()
|
||||||
|
balances = {rt["name"]: rt["balance"] for rt in data}
|
||||||
|
assert balances["Tools"] == 25
|
||||||
@@ -13,7 +13,7 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None) -> s
|
|||||||
if "sub" in to_encode:
|
if "sub" in to_encode:
|
||||||
to_encode["sub"] = str(to_encode["sub"])
|
to_encode["sub"] = str(to_encode["sub"])
|
||||||
|
|
||||||
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
|
expire = datetime.datetime.now() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
|
||||||
to_encode.update({"exp": expire})
|
to_encode.update({"exp": expire})
|
||||||
|
|
||||||
token = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
token = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
|
|||||||
Reference in New Issue
Block a user