1+ """Fleet-mode policy tests: capacity gating, per-stick tracking, expiry.
2+
3+ No real drives and no Qt are involved; every behaviour here is pure
4+ policy logic from core.fleet.
5+ """
6+
7+ from core import fleet
8+
9+
10+ def _drive (serial = "SN123" , path = r"\\.\PHYSICALDRIVE1" , size_gb = 32 ):
11+ return {"serial" : serial , "physical_path" : path , "size_gb" : size_gb }
12+
13+
14+ def _image (tmp_path , name = "ubuntu.iso" , size = 1_000 ):
15+ p = tmp_path / name
16+ p .write_bytes (b"\x00 " * size )
17+ return str (p )
18+
19+
20+ class TestDriveFingerprint :
21+ def test_serial_preferred (self ):
22+ assert fleet .drive_fingerprint (_drive ()) == "SN123"
23+
24+ def test_path_fallback_without_serial (self ):
25+ assert (
26+ fleet .drive_fingerprint (_drive (serial = "" )) == r"\\.\PHYSICALDRIVE1"
27+ )
28+
29+ def test_none_when_neither_known (self ):
30+ assert fleet .drive_fingerprint ({"size_gb" : 8 }) is None
31+
32+
33+ class TestCapacityGate :
34+ def test_image_fits_with_room (self , tmp_path ):
35+ img = _image (tmp_path , size = 2_000 )
36+ session = fleet .FleetSession (images = [img ])
37+ assert session .fits_on_drive (img , _drive (size_gb = 8 ))
38+
39+ def test_capacity_boundary_is_inclusive (self , tmp_path ):
40+ img = _image (tmp_path , size = 1_000 )
41+ # 0.000001 * 1e9 = exactly 1000 bytes of claimed capacity
42+ session = fleet .FleetSession (images = [img ])
43+ assert session .fits_on_drive (img , _drive (size_gb = 0.000001 ))
44+
45+ def test_image_larger_than_drive_rejected (self , tmp_path ):
46+ img = _image (tmp_path , size = 2_000 )
47+ session = fleet .FleetSession (images = [img ])
48+ assert not session .fits_on_drive (img , _drive (size_gb = 0.000001 ))
49+
50+ def test_zero_capacity_rejected (self , tmp_path ):
51+ img = _image (tmp_path , size = 1_000 )
52+ session = fleet .FleetSession (images = [img ])
53+ assert not session .fits_on_drive (img , _drive (size_gb = 0 ))
54+
55+ def test_missing_image_rejected (self ):
56+ session = fleet .FleetSession (images = [r"C:\nope.iso" ])
57+ assert not session .fits_on_drive (r"C:\nope.iso" , _drive ())
58+
59+ def test_drive_without_capacity_rejected (self , tmp_path ):
60+ img = _image (tmp_path , size = 1_000 )
61+ session = fleet .FleetSession (images = [img ])
62+ assert not session .fits_on_drive (img , {"serial" : "SN" })
63+
64+ def test_all_images_must_fit_for_candidate (self , tmp_path ):
65+ small = _image (tmp_path , "a.iso" , size = 1_000 )
66+ big = _image (tmp_path , "b.iso" , size = 2_000 )
67+ # capacity is exactly enough for `small` but not both
68+ session = fleet .FleetSession (images = [small , big ])
69+ drive = _drive (size_gb = 0.000001 )
70+ assert not fleet .pick_candidate ([drive ], session )
71+
72+ def test_missing_image_blocks_candidate (self , tmp_path ):
73+ good = _image (tmp_path , "a.iso" , size = 1_000 )
74+ session = fleet .FleetSession (images = [good , r"C:\gone.iso" ])
75+ assert fleet .pick_candidate ([_drive ()], session ) is None
76+
77+
78+ class TestSessionTracking :
79+ def test_flashed_drive_is_skipped (self , tmp_path ):
80+ img = _image (tmp_path , size = 1_000 )
81+ session = fleet .FleetSession (images = [img ])
82+ d1 = _drive (serial = "SN1" )
83+ d2 = _drive (serial = "SN2" )
84+ assert fleet .pick_candidate ([d1 , d2 ], session ) is d1
85+ session .mark_flashed (d1 )
86+ assert session .done_count == 1
87+ assert fleet .pick_candidate ([d1 , d2 ], session ) is d2
88+
89+ def test_failed_drive_can_be_retried (self , tmp_path ):
90+ """A failure is recorded but does not blacklist the stick: the
91+ operator may re-insert it for another attempt."""
92+ img = _image (tmp_path , size = 1_000 )
93+ session = fleet .FleetSession (images = [img ])
94+ d = _drive (serial = "SN1" )
95+ session .mark_failed ()
96+ assert fleet .pick_candidate ([d ], session ) is d
97+ assert session .failed_count == 1
98+
99+ def test_finished_session_sweeps_every_stick_once (self , tmp_path ):
100+ img = _image (tmp_path , size = 1_000 )
101+ session = fleet .FleetSession (images = [img ])
102+ ds = [_drive (serial = f"SN{ i } " ) for i in range (4 )]
103+ for d in ds :
104+ assert fleet .pick_candidate (ds , session ) is d
105+ session .mark_flashed (d )
106+ assert fleet .pick_candidate (ds , session ) is None
107+ assert session .done_count == 4
108+
109+ def test_sticks_without_serial_tracked_by_path (self , tmp_path ):
110+ img = _image (tmp_path , size = 1_000 )
111+ session = fleet .FleetSession (images = [img ])
112+ d = _drive (serial = "" , path = r"\\.\PHYSICALDRIVE9" )
113+ assert fleet .pick_candidate ([d ], session ) is d
114+ session .mark_flashed (d )
115+ assert fleet .pick_candidate ([d ], session ) is None
116+
117+
118+ class TestExpiry :
119+ def test_expired_session_blocks_candidates (self , tmp_path ):
120+ img = _image (tmp_path , size = 1_000 )
121+ session = fleet .FleetSession (images = [img ])
122+ now = 1_000.0
123+ session .last_activity = now - fleet .IDLE_EXPIRY_SECONDS - 1
124+ assert fleet .pick_candidate ([_drive ()], session , now = now ) is None
125+ assert session .expired (now )
126+
127+ def test_active_session_keeps_candidates (self , tmp_path ):
128+ img = _image (tmp_path , size = 1_000 )
129+ session = fleet .FleetSession (images = [img ])
130+ now = 1_000.0
131+ session .last_activity = now - 10
132+ assert fleet .pick_candidate ([_drive ()], session , now = now ) is not None
0 commit comments