"""In-Memory-Nachbau von login.onyva.dev /api/v1 (Verhalten laut LoginSeite/docs/developer-api.md) für Tests und lokale Entwicklung.""" class FakeLogin: """In-Memory-Nachbau von login.onyva.dev /api/v1 (Verhalten laut LoginSeite/docs/developer-api.md).""" def __init__(self, users=("anna@x.de", "bob@x.de")): self.apps, self.members, self.users, self.calls, self.n, self.rate_limit_once = {}, {}, set(users), [], 0, False def __call__(self, method, url, headers, body): path = url.split("/api/v1")[1] self.calls.append((method, path, body)) if headers.get("Authorization") != "Bearer onyva_testkey": return 401, {"detail": "invalid"}, {} if self.rate_limit_once: self.rate_limit_once = False return 429, {"detail": "slow down"}, {"Retry-After": "2"} parts = path.strip("/").split("/") if parts == ["apps"]: if method == "GET": return 200, {"apps": list(self.apps.values())}, {} self.n += 1 cid = f"app_{self.n:04d}" app = {"client_id": cid, "name": body["name"], "slug": body["name"].lower(), "redirect_uris": body["redirect_uris"], "auth_mode": body.get("auth_mode", "required"), "totp_mode": body.get("totp_mode", "disabled"), "allow_registration": body.get("allow_registration", False), "backchannel_logout_uri": body.get("backchannel_logout_uri")} self.apps[cid], self.members[cid] = app, {} return 201, {**app, "client_secret": f"secret-{self.n}"}, {} cid = parts[1] if cid not in self.apps: return 404, {"detail": "not found"}, {} if len(parts) == 2: if method == "GET": return 200, self.apps[cid], {} if method == "PATCH": self.apps[cid].update(body) return 200, self.apps[cid], {} if method == "DELETE": del self.apps[cid] return 204, None, {} if parts[2] == "rotate-secret": self.n += 1 return 200, {"client_id": cid, "client_secret": f"rotated-{self.n}"}, {} if parts[2] == "memberships": if method == "GET": return 200, {"memberships": [{"email": e, "display_name": e, "role": r} for e, r in self.members[cid].items()]}, {} if method == "PUT": if body["user_email"] not in self.users: return 404, {"detail": "user"}, {} self.members[cid][body["user_email"]] = body.get("role", "viewer") return 200, body, {} if method == "DELETE": self.members[cid].pop(parts[3], None) return 204, None, {} return 404, {}, {} @property def writes(self): return [c for c in self.calls if c[0] != "GET"]