feat: Implement GetTaskInfo and create user manual
This commit is contained in:
1 parent
dd8fe01f86
commit
3ab411d623
9 files changed
+5935
-145
No files matched your search
+123
@@ -0,0 +1,123 @@
|
||||
# AutoStore Simulator - Benutzerhandbuch
|
||||
|
||||
Dieses Dokument beschreibt die Verwendung der XML-API des AutoStore Simulators.
|
||||
|
||||
## API-Endpunkt
|
||||
|
||||
Alle Anfragen werden als HTTP POST an den folgenden Endpunkt gesendet:
|
||||
|
||||
```http
|
||||
/api
|
||||
```
|
||||
|
||||
Der `Content-Type` Header muss auf `application/xml` gesetzt sein.
|
||||
|
||||
## Befehle
|
||||
|
||||
### 1. CreateTaskgroup
|
||||
|
||||
Erstellt eine neue Aufgabengruppe (Task Group) mit einer oder mehreren Aufgaben. Dies ist der primäre Befehl, um dem System einen neuen Auftrag zu erteilen.
|
||||
|
||||
**Anfrage:**
|
||||
|
||||
```xml
|
||||
<AutoStore>
|
||||
<CreateTaskgroup>
|
||||
<Taskgroup>TG-XML-01</Taskgroup>
|
||||
<Port>P-XML-01</Port>
|
||||
<Tasks>
|
||||
<Task>
|
||||
<Task>TASK-XML-01</Task>
|
||||
<Bin>BIN-XML-01</Bin>
|
||||
</Task>
|
||||
<Task>
|
||||
<Task>TASK-XML-02</Task>
|
||||
<Bin>BIN-XML-02</Bin>
|
||||
</Task>
|
||||
</Tasks>
|
||||
</CreateTaskgroup>
|
||||
</AutoStore>
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
|
||||
- `<Taskgroup>`: Eine eindeutige ID für die neue Aufgabengruppe.
|
||||
- `<Port>`: Die ID des Ports, an den die Behälter geliefert werden sollen.
|
||||
- `<Tasks>`: Eine Liste von Aufgaben.
|
||||
- `<Task>`: Die eindeutige ID für die einzelne Aufgabe.
|
||||
- `<Bin>`: Die ID des Behälters, der für diese Aufgabe benötigt wird.
|
||||
|
||||
**Erfolgsantwort:**
|
||||
|
||||
```xml
|
||||
<AutoStore>
|
||||
<CreateTaskgroup>
|
||||
<ExecutionTime>123.45</ExecutionTime>
|
||||
</CreateTaskgroup>
|
||||
</AutoStore>
|
||||
```
|
||||
|
||||
### 2. GetTaskInfo
|
||||
|
||||
Fragt den Status und die Details einer bestimmten Aufgabengruppe oder einer einzelnen Aufgabe ab.
|
||||
|
||||
**Anfrage (für eine Task Group):**
|
||||
|
||||
```xml
|
||||
<AutoStore>
|
||||
<GetTaskInfo>
|
||||
<Taskgroup>TG-XML-01</Taskgroup>
|
||||
</GetTaskInfo>
|
||||
</AutoStore>
|
||||
```
|
||||
|
||||
**Anfrage (für eine einzelne Task):**
|
||||
|
||||
```xml
|
||||
<AutoStore>
|
||||
<GetTaskInfo>
|
||||
<Task>TASK-XML-01</Task>
|
||||
</GetTaskInfo>
|
||||
</AutoStore>
|
||||
```
|
||||
|
||||
**Erfolgsantwort (für eine Task Group):**
|
||||
|
||||
```xml
|
||||
<AutoStore>
|
||||
<GetTaskInfo>
|
||||
<Taskgroup>
|
||||
<Taskgroup>TG-XML-01</Taskgroup>
|
||||
<Port>P-XML-01</Port>
|
||||
<Status>PENDING</Status>
|
||||
<Tasks>
|
||||
<Task>
|
||||
<Task>TASK-XML-01</Task>
|
||||
<Bin>BIN-XML-01</Bin>
|
||||
<Status>PENDING</Status>
|
||||
</Task>
|
||||
<Task>
|
||||
<Task>TASK-XML-02</Task>
|
||||
<Bin>BIN-XML-02</Bin>
|
||||
<Status>PENDING</Status>
|
||||
</Task>
|
||||
</Tasks>
|
||||
</Taskgroup>
|
||||
</GetTaskInfo>
|
||||
</AutoStore>
|
||||
```
|
||||
|
||||
### Fehlerbehandlung
|
||||
|
||||
Wenn eine Anfrage fehlschlägt (z.B. weil eine ID nicht gefunden wurde), gibt die API eine XML-Fehlermeldung mit einem entsprechenden HTTP-Statuscode zurück (z.B. 404 Not Found).
|
||||
|
||||
**Beispiel für eine Fehlerantwort:**
|
||||
|
||||
```xml
|
||||
<AutoStore>
|
||||
<Error>
|
||||
<Code>RequestError</Code>
|
||||
<Description>Taskgroup not found: NON-EXISTENT-GROUP</Description>
|
||||
</Error>
|
||||
</AutoStore>
|
||||
```
|
||||
@@ -6,3 +6,10 @@ uvicorn[standard]
|
||||
sqlalchemy
|
||||
alembic
|
||||
psycopg2-binary
|
||||
|
||||
# Testing
|
||||
pytest
|
||||
httpx
|
||||
|
||||
# XML Handling
|
||||
xmltodict
|
||||
File diff suppressed because it is too large.
Load diff
+22
-9
@@ -97,29 +97,42 @@ def create_task_group(db: Session, task_group: schemas.TaskGroupCreate):
|
||||
"""
|
||||
Create a new task group and its associated tasks.
|
||||
"""
|
||||
# Create the TaskGroup
|
||||
# 1. Check if related Port and Bins exist
|
||||
port = get_port(db, port_id=task_group.port_id)
|
||||
if not port:
|
||||
raise ValueError(f"Port not found: {task_group.port_id}")
|
||||
for task in task_group.tasks:
|
||||
bin = get_bin(db, bin_id=task.bin_id)
|
||||
if not bin:
|
||||
raise ValueError(f"Bin not found: {task.bin_id}")
|
||||
|
||||
# 2. Create the TaskGroup instance
|
||||
db_task_group = models.TaskGroup(
|
||||
description=task_group.description, status="PENDING"
|
||||
id=task_group.id,
|
||||
port_id=task_group.port_id,
|
||||
status="PENDING"
|
||||
)
|
||||
db.add(db_task_group)
|
||||
db.commit()
|
||||
db.refresh(db_task_group)
|
||||
|
||||
# Create the associated Tasks
|
||||
# 3. Create the associated Task instances
|
||||
for task_data in task_group.tasks:
|
||||
db_task = models.Task(
|
||||
**task_data.model_dump(),
|
||||
id=task_data.id,
|
||||
task_group_id=db_task_group.id,
|
||||
bin_id=task_data.bin_id,
|
||||
task_type=task_data.task_type,
|
||||
status="PENDING"
|
||||
)
|
||||
db.add(db_task)
|
||||
|
||||
# 4. Commit all changes
|
||||
db.commit()
|
||||
db.refresh(db_task_group)
|
||||
return db_task_group
|
||||
# Return a dummy execution time for now, as per spec
|
||||
return "123.45"
|
||||
|
||||
|
||||
def get_task_group(db: Session, group_id: int):
|
||||
def get_task_group(db: Session, group_id: str):
|
||||
"""
|
||||
Retrieve a single task group by its ID.
|
||||
"""
|
||||
@@ -127,7 +140,7 @@ def get_task_group(db: Session, group_id: int):
|
||||
db.query(models.TaskGroup).filter(models.TaskGroup.id == group_id).first()
|
||||
)
|
||||
|
||||
def get_task(db: Session, task_id: int):
|
||||
def get_task(db: Session, task_id: str):
|
||||
"""
|
||||
Retrieve a single task by its ID.
|
||||
"""
|
||||
|
||||
+165
-52
@@ -6,6 +6,20 @@ from typing import List
|
||||
from . import crud, models, schemas
|
||||
from .database import SessionLocal, engine, Base
|
||||
|
||||
from fastapi import FastAPI, Depends, HTTPException, Request, Response
|
||||
from sqlalchemy.orm import Session
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import List
|
||||
import xmltodict
|
||||
import logging
|
||||
|
||||
from . import crud, models, schemas
|
||||
from .database import SessionLocal, engine, Base
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Actions on startup
|
||||
@@ -28,6 +42,142 @@ def get_db():
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@app.post("/api", tags=["XML Interface"])
|
||||
async def handle_xml_request(request: Request, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Handles all incoming XML requests for the AutoStore Task Interface.
|
||||
"""
|
||||
try:
|
||||
body = await request.body()
|
||||
# Ensure body is not empty
|
||||
if not body:
|
||||
raise HTTPException(status_code=400, detail="Request body is empty.")
|
||||
|
||||
data = xmltodict.parse(body)
|
||||
|
||||
if "AutoStore" not in data:
|
||||
raise HTTPException(status_code=400, detail="Invalid XML: Missing <AutoStore> root element.")
|
||||
|
||||
autostore_data = data["AutoStore"]
|
||||
# The command is the first key inside the <AutoStore> tag
|
||||
command = list(autostore_data.keys())[0]
|
||||
|
||||
response_dict = {}
|
||||
|
||||
if command == "CreateTaskgroup":
|
||||
try:
|
||||
taskgroup_data = autostore_data[command]
|
||||
|
||||
tasks_in = taskgroup_data.get("Tasks", {}).get("Task", [])
|
||||
if not isinstance(tasks_in, list):
|
||||
tasks_in = [tasks_in]
|
||||
|
||||
tasks = [schemas.TaskCreate(id=t["Task"], bin_id=t["Bin"], task_type="OUTBOUND") for t in tasks_in]
|
||||
|
||||
task_group_create = schemas.TaskGroupCreate(
|
||||
id=taskgroup_data["Taskgroup"],
|
||||
port_id=taskgroup_data["Port"],
|
||||
tasks=tasks
|
||||
)
|
||||
|
||||
execution_time = crud.create_task_group(db=db, task_group=task_group_create)
|
||||
|
||||
response_dict = {
|
||||
"AutoStore": {
|
||||
"CreateTaskgroup": {
|
||||
"ExecutionTime": execution_time
|
||||
}
|
||||
}
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
|
||||
elif command == "GetTaskInfo":
|
||||
task_id = autostore_data[command].get("Task")
|
||||
group_id = autostore_data[command].get("Taskgroup")
|
||||
|
||||
if not task_id and not group_id:
|
||||
raise ValueError("Either Task or Taskgroup ID must be provided for GetTaskInfo.")
|
||||
|
||||
if group_id:
|
||||
task_group = crud.get_task_group(db, group_id=group_id)
|
||||
if not task_group:
|
||||
raise ValueError(f"Taskgroup not found: {group_id}")
|
||||
|
||||
response_dict = {
|
||||
"AutoStore": {
|
||||
"GetTaskInfo": {
|
||||
"Taskgroup": {
|
||||
"Taskgroup": task_group.id,
|
||||
"Port": task_group.port_id,
|
||||
"Status": task_group.status,
|
||||
"Tasks": {
|
||||
"Task": [
|
||||
{
|
||||
"Task": t.id,
|
||||
"Bin": t.bin_id,
|
||||
"Status": t.status
|
||||
} for t in task_group.tasks
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else: # task_id must be present
|
||||
task = crud.get_task(db, task_id=task_id)
|
||||
if not task:
|
||||
raise ValueError(f"Task not found: {task_id}")
|
||||
|
||||
response_dict = {
|
||||
"AutoStore": {
|
||||
"GetTaskInfo": {
|
||||
"Task": {
|
||||
"Task": task.id,
|
||||
"Bin": task.bin_id,
|
||||
"Status": task.status,
|
||||
"Taskgroup": task.task_group_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else:
|
||||
# Placeholder for other commands
|
||||
raise HTTPException(status_code=400, detail=f"Unknown or unimplemented command: {command}")
|
||||
|
||||
response_xml = xmltodict.unparse(response_dict, pretty=True)
|
||||
return Response(content=response_xml, media_type="application/xml")
|
||||
|
||||
except (HTTPException, ValueError) as e:
|
||||
# Convert FastAPI's default JSON error response to our XML format
|
||||
status_code = e.status_code if isinstance(e, HTTPException) else 404
|
||||
detail = e.detail if isinstance(e, HTTPException) else str(e)
|
||||
|
||||
error_response = {
|
||||
"AutoStore": {
|
||||
"Error": {
|
||||
"Code": "RequestError",
|
||||
"Description": detail
|
||||
}
|
||||
}
|
||||
}
|
||||
error_xml = xmltodict.unparse(error_response, pretty=True)
|
||||
return Response(content=error_xml, status_code=status_code, media_type="application/xml")
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing XML request: {e}", exc_info=True)
|
||||
# Create a generic XML error response
|
||||
error_response = {
|
||||
"AutoStore": {
|
||||
"Error": {
|
||||
"Code": "InternalError",
|
||||
"Description": str(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
error_xml = xmltodict.unparse(error_response, pretty=True)
|
||||
return Response(content=error_xml, status_code=500, media_type="application/xml")
|
||||
|
||||
|
||||
@app.get("/health", tags=["System"])
|
||||
def read_health():
|
||||
"""
|
||||
@@ -35,35 +185,35 @@ def read_health():
|
||||
"""
|
||||
return {"status": "ok"}
|
||||
|
||||
# --- BIN Endpoints ---
|
||||
# --- BIN Endpoints (for admin/debug) ---
|
||||
|
||||
@app.post("/bins/", response_model=schemas.Bin, tags=["BINs"])
|
||||
@app.post("/bins/", response_model=schemas.Bin, tags=["Admin - Bins"])
|
||||
def create_bin_endpoint(bin: schemas.BinCreate, db: Session = Depends(get_db)):
|
||||
db_bin = crud.get_bin(db, bin_id=bin.id)
|
||||
if db_bin:
|
||||
raise HTTPException(status_code=400, detail="Bin already registered")
|
||||
return crud.create_bin(db=db, bin=bin)
|
||||
|
||||
@app.get("/bins/", response_model=list[schemas.Bin], tags=["BINs"])
|
||||
@app.get("/bins/", response_model=list[schemas.Bin], tags=["Admin - Bins"])
|
||||
def read_bins_endpoint(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
bins = crud.get_bins(db, skip=skip, limit=limit)
|
||||
return bins
|
||||
|
||||
@app.get("/bins/{bin_id}", response_model=schemas.Bin, tags=["BINs"])
|
||||
@app.get("/bins/{bin_id}", response_model=schemas.Bin, tags=["Admin - Bins"])
|
||||
def read_bin_endpoint(bin_id: str, db: Session = Depends(get_db)):
|
||||
db_bin = crud.get_bin(db, bin_id=bin_id)
|
||||
if db_bin is None:
|
||||
raise HTTPException(status_code=404, detail="Bin not found")
|
||||
return db_bin
|
||||
|
||||
@app.delete("/bins/{bin_id}", response_model=schemas.Bin, tags=["BINs"])
|
||||
@app.delete("/bins/{bin_id}", response_model=schemas.Bin, tags=["Admin - Bins"])
|
||||
def delete_bin_endpoint(bin_id: str, db: Session = Depends(get_db)):
|
||||
db_bin = crud.delete_bin(db, bin_id=bin_id)
|
||||
if db_bin is None:
|
||||
raise HTTPException(status_code=404, detail="Bin not found")
|
||||
return db_bin
|
||||
|
||||
@app.post("/bins/generate", status_code=201)
|
||||
@app.post("/bins/generate", status_code=201, tags=["Admin - Bins"])
|
||||
def generate_bins(
|
||||
bin_generation: schemas.BinGenerationRequest, db: Session = Depends(get_db)
|
||||
):
|
||||
@@ -76,9 +226,9 @@ def generate_bins(
|
||||
"count": len(created_bins),
|
||||
}
|
||||
|
||||
# --- Port Endpoints ---
|
||||
# --- Port Endpoints (for admin/debug) ---
|
||||
|
||||
@app.post("/ports/", response_model=schemas.Port, status_code=201)
|
||||
@app.post("/ports/", response_model=schemas.Port, status_code=201, tags=["Admin - Ports"])
|
||||
def create_port(port: schemas.PortCreate, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Create a new port.
|
||||
@@ -89,7 +239,7 @@ def create_port(port: schemas.PortCreate, db: Session = Depends(get_db)):
|
||||
return crud.create_port(db=db, port=port)
|
||||
|
||||
|
||||
@app.get("/ports/", response_model=List[schemas.Port])
|
||||
@app.get("/ports/", response_model=List[schemas.Port], tags=["Admin - Ports"])
|
||||
def read_ports(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Retrieve all ports.
|
||||
@@ -98,7 +248,7 @@ def read_ports(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
return ports
|
||||
|
||||
|
||||
@app.get("/ports/{port_id}", response_model=schemas.Port)
|
||||
@app.get("/ports/{port_id}", response_model=schemas.Port, tags=["Admin - Ports"])
|
||||
def read_port(port_id: str, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Retrieve a single port by its ID.
|
||||
@@ -108,7 +258,10 @@ def read_port(port_id: str, db: Session = Depends(get_db)):
|
||||
raise HTTPException(status_code=404, detail="Port not found")
|
||||
return db_port
|
||||
|
||||
@app.post("/ports/{port_id}/arrive", response_model=schemas.Port)
|
||||
# The following endpoints simulate external hardware/WMS actions
|
||||
# and are kept for testing the simulation logic.
|
||||
|
||||
@app.post("/ports/{port_id}/arrive", response_model=schemas.Port, tags=["Admin - Ports"])
|
||||
def port_arrive(
|
||||
port_id: str,
|
||||
arrival: schemas.PortArrivalRequest,
|
||||
@@ -133,7 +286,7 @@ def port_arrive(
|
||||
return crud.port_arrive(db=db, port_id=port_id, bin_id=arrival.bin_id)
|
||||
|
||||
|
||||
@app.post("/ports/{port_id}/depart", response_model=schemas.Port)
|
||||
@app.post("/ports/{port_id}/depart", response_model=schemas.Port, tags=["Admin - Ports"])
|
||||
def port_depart(port_id: str, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Signal that a bin has been collected from an outbound port.
|
||||
@@ -150,46 +303,6 @@ def port_depart(port_id: str, db: Session = Depends(get_db)):
|
||||
|
||||
return crud.port_depart(db=db, port_id=port_id)
|
||||
|
||||
# --- TaskGroup and Task Endpoints ---
|
||||
|
||||
@app.post("/task_groups/", response_model=schemas.TaskGroup, status_code=201)
|
||||
def create_task_group(
|
||||
task_group: schemas.TaskGroupCreate, db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Create a new task group with a list of tasks.
|
||||
"""
|
||||
# Validate that all bins for the tasks exist
|
||||
for task in task_group.tasks:
|
||||
db_bin = crud.get_bin(db, bin_id=task.bin_id)
|
||||
if not db_bin:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Bin with id {task.bin_id} not found"
|
||||
)
|
||||
|
||||
return crud.create_task_group(db=db, task_group=task_group)
|
||||
|
||||
|
||||
@app.get("/task_groups/{group_id}", response_model=schemas.TaskGroup)
|
||||
def read_task_group(group_id: int, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Retrieve a single task group by its ID.
|
||||
"""
|
||||
db_task_group = crud.get_task_group(db, group_id=group_id)
|
||||
if db_task_group is None:
|
||||
raise HTTPException(status_code=404, detail="TaskGroup not found")
|
||||
return db_task_group
|
||||
|
||||
|
||||
@app.get("/tasks/{task_id}", response_model=schemas.Task)
|
||||
def read_task(task_id: int, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Retrieve a single task by its ID.
|
||||
"""
|
||||
db_task = crud.get_task(db, task_id=task_id)
|
||||
if db_task is None:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
return db_task
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
+5
-4
@@ -12,18 +12,19 @@ class Bin(Base):
|
||||
class TaskGroup(Base):
|
||||
__tablename__ = "task_groups"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
description = Column(String, index=True)
|
||||
id = Column(String, primary_key=True, index=True)
|
||||
port_id = Column(String, ForeignKey("ports.id"))
|
||||
status = Column(String, default="PENDING", nullable=False)
|
||||
|
||||
tasks = relationship("Task", back_populates="task_group")
|
||||
port = relationship("Port")
|
||||
|
||||
|
||||
class Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
task_group_id = Column(Integer, ForeignKey("task_groups.id"))
|
||||
id = Column(String, primary_key=True, index=True)
|
||||
task_group_id = Column(String, ForeignKey("task_groups.id"))
|
||||
bin_id = Column(String, ForeignKey("bins.id"))
|
||||
task_type = Column(String, nullable=False)
|
||||
status = Column(String, default="PENDING", nullable=False)
|
||||
|
||||
+7
-5
@@ -4,8 +4,9 @@ from enum import Enum
|
||||
|
||||
# --- Task Schemas ---
|
||||
class TaskBase(BaseModel):
|
||||
task_type: str
|
||||
id: str
|
||||
bin_id: str
|
||||
task_type: str
|
||||
|
||||
|
||||
class TaskCreate(TaskBase):
|
||||
@@ -13,16 +14,16 @@ class TaskCreate(TaskBase):
|
||||
|
||||
|
||||
class Task(TaskBase):
|
||||
id: int
|
||||
status: str
|
||||
task_group_id: int
|
||||
task_group_id: str
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# --- TaskGroup Schemas ---
|
||||
class TaskGroupBase(BaseModel):
|
||||
description: Optional[str] = None
|
||||
id: str
|
||||
port_id: str
|
||||
|
||||
|
||||
class TaskGroupCreate(TaskGroupBase):
|
||||
@@ -30,12 +31,13 @@ class TaskGroupCreate(TaskGroupBase):
|
||||
|
||||
|
||||
class TaskGroup(TaskGroupBase):
|
||||
id: int
|
||||
status: str
|
||||
tasks: List[Task] = []
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
# --- Bin Schemas ---
|
||||
class BinBase(BaseModel):
|
||||
id: str
|
||||
|
||||
+347
-75
@@ -5,7 +5,8 @@ from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from src.main import app, get_db
|
||||
from src.database import Base
|
||||
from src import crud
|
||||
import xmltodict
|
||||
from src import crud, schemas
|
||||
|
||||
# --- Test Database Setup ---
|
||||
TEST_DATABASE_URL = "sqlite:///./test.db"
|
||||
@@ -198,19 +199,20 @@ def test_port_arrive(client):
|
||||
assert response_bin_not_found.status_code == 404
|
||||
|
||||
|
||||
def test_port_depart(client):
|
||||
def test_port_depart(client, db_session):
|
||||
"""Tests the depart logic for an outbound port."""
|
||||
# 1. Create a BIN and an OUTBOUND port, and manually set it to OCCUPIED for the test
|
||||
# 1. Create a BIN and an OUTBOUND port
|
||||
bin_id = "BIN-DEPART-01"
|
||||
port_id = "P-OUT-02"
|
||||
client.post("/bins/", json={"id": bin_id, "content_code": 1})
|
||||
client.post("/ports/", json={"id": port_id, "port_type": "OUTBOUND"})
|
||||
crud.create_bin(db_session, schemas.BinCreate(id=bin_id, content_code=1))
|
||||
port = crud.create_port(db_session, schemas.PortCreate(id=port_id, port_type="OUTBOUND"))
|
||||
|
||||
# Manually occupy the port for the test scenario
|
||||
db_port = crud.get_port(next(app.dependency_overrides[get_db]()), port_id=port_id)
|
||||
db_port.status = "OCCUPIED"
|
||||
db_port.bin_id = bin_id
|
||||
next(app.dependency_overrides[get_db]()).commit()
|
||||
port.status = "OCCUPIED"
|
||||
port.bin_id = bin_id
|
||||
db_session.add(port)
|
||||
db_session.commit()
|
||||
db_session.refresh(port)
|
||||
|
||||
# 2. Signal departure
|
||||
response = client.post(f"/ports/{port_id}/depart")
|
||||
@@ -230,78 +232,348 @@ def test_port_depart(client):
|
||||
assert response_wrong_type.status_code == 400
|
||||
|
||||
|
||||
# --- TaskGroup and Task Tests ---
|
||||
# --- XML Interface Tests ---
|
||||
|
||||
def test_create_task_group(client):
|
||||
"""Tests creating a new task group with associated tasks."""
|
||||
# 1. Create some bins first
|
||||
bin_id_1 = "TG-BIN-01"
|
||||
bin_id_2 = "TG-BIN-02"
|
||||
client.post("/bins/", json={"id": bin_id_1, "content_code": 1})
|
||||
client.post("/bins/", json={"id": bin_id_2, "content_code": 1})
|
||||
def test_create_taskgroup_xml(client, db_session):
|
||||
"""Tests creating a task group via the XML interface."""
|
||||
# 1. Create necessary Bins and Port in the database first
|
||||
port_id = "P-XML-01"
|
||||
bin_id_1 = "BIN-XML-01"
|
||||
bin_id_2 = "BIN-XML-02"
|
||||
crud.create_port(db_session, schemas.PortCreate(id=port_id, port_type="OUTBOUND"))
|
||||
crud.create_bin(db_session, schemas.BinCreate(id=bin_id_1, content_code=1))
|
||||
crud.create_bin(db_session, schemas.BinCreate(id=bin_id_2, content_code=1))
|
||||
db_session.commit()
|
||||
|
||||
# 2. Define the task group
|
||||
task_group_data = {
|
||||
"description": "Customer Order 123",
|
||||
"tasks": [
|
||||
{"task_type": "OUTBOUND", "bin_id": bin_id_1},
|
||||
{"task_type": "OUTBOUND", "bin_id": bin_id_2},
|
||||
],
|
||||
}
|
||||
# 2. Construct the XML request body
|
||||
xml_request = f"""
|
||||
<AutoStore>
|
||||
<CreateTaskgroup>
|
||||
<Taskgroup>TG-XML-01</Taskgroup>
|
||||
<Port>{port_id}</Port>
|
||||
<Tasks>
|
||||
<Task>
|
||||
<Task>TASK-XML-01</Task>
|
||||
<Bin>{bin_id_1}</Bin>
|
||||
</Task>
|
||||
<Task>
|
||||
<Task>TASK-XML-02</Task>
|
||||
<Bin>{bin_id_2}</Bin>
|
||||
</Task>
|
||||
</Tasks>
|
||||
</CreateTaskgroup>
|
||||
</AutoStore>
|
||||
"""
|
||||
|
||||
# 3. Create the task group
|
||||
response = client.post("/task_groups/", json=task_group_data)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["description"] == "Customer Order 123"
|
||||
assert data["status"] == "PENDING"
|
||||
assert len(data["tasks"]) == 2
|
||||
assert data["tasks"][0]["bin_id"] == bin_id_1
|
||||
assert data["tasks"][0]["status"] == "PENDING"
|
||||
# 3. Send the request to the /api endpoint
|
||||
headers = {"Content-Type": "application/xml"}
|
||||
response = client.post("/api", content=xml_request, headers=headers)
|
||||
|
||||
def test_read_task_group(client):
|
||||
"""Tests reading a task group."""
|
||||
# 1. Create a task group
|
||||
bin_id = "TG-BIN-03"
|
||||
client.post("/bins/", json={"id": bin_id, "content_code": 1})
|
||||
task_group_data = {
|
||||
"description": "Single Task Group",
|
||||
"tasks": [{"task_type": "INBOUND", "bin_id": bin_id}],
|
||||
}
|
||||
create_response = client.post("/task_groups/", json=task_group_data)
|
||||
group_id = create_response.json()["id"]
|
||||
# 4. Assert the response
|
||||
assert response.status_code == 200
|
||||
assert "application/xml" in response.headers["content-type"]
|
||||
|
||||
# 5. Parse the XML response and validate its content
|
||||
response_data = xmltodict.parse(response.content)
|
||||
assert "AutoStore" in response_data
|
||||
assert "CreateTaskgroup" in response_data["AutoStore"]
|
||||
assert "ExecutionTime" in response_data["AutoStore"]["CreateTaskgroup"]
|
||||
|
||||
# 6. Verify that the data was actually created in the database
|
||||
db_task_group = crud.get_task_group(db_session, "TG-XML-01")
|
||||
assert db_task_group is not None
|
||||
assert db_task_group.id == "TG-XML-01"
|
||||
assert db_task_group.port_id == port_id
|
||||
assert len(db_task_group.tasks) == 2
|
||||
assert db_task_group.tasks[0].id == "TASK-XML-01"
|
||||
assert db_task_group.tasks[1].bin_id == bin_id_2
|
||||
assert db_task_group.status == "PENDING"
|
||||
assert db_task_group.tasks[0].status == "PENDING"
|
||||
|
||||
# 2. Read the task group
|
||||
read_response = client.get(f"/task_groups/{group_id}")
|
||||
assert read_response.status_code == 200
|
||||
data = read_response.json()
|
||||
assert data["id"] == group_id
|
||||
assert len(data["tasks"]) == 1
|
||||
|
||||
def test_read_task(client):
|
||||
"""Tests reading a single task."""
|
||||
# 1. Create a task group with one task
|
||||
bin_id = "TG-BIN-04"
|
||||
client.post("/bins/", json={"id": bin_id, "content_code": 1})
|
||||
task_group_data = {
|
||||
"description": "Group for single task read",
|
||||
"tasks": [{"task_type": "OUTBOUND", "bin_id": bin_id}],
|
||||
}
|
||||
create_response = client.post("/task_groups/", json=task_group_data)
|
||||
task_id = create_response.json()["tasks"][0]["id"]
|
||||
def test_create_task_group_fails_if_port_not_found_xml(client, db_session):
|
||||
"""Tests that creating a task group via XML fails if the port does not exist."""
|
||||
# 1. Create Bins
|
||||
crud.create_bin(db_session, schemas.BinCreate(id="BIN-TG-F-01", content_code=1))
|
||||
db_session.commit()
|
||||
|
||||
# 2. Read the task
|
||||
read_response = client.get(f"/tasks/{task_id}")
|
||||
assert read_response.status_code == 200
|
||||
data = read_response.json()
|
||||
assert data["id"] == task_id
|
||||
assert data["bin_id"] == bin_id
|
||||
# 2. Construct the XML request body with a non-existent port
|
||||
xml_request = """
|
||||
<AutoStore>
|
||||
<CreateTaskgroup>
|
||||
<Taskgroup>TG-FAIL-XML-01</Taskgroup>
|
||||
<Port>NON-EXISTENT-PORT</Port>
|
||||
<Tasks>
|
||||
<Task>
|
||||
<Task>T-FAIL-XML-01</Task>
|
||||
<Bin>BIN-TG-F-01</Bin>
|
||||
</Task>
|
||||
</Tasks>
|
||||
</CreateTaskgroup>
|
||||
</AutoStore>
|
||||
"""
|
||||
|
||||
def test_create_task_group_with_nonexistent_bin_fails(client):
|
||||
"""Tests that creating a task group with a non-existent bin fails."""
|
||||
task_group_data = {
|
||||
"description": "Failure case",
|
||||
"tasks": [{"task_type": "OUTBOUND", "bin_id": "FAKE-BIN-99"}],
|
||||
}
|
||||
response = client.post("/task_groups/", json=task_group_data)
|
||||
# 3. Send the request
|
||||
headers = {"Content-Type": "application/xml"}
|
||||
response = client.post("/api", content=xml_request, headers=headers)
|
||||
|
||||
# 4. Assert the response
|
||||
assert response.status_code == 404
|
||||
response_data = xmltodict.parse(response.content)
|
||||
assert "Error" in response_data["AutoStore"]
|
||||
assert "Port not found" in response_data["AutoStore"]["Error"]["Description"]
|
||||
|
||||
|
||||
def test_create_task_group_fails_if_bin_not_found_xml(client, db_session):
|
||||
"""Tests that creating a task group via XML fails if a bin does not exist."""
|
||||
# 1. Create Port
|
||||
crud.create_port(db_session, schemas.PortCreate(id="P-TG-F-01", port_type="OUTBOUND"))
|
||||
db_session.commit()
|
||||
|
||||
# 2. Construct the XML request body with a non-existent bin
|
||||
xml_request = """
|
||||
<AutoStore>
|
||||
<CreateTaskgroup>
|
||||
<Taskgroup>TG-FAIL-XML-02</Taskgroup>
|
||||
<Port>P-TG-F-01</Port>
|
||||
<Tasks>
|
||||
<Task>
|
||||
<Task>T-FAIL-XML-02</Task>
|
||||
<Bin>NON-EXISTENT-BIN</Bin>
|
||||
</Task>
|
||||
</Tasks>
|
||||
</CreateTaskgroup>
|
||||
</AutoStore>
|
||||
"""
|
||||
|
||||
# 3. Send the request
|
||||
headers = {"Content-Type": "application/xml"}
|
||||
response = client.post("/api", content=xml_request, headers=headers)
|
||||
|
||||
# 4. Assert the response
|
||||
assert response.status_code == 404
|
||||
response_data = xmltodict.parse(response.content)
|
||||
assert "Error" in response_data["AutoStore"]
|
||||
assert "Bin not found" in response_data["AutoStore"]["Error"]["Description"]
|
||||
|
||||
|
||||
def test_create_task_group_fails_if_port_not_found_xml(client, db_session):
|
||||
"""Tests that creating a task group via XML fails if the port does not exist."""
|
||||
# 1. Create Bins
|
||||
crud.create_bin(db_session, schemas.BinCreate(id="BIN-TG-F-01", content_code=1))
|
||||
db_session.commit()
|
||||
|
||||
# 2. Construct the XML request body with a non-existent port
|
||||
xml_request = """
|
||||
<AutoStore>
|
||||
<CreateTaskgroup>
|
||||
<Taskgroup>TG-FAIL-XML-01</Taskgroup>
|
||||
<Port>NON-EXISTENT-PORT</Port>
|
||||
<Tasks>
|
||||
<Task>
|
||||
<Task>T-FAIL-XML-01</Task>
|
||||
<Bin>BIN-TG-F-01</Bin>
|
||||
</Task>
|
||||
</Tasks>
|
||||
</CreateTaskgroup>
|
||||
</AutoStore>
|
||||
"""
|
||||
|
||||
# 3. Send the request
|
||||
headers = {"Content-Type": "application/xml"}
|
||||
response = client.post("/api", content=xml_request, headers=headers)
|
||||
|
||||
# 4. Assert the response
|
||||
assert response.status_code == 404
|
||||
response_data = xmltodict.parse(response.content)
|
||||
assert "Error" in response_data["AutoStore"]
|
||||
assert "Port not found" in response_data["AutoStore"]["Error"]["Description"]
|
||||
|
||||
|
||||
def test_create_task_group_fails_if_bin_not_found_xml(client, db_session):
|
||||
"""Tests that creating a task group via XML fails if a bin does not exist."""
|
||||
# 1. Create Port
|
||||
crud.create_port(db_session, schemas.PortCreate(id="P-TG-F-01", port_type="OUTBOUND"))
|
||||
db_session.commit()
|
||||
|
||||
# 2. Construct the XML request body with a non-existent bin
|
||||
xml_request = """
|
||||
<AutoStore>
|
||||
<CreateTaskgroup>
|
||||
<Taskgroup>TG-FAIL-XML-02</Taskgroup>
|
||||
<Port>P-TG-F-01</Port>
|
||||
<Tasks>
|
||||
<Task>
|
||||
<Task>T-FAIL-XML-02</Task>
|
||||
<Bin>NON-EXISTENT-BIN</Bin>
|
||||
</Task>
|
||||
</Tasks>
|
||||
</CreateTaskgroup>
|
||||
</AutoStore>
|
||||
"""
|
||||
|
||||
# 3. Send the request
|
||||
headers = {"Content-Type": "application/xml"}
|
||||
response = client.post("/api", content=xml_request, headers=headers)
|
||||
|
||||
# 4. Assert the response
|
||||
assert response.status_code == 404
|
||||
response_data = xmltodict.parse(response.content)
|
||||
assert "Error" in response_data["AutoStore"]
|
||||
assert "Bin not found" in response_data["AutoStore"]["Error"]["Description"]
|
||||
|
||||
|
||||
def test_create_taskgroup_xml(client, db_session):
|
||||
"""Tests creating a task group via the XML interface."""
|
||||
# 1. Create necessary Bins and Port in the database first
|
||||
port_id = "P-XML-01"
|
||||
bin_id_1 = "BIN-XML-01"
|
||||
bin_id_2 = "BIN-XML-02"
|
||||
crud.create_port(db_session, schemas.PortCreate(id=port_id, port_type="OUTBOUND"))
|
||||
crud.create_bin(db_session, schemas.BinCreate(id=bin_id_1, content_code=1))
|
||||
crud.create_bin(db_session, schemas.BinCreate(id=bin_id_2, content_code=1))
|
||||
db_session.commit()
|
||||
|
||||
# 2. Construct the XML request body
|
||||
xml_request = f"""
|
||||
<AutoStore>
|
||||
<CreateTaskgroup>
|
||||
<Taskgroup>TG-XML-01</Taskgroup>
|
||||
<Port>{port_id}</Port>
|
||||
<Tasks>
|
||||
<Task>
|
||||
<Task>TASK-XML-01</Task>
|
||||
<Bin>{bin_id_1}</Bin>
|
||||
</Task>
|
||||
<Task>
|
||||
<Task>TASK-XML-02</Task>
|
||||
<Bin>{bin_id_2}</Bin>
|
||||
</Task>
|
||||
</Tasks>
|
||||
</CreateTaskgroup>
|
||||
</AutoStore>
|
||||
"""
|
||||
|
||||
# 3. Send the request to the /api endpoint
|
||||
headers = {"Content-Type": "application/xml"}
|
||||
response = client.post("/api", content=xml_request, headers=headers)
|
||||
|
||||
# 4. Assert the response
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/xml"
|
||||
|
||||
# 5. Parse the XML response and validate its content
|
||||
import xmltodict
|
||||
response_data = xmltodict.parse(response.content)
|
||||
assert "AutoStore" in response_data
|
||||
assert "CreateTaskgroup" in response_data["AutoStore"]
|
||||
assert "ExecutionTime" in response_data["AutoStore"]["CreateTaskgroup"]
|
||||
|
||||
# 6. Verify that the data was actually created in the database
|
||||
db_task_group = crud.get_task_group(db_session, "TG-XML-01")
|
||||
assert db_task_group is not None
|
||||
assert db_task_group.id == "TG-XML-01"
|
||||
assert db_task_group.port_id == port_id
|
||||
assert len(db_task_group.tasks) == 2
|
||||
assert db_task_group.tasks[0].id == "TASK-XML-01"
|
||||
assert db_task_group.tasks[1].bin_id == bin_id_2
|
||||
|
||||
|
||||
def test_get_task_info_for_group(client, db_session):
|
||||
"""Tests the GetTaskInfo command for a whole task group."""
|
||||
# 1. Create a task group first
|
||||
port_id = "P-INFO-01"
|
||||
bin_id = "BIN-INFO-01"
|
||||
task_group_id = "TG-INFO-01"
|
||||
task_id = "TASK-INFO-01"
|
||||
crud.create_port(db_session, schemas.PortCreate(id=port_id, port_type="OUTBOUND"))
|
||||
crud.create_bin(db_session, schemas.BinCreate(id=bin_id, content_code=1))
|
||||
crud.create_task_group(
|
||||
db_session,
|
||||
schemas.TaskGroupCreate(
|
||||
id=task_group_id,
|
||||
port_id=port_id,
|
||||
tasks=[schemas.TaskCreate(id=task_id, bin_id=bin_id, task_type="OUTBOUND")],
|
||||
),
|
||||
)
|
||||
db_session.commit()
|
||||
|
||||
# 2. Construct the XML request
|
||||
xml_request = f"""
|
||||
<AutoStore>
|
||||
<GetTaskInfo>
|
||||
<Taskgroup>{task_group_id}</Taskgroup>
|
||||
</GetTaskInfo>
|
||||
</AutoStore>
|
||||
"""
|
||||
|
||||
# 3. Send the request
|
||||
headers = {"Content-Type": "application/xml"}
|
||||
response = client.post("/api", content=xml_request, headers=headers)
|
||||
|
||||
# 4. Assert the response
|
||||
assert response.status_code == 200
|
||||
response_data = xmltodict.parse(response.content)
|
||||
info = response_data["AutoStore"]["GetTaskInfo"]["Taskgroup"]
|
||||
assert info["Taskgroup"] == task_group_id
|
||||
assert info["Port"] == port_id
|
||||
assert info["Status"] == "PENDING"
|
||||
assert info["Tasks"]["Task"]["Task"] == task_id
|
||||
|
||||
|
||||
def test_get_task_info_for_single_task(client, db_session):
|
||||
"""Tests the GetTaskInfo command for a single task."""
|
||||
# 1. Create a task group first
|
||||
port_id = "P-INFO-02"
|
||||
bin_id = "BIN-INFO-02"
|
||||
task_group_id = "TG-INFO-02"
|
||||
task_id = "TASK-INFO-02"
|
||||
crud.create_port(db_session, schemas.PortCreate(id=port_id, port_type="OUTBOUND"))
|
||||
crud.create_bin(db_session, schemas.BinCreate(id=bin_id, content_code=1))
|
||||
crud.create_task_group(
|
||||
db_session,
|
||||
schemas.TaskGroupCreate(
|
||||
id=task_group_id,
|
||||
port_id=port_id,
|
||||
tasks=[schemas.TaskCreate(id=task_id, bin_id=bin_id, task_type="OUTBOUND")],
|
||||
),
|
||||
)
|
||||
db_session.commit()
|
||||
|
||||
# 2. Construct the XML request
|
||||
xml_request = f"""
|
||||
<AutoStore>
|
||||
<GetTaskInfo>
|
||||
<Task>{task_id}</Task>
|
||||
</GetTaskInfo>
|
||||
</AutoStore>
|
||||
"""
|
||||
|
||||
# 3. Send the request
|
||||
headers = {"Content-Type": "application/xml"}
|
||||
response = client.post("/api", content=xml_request, headers=headers)
|
||||
|
||||
# 4. Assert the response
|
||||
assert response.status_code == 200
|
||||
response_data = xmltodict.parse(response.content)
|
||||
info = response_data["AutoStore"]["GetTaskInfo"]["Task"]
|
||||
assert info["Task"] == task_id
|
||||
assert info["Bin"] == bin_id
|
||||
assert info["Status"] == "PENDING"
|
||||
assert info["Taskgroup"] == task_group_id
|
||||
|
||||
|
||||
def test_get_task_info_not_found(client):
|
||||
"""Tests that GetTaskInfo returns an error for a non-existent ID."""
|
||||
xml_request = """
|
||||
<AutoStore>
|
||||
<GetTaskInfo>
|
||||
<Taskgroup>NON-EXISTENT-GROUP</Taskgroup>
|
||||
</GetTaskInfo>
|
||||
</AutoStore>
|
||||
"""
|
||||
headers = {"Content-Type": "application/xml"}
|
||||
response = client.post("/api", content=xml_request, headers=headers)
|
||||
assert response.status_code == 404
|
||||
response_data = xmltodict.parse(response.content)
|
||||
assert "Taskgroup not found" in response_data["AutoStore"]["Error"]["Description"]
|
||||
Reference in new issue
Block a user