Rebased lane/daemon onto main first — fast-forward, no conflicts (lane/daemon was already fully merged; main has since taken lane/gui, lane/pkg-qa, lane/proto). 1. capture.offer and capture.getRules were stubs, buried inside D3's generic stub list with no flag for how load-bearing capture.offer is: it's a CLAUDE.md §4 non-negotiable (750ms deadline, fails open) and AGENT-DAEMON.md build step 6, and against the real daemon it 500s to -32603 every time — the extension captures nothing, not "falls through to Firefox on a slow path." Split into their own D7/D8 rows so they stop being invisible. 2. settings.get/settings.set (D9, closed). Four pointer-to-member tables in dispatcher.cpp (bool / ranged int / plain string / string array), five enum-typed keys handled individually since parse_XXX already validates those — covers all 43 SettingKeys without ~40 repetitive hand blocks. store::Settings::kDefaults grew from 14 entries to 43 (the schema itself carries no "default" keyword anywhere, so these are hand-chosen — conservative for the ones ADR 0012 doesn't speak to; capture.monitoredExtensions defaults to the union of every builtin category's extensions rather than an arbitrary list of its own). settings.set validates every field before writing any of them: numeric min/max (hand-checked — the generated parser only checks JSON type, not schema constraints) and saveTo.* paths via fs::resolve_target/canonicalize_root (-32011) — allowedRoots entries checked as roots in their own right, defaultDir/tempDir checked as paths resolving inside the (possibly just-updated, same call) root list. Reports exactly the keys whose effective value actually changed, publishes event.settings.changed with that list, and calls the scheduler's reload_config() when a connection.* key took effect — live, not on next restart. Verified against real veloxd: all 43 keys round-trip with defaults, a keys subset filters, an out-of-range value rejects the whole call, a bad saveTo.defaultDir is -32011, allowedRoots+defaultDir set together cross-validate against the new roots, event.settings.changed fires over a live subscription. New dispatcher_settings_test covers the same ground without a socket. Full ctest: 53/53 (excluding a pre-existing, unrelated conformance failure — see below). Also found, not fixed (not this lane): tests/conformance/run.sh's live-veloxd leg fails "pairing failed: no token issued" reproducibly, isolated, on main before any of this session's changes — it launches the isolated veloxd without VELOX_PAIR_AUTO=1, so EnvAutoApprover denies every session.pair and the WS leg's session.hello never gets a token. tests/conformance/ is PROTO/QA-owned; flagging rather than editing across the lane boundary. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GRDjHGgpYmMoPE2UFbe7pP
143 lines
5.6 KiB
C++
143 lines
5.6 KiB
C++
// settings.get / settings.set: the field <-> SettingKey <-> JSON-type mapping table in
|
|
// dispatcher.cpp, called directly (no socket) against an in-memory store.
|
|
|
|
#include <string>
|
|
|
|
#include "check.hpp"
|
|
#include "rpc/dispatcher.hpp"
|
|
#include "rpc/event_hub.hpp"
|
|
#include "store/migrations.hpp"
|
|
#include "store/sqlite.hpp"
|
|
#include "velox_proto.hpp"
|
|
|
|
using namespace velox::daemon;
|
|
namespace proto = velox::proto;
|
|
|
|
void run() {
|
|
auto db = store::Db::open(":memory:");
|
|
CHECK(db.has_value());
|
|
if (!db) return;
|
|
CHECK(store::migrate_to_head(*db).has_value());
|
|
|
|
rpc::EventHub hub;
|
|
rpc::VeloxDispatcher dispatcher(*db, hub);
|
|
|
|
// --- get with keys=nullopt: every one of the 43 SettingKeys comes back -----------
|
|
{
|
|
proto::SettingsGetParams p;
|
|
auto r = dispatcher.on_settings_get(p);
|
|
CHECK(r.has_value());
|
|
if (r) {
|
|
int present = 0;
|
|
present += r->values.general_launchOnLogin.has_value();
|
|
present += r->values.capture_enabled.has_value();
|
|
present += r->values.saveTo_allowedRoots.has_value();
|
|
present += r->values.connection_maxConcurrentDownloads.has_value();
|
|
present += r->values.proxy_mode.has_value();
|
|
present += r->values.sounds_onError.has_value();
|
|
CHECK_EQ(present, 6);
|
|
// A default, sight-checked: connection.maxConcurrentDownloads is 5 (settings.cpp).
|
|
CHECK_EQ(r->values.connection_maxConcurrentDownloads.value_or(-1), std::int64_t{5});
|
|
CHECK(r->values.saveTo_allowedRoots.has_value());
|
|
if (r->values.saveTo_allowedRoots)
|
|
CHECK_EQ(r->values.saveTo_allowedRoots->size(), std::size_t{1});
|
|
// Enum-typed default round-trips through parse_XXX correctly.
|
|
CHECK(r->values.proxy_mode == proto::SettingsProxyMode::System);
|
|
}
|
|
}
|
|
|
|
// --- get with an explicit key list: only those fields are populated --------------
|
|
{
|
|
proto::SettingsGetParams p;
|
|
p.keys = {proto::SettingKey::GeneralLaunchOnLogin,
|
|
proto::SettingKey::ConnectionMaxConcurrentDownloads};
|
|
auto r = dispatcher.on_settings_get(p);
|
|
CHECK(r.has_value());
|
|
if (r) {
|
|
CHECK(r->values.general_launchOnLogin.has_value());
|
|
CHECK(r->values.connection_maxConcurrentDownloads.has_value());
|
|
CHECK(!r->values.capture_enabled.has_value());
|
|
CHECK(!r->values.proxy_mode.has_value());
|
|
}
|
|
}
|
|
|
|
// --- set: a valid change is persisted, reported in `changed`, and readable back ---
|
|
{
|
|
proto::SettingsSetParams p;
|
|
p.values.connection_maxConcurrentDownloads = 12;
|
|
p.values.general_launchOnLogin = true;
|
|
auto r = dispatcher.on_settings_set(p);
|
|
CHECK(r.has_value());
|
|
if (r) {
|
|
CHECK_EQ(r->changed.size(), std::size_t{2});
|
|
CHECK_EQ(r->values.connection_maxConcurrentDownloads.value_or(-1), std::int64_t{12});
|
|
CHECK_EQ(r->values.general_launchOnLogin.value_or(false), true);
|
|
}
|
|
|
|
proto::SettingsGetParams g;
|
|
g.keys = {proto::SettingKey::ConnectionMaxConcurrentDownloads};
|
|
auto g_r = dispatcher.on_settings_get(g);
|
|
CHECK(g_r.has_value());
|
|
if (g_r) CHECK_EQ(g_r->values.connection_maxConcurrentDownloads.value_or(-1), std::int64_t{12});
|
|
}
|
|
|
|
// --- set: setting the same value again reports it unchanged ----------------------
|
|
{
|
|
proto::SettingsSetParams p;
|
|
p.values.connection_maxConcurrentDownloads = 12;
|
|
auto r = dispatcher.on_settings_set(p);
|
|
CHECK(r.has_value());
|
|
if (r) CHECK(r->changed.empty());
|
|
}
|
|
|
|
// --- set: out of range is rejected, and nothing else in the same call lands ------
|
|
{
|
|
proto::SettingsSetParams p;
|
|
p.values.connection_maxConcurrentDownloads = 999; // max 64
|
|
p.values.general_minimizeToTray = true; // would otherwise succeed
|
|
auto r = dispatcher.on_settings_set(p);
|
|
CHECK(!r.has_value());
|
|
if (!r) CHECK(r.error().code == proto::ErrorCode::InvalidParams);
|
|
|
|
proto::SettingsGetParams g;
|
|
g.keys = {proto::SettingKey::GeneralMinimizeToTray};
|
|
auto g_r = dispatcher.on_settings_get(g);
|
|
CHECK(g_r.has_value());
|
|
// Rejected as a whole: minimizeToTray was never written even though its own value
|
|
// was valid on its own.
|
|
if (g_r) CHECK_EQ(g_r->values.general_minimizeToTray.value_or(true), false);
|
|
}
|
|
|
|
// --- set: an enum field round-trips ------------------------------------------------
|
|
{
|
|
proto::SettingsSetParams p;
|
|
p.values.proxy_mode = proto::SettingsProxyMode::Socks5;
|
|
auto r = dispatcher.on_settings_set(p);
|
|
CHECK(r.has_value());
|
|
if (r) {
|
|
CHECK_EQ(r->changed.size(), std::size_t{1});
|
|
CHECK(r->values.proxy_mode == proto::SettingsProxyMode::Socks5);
|
|
}
|
|
}
|
|
|
|
// --- set: saveTo.defaultDir outside every allowed root is rejected with -32011 ----
|
|
{
|
|
proto::SettingsSetParams p;
|
|
p.values.saveTo_defaultDir = "/definitely/not/an/allowed/root";
|
|
auto r = dispatcher.on_settings_set(p);
|
|
CHECK(!r.has_value());
|
|
if (!r) CHECK(r.error().code == proto::ErrorCode::InvalidPath);
|
|
}
|
|
|
|
// --- set: saveTo.allowedRoots with an unresolvable entry is rejected with -32011 --
|
|
{
|
|
proto::SettingsSetParams p;
|
|
p.values.saveTo_allowedRoots = {"/this/path/should/never/exist/anywhere"};
|
|
auto r = dispatcher.on_settings_set(p);
|
|
CHECK(!r.has_value());
|
|
if (!r) CHECK(r.error().code == proto::ErrorCode::InvalidPath);
|
|
}
|
|
}
|
|
|
|
TEST_MAIN()
|