Change accessauth, timetable model #16
@@ -15,11 +15,18 @@ aa_router = APIRouter(prefix="/api/v1/aa", tags=["AccessAuth"])
|
|||||||
@aa_router.post("/", response_model=AccessAuthorizationResponse)
|
@aa_router.post("/", response_model=AccessAuthorizationResponse)
|
||||||
def add_accessauth(*, db: Session = Depends(get_session), aa: AccessAuthorizationCreate, admin: bool = Depends(auth_is_admin)):
|
def add_accessauth(*, db: Session = Depends(get_session), aa: AccessAuthorizationCreate, admin: bool = Depends(auth_is_admin)):
|
||||||
logger.info(f"Creating accessauth with data: {aa}")
|
logger.info(f"Creating accessauth with data: {aa}")
|
||||||
timetables = [Timetable.model_validate(t) for t in aa.timetables]
|
if aa.timetables is not []:
|
||||||
|
timetables = [Timetable.model_validate(t) for t in aa.timetables]
|
||||||
|
else: timetables = []
|
||||||
|
if aa.oneshot is not None:
|
||||||
|
oneshot = OneShotAccess.model_validate(aa.oneshot)
|
||||||
|
else: oneshot = None
|
||||||
db_aa = AccessAuthorizationDB(
|
db_aa = AccessAuthorizationDB(
|
||||||
name=aa.name,
|
name=aa.name,
|
||||||
|
type=aa.type,
|
||||||
is_active=aa.is_active,
|
is_active=aa.is_active,
|
||||||
timetables=timetables
|
timetables=timetables,
|
||||||
|
oneshot=oneshot
|
||||||
)
|
)
|
||||||
return add_and_refresh(db, db_aa)
|
return add_and_refresh(db, db_aa)
|
||||||
|
|
||||||
@@ -68,12 +75,16 @@ def change_accessauth(*, db: Session = Depends(get_session), aa_id: int, aa: Acc
|
|||||||
db_aa = db.get(AccessAuthorizationDB, aa_id)
|
db_aa = db.get(AccessAuthorizationDB, aa_id)
|
||||||
if db_aa is None:
|
if db_aa is None:
|
||||||
raise HTTPException(status_code=404, detail="AccessAuthorization not found")
|
raise HTTPException(status_code=404, detail="AccessAuthorization not found")
|
||||||
aa_data = aa.model_dump(exclude_unset=True)
|
aa_data = aa.model_dump(exclude_unset=True, exclude_none=True)
|
||||||
if "timetables" in aa_data and aa_data["timetables"] is not None:
|
if "timetables" in aa_data and aa_data["timetables"] is not None:
|
||||||
db_aa.timetables.clear()
|
db_aa.timetables.clear()
|
||||||
timetables = [Timetable.model_validate(t) for t in aa_data["timetables"]]
|
timetables = [Timetable.model_validate(t) for t in aa_data["timetables"]]
|
||||||
db_aa.timetables = timetables
|
db_aa.timetables = timetables
|
||||||
aa_data.pop("timetables")
|
aa_data.pop("timetables")
|
||||||
|
if "oneshot" in aa_data and aa_data["oneshot"] is not None:
|
||||||
|
oneshot = OneShotAccess.model_validate(aa_data["oneshot"])
|
||||||
|
db_aa.oneshot = oneshot
|
||||||
|
aa_data.pop("oneshot")
|
||||||
db_aa.sqlmodel_update(aa_data)
|
db_aa.sqlmodel_update(aa_data)
|
||||||
return add_and_refresh(db, db_aa)
|
return add_and_refresh(db, db_aa)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from sqlmodel import Field, Relationship, Session, SQLModel
|
from sqlmodel import Field, Relationship, Session, SQLModel
|
||||||
from typing import List
|
from typing import List, Literal, Union
|
||||||
from datetime import time
|
from datetime import datetime, time
|
||||||
|
from pydantic import model_validator
|
||||||
|
|
||||||
class Base(SQLModel):
|
class Base(SQLModel):
|
||||||
pass
|
pass
|
||||||
@@ -60,25 +61,57 @@ class GroupResponse(GroupBase):
|
|||||||
#### AccessAuthorization
|
#### AccessAuthorization
|
||||||
class AccessAuthorizationBase(Base):
|
class AccessAuthorizationBase(Base):
|
||||||
name: str = Field(index=True)
|
name: str = Field(index=True)
|
||||||
|
type: Literal["timetable", "oneshot", "somefuturespec"]
|
||||||
is_active: bool
|
is_active: bool
|
||||||
|
|
||||||
class AccessAuthorizationDB(AccessAuthorizationBase, table=True):
|
class AccessAuthorizationDB(AccessAuthorizationBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
type: str
|
||||||
groups: List["GroupDB"] = Relationship(back_populates="accessauths", link_model=AaGroupLink)
|
groups: List["GroupDB"] = Relationship(back_populates="accessauths", link_model=AaGroupLink)
|
||||||
timetables: List["Timetable"] = Relationship(back_populates="accessauth", cascade_delete=True)
|
timetables: List["Timetable"] = Relationship(back_populates="accessauth", cascade_delete=True)
|
||||||
|
oneshot: "OneShotAccess" = Relationship(back_populates="accessauth", cascade_delete=True)
|
||||||
|
|
||||||
|
class OneShotAccessBase(Base):
|
||||||
|
uses: int = 1
|
||||||
|
ends_at: datetime
|
||||||
|
|
||||||
|
class OneShotAccess(OneShotAccessBase, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
accessauth_id: int = Field(default=None, foreign_key="accessauthorizationdb.id")
|
||||||
|
accessauth: AccessAuthorizationDB = Relationship(back_populates="oneshot")
|
||||||
|
|
||||||
class AccessAuthorizationCreate(AccessAuthorizationBase):
|
class AccessAuthorizationCreate(AccessAuthorizationBase):
|
||||||
timetables: List["TimetableCreate"]
|
timetables: List["TimetableCreate"] = []
|
||||||
|
oneshot: OneShotAccessBase | None = None
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def check_type(self):
|
||||||
|
if self.type == "timetable":
|
||||||
|
if not self.timetables:
|
||||||
|
raise ValueError("timetable auths require at least one timetable object")
|
||||||
|
if self.oneshot is not None:
|
||||||
|
raise ValueError("timetable auths are not allowed oneshot objects")
|
||||||
|
elif self.type == "oneshot":
|
||||||
|
if not self.oneshot:
|
||||||
|
raise ValueError("oneshot auths require a oneshot object")
|
||||||
|
if self.timetables:
|
||||||
|
raise ValueError("oneshot auths are not allowed timetable objects")
|
||||||
|
elif self.type == "somefuturespec":
|
||||||
|
raise ValueError("somefuturespec is not jet implemented. Please do not use")
|
||||||
|
return self
|
||||||
|
|
||||||
class AccessAuthorizationResponse(AccessAuthorizationBase):
|
class AccessAuthorizationResponse(AccessAuthorizationBase):
|
||||||
id: int
|
id: int
|
||||||
timetables: List["Timetable"]
|
timetables: List["Timetable"] = []
|
||||||
|
oneshot: OneShotAccessBase | None = None
|
||||||
groups: List["GroupDB"]
|
groups: List["GroupDB"]
|
||||||
|
|
||||||
class AccessAuthorizationUpdate(Base):
|
class AccessAuthorizationUpdate(Base):
|
||||||
name: str | None = None
|
name: str | None = None
|
||||||
|
type: Literal["timetable", "oneshot", "somefuturespec"] | None = None
|
||||||
is_active: bool | None = None
|
is_active: bool | None = None
|
||||||
timetables: List["TimetableCreate"] | None = None
|
timetables: List["TimetableCreate"] | None = None
|
||||||
|
oneshot: OneShotAccessBase | None = None
|
||||||
|
|
||||||
|
|
||||||
#### Card
|
#### Card
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ from sqlmodel import select
|
|||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import Depends, HTTPException, status
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
import sqlalchemy.exc as exc
|
import sqlalchemy.exc as exc
|
||||||
import datetime
|
from datetime import datetime, date, timedelta
|
||||||
|
|
||||||
from app.services.database import Session, get_session
|
from app.services.database import Session, get_session, add_and_refresh
|
||||||
from app.model.models import *
|
from app.model.models import *
|
||||||
|
|
||||||
doorIsOpen = True
|
doorIsOpen = True
|
||||||
@@ -28,22 +28,36 @@ def closeDoor():
|
|||||||
def isDoorOpen():
|
def isDoorOpen():
|
||||||
return doorIsOpen
|
return doorIsOpen
|
||||||
|
|
||||||
|
def decrementOneshot(db: Session, oneshot: OneShotAccess):
|
||||||
|
data = oneshot.model_dump()
|
||||||
|
if data["uses"] > 0:
|
||||||
|
data["uses"] = data["uses"] - 1
|
||||||
|
oneshot.sqlmodel_update(oneshot, update=data)
|
||||||
|
add_and_refresh(db, oneshot)
|
||||||
|
|
||||||
def checkAccess(key: str, db: Session):
|
def checkAccess(key: str, db: Session):
|
||||||
try:
|
try:
|
||||||
current_weekday = datetime.datetime.weekday(datetime.date.today())
|
current_weekday = datetime.weekday(date.today())
|
||||||
current_time = datetime.datetime.now()
|
current_time = datetime.now()
|
||||||
card = db.exec(select(Card).where(Card.key == key)).one()
|
card = db.exec(select(Card).where(Card.key == key)).one()
|
||||||
for auth in card.group.accessauths:
|
for auth in card.group.accessauths:
|
||||||
logger.info(f"checking auth: {auth.name}")
|
logger.info(f"checking auth: {auth.name}")
|
||||||
for timetable in auth.timetables:
|
if auth.type == "timetable":
|
||||||
logger.info(f" checking timetable {timetable.id}")
|
for timetable in auth.timetables:
|
||||||
logger.info(f" comparing weekday: CUR:{current_weekday} TT:{timetable.weekday}")
|
logger.info(f" checking timetable {timetable.id}")
|
||||||
if current_weekday == timetable.weekday:
|
logger.info(f" comparing weekday: CUR:{current_weekday} TT:{timetable.weekday}")
|
||||||
starttime = datetime.datetime.combine(datetime.date.today(), timetable.starttime)
|
if current_weekday == timetable.weekday:
|
||||||
endtime = starttime + datetime.timedelta(minutes=timetable.duration)
|
starttime = datetime.combine(date.today(), timetable.starttime)
|
||||||
logger.info(f" comparing time: Start:{starttime} Current:{current_time} End:{endtime}")
|
endtime = starttime + timedelta(minutes=timetable.duration)
|
||||||
if starttime < current_time < endtime:
|
logger.info(f" comparing time: Start:{starttime} Current:{current_time} End:{endtime}")
|
||||||
logger.info("Access Valid!")
|
if starttime < current_time < endtime:
|
||||||
|
logger.info("Access Valid!")
|
||||||
|
return True
|
||||||
|
if auth.type == "oneshot":
|
||||||
|
logger.info(f' oneshot auth found: {auth.oneshot}')
|
||||||
|
if current_time < auth.oneshot.ends_at:
|
||||||
|
if auth.oneshot.uses > 0:
|
||||||
|
decrementOneshot(db, auth.oneshot)
|
||||||
return True
|
return True
|
||||||
logger.info("No more auths found")
|
logger.info("No more auths found")
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from fastapi.testclient import TestClient
|
|||||||
from sqlmodel import Session, create_engine, SQLModel
|
from sqlmodel import Session, create_engine, SQLModel
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy.pool import StaticPool
|
from sqlalchemy.pool import StaticPool
|
||||||
|
from datetime import time
|
||||||
|
|
||||||
from app.main import app
|
from app.main import app
|
||||||
from app.model.models import UserDB, Card, GroupDB, AccessAuthorizationDB, Timetable, AaGroupLink
|
from app.model.models import UserDB, Card, GroupDB, AccessAuthorizationDB, Timetable, AaGroupLink
|
||||||
@@ -107,11 +108,18 @@ def test_card(db_session, test_group):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_aa(db_session):
|
def test_aa_tt(db_session):
|
||||||
"""Create a test access authorization."""
|
"""Create a test access authorization with timetable."""
|
||||||
|
tt = Timetable(
|
||||||
|
weekday=1,
|
||||||
|
starttime=time(1, 0, 0, 0),
|
||||||
|
duration=50
|
||||||
|
)
|
||||||
aa = AccessAuthorizationDB(
|
aa = AccessAuthorizationDB(
|
||||||
name="Test AA",
|
name="Test AA",
|
||||||
is_active=True
|
is_active=True,
|
||||||
|
type="timetable",
|
||||||
|
timetables=[tt]
|
||||||
)
|
)
|
||||||
db_session.add(aa)
|
db_session.add(aa)
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def test_group_models():
|
|||||||
def test_access_authorization_models():
|
def test_access_authorization_models():
|
||||||
"""Test access authorization model creation and validation."""
|
"""Test access authorization model creation and validation."""
|
||||||
# Test AccessAuthorizationBase
|
# Test AccessAuthorizationBase
|
||||||
aa_base = AccessAuthorizationBase(name="Test AA", is_active=True)
|
aa_base = AccessAuthorizationBase(name="Test AA", is_active=True, type="timetable")
|
||||||
assert aa_base.name == "Test AA"
|
assert aa_base.name == "Test AA"
|
||||||
assert aa_base.is_active is True
|
assert aa_base.is_active is True
|
||||||
|
|
||||||
@@ -50,6 +50,7 @@ def test_access_authorization_models():
|
|||||||
aa_create = AccessAuthorizationCreate(
|
aa_create = AccessAuthorizationCreate(
|
||||||
name="New AA",
|
name="New AA",
|
||||||
is_active=False,
|
is_active=False,
|
||||||
|
type="timetable",
|
||||||
timetables=[timetable_create]
|
timetables=[timetable_create]
|
||||||
)
|
)
|
||||||
assert aa_create.name == "New AA"
|
assert aa_create.name == "New AA"
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import pytest
|
|||||||
from fastapi import status
|
from fastapi import status
|
||||||
|
|
||||||
|
|
||||||
def test_create_access_auth(client, auth_headers):
|
def test_create_access_auth_tt(client, auth_headers):
|
||||||
"""Test creating a new access authorization."""
|
"""Test creating a new access authorization."""
|
||||||
aa_data = {
|
aa_data = {
|
||||||
"name": "New AA",
|
"name": "New tt_AA",
|
||||||
|
"type": "timetable",
|
||||||
"is_active": True,
|
"is_active": True,
|
||||||
"timetables": [
|
"timetables": [
|
||||||
{"weekday": 1, "starttime": "08:00", "duration": 60},
|
{"weekday": 1, "starttime": "08:00", "duration": 60},
|
||||||
@@ -17,13 +18,46 @@ def test_create_access_auth(client, auth_headers):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data["name"] == "New AA"
|
assert data["name"] == "New tt_AA"
|
||||||
assert data["is_active"] is True
|
assert data["is_active"] is True
|
||||||
|
assert data["type"] == "timetable"
|
||||||
assert "id" in data
|
assert "id" in data
|
||||||
assert len(data["timetables"]) == 2
|
assert len(data["timetables"]) == 2
|
||||||
|
|
||||||
|
def test_create_access_auth_os(client, auth_headers):
|
||||||
|
"""Test creating a new access authorization with oneshot type."""
|
||||||
|
aa_data = {
|
||||||
|
"name": "New os_AA",
|
||||||
|
"type": "oneshot",
|
||||||
|
"is_active": True,
|
||||||
|
"oneshot": {
|
||||||
|
"uses": 1,
|
||||||
|
"ends_at": "2029-07-27"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def test_get_all_access_auths(client, auth_headers, test_aa):
|
response = client.post("/api/v1/aa/", json=aa_data, headers=auth_headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
assert data["name"] == "New os_AA"
|
||||||
|
assert data["type"] == "oneshot"
|
||||||
|
assert data["is_active"] is True
|
||||||
|
assert "id" in data
|
||||||
|
assert data["oneshot"]["uses"] == 1
|
||||||
|
|
||||||
|
def test_create_wrong_aa_type(client, auth_headers):
|
||||||
|
"""Test creating a new access authorization with oneshot type."""
|
||||||
|
aa_data = {
|
||||||
|
"name": "New AA",
|
||||||
|
"type": "wrong",
|
||||||
|
"is_active": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post("/api/v1/aa/", json=aa_data, headers=auth_headers)
|
||||||
|
assert response.status_code == 422
|
||||||
|
|
||||||
|
def test_get_all_access_auths(client, auth_headers, test_aa_tt):
|
||||||
"""Test retrieving all access authorizations."""
|
"""Test retrieving all access authorizations."""
|
||||||
response = client.get("/api/v1/aa/", headers=auth_headers)
|
response = client.get("/api/v1/aa/", headers=auth_headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -32,17 +66,17 @@ def test_get_all_access_auths(client, auth_headers, test_aa):
|
|||||||
assert len(aa_list) >= 1
|
assert len(aa_list) >= 1
|
||||||
|
|
||||||
aa_names = [aa["name"] for aa in aa_list]
|
aa_names = [aa["name"] for aa in aa_list]
|
||||||
assert test_aa.name in aa_names
|
assert test_aa_tt.name in aa_names
|
||||||
|
|
||||||
|
|
||||||
def test_get_access_auth_by_id(client, auth_headers, test_aa):
|
def test_get_access_auth_by_id(client, auth_headers, test_aa_tt):
|
||||||
"""Test retrieving a specific access authorization by ID."""
|
"""Test retrieving a specific access authorization by ID."""
|
||||||
response = client.get(f"/api/v1/aa/{test_aa.id}", headers=auth_headers)
|
response = client.get(f"/api/v1/aa/{test_aa_tt.id}", headers=auth_headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data["id"] == test_aa.id
|
assert data["id"] == test_aa_tt.id
|
||||||
assert data["name"] == test_aa.name
|
assert data["name"] == test_aa_tt.name
|
||||||
|
|
||||||
|
|
||||||
def test_get_nonexistent_access_auth(client, auth_headers):
|
def test_get_nonexistent_access_auth(client, auth_headers):
|
||||||
@@ -51,10 +85,10 @@ def test_get_nonexistent_access_auth(client, auth_headers):
|
|||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_assign_access_auth_to_group(client, auth_headers, test_group, test_aa):
|
def test_assign_access_auth_to_group(client, auth_headers, test_group, test_aa_tt):
|
||||||
"""Test assigning an access authorization to a group."""
|
"""Test assigning an access authorization to a group."""
|
||||||
response = client.put(
|
response = client.put(
|
||||||
f"/api/v1/aa/assign/{test_group.id}/{test_aa.id}",
|
f"/api/v1/aa/assign/{test_group.id}/{test_aa_tt.id}",
|
||||||
headers=auth_headers
|
headers=auth_headers
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -65,14 +99,14 @@ def test_assign_access_auth_to_group(client, auth_headers, test_group, test_aa):
|
|||||||
# Note: The response model might not include the full relationship
|
# Note: The response model might not include the full relationship
|
||||||
|
|
||||||
|
|
||||||
def test_assign_already_assigned_access_auth(client, auth_headers, test_group, test_aa):
|
def test_assign_already_assigned_access_auth(client, auth_headers, test_group, test_aa_tt):
|
||||||
"""Test assigning an already assigned access authorization."""
|
"""Test assigning an already assigned access authorization."""
|
||||||
# First assignment
|
# First assignment
|
||||||
client.put(f"/api/v1/aa/assign/{test_group.id}/{test_aa.id}", headers=auth_headers)
|
client.put(f"/api/v1/aa/assign/{test_group.id}/{test_aa_tt.id}", headers=auth_headers)
|
||||||
|
|
||||||
# Second assignment should indicate it's already assigned
|
# Second assignment should indicate it's already assigned
|
||||||
response = client.put(
|
response = client.put(
|
||||||
f"/api/v1/aa/assign/{test_group.id}/{test_aa.id}",
|
f"/api/v1/aa/assign/{test_group.id}/{test_aa_tt.id}",
|
||||||
headers=auth_headers
|
headers=auth_headers
|
||||||
)
|
)
|
||||||
# According to the code, this returns 409 with "already assigned" message
|
# According to the code, this returns 409 with "already assigned" message
|
||||||
@@ -80,31 +114,31 @@ def test_assign_already_assigned_access_auth(client, auth_headers, test_group, t
|
|||||||
assert "already assigned" in response.json()["detail"].lower()
|
assert "already assigned" in response.json()["detail"].lower()
|
||||||
|
|
||||||
|
|
||||||
def test_unassign_access_auth_from_group(client, auth_headers, test_group, test_aa):
|
def test_unassign_access_auth_from_group(client, auth_headers, test_group, test_aa_tt):
|
||||||
"""Test unassigning an access authorization from a group."""
|
"""Test unassigning an access authorization from a group."""
|
||||||
# First assign
|
# First assign
|
||||||
client.put(f"/api/v1/aa/assign/{test_group.id}/{test_aa.id}", headers=auth_headers)
|
client.put(f"/api/v1/aa/assign/{test_group.id}/{test_aa_tt.id}", headers=auth_headers)
|
||||||
|
|
||||||
# Then unassign
|
# Then unassign
|
||||||
response = client.put(
|
response = client.put(
|
||||||
f"/api/v1/aa/unassign/{test_group.id}/{test_aa.id}",
|
f"/api/v1/aa/unassign/{test_group.id}/{test_aa_tt.id}",
|
||||||
headers=auth_headers
|
headers=auth_headers
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_unassign_nonexistent_assignment(client, auth_headers, test_group, test_aa):
|
def test_unassign_nonexistent_assignment(client, auth_headers, test_group, test_aa_tt):
|
||||||
"""Test unassigning a non-existent assignment."""
|
"""Test unassigning a non-existent assignment."""
|
||||||
response = client.put(
|
response = client.put(
|
||||||
f"/api/v1/aa/unassign/{test_group.id}/{test_aa.id}",
|
f"/api/v1/aa/unassign/{test_group.id}/{test_aa_tt.id}",
|
||||||
headers=auth_headers
|
headers=auth_headers
|
||||||
)
|
)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_assign_to_nonexistent_group(client, auth_headers, test_aa):
|
def test_assign_to_nonexistent_group(client, auth_headers, test_aa_tt):
|
||||||
"""Test assigning an AA to a non-existent group."""
|
"""Test assigning an AA to a non-existent group."""
|
||||||
response = client.put(f"/api/v1/aa/assign/99999/{test_aa.id}", headers=auth_headers)
|
response = client.put(f"/api/v1/aa/assign/99999/{test_aa_tt.id}", headers=auth_headers)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +148,7 @@ def test_assign_nonexistent_aa(client, auth_headers, test_group):
|
|||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_update_access_auth(client, auth_headers, test_aa):
|
def test_update_access_auth(client, auth_headers, test_aa_tt):
|
||||||
"""Test updating an access authorization."""
|
"""Test updating an access authorization."""
|
||||||
update_data = {
|
update_data = {
|
||||||
"name": "Updated AA",
|
"name": "Updated AA",
|
||||||
@@ -122,7 +156,7 @@ def test_update_access_auth(client, auth_headers, test_aa):
|
|||||||
}
|
}
|
||||||
|
|
||||||
response = client.patch(
|
response = client.patch(
|
||||||
f"/api/v1/aa/{test_aa.id}",
|
f"/api/v1/aa/{test_aa_tt.id}",
|
||||||
json=update_data,
|
json=update_data,
|
||||||
headers=auth_headers
|
headers=auth_headers
|
||||||
)
|
)
|
||||||
@@ -133,7 +167,7 @@ def test_update_access_auth(client, auth_headers, test_aa):
|
|||||||
assert data["is_active"] is False
|
assert data["is_active"] is False
|
||||||
|
|
||||||
|
|
||||||
def test_update_access_auth_with_timetables(client, auth_headers, test_aa):
|
def test_update_access_auth_with_timetables(client, auth_headers, test_aa_tt):
|
||||||
"""Test updating an access authorization with new timetables."""
|
"""Test updating an access authorization with new timetables."""
|
||||||
update_data = {
|
update_data = {
|
||||||
"timetables": [
|
"timetables": [
|
||||||
@@ -142,7 +176,7 @@ def test_update_access_auth_with_timetables(client, auth_headers, test_aa):
|
|||||||
}
|
}
|
||||||
|
|
||||||
response = client.patch(
|
response = client.patch(
|
||||||
f"/api/v1/aa/{test_aa.id}",
|
f"/api/v1/aa/{test_aa_tt.id}",
|
||||||
json=update_data,
|
json=update_data,
|
||||||
headers=auth_headers
|
headers=auth_headers
|
||||||
)
|
)
|
||||||
@@ -161,14 +195,14 @@ def test_update_nonexistent_access_auth(client, auth_headers):
|
|||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_delete_access_auth(client, auth_headers, test_aa):
|
def test_delete_access_auth(client, auth_headers, test_aa_tt):
|
||||||
"""Test deleting an access authorization."""
|
"""Test deleting an access authorization."""
|
||||||
response = client.delete(f"/api/v1/aa/{test_aa.id}", headers=auth_headers)
|
response = client.delete(f"/api/v1/aa/{test_aa_tt.id}", headers=auth_headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert "deleted successfully" in response.json()["message"].lower()
|
assert "deleted successfully" in response.json()["message"].lower()
|
||||||
|
|
||||||
# Verify AA is deleted
|
# Verify AA is deleted
|
||||||
response = client.get(f"/api/v1/aa/{test_aa.id}", headers=auth_headers)
|
response = client.get(f"/api/v1/aa/{test_aa_tt.id}", headers=auth_headers)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
@@ -178,7 +212,7 @@ def test_delete_nonexistent_access_auth(client, auth_headers):
|
|||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_aa_operations_by_non_admin(client, test_aa, user_auth_headers):
|
def test_aa_tt_operations_by_non_admin(client, test_aa_tt, user_auth_headers):
|
||||||
"""Test that non-admin users cannot perform AA operations."""
|
"""Test that non-admin users cannot perform AA operations."""
|
||||||
# Try to create an AA
|
# Try to create an AA
|
||||||
response = client.post(
|
response = client.post(
|
||||||
@@ -193,5 +227,5 @@ def test_aa_operations_by_non_admin(client, test_aa, user_auth_headers):
|
|||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
# Try to assign AA
|
# Try to assign AA
|
||||||
response = client.put(f"/api/v1/aa/assign/1/{test_aa.id}", headers=user_auth_headers)
|
response = client.put(f"/api/v1/aa/assign/1/{test_aa_tt.id}", headers=user_auth_headers)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import datetime
|
import datetime
|
||||||
from app.services.door import checkAccess
|
from app.services.door import checkAccess
|
||||||
from app.model.models import Card, GroupDB, AccessAuthorizationDB, Timetable
|
from app.model.models import Card, GroupDB, AccessAuthorizationDB, Timetable, OneShotAccess
|
||||||
|
|
||||||
def test_check_access_with_valid_timetable(db_session):
|
def test_check_access_with_valid_timetable(db_session):
|
||||||
# Setup: create card with valid access
|
# Setup: create card with valid access
|
||||||
@@ -19,7 +19,7 @@ def test_check_access_with_valid_timetable(db_session):
|
|||||||
)
|
)
|
||||||
db_session.add(timetable)
|
db_session.add(timetable)
|
||||||
|
|
||||||
aa = AccessAuthorizationDB(name="Test AA", is_active=True)
|
aa = AccessAuthorizationDB(name="Test AA", is_active=True, type="timetable")
|
||||||
db_session.add(aa)
|
db_session.add(aa)
|
||||||
aa.timetables = [timetable]
|
aa.timetables = [timetable]
|
||||||
group.accessauths = [aa]
|
group.accessauths = [aa]
|
||||||
@@ -46,7 +46,7 @@ def test_check_access_outside_hours(db_session):
|
|||||||
)
|
)
|
||||||
db_session.add(timetable)
|
db_session.add(timetable)
|
||||||
|
|
||||||
aa = AccessAuthorizationDB(name="Test AA", is_active=True)
|
aa = AccessAuthorizationDB(name="Test AA", is_active=True, type="timetable")
|
||||||
db_session.add(aa)
|
db_session.add(aa)
|
||||||
aa.timetables = [timetable]
|
aa.timetables = [timetable]
|
||||||
group.accessauths = [aa]
|
group.accessauths = [aa]
|
||||||
@@ -55,6 +55,33 @@ def test_check_access_outside_hours(db_session):
|
|||||||
result = checkAccess("test-key-123", db_session)
|
result = checkAccess("test-key-123", db_session)
|
||||||
assert result == False
|
assert result == False
|
||||||
|
|
||||||
|
def test_check_access_with_valid_oneshot(db_session):
|
||||||
|
# Setup: create card with valid access
|
||||||
|
group = GroupDB(name="Test Group")
|
||||||
|
db_session.add(group)
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
card = Card(key="test-key-123", group_id=group.id, enabled=True, name="test_card", card_serial="00:00:00:00:00:00:00")
|
||||||
|
db_session.add(card)
|
||||||
|
|
||||||
|
oneshot = OneShotAccess(
|
||||||
|
uses=1,
|
||||||
|
ends_at=datetime.datetime.now() + datetime.timedelta(days=1)
|
||||||
|
)
|
||||||
|
db_session.add(oneshot)
|
||||||
|
|
||||||
|
aa = AccessAuthorizationDB(name="Test AA", is_active=True, type="oneshot")
|
||||||
|
db_session.add(aa)
|
||||||
|
aa.oneshot = oneshot
|
||||||
|
group.accessauths = [aa]
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
# Test: access should be granted within time window
|
||||||
|
result = checkAccess("test-key-123", db_session)
|
||||||
|
assert result == True
|
||||||
|
assert aa.oneshot.uses == 0
|
||||||
|
|
||||||
def test_check_access_invalid_card(db_session):
|
def test_check_access_invalid_card(db_session):
|
||||||
# Should raise exception for non-existent card
|
# Should raise exception for non-existent card
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
|
|||||||
Reference in New Issue
Block a user