import re from aiohttp.web import Request, Response from asyncio import Event from aiohttp.web_response import json_response from injector import singleton, inject from backup.time import Time from typing import List class UrlMatch(): def __init__(self, time: Time, url, fail_after=None, status=None, response=None, wait=False, sleep=None, fail_for=None): self.time = time self.url: str = url self.fail_after: int = fail_after self.status: int = status self.wait_event: Event = Event() self.trigger_event: Event = Event() self.response: str = "" self.wait: bool = wait self.trigger_event.clear() self.wait_event.clear() self.sleep = sleep self.response = response self.fail_for = fail_for self.responses = [] self._calls = 0 self.time = time def addResponse(self, response): self.responses.append(response) def stop(self): self.wait_event.set() self.trigger_event.set() def isMatch(self, request): return re.match(self.url, request.url.path) or re.match(self.url, str(request.url)) async def waitForCall(self): await self.trigger_event.wait() def clear(self): self.wait_event.set() def callCount(self): return self._calls async def _doAction(self, request: Request): self._calls += 1 if len(self.responses) > 0: return self.responses.pop(0) if self.status is not None: await self._readAll(request) if self.response: return json_response(self.response, status=self.status) else: return Response(status=self.status) elif self.wait: self.trigger_event.set() await self.wait_event.wait() elif self.sleep is not None: await self.time.sleepAsync(self.sleep, early_exit=self.wait_event) async def called(self, request: Request): if self.fail_after is None or self.fail_after <= 0: if self.fail_for is not None and self.fail_for > 0: self.fail_for -= 1 return await self._doAction(request) elif self.fail_for is not None: return None return await self._doAction(request) elif self.fail_after is not None: self.fail_after -= 1 async def _readAll(self, request: Request): data = bytearray() content = request.content while True: chunk, done = await content.readchunk() data.extend(chunk) if len(chunk) == 0: break return data @singleton class RequestInterceptor: @inject def __init__(self): self._matchers: List[UrlMatch] = [] self._history = [] self.time = Time() def stop(self): for matcher in self._matchers: matcher.stop() def setError(self, url, status=None, fail_after=None, fail_for=None, response=None) -> UrlMatch: matcher = UrlMatch(self.time, url, fail_after, status=status, response=response, fail_for=fail_for) self._matchers.append(matcher) return matcher def clear(self): self._matchers.clear() self._history.clear() def setWaiter(self, url, attempts=None): matcher = UrlMatch(self.time, url, attempts, wait=True) self._matchers.append(matcher) return matcher def setSleep(self, url, attempts=None, sleep=None, wait_for=None): matcher = UrlMatch(self.time, url, attempts, sleep=sleep, fail_for=wait_for) self._matchers.append(matcher) return matcher async def checkUrl(self, request): ret = None self.record(request) for match in self._matchers: if match.isMatch(request): ret = await match.called(request) return ret def record(self, request: Request): record = str(request.url.path) if len(request.url.query_string) > 0: record += "?" + str(request.url.query_string) self._history.append(record) def urlWasCalled(self, url) -> bool: for called_url in self._history: if url == called_url or re.match(url, called_url): return True return False