"""SD provisioning: ENV.TXT (firmware-exact parsing), disk discovery parsers, the elevated Windows format script, and write/check on a stand-in card.""" import plistlib import pytest from kota_tech.sd.disks import MAX_CARD_BYTES, Disk, parse_macos, parse_windows from kota_tech.sd.envfile import EnvConfig, parse_env from kota_tech.sd.provision import check_card, windows_format_script, write_env URL = "https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota" TOKEN = "T" * 86 + "==" # shape of an InfluxDB token, not a real one def cfg(**kw): base = dict(influx_url=URL, influx_token=TOKEN) base.update(kw) return EnvConfig(**base) # ------------------------------------------------------------------ ENV.TXT def test_render_matches_the_factory_file_format(): raw = cfg().render() assert raw == f"INFLUX_TOKEN={TOKEN}\nINFLUX_URL={URL}\n".encode() assert not raw.startswith(b"\xef\xbb\xbf") and b"\r" not in raw assert parse_env(raw).ok def test_wifi_only_written_when_both_set(): raw = cfg(ssid="Site WiFi", password="correct horse").render().decode() assert "SSID=Site WiFi\n" in raw and "PASSWORD=correct horse\n" in raw assert any("both SSID and PASSWORD" in i.text for i in cfg(ssid="x").problems()) @pytest.mark.parametrize("kw,fragment", [ (dict(influx_url="influx.kota-app.com"), "https://"), (dict(influx_url="https://influx.kota-app.com/"), "write URL"), (dict(influx_token=""), "INFLUX_TOKEN is empty"), (dict(influx_token="abc def" * 5), "spaces"), (dict(influx_token=TOKEN + " "), "space"), (dict(ssid="s" * 33, password="12345678"), "32 bytes"), (dict(ssid="net", password="short"), "8-63"), ]) def test_invalid_settings_are_errors(kw, fragment): errors = [i.text for i in cfg(**kw).problems() if i.level == "error"] assert any(fragment in e for e in errors), errors def test_parser_mirrors_firmware(): raw = b"\xef\xbb\xbfINFLUX_TOKEN=abc\r\nINFLUX_URL = x\nINFLUX_URL=u1\nINFLUX_URL=u2\nHTTP_SERVER=true\n" chk = parse_env(raw) texts = " ".join(i.text for i in chk.issues) assert "BOM" in texts # first key unreadable assert "spaces around the key 'INFLUX_URL'" in texts assert chk.values["INFLUX_URL"] == "u1" # first match wins assert "appears twice" in texts and "setup mode" in texts assert "INFLUX_TOKEN is missing" in texts # hidden behind the BOM assert not chk.ok def test_masking(): chk = parse_env(cfg(ssid="net", password="secretpassword").render()) m = chk.masked() assert TOKEN not in m["INFLUX_TOKEN"] and m["INFLUX_TOKEN"].startswith("TTTT") assert "secret" not in m["PASSWORD"] # ------------------------------------------------------------- disk lists WIN_JSON = """[{"Number":0,"FriendlyName":"ST500DM002-1BC142","Size":500107862016,"BusType":"SATA", "Volumes":[{"DriveLetter":"D","FileSystem":"NTFS","Label":"WINRE"}]}, {"Number":4,"FriendlyName":"Mass Storage Device","Size":0,"BusType":"USB","Volumes":[]}, {"Number":5,"FriendlyName":"SDXC Card","Size":31914983424,"BusType":"SD", "Volumes":{"DriveLetter":"E","FileSystem":"FAT32","Label":"KOTA"}}]""" def test_parse_windows_keeps_only_card_buses(): disks = parse_windows(WIN_JSON) assert [d.id for d in disks] == ["4", "5"] # SATA data disk never listed reader, card = disks assert not reader.has_media and reader.size_text == "no card" assert card.volumes[0].mount == "E:\\" and card.volumes[0].filesystem == "FAT32" def test_parse_windows_single_object_quirk(): one = '{"Number":5,"FriendlyName":"SD","Size":8000000000,"BusType":"USB","Volumes":null}' assert [d.id for d in parse_windows(one)] == ["5"] def test_parse_macos_excludes_system_disk(): listing = plistlib.dumps({"AllDisksAndPartitions": [ {"DeviceIdentifier": "disk0", "Size": 500_000_000_000, "Partitions": []}, {"DeviceIdentifier": "disk3", "Size": 994_000_000_000, "Partitions": [], "APFSVolumes": [{"MountPoint": "/"}]}, {"DeviceIdentifier": "disk4", "Size": 15_931_539_456, "Partitions": [{"DeviceIdentifier": "disk4s1", "MountPoint": "/Volumes/KOTA", "Content": "DOS_FAT_32", "VolumeName": "KOTA"}]}, ]}) infos = {"disk0": plistlib.dumps({"Internal": True, "BusProtocol": "PCI-Express"}), "disk3": plistlib.dumps({"Internal": True}), "disk4": plistlib.dumps({"Internal": True, "RemovableMedia": True, "BusProtocol": "Secure Digital", "MediaName": "SD Card Reader"})} disks = parse_macos(listing, infos) assert [d.id for d in disks] == ["/dev/disk4"] and disks[0].bus == "SD" assert disks[0].volumes[0].mount == "/Volumes/KOTA" # -------------------------------------------------------- format script def test_windows_script_rechecks_disk_before_erasing(tmp_path): disk = Disk("5", "SDXC Card", 63_864_569_856, "SD") ps = windows_format_script(disk, tmp_path / "r.json") erase = ps.index("Clear-Disk") for guard in ("IsSystem -or $d.IsBoot", "'MMC','SD','USB'", "-ne 63864569856"): assert guard in ps and ps.index(guard) < erase assert "-PartitionStyle MBR" in ps and "-FileSystem FAT32" in ps assert str(32 * 1024**3) in ps # FAT32 partition capped at 32 GB assert "NewFileSystemLabel 'KOTA'" in ps def test_large_disks_are_not_cards(): assert MAX_CARD_BYTES == 256 * 1024**3 # ----------------------------------------------- write / check on a card def test_write_env_replaces_variants_and_keeps_buffered_data(tmp_path): card = tmp_path / "card" card.mkdir() (card / "env.txt").write_bytes(b"INFLUX_URL=old\n") (card / "BUFFER.TXT").write_bytes(b"line1\nline2\n\n") out = write_env(str(card), cfg()) assert out.ok, out.message names = sorted(p.name for p in card.iterdir()) assert names == ["BUFFER.TXT", "ENV.TXT"] rep = check_card(str(card), "FAT32") assert rep.env.ok and rep.problems == [] and rep.buffered_lines == 2 def test_write_env_refuses_invalid_settings(tmp_path): assert not write_env(str(tmp_path), cfg(influx_token="")).ok assert not (tmp_path / "ENV.TXT").exists() def test_check_card_reports_missing_env_and_wrong_fs(tmp_path): rep = check_card(str(tmp_path), "exFAT") texts = " ".join(rep.problems) assert "needs FAT32" in texts and "No ENV.TXT" in texts def test_elevated_result_folder_is_not_owner_only(tmp_path, monkeypatch): # Regression: tempfile.mkdtemp() made the elevated script's result unreadable. import kota_tech.sd.provision as prov monkeypatch.setattr(prov, "WORK_ROOT", tmp_path / "tmp") work = prov._workdir() assert work.parent == tmp_path / "tmp" and work.is_dir() (work / "result.json").write_text('{"ok": true}', encoding="utf-8") assert (work / "result.json").read_text(encoding="utf-8")