feat(MAA): 适配库存保持功能 - #314
Conversation
Reviewer's Guide端到端适配 MAA DepotMaintain(仓库维护)任务支持:后端配置与 API、前端用户编辑 UI、任务构建逻辑、常量、schema/OpenAPI 模型以及测试;同时在该功能被禁用或 MAA 版本不支持时保持兼容性。 获取 MAA 仓库物品选项的时序图sequenceDiagram
actor User
participant MAAUserEditView as MAAUserEditView
participant Service as Service
participant ScriptsRouter as ApiScriptsRouter
participant Config as Config
User ->> MAAUserEditView: loadDepotItemOptions
MAAUserEditView ->> Service: getMaaDepotItemsApiScriptsMaaDepotItemsPost
Service ->> ScriptsRouter: POST /api/scripts/maa/depot/items
ScriptsRouter ->> Config: get_maa_depot_items
Config ->> Config: get_maa_depot_items
Config -->> ScriptsRouter: list[dict[label,value]]
ScriptsRouter -->> Service: ComboBoxOut
Service -->> MAAUserEditView: ComboBoxOut
MAAUserEditView -->> User: depotItemOptions updated
构建 DepotMaintain MAA 任务的时序图sequenceDiagram
actor User
participant DepotSection as DepotMaintainConfigSection
participant MaaConfig as MaaUserConfig
participant AutoProxy as AutoProxyTask
participant Builder as _build_depot_maintain_task
participant MAA as MAA
User ->> DepotSection: toggle Task.IfDepotMaintain
User ->> DepotSection: edit DepotMaintainPlans
DepotSection ->> MaaConfig: save Task.IfDepotMaintain
DepotSection ->> MaaConfig: save Task.DepotMaintainPlans
User ->> AutoProxy: set_maa
AutoProxy ->> MaaConfig: get Task.IfDepotMaintain
AutoProxy ->> MaaConfig: get Task.DepotMaintainPlans
AutoProxy ->> Builder: _build_depot_maintain_task
Builder -->> AutoProxy: DepotMaintainTask dict
AutoProxy ->> MAA: write TaskQueue with DepotMaintain
MAA -->> User: DepotMaintain task executed
文件级改动
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard 以:
Getting HelpOriginal review guide in EnglishReviewer's GuideAdapt MAA DepotMaintain task support end-to-end: backend config and API, frontend user-edit UI, task building logic, constants, schema/OpenAPI models, and tests, while keeping compatibility when the feature is disabled or MAA version doesn’t support it. Sequence diagram for fetching MAA depot item optionssequenceDiagram
actor User
participant MAAUserEditView as MAAUserEditView
participant Service as Service
participant ScriptsRouter as ApiScriptsRouter
participant Config as Config
User ->> MAAUserEditView: loadDepotItemOptions
MAAUserEditView ->> Service: getMaaDepotItemsApiScriptsMaaDepotItemsPost
Service ->> ScriptsRouter: POST /api/scripts/maa/depot/items
ScriptsRouter ->> Config: get_maa_depot_items
Config ->> Config: get_maa_depot_items
Config -->> ScriptsRouter: list[dict[label,value]]
ScriptsRouter -->> Service: ComboBoxOut
Service -->> MAAUserEditView: ComboBoxOut
MAAUserEditView -->> User: depotItemOptions updated
Sequence diagram for building DepotMaintain MAA tasksequenceDiagram
actor User
participant DepotSection as DepotMaintainConfigSection
participant MaaConfig as MaaUserConfig
participant AutoProxy as AutoProxyTask
participant Builder as _build_depot_maintain_task
participant MAA as MAA
User ->> DepotSection: toggle Task.IfDepotMaintain
User ->> DepotSection: edit DepotMaintainPlans
DepotSection ->> MaaConfig: save Task.IfDepotMaintain
DepotSection ->> MaaConfig: save Task.DepotMaintainPlans
User ->> AutoProxy: set_maa
AutoProxy ->> MaaConfig: get Task.IfDepotMaintain
AutoProxy ->> MaaConfig: get Task.DepotMaintainPlans
AutoProxy ->> Builder: _build_depot_maintain_task
Builder -->> AutoProxy: DepotMaintainTask dict
AutoProxy ->> MAA: write TaskQueue with DepotMaintain
MAA -->> User: DepotMaintain task executed
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了两个问题,并给出了一些总体反馈:
- DepotMaintainPlans 的 JSON 处理在后端和 DepotMaintainConfigSection 中是重复且无类型约束的;建议引入一个共享的强类型模型或用于解析/序列化计划的工具方法,以减少运行时 JSON 错误并保持 schema 一致。
- 在 get_maa_depot_items 中,所有异常都会被转换成一个 500 的 ComboBoxOut,而且没有任何日志;在失败路径上增加结构化日志,可以更容易排查线上环境中缺失或无效 item_index.json 的问题。
供 AI 代理使用的提示
Please address the comments from this code review:
## Overall Comments
- DepotMaintainPlans 的 JSON 处理在后端和 DepotMaintainConfigSection 中是重复且无类型约束的;建议引入一个共享的强类型模型或用于解析/序列化计划的工具方法,以减少运行时 JSON 错误并保持 schema 一致。
- 在 get_maa_depot_items 中,所有异常都会被转换成一个 500 的 ComboBoxOut,而且没有任何日志;在失败路径上增加结构化日志,可以更容易排查线上环境中缺失或无效 item_index.json 的问题。
## Individual Comments
### Comment 1
<location path="tests/test_maa_depot_maintain.py" line_range="13-22" />
<code_context>
+from app.task.MAA.AutoProxy import _build_depot_maintain_task
+
+
+def test_build_depot_maintain_task_filters_invalid_plans() -> None:
+ task = _build_depot_maintain_task(
+ json.dumps(
+ [
+ {"Stage": "1-7", "DropId": "30012", "DropCount": 100},
+ {"Stage": "CE-6", "DropId": "4001", "DropCount": 0},
+ {"Stage": "", "DropId": "2001", "DropCount": 50},
+ "invalid",
+ ]
+ )
+ )
+
+ assert task["TaskType"] == "DepotMaintain"
+ assert task["PlanList"] == [
+ {
+ "Stage": "1-7",
</code_context>
<issue_to_address>
**suggestion (testing):** 在 `_build_depot_maintain_task` 中为更多边界情况和完整的任务结构添加测试覆盖
这个辅助函数的校验比当前测试所覆盖的要严格:`DropCount` 必须是 `int`(而不是 `bool`)、为正数,并且 `Stage` 和 `DropId` 都必须是非空字符串。建议增加一些用例:`DropCount` 为 bool、非 int 或负数,以及缺少必需键或键类型错误的情况,以防后续重构削弱这些校验。
另外,该辅助函数返回的是一个完整的任务字典(包含 `IsEnable`、`UpdateDepot`、`SkipDuringActivity` 等),但当前测试只断言了 `TaskType` 和 `PlanList`。在这里(或单独的测试中)对这些额外的标志位进行断言,可以防止任务结构在不经意间发生变化。
建议实现:
```python
import json
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
from types import SimpleNamespace
from app.core.config import AppConfig
from app.models.config import MaaConfig
from app.task.MAA.AutoProxy import _build_depot_maintain_task
def test_build_depot_maintain_task_filters_invalid_plans() -> None:
task = _build_depot_maintain_task(
json.dumps(
[
# valid plan
{"Stage": "1-7", "DropId": "30012", "DropCount": 100},
# invalid: DropCount zero
{"Stage": "CE-6", "DropId": "4001", "DropCount": 0},
# invalid: empty Stage
{"Stage": "", "DropId": "2001", "DropCount": 50},
# invalid: DropCount is bool (should not be treated as int)
{"Stage": "2-3", "DropId": "30013", "DropCount": True},
# invalid: DropCount is non-int type (str)
{"Stage": "3-4", "DropId": "30014", "DropCount": "10"},
# invalid: DropCount is negative
{"Stage": "4-5", "DropId": "30015", "DropCount": -1},
# invalid: missing Stage
{"DropId": "30016", "DropCount": 20},
# invalid: missing DropId
{"Stage": "5-6", "DropCount": 30},
# invalid: missing DropCount
{"Stage": "6-7", "DropId": "30017"},
# invalid: Stage wrong type
{"Stage": 123, "DropId": "30018", "DropCount": 40},
# invalid: DropId wrong type
{"Stage": "7-8", "DropId": 456, "DropCount": 50},
# invalid: completely wrong type
"invalid",
]
)
)
# task shape assertions
assert isinstance(task, dict)
assert task["TaskType"] == "DepotMaintain"
assert "PlanList" in task
assert isinstance(task["PlanList"], list)
# only the single valid plan should be kept
assert task["PlanList"] == [
{
"Stage": "1-7",
"DropId": "30012",
"DropCount": 100,
"UseMedicine": False,
"MedicineCount": 0,
"UseStone": False,
"StoneCount": 0,
}
]
# guard the rest of the task flags / shape
for key in ("IsEnable", "UpdateDepot", "SkipDuringActivity"):
assert key in task
assert isinstance(task[key], bool)
def test_build_depot_maintain_task_task_shape_with_minimal_valid_input() -> None:
task = _build_depot_maintain_task(
json.dumps(
[
{"Stage": "1-7", "DropId": "30012", "DropCount": 100},
]
)
)
assert isinstance(task, dict)
assert task["TaskType"] == "DepotMaintain"
assert task["PlanList"] == [
{
"Stage": "1-7",
"DropId": "30012",
"DropCount": 100,
"UseMedicine": False,
"MedicineCount": 0,
"UseStone": False,
"StoneCount": 0,
}
]
# assert the helper consistently sets the task-level flags
for key in ("IsEnable", "UpdateDepot", "SkipDuringActivity"):
assert key in task
assert isinstance(task[key], bool)
```
如果 `_build_depot_maintain_task` 除了 `IsEnable`、`UpdateDepot` 和 `SkipDuringActivity` 之外还设置了其他任务级别的键(例如与调度相关的字段),也可以考虑将这些键加入断言。请根据该辅助函数实际返回的结构调整两处测试中的键列表;如果你知道这些标志位的期望值(例如 `IsEnable` 总是 `True`),还可以将断言从类型检查加强为具体值检查。
</issue_to_address>
### Comment 2
<location path="tests/test_maa_depot_maintain.py" line_range="39-48" />
<code_context>
+ ]
+
+
+def test_get_maa_depot_items_uses_numeric_farmable_items() -> None:
+ with TemporaryDirectory() as temp_dir:
+ root = Path(temp_dir)
+ resource = root / "resource"
+ resource.mkdir()
+ (resource / "item_index.json").write_text(
+ json.dumps(
+ {
+ "1stact": {"name": "活动道具"},
+ "4002": {"name": "源石"},
+ "30012": {"name": "固源岩"},
+ "2001": {"name": "基础作战记录"},
+ }
+ ),
+ encoding="utf-8",
+ )
+
+ script_id = uuid.uuid4()
+ script_config = MaaConfig()
+ script_config.Info_Path.setValue(str(root))
+ owner = SimpleNamespace(ScriptConfig={script_id: script_config})
+
+ items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
+
+ assert items == [
+ {"label": "基础作战记录", "value": "2001"},
+ {"label": "固源岩", "value": "30012"},
</code_context>
<issue_to_address>
**suggestion (testing):** 扩展 `get_maa_depot_items` 的测试,以涵盖错误分支、被排除的 ID 以及缺失名称的情况
当前测试覆盖了主要的成功路径(数值型 ID 和排序),但还有几个关键分支尚未测试:
* 非 MAA 脚本:当 `ScriptConfig[script_id]` 不是 `MaaConfig` 时,`get_maa_depot_items` 应抛出 `TypeError`。可以添加一个使用其他配置类型或简单占位对象的用例,并断言该异常。
* 缺失 `item_index.json`:当 `Info_Path` 下不存在 `resource/item_index.json` 时,应该抛出带有预期信息的 `FileNotFoundError`。可以添加一个测试,让 `Info_Path` 指向一个不包含该文件的目录。
* 被排除的 ID:通过包含一个 ID 属于 `MAA_DEPOT_EXCLUDED_ITEM_IDS` 的物品,并断言该物品不会被返回,来验证这一排除集合。
* 缺失 `name`:验证没有 `name` 的物品(例如 `{"30012": {}}`)会以 `label=item_id` 的形式返回。
覆盖这些场景可以显著增强仓库物品 API 在正常和异常条件下的行为健壮性。
建议实现:
```python
def test_get_maa_depot_items_uses_numeric_farmable_items() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
(resource / "item_index.json").write_text(
json.dumps(
{
"1stact": {"name": "活动道具"},
"4002": {"name": "源石"},
"30012": {"name": "固源岩"},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
assert items == [
{"label": "基础作战记录", "value": "2001"},
{"label": "固源岩", "value": "30012"},
]
def test_get_maa_depot_items_non_maa_script_raises_type_error() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
script_id = uuid.uuid4()
# Use a dummy/non-MaaConfig config object to trigger the type check
non_maa_config = SimpleNamespace(Info_Path=str(root))
owner = SimpleNamespace(ScriptConfig={script_id: non_maa_config})
with pytest.raises(TypeError):
asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
def test_get_maa_depot_items_missing_item_index_raises_file_not_found() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
# Point Info_Path to a directory without resource/item_index.json
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
with pytest.raises(FileNotFoundError):
asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
def test_get_maa_depot_items_excludes_configured_item_ids() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
# Include a known excluded numeric ID and a non-excluded one
(resource / "item_index.json").write_text(
json.dumps(
{
# Assumes 4002 is in MAA_DEPOT_EXCLUDED_ITEM_IDS
"4002": {"name": "源石"},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
# Only the non-excluded item should be returned
assert items == [{"label": "基础作战记录", "value": "2001"}]
# And the excluded ID should not appear in the returned values
assert "4002" not in {item["value"] for item in items}
def test_get_maa_depot_items_uses_item_id_as_label_when_name_missing() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
(resource / "item_index.json").write_text(
json.dumps(
{
# No name provided for 30012, should fall back to ID as label
"30012": {},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
# Both items should be present; 30012 should use its ID as label
assert items == [
{"label": "基础作战记录", "value": "2001"},
{"label": "30012", "value": "30012"},
]
```
这些测试基于以下前提:
1. 在 `tests/test_maa_depot_maintain.py` 文件顶部已经导入了 `pytest`。
2. 该文件已经像现有测试一样导入了 `TemporaryDirectory`、`Path`、`json`、`uuid`、`asyncio`、`SimpleNamespace`、`MaaConfig` 和 `AppConfig`。
3. `test_get_maa_depot_items_excludes_configured_item_ids` 中使用的被排除 ID(`"4002"`)确实是 `MAA_DEPOT_EXCLUDED_ITEM_IDS` 中的值之一。如果实际被排除的是其他 ID,请相应调整该测试中的 JSON 和断言。
4. 如果 `get_maa_depot_items` 抛出的 `FileNotFoundError` 包含特定的错误信息或路径,你也可以使用 `pytest.raises(..., match="expected message")` 来收紧断言。
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据反馈改进后续评审。
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- The DepotMaintainPlans JSON handling is duplicated and untyped between backend and DepotMaintainConfigSection; consider introducing a shared typed model or utility for parsing/serializing plans to reduce runtime JSON errors and keep the schema consistent.
- In get_maa_depot_items, all exceptions are converted into a 500 ComboBoxOut without any logging; adding structured logging for the failure path would make it easier to diagnose missing/invalid item_index.json issues in production.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The DepotMaintainPlans JSON handling is duplicated and untyped between backend and DepotMaintainConfigSection; consider introducing a shared typed model or utility for parsing/serializing plans to reduce runtime JSON errors and keep the schema consistent.
- In get_maa_depot_items, all exceptions are converted into a 500 ComboBoxOut without any logging; adding structured logging for the failure path would make it easier to diagnose missing/invalid item_index.json issues in production.
## Individual Comments
### Comment 1
<location path="tests/test_maa_depot_maintain.py" line_range="13-22" />
<code_context>
+from app.task.MAA.AutoProxy import _build_depot_maintain_task
+
+
+def test_build_depot_maintain_task_filters_invalid_plans() -> None:
+ task = _build_depot_maintain_task(
+ json.dumps(
+ [
+ {"Stage": "1-7", "DropId": "30012", "DropCount": 100},
+ {"Stage": "CE-6", "DropId": "4001", "DropCount": 0},
+ {"Stage": "", "DropId": "2001", "DropCount": 50},
+ "invalid",
+ ]
+ )
+ )
+
+ assert task["TaskType"] == "DepotMaintain"
+ assert task["PlanList"] == [
+ {
+ "Stage": "1-7",
</code_context>
<issue_to_address>
**suggestion (testing):** Add coverage for more edge cases and the full task shape in `_build_depot_maintain_task`
The helper enforces stricter validation than this test currently covers: `DropCount` must be an `int` (not `bool`), positive, and both `Stage` and `DropId` must be non-empty strings. It would be helpful to add cases where `DropCount` is a bool, non-int, or negative, and where required keys are missing or have the wrong type, so refactors don’t weaken validation.
Also, the helper returns a full task dict (`IsEnable`, `UpdateDepot`, `SkipDuringActivity`, etc.), but the test only asserts `TaskType` and `PlanList`. Asserting the other flags (either here or in a dedicated test) would guard against unintended changes to the task shape.
Suggested implementation:
```python
import json
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
from types import SimpleNamespace
from app.core.config import AppConfig
from app.models.config import MaaConfig
from app.task.MAA.AutoProxy import _build_depot_maintain_task
def test_build_depot_maintain_task_filters_invalid_plans() -> None:
task = _build_depot_maintain_task(
json.dumps(
[
# valid plan
{"Stage": "1-7", "DropId": "30012", "DropCount": 100},
# invalid: DropCount zero
{"Stage": "CE-6", "DropId": "4001", "DropCount": 0},
# invalid: empty Stage
{"Stage": "", "DropId": "2001", "DropCount": 50},
# invalid: DropCount is bool (should not be treated as int)
{"Stage": "2-3", "DropId": "30013", "DropCount": True},
# invalid: DropCount is non-int type (str)
{"Stage": "3-4", "DropId": "30014", "DropCount": "10"},
# invalid: DropCount is negative
{"Stage": "4-5", "DropId": "30015", "DropCount": -1},
# invalid: missing Stage
{"DropId": "30016", "DropCount": 20},
# invalid: missing DropId
{"Stage": "5-6", "DropCount": 30},
# invalid: missing DropCount
{"Stage": "6-7", "DropId": "30017"},
# invalid: Stage wrong type
{"Stage": 123, "DropId": "30018", "DropCount": 40},
# invalid: DropId wrong type
{"Stage": "7-8", "DropId": 456, "DropCount": 50},
# invalid: completely wrong type
"invalid",
]
)
)
# task shape assertions
assert isinstance(task, dict)
assert task["TaskType"] == "DepotMaintain"
assert "PlanList" in task
assert isinstance(task["PlanList"], list)
# only the single valid plan should be kept
assert task["PlanList"] == [
{
"Stage": "1-7",
"DropId": "30012",
"DropCount": 100,
"UseMedicine": False,
"MedicineCount": 0,
"UseStone": False,
"StoneCount": 0,
}
]
# guard the rest of the task flags / shape
for key in ("IsEnable", "UpdateDepot", "SkipDuringActivity"):
assert key in task
assert isinstance(task[key], bool)
def test_build_depot_maintain_task_task_shape_with_minimal_valid_input() -> None:
task = _build_depot_maintain_task(
json.dumps(
[
{"Stage": "1-7", "DropId": "30012", "DropCount": 100},
]
)
)
assert isinstance(task, dict)
assert task["TaskType"] == "DepotMaintain"
assert task["PlanList"] == [
{
"Stage": "1-7",
"DropId": "30012",
"DropCount": 100,
"UseMedicine": False,
"MedicineCount": 0,
"UseStone": False,
"StoneCount": 0,
}
]
# assert the helper consistently sets the task-level flags
for key in ("IsEnable", "UpdateDepot", "SkipDuringActivity"):
assert key in task
assert isinstance(task[key], bool)
```
If `_build_depot_maintain_task` sets additional task-level keys beyond `IsEnable`, `UpdateDepot`, and `SkipDuringActivity` (for example, scheduling-related fields), you may want to expand the assertions to cover them as well. Adjust the list of keys in the two tests to match the actual shape returned by the helper, and, if you know the expected values for the flags (e.g. `IsEnable` is always `True`), you can strengthen the assertions from type checks to exact value checks.
</issue_to_address>
### Comment 2
<location path="tests/test_maa_depot_maintain.py" line_range="39-48" />
<code_context>
+ ]
+
+
+def test_get_maa_depot_items_uses_numeric_farmable_items() -> None:
+ with TemporaryDirectory() as temp_dir:
+ root = Path(temp_dir)
+ resource = root / "resource"
+ resource.mkdir()
+ (resource / "item_index.json").write_text(
+ json.dumps(
+ {
+ "1stact": {"name": "活动道具"},
+ "4002": {"name": "源石"},
+ "30012": {"name": "固源岩"},
+ "2001": {"name": "基础作战记录"},
+ }
+ ),
+ encoding="utf-8",
+ )
+
+ script_id = uuid.uuid4()
+ script_config = MaaConfig()
+ script_config.Info_Path.setValue(str(root))
+ owner = SimpleNamespace(ScriptConfig={script_id: script_config})
+
+ items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
+
+ assert items == [
+ {"label": "基础作战记录", "value": "2001"},
+ {"label": "固源岩", "value": "30012"},
</code_context>
<issue_to_address>
**suggestion (testing):** Extend `get_maa_depot_items` tests to cover error paths, excluded IDs, and missing names
The current test covers the main success case (numeric IDs and sorting), but several key branches remain untested:
* Non-MAA script: when `ScriptConfig[script_id]` is not a `MaaConfig`, `get_maa_depot_items` should raise `TypeError`. Add a case using a different config type/dummy object to assert this.
* Missing `item_index.json`: when `resource/item_index.json` is absent under `Info_Path`, it should raise `FileNotFoundError` with the expected message. Add a test that points `Info_Path` to a directory without that file.
* Excluded IDs: verify `MAA_DEPOT_EXCLUDED_ITEM_IDS` by including an item whose ID is in that set and asserting it is not returned.
* Missing `name`: verify that items without a `name` (e.g. `{"30012": {}}`) are returned with `label=item_id`.
Covering these cases will make the depot item API’s behavior in both normal and error conditions much more robust.
Suggested implementation:
```python
def test_get_maa_depot_items_uses_numeric_farmable_items() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
(resource / "item_index.json").write_text(
json.dumps(
{
"1stact": {"name": "活动道具"},
"4002": {"name": "源石"},
"30012": {"name": "固源岩"},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
assert items == [
{"label": "基础作战记录", "value": "2001"},
{"label": "固源岩", "value": "30012"},
]
def test_get_maa_depot_items_non_maa_script_raises_type_error() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
script_id = uuid.uuid4()
# Use a dummy/non-MaaConfig config object to trigger the type check
non_maa_config = SimpleNamespace(Info_Path=str(root))
owner = SimpleNamespace(ScriptConfig={script_id: non_maa_config})
with pytest.raises(TypeError):
asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
def test_get_maa_depot_items_missing_item_index_raises_file_not_found() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
# Point Info_Path to a directory without resource/item_index.json
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
with pytest.raises(FileNotFoundError):
asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
def test_get_maa_depot_items_excludes_configured_item_ids() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
# Include a known excluded numeric ID and a non-excluded one
(resource / "item_index.json").write_text(
json.dumps(
{
# Assumes 4002 is in MAA_DEPOT_EXCLUDED_ITEM_IDS
"4002": {"name": "源石"},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
# Only the non-excluded item should be returned
assert items == [{"label": "基础作战记录", "value": "2001"}]
# And the excluded ID should not appear in the returned values
assert "4002" not in {item["value"] for item in items}
def test_get_maa_depot_items_uses_item_id_as_label_when_name_missing() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
(resource / "item_index.json").write_text(
json.dumps(
{
# No name provided for 30012, should fall back to ID as label
"30012": {},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
# Both items should be present; 30012 should use its ID as label
assert items == [
{"label": "基础作战记录", "value": "2001"},
{"label": "30012", "value": "30012"},
]
```
These tests assume:
1. `pytest` is imported at the top of `tests/test_maa_depot_maintain.py`.
2. `TemporaryDirectory`, `Path`, `json`, `uuid`, `asyncio`, `SimpleNamespace`, `MaaConfig`, and `AppConfig` are already imported as in the existing tests.
3. The excluded ID used in `test_get_maa_depot_items_excludes_configured_item_ids` (`"4002"`) matches one of the values in `MAA_DEPOT_EXCLUDED_ITEM_IDS`. If a different ID is actually excluded, adjust the JSON and assertions in that test accordingly.
4. If `get_maa_depot_items` raises `FileNotFoundError` with a specific message or path, you may want to tighten the assertion using `pytest.raises(..., match="expected message")`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| def test_build_depot_maintain_task_filters_invalid_plans() -> None: | ||
| task = _build_depot_maintain_task( | ||
| json.dumps( | ||
| [ | ||
| {"Stage": "1-7", "DropId": "30012", "DropCount": 100}, | ||
| {"Stage": "CE-6", "DropId": "4001", "DropCount": 0}, | ||
| {"Stage": "", "DropId": "2001", "DropCount": 50}, | ||
| "invalid", | ||
| ] | ||
| ) |
There was a problem hiding this comment.
suggestion (testing): 在 _build_depot_maintain_task 中为更多边界情况和完整的任务结构添加测试覆盖
这个辅助函数的校验比当前测试所覆盖的要严格:DropCount 必须是 int(而不是 bool)、为正数,并且 Stage 和 DropId 都必须是非空字符串。建议增加一些用例:DropCount 为 bool、非 int 或负数,以及缺少必需键或键类型错误的情况,以防后续重构削弱这些校验。
另外,该辅助函数返回的是一个完整的任务字典(包含 IsEnable、UpdateDepot、SkipDuringActivity 等),但当前测试只断言了 TaskType 和 PlanList。在这里(或单独的测试中)对这些额外的标志位进行断言,可以防止任务结构在不经意间发生变化。
建议实现:
import json
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
from types import SimpleNamespace
from app.core.config import AppConfig
from app.models.config import MaaConfig
from app.task.MAA.AutoProxy import _build_depot_maintain_task
def test_build_depot_maintain_task_filters_invalid_plans() -> None:
task = _build_depot_maintain_task(
json.dumps(
[
# valid plan
{"Stage": "1-7", "DropId": "30012", "DropCount": 100},
# invalid: DropCount zero
{"Stage": "CE-6", "DropId": "4001", "DropCount": 0},
# invalid: empty Stage
{"Stage": "", "DropId": "2001", "DropCount": 50},
# invalid: DropCount is bool (should not be treated as int)
{"Stage": "2-3", "DropId": "30013", "DropCount": True},
# invalid: DropCount is non-int type (str)
{"Stage": "3-4", "DropId": "30014", "DropCount": "10"},
# invalid: DropCount is negative
{"Stage": "4-5", "DropId": "30015", "DropCount": -1},
# invalid: missing Stage
{"DropId": "30016", "DropCount": 20},
# invalid: missing DropId
{"Stage": "5-6", "DropCount": 30},
# invalid: missing DropCount
{"Stage": "6-7", "DropId": "30017"},
# invalid: Stage wrong type
{"Stage": 123, "DropId": "30018", "DropCount": 40},
# invalid: DropId wrong type
{"Stage": "7-8", "DropId": 456, "DropCount": 50},
# invalid: completely wrong type
"invalid",
]
)
)
# task shape assertions
assert isinstance(task, dict)
assert task["TaskType"] == "DepotMaintain"
assert "PlanList" in task
assert isinstance(task["PlanList"], list)
# only the single valid plan should be kept
assert task["PlanList"] == [
{
"Stage": "1-7",
"DropId": "30012",
"DropCount": 100,
"UseMedicine": False,
"MedicineCount": 0,
"UseStone": False,
"StoneCount": 0,
}
]
# guard the rest of the task flags / shape
for key in ("IsEnable", "UpdateDepot", "SkipDuringActivity"):
assert key in task
assert isinstance(task[key], bool)
def test_build_depot_maintain_task_task_shape_with_minimal_valid_input() -> None:
task = _build_depot_maintain_task(
json.dumps(
[
{"Stage": "1-7", "DropId": "30012", "DropCount": 100},
]
)
)
assert isinstance(task, dict)
assert task["TaskType"] == "DepotMaintain"
assert task["PlanList"] == [
{
"Stage": "1-7",
"DropId": "30012",
"DropCount": 100,
"UseMedicine": False,
"MedicineCount": 0,
"UseStone": False,
"StoneCount": 0,
}
]
# assert the helper consistently sets the task-level flags
for key in ("IsEnable", "UpdateDepot", "SkipDuringActivity"):
assert key in task
assert isinstance(task[key], bool)如果 _build_depot_maintain_task 除了 IsEnable、UpdateDepot 和 SkipDuringActivity 之外还设置了其他任务级别的键(例如与调度相关的字段),也可以考虑将这些键加入断言。请根据该辅助函数实际返回的结构调整两处测试中的键列表;如果你知道这些标志位的期望值(例如 IsEnable 总是 True),还可以将断言从类型检查加强为具体值检查。
Original comment in English
suggestion (testing): Add coverage for more edge cases and the full task shape in _build_depot_maintain_task
The helper enforces stricter validation than this test currently covers: DropCount must be an int (not bool), positive, and both Stage and DropId must be non-empty strings. It would be helpful to add cases where DropCount is a bool, non-int, or negative, and where required keys are missing or have the wrong type, so refactors don’t weaken validation.
Also, the helper returns a full task dict (IsEnable, UpdateDepot, SkipDuringActivity, etc.), but the test only asserts TaskType and PlanList. Asserting the other flags (either here or in a dedicated test) would guard against unintended changes to the task shape.
Suggested implementation:
import json
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
from types import SimpleNamespace
from app.core.config import AppConfig
from app.models.config import MaaConfig
from app.task.MAA.AutoProxy import _build_depot_maintain_task
def test_build_depot_maintain_task_filters_invalid_plans() -> None:
task = _build_depot_maintain_task(
json.dumps(
[
# valid plan
{"Stage": "1-7", "DropId": "30012", "DropCount": 100},
# invalid: DropCount zero
{"Stage": "CE-6", "DropId": "4001", "DropCount": 0},
# invalid: empty Stage
{"Stage": "", "DropId": "2001", "DropCount": 50},
# invalid: DropCount is bool (should not be treated as int)
{"Stage": "2-3", "DropId": "30013", "DropCount": True},
# invalid: DropCount is non-int type (str)
{"Stage": "3-4", "DropId": "30014", "DropCount": "10"},
# invalid: DropCount is negative
{"Stage": "4-5", "DropId": "30015", "DropCount": -1},
# invalid: missing Stage
{"DropId": "30016", "DropCount": 20},
# invalid: missing DropId
{"Stage": "5-6", "DropCount": 30},
# invalid: missing DropCount
{"Stage": "6-7", "DropId": "30017"},
# invalid: Stage wrong type
{"Stage": 123, "DropId": "30018", "DropCount": 40},
# invalid: DropId wrong type
{"Stage": "7-8", "DropId": 456, "DropCount": 50},
# invalid: completely wrong type
"invalid",
]
)
)
# task shape assertions
assert isinstance(task, dict)
assert task["TaskType"] == "DepotMaintain"
assert "PlanList" in task
assert isinstance(task["PlanList"], list)
# only the single valid plan should be kept
assert task["PlanList"] == [
{
"Stage": "1-7",
"DropId": "30012",
"DropCount": 100,
"UseMedicine": False,
"MedicineCount": 0,
"UseStone": False,
"StoneCount": 0,
}
]
# guard the rest of the task flags / shape
for key in ("IsEnable", "UpdateDepot", "SkipDuringActivity"):
assert key in task
assert isinstance(task[key], bool)
def test_build_depot_maintain_task_task_shape_with_minimal_valid_input() -> None:
task = _build_depot_maintain_task(
json.dumps(
[
{"Stage": "1-7", "DropId": "30012", "DropCount": 100},
]
)
)
assert isinstance(task, dict)
assert task["TaskType"] == "DepotMaintain"
assert task["PlanList"] == [
{
"Stage": "1-7",
"DropId": "30012",
"DropCount": 100,
"UseMedicine": False,
"MedicineCount": 0,
"UseStone": False,
"StoneCount": 0,
}
]
# assert the helper consistently sets the task-level flags
for key in ("IsEnable", "UpdateDepot", "SkipDuringActivity"):
assert key in task
assert isinstance(task[key], bool)If _build_depot_maintain_task sets additional task-level keys beyond IsEnable, UpdateDepot, and SkipDuringActivity (for example, scheduling-related fields), you may want to expand the assertions to cover them as well. Adjust the list of keys in the two tests to match the actual shape returned by the helper, and, if you know the expected values for the flags (e.g. IsEnable is always True), you can strengthen the assertions from type checks to exact value checks.
| def test_get_maa_depot_items_uses_numeric_farmable_items() -> None: | ||
| with TemporaryDirectory() as temp_dir: | ||
| root = Path(temp_dir) | ||
| resource = root / "resource" | ||
| resource.mkdir() | ||
| (resource / "item_index.json").write_text( | ||
| json.dumps( | ||
| { | ||
| "1stact": {"name": "活动道具"}, | ||
| "4002": {"name": "源石"}, |
There was a problem hiding this comment.
suggestion (testing): 扩展 get_maa_depot_items 的测试,以涵盖错误分支、被排除的 ID 以及缺失名称的情况
当前测试覆盖了主要的成功路径(数值型 ID 和排序),但还有几个关键分支尚未测试:
- 非 MAA 脚本:当
ScriptConfig[script_id]不是MaaConfig时,get_maa_depot_items应抛出TypeError。可以添加一个使用其他配置类型或简单占位对象的用例,并断言该异常。 - 缺失
item_index.json:当Info_Path下不存在resource/item_index.json时,应该抛出带有预期信息的FileNotFoundError。可以添加一个测试,让Info_Path指向一个不包含该文件的目录。 - 被排除的 ID:通过包含一个 ID 属于
MAA_DEPOT_EXCLUDED_ITEM_IDS的物品,并断言该物品不会被返回,来验证这一排除集合。 - 缺失
name:验证没有name的物品(例如{"30012": {}})会以label=item_id的形式返回。
覆盖这些场景可以显著增强仓库物品 API 在正常和异常条件下的行为健壮性。
建议实现:
def test_get_maa_depot_items_uses_numeric_farmable_items() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
(resource / "item_index.json").write_text(
json.dumps(
{
"1stact": {"name": "活动道具"},
"4002": {"name": "源石"},
"30012": {"name": "固源岩"},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
assert items == [
{"label": "基础作战记录", "value": "2001"},
{"label": "固源岩", "value": "30012"},
]
def test_get_maa_depot_items_non_maa_script_raises_type_error() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
script_id = uuid.uuid4()
# Use a dummy/non-MaaConfig config object to trigger the type check
non_maa_config = SimpleNamespace(Info_Path=str(root))
owner = SimpleNamespace(ScriptConfig={script_id: non_maa_config})
with pytest.raises(TypeError):
asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
def test_get_maa_depot_items_missing_item_index_raises_file_not_found() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
# Point Info_Path to a directory without resource/item_index.json
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
with pytest.raises(FileNotFoundError):
asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
def test_get_maa_depot_items_excludes_configured_item_ids() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
# Include a known excluded numeric ID and a non-excluded one
(resource / "item_index.json").write_text(
json.dumps(
{
# Assumes 4002 is in MAA_DEPOT_EXCLUDED_ITEM_IDS
"4002": {"name": "源石"},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
# Only the non-excluded item should be returned
assert items == [{"label": "基础作战记录", "value": "2001"}]
# And the excluded ID should not appear in the returned values
assert "4002" not in {item["value"] for item in items}
def test_get_maa_depot_items_uses_item_id_as_label_when_name_missing() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
(resource / "item_index.json").write_text(
json.dumps(
{
# No name provided for 30012, should fall back to ID as label
"30012": {},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
# Both items should be present; 30012 should use its ID as label
assert items == [
{"label": "基础作战记录", "value": "2001"},
{"label": "30012", "value": "30012"},
]这些测试基于以下前提:
- 在
tests/test_maa_depot_maintain.py文件顶部已经导入了pytest。 - 该文件已经像现有测试一样导入了
TemporaryDirectory、Path、json、uuid、asyncio、SimpleNamespace、MaaConfig和AppConfig。 test_get_maa_depot_items_excludes_configured_item_ids中使用的被排除 ID("4002")确实是MAA_DEPOT_EXCLUDED_ITEM_IDS中的值之一。如果实际被排除的是其他 ID,请相应调整该测试中的 JSON 和断言。- 如果
get_maa_depot_items抛出的FileNotFoundError包含特定的错误信息或路径,你也可以使用pytest.raises(..., match="expected message")来收紧断言。
Original comment in English
suggestion (testing): Extend get_maa_depot_items tests to cover error paths, excluded IDs, and missing names
The current test covers the main success case (numeric IDs and sorting), but several key branches remain untested:
- Non-MAA script: when
ScriptConfig[script_id]is not aMaaConfig,get_maa_depot_itemsshould raiseTypeError. Add a case using a different config type/dummy object to assert this. - Missing
item_index.json: whenresource/item_index.jsonis absent underInfo_Path, it should raiseFileNotFoundErrorwith the expected message. Add a test that pointsInfo_Pathto a directory without that file. - Excluded IDs: verify
MAA_DEPOT_EXCLUDED_ITEM_IDSby including an item whose ID is in that set and asserting it is not returned. - Missing
name: verify that items without aname(e.g.{"30012": {}}) are returned withlabel=item_id.
Covering these cases will make the depot item API’s behavior in both normal and error conditions much more robust.
Suggested implementation:
def test_get_maa_depot_items_uses_numeric_farmable_items() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
(resource / "item_index.json").write_text(
json.dumps(
{
"1stact": {"name": "活动道具"},
"4002": {"name": "源石"},
"30012": {"name": "固源岩"},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
assert items == [
{"label": "基础作战记录", "value": "2001"},
{"label": "固源岩", "value": "30012"},
]
def test_get_maa_depot_items_non_maa_script_raises_type_error() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
script_id = uuid.uuid4()
# Use a dummy/non-MaaConfig config object to trigger the type check
non_maa_config = SimpleNamespace(Info_Path=str(root))
owner = SimpleNamespace(ScriptConfig={script_id: non_maa_config})
with pytest.raises(TypeError):
asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
def test_get_maa_depot_items_missing_item_index_raises_file_not_found() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
# Point Info_Path to a directory without resource/item_index.json
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
with pytest.raises(FileNotFoundError):
asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
def test_get_maa_depot_items_excludes_configured_item_ids() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
# Include a known excluded numeric ID and a non-excluded one
(resource / "item_index.json").write_text(
json.dumps(
{
# Assumes 4002 is in MAA_DEPOT_EXCLUDED_ITEM_IDS
"4002": {"name": "源石"},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
# Only the non-excluded item should be returned
assert items == [{"label": "基础作战记录", "value": "2001"}]
# And the excluded ID should not appear in the returned values
assert "4002" not in {item["value"] for item in items}
def test_get_maa_depot_items_uses_item_id_as_label_when_name_missing() -> None:
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
resource = root / "resource"
resource.mkdir()
(resource / "item_index.json").write_text(
json.dumps(
{
# No name provided for 30012, should fall back to ID as label
"30012": {},
"2001": {"name": "基础作战记录"},
}
),
encoding="utf-8",
)
script_id = uuid.uuid4()
script_config = MaaConfig()
script_config.Info_Path.setValue(str(root))
owner = SimpleNamespace(ScriptConfig={script_id: script_config})
items = asyncio.run(AppConfig.get_maa_depot_items(owner, str(script_id)))
# Both items should be present; 30012 should use its ID as label
assert items == [
{"label": "基础作战记录", "value": "2001"},
{"label": "30012", "value": "30012"},
]These tests assume:
pytestis imported at the top oftests/test_maa_depot_maintain.py.TemporaryDirectory,Path,json,uuid,asyncio,SimpleNamespace,MaaConfig, andAppConfigare already imported as in the existing tests.- The excluded ID used in
test_get_maa_depot_items_excludes_configured_item_ids("4002") matches one of the values inMAA_DEPOT_EXCLUDED_ITEM_IDS. If a different ID is actually excluded, adjust the JSON and assertions in that test accordingly. - If
get_maa_depot_itemsraisesFileNotFoundErrorwith a specific message or path, you may want to tighten the assertion usingpytest.raises(..., match="expected message").
| IfDepotMaintain: Optional[bool] = Field(default=None, description="库存保持") | ||
| DepotMaintainPlans: Optional[str] = Field( | ||
| default=None, description="库存保持计划 JSON" | ||
| ) |
| MAA_DEPOT_EXCLUDED_ITEM_IDS = { | ||
| "3213", | ||
| "3223", | ||
| "3233", |
摘要
DepotMaintain任务,并在未启用时保持旧版 MAA 配置兼容item_index.json读取物品选项,在用户表单中支持设置关卡、物品与目标库存验证
python -m pytest tests/test_maa_depot_maintain.py -q:2 passedgit diff --check、新组件 ESLint/Prettier 通过yarn lint、yarn typecheck与pytest仍受dev既有无关错误影响Summary by Sourcery
新增对配置和执行 MAA DepotMaintain(仓库维护)任务的支持,同时保持与旧版本 MAA 的兼容性。
新特性:
MAA item_index.json并返回用于 DepotMaintain 配置的过滤后的物品选项。增强:
测试:
MAA item_index.json生成物品选项的逻辑。Original summary in English
Summary by Sourcery
Add support for configuring and executing MAA DepotMaintain (inventory maintain) tasks while preserving compatibility with older MAA versions.
New Features:
Enhancements:
Tests: