Skip to content

Commit a02d04e

Browse files
committed
Address review: fix compatibility bug, improve docs, add tests
- Pass compatibility= from create_task() to edit_task() (was silently dropped since it is a named param, not in **kwargs) - Fix `if compatibility:` to `if compatibility is not None:` so that value 0 (AT/Windows 2000) is not skipped - Expand docstrings for compatibility in create_task and edit_task to list what each integer value means - Add unit tests in test_win_task_compatibility.py that mock COM and verify compatibility is set correctly for all four values, and that create_task forwards the parameter to edit_task
1 parent 128ae48 commit a02d04e

2 files changed

Lines changed: 133 additions & 10 deletions

File tree

‎salt/modules/win_task.py‎

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,13 @@ def list_actions(name, location="\\"):
530530

531531

532532
def create_task(
533-
name, location="\\", user_name="System", password=None, force=False, compatibility=2, **kwargs
533+
name,
534+
location="\\",
535+
user_name="System",
536+
password=None,
537+
force=False,
538+
compatibility=2,
539+
**kwargs,
534540
):
535541
r"""
536542
Create a new task in the designated location. This function has many keyword
@@ -571,9 +577,15 @@ def create_task(
571577
Default is ``False``.
572578
573579
compatibility (:obj:`int`, optional):
574-
The task compatibility level
580+
The task compatibility level. Determines which versions of Windows
581+
the task is compatible with. Valid values are:
575582
576-
Default is 2
583+
- 0: Windows Server 2003, Windows XP, or Windows 2000
584+
- 1: Windows Vista, Windows Server 2008 (V1 task)
585+
- 2: Windows 7, Windows Server 2008 R2 (V2 task, default)
586+
- 3: Windows 10
587+
588+
Default is ``2``.
577589
578590
Returns:
579591
bool: ``True`` if successful, otherwise ``False``.
@@ -602,6 +614,7 @@ def create_task(
602614
task_definition=task_definition,
603615
user_name=user_name,
604616
password=password,
617+
compatibility=compatibility,
605618
**kwargs,
606619
)
607620

@@ -1077,12 +1090,13 @@ def edit_task(
10771090
Default is ``None``.
10781091
10791092
compatibility (:obj:`int`, optional):
1080-
Sets the task compatibility level. Valid values are:
1081-
1082-
- 0
1083-
- 1
1084-
- 2
1085-
- 3
1093+
Sets the task compatibility level. Determines which versions of
1094+
Windows the task is compatible with. Valid values are:
1095+
1096+
- 0: Windows Server 2003, Windows XP, or Windows 2000
1097+
- 1: Windows Vista, Windows Server 2008 (V1 task)
1098+
- 2: Windows 7, Windows Server 2008 R2 (V2 task)
1099+
- 3: Windows 10
10861100
10871101
Default is ``None``.
10881102
@@ -1164,7 +1178,7 @@ def edit_task(
11641178
# Settings: General Tab
11651179
if hidden is not None:
11661180
task_definition.Settings.Hidden = hidden
1167-
if compatibility:
1181+
if compatibility is not None:
11681182
task_definition.Settings.Compatibility = compatibility
11691183

11701184
# Settings: Conditions Tab (Idle)
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""
2+
Unit tests for the compatibility parameter in win_task.create_task and
3+
win_task.edit_task. These tests use mocks and run on any platform.
4+
"""
5+
6+
import sys
7+
import types
8+
9+
import pytest
10+
11+
from tests.support.mock import MagicMock, patch
12+
13+
# Inject a stub win32com into sys.modules if not already present so that
14+
# salt.modules.win_task can be imported on non-Windows hosts.
15+
if "win32com" not in sys.modules:
16+
_win32com = types.ModuleType("win32com")
17+
_win32com_client = types.ModuleType("win32com.client")
18+
_win32com_client.Dispatch = MagicMock()
19+
_win32com.client = _win32com_client
20+
sys.modules["win32com"] = _win32com
21+
sys.modules["win32com.client"] = _win32com_client
22+
if "pythoncom" not in sys.modules:
23+
sys.modules["pythoncom"] = types.ModuleType("pythoncom")
24+
if "pywintypes" not in sys.modules:
25+
sys.modules["pywintypes"] = types.ModuleType("pywintypes")
26+
27+
import salt.modules.win_task as win_task # noqa: E402
28+
29+
30+
@pytest.mark.parametrize(
31+
"compatibility",
32+
[
33+
0, # Windows 2000/XP/2003
34+
1, # Windows Vista/2008 (V1 task)
35+
2, # Windows 7/2008 R2 (V2 task)
36+
3, # Windows 10
37+
],
38+
)
39+
def test_edit_task_compatibility(compatibility):
40+
"""
41+
edit_task sets Settings.Compatibility to the given value when a
42+
task_definition is passed directly (no COM lookup needed).
43+
"""
44+
mock_task_def = MagicMock()
45+
mock_task_def.Principal.UserID = "SYSTEM"
46+
mock_task_def.Principal.LogonType = 5 # TASK_LOGON_SERVICE_ACCOUNT
47+
48+
with patch("salt.utils.winapi.Com", MagicMock()):
49+
win_task.edit_task(
50+
task_definition=mock_task_def,
51+
compatibility=compatibility,
52+
)
53+
54+
assert mock_task_def.Settings.Compatibility == compatibility
55+
56+
57+
def test_edit_task_compatibility_none_leaves_unset():
58+
"""
59+
edit_task does not touch Settings.Compatibility when compatibility=None.
60+
"""
61+
mock_task_def = MagicMock()
62+
mock_task_def.Principal.UserID = "SYSTEM"
63+
mock_task_def.Principal.LogonType = 5
64+
65+
# Snapshot the auto-generated MagicMock child before the call
66+
sentinel = mock_task_def.Settings.Compatibility
67+
68+
with patch("salt.utils.winapi.Com", MagicMock()):
69+
win_task.edit_task(
70+
task_definition=mock_task_def,
71+
compatibility=None,
72+
)
73+
74+
# If the branch was skipped, the attribute is still the same mock object
75+
assert mock_task_def.Settings.Compatibility is sentinel
76+
77+
78+
def test_create_task_passes_compatibility_to_edit_task():
79+
"""
80+
create_task forwards its compatibility= argument to edit_task so that
81+
Settings.Compatibility is set on the new task definition.
82+
"""
83+
mock_task_def = MagicMock()
84+
mock_task_def.Principal.UserID = "SYSTEM"
85+
mock_task_def.Principal.LogonType = 5
86+
87+
mock_task_service = MagicMock()
88+
mock_task_service.NewTask.return_value = mock_task_def
89+
90+
with (
91+
patch("salt.utils.winapi.Com", MagicMock()),
92+
patch.object(
93+
win_task.win32com.client, "Dispatch", return_value=mock_task_service
94+
),
95+
patch.object(win_task, "list_tasks", return_value=[]),
96+
patch.object(win_task, "add_action", return_value=True),
97+
patch.object(win_task, "add_trigger", return_value=True),
98+
patch.object(win_task, "_save_task_definition", return_value=True),
99+
):
100+
win_task.create_task(
101+
"TestTask",
102+
user_name="System",
103+
force=True,
104+
compatibility=1,
105+
action_type="Execute",
106+
cmd="cmd.exe",
107+
)
108+
109+
assert mock_task_def.Settings.Compatibility == 1

0 commit comments

Comments
 (0)