2026-03-14 19:18:10 -07:00
|
|
|
"""Tests for hermes_cli.cron command handling."""
|
|
|
|
|
|
|
|
|
|
from argparse import Namespace
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from cron.jobs import create_job, get_job, list_jobs
|
|
|
|
|
from hermes_cli.cron import cron_command
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture()
|
|
|
|
|
def tmp_cron_dir(tmp_path, monkeypatch):
|
|
|
|
|
monkeypatch.setattr("cron.jobs.CRON_DIR", tmp_path / "cron")
|
|
|
|
|
monkeypatch.setattr("cron.jobs.JOBS_FILE", tmp_path / "cron" / "jobs.json")
|
|
|
|
|
monkeypatch.setattr("cron.jobs.OUTPUT_DIR", tmp_path / "cron" / "output")
|
|
|
|
|
return tmp_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestCronCommandLifecycle:
|
|
|
|
|
def test_pause_resume_run(self, tmp_cron_dir, capsys):
|
|
|
|
|
job = create_job(prompt="Check server status", schedule="every 1h")
|
|
|
|
|
|
|
|
|
|
cron_command(Namespace(cron_command="pause", job_id=job["id"]))
|
|
|
|
|
paused = get_job(job["id"])
|
|
|
|
|
assert paused["state"] == "paused"
|
|
|
|
|
|
|
|
|
|
cron_command(Namespace(cron_command="resume", job_id=job["id"]))
|
|
|
|
|
resumed = get_job(job["id"])
|
|
|
|
|
assert resumed["state"] == "scheduled"
|
|
|
|
|
|
|
|
|
|
cron_command(Namespace(cron_command="run", job_id=job["id"]))
|
|
|
|
|
triggered = get_job(job["id"])
|
|
|
|
|
assert triggered["state"] == "scheduled"
|
|
|
|
|
|
|
|
|
|
out = capsys.readouterr().out
|
|
|
|
|
assert "Paused job" in out
|
|
|
|
|
assert "Resumed job" in out
|
|
|
|
|
assert "Triggered job" in out
|
|
|
|
|
|
|
|
|
|
def test_edit_can_replace_and_clear_skills(self, tmp_cron_dir, capsys):
|
|
|
|
|
job = create_job(
|
|
|
|
|
prompt="Combine skill outputs",
|
|
|
|
|
schedule="every 1h",
|
|
|
|
|
skill="blogwatcher",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
cron_command(
|
|
|
|
|
Namespace(
|
|
|
|
|
cron_command="edit",
|
|
|
|
|
job_id=job["id"],
|
|
|
|
|
schedule="every 2h",
|
|
|
|
|
prompt="Revised prompt",
|
|
|
|
|
name="Edited Job",
|
|
|
|
|
deliver=None,
|
|
|
|
|
repeat=None,
|
|
|
|
|
skill=None,
|
feat(skills): consolidate find-nearby into maps as a single location skill
find-nearby and the (new) maps optional skill both used OpenStreetMap's
Overpass + Nominatim to answer the same question — 'what's near this
location?' — so shipping both would be duplicate code for overlapping
capability. Consolidate into one active-by-default skill at
skills/productivity/maps/ that is a strict superset of find-nearby.
Moves + deletions:
- optional-skills/productivity/maps/ → skills/productivity/maps/ (active,
no install step needed)
- skills/leisure/find-nearby/ → DELETED (fully superseded)
Upgrades to maps_client.py so it covers everything find-nearby did:
- Overpass server failover — tries overpass-api.de then
overpass.kumi.systems so a single-mirror outage doesn't break the skill
(new overpass_query helper, used by both nearby and bbox)
- nearby now accepts --near "<address>" as a shortcut that auto-geocodes,
so one command replaces the old 'search → copy coords → nearby' chain
- nearby now accepts --category (repeatable) for multi-type queries in
one call (e.g. --category restaurant --category bar), results merged
and deduped by (osm_type, osm_id), sorted by distance, capped at --limit
- Each nearby result now includes maps_url (clickable Google Maps search
link) and directions_url (Google Maps directions from the search point
— only when a ref point is known)
- Promoted commonly-useful OSM tags to top-level fields on each result:
cuisine, hours (opening_hours), phone, website — instead of forcing
callers to dig into the raw tags dict
SKILL.md:
- Version bumped 1.1.0 → 1.2.0, description rewritten to lead with
capability surface
- New 'Working With Telegram Location Pins' section replacing
find-nearby's equivalent workflow
- metadata.hermes.supersedes: [find-nearby] so tooling can flag any
lingering references to the old skill
External references updated:
- optional-skills/productivity/telephony/SKILL.md — related_skills
find-nearby → maps
- website/docs/reference/skills-catalog.md — removed the (now-empty)
'leisure' section, added 'maps' row under productivity
- website/docs/user-guide/features/cron.md — find-nearby example
usages swapped to maps
- tests/tools/test_cronjob_tools.py, tests/hermes_cli/test_cron.py,
tests/cron/test_scheduler.py — fixture string values swapped
- cli.py:5290 — /cron help-hint example swapped
Not touched:
- RELEASE_v0.2.0.md — historical record, left intact
E2E-verified live (Nominatim + Overpass, one query each):
- nearby --near "Times Square" --category restaurant --category bar → 3 results,
sorted by distance, all with maps_url, directions_url, cuisine, phone, website
where OSM had the tags
All 111 targeted tests pass across tests/cron/, tests/tools/, tests/hermes_cli/.
2026-04-19 05:17:39 -07:00
|
|
|
skills=["maps", "blogwatcher"],
|
2026-03-14 19:18:10 -07:00
|
|
|
clear_skills=False,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
updated = get_job(job["id"])
|
feat(skills): consolidate find-nearby into maps as a single location skill
find-nearby and the (new) maps optional skill both used OpenStreetMap's
Overpass + Nominatim to answer the same question — 'what's near this
location?' — so shipping both would be duplicate code for overlapping
capability. Consolidate into one active-by-default skill at
skills/productivity/maps/ that is a strict superset of find-nearby.
Moves + deletions:
- optional-skills/productivity/maps/ → skills/productivity/maps/ (active,
no install step needed)
- skills/leisure/find-nearby/ → DELETED (fully superseded)
Upgrades to maps_client.py so it covers everything find-nearby did:
- Overpass server failover — tries overpass-api.de then
overpass.kumi.systems so a single-mirror outage doesn't break the skill
(new overpass_query helper, used by both nearby and bbox)
- nearby now accepts --near "<address>" as a shortcut that auto-geocodes,
so one command replaces the old 'search → copy coords → nearby' chain
- nearby now accepts --category (repeatable) for multi-type queries in
one call (e.g. --category restaurant --category bar), results merged
and deduped by (osm_type, osm_id), sorted by distance, capped at --limit
- Each nearby result now includes maps_url (clickable Google Maps search
link) and directions_url (Google Maps directions from the search point
— only when a ref point is known)
- Promoted commonly-useful OSM tags to top-level fields on each result:
cuisine, hours (opening_hours), phone, website — instead of forcing
callers to dig into the raw tags dict
SKILL.md:
- Version bumped 1.1.0 → 1.2.0, description rewritten to lead with
capability surface
- New 'Working With Telegram Location Pins' section replacing
find-nearby's equivalent workflow
- metadata.hermes.supersedes: [find-nearby] so tooling can flag any
lingering references to the old skill
External references updated:
- optional-skills/productivity/telephony/SKILL.md — related_skills
find-nearby → maps
- website/docs/reference/skills-catalog.md — removed the (now-empty)
'leisure' section, added 'maps' row under productivity
- website/docs/user-guide/features/cron.md — find-nearby example
usages swapped to maps
- tests/tools/test_cronjob_tools.py, tests/hermes_cli/test_cron.py,
tests/cron/test_scheduler.py — fixture string values swapped
- cli.py:5290 — /cron help-hint example swapped
Not touched:
- RELEASE_v0.2.0.md — historical record, left intact
E2E-verified live (Nominatim + Overpass, one query each):
- nearby --near "Times Square" --category restaurant --category bar → 3 results,
sorted by distance, all with maps_url, directions_url, cuisine, phone, website
where OSM had the tags
All 111 targeted tests pass across tests/cron/, tests/tools/, tests/hermes_cli/.
2026-04-19 05:17:39 -07:00
|
|
|
assert updated["skills"] == ["maps", "blogwatcher"]
|
2026-03-14 19:18:10 -07:00
|
|
|
assert updated["name"] == "Edited Job"
|
|
|
|
|
assert updated["prompt"] == "Revised prompt"
|
|
|
|
|
assert updated["schedule_display"] == "every 120m"
|
|
|
|
|
|
|
|
|
|
cron_command(
|
|
|
|
|
Namespace(
|
|
|
|
|
cron_command="edit",
|
|
|
|
|
job_id=job["id"],
|
|
|
|
|
schedule=None,
|
|
|
|
|
prompt=None,
|
|
|
|
|
name=None,
|
|
|
|
|
deliver=None,
|
|
|
|
|
repeat=None,
|
|
|
|
|
skill=None,
|
|
|
|
|
skills=None,
|
|
|
|
|
clear_skills=True,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
cleared = get_job(job["id"])
|
|
|
|
|
assert cleared["skills"] == []
|
|
|
|
|
assert cleared["skill"] is None
|
|
|
|
|
|
|
|
|
|
out = capsys.readouterr().out
|
|
|
|
|
assert "Updated job" in out
|
|
|
|
|
|
|
|
|
|
def test_create_with_multiple_skills(self, tmp_cron_dir, capsys):
|
|
|
|
|
cron_command(
|
|
|
|
|
Namespace(
|
|
|
|
|
cron_command="create",
|
|
|
|
|
schedule="every 1h",
|
|
|
|
|
prompt="Use both skills",
|
|
|
|
|
name="Skill combo",
|
|
|
|
|
deliver=None,
|
|
|
|
|
repeat=None,
|
|
|
|
|
skill=None,
|
feat(skills): consolidate find-nearby into maps as a single location skill
find-nearby and the (new) maps optional skill both used OpenStreetMap's
Overpass + Nominatim to answer the same question — 'what's near this
location?' — so shipping both would be duplicate code for overlapping
capability. Consolidate into one active-by-default skill at
skills/productivity/maps/ that is a strict superset of find-nearby.
Moves + deletions:
- optional-skills/productivity/maps/ → skills/productivity/maps/ (active,
no install step needed)
- skills/leisure/find-nearby/ → DELETED (fully superseded)
Upgrades to maps_client.py so it covers everything find-nearby did:
- Overpass server failover — tries overpass-api.de then
overpass.kumi.systems so a single-mirror outage doesn't break the skill
(new overpass_query helper, used by both nearby and bbox)
- nearby now accepts --near "<address>" as a shortcut that auto-geocodes,
so one command replaces the old 'search → copy coords → nearby' chain
- nearby now accepts --category (repeatable) for multi-type queries in
one call (e.g. --category restaurant --category bar), results merged
and deduped by (osm_type, osm_id), sorted by distance, capped at --limit
- Each nearby result now includes maps_url (clickable Google Maps search
link) and directions_url (Google Maps directions from the search point
— only when a ref point is known)
- Promoted commonly-useful OSM tags to top-level fields on each result:
cuisine, hours (opening_hours), phone, website — instead of forcing
callers to dig into the raw tags dict
SKILL.md:
- Version bumped 1.1.0 → 1.2.0, description rewritten to lead with
capability surface
- New 'Working With Telegram Location Pins' section replacing
find-nearby's equivalent workflow
- metadata.hermes.supersedes: [find-nearby] so tooling can flag any
lingering references to the old skill
External references updated:
- optional-skills/productivity/telephony/SKILL.md — related_skills
find-nearby → maps
- website/docs/reference/skills-catalog.md — removed the (now-empty)
'leisure' section, added 'maps' row under productivity
- website/docs/user-guide/features/cron.md — find-nearby example
usages swapped to maps
- tests/tools/test_cronjob_tools.py, tests/hermes_cli/test_cron.py,
tests/cron/test_scheduler.py — fixture string values swapped
- cli.py:5290 — /cron help-hint example swapped
Not touched:
- RELEASE_v0.2.0.md — historical record, left intact
E2E-verified live (Nominatim + Overpass, one query each):
- nearby --near "Times Square" --category restaurant --category bar → 3 results,
sorted by distance, all with maps_url, directions_url, cuisine, phone, website
where OSM had the tags
All 111 targeted tests pass across tests/cron/, tests/tools/, tests/hermes_cli/.
2026-04-19 05:17:39 -07:00
|
|
|
skills=["blogwatcher", "maps"],
|
2026-03-14 19:18:10 -07:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
out = capsys.readouterr().out
|
|
|
|
|
assert "Created job" in out
|
|
|
|
|
|
|
|
|
|
jobs = list_jobs()
|
|
|
|
|
assert len(jobs) == 1
|
feat(skills): consolidate find-nearby into maps as a single location skill
find-nearby and the (new) maps optional skill both used OpenStreetMap's
Overpass + Nominatim to answer the same question — 'what's near this
location?' — so shipping both would be duplicate code for overlapping
capability. Consolidate into one active-by-default skill at
skills/productivity/maps/ that is a strict superset of find-nearby.
Moves + deletions:
- optional-skills/productivity/maps/ → skills/productivity/maps/ (active,
no install step needed)
- skills/leisure/find-nearby/ → DELETED (fully superseded)
Upgrades to maps_client.py so it covers everything find-nearby did:
- Overpass server failover — tries overpass-api.de then
overpass.kumi.systems so a single-mirror outage doesn't break the skill
(new overpass_query helper, used by both nearby and bbox)
- nearby now accepts --near "<address>" as a shortcut that auto-geocodes,
so one command replaces the old 'search → copy coords → nearby' chain
- nearby now accepts --category (repeatable) for multi-type queries in
one call (e.g. --category restaurant --category bar), results merged
and deduped by (osm_type, osm_id), sorted by distance, capped at --limit
- Each nearby result now includes maps_url (clickable Google Maps search
link) and directions_url (Google Maps directions from the search point
— only when a ref point is known)
- Promoted commonly-useful OSM tags to top-level fields on each result:
cuisine, hours (opening_hours), phone, website — instead of forcing
callers to dig into the raw tags dict
SKILL.md:
- Version bumped 1.1.0 → 1.2.0, description rewritten to lead with
capability surface
- New 'Working With Telegram Location Pins' section replacing
find-nearby's equivalent workflow
- metadata.hermes.supersedes: [find-nearby] so tooling can flag any
lingering references to the old skill
External references updated:
- optional-skills/productivity/telephony/SKILL.md — related_skills
find-nearby → maps
- website/docs/reference/skills-catalog.md — removed the (now-empty)
'leisure' section, added 'maps' row under productivity
- website/docs/user-guide/features/cron.md — find-nearby example
usages swapped to maps
- tests/tools/test_cronjob_tools.py, tests/hermes_cli/test_cron.py,
tests/cron/test_scheduler.py — fixture string values swapped
- cli.py:5290 — /cron help-hint example swapped
Not touched:
- RELEASE_v0.2.0.md — historical record, left intact
E2E-verified live (Nominatim + Overpass, one query each):
- nearby --near "Times Square" --category restaurant --category bar → 3 results,
sorted by distance, all with maps_url, directions_url, cuisine, phone, website
where OSM had the tags
All 111 targeted tests pass across tests/cron/, tests/tools/, tests/hermes_cli/.
2026-04-19 05:17:39 -07:00
|
|
|
assert jobs[0]["skills"] == ["blogwatcher", "maps"]
|
2026-03-14 19:18:10 -07:00
|
|
|
assert jobs[0]["name"] == "Skill combo"
|