File size: 5,513 Bytes
06b0d69
babab16
06b0d69
 
 
 
 
 
 
 
babab16
06b0d69
3424779
 
 
 
 
 
 
06b0d69
 
 
 
 
 
41120b8
3424779
41120b8
 
babab16
3424779
 
41120b8
 
06b0d69
 
 
 
 
 
 
3424779
06b0d69
 
 
 
 
 
 
 
3424779
06b0d69
3424779
06b0d69
3424779
 
06b0d69
 
 
 
 
 
 
 
 
3424779
 
 
babab16
3424779
 
babab16
 
 
 
 
 
 
06b0d69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431d2c4
 
 
 
 
06b0d69
 
 
 
 
 
 
 
 
 
babab16
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import time
import uuid
from fastapi import APIRouter, Header, HTTPException, Request, Query
from pydantic import BaseModel
from typing import Optional

router = APIRouter(prefix="/mock_api", tags=["mock"])

_issued_tokens = set()
_request_log = {}
_items_db = {}

DYNAMIC_CONFIG = {
    "demo_token": "demo_token_123",
    "client_id": "abc",
    "client_secret": "xyz",
    "dynamic_item_field": "name"
}

LOG_PAGES = {
    None: {"items": list(range(10)), "next_cursor": "cur_abc", "has_more": True},
    "cur_abc": {"items": list(range(10, 20)), "next_cursor": "cur_def", "has_more": True},
    "cur_def": {"items": list(range(20, 25)), "next_cursor": None, "has_more": False},
}

@router.post("/_admin/reset")
async def admin_reset(config: Optional[dict] = None):
    _issued_tokens.clear()
    _request_log.clear()
    _items_db.clear()
    if config:
        DYNAMIC_CONFIG.update(config)
    return {"status": "reset_successful"}


class TokenRequest(BaseModel):
    client_id: str
    client_secret: str

@router.post("/auth/token")
async def get_token(body: TokenRequest):
    if body.client_id == DYNAMIC_CONFIG["client_id"] and body.client_secret == DYNAMIC_CONFIG["client_secret"]:
        token = f"tok_{int(time.time())}"
        _issued_tokens.add(token)
        return {"access_token": token, "expires_in": 60}
    raise HTTPException(status_code=401, detail="Invalid client credentials")


@router.get("/users")
async def get_users(authorization: Optional[str] = Header(default=None)):
    dt = DYNAMIC_CONFIG["demo_token"]
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail=f"Authorization header missing or malformed. Expected format: Bearer <token>. Use '{dt}' for testing.")
    token = authorization.split(" ")[1]
    if token not in _issued_tokens and token != dt:
        raise HTTPException(status_code=401, detail=f"Invalid token. Try '{dt}'.")
    return {"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}


@router.post("/items")
async def create_item(request: Request):
    ct = request.headers.get("content-type", "")
    if "application/json" not in ct:
        raise HTTPException(status_code=415, detail="Content-Type must be application/json")
    body = await request.json()
    field = DYNAMIC_CONFIG.get("dynamic_item_field", "name")
    if field not in body:
        raise HTTPException(status_code=422, detail=[{"loc": ["body", field], "msg": "field required"}])
    item_id = str(uuid.uuid4())
    _items_db[item_id] = body[field]
    return {"item_id": item_id, "name": body[field], "created": True}

@router.delete("/items/{item_id}")
async def delete_item(item_id: str):
    if item_id not in _items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    del _items_db[item_id]
    return {"status": "deleted", "item_id": item_id}


@router.get("/search")
async def search(q: Optional[str] = Query(default=None)):
    if not q:
        raise HTTPException(status_code=422, detail=[{"loc": ["query", "q"], "msg": "field required"}])
    return {"results": [{"title": f"Result for {q}", "score": 0.95}], "total": 1}


class OrderBody(BaseModel):
    product_id: int
    qty: int


@router.post("/orders")
async def create_order(order: OrderBody):
    return {"order_id": 999, "product_id": order.product_id, "qty": order.qty, "status": "confirmed"}


class AddressModel(BaseModel):
    street: str
    city: str


class ProfileBody(BaseModel):
    name: str
    address: AddressModel


@router.post("/profile")
async def update_profile(profile: ProfileBody):
    return {"name": profile.name, "address": profile.address.model_dump(), "updated": True}


@router.get("/protected")
async def protected(authorization: Optional[str] = Header(default=None)):
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Authorization header missing")
    token = authorization.split(" ")[1]
    if token not in _issued_tokens:
        raise HTTPException(status_code=401, detail="Invalid or expired token")
    return {"data": "secret_payload", "accessed_at": int(time.time())}


@router.get("/rate_limited")
async def rate_limited(request: Request, x_retry_after: Optional[str] = Header(default=None)):
    client_id = request.client.host if request.client else "default"
    now = time.time()
    window = [t for t in _request_log.get(client_id, []) if now - t < 5]
    if len(window) >= 3 and not x_retry_after:
        _request_log[client_id] = window
        raise HTTPException(
            status_code=429, 
            detail={"error": "Rate limit exceeded", "hint": "Please add X-Retry-After header with value 2 to override."},
            headers={"Retry-After": "2"}
        )
    window.append(now)
    _request_log[client_id] = window
    return {"data": "rate_limited_resource", "requests_in_window": len(window)}


@router.get("/logs")
async def get_logs(cursor: Optional[str] = Query(default=None)):
    if cursor not in LOG_PAGES:
        raise HTTPException(status_code=400, detail=f"Invalid cursor: {cursor}")
    return LOG_PAGES[cursor]

class MetricsBody(BaseModel):
    cpu_load: float
    memory_usage: float

@router.post("/system/metrics", description="Ingest system metrics. Required JSON fields: cpu_load (float), memory_usage (float).")
async def ingest_metrics(metrics: MetricsBody):
    return {"status": "metrics_logged", "cpu": metrics.cpu_load, "mem": metrics.memory_usage}