test: community interfaces, hex utils, archives and conversation viewer

This commit is contained in:
Ivan
2026-03-24 00:39:11 +03:00
parent 83ed463c3d
commit a9a7dd5212
5 changed files with 365 additions and 2 deletions
+2 -2
View File
@@ -30,7 +30,7 @@ async def test_rnstatus_integration_simulated():
"txb": 200,
},
{
"name": "Quad4 TCP Node 1",
"name": "Remote TCP relay",
"status": False,
"rxb": 0,
"txb": 0,
@@ -44,7 +44,7 @@ async def test_rnstatus_integration_simulated():
assert len(status["interfaces"]) == 2
assert status["interfaces"][0]["name"] == "noDNS1"
assert status["interfaces"][0]["status"] == "Up"
assert status["interfaces"][1]["name"] == "Quad4 TCP Node 1"
assert status["interfaces"][1]["name"] == "Remote TCP relay"
assert status["interfaces"][1]["status"] == "Down"
@@ -0,0 +1,239 @@
import pytest
from hypothesis import given, settings
from hypothesis import strategies as st
from meshchatx.src.backend.community_interfaces_directory import (
DEFAULT_SUBMITTED_URL,
rows_from_payload,
transform_directory_rows,
)
def test_default_url_is_submitted_online():
assert "submitted" in DEFAULT_SUBMITTED_URL
assert "status=online" in DEFAULT_SUBMITTED_URL
def test_rows_from_payload_dict_data():
rows = rows_from_payload({"data": [{"id": 1}]})
assert rows == [{"id": 1}]
def test_rows_from_payload_list():
rows = rows_from_payload([{"id": 2}])
assert rows == [{"id": 2}]
def test_rows_from_payload_invalid():
with pytest.raises(ValueError, match="Expected list"):
rows_from_payload({"foo": 1})
def test_transform_submitted_backbone_without_identity_becomes_tcp():
rows = [
{
"id": 39,
"name": "CRN IPv4",
"type": "backbone",
"typeName": "BackboneInterface",
"network": "clearnet",
"host": "rns.example.org",
"port": 4242,
"status": "online",
"config": "",
},
]
out = transform_directory_rows(rows)
assert len(out) == 1
assert out[0]["type"] == "TCPClientInterface"
assert out[0]["target_host"] == "rns.example.org"
assert out[0]["target_port"] == 4242
def test_transform_submitted_tcp_client():
rows = [
{
"id": 51,
"name": "Ether Whisperer",
"type": "tcp",
"typeName": "TCPClientInterface",
"host": "132.145.75.143",
"port": 4242,
"status": "online",
"config": "",
},
]
out = transform_directory_rows(rows)
assert out[0]["type"] == "TCPClientInterface"
assert out[0]["target_host"] == "132.145.75.143"
def test_transform_backbone_with_transport_id():
rows = [
{
"id": 1,
"name": "BB",
"type": "backbone",
"typeName": "BackboneInterface",
"host": "a.example",
"port": 4242,
"transportId": "e53433e51cde34c42a3245ba3fe1ad69",
"config": "",
},
]
out = transform_directory_rows(rows)
assert out[0]["type"] == "BackboneInterface"
assert out[0]["transport_identity"] == "e53433e51cde34c42a3245ba3fe1ad69"
def test_transform_transport_identity_from_config():
cfg = "[[x]]\ntype = BackboneInterface\ntransport_identity = abcd0123ef\nremote = h.example\ntarget_port = 1"
rows = [
{
"id": 2,
"name": "X",
"type": "backbone",
"typeName": "BackboneInterface",
"host": "",
"port": 4242,
"config": cfg,
},
]
out = transform_directory_rows(rows)
assert len(out) == 1
assert out[0]["type"] == "BackboneInterface"
assert out[0]["transport_identity"] == "abcd0123ef"
assert out[0]["remote"] == "h.example"
def test_transform_host_from_config_remote():
cfg = "remote = cfg-host.example\ntarget_port = 4242"
rows = [
{
"id": 3,
"name": "Y",
"type": "tcp",
"typeName": "TCPClientInterface",
"host": "",
"port": 4242,
"config": cfg,
},
]
out = transform_directory_rows(rows)
assert out[0]["target_host"] == "cfg-host.example"
def test_transform_i2p_uses_host():
rows = [
{
"id": 48,
"name": "Casbah",
"type": "i2p",
"typeName": "I2PInterface",
"host": "aaa.b32.i2p",
"port": None,
"config": "",
},
]
out = transform_directory_rows(rows)
assert out[0]["type"] == "I2PInterface"
assert out[0]["i2p_peers"] == ["aaa.b32.i2p"]
def test_transform_i2p_peer_from_config_only():
cfg = "peers = bbb.b32.i2p"
rows = [
{
"id": 49,
"name": "Relay",
"type": "i2p",
"typeName": "I2PInterface",
"host": "",
"port": None,
"config": cfg,
},
]
out = transform_directory_rows(rows)
assert out[0]["i2p_peers"] == ["bbb.b32.i2p"]
def test_transform_skips_rnode():
rows = [{"id": 1, "type": "rnode", "host": "x", "port": 1}]
assert transform_directory_rows(rows) == []
def test_transform_tcp_row_with_numeric_id():
rows = [
{
"id": 207,
"name": "Public TCP",
"type": "tcp",
"typeName": "TCPClientInterface",
"host": "10.0.0.1",
"port": 4242,
}
]
out = transform_directory_rows(rows)
assert len(out) == 1
assert out[0]["type"] == "TCPClientInterface"
assert out[0]["target_host"] == "10.0.0.1"
def test_transform_tcp_with_backbone_in_config_and_identity():
rows = [
{
"id": 10,
"name": "Hybrid",
"type": "tcp",
"typeName": "TCPClientInterface",
"host": "10.0.0.1",
"port": 4242,
"transportId": "a" * 32,
"config": "BackboneInterface",
},
]
out = transform_directory_rows(rows)
assert out[0]["type"] == "BackboneInterface"
@settings(max_examples=80)
@given(
st.lists(
st.fixed_dictionaries(
{
"id": st.one_of(
st.none(), st.integers(min_value=-1000, max_value=10000)
),
"name": st.text(max_size=40),
"type": st.sampled_from(["backbone", "tcp", "i2p", "rnode", ""]),
"typeName": st.sampled_from(
["BackboneInterface", "TCPClientInterface", "I2PInterface", ""],
),
"host": st.one_of(st.none(), st.text(max_size=30)),
"address": st.one_of(st.none(), st.text(max_size=20)),
"port": st.one_of(
st.none(),
st.integers(min_value=0, max_value=65535),
st.text(max_size=8),
),
"transportId": st.one_of(
st.none(),
st.text(alphabet="0123456789abcdef", min_size=0, max_size=32),
),
"config": st.one_of(st.none(), st.text(max_size=120)),
},
),
max_size=20,
),
)
def test_transform_directory_rows_fuzz(rows):
out = transform_directory_rows(rows)
assert isinstance(out, list)
for item in out:
assert isinstance(item, dict)
assert "type" in item
assert item["type"] in (
"BackboneInterface",
"TCPClientInterface",
"I2PInterface",
)
@@ -0,0 +1,34 @@
from meshchatx.src.backend.meshchat_utils import (
hex_identifier_to_bytes,
normalize_hex_identifier,
)
def test_normalize_hex_identifier_strips_uuid_separators():
u = "BA7F0E59-FC70-4E77-9438-FA83A090F74A"
assert normalize_hex_identifier(u) == "ba7f0e59fc704e779438fa83a090f74a"
def test_normalize_hex_identifier_strips_colons_and_spaces():
assert normalize_hex_identifier("AB: CD : EF") == "abcdef"
def test_hex_identifier_to_bytes_uuid_style():
u = "ba7f0e59-fc70-4e77-9438-fa83a090f74a"
b = hex_identifier_to_bytes(u)
assert b is not None
assert len(b) == 16
def test_hex_identifier_to_bytes_standard_hash():
h = "a" * 64
b = hex_identifier_to_bytes(h)
assert b is not None
assert len(b) == 32
def test_hex_identifier_to_bytes_invalid_returns_none():
assert hex_identifier_to_bytes("not-hex") is None
assert hex_identifier_to_bytes("") is None
assert hex_identifier_to_bytes(None) is None
assert hex_identifier_to_bytes("abc") is None
+71
View File
@@ -0,0 +1,71 @@
import { mount } from "@vue/test-utils";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import ArchivesPage from "@/components/archives/ArchivesPage.vue";
describe("ArchivesPage.vue", () => {
let createObjectURLSpy;
let revokeObjectURLSpy;
beforeEach(() => {
createObjectURLSpy = vi.spyOn(URL, "createObjectURL").mockReturnValue("blob:mock");
revokeObjectURLSpy = vi.spyOn(URL, "revokeObjectURL").mockImplementation(() => {});
});
afterEach(() => {
createObjectURLSpy.mockRestore();
revokeObjectURLSpy.mockRestore();
});
const mountPage = () =>
mount(ArchivesPage, {
global: {
mocks: {
$t: (key, params) => {
if (key === "archives.export_selected_mu") return `Export .mu (${params.count})`;
if (key === "archives.export_mu") return "Export .mu";
return key;
},
},
stubs: {
MaterialDesignIcon: true,
ArchiveSidebar: true,
},
},
});
it("muExportFilename uses .mu extension from page path", () => {
const wrapper = mountPage();
expect(
wrapper.vm.muExportFilename({
page_path: "/node/page.mu",
hash: "abcdef",
})
).toBe("page.mu");
expect(
wrapper.vm.muExportFilename({
page_path: "/readme.txt",
hash: "abcdef",
})
).toBe("readme.mu");
});
it("muExportFilenameDisambiguated appends hash prefix", () => {
const wrapper = mountPage();
expect(
wrapper.vm.muExportFilenameDisambiguated({
page_path: "/a.mu",
hash: "1234567890ab",
})
).toBe("a_12345678.mu");
});
it("downloadTextAsFile creates a blob URL and revokes it", () => {
const wrapper = mountPage();
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(() => {});
wrapper.vm.downloadTextAsFile("hello", "test.mu");
expect(createObjectURLSpy).toHaveBeenCalled();
expect(clickSpy).toHaveBeenCalled();
expect(revokeObjectURLSpy).toHaveBeenCalledWith("blob:mock");
clickSpy.mockRestore();
});
});
+19
View File
@@ -84,6 +84,25 @@ describe("ConversationViewer.vue", () => {
});
};
it("onMessagePaste adds images from clipboard and prevents default", async () => {
const wrapper = mountConversationViewer();
const file = new File([""], "clip.png", { type: "image/png" });
const items = [
{
kind: "file",
type: "image/png",
getAsFile: () => file,
},
];
const event = {
preventDefault: vi.fn(),
clipboardData: { items },
};
wrapper.vm.onMessagePaste(event);
expect(event.preventDefault).toHaveBeenCalled();
expect(wrapper.vm.newMessageImages).toHaveLength(1);
});
it("adds multiple images and renders previews", async () => {
const wrapper = mountConversationViewer();