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 src.resources.routes import resources_router
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
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(maps_router, prefix="/maps")
|
||||
app.include_router(resources_router, prefix="/resources")
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 2607b8f9586f
|
||||
Revision ID: 24a18ed1c824
|
||||
Revises:
|
||||
Create Date: 2025-10-04 15:15:16.698698
|
||||
Create Date: 2025-10-04 18:51:07.316957
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlmodel
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '2607b8f9586f'
|
||||
revision: str = '24a18ed1c824'
|
||||
down_revision: Union[str, Sequence[str], None] = None
|
||||
branch_labels: 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_table('waypoint',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('x', sa.Float(), nullable=False),
|
||||
sa.Column('y', sa.Float(), nullable=False),
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,16 +1,16 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from sqlmodel import select
|
||||
|
||||
from src.db import get_db
|
||||
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
|
||||
|
||||
maps_router = APIRouter()
|
||||
|
||||
|
||||
@auth_required()
|
||||
@maps_router.post("/waypoints", response_model=WaypointResponse)
|
||||
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:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Waypoint not found")
|
||||
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
|
||||
|
||||
|
||||
@@ -6,6 +8,12 @@ class WaypointCreate(BaseModel):
|
||||
x: float
|
||||
y: float
|
||||
|
||||
|
||||
class WaypointPatch(BaseModel):
|
||||
name: Optional[str] = None
|
||||
x: Optional[float] = None
|
||||
y: Optional[float] = None
|
||||
|
||||
class WaypointResponse(WaypointCreate):
|
||||
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:
|
||||
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})
|
||||
|
||||
token = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
|
||||
Reference in New Issue
Block a user