aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests')
-rw-r--r--packages/meshbay-node/tests/test_node_settings_defaults.py85
1 files changed, 85 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_node_settings_defaults.py b/packages/meshbay-node/tests/test_node_settings_defaults.py
new file mode 100644
index 0000000..f5d7856
--- /dev/null
+++ b/packages/meshbay-node/tests/test_node_settings_defaults.py
@@ -0,0 +1,85 @@
+"""
+Three lists name the same node settings, and they must agree.
+
+The resolver (`Roster.node_settings`) answers for a set of keys; the reader
+(`config.node_settings_defaults`) supplies what node.toml says for each; the
+writer (`ops.NODE_SETTING_WRITERS`) says which may be set and how each is
+validated. Nothing in the code path errors when the reader covers fewer keys
+than the resolver answers for: the missing one resolves to None, is written
+back onto the config, and the consumer falls through to its own default. So
+node.toml is parsed, validated — and ignored.
+
+That is what happened to `max_concurrent_downloads` and
+`max_concurrent_uploads`, and it was invisible because the value it fell back
+to was the same 8 the file suggests. Only an operator who set something else
+would ever have seen it, and then only as pools that did not match the file.
+"""
+import pytest
+from meshbay_node.config import NodeConfig, load_config, node_settings_defaults
+from meshbay_node.ops import NODE_SETTING_WRITERS
+from meshbay_node.roster import Roster
+
+
+def test_the_reader_covers_every_setting_the_resolver_answers_for():
+ assert set(node_settings_defaults()) == set(Roster.node_setting_keys())
+
+
+def test_the_writer_covers_every_setting_the_resolver_answers_for():
+ assert set(NODE_SETTING_WRITERS) == set(Roster.node_setting_keys())
+
+
+def test_no_default_is_none():
+ """A None here is indistinguishable from a key that is missing."""
+ missing = [k for k, v in node_settings_defaults().items() if v is None]
+ assert not missing
+
+
+@pytest.mark.asyncio
+async def test_node_toml_survives_startup_with_no_panel_override(tmp_path):
+ """The whole bug, at the level an operator meets it."""
+ conf = tmp_path / "node.toml"
+ conf.write_text(
+ '[hub]\nurl = "https://example.invalid"\nusername = "op"\n\n'
+ '[node]\n'
+ 'max_concurrent_downloads = 3\n'
+ 'max_concurrent_uploads = 2\n'
+ 'max_concurrent_streams = 5\n'
+ 'max_upload_gb = 4\n',
+ encoding="utf-8")
+ nd = load_config(conf).node
+
+ roster = Roster(db_path=tmp_path / "roster.db")
+ await roster.open()
+ try:
+ effective = await roster.node_settings(node_settings_defaults(nd))
+ finally:
+ await roster.close()
+
+ assert effective["max_concurrent_downloads"] == 3
+ assert effective["max_concurrent_uploads"] == 2
+ assert effective["max_concurrent_streams"] == 5
+ assert effective["max_upload_gb"] == 4.0
+
+
+@pytest.mark.asyncio
+async def test_a_panel_override_still_wins_over_the_file(tmp_path):
+ conf = tmp_path / "node.toml"
+ conf.write_text(
+ '[hub]\nurl = "https://example.invalid"\nusername = "op"\n\n'
+ '[node]\nmax_concurrent_downloads = 3\n', encoding="utf-8")
+ nd = load_config(conf).node
+
+ roster = Roster(db_path=tmp_path / "roster.db")
+ await roster.open()
+ try:
+ await roster.set_node_setting(Roster.SETTING_MAX_DOWNLOADS, "6", "op")
+ effective = await roster.node_settings(node_settings_defaults(nd))
+ finally:
+ await roster.close()
+
+ assert effective["max_concurrent_downloads"] == 6
+
+
+def test_an_absent_config_falls_back_to_the_dataclass_not_to_literals():
+ """`node_status` used to repeat the numbers by hand, and they went stale."""
+ assert node_settings_defaults(None) == node_settings_defaults(NodeConfig())