From e3b8dddbb887e3a2d5de84a0c0fd880d32c17453 Mon Sep 17 00:00:00 2001 From: nyannoying1337 Date: Fri, 11 Sep 2026 16:51:18 +0000 Subject: [PATCH 1/3] Read Windows system info natively instead of via PowerShell Follow-up to #245, where the stated goal was to use fastfetch where native is not implemented yet and keep it as the fallback. This starts that on Windows, and orders the providers accordingly. On Windows the CommandProvider shells out once per field. Each of those is a PowerShell start-up, and the fan-out across the command table dominates the time to first presence. Reading the same values from the registry costs a few milliseconds and needs nothing installed. Adds fetchers under fetch_cord/native/Windows/ for os, kernel, cpu, motherboard, host and mem, alongside the existing packages one. Each returns exactly what its PowerShell command returned, so the values stay interchangeable: os Microsoft Windows 11 Pro (Win32_OperatingSystem.Caption) kernel 10.0.26100 (Win32_OperatingSystem.Version) cpu Intel(R) Core(TM) i7-... (Win32_Processor.Name) motherboard ASUSTeK COMPUTER INC. (Win32_BaseBoard.Manufacturer) host PRIME B760-PLUS D4 (Win32_BaseBoard.Product) mem 12.40 GB / 31.92 GB (same format as the command) Providers are now ordered native, fastfetch, commands on every platform. That is safe because a native fetcher returns None when it is not confident rather than guessing - an unreadable registry key, or firmware boilerplate like "To Be Filled By O.E.M." - so Fetch.snapshot's existing gap-filling hands the field straight to fastfetch. Native coverage can then grow one field at a time without any platform losing detail it has today. Nothing changes on Linux or macOS, where no native fetchers exist yet. Two details worth knowing: - The registry's ProductName still reads "Windows 10 Pro" on Windows 11, so the build number decides the release and the name is corrected to match what WMI reports. - Firmware placeholders are reported as absent rather than shown. Also fixes native fetch() stringifying None into the literal "None", which was harmless while packages was the only fetcher. Tests fake the registry at the fetcher boundary, so all of them run on a non-Windows CI runner. One asserts the native path makes no subprocess calls, keeping "this adds no dependencies" verifiable rather than a claim. --- fetch_cord/__main__.py | 23 ++- fetch_cord/fetch.py | 15 +- fetch_cord/native/Windows/__init__.py | 25 ++- fetch_cord/native/Windows/cpu.py | 12 ++ fetch_cord/native/Windows/host.py | 10 ++ fetch_cord/native/Windows/kernel.py | 23 +++ fetch_cord/native/Windows/mem.py | 25 +++ fetch_cord/native/Windows/motherboard.py | 34 ++++ fetch_cord/native/Windows/os_info.py | 28 ++++ fetch_cord/native/Windows/registry.py | 56 +++++++ tests/test_native_windows.py | 188 +++++++++++++++++++++++ 11 files changed, 426 insertions(+), 13 deletions(-) create mode 100644 fetch_cord/native/Windows/cpu.py create mode 100644 fetch_cord/native/Windows/host.py create mode 100644 fetch_cord/native/Windows/kernel.py create mode 100644 fetch_cord/native/Windows/mem.py create mode 100644 fetch_cord/native/Windows/motherboard.py create mode 100644 fetch_cord/native/Windows/os_info.py create mode 100644 fetch_cord/native/Windows/registry.py create mode 100644 tests/test_native_windows.py diff --git a/fetch_cord/__main__.py b/fetch_cord/__main__.py index 2f8e719..ea2b202 100755 --- a/fetch_cord/__main__.py +++ b/fetch_cord/__main__.py @@ -22,6 +22,7 @@ CommandProvider, FastfetchProvider, Fetch, + FieldProvider, NativeProvider, get_component_id, get_infos, @@ -127,17 +128,29 @@ def main( cycle.time = int(float(args.time)) os_type = platform.system() - # Only the commands defined for this OS are run. The structured fastfetch - # provider fills the common fields first; command/native providers only - # fill the gaps (e.g. motherboard/resolution/system_type, or everything on - # Windows where fastfetch may not be installed). + # Only the commands defined for this OS are run. command_map = { component_type: value[os_type] for component_type, value in config["commands"].items() if os_type in value } - fetch = Fetch([FastfetchProvider(), CommandProvider(command_map), NativeProvider()]) + # Native fetchers first: they need no tool installed and cost a few + # milliseconds. fastfetch then covers everything native does not implement + # yet, and the commands cover what neither reaches. + # + # This is only safe because a native fetcher returns None when it is not + # confident rather than guessing - an unreadable registry key, or firmware + # boilerplate like "To Be Filled By O.E.M." - so Fetch.snapshot hands the + # field straight to fastfetch. Native coverage can grow one field at a + # time without any platform ever losing detail it has today. + providers: list[FieldProvider] = [ + NativeProvider(), + FastfetchProvider(), + CommandProvider(command_map), + ] + + fetch = Fetch(providers) def signal_handler(signum: int, frame: object) -> None: stop_event.set() diff --git a/fetch_cord/fetch.py b/fetch_cord/fetch.py index 4002879..a6c70c6 100644 --- a/fetch_cord/fetch.py +++ b/fetch_cord/fetch.py @@ -124,11 +124,20 @@ def fetch(self, skip: set[str] | None = None) -> dict[str, str]: class NativeProvider: - """Uses the bundled native (Windows) fetchers for fields not otherwise - covered, e.g. the installed-packages count on Windows.""" + """Uses the bundled native (Windows) fetchers, which read the registry and + Win32 directly. Adds no tool dependency and no subprocess, and returns {} + for anything it cannot read so another provider fills the gap.""" name = "native" - NATIVE_COMPONENTS = ("packages",) + NATIVE_COMPONENTS = ( + "cpu", + "host", + "kernel", + "mem", + "motherboard", + "os", + "packages", + ) def __init__(self) -> None: self._native = native_module diff --git a/fetch_cord/native/Windows/__init__.py b/fetch_cord/native/Windows/__init__.py index 3e187ce..174bb18 100644 --- a/fetch_cord/native/Windows/__init__.py +++ b/fetch_cord/native/Windows/__init__.py @@ -1,15 +1,30 @@ import os +from types import ModuleType if os.name == "nt": - from . import packages + from . import cpu, host, kernel, mem, motherboard, os_info, packages - def fetch(component_class: str) -> str | None: - fetch_map = {"packages": packages} + fetch_map: dict[str, ModuleType] = { + "cpu": cpu, + "host": host, + "kernel": kernel, + "mem": mem, + "motherboard": motherboard, + "os": os_info, + "packages": packages, + } - if component_class not in fetch_map: + def fetch(component_class: str) -> str | None: + module = fetch_map.get(component_class) + if module is None: return None - return str(fetch_map[component_class].fetch()) + # A fetcher returns None when the value is not readable on this + # machine; pass that through rather than stringifying it, so the next + # provider fills the gap instead of showing "None". + value = module.fetch() + + return str(value) if value else None else: diff --git a/fetch_cord/native/Windows/cpu.py b/fetch_cord/native/Windows/cpu.py new file mode 100644 index 0000000..0907331 --- /dev/null +++ b/fetch_cord/native/Windows/cpu.py @@ -0,0 +1,12 @@ +"""Processor name, matching Win32_Processor.Name. + +e.g. "Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz" +""" + +from fetch_cord.native.Windows import registry + + +def fetch() -> str | None: + name = registry.read(registry.CPU0, "ProcessorNameString") + + return name.strip() if name else None diff --git a/fetch_cord/native/Windows/host.py b/fetch_cord/native/Windows/host.py new file mode 100644 index 0000000..10bc5f1 --- /dev/null +++ b/fetch_cord/native/Windows/host.py @@ -0,0 +1,10 @@ +"""Motherboard product name, matching Win32_BaseBoard.Product. + +e.g. "PRIME B760-PLUS D4" +""" + +from fetch_cord.native.Windows import motherboard, registry + + +def fetch() -> str | None: + return motherboard.usable(registry.read(registry.BIOS, "BaseBoardProduct")) diff --git a/fetch_cord/native/Windows/kernel.py b/fetch_cord/native/Windows/kernel.py new file mode 100644 index 0000000..5e9e187 --- /dev/null +++ b/fetch_cord/native/Windows/kernel.py @@ -0,0 +1,23 @@ +"""Windows version string, matching Win32_OperatingSystem.Version. + +e.g. "10.0.26100" +""" + +from fetch_cord.native.Windows import registry + + +def fetch() -> str | None: + build = registry.read(registry.CURRENT_VERSION, "CurrentBuildNumber") + if build is None: + return None + + major = registry.read_int(registry.CURRENT_VERSION, "CurrentMajorVersionNumber") + minor = registry.read_int(registry.CURRENT_VERSION, "CurrentMinorVersionNumber") + + # Present since Windows 10; fall back to the values every release carries. + if major is None: + major = 10 + if minor is None: + minor = 0 + + return f"{major}.{minor}.{build}" diff --git a/fetch_cord/native/Windows/mem.py b/fetch_cord/native/Windows/mem.py new file mode 100644 index 0000000..7ab1099 --- /dev/null +++ b/fetch_cord/native/Windows/mem.py @@ -0,0 +1,25 @@ +"""Memory use, matching the format the PowerShell command produces. + +e.g. "12.40 GB / 31.92 GB" + +Used is total minus available, which is how the Linux and macOS commands +define it too, so the value stays comparable across platforms. +""" + +import psutil + +GIB = 1024**3 + + +def fetch() -> str | None: + try: + memory = psutil.virtual_memory() + except Exception: + return None + + if not memory.total: + return None + + used = memory.total - memory.available + + return f"{used / GIB:.2f} GB / {memory.total / GIB:.2f} GB" diff --git a/fetch_cord/native/Windows/motherboard.py b/fetch_cord/native/Windows/motherboard.py new file mode 100644 index 0000000..7f4de59 --- /dev/null +++ b/fetch_cord/native/Windows/motherboard.py @@ -0,0 +1,34 @@ +"""Motherboard manufacturer, matching Win32_BaseBoard.Manufacturer. + +e.g. "ASUSTeK COMPUTER INC." +""" + +from fetch_cord.native.Windows import registry + +# Strings firmware ships when the vendor never filled the field in. Returning +# None for these lets another provider supply something better. +PLACEHOLDERS = { + "", + "to be filled by o.e.m.", + "default string", + "system manufacturer", + "not applicable", + "not specified", + "none", + "o.e.m.", + "unknown", +} + + +def usable(value: str | None) -> str | None: + """The value, unless it is firmware boilerplate.""" + if value is None: + return None + + value = value.strip() + + return None if value.lower() in PLACEHOLDERS else value + + +def fetch() -> str | None: + return usable(registry.read(registry.BIOS, "BaseBoardManufacturer")) diff --git a/fetch_cord/native/Windows/os_info.py b/fetch_cord/native/Windows/os_info.py new file mode 100644 index 0000000..9b74599 --- /dev/null +++ b/fetch_cord/native/Windows/os_info.py @@ -0,0 +1,28 @@ +"""Windows edition name, matching Win32_OperatingSystem.Caption. + +The registry's ProductName still reads "Windows 10 Pro" on Windows 11, so the +build number decides the release and the name is corrected to match what WMI +would report. +""" + +from fetch_cord.native.Windows import registry + +# Windows 11 starts here; the registry name was never updated for it. +WINDOWS_11_BUILD = 22000 + + +def fetch() -> str | None: + product = registry.read(registry.CURRENT_VERSION, "ProductName") + if product is None: + return None + + build = registry.read_int(registry.CURRENT_VERSION, "CurrentBuildNumber") + if build is not None and build >= WINDOWS_11_BUILD: + product = product.replace("Windows 10", "Windows 11") + + # WMI's Caption is prefixed; match it so the value is interchangeable with + # the PowerShell command this replaces. + if not product.startswith("Microsoft "): + product = f"Microsoft {product}" + + return product diff --git a/fetch_cord/native/Windows/registry.py b/fetch_cord/native/Windows/registry.py new file mode 100644 index 0000000..af9634b --- /dev/null +++ b/fetch_cord/native/Windows/registry.py @@ -0,0 +1,56 @@ +"""Small helpers for reading the Windows registry. + +Every native Windows fetcher reads from here rather than shelling out, so the +whole native path stays free of subprocesses and of any external tool. +""" + +import contextlib +import sys +from typing import TYPE_CHECKING + +# Importable on any platform so the fetchers - and their tests - can be +# imported off Windows; every read simply reports None there. The TYPE_CHECKING +# arm keeps winreg visible to mypy when it runs on a non-Windows host. +if sys.platform == "win32" or TYPE_CHECKING: + import winreg + +# Where the values the fetchers need live. +CURRENT_VERSION = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" +BIOS = "HARDWARE\\DESCRIPTION\\System\\BIOS" +CPU0 = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0" + + +def read(path: str, name: str) -> str | None: + """Read one HKEY_LOCAL_MACHINE value, or None if it is not there. + + A missing key or value is an expected outcome - hardware varies and values + come and go between Windows releases - so it is reported as None and the + next provider fills the gap. + """ + if sys.platform != "win32": + return None + + # Read the 64-bit view explicitly: a 32-bit Python would otherwise be + # redirected to WOW6432Node and see a different (or missing) ProductName. + access = winreg.KEY_READ | winreg.KEY_WOW64_64KEY + + with ( + contextlib.suppress(OSError), + winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0, access) as key, + ): + value = winreg.QueryValueEx(key, name)[0] + return str(value) if value != "" else None + + return None + + +def read_int(path: str, name: str) -> int | None: + """Read one registry value as an int, or None if it is absent or not numeric.""" + value = read(path, name) + if value is None: + return None + + try: + return int(value) + except ValueError: + return None diff --git a/tests/test_native_windows.py b/tests/test_native_windows.py new file mode 100644 index 0000000..fab57e0 --- /dev/null +++ b/tests/test_native_windows.py @@ -0,0 +1,188 @@ +"""Tests for the native Windows fetchers and the NativeProvider wiring. + +The registry is faked at the fetcher boundary so these run on any OS, which +keeps them useful on the Linux CI runner. +""" + +import subprocess +import unittest +from unittest.mock import MagicMock, patch + +from fetch_cord.fetch import NativeProvider + +CURRENT_VERSION = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" +BIOS = "HARDWARE\\DESCRIPTION\\System\\BIOS" +CPU0 = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0" + +# Values as the registry actually reports them on a Windows 11 machine. Note +# ProductName still says "Windows 10" - the build number is what distinguishes +# the release. +REGISTRY = { + (CURRENT_VERSION, "ProductName"): "Windows 10 Pro", + (CURRENT_VERSION, "CurrentBuildNumber"): "26100", + (CURRENT_VERSION, "CurrentMajorVersionNumber"): "10", + (CURRENT_VERSION, "CurrentMinorVersionNumber"): "0", + (CPU0, "ProcessorNameString"): "Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz", + (BIOS, "BaseBoardManufacturer"): "ASUSTeK COMPUTER INC.", + (BIOS, "BaseBoardProduct"): "PRIME B760-PLUS D4", +} + + +def fake_registry(values: dict[tuple[str, str], str] | None = None) -> MagicMock: + """Patch the registry helper with a fixed set of values.""" + table = REGISTRY if values is None else values + + def read(path: str, name: str) -> str | None: + return table.get((path, name)) + + return patch("fetch_cord.native.Windows.registry.read", side_effect=read) + + +class TestWindowsFetchers(unittest.TestCase): + """Each fetcher must return the same shape the PowerShell command did.""" + + def test_os_corrects_the_stale_product_name(self) -> None: + from fetch_cord.native.Windows import os_info + + with fake_registry(): + self.assertEqual(os_info.fetch(), "Microsoft Windows 11 Pro") + + def test_os_leaves_windows_10_alone(self) -> None: + from fetch_cord.native.Windows import os_info + + values = dict(REGISTRY) + values[(CURRENT_VERSION, "CurrentBuildNumber")] = "19045" + + with fake_registry(values): + self.assertEqual(os_info.fetch(), "Microsoft Windows 10 Pro") + + def test_kernel_matches_win32_version(self) -> None: + from fetch_cord.native.Windows import kernel + + with fake_registry(): + self.assertEqual(kernel.fetch(), "10.0.26100") + + def test_cpu_matches_win32_name(self) -> None: + from fetch_cord.native.Windows import cpu + + with fake_registry(): + self.assertEqual(cpu.fetch(), "Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz") + + def test_motherboard_and_host(self) -> None: + from fetch_cord.native.Windows import host, motherboard + + with fake_registry(): + self.assertEqual(motherboard.fetch(), "ASUSTeK COMPUTER INC.") + self.assertEqual(host.fetch(), "PRIME B760-PLUS D4") + + def test_oem_placeholders_are_not_reported(self) -> None: + """Firmware boilerplate should let another provider supply the value.""" + from fetch_cord.native.Windows import motherboard + + values = dict(REGISTRY) + values[(BIOS, "BaseBoardManufacturer")] = "To Be Filled By O.E.M." + + with fake_registry(values): + self.assertIsNone(motherboard.fetch()) + + def test_unreadable_registry_returns_none(self) -> None: + from fetch_cord.native.Windows import cpu, kernel, os_info + + with fake_registry({}): + self.assertIsNone(os_info.fetch()) + self.assertIsNone(kernel.fetch()) + self.assertIsNone(cpu.fetch()) + + def test_mem_matches_the_command_format(self) -> None: + from fetch_cord.native.Windows import mem + + memory = MagicMock(total=32 * 1024**3, available=20 * 1024**3) + with patch( + "fetch_cord.native.Windows.mem.psutil.virtual_memory", return_value=memory + ): + self.assertEqual(mem.fetch(), "12.00 GB / 32.00 GB") + + +class TestNativeProvider(unittest.TestCase): + def test_returns_the_fields_it_can_read(self) -> None: + with patch( + "fetch_cord.fetch.native_module.fetch", + side_effect=lambda component: { + "cpu": "AMD", + "os": "Microsoft Windows 11 Pro", + }.get(component), + ): + fields = NativeProvider().fetch() + + self.assertEqual(fields, {"cpu": "AMD", "os": "Microsoft Windows 11 Pro"}) + + def test_skips_fields_an_earlier_provider_supplied(self) -> None: + """Gap-filling is how Fetch.snapshot composes providers.""" + calls = [] + + def record(component: str) -> str: + calls.append(component) + return "value" + + with patch("fetch_cord.fetch.native_module.fetch", side_effect=record): + NativeProvider().fetch(skip={"cpu", "os"}) + + self.assertNotIn("cpu", calls) + self.assertNotIn("os", calls) + self.assertIn("kernel", calls) + + def test_returns_nothing_off_windows(self) -> None: + """native.fetch returns None on other platforms, so the dict is empty.""" + with patch("fetch_cord.fetch.native_module.fetch", return_value=None): + self.assertEqual(NativeProvider().fetch(), {}) + + def test_collecting_natively_runs_no_subprocess(self) -> None: + """The point of the native path: no tool dependency, no shelling out.""" + with ( + patch("fetch_cord.fetch.native_module.fetch", return_value="value"), + patch.object(subprocess, "run") as run, + patch.object(subprocess, "Popen") as popen, + patch.object(subprocess, "check_output") as check_output, + ): + NativeProvider().fetch() + + run.assert_not_called() + popen.assert_not_called() + check_output.assert_not_called() + + +class TestNativeFirstWithFastfetchFallback(unittest.TestCase): + """Native runs first; fastfetch covers whatever native has not implemented.""" + + def test_native_wins_and_fastfetch_fills_the_gaps(self) -> None: + from fetch_cord.fetch import Fetch + + native = MagicMock() + native.fetch.return_value = {"cpu": "from native"} + fastfetch = MagicMock() + fastfetch.fetch.return_value = { + "cpu": "from fastfetch", + "gpu": "from fastfetch", + } + + data = Fetch([native, fastfetch]).snapshot() + + self.assertEqual(data["cpu"], "from native") + self.assertEqual(data["gpu"], "from fastfetch") + + def test_a_field_native_cannot_read_falls_through(self) -> None: + """The safety property: native omits what it is unsure of, so nothing + is lost by putting it first.""" + from fetch_cord.fetch import Fetch + + fastfetch = MagicMock() + fastfetch.fetch.return_value = {"os": "from fastfetch"} + + with patch("fetch_cord.fetch.native_module.fetch", return_value=None): + data = Fetch([NativeProvider(), fastfetch]).snapshot() + + self.assertEqual(data["os"], "from fastfetch") + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 026a2803e48fa0409052e3a8e5d983ffbe87f277 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 17:00:17 +0000 Subject: [PATCH 2/3] Make the registry fake a real context manager fake_registry was annotated -> MagicMock but returned patch(...), which is a _patch object. Only used as a context manager, so make it one with @contextmanager and type it Iterator[MagicMock]. Caught by CI's "mypy fetch_cord tests" - the suite is type checked too. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WB6HG4WLukGEjhcq8FZJ7A --- tests/test_native_windows.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_native_windows.py b/tests/test_native_windows.py index fab57e0..7d10ac0 100644 --- a/tests/test_native_windows.py +++ b/tests/test_native_windows.py @@ -6,6 +6,8 @@ import subprocess import unittest +from collections.abc import Iterator +from contextlib import contextmanager from unittest.mock import MagicMock, patch from fetch_cord.fetch import NativeProvider @@ -28,14 +30,20 @@ } -def fake_registry(values: dict[tuple[str, str], str] | None = None) -> MagicMock: +@contextmanager +def fake_registry( + values: dict[tuple[str, str], str] | None = None, +) -> Iterator[MagicMock]: """Patch the registry helper with a fixed set of values.""" table = REGISTRY if values is None else values def read(path: str, name: str) -> str | None: return table.get((path, name)) - return patch("fetch_cord.native.Windows.registry.read", side_effect=read) + with patch( + "fetch_cord.native.Windows.registry.read", side_effect=read + ) as mock_read: + yield mock_read class TestWindowsFetchers(unittest.TestCase): From 81a83af6914480e148135ed542d4045714e709c5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 11 Sep 2026 18:10:52 +0000 Subject: [PATCH 3/3] Join board vendor and product in native motherboard fetch Align with #243 so the host cycle still gets a board-model string for id matching when NativeProvider wins over the PowerShell command. Co-authored-by: BlivionIaG --- fetch_cord/native/Windows/motherboard.py | 14 +++++++++++--- tests/test_native_windows.py | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/fetch_cord/native/Windows/motherboard.py b/fetch_cord/native/Windows/motherboard.py index 7f4de59..7803262 100644 --- a/fetch_cord/native/Windows/motherboard.py +++ b/fetch_cord/native/Windows/motherboard.py @@ -1,6 +1,11 @@ -"""Motherboard manufacturer, matching Win32_BaseBoard.Manufacturer. +"""Motherboard vendor and product, matching the Windows PowerShell command. -e.g. "ASUSTeK COMPUTER INC." +Joins BaseBoardManufacturer and BaseBoardProduct the same way Linux reports +board_vendor + board_name, so the host cycle's app_id/top_line can match +board-specific entries in motherboards.json (e.g. TUF) rather than only the +generic vendor. + +e.g. "ASUSTeK COMPUTER INC. PRIME B760-PLUS D4" """ from fetch_cord.native.Windows import registry @@ -31,4 +36,7 @@ def usable(value: str | None) -> str | None: def fetch() -> str | None: - return usable(registry.read(registry.BIOS, "BaseBoardManufacturer")) + manufacturer = usable(registry.read(registry.BIOS, "BaseBoardManufacturer")) + product = usable(registry.read(registry.BIOS, "BaseBoardProduct")) + parts = [part for part in (manufacturer, product) if part] + return " ".join(parts) or None diff --git a/tests/test_native_windows.py b/tests/test_native_windows.py index 7d10ac0..c523d85 100644 --- a/tests/test_native_windows.py +++ b/tests/test_native_windows.py @@ -80,15 +80,28 @@ def test_motherboard_and_host(self) -> None: from fetch_cord.native.Windows import host, motherboard with fake_registry(): - self.assertEqual(motherboard.fetch(), "ASUSTeK COMPUTER INC.") + # Matches #243's PowerShell: vendor + product for id matching. + self.assertEqual( + motherboard.fetch(), "ASUSTeK COMPUTER INC. PRIME B760-PLUS D4" + ) self.assertEqual(host.fetch(), "PRIME B760-PLUS D4") + def test_motherboard_falls_back_to_vendor_when_product_missing(self) -> None: + from fetch_cord.native.Windows import motherboard + + values = dict(REGISTRY) + del values[(BIOS, "BaseBoardProduct")] + + with fake_registry(values): + self.assertEqual(motherboard.fetch(), "ASUSTeK COMPUTER INC.") + def test_oem_placeholders_are_not_reported(self) -> None: """Firmware boilerplate should let another provider supply the value.""" from fetch_cord.native.Windows import motherboard values = dict(REGISTRY) values[(BIOS, "BaseBoardManufacturer")] = "To Be Filled By O.E.M." + values[(BIOS, "BaseBoardProduct")] = "To Be Filled By O.E.M." with fake_registry(values): self.assertIsNone(motherboard.fetch())