Skip to content

Commit 8ef3040

Browse files
authored
Automatically remove version numbers from uploaded modlists. (zeusops#21)
* Describe and test for automatic version number removal * Automatically remove version numbers from uploaded modlists * Add `keep_versions` flag to `/zeus-upload`
1 parent 772c494 commit 8ef3040

File tree

6 files changed

+59
-8
lines changed

6 files changed

+59
-8
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ The project uses semantic versioning (see [semver](https://semver.org)).
99

1010
- New optional argument `activate` to `/zeus-upload`. If set to true, the
1111
uploaded mission is automatically set as the active config.
12+
- Version numbers are automatically removed from the uploaded modlist. A new
13+
optional argument `keep_versions=False` for `/zeus-upload` can be used to
14+
preserve the version numbers.
1215

1316
## v0.5.0 - 2025-03-26
1417

features/reforger_upload.feature

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ Scenario: Upload next mission
1313
And Zeus specifies <modlist.json>, <scenarioId>, <filename>
1414
Then a new server config file is created
1515
And the config file is patched with <modlist.json> and <scenarioId>
16+
And the version numbers are removed from the mods.
17+
18+
Scenario: Upload next mission with versions retained
19+
Given a Zeusops mission locally ready
20+
When Zeus calls "/zeus-upload" with "keep_versions=True"
21+
Then a new server config file is created
22+
And the config file is patched with <modlist.json> and <scenarioId>
23+
And the version numbers are kept as-is.
1624

1725
Scenario: Upload next mission and activate
1826
Given a Zeusops mission locally ready

src/zeusops_bot/cogs/zeus_upload.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,30 @@ def __init__(self, bot: "ZeusopsBot", reforger_confgen: ReforgerConfigGenerator)
5151
description="Immediately use this uploaded mission as the active mission",
5252
required=False,
5353
)
54+
@discord.option(
55+
"keep_versions",
56+
description=(
57+
"Prevent version numbers from being removed from the uploaded modlist"
58+
),
59+
required=False,
60+
)
5461
async def zeus_upload(
5562
self,
5663
ctx: discord.ApplicationContext,
5764
scenario_id: str,
5865
filename: str,
5966
modlist: discord.Attachment | None = None,
6067
activate: bool = False,
68+
keep_versions: bool = True,
6169
):
6270
"""Upload a mission as a Zeus"""
6371
extracted_mods = None
6472
try:
6573
if modlist is not None:
6674
data = await modlist.read()
67-
extracted_mods = extract_mods(data.decode())
75+
extracted_mods = extract_mods(
76+
data.decode(), keep_versions=keep_versions
77+
)
6878
except ConfigFileInvalidJson as e:
6979
await ctx.respond(
7080
"Failed to understand the attached modlist as JSON. "

src/zeusops_bot/reforger_config_gen.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,14 @@ def patch_file(source: dict, modlist: list[ModDetail] | None, scenario_id: str)
158158
return mod
159159

160160

161-
def extract_mods(modlist: str | None) -> list[ModDetail] | None:
161+
def extract_mods(modlist: str | None, keep_versions=False) -> list[ModDetail] | None:
162162
"""Extracts a list of ModDetail entries from a mod list exported from Reforger
163163
164164
Args:
165165
modlist: A partial JSON string of mods to extract. Can be None if the
166166
user did not provide any mods, in which case the operation is a
167167
no-op.
168+
keep_versions: Preserve mod versions in the extracted list of mods.
168169
169170
Raises:
170171
pydantic.ValidationError: Generic error in validating the mod list
@@ -177,7 +178,7 @@ def extract_mods(modlist: str | None) -> list[ModDetail] | None:
177178
return None
178179
modlist = f"[{modlist}]"
179180
try:
180-
return modlist_typeadapter.validate_json(modlist)
181+
mods = modlist_typeadapter.validate_json(modlist)
181182
except ValidationError as e:
182183
errors = e.errors()
183184
if len(errors) > 1:
@@ -190,3 +191,9 @@ def extract_mods(modlist: str | None) -> list[ModDetail] | None:
190191
case _:
191192
# Unknown error
192193
raise e
194+
195+
if not keep_versions:
196+
for mod in mods:
197+
del mod["version"]
198+
199+
return mods

tests/fixtures.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
{"modId": "60EAEA0389DB3CC2", "name": "ACE Trenches", "version": "1.2.0"},
2323
]
2424

25+
MODLIST_DICT_VERSIONLESS: list[ModDetail] = [
26+
{"modId": "595F2BF2F44836FB", "name": "RHS - Status Quo"},
27+
{"modId": "5EB744C5F42E0800", "name": "ACE Chopping"},
28+
{"modId": "60EAEA0389DB3CC2", "name": "ACE Trenches"},
29+
]
30+
2531
# NOTE: These two formats are not 100% equivalent: Reforger exports the mods in
2632
# a JSON list, but doesn't actually include the outer [] in the exported
2733
# string, because the data is meant to be pasted directly inside the

tests/test_upload_mission.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,29 @@
1111

1212
import pytest
1313

14-
from tests.fixtures import BASE_CONFIG, MODLIST_DICT, MODLIST_JSON
14+
from tests.fixtures import (
15+
BASE_CONFIG,
16+
MODLIST_DICT,
17+
MODLIST_DICT_VERSIONLESS,
18+
MODLIST_JSON,
19+
)
20+
from zeusops_bot.models import ModDetail
1521
from zeusops_bot.reforger_config_gen import ReforgerConfigGenerator, extract_mods
1622

1723

18-
@pytest.mark.parametrize("activate", (False, True))
19-
def test_upload_edits_files(base_config: Path, mission_dir: Path, activate: bool):
24+
@pytest.mark.parametrize(
25+
"keep_versions,mods",
26+
[(False, MODLIST_DICT_VERSIONLESS), (True, MODLIST_DICT)],
27+
ids=["strip versions", "keep versions"],
28+
)
29+
@pytest.mark.parametrize("activate", (False, True), ids=["no activate", "activate"])
30+
def test_upload_edits_files(
31+
base_config: Path,
32+
mission_dir: Path,
33+
activate: bool,
34+
keep_versions: bool,
35+
mods: list[ModDetail],
36+
):
2037
"""Scenario: Upload next mission creates file"""
2138
# Given a Zeusops mission locally ready
2239
# And Zeus specifies <modlist.json>, <scenarioId>, <filename>
@@ -26,15 +43,15 @@ def test_upload_edits_files(base_config: Path, mission_dir: Path, activate: bool
2643
base_config_file=base_config, target_folder=mission_dir
2744
)
2845
# When Zeus calls "/zeus-upload"
29-
modlist = extract_mods(MODLIST_JSON)
46+
modlist = extract_mods(MODLIST_JSON, keep_versions)
3047
out_path = config_gen.zeus_upload(scenario_id, filename, modlist, activate)
3148
# Then a new server config file is created
3249
assert out_path.is_file(), "Should have generated a file on disk"
3350
# And the config file is patched with <modlist.json> and <scenarioId>
3451
config = json.loads(out_path.read_text())
3552
assert config["game"]["scenarioId"] == scenario_id, "Should update scenarioId"
3653
assert isinstance(config["game"]["mods"], list)
37-
assert config["game"]["mods"][0] == MODLIST_DICT[0]
54+
assert config["game"]["mods"][0] == mods[0]
3855

3956

4057
def test_upload_activate_mission(base_config: Path, mission_dir: Path):

0 commit comments

Comments
 (0)