Compare commits

..
40 Commits
Author SHA1 Message Date
aclist 3b4a736ce6 Merge pull request #420 from aclist/chore/update-tests
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
chore: update tests
2026-07-27 20:22:43 +09:00
aclist b70428fe3d chore: BM API is expected to fail (#417) 2026-07-27 20:21:56 +09:00
aclist 3f037cdad6 chore: add realconfig namespace to local file tests 2026-07-27 20:21:39 +09:00
aclist 9b63d38c5a chore: more granular test namespaces 2026-07-27 20:21:13 +09:00
aclist 4c26d85afd Merge branch 'dzgui7' of github.com:aclist/dztui into dzgui7 2026-07-27 20:15:38 +09:00
aclist 35b8d21181 Merge pull request #419 from aclist/fix/deprecation-warnings-2
chore: comment out unused code
2026-07-27 20:15:30 +09:00
aclist e368e98a1a Merge branch 'fix/deprecation-warnings-2' of github.com:aclist/dztui into fix/deprecation-warnings-2 2026-07-27 20:14:55 +09:00
aclist 2ea6490a9b chore: comment out unused code 2026-07-27 20:14:43 +09:00
aclist 7b57f95f5f Merge pull request #418 from aclist/fix/deprecation-warnings
fix: clear deprecation warnings
2026-07-27 20:14:30 +09:00
aclist a811e5945d chore: commend out unused code 2026-07-27 20:13:47 +09:00
aclist 9b305d806b Merge pull request #418 from aclist/fix/deprecation-warnings
fix: clear deprecation warnings
2026-07-27 20:11:32 +09:00
aclist 1955abd3e6 Merge branch 'dzgui7' into fix/deprecation-warnings 2026-07-27 20:10:45 +09:00
aclist aac29c0a5c chore: bump version 2026-07-27 20:10:08 +09:00
aclist 218ed43403 Merge pull request #416 from aclist/feat/dynamic-copy
feat: dynamic clipboard button
2026-07-27 20:08:39 +09:00
aclist 380f6f82a1 Merge pull request #414 from aclist/feat/offline-eventbox
feat: offline eventbox
2026-07-27 20:07:38 +09:00
aclist cef9e1984f Merge pull request #415 from aclist/feat/wizard-mixins
feat: wizard mixins
2026-07-27 20:06:06 +09:00
aclist 0520ced2ea chore: drop unused import 2026-07-27 18:58:41 +09:00
aclist 4bbe179940 chore: comment out more unused blocks 2026-07-27 17:42:45 +09:00
aclist 1527239567 fix: clear deprecation warnings 2026-07-27 17:41:12 +09:00
aclist 64c8b905c4 chore: add comments 2026-07-24 02:00:18 +09:00
aclist 52ab81f4ec feat: dynamic update on clipboard button click 2026-07-24 01:56:29 +09:00
aclist ec7828df85 feat: add eventbox to offline mods page 2026-07-24 01:30:32 +09:00
aclist 525de56036 feat: redact user id in logs
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-07-24 01:13:00 +09:00
aclist 7edb66c311 chore: distinguish between api and web tests 2026-07-24 01:05:01 +09:00
aclist dc2a635d2f chore: add typehints to test 2026-07-24 01:04:41 +09:00
aclist 8528300851 chore: update crc tests 2026-07-24 01:01:39 +09:00
aclist daa4aa0059 chore: clear typehinting errors resulting from mixins
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
2026-07-24 00:48:45 +09:00
aclist 71a0481600 fix: add comma 2026-07-24 00:47:28 +09:00
aclist ba1b980a7f chore: rename test namespace 2026-07-24 00:47:08 +09:00
aclist 1dc20b838d fix: support source version of DZGUI 2026-07-24 00:45:27 +09:00
aclist 0c6421fa63 chore: update freedesktop comment
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-07-21 16:30:07 +09:00
aclist b47cb482f5 Merge pull request #412 from aclist/fix/deck-unfullscreen-dimensions
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
fix: set explicitly safe dimensions for Steam Deck
2026-07-21 16:03:20 +09:00
aclist 152999d728 fix: set explicitly safe dimensions for Steam Deck 2026-07-21 16:01:48 +09:00
aclist cdcb3b794f Merge pull request #411 from aclist/fix/deck-unfullscreen
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
fix: delegate dialog size to system settings on Steam Deck
2026-07-21 15:22:37 +09:00
aclist cd23f403c9 fix: try delegating size to desktop resolution 2026-07-21 15:21:34 +09:00
aclist a40bef8f82 fix: hotfix for Steam OS fullscreen bug 2026-07-21 15:16:42 +09:00
aclist a4f9a67da1 fix: use drop-in mixin to set page progress state 2026-07-21 15:01:11 +09:00
aclist 5c07a77b63 Merge pull request #409 from aclist/fix/beta-client-vdf
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
fix: handle alternate client versions when parsing vdf
2026-07-15 11:29:54 +09:00
aclist 91773c933c chore: bump version 2026-07-15 11:26:01 +09:00
aclist 6a30ed44fc fix: handle alternate client versions when parsing vdf 2026-07-15 11:25:47 +09:00
25 changed files with 241 additions and 67 deletions

No files matched your search

+21 -13
View File
@@ -49,9 +49,21 @@ class Shortcuts:
def find_appname_by_unsigned_id(self, appid: int) -> str:
# NOTE: bitmask signed int back to 32-bit CRC
for key in self.shortcuts["shortcuts"].keys():
if self.shortcuts["shortcuts"][key]["appid"] & 0xFFFFFFFF == appid:
return str(self.shortcuts["shortcuts"][key]["appname"])
# varying client versions treat case sensitivity differently
try:
for key in self.shortcuts["shortcuts"].keys():
if self.shortcuts["shortcuts"][key]["appid"] & 0xFFFFFFFF == appid:
try:
return str(self.shortcuts["shortcuts"][key]["appname"])
except Exception as e:
logger.debug(e)
try:
return str(self.shortcuts["shortcuts"][key]["AppName"])
except Exception as e:
logger.debug(e)
return unknown
except Exception as e:
logger.debug(e)
return unknown
def get_shortcuts(self) -> Any:
@@ -119,22 +131,18 @@ class Shortcuts:
https://developer.valvesoftware.com/wiki/Add_Non-Steam_Game
https://developer.valvesoftware.com/wiki/Steam_Library_Shortcuts
Wiki variously lists keys with title case and lowercase, but keys actually do
not seem to be case-sensitive. Some entries generated by Steam do not match the wiki
Keys are entered into the dictionary in a linear insertion order
# TODO: try to replicate key case sensitivity as it is created by Steam with generic shortcut
Key case is not internally consistent and varies between client versions
Most keys use Pascal case, but some do not
appid: signed int CRC
exe: absolute path to the executable, must be wrapped in literal quotes
Exe: absolute path to the executable, must be wrapped in literal quotes
StartDir: directory the executable starts in, generally the parent
"""
NEW_ENTRY: dict[str, Any] = {}
NEW_ENTRY["appid"] = meta.appid
NEW_ENTRY["appname"] = meta.appname
NEW_ENTRY["exe"] = f'"{meta.exe_path}"'
NEW_ENTRY["AppName"] = meta.appname
NEW_ENTRY["Exe"] = f'"{meta.exe_path}"'
NEW_ENTRY["StartDir"] = f"{meta.start_dir}"
NEW_ENTRY["icon"] = meta.icon
NEW_ENTRY["ShortcutPath"] = ""
@@ -142,7 +150,7 @@ class Shortcuts:
NEW_ENTRY["IsHidden"] = 0
NEW_ENTRY["AllowDesktopConfig"] = 1
NEW_ENTRY["AllowOverlay"] = 1
NEW_ENTRY["openvr"] = 0
NEW_ENTRY["OpenVR"] = 0
NEW_ENTRY["Devkit"] = 0
NEW_ENTRY["DevkitGameID"] = ""
NEW_ENTRY["DevkitOverrideAppID"] = 0
+9 -2
View File
@@ -205,21 +205,28 @@ def launch_offline(
proc = subprocess.run([*client_args, *params])
return proc.returncode
def find_loginusers(path: Path) -> Path:
return path / "config" / "loginusers.vdf"
def find_user_id(path: Path) -> str | None:
resolved_path = path / "config" / "loginusers.vdf"
resolved_path = find_loginusers(path)
try:
with open(resolved_path, "r") as f:
v = vdf.load(f)
# NOTE: beta client
# /package/beta
if len(v["users"]) == 1:
return str(list(v["users"].keys())[0])
for user in v["users"]:
if v["users"][user]["MostRecent"] == "1":
return str(user)
return None
except Exception as e:
logger.warn(e)
logger.debug(e)
return None
def find_user_id_32(path: Path) -> int:
uid = find_user_id(path)
if uid is None:
+1 -1
View File
@@ -21,7 +21,7 @@ def write_desktop_file(exe_path: Path) -> Path:
Terminal=false
Exec={exe_path}
Name=DZGUI
Comment=dzgui
Comment=DayZ server browser and mod manager
Icon={icon}
Categories=Game"""
+1 -1
View File
@@ -259,7 +259,7 @@ class ProxyModelManager:
def set_cache(self, filters: tuple[str], filtered_rows: list[tuple]) -> None:
self.filter_cache[filters] = filtered_rows
@deprecated("Currently unused")
# @deprecated("Currently unused")
# def convert_model_to_list(self, model: "FastInsertListStore") -> list:
# return [[el for el in row] for row in model]
+3
View File
@@ -1,4 +1,7 @@
heading = "Play offline"
heading_disclaimer = (
"Not all mod combinations will function offline. Use at your own discretion."
)
mission_button = "Select mission folder"
mission_frame = "Mission"
+2 -1
View File
@@ -4,8 +4,9 @@ from typing import Literal
api_filter = r"(.*&key=)([^&]*)(.*)"
home_filter = r"(/home/)([^\s'\/]*)(.*)"
user_filter = r"(.*Steam/userdata/)([^/]*)(.*)"
REDACTED = r"\1REDACTED\3"
REDACTION_PATTERNS = [api_filter, home_filter]
REDACTION_PATTERNS = [api_filter, home_filter, user_filter]
def redact_home(text: str) -> str:
+18 -1
View File
@@ -1,4 +1,4 @@
from typing import Callable, Self, TYPE_CHECKING, Union
from typing import Callable, Literal, Self, TYPE_CHECKING, Union
from dzgui.util.clip import copy_clipboard
from dzgui.util.format import pluralize
@@ -66,6 +66,15 @@ class IconButton(Gtk.Button):
# self.set_image_position(Gtk.PositionType.RIGHT)
self.set_focus_on_click(False)
def swap_icon(self, icon: str) -> None:
start = self.icon.get_margin_start()
end = self.icon.get_margin_end()
alt_icon = Icon(icon, margin_start=start, margin_end=end)
self.set_image(alt_icon)
def revert_icon(self) -> None:
self.set_image(self.icon)
class IconTextButton(IconButton):
def __init__(
@@ -96,11 +105,18 @@ class ClipboardButton(IconTextButton):
self.controller = controller
self.connect("clicked", self._on_button_clicked, func)
# TODO: strings
self.set_tooltip_text("Copy to clipboard")
def _on_button_clicked(self, button: Self, func: Callable) -> None:
def revert() -> Literal[False]:
self.revert_icon()
return False
self.swap_icon("object-select-symbolic")
data = func()
copy_clipboard(data)
GLib.timeout_add(600, revert)
# TODO: determine when controller would be passed to this button or drop
@@ -108,6 +124,7 @@ class CopyIpButton(ClipboardButton):
def __init__(self, controller: Union["Controller", None], func: Callable) -> None:
super().__init__(controller, func)
# TODO: strings
self.set_tooltip_text("Copy IP to clipboard")
+2 -1
View File
@@ -162,7 +162,8 @@ class APIEntry(Gtk.Box):
self.entry.connect("icon-release", self._on_icon_release)
self.entry.connect("activate", self._on_field_activated)
self.submit = Gtk.Button(label="Submit")
# TODO: strings
self.submit = Gtk.Button(label="Validate")
self.submit.set_sensitive(False)
self.submit.connect("clicked", self._on_submit)
+3
View File
@@ -30,6 +30,9 @@ class InfoEventBox(Gtk.EventBox):
self.connect("leave-notify-event", self._on_leave_tooltip)
self.add(box)
def set_icon_yalign(self, offset: float) -> None:
self.icon.set_property("yalign", offset)
def _on_enter_tooltip(
self, eventbox: Gtk.EventBox, eventcrossing: Gdk.EventCrossing
) -> None:
+1
View File
@@ -131,6 +131,7 @@ class RightPanel(Gtk.Box):
return
self.copying = True
version = self.version_label.get_text()
# TODO: strings
self.version_label.set_text("Copied!")
copy_clipboard(version)
GLib.timeout_add_seconds(1, revert)
+1 -1
View File
@@ -41,7 +41,7 @@ class BootDialog(Gtk.Dialog):
def __init__(self, parent: "BootWindow", xdg: "Xdg", version: str) -> None:
super().__init__(
title=dialog_header,
parent=parent,
transient_for=parent,
modal=True,
)
+1 -1
View File
@@ -13,7 +13,7 @@ class FilePicker(Gtk.FileChooserDialog):
super().__init__(
title=picker.title,
action=Gtk.FileChooserAction.SAVE,
parent=parent,
transient_for=parent,
resizable=True,
)
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL)
+29 -24
View File
@@ -50,6 +50,13 @@ class PageNum(Enum):
FINAL = 8
class OptionalPageMixin:
"""Marks optional pages as advanceable"""
def _on_map(self, page: "ScrolledWizardPage") -> None:
EMITTER.emit("step_complete")
class DescriptionArea(Gtk.Box):
def __init__(self, text: str):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
@@ -91,6 +98,7 @@ class ScrolledWizardPage(Gtk.ScrolledWindow):
margin_top=50,
spacing=20,
)
self.add(self.box)
self.prog = Progress()
self.box.pack_end(self.prog, expand=False, fill=False, padding=0)
@@ -215,7 +223,7 @@ class APIValidationPage(ScrolledWizardPage):
self.spinner.stop()
class BMValidationPage(APIValidationPage):
class BMValidationPage(OptionalPageMixin, APIValidationPage): # type: ignore
def __init__(self) -> None:
super().__init__(
enum=PageNum.BM_API,
@@ -224,6 +232,7 @@ class BMValidationPage(APIValidationPage):
link=BM_API_SETUP,
func=self._validate,
)
self.connect("map", self._on_map)
@call_on_thread("", show_dialog=False)
def _validate(self, key: str) -> None:
@@ -440,10 +449,12 @@ class Assistant(Gtk.Assistant):
def __init__(self, is_deck: bool, XDG: "Xdg"):
super().__init__()
if is_deck:
self.fullscreen()
# NOTE: deemed to be "safe" dimensions that exclude taskbar size
self.set_default_size(1085, 670)
else:
self.set_default_size(1500, 900)
self.is_binary = False if os.getenv("PYAPP") is None else True
self.config_path = XDG.config
self.config_values: dict[str, Any] = config_boilerplate
@@ -484,7 +495,7 @@ class Assistant(Gtk.Assistant):
):
continue
# NOTE: disabled for now on system-provided packages
if isinstance(page, ShortcutCreationPage) and os.getenv("PYAPP") is None:
if isinstance(page, ShortcutCreationPage) and not self.is_binary:
continue
self._add_page(page, page.get_page_type())
@@ -499,32 +510,33 @@ class Assistant(Gtk.Assistant):
def _advance_page(self, index: int) -> int:
page = self.get_nth_page(index)
# TODO: use enums/isinstance
match page:
case self.page1:
case IntroductionPage():
pass
case self.page2:
if self.page2.is_migrated():
case ConfigMigrationPage():
if page.is_migrated():
steam_path = lookup(self.config_path, Preferences.DEFAULT)
self.page7.set_steam_path(steam_path)
return self.get_n_pages() - 2
case self.page3:
offset = 1 if not self.is_binary else 2
self.setup_complete = True
return self.get_n_pages() - offset
case SteamPathPage():
self.config_values["default_steam_path"] = page.get_path_from_radio()
case self.page4:
case SteamValidationPage():
self.config_values["steam_api"] = page.get_api_key()
case self.page5:
case BMValidationPage():
self.config_values["bm_api"] = page.get_api_key()
# NOTE: collects config values before advancing to last page
case self.page6:
case PreferencesPage():
# NOTE: collects config values before advancing to last page
name, use_miles, client = self.page6.get_prefs()
self.config_values["name"] = name
self.config_values["use_miles"] = use_miles
self.config_values["client"] = client
self.write_config()
self.page7.set_steam_path(self.config_values["default_steam_path"])
case self.page7:
self.page7.create_shortcuts()
self.setup_complete = True
case ShortcutCreationPage():
page.create_shortcuts()
case _:
raise AttributeError("Trying to advance a non-canonical page")
return index + 1
@@ -568,11 +580,7 @@ class Assistant(Gtk.Assistant):
bar.set_fraction(fraction)
bar.set_text(f"{page_num}/{total}")
# NOTE: disable forward action
# TODO: use page enums
if page == self.page5:
return
if page != self.page1:
if not isinstance(page, IntroductionPage):
EMITTER.emit("step_pending")
@@ -599,7 +607,7 @@ class CheckboxWithLabel(Gtk.Box):
self.button.set_active(state)
class ShortcutCreationPage(ScrolledWizardPage):
class ShortcutCreationPage(OptionalPageMixin, ScrolledWizardPage): # type: ignore
def __init__(self, shortcut: Path) -> None:
super().__init__(
enum=PageNum.SHORTCUTS,
@@ -642,9 +650,6 @@ class ShortcutCreationPage(ScrolledWizardPage):
self.desktop_checkbox.set_active(state)
self.desktop_checkbox.set_sensitive(state)
def _on_map(self, page: "ScrolledWizardPage") -> None:
EMITTER.emit("step_complete")
def set_steam_path(self, path: Path) -> None:
self.steam_path = path
+14 -4
View File
@@ -41,9 +41,18 @@ class FolderError(Enum):
FOLDER_CHANGED = 3
class PageHeading(Gtk.Label):
def __init__(self, label: str) -> None:
super().__init__(label=label, halign=Gtk.Align.CENTER)
class PageHeading(Gtk.Box):
def __init__(self, label: str, controller: "Controller") -> None:
super().__init__(
halign=Gtk.Align.CENTER, orientation=Gtk.Orientation.HORIZONTAL
)
self.label = Gtk.Label(label=label)
self.eb = InfoEventBox(offline.heading_disclaimer, controller)
self.eb.set_icon_yalign(0.7)
for el in self.label, self.eb:
self.add(el)
css.add_class(self, "page-heading")
@@ -434,7 +443,8 @@ class OfflineLoader(Gtk.Box):
self.emitter = controller.get_emitter()
self.offline_man = OfflineManager(controller)
self.add(PageHeading(offline.heading))
heading = PageHeading(offline.heading, controller)
self.add(heading)
self.local_frame = ModFrame(self, controller, offline.local_frame)
self.custom_frame = CustomModFrame(self, controller, offline.custom_frame)
+2 -3
View File
@@ -1,7 +1,6 @@
import logging
from typing import Any, Optional, TYPE_CHECKING
from warnings import deprecated
from dzgui.const.constants import APP_NAME, SEPARATOR
from dzgui.util.keys import is_ctrl_mask, is_navkey
@@ -173,7 +172,7 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
(model, pathlist) = sels
return (model, pathlist)
@deprecated("Currently unused")
# @deprecated("Currently unused")
# def get_mpath(self) -> Optional[Gtk.TreePath]:
# (model, pathlist) = self.get_model_and_pathlist()
# if len(pathlist) < 1:
@@ -195,7 +194,7 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
return True
return False
@deprecated("unused")
# @deprecated("unused")
# def get_selected_row(self) -> Optional[Gtk.TreeModelRow]:
# ind = self.get_selected_row_index()
# model = self.get_model()
+1 -1
View File
@@ -53,7 +53,7 @@ class LogTreeView(ContextMixin, TreeView): # type: ignore
model = ModelFactory().new_model_from_logfile(filepath)
_filter = model.filter_new()
_filter.set_visible_func(self._filter_rows)
sortable = Gtk.TreeModelSort(_filter)
sortable = Gtk.TreeModelSort.new_with_model(_filter)
self.set_model(sortable)
_filter.refilter()
path = Gtk.TreePath.new_from_indices([0])
+4 -2
View File
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [
{name = "aclist"}
]
version = "7.0.0b17"
version = "7.0.0b20"
license = "GPL-3.0-or-later"
license-files = ["LICENSE"]
readme = "README.md"
@@ -107,11 +107,13 @@ config-settings-package = { pygobject-stubs = { config = "Gtk3,Gdk3,Soup2" } }
[tool.pytest.ini_options]
markers = [
"apitest: checks remote endpoints",
"config: config file keys/values",
"realconfig: depends on actual generated local config file",
"mods: tests mod metadata/link creation",
"pefile: validate PE files",
"post_install: requires a completed installation",
"redact: log redaction mechanisms",
"slow: long-running tests",
"webtest: checks remote endpoints"
"webtest: depends on remote endpoint",
]
+13
View File
@@ -0,0 +1,13 @@
"users"
{
"0"
{
"AccountName" "STEAMUSER"
"PersonaName" "STEAMUSER"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AutoLogin" "1"
"Timestamp" "0"
}
}
+14
View File
@@ -0,0 +1,14 @@
"users"
{
"0"
{
"AccountName" "STEAMUSER"
"PersonaName" "STEAMUSER"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "1"
"Timestamp" "0"
}
}
@@ -0,0 +1,25 @@
"users"
{
"0"
{
"AccountName" "STEAMUSER"
"PersonaName" "STEAMUSER"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "0"
"Timestamp" "0"
}
"1"
{
"AccountName" "STEAMUSER2"
"PersonaName" "STEAMUSER2"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "1"
"Timestamp" "0"
}
}
+6 -2
View File
@@ -7,13 +7,12 @@ from dzgui.config.xdg import get_xdg_paths, parse_filepaths
from dzgui.config import convert
from tests.fixtures import fixture_path
pytestmark = pytest.mark.config
@pytest.fixture
def legacy_config():
return fixture_path("dztuirc_1")
@pytest.fixture
def unset_values():
return fixture_path("dztuirc_3")
@@ -38,6 +37,7 @@ def keys():
# TODO: use a static fixture instead of system config
@pytest.fixture
@pytest.mark.realconfig
def config():
paths = get_xdg_paths()
xdg = parse_filepaths(paths)
@@ -45,18 +45,21 @@ def config():
@pytest.mark.post_install
@pytest.mark.realconfig
def test_invalid_config_value(config):
with pytest.raises(Exception):
assert config["foo"] is None
@pytest.mark.post_install
@pytest.mark.realconfig
def test_default_config_values(keys, config):
for key in keys:
assert config[key] is not None
@pytest.mark.post_install
@pytest.mark.realconfig
def test_contains_invalid_values(keys, config):
for key in config:
assert key in keys
@@ -91,6 +94,7 @@ def test_key_conversion(legacy_config):
for key in keys:
assert key not in j
def test_missing_values(unset_values):
j = convert.rc2json(unset_values)
j = json.loads(j)
+8
View File
@@ -37,6 +37,14 @@ class RecordsListHandler(logging.Handler):
"Error in directory: '/home/SENSITIVE_USERNAME'",
"Error in directory: '/home/REDACTED'",
),
(
"User directory: /home/user/.local/share/Steam/userdata/999999/grid",
"User directory: /home/REDACTED/.local/share/Steam/userdata/REDACTED/grid",
),
(
"User directory: /drive/.local/share/Steam/userdata/999999",
"User directory: /drive/.local/share/Steam/userdata/REDACTED",
),
],
)
def test_log_redaction(log_error: str, expect: str) -> None:
+25
View File
@@ -0,0 +1,25 @@
import pytest
from pathlib import Path
from dzgui.api.steam import find_user_id
from tests.fixtures import fixture_path
pytestmark = pytest.mark.apitest
@pytest.mark.parametrize(
"fixture, expect",
[
("api/loginusers_legacy_client.vdf", 0),
("api/loginusers_legacy_client_multiple.vdf", 1),
("api/loginusers_beta_client.vdf", 0),
],
)
def test_loginusers(monkeypatch, fixture: str, expect: int) -> None:
def mock_loginusers(path: Path) -> Path:
return fixture_path(fixture)
monkeypatch.setattr("dzgui.api.steam.find_loginusers", mock_loginusers)
uid = find_user_id(Path(""))
assert int(uid) == expect
+5 -1
View File
@@ -13,13 +13,17 @@ def config():
xdg = parse_filepaths(paths)
return get_config(xdg.config)
def test_ipdb():
assert probe.test_ipdb()
def test_steam(config):
key = config["steam_api"]
assert probe.test_steam_api(key)
def test_bm(config):
# NOTE: see ticket #417; expected to return False
key = config["bm_api"]
assert probe.test_bm_api(key)
assert probe.test_bm_api(key) is False
+32 -8
View File
@@ -30,33 +30,33 @@ def test_no_shortcuts(monkeypatch) -> None:
@pytest.fixture
def dummy_app() -> None:
d = {
"appname": "TEST APP",
"AppName": "TEST APP",
"StartDir": "TEST_DIR",
"exe": "TEST_DIR/TEST_EXE.EXE",
"Exe": "TEST_DIR/TEST_EXE.EXE",
"icon": "IMAGES_DIR/TEST_IMAGE.PNG",
}
return d
def test_wrap_exe(dummy_app) -> None:
def test_wrap_exe(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
assert s.shortcuts["shortcuts"]["0"]["exe"][0] == '"'
assert s.shortcuts["shortcuts"]["0"]["exe"][-1] == '"'
assert s.shortcuts["shortcuts"]["0"]["Exe"][0] == '"'
assert s.shortcuts["shortcuts"]["0"]["Exe"][-1] == '"'
def test_add_shortcut(dummy_app) -> None:
def test_add_shortcut(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
new = s.shortcuts["shortcuts"]
ind = str(len(new) - 1)
for k, v in dummy_app.items():
if k == "exe":
if k == "Exe":
v = f'"{v}"'
assert new[ind][k] == v
def test_save_shortcut(dummy_app) -> None:
def test_save_shortcut(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
with tempfile.NamedTemporaryFile() as f:
@@ -65,3 +65,27 @@ def test_save_shortcut(dummy_app) -> None:
s.save_shortcuts()
s._load_shortcuts(tmp)
assert len(s.shortcuts["shortcuts"]) == 1
def test_shortcut_crc(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
for key in s.shortcuts["shortcuts"].keys():
entry = s.shortcuts["shortcuts"][key]
name = entry["AppName"]
exe = entry["Exe"]
uid = name + exe
bpid = s.gen_bpid(uid)
assert entry["appid"] & 0xFFFFFFFF == bpid
def test_reverse_crc(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
for key in s.shortcuts["shortcuts"].keys():
entry = s.shortcuts["shortcuts"][key]
name = entry["AppName"]
exe = entry["Exe"]
uid = name + exe
bpid = s.gen_bpid(uid)
assert s.find_appname_by_unsigned_id(bpid) == name