Python · 4826 bytes Raw Blame History
1 """End-to-end peer-mode: start a server in a thread, pull from the client.
2
3 Avoids subprocess gymnastics — everything runs in one process. The
4 server runs in a background thread; the test thread is the peer
5 client. Verifies:
6
7 1. The HTTP server accepts a valid token on GET /<dlm_id>
8 2. The pack bytes arrive intact (byte-for-byte equal)
9 3. An expired token is refused with HTTP 403
10 4. An unknown dlm_id is refused with HTTP 404
11 5. Missing token is refused with HTTP 401
12 """
13
14 from __future__ import annotations
15
16 import threading
17 import time
18 import urllib.error
19 import urllib.request
20 from pathlib import Path
21
22 import pytest
23
24 from dlm.share import ServeHandle
25
26
27 def _start_server_in_thread(
28 tmp_path: Path, *, ttl: int = 600
29 ) -> tuple[ServeHandle, threading.Thread, bytes]:
30 """Helper: pack a trivial file + start the peer server in a thread.
31
32 Returns `(handle, thread, pack_bytes)`. Caller stops via
33 `handle._server.shutdown()` + `thread.join()`.
34 """
35 from dlm.share import ServeOptions, serve
36
37 # Simulate a "pack" — any bytes will do for the transport test.
38 pack = tmp_path / "fake.dlm.pack"
39 pack_bytes = b"dlm-pack-contents-" * 256 # ~4 KB
40 pack.write_bytes(pack_bytes)
41
42 opts = ServeOptions(port=0, token_ttl_seconds=ttl) # port=0 → OS picks free port
43 try:
44 handle = serve("01HZTESTID", pack, opts)
45 except PermissionError as exc:
46 pytest.skip(f"loopback bind blocked on this host: {exc}")
47
48 thread = threading.Thread(target=handle._server.serve_forever, daemon=True)
49 thread.start()
50
51 # Give the server a moment to bind.
52 time.sleep(0.05)
53 return handle, thread, pack_bytes
54
55
56 def _stop_server(handle: ServeHandle, thread: threading.Thread) -> None:
57 handle._server.shutdown()
58 handle._server.server_close()
59 thread.join(timeout=2.0)
60
61
62 class TestPeerRoundTrip:
63 def test_happy_path(self, tmp_path: Path) -> None:
64 handle, thread, original = _start_server_in_thread(tmp_path)
65 try:
66 # Construct the actual bind URL from the handle — resolve_bind
67 # returns 127.0.0.1 by default, and port 0 was replaced with
68 # the real port by the OS on serve_forever.
69 real_port = handle._server.server_address[1]
70 url = f"http://127.0.0.1:{real_port}/{handle.session.dlm_id}?token={handle.token}"
71
72 with urllib.request.urlopen(url, timeout=2) as resp: # noqa: S310
73 assert resp.status == 200
74 received = resp.read()
75 assert received == original
76 finally:
77 _stop_server(handle, thread)
78
79 def test_missing_token_refused(self, tmp_path: Path) -> None:
80 handle, thread, _ = _start_server_in_thread(tmp_path)
81 try:
82 real_port = handle._server.server_address[1]
83 url = f"http://127.0.0.1:{real_port}/{handle.session.dlm_id}"
84 with pytest.raises(urllib.error.HTTPError) as exc_info:
85 urllib.request.urlopen(url, timeout=2) # noqa: S310
86 assert exc_info.value.code == 401
87 finally:
88 _stop_server(handle, thread)
89
90 def test_bad_token_refused(self, tmp_path: Path) -> None:
91 handle, thread, _ = _start_server_in_thread(tmp_path)
92 try:
93 real_port = handle._server.server_address[1]
94 url = f"http://127.0.0.1:{real_port}/{handle.session.dlm_id}?token=garbage"
95 with pytest.raises(urllib.error.HTTPError) as exc_info:
96 urllib.request.urlopen(url, timeout=2) # noqa: S310
97 assert exc_info.value.code == 403
98 finally:
99 _stop_server(handle, thread)
100
101 def test_unknown_dlm_id_refused(self, tmp_path: Path) -> None:
102 handle, thread, _ = _start_server_in_thread(tmp_path)
103 try:
104 real_port = handle._server.server_address[1]
105 url = f"http://127.0.0.1:{real_port}/01HZDIFFERENT?token={handle.token}"
106 with pytest.raises(urllib.error.HTTPError) as exc_info:
107 urllib.request.urlopen(url, timeout=2) # noqa: S310
108 assert exc_info.value.code == 404
109 finally:
110 _stop_server(handle, thread)
111
112 def test_expired_token_refused(self, tmp_path: Path) -> None:
113 # TTL of 0 → token is born expired.
114 handle, thread, _ = _start_server_in_thread(tmp_path, ttl=0)
115 try:
116 time.sleep(0.01) # ensure clock moves past expiry
117 real_port = handle._server.server_address[1]
118 url = f"http://127.0.0.1:{real_port}/{handle.session.dlm_id}?token={handle.token}"
119 with pytest.raises(urllib.error.HTTPError) as exc_info:
120 urllib.request.urlopen(url, timeout=2) # noqa: S310
121 assert exc_info.value.code == 403
122 finally:
123 _stop_server(handle, thread)