refactor(delivery): use workspace out as artifact root
Some checks failed
Test Suite / test (3.11) (push) Failing after 1m24s
Test Suite / test (3.12) (push) Failing after 1m46s
Test Suite / test (3.13) (push) Failing after 2m1s

This commit is contained in:
Hua
2026-03-20 09:10:33 +08:00
parent 73af8c574e
commit 9ac73f1e26
13 changed files with 272 additions and 344 deletions

View File

@@ -1,4 +1,3 @@
import os
from base64 import b64encode
from types import SimpleNamespace
@@ -185,7 +184,7 @@ async def test_send_local_media_falls_back_to_text_notice_when_publishing_not_co
{
"openid": "user123",
"msg_type": 0,
"content": "hello\n[Failed to send: demo.png - QQ local media publishing is not configured]",
"content": "hello\n[Failed to send: demo.png - local media publishing is not configured]",
"msg_id": "msg1",
"msg_seq": 2,
}
@@ -193,70 +192,7 @@ async def test_send_local_media_falls_back_to_text_notice_when_publishing_not_co
@pytest.mark.asyncio
async def test_send_local_media_under_public_dir_uses_c2c_file_api(
monkeypatch,
tmp_path,
) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
public_dir = workspace / "public" / "qq"
public_dir.mkdir(parents=True)
source = public_dir / "demo.png"
source.write_bytes(b"fake-png")
channel = QQChannel(
QQConfig(
app_id="app",
secret="secret",
allow_from=["*"],
media_base_url="https://files.example.com/public/qq",
media_public_dir="public/qq",
media_ttl_seconds=0,
),
MessageBus(),
workspace=workspace,
)
channel._client = _FakeClient()
monkeypatch.setattr("nanobot.channels.qq.validate_url_target", lambda url: (True, ""))
await channel.send(
OutboundMessage(
channel="qq",
chat_id="user123",
content="hello",
media=[str(source)],
metadata={"message_id": "msg1"},
)
)
assert channel._client.api.raw_file_upload_calls == [
{
"method": "POST",
"path": "/v2/users/{openid}/files",
"params": {"openid": "user123"},
"json": {
"file_type": 1,
"url": "https://files.example.com/public/qq/demo.png",
"file_data": b64encode(b"fake-png").decode("ascii"),
"srv_send_msg": False,
},
}
]
assert channel._client.api.c2c_file_calls == []
assert channel._client.api.c2c_calls == [
{
"openid": "user123",
"msg_type": 7,
"content": "hello",
"media": {"file_info": "c2c-file-info", "file_uuid": "c2c-file", "ttl": 60},
"msg_id": "msg1",
"msg_seq": 2,
}
]
@pytest.mark.asyncio
async def test_send_local_media_from_out_auto_links_into_public_then_uses_c2c_file_api(
async def test_send_local_media_under_out_dir_uses_c2c_file_api(
monkeypatch,
tmp_path,
) -> None:
@@ -264,7 +200,7 @@ async def test_send_local_media_from_out_auto_links_into_public_then_uses_c2c_fi
workspace.mkdir()
out_dir = workspace / "out"
out_dir.mkdir()
source = out_dir / "outside.png"
source = out_dir / "demo.png"
source.write_bytes(b"\x89PNG\r\n\x1a\nfake-png")
channel = QQChannel(
@@ -272,9 +208,7 @@ async def test_send_local_media_from_out_auto_links_into_public_then_uses_c2c_fi
app_id="app",
secret="secret",
allow_from=["*"],
media_base_url="https://files.example.com/public/qq",
media_public_dir="public/qq",
media_ttl_seconds=0,
media_base_url="https://files.example.com/out",
),
MessageBus(),
workspace=workspace,
@@ -292,11 +226,6 @@ async def test_send_local_media_from_out_auto_links_into_public_then_uses_c2c_fi
)
)
published = list((workspace / "public" / "qq").iterdir())
assert len(published) == 1
assert published[0].name.startswith("outside-")
assert published[0].suffix == ".png"
assert os.stat(source).st_ino == os.stat(published[0]).st_ino
assert channel._client.api.raw_file_upload_calls == [
{
"method": "POST",
@@ -304,7 +233,7 @@ async def test_send_local_media_from_out_auto_links_into_public_then_uses_c2c_fi
"params": {"openid": "user123"},
"json": {
"file_type": 1,
"url": f"https://files.example.com/public/qq/{published[0].name}",
"url": "https://files.example.com/out/demo.png",
"file_data": b64encode(b"\x89PNG\r\n\x1a\nfake-png").decode("ascii"),
"srv_send_msg": False,
},
@@ -324,7 +253,69 @@ async def test_send_local_media_from_out_auto_links_into_public_then_uses_c2c_fi
@pytest.mark.asyncio
async def test_send_local_media_outside_public_and_out_falls_back_to_text_notice(
async def test_send_local_media_in_nested_out_path_uses_relative_url(
monkeypatch,
tmp_path,
) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
out_dir = workspace / "out"
source_dir = out_dir / "shots"
source_dir.mkdir(parents=True)
source = source_dir / "github.png"
source.write_bytes(b"\x89PNG\r\n\x1a\nfake-png")
channel = QQChannel(
QQConfig(
app_id="app",
secret="secret",
allow_from=["*"],
media_base_url="https://files.example.com/qq-media",
),
MessageBus(),
workspace=workspace,
)
channel._client = _FakeClient()
monkeypatch.setattr("nanobot.channels.qq.validate_url_target", lambda url: (True, ""))
await channel.send(
OutboundMessage(
channel="qq",
chat_id="user123",
content="hello",
media=[str(source)],
metadata={"message_id": "msg1"},
)
)
assert channel._client.api.raw_file_upload_calls == [
{
"method": "POST",
"path": "/v2/users/{openid}/files",
"params": {"openid": "user123"},
"json": {
"file_type": 1,
"url": "https://files.example.com/qq-media/shots/github.png",
"file_data": b64encode(b"\x89PNG\r\n\x1a\nfake-png").decode("ascii"),
"srv_send_msg": False,
},
}
]
assert channel._client.api.c2c_file_calls == []
assert channel._client.api.c2c_calls == [
{
"openid": "user123",
"msg_type": 7,
"content": "hello",
"media": {"file_info": "c2c-file-info", "file_uuid": "c2c-file", "ttl": 60},
"msg_id": "msg1",
"msg_seq": 2,
}
]
@pytest.mark.asyncio
async def test_send_local_media_outside_out_falls_back_to_text_notice(
monkeypatch,
tmp_path,
) -> None:
@@ -340,9 +331,7 @@ async def test_send_local_media_outside_public_and_out_falls_back_to_text_notice
app_id="app",
secret="secret",
allow_from=["*"],
media_base_url="https://files.example.com/public/qq",
media_public_dir="public/qq",
media_ttl_seconds=0,
media_base_url="https://files.example.com/out",
),
MessageBus(),
workspace=workspace,
@@ -365,8 +354,10 @@ async def test_send_local_media_outside_public_and_out_falls_back_to_text_notice
{
"openid": "user123",
"msg_type": 0,
"content": "hello\n[Failed to send: outside.png - QQ local media must stay under "
f"{workspace / 'public' / 'qq'} or {workspace / 'out'}]",
"content": (
"hello\n[Failed to send: outside.png - local delivery media must stay under "
f"{workspace / 'out'}]"
),
"msg_id": "msg1",
"msg_seq": 2,
}
@@ -380,19 +371,17 @@ async def test_send_local_media_falls_back_to_url_only_upload_when_file_data_upl
) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
public_dir = workspace / "public" / "qq"
public_dir.mkdir(parents=True)
source = public_dir / "demo.png"
source.write_bytes(b"fake-png")
out_dir = workspace / "out"
out_dir.mkdir()
source = out_dir / "demo.png"
source.write_bytes(b"\x89PNG\r\n\x1a\nfake-png")
channel = QQChannel(
QQConfig(
app_id="app",
secret="secret",
allow_from=["*"],
media_base_url="https://files.example.com/public/qq",
media_public_dir="public/qq",
media_ttl_seconds=0,
media_base_url="https://files.example.com/out",
),
MessageBus(),
workspace=workspace,
@@ -415,7 +404,7 @@ async def test_send_local_media_falls_back_to_url_only_upload_when_file_data_upl
{
"openid": "user123",
"file_type": 1,
"url": "https://files.example.com/public/qq/demo.png",
"url": "https://files.example.com/out/demo.png",
"srv_send_msg": False,
}
]
@@ -432,17 +421,17 @@ async def test_send_local_media_falls_back_to_url_only_upload_when_file_data_upl
@pytest.mark.asyncio
async def test_send_local_media_symlink_to_outside_public_dir_is_rejected(
async def test_send_local_media_symlink_to_outside_out_dir_is_rejected(
monkeypatch,
tmp_path,
) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
public_dir = workspace / "public" / "qq"
public_dir.mkdir(parents=True)
out_dir = workspace / "out"
out_dir.mkdir()
outside = tmp_path / "secret.png"
outside.write_bytes(b"secret")
source = public_dir / "linked.png"
source = out_dir / "linked.png"
source.symlink_to(outside)
channel = QQChannel(
@@ -450,9 +439,7 @@ async def test_send_local_media_symlink_to_outside_public_dir_is_rejected(
app_id="app",
secret="secret",
allow_from=["*"],
media_base_url="https://files.example.com/public/qq",
media_public_dir="public/qq",
media_ttl_seconds=0,
media_base_url="https://files.example.com/out",
),
MessageBus(),
workspace=workspace,
@@ -475,8 +462,10 @@ async def test_send_local_media_symlink_to_outside_public_dir_is_rejected(
{
"openid": "user123",
"msg_type": 0,
"content": "hello\n[Failed to send: linked.png - QQ local media must stay under "
f"{workspace / 'public' / 'qq'} or {workspace / 'out'}]",
"content": (
"hello\n[Failed to send: linked.png - local delivery media must stay under "
f"{workspace / 'out'}]"
),
"msg_id": "msg1",
"msg_seq": 2,
}
@@ -500,9 +489,7 @@ async def test_send_non_image_media_from_out_falls_back_to_text_notice(
app_id="app",
secret="secret",
allow_from=["*"],
media_base_url="https://files.example.com/public/qq",
media_public_dir="public/qq",
media_ttl_seconds=0,
media_base_url="https://files.example.com/out",
),
MessageBus(),
workspace=workspace,
@@ -525,7 +512,7 @@ async def test_send_non_image_media_from_out_falls_back_to_text_notice(
{
"openid": "user123",
"msg_type": 0,
"content": "hello\n[Failed to send: note.txt - QQ local media must be an image]",
"content": "hello\n[Failed to send: note.txt - local delivery media must be an image]",
"msg_id": "msg1",
"msg_seq": 2,
}