mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-27 04:29:58 +08:00
add # 新增种子商店界面
fixed # 修复数据库目录错误的BUG
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# 文字指令种子商店
|
||||
|
||||
采用用户选定的方案二:木质价目板、单列六种。发送 `种子商店` 或
|
||||
`种子商店 2` 返回 1024×1536 PNG;标题、余额、商品信息、购买示例和
|
||||
页码都由程序绘制。原始作物 icon 复用包内资源;缺图显示“暂无图”。
|
||||
|
||||
显示农场等级、农场币和点券,商品显示稳定本地编号、名称、实际单价、
|
||||
币种、购买等级、首熟时长、可收获季数和每季基础产量。
|
||||
首熟时长和产量是目录基础值,虫害等实际种植结算仍由核心处理。
|
||||
等级不足时突出“需 Lv.…”。未开通用户可浏览,购买仍需先开通农场。
|
||||
|
||||
只展示 buyable 作物,按本地编号排序,每页六种。最后一页不补假商品,
|
||||
越界页码返回中文提示,页数按实际目录计算。字体不可用时返回同样分页的
|
||||
文字列表。图片通过既有有限任务队列绘制,不在事件循环里执行 Pillow。
|
||||
相同待处理请求合并,关闭时等待任务排空。
|
||||
|
||||
购买入口保持核心事务和消息去重不变:
|
||||
|
||||
- `购买种子 #1 5`
|
||||
- `购买种子 胡萝卜 5`
|
||||
- 名称有空格时使用引号;重名作物须用编号。
|
||||
|
||||
木牌空白底图为内置图像生成工具生成,非 QQ 农场原始资源。源文件
|
||||
`src/farm_render/source_assets/webgl/shop-board.png`,由素材导出脚本加入
|
||||
assets.zip,并在 metadata.json 保存 SHA-256。底图不含商品、价格和文字。
|
||||
生成提示要点:1024×1536,QQ 农场风格暖色手绘木牌、橙色标题、米色纸面、
|
||||
单列六个空白商品条、底部文字指令区;禁止生成文字、数字和商品图。
|
||||
|
||||
执行 `scripts/preview_shop.py --output <目录>` 可复现首页、第二页、末页与
|
||||
未开通预览。预览余额为演示值,商品使用实际目录。此改动不部署、不重启。
|
||||
@@ -16,6 +16,7 @@ from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
from .runtime import COMMANDS, Runtime
|
||||
from .data_paths import data_directories
|
||||
from .config_migration import migrate_config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
driver = get_driver()
|
||||
@@ -30,7 +31,14 @@ runtime = Runtime(
|
||||
font_dir=FONT_PATH,
|
||||
legacy_data_dir=legacy_data_dir,
|
||||
)
|
||||
driver.on_startup(runtime.start)
|
||||
async def start_runtime():
|
||||
from zhenxun.configs.config import Config
|
||||
|
||||
migrate_config(Config, __package__.split(".")[-1])
|
||||
await runtime.start()
|
||||
|
||||
|
||||
driver.on_startup(start_runtime)
|
||||
driver.on_shutdown(runtime.close)
|
||||
_avatars = {}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""仅清理农场已废弃配置,保存备份并同步网页配置数据。"""
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
OBSOLETE_KEYS = (
|
||||
"绘制农场清晰度", "动态背景", "签到图片样式", "兑换倍数",
|
||||
"点券兑换倍数", "手续费", "服务地址",
|
||||
)
|
||||
|
||||
|
||||
def migrate_config(manager, module):
|
||||
group = manager.get(module)
|
||||
simple = manager._simple_data.get(module, {})
|
||||
removed = [key for key in OBSOLETE_KEYS
|
||||
if key in group.configs or key in simple]
|
||||
if not removed:
|
||||
return
|
||||
for source in (manager.file, manager._simple_file):
|
||||
source = Path(source)
|
||||
backup = source.with_name(source.name + ".farm-v1.bak")
|
||||
if source.exists() and not backup.exists():
|
||||
shutil.copy2(source, backup)
|
||||
for key in removed:
|
||||
group.configs.pop(key, None)
|
||||
simple.pop(key, None)
|
||||
manager.save(save_simple_data=True)
|
||||
logging.getLogger(__name__).info(
|
||||
"farm config migration module=%s removed_keys=%s", module, removed,
|
||||
)
|
||||
@@ -12,6 +12,7 @@ from ._vendor.farm_core.storage import SCHEMA_VERSION
|
||||
from ._vendor.farm_core.presentation import receipt_text, snapshot_text
|
||||
from ._vendor.farm_render import FarmRenderer, RenderOptions, RenderService
|
||||
from ._vendor.farm_render.seasons import season_for
|
||||
from ._vendor.farm_render.shop import shop_page
|
||||
from .data_paths import prepare_directory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -167,17 +168,29 @@ class Runtime:
|
||||
return HELP
|
||||
if action == "shop":
|
||||
page = int(args[0]) if args else 1
|
||||
if not 1 <= page <= 100 or len(args) > 1:
|
||||
if len(args) > 1:
|
||||
raise ValueError()
|
||||
plants = sorted(
|
||||
(p for p in self.service.catalogue if p.buyable),
|
||||
key=lambda p: p.plant_id,
|
||||
)
|
||||
chunk = plants[(page-1)*12:page*12]
|
||||
return "种子商店 " + str(page) + "页\n" + "\n".join(
|
||||
f"{p.name}(#{p.plant_id}):"
|
||||
f"{p.cost}{'点券' if p.is_vip else '农场币'}"
|
||||
f" / 等级{p.level}" for p in chunk
|
||||
try:
|
||||
chunk, pages = shop_page(self.service.catalogue, page)
|
||||
except ValueError:
|
||||
return '页码无效,请发送:种子商店 1'
|
||||
if self.rendering:
|
||||
account = None
|
||||
try:
|
||||
account = (await self.service.snapshot(who)).account
|
||||
except FarmError as error:
|
||||
if error.code != 'not_registered':
|
||||
raise
|
||||
return await self.rendering.seed_shop(
|
||||
chunk, account, page, pages,
|
||||
)
|
||||
return f'种子商店 第{page}/{pages}页\n' + '\n'.join(
|
||||
f'{p.name}(#{p.plant_id}):'
|
||||
f'{p.cost}{"点券" if p.is_vip else "农场币"}'
|
||||
f' / 等级{p.level}' for p in chunk
|
||||
) + (
|
||||
'\n购买种子 #编号 数量 或 购买种子 种子名 数量'
|
||||
+ (f'\n下一页:种子商店 {page+1}' if page < pages else '')
|
||||
)
|
||||
if action == "inventory":
|
||||
if args:
|
||||
|
||||
@@ -45,7 +45,7 @@ def build(root, output):
|
||||
integration = root / "integrations/zhenxun"
|
||||
for name in (
|
||||
"__init__.py", "runtime.py", "commands.py", "data_paths.py",
|
||||
"requirements.txt",
|
||||
"requirements.txt", "config_migration.py",
|
||||
):
|
||||
files[name] = (integration / name).read_bytes()
|
||||
files["_vendor/__init__.py"] = b'"""Private shared farm modules."""\n'
|
||||
|
||||
@@ -91,6 +91,7 @@ def export(root):
|
||||
metadata["panel"] = manifest["panel"]
|
||||
entries = {path.relative_to(source).as_posix(): path for path in paths}
|
||||
entries.update({name: webgl_source / name for name in manifest["images"]})
|
||||
entries['webgl/shop-board.png'] = webgl_source / 'webgl/shop-board.png'
|
||||
with ZipFile(output, "w", compression=ZIP_STORED) as bundle:
|
||||
for name, path in sorted(entries.items()):
|
||||
content = path.read_bytes()
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""用实际目录和离线示例账户生成种子商店预览。"""
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / 'src'))
|
||||
|
||||
from ci_font import choose_font
|
||||
from farm_core import Catalogue
|
||||
from farm_core.domain import Account
|
||||
from farm_render import FarmRenderer
|
||||
from farm_render.shop import render_shop, shop_page
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
args.output.mkdir(parents=True, exist_ok=True)
|
||||
renderer = FarmRenderer(choose_font())
|
||||
catalogue = Catalogue.bundled()
|
||||
account = Account('演示农场', 12580, 36, 3420)
|
||||
_, pages = shop_page(catalogue, 1)
|
||||
for page in dict.fromkeys((1, min(2, pages), pages)):
|
||||
plants, _ = shop_page(catalogue, page)
|
||||
(args.output / f'shop-page-{page}.png').write_bytes(render_shop(
|
||||
renderer, plants, account, page, pages,
|
||||
))
|
||||
plants, _ = shop_page(catalogue, 1)
|
||||
(args.output / 'shop-unregistered.png').write_bytes(render_shop(
|
||||
renderer, plants, None, 1, pages,
|
||||
))
|
||||
print(f'Shop previews: {args.output}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
@@ -6244,6 +6244,13 @@
|
||||
30
|
||||
]
|
||||
},
|
||||
"webgl/shop-board.png": {
|
||||
"sha256": "88730b82d88cb33fae45c7180a88954fcc0241b2b4f5de095fc6b22b30296645",
|
||||
"size": [
|
||||
1024,
|
||||
1536
|
||||
]
|
||||
},
|
||||
"webgl/wither.png": {
|
||||
"sha256": "1c87f05e0b7bd8287926351e07746bc9e36371a860afaef0e4b00c51536dc472",
|
||||
"size": [
|
||||
|
||||
@@ -4,6 +4,9 @@ import asyncio
|
||||
import logging
|
||||
from time import perf_counter
|
||||
import time
|
||||
import hashlib
|
||||
|
||||
from .shop import render_shop
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -60,13 +63,39 @@ class RenderService:
|
||||
return await asyncio.shield(task)
|
||||
|
||||
async def _run(self, *args):
|
||||
return await self._run_function(self.renderer.render, *args)
|
||||
|
||||
async def seed_shop(self, plants, account, page, pages):
|
||||
if self._closed:
|
||||
raise RuntimeError('Render service closed')
|
||||
key = 'shop:' + hashlib.sha256(
|
||||
repr((plants, account, page, pages)).encode(),
|
||||
).hexdigest()
|
||||
task = self._pending.get(key)
|
||||
if task is None:
|
||||
if len(self._pending) >= self._max_pending:
|
||||
raise RuntimeError('Render queue full')
|
||||
task = asyncio.create_task(self._run_function(
|
||||
render_shop, self.renderer, plants, account, page, pages,
|
||||
))
|
||||
self._pending[key] = task
|
||||
|
||||
def finished(done):
|
||||
self._pending.pop(key, None)
|
||||
if not done.cancelled():
|
||||
done.exception()
|
||||
|
||||
task.add_done_callback(finished)
|
||||
return await asyncio.shield(task)
|
||||
|
||||
async def _run_function(self, function, *args):
|
||||
started = perf_counter()
|
||||
async with self._semaphore:
|
||||
logger.info(
|
||||
"farm render queue_ms=%.2f", (perf_counter()-started)*1000,
|
||||
)
|
||||
worker = asyncio.create_task(asyncio.to_thread(
|
||||
self.renderer.render, *args,
|
||||
function, *args,
|
||||
))
|
||||
try:
|
||||
return await asyncio.shield(worker)
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"""文字指令使用的木质种子价目板;全部价格来自离线目录。"""
|
||||
|
||||
import logging
|
||||
import shlex
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .canvas import Canvas, font
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
PAGE_SIZE = 6
|
||||
|
||||
|
||||
def shop_page(catalogue, page):
|
||||
plants = sorted((p for p in catalogue if p.buyable),
|
||||
key=lambda p: p.plant_id)
|
||||
pages = max(1, (len(plants) + PAGE_SIZE - 1) // PAGE_SIZE)
|
||||
if type(page) is not int or not 1 <= page <= pages:
|
||||
raise ValueError('Invalid shop page')
|
||||
start = (page - 1) * PAGE_SIZE
|
||||
return tuple(plants[start:start + PAGE_SIZE]), pages
|
||||
|
||||
|
||||
def duration(seconds):
|
||||
hours, remainder = divmod(seconds, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
return ''.join(f'{value}{unit}' for value, unit in (
|
||||
(hours, '小时'), (minutes, '分'), (seconds, '秒'),
|
||||
) if value) or '0秒'
|
||||
|
||||
|
||||
def render_shop(renderer, plants, account, page, pages):
|
||||
canvas = Canvas(renderer.assets.image('webgl/shop-board.png', (1024, 1536)))
|
||||
|
||||
def text(value, xy, size=28, color='#5b3015', width=850):
|
||||
value = canvas.fit_text(value, renderer.font_path, size, width)
|
||||
box = font(renderer.font_path, size).getbbox(value)
|
||||
canvas.text((xy[0], xy[1]-box[1]), value,
|
||||
renderer.font_path, size, color)
|
||||
|
||||
def centered(value, y, size, color='#5b3015'):
|
||||
x = round((1024 - canvas.measure_text(
|
||||
value, renderer.font_path, size,
|
||||
)) / 2)
|
||||
text(value, (x, y), size, color)
|
||||
|
||||
centered('种 子 商 店', 58, 54, '#fff5d3')
|
||||
centered('种子价目板 · 发送文字指令购买', 178, 25, '#7a6439')
|
||||
balances = ('未开通农场 · 可浏览种子,购买前请发送:开通农场'
|
||||
if account is None else
|
||||
f'Lv.{account.level} 农场币 {account.point:,} '
|
||||
f'点券 {account.vip_point:,}')
|
||||
text(balances, (142, 248), 29, width=745)
|
||||
for index, plant in enumerate(plants):
|
||||
y = 326 + index * 156
|
||||
icon = f'plant/{plant.name}/icon.png'
|
||||
if icon in renderer.assets.metadata['images']:
|
||||
image = renderer.assets.image(icon)
|
||||
image.thumbnail((104, 110), Image.Resampling.LANCZOS)
|
||||
canvas.paste(image, (175-image.width//2, y+67-image.height//2))
|
||||
else:
|
||||
text('暂无图', (133, y+55), 23, '#8b8057', width=92)
|
||||
logger.info('farm shop packaged icon unavailable id=%s',
|
||||
plant.plant_id)
|
||||
text(plant.name, (268, y+13), 34, width=490)
|
||||
text(f'#{plant.plant_id}', (774, y+17), 26, '#69783e', width=132)
|
||||
currency = '点券' if plant.is_vip else '农场币'
|
||||
text(f'{plant.cost:,} {currency} / 粒', (268, y+62), 32,
|
||||
'#b66522', width=480)
|
||||
locked = account is not None and account.level < plant.level
|
||||
text(f'{"需" if locked else "等级"} Lv.{plant.level}',
|
||||
(774, y+69), 23, '#ac542d' if locked else '#66733d', width=132)
|
||||
text(f'首熟 {duration(plant.growth_seconds)} · {plant.crop}季'
|
||||
f' · 每季{plant.harvest}个', (268, y+108), 23, width=635)
|
||||
if not plants:
|
||||
text('暂无出售中的种子', (340, 690), 34)
|
||||
example = plants[0] if plants else None
|
||||
if example:
|
||||
text(f'按编号:购买种子 #{example.plant_id} 5', (142, 1291), 27)
|
||||
name = example.name
|
||||
if any(c.isspace() for c in name) or any(c in name for c in '\"\''):
|
||||
name = shlex.quote(name)
|
||||
text(f'按名称:购买种子 {name} 5', (142, 1341), 27, width=750)
|
||||
else:
|
||||
text('购买种子 #编号 数量 或 购买种子 种子名 数量',
|
||||
(142, 1316), 25, width=750)
|
||||
navigation = f'第 {page} / {pages} 页'
|
||||
if page < pages:
|
||||
navigation += f' 下一页:种子商店 {page+1}'
|
||||
elif page > 1:
|
||||
navigation += f' 上一页:种子商店 {page-1}'
|
||||
text(navigation, (263, 1420), 23, width=510)
|
||||
return canvas.encode('PNG')
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
@@ -0,0 +1,47 @@
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"farm_config_migration",
|
||||
ROOT / "integrations/zhenxun/config_migration.py",
|
||||
)
|
||||
migration = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(migration)
|
||||
|
||||
|
||||
class ConfigMigrationTests(unittest.TestCase):
|
||||
def test_cleanup_backups_and_repeat_are_scoped(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
group = SimpleNamespace(configs={
|
||||
"绘制农场清晰度": "low", "CUSTOM": "keep",
|
||||
})
|
||||
saves = []
|
||||
manager = SimpleNamespace(
|
||||
file=root / "plugins2config.yaml",
|
||||
_simple_file=root / "config.yaml",
|
||||
_simple_data={
|
||||
"farm": {"绘制农场清晰度": "low", "CUSTOM": "keep"},
|
||||
"other": {"服务地址": "keep"},
|
||||
},
|
||||
get=lambda module: group,
|
||||
save=lambda **kwargs: saves.append(kwargs),
|
||||
)
|
||||
for path in (manager.file, manager._simple_file):
|
||||
path.write_text("original", encoding="utf-8")
|
||||
migration.migrate_config(manager, "farm")
|
||||
self.assertEqual(group.configs, {"CUSTOM": "keep"})
|
||||
self.assertEqual(manager._simple_data["farm"], {"CUSTOM": "keep"})
|
||||
self.assertEqual(manager._simple_data["other"], {"服务地址": "keep"})
|
||||
self.assertEqual(saves, [{"save_simple_data": True}])
|
||||
for path in (manager.file, manager._simple_file):
|
||||
self.assertEqual(path.with_name(
|
||||
path.name + ".farm-v1.bak",
|
||||
).read_text(encoding="utf-8"), "original")
|
||||
migration.migrate_config(manager, "farm")
|
||||
self.assertEqual(len(saves), 1)
|
||||
@@ -0,0 +1,49 @@
|
||||
from dataclasses import replace
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from PIL import Image
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
|
||||
from ci_font import choose_font
|
||||
from farm_core import Catalogue
|
||||
from farm_core.domain import Account
|
||||
from farm_render import FarmRenderer
|
||||
from farm_render.shop import shop_page, render_shop
|
||||
from farm_render.canvas import Canvas
|
||||
|
||||
|
||||
class ShopRenderTests(unittest.TestCase):
|
||||
def test_pages_filter_unavailable_and_keep_stable_ids(self):
|
||||
plant = Catalogue.bundled().get('胡萝卜')
|
||||
plants = [replace(plant, plant_id=i) for i in range(1, 9)]
|
||||
plants += [replace(plant, plant_id=99, purchasable=False)]
|
||||
rows, total = shop_page(plants, 2)
|
||||
self.assertEqual(total, 2)
|
||||
self.assertEqual([p.plant_id for p in rows], [7, 8])
|
||||
for page in (0, 3, True):
|
||||
with self.assertRaises(ValueError):
|
||||
shop_page(plants, page)
|
||||
self.assertEqual(shop_page([], 1), ((), 1))
|
||||
|
||||
def test_shop_uses_actual_fields_and_missing_icon_fallback(self):
|
||||
renderer = FarmRenderer(choose_font())
|
||||
plant = Catalogue.bundled().get('胡萝卜')
|
||||
rows = (plant, replace(plant, plant_id=99999,
|
||||
name='不存在的素材', is_vip=True, vip_buy=35))
|
||||
with patch.object(Canvas, 'text', autospec=True) as text:
|
||||
result = render_shop(
|
||||
renderer, rows, Account('商店', 12580, 36, 0), 1, 1,
|
||||
)
|
||||
labels = [call.args[2] for call in text.call_args_list]
|
||||
self.assertIn(f'{plant.cost:,} 农场币 / 粒', labels)
|
||||
self.assertIn('35 点券 / 粒', labels)
|
||||
self.assertIn('暂无图', labels)
|
||||
self.assertIn(f'按编号:购买种子 #{plant.plant_id} 5', labels)
|
||||
self.assertIn('按名称:购买种子 胡萝卜 5', labels)
|
||||
with Image.open(BytesIO(result)) as image:
|
||||
self.assertEqual(image.size, (1024, 1536))
|
||||
self.assertEqual(image.format, 'PNG')
|
||||
@@ -69,7 +69,12 @@ class ZhenxunRuntimeTests(unittest.IsolatedAsyncioTestCase):
|
||||
replace(carrot, plant_id=9002, enabled=False, name="停用菜"),
|
||||
replace(carrot, plant_id=9003, crop_type=11, name="特殊道具"),
|
||||
])
|
||||
shop = await self.call("shop")
|
||||
rendering = self.runtime.rendering
|
||||
self.runtime.rendering = None
|
||||
try:
|
||||
shop = await self.call("shop")
|
||||
finally:
|
||||
self.runtime.rendering = rendering
|
||||
self.assertIn("胡萝卜(#1)", shop)
|
||||
for name in ("禁售菜", "停用菜", "特殊道具"):
|
||||
self.assertNotIn(name, shop)
|
||||
@@ -78,6 +83,28 @@ class ZhenxunRuntimeTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertIn("不出售", await self.call("buy", "禁售菜 1", "blocked"))
|
||||
self.assertIn("(#1)", await self.call("inventory"))
|
||||
|
||||
async def test_shop_image_page_and_buy_by_name_or_id(self):
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
result = await self.call('shop')
|
||||
with Image.open(BytesIO(result)) as image:
|
||||
self.assertEqual(image.size, (1024, 1536))
|
||||
await self.call('register')
|
||||
self.assertIn('已完成', await self.call('buy', '白萝卜 1', 'name'))
|
||||
plant = self.runtime.service.catalogue.get('白萝卜')
|
||||
self.assertIn('已完成', await self.call(
|
||||
'buy', f'#{plant.plant_id} 1', 'id',
|
||||
))
|
||||
from unittest.mock import AsyncMock
|
||||
self.runtime.rendering.seed_shop = AsyncMock(return_value=b'shop')
|
||||
self.assertEqual(await self.call('shop', '2'), b'shop')
|
||||
args = self.runtime.rendering.seed_shop.call_args.args
|
||||
self.assertEqual(len(args[0]), 6)
|
||||
self.assertEqual(args[1].name, '离线玩家')
|
||||
self.assertEqual(args[2], 2)
|
||||
for page in ('0', '999999'):
|
||||
self.assertIn('页码无效', await self.call('shop', page))
|
||||
|
||||
async def test_farm_uses_calendar_theme_and_bee_is_not_a_user_option(self):
|
||||
rendering = self.runtime.rendering
|
||||
from unittest.mock import AsyncMock
|
||||
@@ -251,8 +278,13 @@ class ZhenxunRuntimeTests(unittest.IsolatedAsyncioTestCase):
|
||||
))
|
||||
platform = ModuleType("zhenxun.utils.platform")
|
||||
platform.PlatformUtils = SimpleNamespace()
|
||||
config = ModuleType("zhenxun.configs.config")
|
||||
config.Config = SimpleNamespace(
|
||||
get=lambda module: SimpleNamespace(configs={}),
|
||||
_simple_data={},
|
||||
)
|
||||
modules = {m.__name__:m for m in (
|
||||
nonebot, adapters, params, uninfo, paths, messages, platform,
|
||||
nonebot, adapters, params, uninfo, paths, messages, platform, config,
|
||||
)}
|
||||
with patch.dict(sys.modules, modules):
|
||||
imported = importlib.import_module(self.package + ".commands")
|
||||
|
||||
Reference in New Issue
Block a user