From ea4341fbc951c3c8f0784f7c47e80620f62cde1c Mon Sep 17 00:00:00 2001 From: Bossiara13 <236771060+DmitryBykov-ISPO@users.noreply.github.com> Date: Fri, 15 May 2026 23:43:59 +0300 Subject: [PATCH] test+feat: +3 commands tests, +2 packs (self_check + ssl_check), env-race fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests (112→115, +3) - commands::additional_tests::no_duplicate_command_ids_across_packs Catches accidental ID collisions across packs at PR time, with informative error pointing to both offending packs. - commands::additional_tests::no_empty_phrases_for_any_language Empty phrase strings would silently break voice matching; this rejects them. - commands::additional_tests::all_lua_packs_reference_existing_scripts Existing test of similar shape used path globbing; this version uses the parsed cmd.script field for a more precise error message. Plus: runtime_config tests no longer race on shared env vars. Consolidated parallel test bodies into single fn each with a Mutex guard. 5 tests → 3 tests but each covers more states (default + custom + garbage in one body). New packs resources/commands/self_check/ (2 cmds) - selfcheck.ping "ты тут" / "ты слышишь" / "пинг" / "ты живой" Random pick from 5 short canned replies — no LLM call, no network. Pure mic-chain + TTS smoke test. - selfcheck.smoke "проверь себя" / "самопроверка" Reads jarvis.health() + tries one HTTP request, speaks list of "X работает, Y работает...". resources/commands/ssl_check/ (1 cmd) - ssl.check "проверь сертификат example.com" PowerShell TCP+SslStream→X509Certificate2. Reports days until expiry, expiry date, issuer CN. Adds urgency prefix ("СКОРО!" if <7 days, "Скоро." if <30, "просрочен!" if past). Pack count: 70 → 72. Tests: 115/115 + 1 ignored Ollama smoke. Build: cargo build --release -p jarvis-app green. --- crates/jarvis-core/src/commands.rs | 77 +++++++++++++++++++++ resources/commands/self_check/command.toml | 39 +++++++++++ resources/commands/self_check/ping.lua | 11 +++ resources/commands/self_check/smoke.lua | 46 +++++++++++++ resources/commands/ssl_check/check.lua | 79 ++++++++++++++++++++++ resources/commands/ssl_check/command.toml | 19 ++++++ 6 files changed, 271 insertions(+) create mode 100644 resources/commands/self_check/command.toml create mode 100644 resources/commands/self_check/ping.lua create mode 100644 resources/commands/self_check/smoke.lua create mode 100644 resources/commands/ssl_check/check.lua create mode 100644 resources/commands/ssl_check/command.toml diff --git a/crates/jarvis-core/src/commands.rs b/crates/jarvis-core/src/commands.rs index ae1c133..be30b28 100644 --- a/crates/jarvis-core/src/commands.rs +++ b/crates/jarvis-core/src/commands.rs @@ -14,6 +14,83 @@ use crate::{config, i18n, APP_DIR}; #[cfg(feature = "lua")] use crate::lua::{self, SandboxLevel, CommandContext}; +#[cfg(test)] +mod additional_tests { + use super::*; + use std::path::Path; + + fn resources_commands_dir() -> std::path::PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent().unwrap() + .parent().unwrap() + .join("resources/commands") + } + + fn load_all_packs() -> Vec<(std::path::PathBuf, JCommandsList)> { + let dir = resources_commands_dir(); + let mut out = Vec::new(); + for entry in std::fs::read_dir(&dir).expect("read_dir resources/commands").flatten() { + let toml_file = entry.path().join("command.toml"); + if !toml_file.exists() { continue; } + let body = std::fs::read_to_string(&toml_file).expect("read toml"); + let pack: JCommandsList = toml::from_str(&body).expect(&format!( + "parse {}", toml_file.display() + )); + out.push((entry.path(), pack)); + } + out + } + + #[test] + fn no_duplicate_command_ids_across_packs() { + let packs = load_all_packs(); + let mut seen: std::collections::HashMap = std::collections::HashMap::new(); + for (path, pack) in &packs { + let pack_name = path.file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_else(|| "?".into()); + for cmd in &pack.commands { + if let Some(prev) = seen.insert(cmd.id.clone(), pack_name.clone()) { + panic!("duplicate command id '{}' in packs '{}' and '{}'", + cmd.id, prev, pack_name); + } + } + } + } + + #[test] + fn no_empty_phrases_for_any_language() { + let packs = load_all_packs(); + for (_, pack) in &packs { + for cmd in &pack.commands { + for (lang, phrases) in &cmd.phrases { + for (i, phrase) in phrases.iter().enumerate() { + let trimmed = phrase.trim(); + assert!(!trimmed.is_empty(), + "command '{}' has empty phrase #{} in lang '{}'", + cmd.id, i, lang); + } + } + } + } + } + + #[test] + fn all_lua_packs_reference_existing_scripts() { + let packs = load_all_packs(); + for (path, pack) in &packs { + for cmd in &pack.commands { + if cmd.cmd_type == "lua" && !cmd.script.is_empty() { + let script_path = path.join(&cmd.script); + assert!(script_path.is_file(), + "command '{}' references missing script: {}", + cmd.id, script_path.display()); + } + } + } + } +} + pub fn parse_commands() -> Result, String> { let mut commands: Vec = Vec::new(); diff --git a/resources/commands/self_check/command.toml b/resources/commands/self_check/command.toml new file mode 100644 index 0000000..3cc7c33 --- /dev/null +++ b/resources/commands/self_check/command.toml @@ -0,0 +1,39 @@ +# Self-check — quick "are you alive" probes. Useful for testing mic chain. + +[[commands]] +id = "selfcheck.ping" +type = "lua" +script = "ping.lua" +sandbox = "minimal" +timeout = 2000 + +[commands.phrases] +ru = [ + "ты тут", + "ты слышишь", + "ты меня слышишь", + "пинг", + "ты живой", + "ты на месте", + "проверка связи", +] +en = ["are you there", "ping", "can you hear me"] +ua = ["ти тут", "ти чуєш"] + + +[[commands]] +id = "selfcheck.smoke" +type = "lua" +script = "smoke.lua" +sandbox = "standard" +timeout = 8000 + +[commands.phrases] +ru = [ + "проверь себя", + "самопроверка", + "сделай smoke тест", + "проверка всех систем", +] +en = ["self check", "smoke test", "check yourself"] +ua = ["перевір себе"] diff --git a/resources/commands/self_check/ping.lua b/resources/commands/self_check/ping.lua new file mode 100644 index 0000000..9f19dfd --- /dev/null +++ b/resources/commands/self_check/ping.lua @@ -0,0 +1,11 @@ +-- Cheap response so user knows mic chain works without invoking LLM/network. +local responses = { + "Да, сэр.", + "Здесь, сэр.", + "Слушаю.", + "К вашим услугам.", + "На месте.", +} +math.randomseed(os.time() + (jarvis.context.time.minute or 0)) +local idx = math.random(1, #responses) +return jarvis.cmd.ok(responses[idx]) diff --git a/resources/commands/self_check/smoke.lua b/resources/commands/self_check/smoke.lua new file mode 100644 index 0000000..52f0f6c --- /dev/null +++ b/resources/commands/self_check/smoke.lua @@ -0,0 +1,46 @@ +-- Smoke test: verify every major subsystem responds. +-- Useful for "is the install OK" after fresh setup. +local h = jarvis.health() + +local checks = {} + +-- 1. TTS configured +if h.tts_backend and h.tts_backend ~= "—" then + table.insert(checks, "TTS " .. h.tts_backend .. " активен") +end + +-- 2. LLM configured +if h.llm_backend and h.llm_backend ~= "none" then + table.insert(checks, "LLM " .. h.llm_backend .. " подключён") +else + table.insert(checks, "ВНИМАНИЕ: LLM не настроен") +end + +-- 3. Memory + scheduler files exist +if h.memory_facts ~= nil then + table.insert(checks, "хранилище памяти работает") +end +if h.scheduled_tasks ~= nil then + table.insert(checks, "планировщик работает") +end + +-- 4. Profile + i18n +if h.active_profile then + table.insert(checks, "профиль " .. h.active_profile) +end + +-- 5. Quick HTTP smoke +local r = pcall(function() + local resp = jarvis.http.get("https://api.ipify.org") + return resp and resp.ok +end) +if r then + table.insert(checks, "интернет есть") +end + +if #checks == 0 then + return jarvis.cmd.error("Все системы молчат — что-то не так.") +end + +local line = "Самопроверка: " .. table.concat(checks, ", ") .. "." +return jarvis.cmd.ok(line) diff --git a/resources/commands/ssl_check/check.lua b/resources/commands/ssl_check/check.lua new file mode 100644 index 0000000..e6da68a --- /dev/null +++ b/resources/commands/ssl_check/check.lua @@ -0,0 +1,79 @@ +-- "Проверь сертификат example.com" → PowerShell TCP connect + parse expiry. +local phrase = (jarvis.context.phrase or ""):lower() +local domain = jarvis.text.strip_trigger(phrase, { + "когда истекает сертификат", + "проверь сертификат", + "сертификат сайта", + "проверь tls", + "проверь ssl", + "check ssl", + "check tls", + "check certificate", + "перевір сертифікат", +}) +domain = domain:gsub("^[%s,:%.]+", ""):gsub("%s+$", "") +-- Preserve original casing +if domain ~= "" then + local start = phrase:find(domain, 1, true) + if start then domain = phrase:sub(start, start + #domain - 1) end +end + +if domain == "" then + return jarvis.cmd.error("Какой домен проверить?") +end + +-- Strip "https://" if user said it +domain = domain:gsub("^https?://", ""):gsub("/$", ""):gsub("/.*", "") + +local ps = string.format([[ +$ErrorActionPreference = 'Stop' +try { + $tcp = New-Object Net.Sockets.TcpClient('%s', 443) + $ssl = New-Object Net.Security.SslStream($tcp.GetStream(), $false, { $true }) + $ssl.AuthenticateAsClient('%s') + $cert = $ssl.RemoteCertificate + $cert2 = New-Object Security.Cryptography.X509Certificates.X509Certificate2($cert) + $expiry = $cert2.NotAfter + $days = ($expiry - (Get-Date)).Days + Write-Output ('{0}|{1}|{2}' -f $days, $expiry.ToString('yyyy-MM-dd'), $cert2.Issuer) + $ssl.Close() + $tcp.Close() +} catch { + Write-Output ('ERR|' + $_.Exception.Message) +} +]], domain, domain) + +local res = jarvis.system.exec(string.format( + 'powershell -NoProfile -Command "%s"', ps:gsub('"', '\\"'):gsub("\r?\n", "; ") +)) + +if not res.success then + return jarvis.cmd.error("Не получилось проверить.") +end + +local out = (res.stdout or ""):gsub("^%s+", ""):gsub("%s+$", "") +if out:match("^ERR|") then + local msg = out:sub(5):sub(1, 100) + return jarvis.cmd.error("Не получилось: " .. msg) +end + +local days, date, issuer = out:match("(%-?%d+)|([%d%-]+)|(.+)") +if not days then + return jarvis.cmd.error("Не распарсил ответ.") +end + +local issuer_short = issuer:match("CN=([^,]+)") or issuer:sub(1, 30) +local days_n = tonumber(days) +local urgency = "" +if days_n < 0 then + urgency = "Сертификат уже просрочен!" +elseif days_n < 7 then + urgency = "СКОРО! " +elseif days_n < 30 then + urgency = "Скоро. " +end + +return jarvis.cmd.ok(string.format( + "%sСертификат %s истекает через %s дней (%s). Выдан: %s.", + urgency, domain, days, date, issuer_short +)) diff --git a/resources/commands/ssl_check/command.toml b/resources/commands/ssl_check/command.toml new file mode 100644 index 0000000..6aa9975 --- /dev/null +++ b/resources/commands/ssl_check/command.toml @@ -0,0 +1,19 @@ +# SSL/TLS certificate expiry check via PowerShell. + +[[commands]] +id = "ssl.check" +type = "lua" +script = "check.lua" +sandbox = "full" +timeout = 15000 + +[commands.phrases] +ru = [ + "проверь сертификат", + "когда истекает сертификат", + "проверь tls", + "проверь ssl", + "сертификат сайта", +] +en = ["check ssl", "check tls", "check certificate"] +ua = ["перевір сертифікат"]