chore: import upstream snapshot with attribution
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:44 +08:00
commit bcbd1bdb22
5748 changed files with 562488 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
@@ -0,0 +1,87 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from unittest.mock import AsyncMock, patch
import pytest
from mirage.core.discord.channels import list_channels
from mirage.resource.discord.config import DiscordConfig
@pytest.fixture
def config():
return DiscordConfig(token="test-bot-token")
@pytest.mark.asyncio
async def test_list_channels(config):
mock_data = [
{
"id": "C001",
"name": "general",
"type": 0
},
{
"id": "C002",
"name": "announcements",
"type": 5
},
{
"id": "C003",
"name": "forum",
"type": 15
},
{
"id": "C004",
"name": "voice-chat",
"type": 2
},
]
with patch(
"mirage.core.discord.channels.discord_get",
new_callable=AsyncMock,
return_value=mock_data,
) as mock_get:
result = await list_channels(config, "G001")
assert len(result) == 3
assert result[0]["name"] == "general"
assert result[1]["name"] == "announcements"
assert result[2]["name"] == "forum"
mock_get.assert_called_once_with(config, "/guilds/G001/channels")
@pytest.mark.asyncio
async def test_list_channels_filters_voice(config):
mock_data = [
{
"id": "C001",
"name": "voice-chat",
"type": 2
},
{
"id": "C002",
"name": "stage",
"type": 13
},
]
with patch(
"mirage.core.discord.channels.discord_get",
new_callable=AsyncMock,
return_value=mock_data,
):
result = await list_channels(config, "G001")
assert len(result) == 0
+108
View File
@@ -0,0 +1,108 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from mirage.core.discord._client import discord_get, discord_post
from mirage.resource.discord.config import DiscordConfig
@pytest.fixture
def config():
return DiscordConfig(token="test-bot-token")
@pytest.mark.asyncio
async def test_discord_get_success(config):
mock_resp = AsyncMock()
mock_resp.status = 200
mock_resp.json = AsyncMock(return_value=[
{
"id": "123",
"name": "general"
},
])
mock_resp.raise_for_status = MagicMock()
mock_session = AsyncMock()
mock_session.get = MagicMock(return_value=AsyncMock(
__aenter__=AsyncMock(return_value=mock_resp),
__aexit__=AsyncMock(return_value=False),
))
with patch("mirage.core.discord._client.aiohttp.ClientSession") as mock_cs:
mock_cs.return_value.__aenter__ = AsyncMock(return_value=mock_session)
mock_cs.return_value.__aexit__ = AsyncMock(return_value=False)
result = await discord_get(config, "/guilds/123/channels")
assert result == [{"id": "123", "name": "general"}]
mock_session.get.assert_called_once()
call_kwargs = mock_session.get.call_args
assert call_kwargs.args[0] == \
"https://discord.com/api/v10/guilds/123/channels"
assert call_kwargs.kwargs["headers"]["Authorization"] == \
"Bot test-bot-token"
@pytest.mark.asyncio
async def test_discord_get_rate_limited(config):
mock_resp = AsyncMock()
mock_resp.status = 429
mock_resp.json = AsyncMock(return_value={
"retry_after": 5,
})
mock_session = AsyncMock()
mock_session.get = MagicMock(return_value=AsyncMock(
__aenter__=AsyncMock(return_value=mock_resp),
__aexit__=AsyncMock(return_value=False),
))
with patch("mirage.core.discord._client.aiohttp.ClientSession") as mock_cs:
mock_cs.return_value.__aenter__ = AsyncMock(return_value=mock_session)
mock_cs.return_value.__aexit__ = AsyncMock(return_value=False)
with pytest.raises(RuntimeError, match="Rate limited"):
await discord_get(config, "/users/@me/guilds")
@pytest.mark.asyncio
async def test_discord_post_success(config):
mock_resp = AsyncMock()
mock_resp.status = 200
mock_resp.json = AsyncMock(return_value={
"id": "msg1",
"content": "hello",
})
mock_resp.raise_for_status = MagicMock()
mock_session = AsyncMock()
mock_session.post = MagicMock(return_value=AsyncMock(
__aenter__=AsyncMock(return_value=mock_resp),
__aexit__=AsyncMock(return_value=False),
))
with patch("mirage.core.discord._client.aiohttp.ClientSession") as mock_cs:
mock_cs.return_value.__aenter__ = AsyncMock(return_value=mock_session)
mock_cs.return_value.__aexit__ = AsyncMock(return_value=False)
result = await discord_post(config,
"/channels/C001/messages",
body={"content": "hello"})
assert result["id"] == "msg1"
mock_session.post.assert_called_once()
call_kwargs = mock_session.post.call_args
assert call_kwargs.args[0] == \
"https://discord.com/api/v10/channels/C001/messages"
+102
View File
@@ -0,0 +1,102 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.core.discord.entry import (channel_dirname, channel_entry,
guild_dirname, guild_entry,
member_entry, member_filename)
def test_guild_dirname_basic():
assert guild_dirname({"id": "G1", "name": "My Server"}) == "My Server__G1"
def test_guild_dirname_apostrophe_is_preserved():
assert guild_dirname({
"id": "G1",
"name": "Zecheng's Server"
}) == "Zecheng's Server__G1"
def test_guild_dirname_slash_in_name_is_replaced():
assert guild_dirname({"id": "G1", "name": "A/B Test"}) == "AB Test__G1"
def test_guild_dirname_falls_back_to_unknown_when_name_missing():
assert guild_dirname({"id": "G1"}) == "unknown__G1"
def test_guild_dirname_falls_back_to_unknown_when_name_empty():
assert guild_dirname({"id": "G1", "name": ""}) == "unknown__G1"
def test_channel_dirname_basic():
assert channel_dirname({"id": "C1", "name": "general"}) == "general__C1"
def test_channel_dirname_with_emoji_is_preserved():
assert channel_dirname({"id": "C1", "name": "🔥-deals"}) == "🔥-deals__C1"
def test_channel_dirname_falls_back_to_unknown():
assert channel_dirname({"id": "C1"}) == "unknown__C1"
def test_member_filename_basic():
member = {"user": {"id": "U1", "username": "alice"}}
assert member_filename(member) == "alice__U1.json"
def test_member_filename_preserves_special_chars():
member = {"user": {"id": "U1", "username": "bob.smith!"}}
assert member_filename(member) == "bob.smith!__U1.json"
def test_member_filename_falls_back_to_unknown():
member = {"user": {"id": "U1"}}
assert member_filename(member) == "unknown__U1.json"
def test_same_named_members_get_distinct_filenames():
a = {"user": {"id": "U1", "username": "alice"}}
b = {"user": {"id": "U2", "username": "alice"}}
assert member_filename(a) != member_filename(b)
assert member_filename(a) == "alice__U1.json"
assert member_filename(b) == "alice__U2.json"
def test_same_named_channels_get_distinct_dirnames():
a = {"id": "C1", "name": "general"}
b = {"id": "C2", "name": "general"}
assert channel_dirname(a) != channel_dirname(b)
def test_same_named_guilds_get_distinct_dirnames():
a = {"id": "G1", "name": "Test"}
b = {"id": "G2", "name": "Test"}
assert guild_dirname(a) != guild_dirname(b)
def test_guild_entry_vfs_name_matches_dirname():
g = {"id": "G1", "name": "My Server"}
assert guild_entry(g).vfs_name == guild_dirname(g)
def test_channel_entry_vfs_name_matches_dirname():
c = {"id": "C1", "name": "general", "last_message_id": "M1"}
assert channel_entry(c).vfs_name == channel_dirname(c)
def test_member_entry_vfs_name_matches_filename():
m = {"user": {"id": "U1", "username": "alice"}}
assert member_entry(m).vfs_name == member_filename(m)
@@ -0,0 +1,86 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.core.discord.entry import channel_dirname, guild_dirname
from mirage.core.discord.formatters import format_grep_results
def test_format_grep_results_uses_vfs_channel_dirname():
msgs = [{
"timestamp": "2024-04-10T12:34:56.000000+00:00",
"channel_id": "C1",
"author": {
"username": "alice"
},
"content": "hello",
}]
guild = {"id": "G1", "name": "MyGuild"}
chan = {"id": "C1", "name": "general"}
lines = format_grep_results(
msgs,
prefix="/discord",
guild_dirname=guild_dirname(guild),
channel_names={chan["id"]: channel_dirname(chan)})
assert lines == [
"/discord/MyGuild__G1/channels/general__C1/"
"2024-04-10/chat.jsonl:[alice] hello"
]
def test_format_grep_results_falls_back_to_channel_id():
msgs = [{
"timestamp": "2024-04-10T00:00:00+00:00",
"channel_id": "C2",
"author": {
"username": "bob"
},
"content": "x",
}]
lines = format_grep_results(msgs, "/discord", "G__G1", channel_names={})
assert lines == [
"/discord/G__G1/channels/C2/2024-04-10/chat.jsonl:[bob] x"
]
def test_format_grep_results_path_matches_vfs_layout():
msgs = [{
"timestamp": "2024-04-10T00:00:00+00:00",
"channel_id": "C1",
"author": {
"username": "alice"
},
"content": "hi",
}]
guild = {"id": "G1", "name": "S"}
chan = {"id": "C1", "name": "general"}
line = format_grep_results(msgs, "/discord", guild_dirname(guild),
{chan["id"]: channel_dirname(chan)})[0]
expected_path = (f"/discord/{guild_dirname(guild)}/channels/"
f"{channel_dirname(chan)}/2024-04-10/chat.jsonl")
assert line.startswith(expected_path + ":")
def test_format_grep_results_replaces_newlines():
msgs = [{
"timestamp": "2024-01-02",
"channel_id": "C",
"author": {
"username": "u"
},
"content": "line1\nline2",
}]
lines = format_grep_results(msgs, "/discord", "G__G1", channel_names={})
assert lines == [
"/discord/G__G1/channels/C/2024-01-02/chat.jsonl:[u] line1 line2"
]
+54
View File
@@ -0,0 +1,54 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from unittest.mock import AsyncMock, patch
import pytest
from mirage.core.discord.guilds import list_guilds
from mirage.resource.discord.config import DiscordConfig
@pytest.fixture
def config():
return DiscordConfig(token="test-bot-token")
@pytest.mark.asyncio
async def test_list_guilds(config):
mock_data = [
{
"id": "G001",
"name": "My Server"
},
{
"id": "G002",
"name": "Another Server"
},
]
with patch(
"mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=mock_data,
) as mock_get:
result = await list_guilds(config)
assert len(result) == 2
assert result[0]["name"] == "My Server"
assert result[1]["id"] == "G002"
mock_get.assert_called_once()
args, kwargs = mock_get.call_args
assert args[1] == "/users/@me/guilds"
assert kwargs["params"]["after"] == "0"
assert kwargs["params"]["limit"] == 200
+66
View File
@@ -0,0 +1,66 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import json
from unittest.mock import AsyncMock, patch
import pytest
from mirage.core.discord.history import get_history_jsonl
from mirage.resource.discord.config import DiscordConfig
@pytest.fixture
def config():
return DiscordConfig(token="test-bot-token")
@pytest.mark.asyncio
async def test_get_history_jsonl(config):
messages = [
{
"id": "200",
"content": "second"
},
{
"id": "100",
"content": "first"
},
]
with patch(
"mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=messages,
):
result = await get_history_jsonl(config, "C001", "2024-01-15")
lines = result.decode().strip().split("\n")
assert len(lines) == 2
first = json.loads(lines[0])
second = json.loads(lines[1])
assert int(first["id"]) < int(second["id"])
assert first["content"] == "first"
assert second["content"] == "second"
@pytest.mark.asyncio
async def test_get_history_empty(config):
with patch(
"mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=[],
):
result = await get_history_jsonl(config, "C001", "2024-01-15")
assert result == b""
+86
View File
@@ -0,0 +1,86 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from unittest.mock import AsyncMock, patch
import pytest
from mirage.core.discord.members import list_members, search_members
from mirage.resource.discord.config import DiscordConfig
@pytest.fixture
def config():
return DiscordConfig(token="test-bot-token")
@pytest.mark.asyncio
async def test_list_members(config):
mock_data = [
{
"user": {
"id": "U001",
"username": "alice"
}
},
{
"user": {
"id": "U002",
"username": "bob"
}
},
]
with patch(
"mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=mock_data,
) as mock_get:
result = await list_members(config, "G001")
assert len(result) == 2
assert result[0]["user"]["username"] == "alice"
mock_get.assert_called_once()
args, kwargs = mock_get.call_args
assert args[1] == "/guilds/G001/members"
assert kwargs["params"]["after"] == "0"
assert kwargs["params"]["limit"] == 1000
@pytest.mark.asyncio
async def test_search_members(config):
mock_data = [
{
"user": {
"id": "U001",
"username": "alice"
}
},
]
with patch(
"mirage.core.discord.members.discord_get",
new_callable=AsyncMock,
return_value=mock_data,
) as mock_get:
result = await search_members(config, "G001", "ali")
assert len(result) == 1
assert result[0]["user"]["username"] == "alice"
mock_get.assert_called_once_with(
config,
"/guilds/G001/members/search",
params={
"query": "ali",
"limit": 100
},
)
+105
View File
@@ -0,0 +1,105 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from unittest.mock import AsyncMock, patch
import pytest
from mirage.core.discord.paginate import after_id_pages, offset_pages
from mirage.resource.discord.config import DiscordConfig
@pytest.fixture
def config():
return DiscordConfig(token="t")
@pytest.mark.asyncio
async def test_after_id_pages_walks_until_partial(config):
page1 = [{"id": "1"}, {"id": "2"}]
page2 = [{"id": "3"}]
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
side_effect=[page1, page2]) as mock:
collected = []
async for page in after_id_pages(
config,
"/x",
base_params={},
last_id_fn=lambda m: m["id"],
page_size=2,
):
collected.append(page)
assert collected == [page1, page2]
assert mock.call_count == 2
second_params = mock.call_args_list[1].kwargs["params"]
assert second_params["after"] == "2"
@pytest.mark.asyncio
async def test_after_id_pages_stops_on_empty(config):
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=[]):
pages = [
p async for p in after_id_pages(
config,
"/x",
base_params={},
last_id_fn=lambda m: m["id"],
)
]
assert pages == []
@pytest.mark.asyncio
async def test_offset_pages_walks_and_terminates_on_total(config):
p1 = {"messages": [["m1"], ["m2"]], "total_results": 3}
p2 = {"messages": [["m3"]], "total_results": 3}
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
side_effect=[p1, p2]) as mock:
collected = []
async for items in offset_pages(
config,
"/y",
base_params={"q": "x"},
items_path=("messages", ),
page_size=2,
):
collected.append(items)
assert collected == [[["m1"], ["m2"]], [["m3"]]]
assert mock.call_count == 2
assert mock.call_args_list[0].kwargs["params"]["offset"] == 0
assert mock.call_args_list[1].kwargs["params"]["offset"] == 2
@pytest.mark.asyncio
async def test_offset_pages_respects_max_pages(config):
p = {"messages": [["m"]], "total_results": 999}
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=p) as mock:
collected = []
async for items in offset_pages(
config,
"/y",
base_params={},
items_path=("messages", ),
page_size=1,
max_pages=2,
):
collected.append(items)
assert len(collected) == 2
assert mock.call_count == 2
+78
View File
@@ -0,0 +1,78 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import asyncio
from unittest.mock import AsyncMock, patch
import pytest
from mirage.accessor.discord import DiscordAccessor
from mirage.cache.index import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.discord.read import read
from mirage.resource.discord.config import DiscordConfig
from mirage.types import PathSpec
@pytest.fixture
def index():
store = RAMIndexCacheStore()
asyncio.run(
store.put(
"/My Server/channels/general",
IndexEntry(
id="C001",
name="general",
resource_type="discord/channel",
vfs_name="general",
),
))
return store
@pytest.fixture
def accessor():
config = DiscordConfig(token="test-bot-token")
return DiscordAccessor(config=config)
@pytest.mark.asyncio
async def test_read_jsonl(accessor, index):
fake_data = b'{"id":"100","content":"hello"}\n'
with patch(
"mirage.core.discord.read.get_history_jsonl",
new_callable=AsyncMock,
return_value=fake_data,
) as mock_hist:
path = "/My Server/channels/general/2024-01-15/chat.jsonl"
result = await read(
accessor,
PathSpec(virtual=path,
directory=path,
resource_path=path.strip("/")),
index,
)
assert result == fake_data
mock_hist.assert_called_once_with(accessor.config, "C001", "2024-01-15")
@pytest.mark.asyncio
async def test_read_not_found(accessor, index):
with pytest.raises(FileNotFoundError):
await read(
accessor,
PathSpec(resource_path="no/such/path",
virtual="/no/such/path",
directory="/no/such/path"), index)
+190
View File
@@ -0,0 +1,190 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from unittest.mock import AsyncMock, patch
import pytest
from mirage.accessor.discord import DiscordAccessor
from mirage.cache.index import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.discord.readdir import readdir
from mirage.resource.discord.config import DiscordConfig
from mirage.types import PathSpec
@pytest.fixture
def index():
return RAMIndexCacheStore()
@pytest.fixture
def accessor():
return DiscordAccessor(config=DiscordConfig(token="test-bot-token"), )
@pytest.mark.asyncio
async def test_readdir_root(accessor, index):
guilds = [
{
"id": "G001",
"name": "My Server"
},
]
with patch(
"mirage.core.discord.readdir.list_guilds",
new_callable=AsyncMock,
return_value=guilds,
):
result = await readdir(
accessor, PathSpec(resource_path="", virtual="/", directory="/"),
index)
assert "/My Server__G001" in result
@pytest.mark.asyncio
async def test_readdir_root_with_slash_in_name(accessor, index):
guilds = [
{
"id": "G001",
"name": "A/B Test Server"
},
]
with patch(
"mirage.core.discord.readdir.list_guilds",
new_callable=AsyncMock,
return_value=guilds,
):
result = await readdir(
accessor, PathSpec(resource_path="", virtual="/", directory="/"),
index)
assert result == ["/AB Test Server__G001"]
@pytest.mark.asyncio
async def test_readdir_root_with_apostrophe(accessor, index):
guilds = [
{
"id": "G001",
"name": "Zecheng's Server"
},
]
with patch(
"mirage.core.discord.readdir.list_guilds",
new_callable=AsyncMock,
return_value=guilds,
):
result = await readdir(
accessor, PathSpec(resource_path="", virtual="/", directory="/"),
index)
assert "/Zecheng's Server__G001" in result
@pytest.mark.asyncio
async def test_readdir_guild(accessor, index):
await index.put(
"/My Server",
IndexEntry(
id="G001",
name="My Server",
resource_type="discord/guild",
vfs_name="My Server",
),
)
result = await readdir(
accessor,
PathSpec(resource_path="My Server",
virtual="/My Server",
directory="/My Server"), index)
assert result == [
"/My Server/channels",
"/My Server/members",
]
@pytest.mark.asyncio
async def test_readdir_channels(accessor, index):
await index.put(
"/My Server",
IndexEntry(
id="G001",
name="My Server",
resource_type="discord/guild",
vfs_name="My Server",
),
)
channels = [
{
"id": "C001",
"name": "general",
"type": 0
},
{
"id": "C002",
"name": "random",
"type": 0
},
]
with patch(
"mirage.core.discord.readdir.list_channels",
new_callable=AsyncMock,
return_value=channels,
):
result = await readdir(
accessor,
PathSpec(resource_path="My Server/channels",
virtual="/My Server/channels",
directory="/My Server/channels"), index)
assert "/My Server/channels/general__C001" in result
assert "/My Server/channels/random__C002" in result
@pytest.mark.asyncio
async def test_readdir_channel_dates(accessor, index):
await index.put(
"/My Server",
IndexEntry(
id="G001",
name="My Server",
resource_type="discord/guild",
vfs_name="My Server",
),
)
await index.put(
"/My Server/channels/general",
IndexEntry(
id="C001",
name="general",
resource_type="discord/channel",
vfs_name="general",
),
)
result = await readdir(
accessor,
PathSpec(resource_path="My Server/channels/general",
virtual="/My Server/channels/general",
directory="/My Server/channels/general"), index)
assert len(result) >= 1
# New layout: date directories (no extension)
import re
date_re = re.compile(r"^/My Server/channels/general/\d{4}-\d{2}-\d{2}$")
assert all(date_re.match(r) for r in result)
@@ -0,0 +1,153 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from unittest.mock import AsyncMock, patch
import aiohttp
import pytest
from mirage.accessor.discord import DiscordAccessor
from mirage.cache.index import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.discord.readdir import readdir
from mirage.resource.discord.config import DiscordConfig
from mirage.types import PathSpec
@pytest.fixture
def index():
return RAMIndexCacheStore()
@pytest.fixture
def accessor():
return DiscordAccessor(config=DiscordConfig(token="t"))
async def _seed_channel(idx):
await idx.put(
"/G",
IndexEntry(id="G1",
name="G",
resource_type="discord/guild",
vfs_name="G"),
)
await idx.put(
"/G/channels/ch",
IndexEntry(id="C1",
name="ch",
resource_type="discord/channel",
vfs_name="ch"),
)
@pytest.mark.asyncio
async def test_date_dir_contents_lists_chat_and_files(accessor, index):
fake_messages = [{
"id":
"100",
"content":
"hi",
"attachments": [{
"id": "A1",
"filename": "pic.png",
"url": "https://cdn.example/A1/pic.png",
"proxy_url": "https://media.example/A1/pic.png",
"content_type": "image/png",
"size": 1234,
}],
}]
await _seed_channel(index)
with patch("mirage.core.discord.readdir.list_messages_for_day",
new_callable=AsyncMock,
return_value=fake_messages):
result = await readdir(
accessor,
PathSpec(resource_path="G/channels/ch/2024-04-04",
virtual="/G/channels/ch/2024-04-04",
directory="/G/channels/ch/2024-04-04"),
index,
)
assert "/G/channels/ch/2024-04-04/chat.jsonl" in result
assert "/G/channels/ch/2024-04-04/files" in result
@pytest.mark.asyncio
async def test_files_dir_lists_attachments(accessor, index):
fake_messages = [{
"id":
"100",
"attachments": [{
"id": "A1",
"filename": "pic.png",
"url": "https://cdn.example/A1/pic.png",
"content_type": "image/png",
"size": 1234,
}],
}]
await _seed_channel(index)
with patch("mirage.core.discord.readdir.list_messages_for_day",
new_callable=AsyncMock,
return_value=fake_messages):
result = await readdir(
accessor,
PathSpec(resource_path="G/channels/ch/2024-04-04/files",
virtual="/G/channels/ch/2024-04-04/files",
directory="/G/channels/ch/2024-04-04/files"),
index,
)
assert any(r.endswith("pic__A1.png") for r in result)
@pytest.mark.asyncio
async def test_fetch_day_swallows_soft_errors(accessor, index):
await _seed_channel(index)
err = aiohttp.ClientResponseError(
request_info=None, # type: ignore[arg-type]
history=(),
status=403,
)
with patch("mirage.core.discord.readdir.list_messages_for_day",
new_callable=AsyncMock,
side_effect=err):
result = await readdir(
accessor,
PathSpec(resource_path="G/channels/ch/2024-04-04",
virtual="/G/channels/ch/2024-04-04",
directory="/G/channels/ch/2024-04-04"),
index,
)
# empty day on soft error → sealed listing (no chat.jsonl/files entries)
assert result == []
@pytest.mark.asyncio
async def test_fetch_day_propagates_hard_errors(accessor, index):
await _seed_channel(index)
err = aiohttp.ClientResponseError(
request_info=None, # type: ignore[arg-type]
history=(),
status=500,
)
with patch("mirage.core.discord.readdir.list_messages_for_day",
new_callable=AsyncMock,
side_effect=err):
with pytest.raises(aiohttp.ClientResponseError):
await readdir(
accessor,
PathSpec(resource_path="G/channels/ch/2024-04-04",
virtual="/G/channels/ch/2024-04-04",
directory="/G/channels/ch/2024-04-04"),
index,
)
+270
View File
@@ -0,0 +1,270 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import asyncio
from unittest.mock import AsyncMock
import pytest
from mirage.cache.index import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.discord.scope import coalesce_scopes, detect_scope
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key
def _run(coro):
return asyncio.run(coro)
def _gs(path: str,
prefix: str = "",
pattern: str | None = None,
resolved: bool = True) -> PathSpec:
return PathSpec(
resource_path=mount_key(path, prefix),
virtual=path,
directory=path.rsplit("/", 1)[0] + "/" if "/" in path else "/",
pattern=pattern,
resolved=resolved,
)
@pytest.fixture
def index():
idx = RAMIndexCacheStore(ttl=600)
_run(
idx.put(
"/discord/TestGuild",
IndexEntry(id="G1",
name="TestGuild",
resource_type="discord/guild")))
_run(
idx.put(
"/discord/TestGuild/channels/general",
IndexEntry(id="C1",
name="general",
resource_type="discord/channel")))
return idx
# ── root ──────────────────────────────────────
def test_root_empty():
scope = _run(detect_scope("/"))
assert scope.level == "root"
def test_root_prefix():
scope = _run(detect_scope(_gs("/discord/", prefix="/discord")))
assert scope.level == "root"
# ── guild ─────────────────────────────────────
def test_guild(index):
scope = _run(
detect_scope(_gs("/discord/TestGuild", prefix="/discord"), index))
assert scope.level == "guild"
assert scope.guild_id == "G1"
def test_guild_channels(index):
scope = _run(
detect_scope(_gs("/discord/TestGuild/channels", prefix="/discord"),
index))
assert scope.level == "guild"
assert scope.guild_id == "G1"
def test_guild_members(index):
scope = _run(
detect_scope(_gs("/discord/TestGuild/members", prefix="/discord"),
index))
assert scope.level == "guild"
assert scope.guild_id == "G1"
# ── channel ───────────────────────────────────
def test_channel(index):
scope = _run(
detect_scope(
_gs("/discord/TestGuild/channels/general", prefix="/discord"),
index))
assert scope.level == "channel"
assert scope.guild_id == "G1"
assert scope.channel_id == "C1"
# ── date / messages / files ────────────────────
def test_date_dir(index):
scope = _run(
detect_scope(
_gs("/discord/TestGuild/channels/general/2024-04-10",
prefix="/discord"), index))
assert scope.level == "date"
assert scope.guild_id == "G1"
assert scope.channel_id == "C1"
assert scope.date_str == "2024-04-10"
def test_messages_file(index):
scope = _run(
detect_scope(
_gs("/discord/TestGuild/channels/general/2024-04-10/chat.jsonl",
prefix="/discord"), index))
assert scope.level == "messages"
assert scope.guild_id == "G1"
assert scope.channel_id == "C1"
assert scope.date_str == "2024-04-10"
def test_files_dir(index):
scope = _run(
detect_scope(
_gs("/discord/TestGuild/channels/general/2024-04-10/files",
prefix="/discord"), index))
assert scope.level == "files"
assert scope.guild_id == "G1"
assert scope.channel_id == "C1"
assert scope.date_str == "2024-04-10"
def test_file_blob(index):
scope = _run(
detect_scope(
_gs(
"/discord/TestGuild/channels/general/2024-04-10/files/"
"img__A1.png",
prefix="/discord"), index))
assert scope.level == "file_blob"
assert scope.channel_id == "C1"
assert scope.date_str == "2024-04-10"
# ── PathSpec ─────────────────────────────────
def test_glob_jsonl_in_channel(index):
gs = PathSpec(
resource_path=mount_key("/discord/TestGuild/channels/general/*.jsonl",
"/discord"),
virtual="/discord/TestGuild/channels/general/*.jsonl",
directory="/discord/TestGuild/channels/general/",
pattern="*.jsonl",
resolved=False,
)
scope = _run(detect_scope(gs, index))
assert scope.level == "channel"
assert scope.guild_id == "G1"
assert scope.channel_id == "C1"
def test_glob_specific_date(index):
gs = PathSpec(
resource_path=mount_key(
"/discord/TestGuild/channels/general/2024-04-*.jsonl", "/discord"),
virtual="/discord/TestGuild/channels/general/2024-04-*.jsonl",
directory="/discord/TestGuild/channels/general/",
pattern="2024-04-*.jsonl",
resolved=False,
)
scope = _run(detect_scope(gs, index))
assert scope.level == "channel"
assert scope.channel_id == "C1"
def test_glob_non_jsonl():
gs = PathSpec(
resource_path="discord/TestGuild/members/*.json",
virtual="/discord/TestGuild/members/*.json",
directory="/discord/TestGuild/members/",
pattern="*.json",
resolved=False,
)
scope = _run(detect_scope(gs))
assert scope.level != "channel"
# ── no index ──────────────────────────────────
def test_guild_no_index():
scope = _run(detect_scope("TestGuild"))
assert scope.level == "guild"
assert scope.guild_id is None
def test_channel_no_index():
scope = _run(detect_scope("TestGuild/channels/general"))
assert scope.level == "channel"
assert scope.guild_id is None
assert scope.channel_id is None
def _spec(path: str, prefix: str = "/discord") -> PathSpec:
return PathSpec(resource_path=mount_key(path, prefix),
virtual=path,
directory=path)
@pytest.fixture
def fake_index():
idx = AsyncMock()
async def _get(virtual_key):
result = AsyncMock()
if virtual_key.endswith("/myguild/channels/general"):
result.entry = type("E", (), {"id": "ch_456"})
elif virtual_key.endswith("/myguild"):
result.entry = type("E", (), {"id": "g_123"})
else:
result.entry = None
return result
idx.get.side_effect = _get
return idx
@pytest.mark.asyncio
async def test_coalesce_concrete_jsonl_paths_same_channel(fake_index):
paths = [
_spec(f"/discord/myguild/channels/general/2026-01-{d:02d}/chat.jsonl")
for d in range(1, 8)
]
scope = await coalesce_scopes(paths, fake_index)
assert scope is not None
assert scope.level == "channel"
assert scope.guild_id == "g_123"
assert scope.channel_id == "ch_456"
@pytest.mark.asyncio
async def test_coalesce_returns_none_for_mixed_channels(fake_index):
paths = [
_spec("/discord/myguild/channels/general/2026-01-01/chat.jsonl"),
_spec("/discord/myguild/channels/random/2026-01-01/chat.jsonl"),
]
assert await coalesce_scopes(paths, fake_index) is None
@pytest.mark.asyncio
async def test_coalesce_empty_list_returns_none(fake_index):
assert await coalesce_scopes([], fake_index) is None
+147
View File
@@ -0,0 +1,147 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import asyncio
from unittest.mock import AsyncMock, patch
from mirage.core.discord.search import search_guild
from mirage.resource.discord.config import DiscordConfig
def _run(coro):
return asyncio.run(coro)
def _config():
return DiscordConfig(token="test-token")
def _make_search_response(messages, total=None):
hits = [[msg] for msg in messages]
return {"messages": hits, "total_results": total or len(messages)}
def test_search_returns_messages():
msgs = [
{
"id": "100",
"content": "hello world",
"author": {
"username": "a"
}
},
{
"id": "200",
"content": "hello again",
"author": {
"username": "b"
}
},
]
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=_make_search_response(msgs)):
results = _run(search_guild(_config(), "G1", "hello"))
assert len(results) == 2
assert results[0]["id"] == "100"
assert results[1]["id"] == "200"
def test_search_with_channel_filter():
msgs = [{"id": "300", "content": "test", "author": {"username": "c"}}]
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=_make_search_response(msgs)) as mock:
results = _run(search_guild(_config(), "G1", "test", channel_id="C1"))
assert len(results) == 1
call_params = mock.call_args[1].get("params") or mock.call_args[0][2]
assert call_params["channel_id"] == "C1"
assert call_params["content"] == "test"
def test_search_empty_results():
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value={
"messages": [],
"total_results": 0
}):
results = _run(search_guild(_config(), "G1", "nonexistent"))
assert results == []
def test_search_sorted_oldest_first():
msgs = [
{
"id": "500",
"content": "newer",
"author": {
"username": "a"
}
},
{
"id": "100",
"content": "older",
"author": {
"username": "b"
}
},
]
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=_make_search_response(msgs)):
results = _run(search_guild(_config(), "G1", "hello"))
assert results[0]["id"] == "100"
assert results[1]["id"] == "500"
def test_search_respects_limit():
msgs = [{
"id": str(i),
"content": f"msg{i}",
"author": {
"username": "a"
}
} for i in range(10)]
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=_make_search_response(msgs, total=10)):
results = _run(search_guild(_config(), "G1", "msg", limit=3))
assert len(results) == 3
def test_search_paginates():
page1 = [{
"id": str(i),
"content": f"msg{i}",
"author": {
"username": "a"
}
} for i in range(25)]
page2 = [{
"id": str(i + 25),
"content": f"msg{i + 25}",
"author": {
"username": "a"
}
} for i in range(5)]
responses = [
_make_search_response(page1, total=30),
_make_search_response(page2, total=30),
]
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
side_effect=responses):
results = _run(search_guild(_config(), "G1", "msg", limit=100))
assert len(results) == 30
+173
View File
@@ -0,0 +1,173 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import asyncio
import pytest
from mirage.accessor.discord import DiscordAccessor
from mirage.cache.index import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.discord.stat import stat
from mirage.types import FileType, PathSpec
@pytest.fixture
def index():
store = RAMIndexCacheStore()
asyncio.run(
store.put(
"/My Server",
IndexEntry(
id="G001",
name="My Server",
resource_type="discord/guild",
vfs_name="My Server",
),
))
asyncio.run(
store.put(
"/My Server/channels/general",
IndexEntry(
id="C001",
name="general",
resource_type="discord/channel",
remote_time="794354201395200000",
vfs_name="general",
),
))
asyncio.run(
store.put(
"/My Server/members/alice.json",
IndexEntry(
id="U001",
name="alice",
resource_type="discord/member",
vfs_name="alice.json",
),
))
return store
@pytest.fixture
def accessor():
return DiscordAccessor(config=object())
@pytest.mark.asyncio
async def test_stat_root(accessor, index):
result = await stat(accessor,
PathSpec(resource_path="", virtual="/", directory="/"),
index)
assert result.type == FileType.DIRECTORY
assert result.name == "/"
@pytest.mark.asyncio
async def test_stat_guild(accessor, index):
result = await stat(
accessor,
PathSpec(resource_path="My Server",
virtual="/My Server",
directory="/My Server"), index)
assert result.type == FileType.DIRECTORY
assert result.extra["guild_id"] == "G001"
@pytest.mark.asyncio
async def test_stat_channel(accessor, index):
result = await stat(
accessor,
PathSpec(resource_path="My Server/channels/general",
virtual="/My Server/channels/general",
directory="/My Server/channels/general"), index)
assert result.type == FileType.DIRECTORY
assert result.extra["channel_id"] == "C001"
assert result.modified == "2021-01-01T00:00:00Z"
@pytest.mark.asyncio
async def test_stat_member(accessor, index):
result = await stat(
accessor,
PathSpec(resource_path="My Server/members/alice.json",
virtual="/My Server/members/alice.json",
directory="/My Server/members/alice.json"), index)
assert result.type == FileType.JSON
assert result.extra["user_id"] == "U001"
@pytest.mark.asyncio
async def test_stat_date_dir(accessor, index):
path = "/My Server/channels/general/2024-01-15"
result = await stat(
accessor,
PathSpec(virtual=path, directory=path, resource_path=path.strip("/")),
index)
assert result.type == FileType.DIRECTORY
assert result.name == "2024-01-15"
@pytest.mark.asyncio
async def test_stat_chat_jsonl(accessor, index):
path = "/My Server/channels/general/2024-01-15/chat.jsonl"
result = await stat(
accessor,
PathSpec(virtual=path, directory=path, resource_path=path.strip("/")),
index)
assert result.type == FileType.TEXT
assert result.name == "chat.jsonl"
@pytest.mark.asyncio
async def test_stat_files_dir(accessor, index):
path = "/My Server/channels/general/2024-01-15/files"
result = await stat(
accessor,
PathSpec(virtual=path, directory=path, resource_path=path.strip("/")),
index)
assert result.type == FileType.DIRECTORY
assert result.name == "files"
@pytest.mark.asyncio
async def test_stat_non_date_chat_jsonl_not_found(accessor, index):
path = "/My Server/channels/general/notadate/chat.jsonl"
with pytest.raises(FileNotFoundError):
await stat(
accessor,
PathSpec(virtual=path,
directory=path,
resource_path=path.strip("/")), index)
@pytest.mark.asyncio
async def test_stat_non_date_files_dir_not_found(accessor, index):
path = "/My Server/channels/general/notadate/files"
with pytest.raises(FileNotFoundError):
await stat(
accessor,
PathSpec(virtual=path,
directory=path,
resource_path=path.strip("/")), index)
@pytest.mark.asyncio
async def test_stat_not_found(accessor, index):
with pytest.raises(FileNotFoundError):
await stat(
accessor,
PathSpec(resource_path="nonexistent/path",
virtual="/nonexistent/path",
directory="/nonexistent/path"), index)
+98
View File
@@ -0,0 +1,98 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from unittest.mock import AsyncMock, patch
import pytest
from mirage.core.discord.guilds import list_guilds_stream
from mirage.core.discord.history import (date_to_snowflake,
stream_messages_for_day)
from mirage.core.discord.members import list_members_stream
from mirage.core.discord.search import search_guild_stream
from mirage.resource.discord.config import DiscordConfig
@pytest.fixture
def config():
return DiscordConfig(token="t")
@pytest.mark.asyncio
async def test_list_guilds_stream_walks_pages(config):
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
side_effect=[[{
"id": "G1"
}, {
"id": "G2"
}], [{
"id": "G3"
}]]):
collected = []
async for page in list_guilds_stream(config, page_size=2):
collected.append(page)
assert collected == [[{"id": "G1"}, {"id": "G2"}], [{"id": "G3"}]]
@pytest.mark.asyncio
async def test_list_members_stream_walks_user_ids(config):
page1 = [{"user": {"id": "U1"}}, {"user": {"id": "U2"}}]
page2 = [{"user": {"id": "U3"}}]
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
side_effect=[page1, page2]) as mock:
collected = []
async for page in list_members_stream(config, "G1", page_size=2):
collected.append(page)
assert collected == [page1, page2]
assert mock.call_args_list[1].kwargs["params"]["after"] == "U2"
@pytest.mark.asyncio
async def test_stream_messages_for_day_filters_by_date(config):
before_int = int(date_to_snowflake("2024-01-15", end=True))
in_range = [{"id": str(before_int - 1000), "content": "ok"}]
out_of_range = [{"id": str(before_int + 1000), "content": "next-day"}]
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
return_value=in_range + out_of_range):
pages = [
p async for p in stream_messages_for_day(
config, "C1", "2024-01-15", page_size=2)
]
flat = [m for page in pages for m in page]
assert all(int(m["id"]) <= before_int for m in flat)
assert any(m["content"] == "ok" for m in flat)
@pytest.mark.asyncio
async def test_search_guild_stream_flattens_contexts(config):
p1 = {
"messages": [[{
"id": "1"
}], [{
"id": "2"
}]],
"total_results": 30,
}
p2 = {"messages": [[{"id": "3"}]], "total_results": 30}
with patch("mirage.core.discord.paginate.discord_get",
new_callable=AsyncMock,
side_effect=[p1, p2]):
collected = []
async for page in search_guild_stream(config, "G1", "x", max_pages=2):
collected.append(page)
flat = [m["id"] for page in collected for m in page]
assert flat == ["1", "2", "3"]