| 1 | #!/usr/bin/env python3 |
| 2 | """Exercise editable local drafts while creation is slow and its first reply is lost.""" |
| 3 | import base64 |
| 4 | import http.server |
| 5 | import json |
| 6 | import os |
| 7 | from pathlib import Path |
| 8 | import socket |
| 9 | import subprocess |
| 10 | import threading |
| 11 | import time |
| 12 | import urllib.parse |
| 13 | root = Path(__file__).resolve().parents[2] |
| 14 | class Handler(http.server.BaseHTTPRequestHandler): |
| 15 | documents = {} |
| 16 | updates = [] |
| 17 | creations = 0 |
| 18 | title = "" |
| 19 | renames = 0 |
| 20 | metadata = 0 |
| 21 | streams = 0 |
| 22 | def reply(self, status, data=b"", **headers): |
| 23 | self.send_response(status) |
| 24 | self.send_header("Content-Length", str(len(data))) |
| 25 | for key, value in headers.items(): self.send_header(key, value) |
| 26 | self.end_headers() |
| 27 | try: self.wfile.write(data) |
| 28 | except (BrokenPipeError, ConnectionResetError): pass |
| 29 | def do_POST(self): |
| 30 | cls = type(self) |
| 31 | data = self.rfile.read(int(self.headers.get("Content-Length", "0"))) |
| 32 | form = urllib.parse.parse_qs(data.decode()) if self.path == "/documents" or self.path.endswith("/title") else {} |
| 33 | if self.path == "/documents": |
| 34 | cls.creations += 1 |
| 35 | number = cls.creations |
| 36 | token = form["creation_token"][0] |
| 37 | cls.documents.setdefault(token, str(len(cls.documents) + 1)) |
| 38 | time.sleep(1) |
| 39 | if number == 1: |
| 40 | self.connection.shutdown(socket.SHUT_RDWR) |
| 41 | self.connection.close() |
| 42 | return |
| 43 | self.reply(201, json.dumps(dict(sqid=cls.documents[token], title="Untitled document")).encode()) |
| 44 | elif self.path.endswith("/title"): |
| 45 | cls.renames += 1 |
| 46 | time.sleep(0.3) |
| 47 | if cls.renames == 1: |
| 48 | self.reply(503) |
| 49 | else: |
| 50 | cls.title = form["title"][0] |
| 51 | self.reply(204) |
| 52 | else: |
| 53 | cls.updates.append(base64.b64encode(data).decode()) |
| 54 | self.reply(204) |
| 55 | def do_GET(self): |
| 56 | cls = type(self) |
| 57 | if self.path == "/stats": |
| 58 | self.reply(200, json.dumps(dict(documents=len(cls.documents), updates=cls.updates, title=cls.title, creations=cls.creations, metadata=cls.metadata)).encode()) |
| 59 | elif self.path.endswith("/stream"): |
| 60 | cls.streams += 1 |
| 61 | if cls.streams == 1: |
| 62 | self.reply(503) |
| 63 | return |
| 64 | self.send_response(200); self.send_header("Content-Type", "text/event-stream"); self.end_headers() |
| 65 | self.wfile.write(b"data: 0\n\n"); self.wfile.flush(); time.sleep(5) |
| 66 | elif "/updates" in self.path: |
| 67 | self.reply(200, **{"Docs-Seq": "0"}) |
| 68 | else: |
| 69 | cls.metadata += 1 |
| 70 | title = cls.title or "Untitled document" |
| 71 | if cls.metadata == 2: time.sleep(0.9) # Old title arrives after rename acknowledgement. |
| 72 | self.reply(200, json.dumps(dict(sqid=self.path.split("/")[-1], title=title, guid="server-guid", can_write=True, can_share=True)).encode()) |
| 73 | def log_message(self, *_): pass |
| 74 | with http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler) as server: |
| 75 | threading.Thread(target=server.serve_forever, daemon=True).start() |
| 76 | result = subprocess.run(["swift", "test", "--package-path", str(root / "native/apple"), "--filter", "DocumentDraftTests"], env=dict(os.environ, DOCS_DRAFT_TEST_ORIGIN=f"http://127.0.0.1:{server.server_port}"), timeout=180) |
| 77 | server.shutdown() |
| 78 | raise SystemExit(result.returncode) |
| 79 | |