feat: Implement GetTaskInfo and create user manual

This commit is contained in:
rewa81 committed 2025-08-30 18:59:36 +02:00
1 parent dd8fe01f86
commit 3ab411d623
9 files changed
+5935 -145

No files matched your search

+123
View File
@@ -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>
```
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
BIN
View File
Binary file not shown.
+347 -75
View File
@@ -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"]